use new syntax for connecting to backend signals that enforces explicit connection...
[ardour.git] / gtk2_ardour / port_matrix_body.cc
1 /*
2     Copyright (C) 2002-2009 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <iostream>
21 #include "ardour/bundle.h"
22 #include "ardour/types.h"
23 #include "port_matrix_body.h"
24 #include "port_matrix.h"
25 #include "port_matrix_column_labels.h"
26 #include "port_matrix_row_labels.h"
27 #include "port_matrix_grid.h"
28
29 using namespace std;
30
31 PortMatrixBody::PortMatrixBody (PortMatrix* p)
32         : _matrix (p),
33           _alloc_width (0),
34           _alloc_height (0),
35           _xoffset (0),
36           _yoffset (0),
37           _column_labels_border_x (0),
38           _column_labels_height (0),
39           _ignore_component_size_changed (false)
40 {
41         _column_labels = new PortMatrixColumnLabels (p, this);
42         _row_labels = new PortMatrixRowLabels (p, this);
43         _grid = new PortMatrixGrid (p, this);
44
45         _components.push_back (_column_labels);
46         _components.push_back (_row_labels);
47         _components.push_back (_grid);
48
49         add_events (Gdk::LEAVE_NOTIFY_MASK | Gdk::POINTER_MOTION_MASK);
50 }
51
52
53 PortMatrixBody::~PortMatrixBody ()
54 {
55         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
56                 delete *i;
57         }
58 }
59
60 bool
61 PortMatrixBody::on_expose_event (GdkEventExpose* event)
62 {
63         if (
64                 _matrix->visible_columns() == 0 || _matrix->visible_rows() == 0 ||
65                 _matrix->visible_columns()->bundles().empty() || _matrix->visible_rows()->bundles().empty()
66                 ) {
67
68                 /* nothing to connect */
69
70                 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
71
72                 cairo_set_source_rgb (cr, 0, 0, 0);
73                 cairo_rectangle (cr, 0, 0, _alloc_width, _alloc_height);
74                 cairo_fill (cr);
75
76                 stringstream t;
77                 t << _("There are no ") << (_matrix->type() == ARDOUR::DataType::AUDIO ? _("audio") : _("MIDI")) << _(" ports to connect.");
78
79                 cairo_text_extents_t ext;
80                 cairo_text_extents (cr, t.str().c_str(), &ext);
81
82                 cairo_set_source_rgb (cr, 1, 1, 1);
83                 cairo_move_to (cr, (_alloc_width - ext.width) / 2, (_alloc_height + ext.height) / 2);
84                 cairo_show_text (cr, t.str().c_str ());
85
86                 cairo_destroy (cr);
87
88                 return true;
89         }
90
91         Gdk::Rectangle const exposure (
92                 event->area.x, event->area.y, event->area.width, event->area.height
93                 );
94
95         bool intersects;
96
97         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
98
99                 Gdk::Rectangle r = exposure;
100
101                 /* the get_pixmap call may cause things to be rerendered and sizes to change,
102                    so fetch the pixmap before calculating where to put it */
103                 GdkPixmap* p = (*i)->get_pixmap (get_window()->gobj());
104                 r.intersect ((*i)->parent_rectangle(), intersects);
105
106                 if (intersects) {
107                         
108                         gdk_draw_drawable (
109                                 get_window()->gobj(),
110                                 get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
111                                 p,
112                                 (*i)->parent_to_component_x (r.get_x()),
113                                 (*i)->parent_to_component_y (r.get_y()),
114                                 r.get_x(),
115                                 r.get_y(),
116                                 r.get_width(),
117                                 r.get_height()
118                                 );
119                 }
120
121         }
122
123         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
124
125         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
126                 cairo_save (cr);
127                 set_cairo_clip (cr, (*i)->parent_rectangle ());
128                 (*i)->draw_extra (cr);
129                 cairo_restore (cr);
130         }
131
132         cairo_destroy (cr);
133
134         return true;
135 }
136
137 void
138 PortMatrixBody::on_size_request (Gtk::Requisition *req)
139 {
140         pair<int, int> const col = _column_labels->dimensions ();
141         pair<int, int> const row = _row_labels->dimensions ();
142         pair<int, int> const grid = _grid->dimensions ();
143
144         if (grid.first == 0 && grid.second == 0) {
145                 /* nothing to display */
146                 req->width = 256;
147                 req->height = 64;
148                 return;
149         }
150
151         /* don't ask for the maximum size of our contents, otherwise GTK won't
152            let the containing window shrink below this size */
153
154         /* XXX these shouldn't be hard-coded */
155         int const min_width = 512;
156         int const min_height = 512;
157
158         req->width = min (min_width, max (col.first, grid.first + row.first));
159         req->height = min (min_height / _matrix->min_height_divisor(), col.second + grid.second);
160 }
161
162 void
163 PortMatrixBody::on_size_allocate (Gtk::Allocation& alloc)
164 {
165         Gtk::EventBox::on_size_allocate (alloc);
166
167         _alloc_width = alloc.get_width ();
168         _alloc_height = alloc.get_height ();
169
170         compute_rectangles ();
171         _matrix->setup_scrollbars ();
172 }
173
174 void
175 PortMatrixBody::compute_rectangles ()
176 {
177         /* full sizes of components */
178         pair<uint32_t, uint32_t> const col = _column_labels->dimensions ();
179         uint32_t const col_overhang = _column_labels->overhang ();
180         pair<uint32_t, uint32_t> const row = _row_labels->dimensions ();
181         pair<uint32_t, uint32_t> const grid = _grid->dimensions ();
182
183         Gdk::Rectangle col_rect;
184         Gdk::Rectangle row_rect;
185         Gdk::Rectangle grid_rect;
186
187         if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
188
189                 col_rect.set_x (0);
190                 _column_labels_border_x = col_overhang;
191                 col_rect.set_y (0);
192                 grid_rect.set_x (0);
193
194                 col_rect.set_width (min (col.first, _alloc_width));
195
196                 uint32_t const y = min (_alloc_height, col.second);
197                 col_rect.set_height (y);
198                 row_rect.set_y (y);
199                 row_rect.set_height (_alloc_height - y);
200                 grid_rect.set_y (y);
201                 grid_rect.set_height (_alloc_height - y);
202
203                 uint32_t x = 0;
204                 if (_alloc_width > (grid.first + row.first)) {
205                         x = grid.first;
206                 } else if (_alloc_width > row.first) {
207                         x = _alloc_width - row.first;
208                 }
209
210                 grid_rect.set_width (x);
211                 row_rect.set_x (x);
212                 row_rect.set_width (_alloc_width - x);
213
214
215         } else if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
216
217                 col_rect.set_height (min (_alloc_height, col.second));
218
219                 row_rect.set_x (0);
220                 row_rect.set_y (0);
221                 row_rect.set_width (min (_alloc_width, row.first));
222                 row_rect.set_height (std::min (_alloc_height - col_rect.get_height(), row.second));
223
224                 grid_rect.set_x (row_rect.get_width());
225                 grid_rect.set_y (0);
226                 grid_rect.set_width (std::min (_alloc_width - row_rect.get_width(), grid.first));
227                 grid_rect.set_height (row_rect.get_height ());
228
229                 col_rect.set_width (grid_rect.get_width () + col_overhang);
230                 col_rect.set_x (row_rect.get_width() + grid_rect.get_width() - col_rect.get_width());
231                 _column_labels_border_x = col_rect.get_x () >= 0 ? col_rect.get_x () : 0;
232                 col_rect.set_y (row_rect.get_height());
233         }
234
235         _column_labels_height = col_rect.get_height ();
236
237         _row_labels->set_parent_rectangle (row_rect);
238         _column_labels->set_parent_rectangle (col_rect);
239         _grid->set_parent_rectangle (grid_rect);
240
241         DimensionsChanged (); /* EMIT SIGNAL */
242 }
243
244 void
245 PortMatrixBody::setup ()
246 {
247         /* Discard any old connections to bundles */
248
249         _bundle_connections.drop_connections ();
250
251         /* Connect to bundles so that we find out when their names change */
252
253         if (_matrix->visible_rows()) {
254                 PortGroup::BundleList r = _matrix->visible_rows()->bundles ();
255                 for (PortGroup::BundleList::iterator i = r.begin(); i != r.end(); ++i) {
256                         
257                         (*i)->bundle->Changed.connect (_bundle_connections, boost::bind (&PortMatrixBody::rebuild_and_draw_row_labels, this));
258                         
259                 }
260         }
261         
262         if (_matrix->visible_columns()) {
263                 PortGroup::BundleList c = _matrix->visible_columns()->bundles ();
264                 for (PortGroup::BundleList::iterator i = c.begin(); i != c.end(); ++i) {
265                         (*i)->bundle->Changed.connect (_bundle_connections, boost::bind (&PortMatrixBody::rebuild_and_draw_column_labels, this));
266                 }
267         }
268                 
269         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
270                 (*i)->setup ();
271         }
272
273         set_mouseover (PortMatrixNode ());
274         
275         _ignore_component_size_changed = true;
276         compute_rectangles ();
277         _ignore_component_size_changed = false;
278 }
279
280 uint32_t
281 PortMatrixBody::full_scroll_width ()
282 {
283         return _grid->dimensions().first;
284
285 }
286
287 uint32_t
288 PortMatrixBody::alloc_scroll_width ()
289 {
290         return _grid->parent_rectangle().get_width();
291 }
292
293 uint32_t
294 PortMatrixBody::full_scroll_height ()
295 {
296         return _grid->dimensions().second;
297 }
298
299 uint32_t
300 PortMatrixBody::alloc_scroll_height ()
301 {
302         return _grid->parent_rectangle().get_height();
303 }
304
305 /** Set x offset (for scrolling) */
306 void
307 PortMatrixBody::set_xoffset (uint32_t xo)
308 {
309         _xoffset = xo;
310         queue_draw ();
311 }
312
313 /** Set y offset (for scrolling) */
314 void
315 PortMatrixBody::set_yoffset (uint32_t yo)
316 {
317         _yoffset = yo;
318         queue_draw ();
319 }
320
321 bool
322 PortMatrixBody::on_button_press_event (GdkEventButton* ev)
323 {
324         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
325                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
326                         (*i)->button_press (
327                                 (*i)->parent_to_component_x (ev->x),
328                                 (*i)->parent_to_component_y (ev->y),
329                                 ev->button, ev->time, ev->state
330                                 );
331                 }
332         }
333
334         return true;
335 }
336
337 bool
338 PortMatrixBody::on_button_release_event (GdkEventButton* ev)
339 {
340         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
341                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
342                         (*i)->button_release (
343                                 (*i)->parent_to_component_x (ev->x),
344                                 (*i)->parent_to_component_y (ev->y),
345                                 ev->button, ev->time, ev->state
346                                 );
347                 } else {
348                         (*i)->button_release (
349                                 -1, -1,
350                                 ev->button, ev->time, ev->state
351                                 );
352                 }
353         }
354
355         return true;
356 }
357
358 void
359 PortMatrixBody::rebuild_and_draw_grid ()
360 {
361         _grid->require_rebuild ();
362         queue_draw ();
363 }
364
365 void
366 PortMatrixBody::rebuild_and_draw_column_labels ()
367 {
368         _column_labels->require_rebuild ();
369         queue_draw ();
370 }
371
372 void
373 PortMatrixBody::rebuild_and_draw_row_labels ()
374 {
375         _row_labels->require_rebuild ();
376         queue_draw ();
377 }
378
379 bool
380 PortMatrixBody::on_leave_notify_event (GdkEventCrossing* ev)
381 {
382         if (ev->type == GDK_LEAVE_NOTIFY) {
383                 set_mouseover (PortMatrixNode ());
384         }
385
386         return true;
387 }
388
389 bool
390 PortMatrixBody::on_motion_notify_event (GdkEventMotion* ev)
391 {
392         bool done = false;
393         
394         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
395                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
396                         (*i)->motion (
397                                 (*i)->parent_to_component_x (ev->x),
398                                 (*i)->parent_to_component_y (ev->y)
399                                 );
400
401                         done = true;
402                 }
403         }
404                         
405
406         if (!done) {
407                 set_mouseover (PortMatrixNode ());
408         }
409
410         return true;
411 }
412
413 void
414 PortMatrixBody::set_mouseover (PortMatrixNode const & n)
415 {
416         list<PortMatrixNode> m;
417         m.push_back (n);
418         set_mouseover (m);
419 }
420
421 void
422 PortMatrixBody::set_mouseover (list<PortMatrixNode> const & n)
423 {
424         if (n == _mouseover) {
425                 return;
426         }
427
428         /* Channel highlights are set up only on mouseovers, so
429            it's reasonable to remove all channel highlights here.
430            We can't let individual components clear their own highlights
431            because of the case where, say, the row labels set up some column
432            highlights, and then we ask the column labels to set up their
433            own highlights and they clear them out before they start.
434         */
435
436         _row_labels->clear_channel_highlights ();
437         _column_labels->clear_channel_highlights ();
438
439         list<PortMatrixNode> old = _mouseover;
440         _mouseover = n;
441         
442         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
443                 (*i)->mouseover_changed (old);
444         }
445 }
446
447 void
448 PortMatrixBody::highlight_associated_channels (int dim, ARDOUR::BundleChannel h)
449 {
450         ARDOUR::BundleChannel bc[2];
451         bc[dim] = h;
452
453         if (!bc[dim].bundle) {
454                 return;
455         }
456
457         if (dim == _matrix->column_index()) {
458                 _column_labels->add_channel_highlight (bc[dim]);
459         } else {
460                 _row_labels->add_channel_highlight (bc[dim]);
461         }
462
463         PortGroup::BundleList const b = _matrix->visible_ports(1 - dim)->bundles ();
464
465         for (PortGroup::BundleList::const_iterator i = b.begin(); i != b.end(); ++i) {
466                 for (uint32_t j = 0; j < (*i)->bundle->nchannels(); ++j) {
467                         bc[1 - dim] = ARDOUR::BundleChannel ((*i)->bundle, j);
468                         if (_matrix->get_state (bc) == PortMatrixNode::ASSOCIATED) {
469                                 if (dim == _matrix->column_index()) {
470                                         _row_labels->add_channel_highlight (bc[1 - dim]);
471                                 } else {
472                                         _column_labels->add_channel_highlight (bc[1 - dim]);
473                                 }
474                         }
475                 }
476         }
477 }
478
479 void
480 PortMatrixBody::set_cairo_clip (cairo_t* cr, Gdk::Rectangle const & r) const
481 {
482         cairo_rectangle (cr, r.get_x(), r.get_y(), r.get_width(), r.get_height());
483         cairo_clip (cr);
484 }
485
486 void
487 PortMatrixBody::component_size_changed ()
488 {
489         if (_ignore_component_size_changed) {
490                 return;
491         }
492         
493         compute_rectangles ();
494         _matrix->setup_scrollbars ();
495 }
496
497 pair<uint32_t, uint32_t>
498 PortMatrixBody::max_size () const
499 {
500         pair<uint32_t, uint32_t> const col = _column_labels->dimensions ();
501         pair<uint32_t, uint32_t> const row = _row_labels->dimensions ();
502         pair<uint32_t, uint32_t> const grid = _grid->dimensions ();
503
504         return make_pair (std::max (row.first, _column_labels->overhang()) + grid.first, col.second + grid.second);
505 }
506
507 /** @return x position at which the column labels meet the border of the matrix */
508 uint32_t
509 PortMatrixBody::column_labels_border_x () const
510 {
511         return _column_labels_border_x;
512 }
513
514 uint32_t
515 PortMatrixBody::column_labels_height () const
516 {
517         return _column_labels_height;
518 }