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>
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.
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.
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.
25 #include <gtkmm/scrolledwindow.h>
26 #include <gtkmm/adjustment.h>
27 #include <gtkmm/label.h>
28 #include <gtkmm/menu.h>
29 #include <gtkmm/menushell.h>
30 #include <gtkmm/menu_elems.h>
31 #include <gtkmm/window.h>
32 #include <gtkmm/stock.h>
33 #include <gtkmm/messagedialog.h>
34 #include "ardour/bundle.h"
35 #include "ardour/types.h"
36 #include "ardour/session.h"
37 #include "ardour/route.h"
38 #include "ardour/audioengine.h"
39 #include "gtkmm2ext/utils.h"
40 #include "port_matrix.h"
41 #include "port_matrix_body.h"
42 #include "port_matrix_component.h"
43 #include "ardour_dialog.h"
45 #include "gui_thread.h"
50 using namespace ARDOUR;
51 using namespace ARDOUR_UI_UTILS;
53 /** PortMatrix constructor.
54 * @param session Our session.
55 * @param type Port type that we are handling.
57 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
62 , _arrangement (TOP_TO_RIGHT)
65 , _min_height_divisor (1)
66 , _show_only_bundles (false)
67 , _inhibit_toggle_show_only_bundles (false)
68 , _ignore_notebook_page_selected (false)
70 set_session (session);
72 _body = new PortMatrixBody (this);
73 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
75 _hbox.pack_end (_hspacer, true, true);
76 _hbox.pack_end (_hnotebook, false, false);
77 _hbox.pack_end (_hlabel, false, false);
79 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
80 _vnotebook.property_tab_border() = 4;
81 _vnotebook.set_name (X_("PortMatrixLabel"));
82 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
83 _hnotebook.property_tab_border() = 4;
84 _hnotebook.set_name (X_("PortMatrixLabel"));
86 _vlabel.set_use_markup ();
87 _vlabel.set_alignment (1, 1);
88 _vlabel.set_padding (4, 16);
89 _vlabel.set_name (X_("PortMatrixLabel"));
90 _hlabel.set_use_markup ();
91 _hlabel.set_alignment (1, 0.5);
92 _hlabel.set_padding (16, 4);
93 _hlabel.set_name (X_("PortMatrixLabel"));
95 set_row_spacing (0, 8);
96 set_col_spacing (0, 8);
97 set_row_spacing (2, 8);
98 set_col_spacing (2, 8);
113 PortMatrix::~PortMatrix ()
119 /** Perform initial and once-only setup. This must be called by
120 * subclasses after they have set up _ports[] to at least some
121 * reasonable extent. Two-part initialisation is necessary because
122 * setting up _ports is largely done by virtual functions in
129 select_arrangement ();
131 /* Signal handling is kind of split into three parts:
133 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
134 * representation of the information in _ports[].
136 * 2. When certain other things change, we need to get our subclass to clear and
137 * re-fill _ports[], which in turn causes appropriate signals to be raised to
138 * hook into part (1).
140 * 3. Assorted other signals.
144 /* Part 1: the basic _ports[] change -> reset visuals */
146 for (int i = 0; i < 2; ++i) {
147 /* watch for the content of _ports[] changing */
148 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
150 /* and for bundles in _ports[] changing */
151 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
154 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
156 /* watch for routes being added or removed */
157 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
159 /* and also bundles */
160 _session->BundleAddedOrRemoved.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
163 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
165 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
166 PresentationInfo::Change.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
168 /* Part 3: other stuff */
170 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
172 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
173 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
175 reconnect_to_routes ();
180 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
182 PortMatrix::reconnect_to_routes ()
184 _route_connections.drop_connections ();
186 boost::shared_ptr<RouteList> routes = _session->get_routes ();
187 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
188 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
189 (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
194 PortMatrix::route_processors_changed (RouteProcessorChange c)
196 if (c.type == RouteProcessorChange::MeterPointChange) {
197 /* this change has no impact on the port matrix */
201 setup_global_ports ();
204 /** A route has been added to or removed from the session */
206 PortMatrix::routes_changed ()
208 if (!_session) return;
209 reconnect_to_routes ();
210 setup_global_ports ();
213 /** Set up everything that depends on the content of _ports[] */
218 _route_connections.drop_connections ();
219 return; // session went away
222 /* this needs to be done first, as the visible_ports() method uses the
223 notebook state to decide which ports are being shown */
229 update_tab_highlighting ();
234 PortMatrix::set_type (DataType t)
240 PortMatrix::hscroll_changed ()
242 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
246 PortMatrix::vscroll_changed ()
248 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
252 PortMatrix::setup_scrollbars ()
254 Adjustment* a = _hscroll.get_adjustment ();
256 a->set_page_size (_body->alloc_scroll_width());
257 a->set_step_increment (32);
258 a->set_page_increment (128);
260 /* Set the adjustment to zero if the size has changed.*/
261 if (a->get_upper() != _body->full_scroll_width()) {
262 a->set_upper (_body->full_scroll_width());
266 a = _vscroll.get_adjustment ();
268 a->set_page_size (_body->alloc_scroll_height());
269 a->set_step_increment (32);
270 a->set_page_increment (128);
272 if (a->get_upper() != _body->full_scroll_height()) {
273 a->set_upper (_body->full_scroll_height());
278 /** Disassociate all of our ports from each other */
280 PortMatrix::disassociate_all ()
282 PortGroup::BundleList a = _ports[0].bundles ();
283 PortGroup::BundleList b = _ports[1].bundles ();
285 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
286 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
287 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
288 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
290 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
294 BundleChannel c[2] = {
295 BundleChannel ((*i)->bundle, j),
296 BundleChannel ((*k)->bundle, l)
299 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
300 set_state (c, false);
308 _body->rebuild_and_draw_grid ();
311 /* Decide how to arrange the components of the matrix */
313 PortMatrix::select_arrangement ()
315 uint32_t const N[2] = {
316 count_of_our_type_min_1 (_ports[0].total_channels()),
317 count_of_our_type_min_1 (_ports[1].total_channels())
320 /* XXX: shirley there's an easier way than this */
322 if (_vspacer.get_parent()) {
323 _vbox.remove (_vspacer);
326 if (_vnotebook.get_parent()) {
327 _vbox.remove (_vnotebook);
330 if (_vlabel.get_parent()) {
331 _vbox.remove (_vlabel);
334 /* The list with the most channels goes on left or right, so that the most channel
335 names are printed horizontally and hence more readable. However we also
336 maintain notional `signal flow' vaguely from left to right. Subclasses
337 should choose where to put ports based on signal flowing from _ports[0]
344 _arrangement = LEFT_TO_BOTTOM;
345 _vlabel.set_label (_("<b>Sources</b>"));
346 _hlabel.set_label (_("<b>Destinations</b>"));
347 _vlabel.set_angle (90);
349 _vbox.pack_end (_vlabel, false, false);
350 _vbox.pack_end (_vnotebook, false, false);
351 _vbox.pack_end (_vspacer, true, true);
353 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
354 REMOVE_FROM_GTK_PARENT(*_body)
355 REMOVE_FROM_GTK_PARENT(_vscroll)
356 REMOVE_FROM_GTK_PARENT(_hscroll)
357 REMOVE_FROM_GTK_PARENT(_vbox)
358 REMOVE_FROM_GTK_PARENT(_hbox)
360 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
361 attach (_vscroll, 3, 4, 1, 2, SHRINK);
362 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
363 attach (_vbox, 1, 2, 1, 2, SHRINK);
364 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
370 _arrangement = TOP_TO_RIGHT;
371 _hlabel.set_label (_("<b>Sources</b>"));
372 _vlabel.set_label (_("<b>Destinations</b>"));
373 _vlabel.set_angle (-90);
375 _vbox.pack_end (_vspacer, true, true);
376 _vbox.pack_end (_vnotebook, false, false);
377 _vbox.pack_end (_vlabel, false, false);
379 REMOVE_FROM_GTK_PARENT(*_body)
380 REMOVE_FROM_GTK_PARENT(_vscroll)
381 REMOVE_FROM_GTK_PARENT(_hscroll)
382 REMOVE_FROM_GTK_PARENT(_vbox)
383 REMOVE_FROM_GTK_PARENT(_hbox)
385 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
386 attach (_vscroll, 3, 4, 2, 3, SHRINK);
387 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
388 attach (_vbox, 2, 3, 2, 3, SHRINK);
389 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
393 /** @return columns list */
394 PortGroupList const *
395 PortMatrix::columns () const
397 return &_ports[_column_index];
400 boost::shared_ptr<const PortGroup>
401 PortMatrix::visible_columns () const
403 return visible_ports (_column_index);
406 /* @return rows list */
407 PortGroupList const *
408 PortMatrix::rows () const
410 return &_ports[_row_index];
413 boost::shared_ptr<const PortGroup>
414 PortMatrix::visible_rows () const
416 return visible_ports (_row_index);
419 /** @param column Column; its bundle may be 0 if we are over a row heading.
420 * @param row Row; its bundle may be 0 if we are over a column heading.
423 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
425 using namespace Menu_Helpers;
430 _menu->set_name ("ArdourContextMenu");
432 MenuList& items = _menu->items ();
435 bc[_column_index] = column;
436 bc[_row_index] = row;
439 bool need_separator = false;
441 for (int dim = 0; dim < 2; ++dim) {
443 if (bc[dim].bundle) {
445 Menu* m = manage (new Menu);
446 MenuList& sub = m->items ();
448 boost::weak_ptr<Bundle> w (bc[dim].bundle);
450 if (can_add_channels (bc[dim].bundle)) {
451 /* Start off with options for the `natural' port type */
452 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
453 if (should_show (*i) && can_add_channel_proxy (w, *i)) {
454 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
455 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
459 /* Now add other ones */
460 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
461 if (!should_show (*i) && can_add_channel_proxy (w, *i)) {
462 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
463 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
468 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
470 buf, sizeof (buf), _("Rename '%s'..."),
471 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
476 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
481 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
482 if (bc[dim].channel != -1) {
483 add_remove_option (sub, w, bc[dim].channel);
486 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
489 if (bc[dim].bundle->nchannels().n_total() > 1) {
490 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
491 if (should_show (bc[dim].bundle->channel_type(i))) {
492 add_remove_option (sub, w, i);
499 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
500 if ((_show_only_bundles && c > 0) || c == 1) {
502 /* we're looking just at bundles, or our bundle has only one channel, so just offer
503 to disassociate all on the bundle.
506 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
508 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
513 if (bc[dim].channel != -1) {
514 /* specific channel under the menu, so just offer to disassociate that */
515 add_disassociate_option (sub, w, dim, bc[dim].channel);
517 /* no specific channel; offer to disassociate all, or any one in particular */
518 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
520 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
523 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
524 if (should_show (bc[dim].bundle->channel_type(i))) {
525 add_disassociate_option (sub, w, dim, i);
531 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
532 need_separator = true;
537 if (need_separator) {
538 items.push_back (SeparatorElem ());
541 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
543 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
544 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
545 _inhibit_toggle_show_only_bundles = true;
546 i->set_active (!_show_only_bundles);
547 _inhibit_toggle_show_only_bundles = false;
549 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
550 items.back().set_sensitive (can_flip ());
556 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
558 boost::shared_ptr<Bundle> sb = b.lock ();
563 remove_channel (BundleChannel (sb, c));
568 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
570 boost::shared_ptr<Bundle> sb = b.lock ();
575 rename_channel (BundleChannel (sb, c));
579 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
581 boost::shared_ptr<Bundle> sb = bundle.lock ();
586 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
587 if (should_show (sb->channel_type(i))) {
588 disassociate_all_on_channel (bundle, i, dim);
594 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
596 boost::shared_ptr<Bundle> sb = bundle.lock ();
601 PortGroup::BundleList a = _ports[1-dim].bundles ();
603 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
604 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
606 if (!should_show ((*i)->bundle->channel_type(j))) {
611 c[dim] = BundleChannel (sb, channel);
612 c[1-dim] = BundleChannel ((*i)->bundle, j);
614 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
615 set_state (c, false);
620 _body->rebuild_and_draw_grid ();
624 PortMatrix::setup_global_ports ()
626 if (!_session || _session->deletion_in_progress()) return;
627 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
629 for (int i = 0; i < 2; ++i) {
630 if (list_is_global (i)) {
637 PortMatrix::setup_global_ports_proxy ()
639 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
643 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
647 PortMatrix::setup_all_ports ()
649 if (_session->deletion_in_progress()) {
653 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
660 PortMatrix::toggle_show_only_bundles ()
662 if (_inhibit_toggle_show_only_bundles) {
666 _show_only_bundles = !_show_only_bundles;
670 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
671 setting, so we need to set things up again now.
676 pair<uint32_t, uint32_t>
677 PortMatrix::max_size () const
679 pair<uint32_t, uint32_t> m = _body->max_size ();
681 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
682 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
688 PortMatrix::on_scroll_event (GdkEventScroll* ev)
690 double const h = _hscroll.get_value ();
691 double const v = _vscroll.get_value ();
693 switch (ev->direction) {
695 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
697 case GDK_SCROLL_DOWN:
698 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
700 case GDK_SCROLL_LEFT:
701 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
703 case GDK_SCROLL_RIGHT:
704 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
711 boost::shared_ptr<IO>
712 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
714 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
716 io = _ports[1].io_from_bundle (b);
723 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
725 return io_from_bundle (b) != 0;
729 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
731 boost::shared_ptr<IO> io = io_from_bundle (b);
734 int const r = io->add_port ("", this, t);
736 Gtk::MessageDialog msg (_("It is not possible to add a port here."));
737 msg.set_title (_("Cannot add port"));
744 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
746 return io_from_bundle (b) != 0;
750 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
753 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
754 boost::shared_ptr<Port> p = io->nth (b.channel);
760 if (io->n_ports ().n_total () == 1) {
761 errmsg = _("The last port cannot be removed");
763 if (-1 == io->remove_port (p, this)) {
764 errmsg = _("This port cannot be removed.");
768 if (!errmsg.empty ()) {
769 ArdourDialog d (_("Port removal not allowed"));
771 d.get_vbox()->pack_start (l);
772 d.add_button (Stock::OK, RESPONSE_ACCEPT);
780 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
782 boost::shared_ptr<Bundle> b = w.lock ();
787 /* Remove channels backwards so that we don't renumber channels
788 that we are about to remove.
790 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
791 if (should_show (b->channel_type(i))) {
792 remove_channel (ARDOUR::BundleChannel (b, i));
798 PortMatrix::can_add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t) const
800 boost::shared_ptr<Bundle> b = w.lock ();
804 boost::shared_ptr<IO> io = io_from_bundle (b);
805 return io->can_add_port (t);
809 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
811 boost::shared_ptr<Bundle> b = w.lock ();
820 PortMatrix::setup_notebooks ()
822 int const h_current_page = _hnotebook.get_current_page ();
823 int const v_current_page = _vnotebook.get_current_page ();
825 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
826 when adding or removing pages to or from notebooks, so ignore them */
828 _ignore_notebook_page_selected = true;
830 remove_notebook_pages (_hnotebook);
831 remove_notebook_pages (_vnotebook);
833 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
834 HBox* dummy = manage (new HBox);
836 Label* label = manage (new Label ((*i)->name));
837 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
838 label->set_use_markup ();
840 if (_arrangement == LEFT_TO_BOTTOM) {
841 _vnotebook.prepend_page (*dummy, *label);
843 /* Reverse the order of vertical tabs when they are on the right hand side
844 so that from top to bottom it is the same order as that from left to right
847 _vnotebook.append_page (*dummy, *label);
851 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
852 HBox* dummy = manage (new HBox);
854 Label* label = manage (new Label ((*i)->name));
855 label->set_use_markup ();
857 _hnotebook.append_page (*dummy, *label);
860 _ignore_notebook_page_selected = false;
862 if (_arrangement == TOP_TO_RIGHT) {
863 _vnotebook.set_tab_pos (POS_RIGHT);
864 _hnotebook.set_tab_pos (POS_TOP);
866 _vnotebook.set_tab_pos (POS_LEFT);
867 _hnotebook.set_tab_pos (POS_BOTTOM);
870 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
871 _hnotebook.set_current_page (h_current_page);
873 _hnotebook.set_current_page (0);
876 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
877 _vnotebook.set_current_page (v_current_page);
879 _vnotebook.set_current_page (0);
882 if (_hnotebook.get_n_pages() <= 1) {
888 if (_vnotebook.get_n_pages() <= 1) {
896 PortMatrix::remove_notebook_pages (Notebook& n)
898 int const N = n.get_n_pages ();
900 for (int i = 0; i < N; ++i) {
906 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
908 if (_ignore_notebook_page_selected) {
918 PortMatrix::session_going_away ()
924 PortMatrix::body_dimensions_changed ()
926 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
927 if (_arrangement == TOP_TO_RIGHT) {
928 _vspacer.set_size_request (-1, _body->column_labels_height ());
936 _parent->get_size (curr_width, curr_height);
938 pair<uint32_t, uint32_t> m = max_size ();
940 /* Don't shrink the window */
941 m.first = max (int (m.first), curr_width);
942 m.second = max (int (m.second), curr_height);
944 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
947 /** @return The PortGroup that is currently visible (ie selected by
948 * the notebook) along a given axis.
950 boost::shared_ptr<const PortGroup>
951 PortMatrix::visible_ports (int d) const
953 PortGroupList const & p = _ports[d];
954 PortGroupList::List::const_iterator j = p.begin ();
956 /* The logic to compute the index here is a bit twisty because for
957 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
958 tabs in setup_notebooks ().
962 if (d == _row_index) {
963 if (_arrangement == LEFT_TO_BOTTOM) {
964 n = p.size() - _vnotebook.get_current_page () - 1;
966 n = _vnotebook.get_current_page ();
969 n = _hnotebook.get_current_page ();
973 while (i != int (n) && j != p.end ()) {
979 return boost::shared_ptr<const PortGroup> ();
986 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
988 using namespace Menu_Helpers;
990 boost::shared_ptr<Bundle> b = w.lock ();
996 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
997 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
1001 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
1003 using namespace Menu_Helpers;
1005 boost::shared_ptr<Bundle> b = w.lock ();
1011 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
1012 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1016 PortMatrix::port_connected_or_disconnected ()
1018 _body->rebuild_and_draw_grid ();
1019 update_tab_highlighting ();
1022 /** Update the highlighting of tab names to reflect which ones
1023 * have connections. This is pretty inefficient, unfortunately,
1024 * but maybe that doesn't matter too much.
1027 PortMatrix::update_tab_highlighting ()
1033 for (int i = 0; i < 2; ++i) {
1035 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1037 PortGroupList const * gl = ports (i);
1039 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1040 bool has_connection = false;
1041 PortGroup::BundleList const & bl = (*j)->bundles ();
1042 PortGroup::BundleList::const_iterator k = bl.begin ();
1043 while (k != bl.end()) {
1044 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1045 has_connection = true;
1051 /* Find the page index that we should update; this is backwards
1052 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1055 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1056 page = notebook->get_n_pages() - p - 1;
1059 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1060 string c = label->get_label ();
1061 if (c.length() && c[0] == '<' && !has_connection) {
1062 /* this label is marked up with <b> but shouldn't be */
1063 label->set_text ((*j)->name);
1064 } else if (c.length() && c[0] != '<' && has_connection) {
1065 /* this label is not marked up with <b> but should be */
1066 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1075 PortMatrix::channel_noun () const
1077 return _("channel");
1080 /** @return true if this matrix should show bundles / ports of type \t */
1082 PortMatrix::should_show (DataType t) const
1084 return (_type == DataType::NIL || t == _type);
1088 PortMatrix::count_of_our_type (ChanCount c) const
1090 if (_type == DataType::NIL) {
1091 return c.n_total ();
1094 return c.get (_type);
1097 /** @return The number of ports of our type in the given channel count,
1098 * but returning 1 if there are no ports.
1101 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1103 uint32_t n = count_of_our_type (c);
1111 PortMatrixNode::State
1112 PortMatrix::get_association (PortMatrixNode node) const
1114 if (show_only_bundles ()) {
1116 bool have_off_diagonal_association = false;
1117 bool have_diagonal_association = false;
1118 bool have_diagonal_not_association = false;
1120 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1122 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1124 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1128 ARDOUR::BundleChannel c[2];
1129 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1130 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1132 PortMatrixNode::State const s = get_state (c);
1135 case PortMatrixNode::ASSOCIATED:
1137 have_diagonal_association = true;
1139 have_off_diagonal_association = true;
1143 case PortMatrixNode::NOT_ASSOCIATED:
1145 have_diagonal_not_association = true;
1155 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1156 return PortMatrixNode::ASSOCIATED;
1157 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1158 return PortMatrixNode::NOT_ASSOCIATED;
1161 return PortMatrixNode::PARTIAL;
1165 ARDOUR::BundleChannel c[2];
1166 c[column_index()] = node.column;
1167 c[row_index()] = node.row;
1168 return get_state (c);
1172 abort(); /* NOTREACHED */
1173 return PortMatrixNode::NOT_ASSOCIATED;
1176 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1178 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1180 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1183 /** See if a `flip' is possible.
1184 * @return If flip is possible, the new (row, column) notebook indices that
1185 * should be selected; otherwise, (-1, -1)
1188 PortMatrix::check_flip () const
1190 /* Look for the row's port group name in the columns */
1193 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1194 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1195 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1200 if (i == _ports[_column_index].end ()) {
1201 return make_pair (-1, -1);
1204 /* Look for the column's port group name in the rows */
1207 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1208 i = _ports[_row_index].begin();
1209 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1214 if (i == _ports[_row_index].end ()) {
1215 return make_pair (-1, -1);
1218 if (_arrangement == LEFT_TO_BOTTOM) {
1219 new_row = _ports[_row_index].size() - new_row - 1;
1222 return make_pair (new_row, new_column);
1226 PortMatrix::can_flip () const
1228 return check_flip().first != -1;
1231 /** Flip the column and row pages around, if possible */
1235 pair<int, int> n = check_flip ();
1236 if (n.first == -1) {
1240 _vnotebook.set_current_page (n.first);
1241 _hnotebook.set_current_page (n.second);
1245 PortMatrix::key_press (GdkEventKey* k)
1247 if (k->keyval == GDK_f) {