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