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