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