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.
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>
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"
41 #include "gtkmm2ext/utils.h"
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"
50 #include "ui_config.h"
56 using namespace ARDOUR;
57 using namespace ARDOUR_UI_UTILS;
59 /** PortMatrix constructor.
60 * @param session Our session.
61 * @param type Port type that we are handling.
63 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
68 , _arrangement (TOP_TO_RIGHT)
71 , _min_height_divisor (1)
72 , _show_only_bundles (false)
73 , _inhibit_toggle_show_only_bundles (false)
74 , _ignore_notebook_page_selected (false)
76 set_session (session);
78 _body = new PortMatrixBody (this);
79 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
81 _hbox.pack_end (_hspacer, true, true);
82 _hbox.pack_end (_hnotebook, false, false);
83 _hbox.pack_end (_hlabel, false, false);
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"));
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"));
101 set_row_spacing (0, 8);
102 set_col_spacing (0, 8);
103 set_row_spacing (2, 8);
104 set_col_spacing (2, 8);
118 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &PortMatrix::parameter_changed));
121 PortMatrix::~PortMatrix ()
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
137 select_arrangement ();
139 /* Signal handling is kind of split into three parts:
141 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
142 * representation of the information in _ports[].
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).
148 * 3. Assorted other signals.
152 /* Part 1: the basic _ports[] change -> reset visuals */
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());
158 /* and for bundles in _ports[] changing */
159 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
162 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
164 /* watch for routes being added or removed */
165 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
167 /* and also bundles */
168 _session->BundleAddedOrRemoved.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
171 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
173 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
174 PresentationInfo::Change.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
176 /* Part 3: other stuff */
178 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
180 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
181 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
183 reconnect_to_routes ();
188 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
190 PortMatrix::reconnect_to_routes ()
192 _route_connections.drop_connections ();
194 boost::shared_ptr<RouteList> routes = _session->get_routes ();
195 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
196 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
197 (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
202 PortMatrix::route_processors_changed (RouteProcessorChange c)
204 if (c.type == RouteProcessorChange::MeterPointChange) {
205 /* this change has no impact on the port matrix */
209 setup_global_ports ();
212 /** A route has been added to or removed from the session */
214 PortMatrix::routes_changed ()
216 if (!_session) return;
217 reconnect_to_routes ();
218 setup_global_ports ();
221 /** Set up everything that depends on the content of _ports[] */
226 _route_connections.drop_connections ();
227 return; // session went away
230 /* this needs to be done first, as the visible_ports() method uses the
231 notebook state to decide which ports are being shown */
237 update_tab_highlighting ();
242 PortMatrix::set_type (DataType t)
248 PortMatrix::hscroll_changed ()
250 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
254 PortMatrix::vscroll_changed ()
256 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
260 PortMatrix::setup_scrollbars ()
262 Adjustment* a = _hscroll.get_adjustment ();
264 a->set_page_size (_body->alloc_scroll_width());
265 a->set_step_increment (32);
266 a->set_page_increment (128);
268 /* Set the adjustment to zero if the size has changed.*/
269 if (a->get_upper() != _body->full_scroll_width()) {
270 a->set_upper (_body->full_scroll_width());
274 a = _vscroll.get_adjustment ();
276 a->set_page_size (_body->alloc_scroll_height());
277 a->set_step_increment (32);
278 a->set_page_increment (128);
280 if (a->get_upper() != _body->full_scroll_height()) {
281 a->set_upper (_body->full_scroll_height());
286 /** Disassociate all of our ports from each other */
288 PortMatrix::disassociate_all ()
290 PortGroup::BundleList a = _ports[0].bundles ();
291 PortGroup::BundleList b = _ports[1].bundles ();
293 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
294 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
295 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
296 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
298 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
302 BundleChannel c[2] = {
303 BundleChannel ((*i)->bundle, j),
304 BundleChannel ((*k)->bundle, l)
307 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
308 set_state (c, false);
316 _body->rebuild_and_draw_grid ();
319 /* Decide how to arrange the components of the matrix */
321 PortMatrix::select_arrangement ()
323 uint32_t const N[2] = {
324 count_of_our_type_min_1 (_ports[0].total_channels()),
325 count_of_our_type_min_1 (_ports[1].total_channels())
328 /* XXX: shirley there's an easier way than this */
330 if (_vspacer.get_parent()) {
331 _vbox.remove (_vspacer);
334 if (_vnotebook.get_parent()) {
335 _vbox.remove (_vnotebook);
338 if (_vlabel.get_parent()) {
339 _vbox.remove (_vlabel);
342 /* The list with the most channels goes on left or right, so that the most channel
343 names are printed horizontally and hence more readable. However we also
344 maintain notional `signal flow' vaguely from left to right. Subclasses
345 should choose where to put ports based on signal flowing from _ports[0]
352 _arrangement = LEFT_TO_BOTTOM;
353 _vlabel.set_label (_("<b>Sources</b>"));
354 _hlabel.set_label (_("<b>Destinations</b>"));
355 _vlabel.set_angle (90);
357 _vbox.pack_end (_vlabel, false, false);
358 _vbox.pack_end (_vnotebook, false, false);
359 _vbox.pack_end (_vspacer, true, true);
361 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
362 REMOVE_FROM_GTK_PARENT(*_body)
363 REMOVE_FROM_GTK_PARENT(_vscroll)
364 REMOVE_FROM_GTK_PARENT(_hscroll)
365 REMOVE_FROM_GTK_PARENT(_vbox)
366 REMOVE_FROM_GTK_PARENT(_hbox)
368 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
369 attach (_vscroll, 3, 4, 1, 2, SHRINK);
370 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
371 attach (_vbox, 1, 2, 1, 2, SHRINK);
372 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
378 _arrangement = TOP_TO_RIGHT;
379 _hlabel.set_label (_("<b>Sources</b>"));
380 _vlabel.set_label (_("<b>Destinations</b>"));
381 _vlabel.set_angle (-90);
383 _vbox.pack_end (_vspacer, true, true);
384 _vbox.pack_end (_vnotebook, false, false);
385 _vbox.pack_end (_vlabel, false, false);
387 REMOVE_FROM_GTK_PARENT(*_body)
388 REMOVE_FROM_GTK_PARENT(_vscroll)
389 REMOVE_FROM_GTK_PARENT(_hscroll)
390 REMOVE_FROM_GTK_PARENT(_vbox)
391 REMOVE_FROM_GTK_PARENT(_hbox)
393 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
394 attach (_vscroll, 3, 4, 2, 3, SHRINK);
395 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
396 attach (_vbox, 2, 3, 2, 3, SHRINK);
397 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
401 /** @return columns list */
402 PortGroupList const *
403 PortMatrix::columns () const
405 return &_ports[_column_index];
408 boost::shared_ptr<const PortGroup>
409 PortMatrix::visible_columns () const
411 return visible_ports (_column_index);
414 /* @return rows list */
415 PortGroupList const *
416 PortMatrix::rows () const
418 return &_ports[_row_index];
421 boost::shared_ptr<const PortGroup>
422 PortMatrix::visible_rows () const
424 return visible_ports (_row_index);
427 /** @param column Column; its bundle may be 0 if we are over a row heading.
428 * @param row Row; its bundle may be 0 if we are over a column heading.
431 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
433 using namespace Menu_Helpers;
438 _menu->set_name ("ArdourContextMenu");
440 MenuList& items = _menu->items ();
443 bc[_column_index] = column;
444 bc[_row_index] = row;
447 bool need_separator = false;
449 for (int dim = 0; dim < 2; ++dim) {
451 if (bc[dim].bundle) {
453 Menu* m = manage (new Menu);
454 MenuList& sub = m->items ();
456 boost::weak_ptr<Bundle> w (bc[dim].bundle);
458 if (can_add_channels (bc[dim].bundle)) {
459 /* Start off with options for the `natural' port type */
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)));
467 /* Now add other ones */
468 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
469 if (!should_show (*i) && can_add_channel_proxy (w, *i)) {
470 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
471 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
476 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
478 buf, sizeof (buf), _("Rename '%s'..."),
479 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
484 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
489 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
490 if (bc[dim].channel != -1) {
491 add_remove_option (sub, w, bc[dim].channel);
494 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
497 if (bc[dim].bundle->nchannels().n_total() > 1) {
498 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
499 if (should_show (bc[dim].bundle->channel_type(i))) {
500 add_remove_option (sub, w, i);
507 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
508 if ((_show_only_bundles && c > 0) || c == 1) {
510 /* we're looking just at bundles, or our bundle has only one channel, so just offer
511 to disassociate all on the bundle.
514 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
516 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
521 if (bc[dim].channel != -1) {
522 /* specific channel under the menu, so just offer to disassociate that */
523 add_disassociate_option (sub, w, dim, bc[dim].channel);
525 /* no specific channel; offer to disassociate all, or any one in particular */
526 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
528 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
531 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
532 if (should_show (bc[dim].bundle->channel_type(i))) {
533 add_disassociate_option (sub, w, dim, i);
539 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
540 need_separator = true;
545 if (need_separator) {
546 items.push_back (SeparatorElem ());
549 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
551 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
552 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
553 _inhibit_toggle_show_only_bundles = true;
554 i->set_active (!_show_only_bundles);
555 _inhibit_toggle_show_only_bundles = false;
557 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
558 items.back().set_sensitive (can_flip ());
564 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
566 boost::shared_ptr<Bundle> sb = b.lock ();
571 remove_channel (BundleChannel (sb, c));
576 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
578 boost::shared_ptr<Bundle> sb = b.lock ();
583 rename_channel (BundleChannel (sb, c));
587 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
589 boost::shared_ptr<Bundle> sb = bundle.lock ();
594 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
595 if (should_show (sb->channel_type(i))) {
596 disassociate_all_on_channel (bundle, i, dim);
602 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
604 boost::shared_ptr<Bundle> sb = bundle.lock ();
609 PortGroup::BundleList a = _ports[1-dim].bundles ();
611 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
612 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
614 if (!should_show ((*i)->bundle->channel_type(j))) {
619 c[dim] = BundleChannel (sb, channel);
620 c[1-dim] = BundleChannel ((*i)->bundle, j);
622 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
623 set_state (c, false);
628 _body->rebuild_and_draw_grid ();
632 PortMatrix::setup_global_ports ()
634 if (!_session || _session->deletion_in_progress()) return;
635 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
637 for (int i = 0; i < 2; ++i) {
638 if (list_is_global (i)) {
645 PortMatrix::setup_global_ports_proxy ()
647 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
651 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
655 PortMatrix::setup_all_ports ()
657 if (_session->deletion_in_progress()) {
661 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
668 PortMatrix::toggle_show_only_bundles ()
670 if (_inhibit_toggle_show_only_bundles) {
674 _show_only_bundles = !_show_only_bundles;
678 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
679 setting, so we need to set things up again now.
684 pair<uint32_t, uint32_t>
685 PortMatrix::max_size () const
687 pair<uint32_t, uint32_t> m = _body->max_size ();
689 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
690 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
696 PortMatrix::on_scroll_event (GdkEventScroll* ev)
698 double const h = _hscroll.get_value ();
699 double const v = _vscroll.get_value ();
701 switch (ev->direction) {
703 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
705 case GDK_SCROLL_DOWN:
706 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
708 case GDK_SCROLL_LEFT:
709 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
711 case GDK_SCROLL_RIGHT:
712 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
719 boost::shared_ptr<IO>
720 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
722 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
724 io = _ports[1].io_from_bundle (b);
731 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
733 return io_from_bundle (b) != 0;
737 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
739 boost::shared_ptr<IO> io = io_from_bundle (b);
742 int const r = io->add_port ("", this, t);
744 ArdourMessageDialog msg (_("It is not possible to add a port here."));
745 msg.set_title (_("Cannot add port"));
752 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
754 return io_from_bundle (b) != 0;
758 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
761 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
762 boost::shared_ptr<Port> p = io->nth (b.channel);
768 if (io->n_ports ().n_total () == 1) {
769 errmsg = _("The last port cannot be removed");
771 if (-1 == io->remove_port (p, this)) {
772 errmsg = _("This port cannot be removed.");
776 if (!errmsg.empty ()) {
777 ArdourDialog d (_("Port removal not allowed"));
779 d.get_vbox()->pack_start (l);
780 d.add_button (Stock::OK, RESPONSE_ACCEPT);
788 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
790 boost::shared_ptr<Bundle> b = w.lock ();
795 /* Remove channels backwards so that we don't renumber channels
796 that we are about to remove.
798 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
799 if (should_show (b->channel_type(i))) {
800 remove_channel (ARDOUR::BundleChannel (b, i));
806 PortMatrix::can_add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t) const
808 boost::shared_ptr<Bundle> b = w.lock ();
812 boost::shared_ptr<IO> io = io_from_bundle (b);
813 return io->can_add_port (t);
817 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
819 boost::shared_ptr<Bundle> b = w.lock ();
828 PortMatrix::setup_notebooks ()
830 int const h_current_page = _hnotebook.get_current_page ();
831 int const v_current_page = _vnotebook.get_current_page ();
833 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
834 when adding or removing pages to or from notebooks, so ignore them */
836 _ignore_notebook_page_selected = true;
838 remove_notebook_pages (_hnotebook);
839 remove_notebook_pages (_vnotebook);
841 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
842 HBox* dummy = manage (new HBox);
844 Label* label = manage (new Label ((*i)->name));
845 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
846 label->set_use_markup ();
848 if (_arrangement == LEFT_TO_BOTTOM) {
849 _vnotebook.prepend_page (*dummy, *label);
851 /* Reverse the order of vertical tabs when they are on the right hand side
852 so that from top to bottom it is the same order as that from left to right
855 _vnotebook.append_page (*dummy, *label);
859 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
860 HBox* dummy = manage (new HBox);
862 Label* label = manage (new Label ((*i)->name));
863 label->set_use_markup ();
865 _hnotebook.append_page (*dummy, *label);
868 _ignore_notebook_page_selected = false;
870 if (_arrangement == TOP_TO_RIGHT) {
871 _vnotebook.set_tab_pos (POS_RIGHT);
872 _hnotebook.set_tab_pos (POS_TOP);
874 _vnotebook.set_tab_pos (POS_LEFT);
875 _hnotebook.set_tab_pos (POS_BOTTOM);
878 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
879 _hnotebook.set_current_page (h_current_page);
881 _hnotebook.set_current_page (0);
884 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
885 _vnotebook.set_current_page (v_current_page);
887 _vnotebook.set_current_page (0);
890 if (_hnotebook.get_n_pages() <= 1) {
896 if (_vnotebook.get_n_pages() <= 1) {
904 PortMatrix::remove_notebook_pages (Notebook& n)
906 int const N = n.get_n_pages ();
908 for (int i = 0; i < N; ++i) {
914 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
916 if (_ignore_notebook_page_selected) {
926 PortMatrix::session_going_away ()
932 PortMatrix::body_dimensions_changed ()
934 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
935 if (_arrangement == TOP_TO_RIGHT) {
936 _vspacer.set_size_request (-1, _body->column_labels_height ());
944 _parent->get_size (curr_width, curr_height);
946 pair<uint32_t, uint32_t> m = max_size ();
948 /* Don't shrink the window */
949 m.first = max (int (m.first), curr_width);
950 m.second = max (int (m.second), curr_height);
952 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
955 /** @return The PortGroup that is currently visible (ie selected by
956 * the notebook) along a given axis.
958 boost::shared_ptr<const PortGroup>
959 PortMatrix::visible_ports (int d) const
961 PortGroupList const & p = _ports[d];
962 PortGroupList::List::const_iterator j = p.begin ();
964 /* The logic to compute the index here is a bit twisty because for
965 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
966 tabs in setup_notebooks ().
970 if (d == _row_index) {
971 if (_arrangement == LEFT_TO_BOTTOM) {
972 n = p.size() - _vnotebook.get_current_page () - 1;
974 n = _vnotebook.get_current_page ();
977 n = _hnotebook.get_current_page ();
981 while (i != int (n) && j != p.end ()) {
987 return boost::shared_ptr<const PortGroup> ();
994 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
996 using namespace Menu_Helpers;
998 boost::shared_ptr<Bundle> b = w.lock ();
1004 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
1005 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
1009 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
1011 using namespace Menu_Helpers;
1013 boost::shared_ptr<Bundle> b = w.lock ();
1019 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
1020 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1024 PortMatrix::port_connected_or_disconnected ()
1026 _body->rebuild_and_draw_grid ();
1027 update_tab_highlighting ();
1030 /** Update the highlighting of tab names to reflect which ones
1031 * have connections. This is pretty inefficient, unfortunately,
1032 * but maybe that doesn't matter too much.
1035 PortMatrix::update_tab_highlighting ()
1041 for (int i = 0; i < 2; ++i) {
1043 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1045 PortGroupList const * gl = ports (i);
1047 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1048 bool has_connection = false;
1049 PortGroup::BundleList const & bl = (*j)->bundles ();
1050 PortGroup::BundleList::const_iterator k = bl.begin ();
1051 while (k != bl.end()) {
1052 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1053 has_connection = true;
1059 /* Find the page index that we should update; this is backwards
1060 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1063 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1064 page = notebook->get_n_pages() - p - 1;
1067 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1068 string c = label->get_label ();
1069 if (c.length() && c[0] == '<' && !has_connection) {
1070 /* this label is marked up with <b> but shouldn't be */
1071 label->set_text ((*j)->name);
1072 } else if (c.length() && c[0] != '<' && has_connection) {
1073 /* this label is not marked up with <b> but should be */
1074 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1083 PortMatrix::channel_noun () const
1085 return _("channel");
1088 /** @return true if this matrix should show bundles / ports of type \t */
1090 PortMatrix::should_show (DataType t) const
1092 return (_type == DataType::NIL || t == _type);
1096 PortMatrix::count_of_our_type (ChanCount c) const
1098 if (_type == DataType::NIL) {
1099 return c.n_total ();
1102 return c.get (_type);
1105 /** @return The number of ports of our type in the given channel count,
1106 * but returning 1 if there are no ports.
1109 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1111 uint32_t n = count_of_our_type (c);
1119 PortMatrixNode::State
1120 PortMatrix::get_association (PortMatrixNode node) const
1122 if (show_only_bundles ()) {
1124 bool have_off_diagonal_association = false;
1125 bool have_diagonal_association = false;
1126 bool have_diagonal_not_association = false;
1128 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1130 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1132 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1136 ARDOUR::BundleChannel c[2];
1137 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1138 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1140 PortMatrixNode::State const s = get_state (c);
1143 case PortMatrixNode::ASSOCIATED:
1145 have_diagonal_association = true;
1147 have_off_diagonal_association = true;
1151 case PortMatrixNode::NOT_ASSOCIATED:
1153 have_diagonal_not_association = true;
1163 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1164 return PortMatrixNode::ASSOCIATED;
1165 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1166 return PortMatrixNode::NOT_ASSOCIATED;
1169 return PortMatrixNode::PARTIAL;
1173 ARDOUR::BundleChannel c[2];
1174 c[column_index()] = node.column;
1175 c[row_index()] = node.row;
1176 return get_state (c);
1180 abort(); /* NOTREACHED */
1181 return PortMatrixNode::NOT_ASSOCIATED;
1184 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1186 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1188 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1191 /** See if a `flip' is possible.
1192 * @return If flip is possible, the new (row, column) notebook indices that
1193 * should be selected; otherwise, (-1, -1)
1196 PortMatrix::check_flip () const
1198 /* Look for the row's port group name in the columns */
1201 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1202 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1203 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1208 if (i == _ports[_column_index].end ()) {
1209 return make_pair (-1, -1);
1212 /* Look for the column's port group name in the rows */
1215 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1216 i = _ports[_row_index].begin();
1217 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1222 if (i == _ports[_row_index].end ()) {
1223 return make_pair (-1, -1);
1226 if (_arrangement == LEFT_TO_BOTTOM) {
1227 new_row = _ports[_row_index].size() - new_row - 1;
1230 return make_pair (new_row, new_column);
1234 PortMatrix::can_flip () const
1236 return check_flip().first != -1;
1239 /** Flip the column and row pages around, if possible */
1243 pair<int, int> n = check_flip ();
1244 if (n.first == -1) {
1248 _vnotebook.set_current_page (n.first);
1249 _hnotebook.set_current_page (n.second);
1253 PortMatrix::key_press (GdkEventKey* k)
1255 if (k->keyval == GDK_f) {
1264 PortMatrix::parameter_changed (string p)
1266 if (p == "font-scale") {