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
PortMatrixBody(PortMatrix * p)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
~PortMatrixBody()59 PortMatrixBody::~PortMatrixBody ()
60 {
61 for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
62 delete *i;
63 }
64 }
65
66 bool
on_expose_event(GdkEventExpose * event)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
on_size_request(Gtk::Requisition * req)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
on_size_allocate(Gtk::Allocation & alloc)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
compute_rectangles()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
setup()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
full_scroll_width()293 PortMatrixBody::full_scroll_width ()
294 {
295 return _grid->dimensions().first;
296
297 }
298
299 uint32_t
alloc_scroll_width()300 PortMatrixBody::alloc_scroll_width ()
301 {
302 return _grid->parent_rectangle().get_width();
303 }
304
305 uint32_t
full_scroll_height()306 PortMatrixBody::full_scroll_height ()
307 {
308 return _grid->dimensions().second;
309 }
310
311 uint32_t
alloc_scroll_height()312 PortMatrixBody::alloc_scroll_height ()
313 {
314 return _grid->parent_rectangle().get_height();
315 }
316
317 /** Set x offset (for scrolling) */
318 void
set_xoffset(uint32_t xo)319 PortMatrixBody::set_xoffset (uint32_t xo)
320 {
321 _xoffset = xo;
322 queue_draw ();
323 }
324
325 /** Set y offset (for scrolling) */
326 void
set_yoffset(uint32_t yo)327 PortMatrixBody::set_yoffset (uint32_t yo)
328 {
329 _yoffset = yo;
330 queue_draw ();
331 }
332
333 bool
on_button_press_event(GdkEventButton * ev)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
on_button_release_event(GdkEventButton * ev)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
rebuild_and_draw_grid()371 PortMatrixBody::rebuild_and_draw_grid ()
372 {
373 _grid->require_rebuild ();
374 queue_draw ();
375 }
376
377 void
rebuild_and_draw_column_labels()378 PortMatrixBody::rebuild_and_draw_column_labels ()
379 {
380 _column_labels->require_rebuild ();
381 queue_draw ();
382 }
383
384 void
rebuild_and_draw_row_labels()385 PortMatrixBody::rebuild_and_draw_row_labels ()
386 {
387 _row_labels->require_rebuild ();
388 queue_draw ();
389 }
390
391 bool
on_leave_notify_event(GdkEventCrossing * ev)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
on_motion_notify_event(GdkEventMotion * ev)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
set_mouseover(PortMatrixNode const & n)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
set_mouseover(list<PortMatrixNode> const & n)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
highlight_associated_channels(int dim,ARDOUR::BundleChannel h)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
set_cairo_clip(cairo_t * cr,Gdk::Rectangle const & r) const502 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
component_size_changed()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>
max_size() const520 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
column_labels_border_x() const531 PortMatrixBody::column_labels_border_x () const
532 {
533 return _column_labels_border_x;
534 }
535
536 uint32_t
column_labels_height() const537 PortMatrixBody::column_labels_height () const
538 {
539 return _column_labels_height;
540 }
541