Redraw the matrix correctly when available ports change in the currently visible...
[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         for (list<sigc::connection>::iterator i = _bundle_connections.begin(); i != _bundle_connections.end(); ++i) {
250                 i->disconnect ();
251         }
252         _bundle_connections.clear ();
253
254         /* Connect to bundles so that we find out when their names change */
255
256         if (_matrix->visible_rows()) {
257                 PortGroup::BundleList r = _matrix->visible_rows()->bundles ();
258                 for (PortGroup::BundleList::iterator i = r.begin(); i != r.end(); ++i) {
259                         
260                         _bundle_connections.push_back (
261                                 i->bundle->Changed.connect (sigc::hide (sigc::mem_fun (*this, &PortMatrixBody::rebuild_and_draw_row_labels)))
262                                 );
263                         
264                 }
265         }
266         
267         if (_matrix->visible_columns()) {
268                 PortGroup::BundleList c = _matrix->visible_columns()->bundles ();
269                 for (PortGroup::BundleList::iterator i = c.begin(); i != c.end(); ++i) {
270                         _bundle_connections.push_back (
271                                 i->bundle->Changed.connect (sigc::hide (sigc::mem_fun (*this, &PortMatrixBody::rebuild_and_draw_column_labels)))
272                                 );
273                 }
274         }
275                 
276         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
277                 (*i)->setup ();
278         }
279
280         set_mouseover (PortMatrixNode ());
281         
282         _ignore_component_size_changed = true;
283         compute_rectangles ();
284         _ignore_component_size_changed = false;
285 }
286
287 uint32_t
288 PortMatrixBody::full_scroll_width ()
289 {
290         return _grid->dimensions().first;
291
292 }
293
294 uint32_t
295 PortMatrixBody::alloc_scroll_width ()
296 {
297         return _grid->parent_rectangle().get_width();
298 }
299
300 uint32_t
301 PortMatrixBody::full_scroll_height ()
302 {
303         return _grid->dimensions().second;
304 }
305
306 uint32_t
307 PortMatrixBody::alloc_scroll_height ()
308 {
309         return _grid->parent_rectangle().get_height();
310 }
311
312 /** Set x offset (for scrolling) */
313 void
314 PortMatrixBody::set_xoffset (uint32_t xo)
315 {
316         _xoffset = xo;
317         queue_draw ();
318 }
319
320 /** Set y offset (for scrolling) */
321 void
322 PortMatrixBody::set_yoffset (uint32_t yo)
323 {
324         _yoffset = yo;
325         queue_draw ();
326 }
327
328 bool
329 PortMatrixBody::on_button_press_event (GdkEventButton* ev)
330 {
331         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
332                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
333                         (*i)->button_press (
334                                 (*i)->parent_to_component_x (ev->x),
335                                 (*i)->parent_to_component_y (ev->y),
336                                 ev->button, ev->time, ev->state
337                                 );
338                 }
339         }
340
341         return true;
342 }
343
344 bool
345 PortMatrixBody::on_button_release_event (GdkEventButton* ev)
346 {
347         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
348                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
349                         (*i)->button_release (
350                                 (*i)->parent_to_component_x (ev->x),
351                                 (*i)->parent_to_component_y (ev->y),
352                                 ev->button, ev->time, ev->state
353                                 );
354                 } else {
355                         (*i)->button_release (
356                                 -1, -1,
357                                 ev->button, ev->time, ev->state
358                                 );
359                 }
360         }
361
362         return true;
363 }
364
365 void
366 PortMatrixBody::rebuild_and_draw_grid ()
367 {
368         _grid->require_rebuild ();
369         queue_draw ();
370 }
371
372 void
373 PortMatrixBody::rebuild_and_draw_column_labels ()
374 {
375         _column_labels->require_rebuild ();
376         queue_draw ();
377 }
378
379 void
380 PortMatrixBody::rebuild_and_draw_row_labels ()
381 {
382         _row_labels->require_rebuild ();
383         queue_draw ();
384 }
385
386 bool
387 PortMatrixBody::on_leave_notify_event (GdkEventCrossing* ev)
388 {
389         if (ev->type == GDK_LEAVE_NOTIFY) {
390                 set_mouseover (PortMatrixNode ());
391         }
392
393         return true;
394 }
395
396 bool
397 PortMatrixBody::on_motion_notify_event (GdkEventMotion* ev)
398 {
399         bool done = false;
400         
401         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
402                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
403                         (*i)->motion (
404                                 (*i)->parent_to_component_x (ev->x),
405                                 (*i)->parent_to_component_y (ev->y)
406                                 );
407
408                         done = true;
409                 }
410         }
411                         
412
413         if (!done) {
414                 set_mouseover (PortMatrixNode ());
415         }
416
417         return true;
418 }
419
420 void
421 PortMatrixBody::set_mouseover (PortMatrixNode const & n)
422 {
423         list<PortMatrixNode> m;
424         m.push_back (n);
425         set_mouseover (m);
426 }
427
428 void
429 PortMatrixBody::set_mouseover (list<PortMatrixNode> const & n)
430 {
431         if (n == _mouseover) {
432                 return;
433         }
434
435         /* Channel highlights are set up only on mouseovers, so
436            it's reasonable to remove all channel highlights here.
437            We can't let individual components clear their own highlights
438            because of the case where, say, the row labels set up some column
439            highlights, and then we ask the column labels to set up their
440            own highlights and they clear them out before they start.
441         */
442
443         _row_labels->clear_channel_highlights ();
444         _column_labels->clear_channel_highlights ();
445
446         list<PortMatrixNode> old = _mouseover;
447         _mouseover = n;
448         
449         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
450                 (*i)->mouseover_changed (old);
451         }
452 }
453
454 void
455 PortMatrixBody::highlight_associated_channels (int dim, ARDOUR::BundleChannel h)
456 {
457         ARDOUR::BundleChannel bc[2];
458         bc[dim] = h;
459
460         if (!bc[dim].bundle) {
461                 return;
462         }
463
464         if (dim == _matrix->column_index()) {
465                 _column_labels->add_channel_highlight (bc[dim]);
466         } else {
467                 _row_labels->add_channel_highlight (bc[dim]);
468         }
469
470         PortGroup::BundleList const b = _matrix->visible_ports(1 - dim)->bundles ();
471
472         for (PortGroup::BundleList::const_iterator i = b.begin(); i != b.end(); ++i) {
473                 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
474                         bc[1 - dim] = ARDOUR::BundleChannel (i->bundle, j);
475                         if (_matrix->get_state (bc) == PortMatrixNode::ASSOCIATED) {
476                                 if (dim == _matrix->column_index()) {
477                                         _row_labels->add_channel_highlight (bc[1 - dim]);
478                                 } else {
479                                         _column_labels->add_channel_highlight (bc[1 - dim]);
480                                 }
481                         }
482                 }
483         }
484 }
485
486 void
487 PortMatrixBody::set_cairo_clip (cairo_t* cr, Gdk::Rectangle const & r) const
488 {
489         cairo_rectangle (cr, r.get_x(), r.get_y(), r.get_width(), r.get_height());
490         cairo_clip (cr);
491 }
492
493 void
494 PortMatrixBody::component_size_changed ()
495 {
496         if (_ignore_component_size_changed) {
497                 return;
498         }
499         
500         compute_rectangles ();
501         _matrix->setup_scrollbars ();
502 }
503
504 pair<uint32_t, uint32_t>
505 PortMatrixBody::max_size () const
506 {
507         pair<uint32_t, uint32_t> const col = _column_labels->dimensions ();
508         pair<uint32_t, uint32_t> const row = _row_labels->dimensions ();
509         pair<uint32_t, uint32_t> const grid = _grid->dimensions ();
510
511         return make_pair (std::max (row.first, _column_labels->overhang()) + grid.first, col.second + grid.second);
512 }
513
514 /** @return x position at which the column labels meet the border of the matrix */
515 uint32_t
516 PortMatrixBody::column_labels_border_x () const
517 {
518         return _column_labels_border_x;
519 }
520
521 uint32_t
522 PortMatrixBody::column_labels_height () const
523 {
524         return _column_labels_height;
525 }