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"
55 using namespace ARDOUR;
56 using namespace ARDOUR_UI_UTILS;
58 /** PortMatrix constructor.
59 * @param session Our session.
60 * @param type Port type that we are handling.
62 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
67 , _arrangement (TOP_TO_RIGHT)
70 , _min_height_divisor (1)
71 , _show_only_bundles (false)
72 , _inhibit_toggle_show_only_bundles (false)
73 , _ignore_notebook_page_selected (false)
75 set_session (session);
77 _body = new PortMatrixBody (this);
78 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
80 _hbox.pack_end (_hspacer, true, true);
81 _hbox.pack_end (_hnotebook, false, false);
82 _hbox.pack_end (_hlabel, false, false);
84 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
85 _vnotebook.property_tab_border() = 4;
86 _vnotebook.set_name (X_("PortMatrixLabel"));
87 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
88 _hnotebook.property_tab_border() = 4;
89 _hnotebook.set_name (X_("PortMatrixLabel"));
91 _vlabel.set_use_markup ();
92 _vlabel.set_alignment (1, 1);
93 _vlabel.set_padding (4, 16);
94 _vlabel.set_name (X_("PortMatrixLabel"));
95 _hlabel.set_use_markup ();
96 _hlabel.set_alignment (1, 0.5);
97 _hlabel.set_padding (16, 4);
98 _hlabel.set_name (X_("PortMatrixLabel"));
100 set_row_spacing (0, 8);
101 set_col_spacing (0, 8);
102 set_row_spacing (2, 8);
103 set_col_spacing (2, 8);
118 PortMatrix::~PortMatrix ()
124 /** Perform initial and once-only setup. This must be called by
125 * subclasses after they have set up _ports[] to at least some
126 * reasonable extent. Two-part initialisation is necessary because
127 * setting up _ports is largely done by virtual functions in
134 select_arrangement ();
136 /* Signal handling is kind of split into three parts:
138 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
139 * representation of the information in _ports[].
141 * 2. When certain other things change, we need to get our subclass to clear and
142 * re-fill _ports[], which in turn causes appropriate signals to be raised to
143 * hook into part (1).
145 * 3. Assorted other signals.
149 /* Part 1: the basic _ports[] change -> reset visuals */
151 for (int i = 0; i < 2; ++i) {
152 /* watch for the content of _ports[] changing */
153 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
155 /* and for bundles in _ports[] changing */
156 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
159 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
161 /* watch for routes being added or removed */
162 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
164 /* and also bundles */
165 _session->BundleAddedOrRemoved.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
168 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
170 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
171 PresentationInfo::Change.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
173 /* Part 3: other stuff */
175 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
177 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
178 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
180 reconnect_to_routes ();
185 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
187 PortMatrix::reconnect_to_routes ()
189 _route_connections.drop_connections ();
191 boost::shared_ptr<RouteList> routes = _session->get_routes ();
192 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
193 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
194 (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
199 PortMatrix::route_processors_changed (RouteProcessorChange c)
201 if (c.type == RouteProcessorChange::MeterPointChange) {
202 /* this change has no impact on the port matrix */
206 setup_global_ports ();
209 /** A route has been added to or removed from the session */
211 PortMatrix::routes_changed ()
213 if (!_session) return;
214 reconnect_to_routes ();
215 setup_global_ports ();
218 /** Set up everything that depends on the content of _ports[] */
223 _route_connections.drop_connections ();
224 return; // session went away
227 /* this needs to be done first, as the visible_ports() method uses the
228 notebook state to decide which ports are being shown */
234 update_tab_highlighting ();
239 PortMatrix::set_type (DataType t)
245 PortMatrix::hscroll_changed ()
247 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
251 PortMatrix::vscroll_changed ()
253 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
257 PortMatrix::setup_scrollbars ()
259 Adjustment* a = _hscroll.get_adjustment ();
261 a->set_page_size (_body->alloc_scroll_width());
262 a->set_step_increment (32);
263 a->set_page_increment (128);
265 /* Set the adjustment to zero if the size has changed.*/
266 if (a->get_upper() != _body->full_scroll_width()) {
267 a->set_upper (_body->full_scroll_width());
271 a = _vscroll.get_adjustment ();
273 a->set_page_size (_body->alloc_scroll_height());
274 a->set_step_increment (32);
275 a->set_page_increment (128);
277 if (a->get_upper() != _body->full_scroll_height()) {
278 a->set_upper (_body->full_scroll_height());
283 /** Disassociate all of our ports from each other */
285 PortMatrix::disassociate_all ()
287 PortGroup::BundleList a = _ports[0].bundles ();
288 PortGroup::BundleList b = _ports[1].bundles ();
290 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
291 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
292 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
293 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
295 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
299 BundleChannel c[2] = {
300 BundleChannel ((*i)->bundle, j),
301 BundleChannel ((*k)->bundle, l)
304 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
305 set_state (c, false);
313 _body->rebuild_and_draw_grid ();
316 /* Decide how to arrange the components of the matrix */
318 PortMatrix::select_arrangement ()
320 uint32_t const N[2] = {
321 count_of_our_type_min_1 (_ports[0].total_channels()),
322 count_of_our_type_min_1 (_ports[1].total_channels())
325 /* XXX: shirley there's an easier way than this */
327 if (_vspacer.get_parent()) {
328 _vbox.remove (_vspacer);
331 if (_vnotebook.get_parent()) {
332 _vbox.remove (_vnotebook);
335 if (_vlabel.get_parent()) {
336 _vbox.remove (_vlabel);
339 /* The list with the most channels goes on left or right, so that the most channel
340 names are printed horizontally and hence more readable. However we also
341 maintain notional `signal flow' vaguely from left to right. Subclasses
342 should choose where to put ports based on signal flowing from _ports[0]
349 _arrangement = LEFT_TO_BOTTOM;
350 _vlabel.set_label (_("<b>Sources</b>"));
351 _hlabel.set_label (_("<b>Destinations</b>"));
352 _vlabel.set_angle (90);
354 _vbox.pack_end (_vlabel, false, false);
355 _vbox.pack_end (_vnotebook, false, false);
356 _vbox.pack_end (_vspacer, true, true);
358 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
359 REMOVE_FROM_GTK_PARENT(*_body)
360 REMOVE_FROM_GTK_PARENT(_vscroll)
361 REMOVE_FROM_GTK_PARENT(_hscroll)
362 REMOVE_FROM_GTK_PARENT(_vbox)
363 REMOVE_FROM_GTK_PARENT(_hbox)
365 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
366 attach (_vscroll, 3, 4, 1, 2, SHRINK);
367 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
368 attach (_vbox, 1, 2, 1, 2, SHRINK);
369 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
375 _arrangement = TOP_TO_RIGHT;
376 _hlabel.set_label (_("<b>Sources</b>"));
377 _vlabel.set_label (_("<b>Destinations</b>"));
378 _vlabel.set_angle (-90);
380 _vbox.pack_end (_vspacer, true, true);
381 _vbox.pack_end (_vnotebook, false, false);
382 _vbox.pack_end (_vlabel, false, false);
384 REMOVE_FROM_GTK_PARENT(*_body)
385 REMOVE_FROM_GTK_PARENT(_vscroll)
386 REMOVE_FROM_GTK_PARENT(_hscroll)
387 REMOVE_FROM_GTK_PARENT(_vbox)
388 REMOVE_FROM_GTK_PARENT(_hbox)
390 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
391 attach (_vscroll, 3, 4, 2, 3, SHRINK);
392 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
393 attach (_vbox, 2, 3, 2, 3, SHRINK);
394 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
398 /** @return columns list */
399 PortGroupList const *
400 PortMatrix::columns () const
402 return &_ports[_column_index];
405 boost::shared_ptr<const PortGroup>
406 PortMatrix::visible_columns () const
408 return visible_ports (_column_index);
411 /* @return rows list */
412 PortGroupList const *
413 PortMatrix::rows () const
415 return &_ports[_row_index];
418 boost::shared_ptr<const PortGroup>
419 PortMatrix::visible_rows () const
421 return visible_ports (_row_index);
424 /** @param column Column; its bundle may be 0 if we are over a row heading.
425 * @param row Row; its bundle may be 0 if we are over a column heading.
428 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
430 using namespace Menu_Helpers;
435 _menu->set_name ("ArdourContextMenu");
437 MenuList& items = _menu->items ();
440 bc[_column_index] = column;
441 bc[_row_index] = row;
444 bool need_separator = false;
446 for (int dim = 0; dim < 2; ++dim) {
448 if (bc[dim].bundle) {
450 Menu* m = manage (new Menu);
451 MenuList& sub = m->items ();
453 boost::weak_ptr<Bundle> w (bc[dim].bundle);
455 if (can_add_channels (bc[dim].bundle)) {
456 /* Start off with options for the `natural' port type */
457 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
458 if (should_show (*i) && can_add_channel_proxy (w, *i)) {
459 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
460 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
464 /* Now add other ones */
465 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
466 if (!should_show (*i) && can_add_channel_proxy (w, *i)) {
467 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
468 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
473 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
475 buf, sizeof (buf), _("Rename '%s'..."),
476 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
481 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
486 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
487 if (bc[dim].channel != -1) {
488 add_remove_option (sub, w, bc[dim].channel);
491 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
494 if (bc[dim].bundle->nchannels().n_total() > 1) {
495 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
496 if (should_show (bc[dim].bundle->channel_type(i))) {
497 add_remove_option (sub, w, i);
504 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
505 if ((_show_only_bundles && c > 0) || c == 1) {
507 /* we're looking just at bundles, or our bundle has only one channel, so just offer
508 to disassociate all on the bundle.
511 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
513 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
518 if (bc[dim].channel != -1) {
519 /* specific channel under the menu, so just offer to disassociate that */
520 add_disassociate_option (sub, w, dim, bc[dim].channel);
522 /* no specific channel; offer to disassociate all, or any one in particular */
523 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
525 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
528 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
529 if (should_show (bc[dim].bundle->channel_type(i))) {
530 add_disassociate_option (sub, w, dim, i);
536 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
537 need_separator = true;
542 if (need_separator) {
543 items.push_back (SeparatorElem ());
546 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
548 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
549 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
550 _inhibit_toggle_show_only_bundles = true;
551 i->set_active (!_show_only_bundles);
552 _inhibit_toggle_show_only_bundles = false;
554 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
555 items.back().set_sensitive (can_flip ());
561 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
563 boost::shared_ptr<Bundle> sb = b.lock ();
568 remove_channel (BundleChannel (sb, c));
573 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
575 boost::shared_ptr<Bundle> sb = b.lock ();
580 rename_channel (BundleChannel (sb, c));
584 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
586 boost::shared_ptr<Bundle> sb = bundle.lock ();
591 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
592 if (should_show (sb->channel_type(i))) {
593 disassociate_all_on_channel (bundle, i, dim);
599 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
601 boost::shared_ptr<Bundle> sb = bundle.lock ();
606 PortGroup::BundleList a = _ports[1-dim].bundles ();
608 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
609 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
611 if (!should_show ((*i)->bundle->channel_type(j))) {
616 c[dim] = BundleChannel (sb, channel);
617 c[1-dim] = BundleChannel ((*i)->bundle, j);
619 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
620 set_state (c, false);
625 _body->rebuild_and_draw_grid ();
629 PortMatrix::setup_global_ports ()
631 if (!_session || _session->deletion_in_progress()) return;
632 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
634 for (int i = 0; i < 2; ++i) {
635 if (list_is_global (i)) {
642 PortMatrix::setup_global_ports_proxy ()
644 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
648 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
652 PortMatrix::setup_all_ports ()
654 if (_session->deletion_in_progress()) {
658 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
665 PortMatrix::toggle_show_only_bundles ()
667 if (_inhibit_toggle_show_only_bundles) {
671 _show_only_bundles = !_show_only_bundles;
675 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
676 setting, so we need to set things up again now.
681 pair<uint32_t, uint32_t>
682 PortMatrix::max_size () const
684 pair<uint32_t, uint32_t> m = _body->max_size ();
686 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
687 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
693 PortMatrix::on_scroll_event (GdkEventScroll* ev)
695 double const h = _hscroll.get_value ();
696 double const v = _vscroll.get_value ();
698 switch (ev->direction) {
700 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
702 case GDK_SCROLL_DOWN:
703 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
705 case GDK_SCROLL_LEFT:
706 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
708 case GDK_SCROLL_RIGHT:
709 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
716 boost::shared_ptr<IO>
717 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
719 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
721 io = _ports[1].io_from_bundle (b);
728 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
730 return io_from_bundle (b) != 0;
734 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
736 boost::shared_ptr<IO> io = io_from_bundle (b);
739 int const r = io->add_port ("", this, t);
741 ArdourMessageDialog msg (_("It is not possible to add a port here."));
742 msg.set_title (_("Cannot add port"));
749 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
751 return io_from_bundle (b) != 0;
755 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
758 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
759 boost::shared_ptr<Port> p = io->nth (b.channel);
765 if (io->n_ports ().n_total () == 1) {
766 errmsg = _("The last port cannot be removed");
768 if (-1 == io->remove_port (p, this)) {
769 errmsg = _("This port cannot be removed.");
773 if (!errmsg.empty ()) {
774 ArdourDialog d (_("Port removal not allowed"));
776 d.get_vbox()->pack_start (l);
777 d.add_button (Stock::OK, RESPONSE_ACCEPT);
785 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
787 boost::shared_ptr<Bundle> b = w.lock ();
792 /* Remove channels backwards so that we don't renumber channels
793 that we are about to remove.
795 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
796 if (should_show (b->channel_type(i))) {
797 remove_channel (ARDOUR::BundleChannel (b, i));
803 PortMatrix::can_add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t) const
805 boost::shared_ptr<Bundle> b = w.lock ();
809 boost::shared_ptr<IO> io = io_from_bundle (b);
810 return io->can_add_port (t);
814 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
816 boost::shared_ptr<Bundle> b = w.lock ();
825 PortMatrix::setup_notebooks ()
827 int const h_current_page = _hnotebook.get_current_page ();
828 int const v_current_page = _vnotebook.get_current_page ();
830 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
831 when adding or removing pages to or from notebooks, so ignore them */
833 _ignore_notebook_page_selected = true;
835 remove_notebook_pages (_hnotebook);
836 remove_notebook_pages (_vnotebook);
838 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
839 HBox* dummy = manage (new HBox);
841 Label* label = manage (new Label ((*i)->name));
842 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
843 label->set_use_markup ();
845 if (_arrangement == LEFT_TO_BOTTOM) {
846 _vnotebook.prepend_page (*dummy, *label);
848 /* Reverse the order of vertical tabs when they are on the right hand side
849 so that from top to bottom it is the same order as that from left to right
852 _vnotebook.append_page (*dummy, *label);
856 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
857 HBox* dummy = manage (new HBox);
859 Label* label = manage (new Label ((*i)->name));
860 label->set_use_markup ();
862 _hnotebook.append_page (*dummy, *label);
865 _ignore_notebook_page_selected = false;
867 if (_arrangement == TOP_TO_RIGHT) {
868 _vnotebook.set_tab_pos (POS_RIGHT);
869 _hnotebook.set_tab_pos (POS_TOP);
871 _vnotebook.set_tab_pos (POS_LEFT);
872 _hnotebook.set_tab_pos (POS_BOTTOM);
875 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
876 _hnotebook.set_current_page (h_current_page);
878 _hnotebook.set_current_page (0);
881 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
882 _vnotebook.set_current_page (v_current_page);
884 _vnotebook.set_current_page (0);
887 if (_hnotebook.get_n_pages() <= 1) {
893 if (_vnotebook.get_n_pages() <= 1) {
901 PortMatrix::remove_notebook_pages (Notebook& n)
903 int const N = n.get_n_pages ();
905 for (int i = 0; i < N; ++i) {
911 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
913 if (_ignore_notebook_page_selected) {
923 PortMatrix::session_going_away ()
929 PortMatrix::body_dimensions_changed ()
931 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
932 if (_arrangement == TOP_TO_RIGHT) {
933 _vspacer.set_size_request (-1, _body->column_labels_height ());
941 _parent->get_size (curr_width, curr_height);
943 pair<uint32_t, uint32_t> m = max_size ();
945 /* Don't shrink the window */
946 m.first = max (int (m.first), curr_width);
947 m.second = max (int (m.second), curr_height);
949 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
952 /** @return The PortGroup that is currently visible (ie selected by
953 * the notebook) along a given axis.
955 boost::shared_ptr<const PortGroup>
956 PortMatrix::visible_ports (int d) const
958 PortGroupList const & p = _ports[d];
959 PortGroupList::List::const_iterator j = p.begin ();
961 /* The logic to compute the index here is a bit twisty because for
962 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
963 tabs in setup_notebooks ().
967 if (d == _row_index) {
968 if (_arrangement == LEFT_TO_BOTTOM) {
969 n = p.size() - _vnotebook.get_current_page () - 1;
971 n = _vnotebook.get_current_page ();
974 n = _hnotebook.get_current_page ();
978 while (i != int (n) && j != p.end ()) {
984 return boost::shared_ptr<const PortGroup> ();
991 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
993 using namespace Menu_Helpers;
995 boost::shared_ptr<Bundle> b = w.lock ();
1001 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
1002 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
1006 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
1008 using namespace Menu_Helpers;
1010 boost::shared_ptr<Bundle> b = w.lock ();
1016 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
1017 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1021 PortMatrix::port_connected_or_disconnected ()
1023 _body->rebuild_and_draw_grid ();
1024 update_tab_highlighting ();
1027 /** Update the highlighting of tab names to reflect which ones
1028 * have connections. This is pretty inefficient, unfortunately,
1029 * but maybe that doesn't matter too much.
1032 PortMatrix::update_tab_highlighting ()
1038 for (int i = 0; i < 2; ++i) {
1040 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1042 PortGroupList const * gl = ports (i);
1044 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1045 bool has_connection = false;
1046 PortGroup::BundleList const & bl = (*j)->bundles ();
1047 PortGroup::BundleList::const_iterator k = bl.begin ();
1048 while (k != bl.end()) {
1049 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1050 has_connection = true;
1056 /* Find the page index that we should update; this is backwards
1057 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1060 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1061 page = notebook->get_n_pages() - p - 1;
1064 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1065 string c = label->get_label ();
1066 if (c.length() && c[0] == '<' && !has_connection) {
1067 /* this label is marked up with <b> but shouldn't be */
1068 label->set_text ((*j)->name);
1069 } else if (c.length() && c[0] != '<' && has_connection) {
1070 /* this label is not marked up with <b> but should be */
1071 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1080 PortMatrix::channel_noun () const
1082 return _("channel");
1085 /** @return true if this matrix should show bundles / ports of type \t */
1087 PortMatrix::should_show (DataType t) const
1089 return (_type == DataType::NIL || t == _type);
1093 PortMatrix::count_of_our_type (ChanCount c) const
1095 if (_type == DataType::NIL) {
1096 return c.n_total ();
1099 return c.get (_type);
1102 /** @return The number of ports of our type in the given channel count,
1103 * but returning 1 if there are no ports.
1106 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1108 uint32_t n = count_of_our_type (c);
1116 PortMatrixNode::State
1117 PortMatrix::get_association (PortMatrixNode node) const
1119 if (show_only_bundles ()) {
1121 bool have_off_diagonal_association = false;
1122 bool have_diagonal_association = false;
1123 bool have_diagonal_not_association = false;
1125 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1127 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1129 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1133 ARDOUR::BundleChannel c[2];
1134 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1135 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1137 PortMatrixNode::State const s = get_state (c);
1140 case PortMatrixNode::ASSOCIATED:
1142 have_diagonal_association = true;
1144 have_off_diagonal_association = true;
1148 case PortMatrixNode::NOT_ASSOCIATED:
1150 have_diagonal_not_association = true;
1160 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1161 return PortMatrixNode::ASSOCIATED;
1162 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1163 return PortMatrixNode::NOT_ASSOCIATED;
1166 return PortMatrixNode::PARTIAL;
1170 ARDOUR::BundleChannel c[2];
1171 c[column_index()] = node.column;
1172 c[row_index()] = node.row;
1173 return get_state (c);
1177 abort(); /* NOTREACHED */
1178 return PortMatrixNode::NOT_ASSOCIATED;
1181 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1183 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1185 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1188 /** See if a `flip' is possible.
1189 * @return If flip is possible, the new (row, column) notebook indices that
1190 * should be selected; otherwise, (-1, -1)
1193 PortMatrix::check_flip () const
1195 /* Look for the row's port group name in the columns */
1198 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1199 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1200 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1205 if (i == _ports[_column_index].end ()) {
1206 return make_pair (-1, -1);
1209 /* Look for the column's port group name in the rows */
1212 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1213 i = _ports[_row_index].begin();
1214 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1219 if (i == _ports[_row_index].end ()) {
1220 return make_pair (-1, -1);
1223 if (_arrangement == LEFT_TO_BOTTOM) {
1224 new_row = _ports[_row_index].size() - new_row - 1;
1227 return make_pair (new_row, new_column);
1231 PortMatrix::can_flip () const
1233 return check_flip().first != -1;
1236 /** Flip the column and row pages around, if possible */
1240 pair<int, int> n = check_flip ();
1241 if (n.first == -1) {
1245 _vnotebook.set_current_page (n.first);
1246 _hnotebook.set_current_page (n.second);
1250 PortMatrix::key_press (GdkEventKey* k)
1252 if (k->keyval == GDK_f) {