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