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