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