1 /*
2 * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
3 * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
4 * Copyright (C) 2008-2016 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2008 Hans Baier <hansfbaier@googlemail.com>
6 * Copyright (C) 2013-2015 Nick Mainsbridge <mainsbridge@gmail.com>
7 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24 #include <iostream>
25
26 #include <gtkmm/scrolledwindow.h>
27 #include <gtkmm/adjustment.h>
28 #include <gtkmm/label.h>
29 #include <gtkmm/menu.h>
30 #include <gtkmm/menushell.h>
31 #include <gtkmm/menu_elems.h>
32 #include <gtkmm/window.h>
33 #include <gtkmm/stock.h>
34
35 #include "ardour/bundle.h"
36 #include "ardour/types.h"
37 #include "ardour/session.h"
38 #include "ardour/route.h"
39 #include "ardour/audioengine.h"
40
41 #include "gtkmm2ext/utils.h"
42
43 #include "ardour_dialog.h"
44 #include "ardour_message.h"
45 #include "gui_thread.h"
46 #include "port_matrix.h"
47 #include "port_matrix_body.h"
48 #include "port_matrix_component.h"
49 #include "utils.h"
50 #include "ui_config.h"
51
52 #include "pbd/i18n.h"
53
54 using namespace std;
55 using namespace Gtk;
56 using namespace ARDOUR;
57 using namespace ARDOUR_UI_UTILS;
58
59 /** PortMatrix constructor.
60 * @param session Our session.
61 * @param type Port type that we are handling.
62 */
PortMatrix(Window * parent,Session * session,DataType type)63 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
64 : Table (4, 4)
65 , _parent (parent)
66 , _type (type)
67 , _menu (0)
68 , _arrangement (TOP_TO_RIGHT)
69 , _row_index (0)
70 , _column_index (1)
71 , _min_height_divisor (1)
72 , _show_only_bundles (false)
73 , _inhibit_toggle_show_only_bundles (false)
74 , _ignore_notebook_page_selected (false)
75 {
76 set_session (session);
77
78 _body = new PortMatrixBody (this);
79 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
80
81 _hbox.pack_end (_hspacer, true, true);
82 _hbox.pack_end (_hnotebook, false, false);
83 _hbox.pack_end (_hlabel, false, false);
84
85 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
86 _vnotebook.property_tab_border() = 4;
87 _vnotebook.set_name (X_("PortMatrixLabel"));
88 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
89 _hnotebook.property_tab_border() = 4;
90 _hnotebook.set_name (X_("PortMatrixLabel"));
91
92 _vlabel.set_use_markup ();
93 _vlabel.set_alignment (1, 1);
94 _vlabel.set_padding (4, 16);
95 _vlabel.set_name (X_("PortMatrixLabel"));
96 _hlabel.set_use_markup ();
97 _hlabel.set_alignment (1, 0.5);
98 _hlabel.set_padding (16, 4);
99 _hlabel.set_name (X_("PortMatrixLabel"));
100
101 set_row_spacing (0, 8);
102 set_col_spacing (0, 8);
103 set_row_spacing (2, 8);
104 set_col_spacing (2, 8);
105
106 _body->show ();
107 _vbox.show ();
108 _hbox.show ();
109 _vscroll.show ();
110 _hscroll.show ();
111 _vlabel.show ();
112 _hlabel.show ();
113 _hspacer.show ();
114 _vspacer.show ();
115 _vnotebook.show ();
116 _hnotebook.show ();
117
118 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &PortMatrix::parameter_changed));
119 }
120
~PortMatrix()121 PortMatrix::~PortMatrix ()
122 {
123 delete _body;
124 delete _menu;
125 }
126
127 /** Perform initial and once-only setup. This must be called by
128 * subclasses after they have set up _ports[] to at least some
129 * reasonable extent. Two-part initialisation is necessary because
130 * setting up _ports is largely done by virtual functions in
131 * subclasses.
132 */
133
134 void
init()135 PortMatrix::init ()
136 {
137 select_arrangement ();
138
139 /* Signal handling is kind of split into three parts:
140 *
141 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
142 * representation of the information in _ports[].
143 *
144 * 2. When certain other things change, we need to get our subclass to clear and
145 * re-fill _ports[], which in turn causes appropriate signals to be raised to
146 * hook into part (1).
147 *
148 * 3. Assorted other signals.
149 */
150
151
152 /* Part 1: the basic _ports[] change -> reset visuals */
153
154 for (int i = 0; i < 2; ++i) {
155 /* watch for the content of _ports[] changing */
156 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
157
158 /* and for bundles in _ports[] changing */
159 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
160 }
161
162 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
163
164 /* watch for routes being added or removed */
165 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
166
167 /* and also bundles */
168 _session->BundleAddedOrRemoved.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
169
170 /* and also ports */
171 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
172
173 _session->engine().PortPrettyNameChanged.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_all_ports, this), gui_context());
174
175 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
176 PresentationInfo::Change.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
177
178 /* Part 3: other stuff */
179
180 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
181
182 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
183 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
184
185 reconnect_to_routes ();
186
187 setup ();
188 }
189
190 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
191 void
reconnect_to_routes()192 PortMatrix::reconnect_to_routes ()
193 {
194 _route_connections.drop_connections ();
195
196 boost::shared_ptr<RouteList> routes = _session->get_routes ();
197 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
198 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
199 (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
200 }
201 }
202
203 void
route_processors_changed(RouteProcessorChange c)204 PortMatrix::route_processors_changed (RouteProcessorChange c)
205 {
206 if (c.type == RouteProcessorChange::MeterPointChange) {
207 /* this change has no impact on the port matrix */
208 return;
209 }
210
211 setup_global_ports ();
212 }
213
214 /** A route has been added to or removed from the session */
215 void
routes_changed()216 PortMatrix::routes_changed ()
217 {
218 if (!_session) return;
219 reconnect_to_routes ();
220 setup_global_ports ();
221 }
222
223 /** Set up everything that depends on the content of _ports[] */
224 void
setup()225 PortMatrix::setup ()
226 {
227 if (!_session) {
228 _route_connections.drop_connections ();
229 return; // session went away
230 }
231
232 /* this needs to be done first, as the visible_ports() method uses the
233 notebook state to decide which ports are being shown */
234
235 setup_notebooks ();
236
237 _body->setup ();
238 setup_scrollbars ();
239 update_tab_highlighting ();
240 queue_draw ();
241 }
242
243 void
set_type(DataType t)244 PortMatrix::set_type (DataType t)
245 {
246 _type = t;
247 }
248
249 void
hscroll_changed()250 PortMatrix::hscroll_changed ()
251 {
252 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
253 }
254
255 void
vscroll_changed()256 PortMatrix::vscroll_changed ()
257 {
258 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
259 }
260
261 void
setup_scrollbars()262 PortMatrix::setup_scrollbars ()
263 {
264 Adjustment* a = _hscroll.get_adjustment ();
265 a->set_lower (0);
266 a->set_page_size (_body->alloc_scroll_width());
267 a->set_step_increment (32);
268 a->set_page_increment (128);
269
270 /* Set the adjustment to zero if the size has changed.*/
271 if (a->get_upper() != _body->full_scroll_width()) {
272 a->set_upper (_body->full_scroll_width());
273 a->set_value (0);
274 }
275
276 a = _vscroll.get_adjustment ();
277 a->set_lower (0);
278 a->set_page_size (_body->alloc_scroll_height());
279 a->set_step_increment (32);
280 a->set_page_increment (128);
281
282 if (a->get_upper() != _body->full_scroll_height()) {
283 a->set_upper (_body->full_scroll_height());
284 a->set_value (0);
285 }
286 }
287
288 /** Disassociate all of our ports from each other */
289 void
disassociate_all()290 PortMatrix::disassociate_all ()
291 {
292 PortGroup::BundleList a = _ports[0].bundles ();
293 PortGroup::BundleList b = _ports[1].bundles ();
294
295 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
296 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
297 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
298 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
299
300 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
301 continue;
302 }
303
304 BundleChannel c[2] = {
305 BundleChannel ((*i)->bundle, j),
306 BundleChannel ((*k)->bundle, l)
307 };
308
309 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
310 set_state (c, false);
311 }
312
313 }
314 }
315 }
316 }
317
318 _body->rebuild_and_draw_grid ();
319 }
320
321 /* Decide how to arrange the components of the matrix */
322 void
select_arrangement()323 PortMatrix::select_arrangement ()
324 {
325 uint32_t const N[2] = {
326 count_of_our_type_min_1 (_ports[0].total_channels()),
327 count_of_our_type_min_1 (_ports[1].total_channels())
328 };
329
330 /* XXX: shirley there's an easier way than this */
331
332 if (_vspacer.get_parent()) {
333 _vbox.remove (_vspacer);
334 }
335
336 if (_vnotebook.get_parent()) {
337 _vbox.remove (_vnotebook);
338 }
339
340 if (_vlabel.get_parent()) {
341 _vbox.remove (_vlabel);
342 }
343
344 /* The list with the most channels goes on left or right, so that the most channel
345 names are printed horizontally and hence more readable. However we also
346 maintain notional `signal flow' vaguely from left to right. Subclasses
347 should choose where to put ports based on signal flowing from _ports[0]
348 to _ports[1] */
349
350 if (N[0] > N[1]) {
351
352 _row_index = 0;
353 _column_index = 1;
354 _arrangement = LEFT_TO_BOTTOM;
355 _vlabel.set_label (_("<b>Sources</b>"));
356 _hlabel.set_label (_("<b>Destinations</b>"));
357 _vlabel.set_angle (90);
358
359 _vbox.pack_end (_vlabel, false, false);
360 _vbox.pack_end (_vnotebook, false, false);
361 _vbox.pack_end (_vspacer, true, true);
362
363 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
364 REMOVE_FROM_GTK_PARENT(*_body)
365 REMOVE_FROM_GTK_PARENT(_vscroll)
366 REMOVE_FROM_GTK_PARENT(_hscroll)
367 REMOVE_FROM_GTK_PARENT(_vbox)
368 REMOVE_FROM_GTK_PARENT(_hbox)
369
370 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
371 attach (_vscroll, 3, 4, 1, 2, SHRINK);
372 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
373 attach (_vbox, 1, 2, 1, 2, SHRINK);
374 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
375
376 } else {
377
378 _row_index = 1;
379 _column_index = 0;
380 _arrangement = TOP_TO_RIGHT;
381 _hlabel.set_label (_("<b>Sources</b>"));
382 _vlabel.set_label (_("<b>Destinations</b>"));
383 _vlabel.set_angle (-90);
384
385 _vbox.pack_end (_vspacer, true, true);
386 _vbox.pack_end (_vnotebook, false, false);
387 _vbox.pack_end (_vlabel, false, false);
388
389 REMOVE_FROM_GTK_PARENT(*_body)
390 REMOVE_FROM_GTK_PARENT(_vscroll)
391 REMOVE_FROM_GTK_PARENT(_hscroll)
392 REMOVE_FROM_GTK_PARENT(_vbox)
393 REMOVE_FROM_GTK_PARENT(_hbox)
394
395 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
396 attach (_vscroll, 3, 4, 2, 3, SHRINK);
397 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
398 attach (_vbox, 2, 3, 2, 3, SHRINK);
399 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
400 }
401 }
402
403 /** @return columns list */
404 PortGroupList const *
columns() const405 PortMatrix::columns () const
406 {
407 return &_ports[_column_index];
408 }
409
410 boost::shared_ptr<const PortGroup>
visible_columns() const411 PortMatrix::visible_columns () const
412 {
413 return visible_ports (_column_index);
414 }
415
416 /* @return rows list */
417 PortGroupList const *
rows() const418 PortMatrix::rows () const
419 {
420 return &_ports[_row_index];
421 }
422
423 boost::shared_ptr<const PortGroup>
visible_rows() const424 PortMatrix::visible_rows () const
425 {
426 return visible_ports (_row_index);
427 }
428
429 /** @param column Column; its bundle may be 0 if we are over a row heading.
430 * @param row Row; its bundle may be 0 if we are over a column heading.
431 */
432 void
popup_menu(BundleChannel column,BundleChannel row,uint32_t t)433 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
434 {
435 using namespace Menu_Helpers;
436
437 delete _menu;
438
439 _menu = new Menu;
440 _menu->set_name ("ArdourContextMenu");
441
442 MenuList& items = _menu->items ();
443
444 BundleChannel bc[2];
445 bc[_column_index] = column;
446 bc[_row_index] = row;
447
448 char buf [64];
449 bool need_separator = false;
450
451 for (int dim = 0; dim < 2; ++dim) {
452
453 if (bc[dim].bundle) {
454
455 Menu* m = manage (new Menu);
456 MenuList& sub = m->items ();
457
458 boost::weak_ptr<Bundle> w (bc[dim].bundle);
459
460 if (can_add_channels (bc[dim].bundle)) {
461 /* Start off with options for the `natural' port type */
462 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
463 if (should_show (*i) && can_add_port_proxy (w, *i)) {
464 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
465 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
466 }
467 }
468
469 /* Now add other ones */
470 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
471 if (!should_show (*i) && can_add_port_proxy (w, *i)) {
472 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
473 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
474 }
475 }
476 }
477
478 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
479 snprintf (
480 buf, sizeof (buf), _("Rename '%s'..."),
481 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
482 );
483 sub.push_back (
484 MenuElem (
485 buf,
486 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
487 )
488 );
489 }
490
491 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
492 if (bc[dim].channel != -1) {
493 add_remove_option (sub, w, bc[dim].channel);
494 } else {
495 sub.push_back (
496 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
497 );
498
499 if (bc[dim].bundle->nchannels().n_total() > 1) {
500 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
501 if (should_show (bc[dim].bundle->channel_type(i))) {
502 add_remove_option (sub, w, i);
503 }
504 }
505 }
506 }
507 }
508
509 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
510 if ((_show_only_bundles && c > 0) || c == 1) {
511
512 /* we're looking just at bundles, or our bundle has only one channel, so just offer
513 to disassociate all on the bundle.
514 */
515
516 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
517 sub.push_back (
518 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
519 );
520
521 } else if (c != 0) {
522
523 if (bc[dim].channel != -1) {
524 /* specific channel under the menu, so just offer to disassociate that */
525 add_disassociate_option (sub, w, dim, bc[dim].channel);
526 } else {
527 /* no specific channel; offer to disassociate all, or any one in particular */
528 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
529 sub.push_back (
530 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
531 );
532
533 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
534 if (should_show (bc[dim].bundle->channel_type(i))) {
535 add_disassociate_option (sub, w, dim, i);
536 }
537 }
538 }
539 }
540
541 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
542 need_separator = true;
543 }
544
545 }
546
547 if (need_separator) {
548 items.push_back (SeparatorElem ());
549 }
550
551 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
552
553 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
554 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
555 _inhibit_toggle_show_only_bundles = true;
556 i->set_active (!_show_only_bundles);
557 _inhibit_toggle_show_only_bundles = false;
558
559 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
560 items.back().set_sensitive (can_flip ());
561
562 _menu->popup (1, t);
563 }
564
565 void
remove_channel_proxy(boost::weak_ptr<Bundle> b,uint32_t c)566 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
567 {
568 boost::shared_ptr<Bundle> sb = b.lock ();
569 if (!sb) {
570 return;
571 }
572
573 remove_channel (BundleChannel (sb, c));
574
575 }
576
577 void
rename_channel_proxy(boost::weak_ptr<Bundle> b,uint32_t c)578 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
579 {
580 boost::shared_ptr<Bundle> sb = b.lock ();
581 if (!sb) {
582 return;
583 }
584
585 rename_channel (BundleChannel (sb, c));
586 }
587
588 void
disassociate_all_on_bundle(boost::weak_ptr<Bundle> bundle,int dim)589 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
590 {
591 boost::shared_ptr<Bundle> sb = bundle.lock ();
592 if (!sb) {
593 return;
594 }
595
596 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
597 if (should_show (sb->channel_type(i))) {
598 disassociate_all_on_channel (bundle, i, dim);
599 }
600 }
601 }
602
603 void
disassociate_all_on_channel(boost::weak_ptr<Bundle> bundle,uint32_t channel,int dim)604 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
605 {
606 boost::shared_ptr<Bundle> sb = bundle.lock ();
607 if (!sb) {
608 return;
609 }
610
611 PortGroup::BundleList a = _ports[1-dim].bundles ();
612
613 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
614 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
615
616 if (!should_show ((*i)->bundle->channel_type(j))) {
617 continue;
618 }
619
620 BundleChannel c[2];
621 c[dim] = BundleChannel (sb, channel);
622 c[1-dim] = BundleChannel ((*i)->bundle, j);
623
624 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
625 set_state (c, false);
626 }
627 }
628 }
629
630 _body->rebuild_and_draw_grid ();
631 }
632
633 void
setup_global_ports()634 PortMatrix::setup_global_ports ()
635 {
636 if (!_session || _session->deletion_in_progress()) return;
637 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
638
639 for (int i = 0; i < 2; ++i) {
640 if (list_is_global (i)) {
641 setup_ports (i);
642 }
643 }
644 }
645
646 void
setup_global_ports_proxy()647 PortMatrix::setup_global_ports_proxy ()
648 {
649 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
650 for a discussion.
651 */
652
653 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
654 }
655
656 void
setup_all_ports()657 PortMatrix::setup_all_ports ()
658 {
659 if (_session->deletion_in_progress()) {
660 return;
661 }
662
663 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
664
665 setup_ports (0);
666 setup_ports (1);
667 }
668
669 void
toggle_show_only_bundles()670 PortMatrix::toggle_show_only_bundles ()
671 {
672 if (_inhibit_toggle_show_only_bundles) {
673 return;
674 }
675
676 _show_only_bundles = !_show_only_bundles;
677
678 setup ();
679
680 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
681 setting, so we need to set things up again now.
682 */
683 setup_all_ports ();
684 }
685
686 pair<uint32_t, uint32_t>
max_size() const687 PortMatrix::max_size () const
688 {
689 pair<uint32_t, uint32_t> m = _body->max_size ();
690
691 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
692 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
693
694 return m;
695 }
696
697 bool
on_scroll_event(GdkEventScroll * ev)698 PortMatrix::on_scroll_event (GdkEventScroll* ev)
699 {
700 double const h = _hscroll.get_value ();
701 double const v = _vscroll.get_value ();
702
703 switch (ev->direction) {
704 case GDK_SCROLL_UP:
705 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
706 break;
707 case GDK_SCROLL_DOWN:
708 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
709 break;
710 case GDK_SCROLL_LEFT:
711 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
712 break;
713 case GDK_SCROLL_RIGHT:
714 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
715 break;
716 }
717
718 return true;
719 }
720
721 boost::shared_ptr<IO>
io_from_bundle(boost::shared_ptr<Bundle> b) const722 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
723 {
724 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
725 if (!io) {
726 io = _ports[1].io_from_bundle (b);
727 }
728
729 return io;
730 }
731
732 bool
can_add_channels(boost::shared_ptr<Bundle> b) const733 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
734 {
735 return io_from_bundle (b) != 0;
736 }
737
738 bool
can_add_port(boost::shared_ptr<Bundle> b,DataType t) const739 PortMatrix::can_add_port (boost::shared_ptr<Bundle> b, DataType t) const
740 {
741 boost::shared_ptr<IO> io = io_from_bundle (b);
742 return io && io->can_add_port (t);
743 }
744
745 void
add_channel(boost::shared_ptr<Bundle> b,DataType t)746 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
747 {
748 boost::shared_ptr<IO> io = io_from_bundle (b);
749
750 if (io) {
751 int const r = io->add_port ("", this, t);
752 if (r == -1) {
753 ArdourMessageDialog msg (_("It is not possible to add a port here."));
754 msg.set_title (_("Cannot add port"));
755 msg.run ();
756 }
757 }
758 }
759
760 bool
can_remove_channels(boost::shared_ptr<Bundle> b) const761 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
762 {
763 return io_from_bundle (b) != 0;
764 }
765
766 void
remove_channel(ARDOUR::BundleChannel b)767 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
768 {
769 std::string errmsg;
770 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
771 boost::shared_ptr<Port> p = io->nth (b.channel);
772
773 if (!io || !p) {
774 return;
775 }
776
777 if (io->n_ports ().n_total () == 1) {
778 errmsg = _("The last port cannot be removed");
779 } else {
780 if (-1 == io->remove_port (p, this)) {
781 errmsg = _("This port cannot be removed.");
782 }
783 }
784
785 if (!errmsg.empty ()) {
786 ArdourDialog d (_("Port removal not allowed"));
787 Label l (errmsg);
788 d.get_vbox()->pack_start (l);
789 d.add_button (Stock::OK, RESPONSE_ACCEPT);
790 d.set_modal (true);
791 d.show_all ();
792 d.run ();
793 }
794 }
795
796 void
remove_all_channels(boost::weak_ptr<Bundle> w)797 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
798 {
799 boost::shared_ptr<Bundle> b = w.lock ();
800 if (!b) {
801 return;
802 }
803
804 /* Remove channels backwards so that we don't renumber channels
805 that we are about to remove.
806 */
807 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
808 if (should_show (b->channel_type(i))) {
809 remove_channel (ARDOUR::BundleChannel (b, i));
810 }
811 }
812 }
813
814 bool
can_add_port_proxy(boost::weak_ptr<Bundle> w,DataType t) const815 PortMatrix::can_add_port_proxy (boost::weak_ptr<Bundle> w, DataType t) const
816 {
817 boost::shared_ptr<Bundle> b = w.lock ();
818 if (!b) {
819 return false;
820 }
821 return can_add_port (b, t);
822 }
823
824 void
add_channel_proxy(boost::weak_ptr<Bundle> w,DataType t)825 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
826 {
827 boost::shared_ptr<Bundle> b = w.lock ();
828 if (!b) {
829 return;
830 }
831
832 add_channel (b, t);
833 }
834
835 void
setup_notebooks()836 PortMatrix::setup_notebooks ()
837 {
838 int const h_current_page = _hnotebook.get_current_page ();
839 int const v_current_page = _vnotebook.get_current_page ();
840
841 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
842 when adding or removing pages to or from notebooks, so ignore them */
843
844 _ignore_notebook_page_selected = true;
845
846 remove_notebook_pages (_hnotebook);
847 remove_notebook_pages (_vnotebook);
848
849 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
850 HBox* dummy = manage (new HBox);
851 dummy->show ();
852 Label* label = manage (new Label ((*i)->name));
853 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
854 label->set_use_markup ();
855 label->show ();
856 if (_arrangement == LEFT_TO_BOTTOM) {
857 _vnotebook.prepend_page (*dummy, *label);
858 } else {
859 /* Reverse the order of vertical tabs when they are on the right hand side
860 so that from top to bottom it is the same order as that from left to right
861 for the top tabs.
862 */
863 _vnotebook.append_page (*dummy, *label);
864 }
865 }
866
867 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
868 HBox* dummy = manage (new HBox);
869 dummy->show ();
870 Label* label = manage (new Label ((*i)->name));
871 label->set_use_markup ();
872 label->show ();
873 _hnotebook.append_page (*dummy, *label);
874 }
875
876 _ignore_notebook_page_selected = false;
877
878 if (_arrangement == TOP_TO_RIGHT) {
879 _vnotebook.set_tab_pos (POS_RIGHT);
880 _hnotebook.set_tab_pos (POS_TOP);
881 } else {
882 _vnotebook.set_tab_pos (POS_LEFT);
883 _hnotebook.set_tab_pos (POS_BOTTOM);
884 }
885
886 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
887 _hnotebook.set_current_page (h_current_page);
888 } else {
889 _hnotebook.set_current_page (0);
890 }
891
892 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
893 _vnotebook.set_current_page (v_current_page);
894 } else {
895 _vnotebook.set_current_page (0);
896 }
897
898 if (_hnotebook.get_n_pages() <= 1) {
899 _hbox.hide ();
900 } else {
901 _hbox.show ();
902 }
903
904 if (_vnotebook.get_n_pages() <= 1) {
905 _vbox.hide ();
906 } else {
907 _vbox.show ();
908 }
909 }
910
911 void
remove_notebook_pages(Notebook & n)912 PortMatrix::remove_notebook_pages (Notebook& n)
913 {
914 int const N = n.get_n_pages ();
915
916 for (int i = 0; i < N; ++i) {
917 n.remove_page ();
918 }
919 }
920
921 void
notebook_page_selected(GtkNotebookPage *,guint)922 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
923 {
924 if (_ignore_notebook_page_selected) {
925 return;
926 }
927
928 _body->setup ();
929 setup_scrollbars ();
930 queue_draw ();
931 }
932
933 void
session_going_away()934 PortMatrix::session_going_away ()
935 {
936 _session = 0;
937 }
938
939 void
body_dimensions_changed()940 PortMatrix::body_dimensions_changed ()
941 {
942 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
943 if (_arrangement == TOP_TO_RIGHT) {
944 _vspacer.set_size_request (-1, _body->column_labels_height ());
945 _vspacer.show ();
946 } else {
947 _vspacer.hide ();
948 }
949
950 int curr_width;
951 int curr_height;
952 _parent->get_size (curr_width, curr_height);
953
954 pair<uint32_t, uint32_t> m = max_size ();
955
956 /* Don't shrink the window */
957 m.first = max (int (m.first), curr_width);
958 m.second = max (int (m.second), curr_height);
959
960 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
961 }
962
963 /** @return The PortGroup that is currently visible (ie selected by
964 * the notebook) along a given axis.
965 */
966 boost::shared_ptr<const PortGroup>
visible_ports(int d) const967 PortMatrix::visible_ports (int d) const
968 {
969 PortGroupList const & p = _ports[d];
970 PortGroupList::List::const_iterator j = p.begin ();
971
972 /* The logic to compute the index here is a bit twisty because for
973 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
974 tabs in setup_notebooks ().
975 */
976
977 int n = 0;
978 if (d == _row_index) {
979 if (_arrangement == LEFT_TO_BOTTOM) {
980 n = p.size() - _vnotebook.get_current_page () - 1;
981 } else {
982 n = _vnotebook.get_current_page ();
983 }
984 } else {
985 n = _hnotebook.get_current_page ();
986 }
987
988 int i = 0;
989 while (i != int (n) && j != p.end ()) {
990 ++i;
991 ++j;
992 }
993
994 if (j == p.end()) {
995 return boost::shared_ptr<const PortGroup> ();
996 }
997
998 return *j;
999 }
1000
1001 void
add_remove_option(Menu_Helpers::MenuList & m,boost::weak_ptr<Bundle> w,int c)1002 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
1003 {
1004 using namespace Menu_Helpers;
1005
1006 boost::shared_ptr<Bundle> b = w.lock ();
1007 if (!b) {
1008 return;
1009 }
1010
1011 char buf [64];
1012 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
1013 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
1014 }
1015
1016 void
add_disassociate_option(Menu_Helpers::MenuList & m,boost::weak_ptr<Bundle> w,int d,int c)1017 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
1018 {
1019 using namespace Menu_Helpers;
1020
1021 boost::shared_ptr<Bundle> b = w.lock ();
1022 if (!b) {
1023 return;
1024 }
1025
1026 char buf [64];
1027 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
1028 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1029 }
1030
1031 void
port_connected_or_disconnected()1032 PortMatrix::port_connected_or_disconnected ()
1033 {
1034 _body->rebuild_and_draw_grid ();
1035 update_tab_highlighting ();
1036 }
1037
1038 /** Update the highlighting of tab names to reflect which ones
1039 * have connections. This is pretty inefficient, unfortunately,
1040 * but maybe that doesn't matter too much.
1041 */
1042 void
update_tab_highlighting()1043 PortMatrix::update_tab_highlighting ()
1044 {
1045 if (!_session) {
1046 return;
1047 }
1048
1049 for (int i = 0; i < 2; ++i) {
1050
1051 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1052
1053 PortGroupList const * gl = ports (i);
1054 int p = 0;
1055 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1056 bool has_connection = false;
1057 PortGroup::BundleList const & bl = (*j)->bundles ();
1058 PortGroup::BundleList::const_iterator k = bl.begin ();
1059 while (k != bl.end()) {
1060 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1061 has_connection = true;
1062 break;
1063 }
1064 ++k;
1065 }
1066
1067 /* Find the page index that we should update; this is backwards
1068 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1069 */
1070 int page = p;
1071 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1072 page = notebook->get_n_pages() - p - 1;
1073 }
1074
1075 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1076 string c = label->get_label ();
1077 if (c.length() && c[0] == '<' && !has_connection) {
1078 /* this label is marked up with <b> but shouldn't be */
1079 label->set_text ((*j)->name);
1080 } else if (c.length() && c[0] != '<' && has_connection) {
1081 /* this label is not marked up with <b> but should be */
1082 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1083 }
1084
1085 ++p;
1086 }
1087 }
1088 }
1089
1090 string
channel_noun() const1091 PortMatrix::channel_noun () const
1092 {
1093 return _("channel");
1094 }
1095
1096 /** @return true if this matrix should show bundles / ports of type \t */
1097 bool
should_show(DataType t) const1098 PortMatrix::should_show (DataType t) const
1099 {
1100 return (_type == DataType::NIL || t == _type);
1101 }
1102
1103 uint32_t
count_of_our_type(ChanCount c) const1104 PortMatrix::count_of_our_type (ChanCount c) const
1105 {
1106 if (_type == DataType::NIL) {
1107 return c.n_total ();
1108 }
1109
1110 return c.get (_type);
1111 }
1112
1113 /** @return The number of ports of our type in the given channel count,
1114 * but returning 1 if there are no ports.
1115 */
1116 uint32_t
count_of_our_type_min_1(ChanCount c) const1117 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1118 {
1119 uint32_t n = count_of_our_type (c);
1120 if (n == 0) {
1121 n = 1;
1122 }
1123
1124 return n;
1125 }
1126
1127 PortMatrixNode::State
get_association(PortMatrixNode node) const1128 PortMatrix::get_association (PortMatrixNode node) const
1129 {
1130 if (show_only_bundles ()) {
1131
1132 bool have_off_diagonal_association = false;
1133 bool have_diagonal_association = false;
1134 bool have_diagonal_not_association = false;
1135
1136 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1137
1138 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1139
1140 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1141 continue;
1142 }
1143
1144 ARDOUR::BundleChannel c[2];
1145 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1146 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1147
1148 PortMatrixNode::State const s = get_state (c);
1149
1150 switch (s) {
1151 case PortMatrixNode::ASSOCIATED:
1152 if (i == j) {
1153 have_diagonal_association = true;
1154 } else {
1155 have_off_diagonal_association = true;
1156 }
1157 break;
1158
1159 case PortMatrixNode::NOT_ASSOCIATED:
1160 if (i == j) {
1161 have_diagonal_not_association = true;
1162 }
1163 break;
1164
1165 default:
1166 break;
1167 }
1168 }
1169 }
1170
1171 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1172 return PortMatrixNode::ASSOCIATED;
1173 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1174 return PortMatrixNode::NOT_ASSOCIATED;
1175 }
1176
1177 return PortMatrixNode::PARTIAL;
1178
1179 } else {
1180
1181 ARDOUR::BundleChannel c[2];
1182 c[column_index()] = node.column;
1183 c[row_index()] = node.row;
1184 return get_state (c);
1185
1186 }
1187
1188 abort(); /* NOTREACHED */
1189 return PortMatrixNode::NOT_ASSOCIATED;
1190 }
1191
1192 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1193 bool
bundle_with_channels(boost::shared_ptr<ARDOUR::Bundle> b)1194 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1195 {
1196 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1197 }
1198
1199 /** See if a `flip' is possible.
1200 * @return If flip is possible, the new (row, column) notebook indices that
1201 * should be selected; otherwise, (-1, -1)
1202 */
1203 pair<int, int>
check_flip() const1204 PortMatrix::check_flip () const
1205 {
1206 /* Look for the row's port group name in the columns */
1207
1208 int new_column = 0;
1209 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1210 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1211 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1212 ++i;
1213 ++new_column;
1214 }
1215
1216 if (i == _ports[_column_index].end ()) {
1217 return make_pair (-1, -1);
1218 }
1219
1220 /* Look for the column's port group name in the rows */
1221
1222 int new_row = 0;
1223 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1224 i = _ports[_row_index].begin();
1225 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1226 ++i;
1227 ++new_row;
1228 }
1229
1230 if (i == _ports[_row_index].end ()) {
1231 return make_pair (-1, -1);
1232 }
1233
1234 if (_arrangement == LEFT_TO_BOTTOM) {
1235 new_row = _ports[_row_index].size() - new_row - 1;
1236 }
1237
1238 return make_pair (new_row, new_column);
1239 }
1240
1241 bool
can_flip() const1242 PortMatrix::can_flip () const
1243 {
1244 return check_flip().first != -1;
1245 }
1246
1247 /** Flip the column and row pages around, if possible */
1248 void
flip()1249 PortMatrix::flip ()
1250 {
1251 pair<int, int> n = check_flip ();
1252 if (n.first == -1) {
1253 return;
1254 }
1255
1256 _vnotebook.set_current_page (n.first);
1257 _hnotebook.set_current_page (n.second);
1258 }
1259
1260 bool
key_press(GdkEventKey * k)1261 PortMatrix::key_press (GdkEventKey* k)
1262 {
1263 if (k->keyval == GDK_f) {
1264 flip ();
1265 return true;
1266 }
1267
1268 return false;
1269 }
1270
1271 void
parameter_changed(string p)1272 PortMatrix::parameter_changed (string p)
1273 {
1274 if (p == "font-scale") {
1275 setup ();
1276 }
1277 }
1278