2 Copyright (C) 2002-2009 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include <gtkmm/scrolledwindow.h>
22 #include <gtkmm/adjustment.h>
23 #include <gtkmm/label.h>
24 #include <gtkmm/menu.h>
25 #include <gtkmm/menushell.h>
26 #include <gtkmm/menu_elems.h>
27 #include <gtkmm/window.h>
28 #include <gtkmm/stock.h>
29 #include <gtkmm/messagedialog.h>
30 #include "ardour/bundle.h"
31 #include "ardour/types.h"
32 #include "ardour/session.h"
33 #include "ardour/route.h"
34 #include "ardour/audioengine.h"
35 #include "port_matrix.h"
36 #include "port_matrix_body.h"
37 #include "port_matrix_component.h"
38 #include "ardour_dialog.h"
40 #include "gui_thread.h"
45 using namespace ARDOUR;
47 /** PortMatrix constructor.
48 * @param session Our session.
49 * @param type Port type that we are handling.
51 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
56 , _arrangement (TOP_TO_RIGHT)
59 , _min_height_divisor (1)
60 , _show_only_bundles (false)
61 , _inhibit_toggle_show_only_bundles (false)
62 , _ignore_notebook_page_selected (false)
64 set_session (session);
66 _body = new PortMatrixBody (this);
67 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
69 _hbox.pack_end (_hspacer, true, true);
70 _hbox.pack_end (_hnotebook, false, false);
71 _hbox.pack_end (_hlabel, false, false);
73 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
74 _vnotebook.property_tab_border() = 4;
75 _vnotebook.set_name (X_("PortMatrixLabel"));
76 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
77 _hnotebook.property_tab_border() = 4;
78 _hnotebook.set_name (X_("PortMatrixLabel"));
80 _vlabel.set_use_markup ();
81 _vlabel.set_alignment (1, 1);
82 _vlabel.set_padding (4, 16);
83 _vlabel.set_name (X_("PortMatrixLabel"));
84 _hlabel.set_use_markup ();
85 _hlabel.set_alignment (1, 0.5);
86 _hlabel.set_padding (16, 4);
87 _hlabel.set_name (X_("PortMatrixLabel"));
89 set_row_spacing (0, 8);
90 set_col_spacing (0, 8);
91 set_row_spacing (2, 8);
92 set_col_spacing (2, 8);
107 PortMatrix::~PortMatrix ()
113 /** Perform initial and once-only setup. This must be called by
114 * subclasses after they have set up _ports[] to at least some
115 * reasonable extent. Two-part initialisation is necessary because
116 * setting up _ports is largely done by virtual functions in
123 select_arrangement ();
125 /* Signal handling is kind of split into three parts:
127 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
128 * representation of the information in _ports[].
130 * 2. When certain other things change, we need to get our subclass to clear and
131 * re-fill _ports[], which in turn causes appropriate signals to be raised to
132 * hook into part (1).
134 * 3. Assorted other signals.
138 /* Part 1: the basic _ports[] change -> reset visuals */
140 for (int i = 0; i < 2; ++i) {
141 /* watch for the content of _ports[] changing */
142 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
144 /* and for bundles in _ports[] changing */
145 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
148 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
150 /* watch for routes being added or removed */
151 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
153 /* and also bundles */
154 _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
157 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
159 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
160 _session->RouteOrderKeyChanged.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
162 /* Part 3: other stuff */
164 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
166 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
167 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
169 reconnect_to_routes ();
174 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
176 PortMatrix::reconnect_to_routes ()
178 _route_connections.drop_connections ();
180 boost::shared_ptr<RouteList> routes = _session->get_routes ();
181 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
182 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
187 PortMatrix::route_processors_changed (RouteProcessorChange c)
189 if (c.type == RouteProcessorChange::MeterPointChange) {
190 /* this change has no impact on the port matrix */
194 setup_global_ports ();
197 /** A route has been added to or removed from the session */
199 PortMatrix::routes_changed ()
201 reconnect_to_routes ();
202 setup_global_ports ();
205 /** Set up everything that depends on the content of _ports[] */
209 /* this needs to be done first, as the visible_ports() method uses the
210 notebook state to decide which ports are being shown */
220 PortMatrix::set_type (DataType t)
226 PortMatrix::hscroll_changed ()
228 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
232 PortMatrix::vscroll_changed ()
234 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
238 PortMatrix::setup_scrollbars ()
240 Adjustment* a = _hscroll.get_adjustment ();
242 a->set_upper (_body->full_scroll_width());
243 a->set_page_size (_body->alloc_scroll_width());
244 a->set_step_increment (32);
245 a->set_page_increment (128);
247 a = _vscroll.get_adjustment ();
249 a->set_upper (_body->full_scroll_height());
250 a->set_page_size (_body->alloc_scroll_height());
251 a->set_step_increment (32);
252 a->set_page_increment (128);
255 /** Disassociate all of our ports from each other */
257 PortMatrix::disassociate_all ()
259 PortGroup::BundleList a = _ports[0].bundles ();
260 PortGroup::BundleList b = _ports[1].bundles ();
262 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
263 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
264 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
265 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
267 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
271 BundleChannel c[2] = {
272 BundleChannel ((*i)->bundle, j),
273 BundleChannel ((*k)->bundle, l)
276 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
277 set_state (c, false);
285 _body->rebuild_and_draw_grid ();
288 /* Decide how to arrange the components of the matrix */
290 PortMatrix::select_arrangement ()
292 uint32_t const N[2] = {
293 count_of_our_type_min_1 (_ports[0].total_channels()),
294 count_of_our_type_min_1 (_ports[1].total_channels())
297 /* XXX: shirley there's an easier way than this */
299 if (_vspacer.get_parent()) {
300 _vbox.remove (_vspacer);
303 if (_vnotebook.get_parent()) {
304 _vbox.remove (_vnotebook);
307 if (_vlabel.get_parent()) {
308 _vbox.remove (_vlabel);
311 /* The list with the most channels goes on left or right, so that the most channel
312 names are printed horizontally and hence more readable. However we also
313 maintain notional `signal flow' vaguely from left to right. Subclasses
314 should choose where to put ports based on signal flowing from _ports[0]
321 _arrangement = LEFT_TO_BOTTOM;
322 _vlabel.set_label (_("<b>Sources</b>"));
323 _hlabel.set_label (_("<b>Destinations</b>"));
324 _vlabel.set_angle (90);
326 _vbox.pack_end (_vlabel, false, false);
327 _vbox.pack_end (_vnotebook, false, false);
328 _vbox.pack_end (_vspacer, true, true);
330 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
331 attach (_vscroll, 3, 4, 1, 2, SHRINK);
332 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
333 attach (_vbox, 1, 2, 1, 2, SHRINK);
334 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
340 _arrangement = TOP_TO_RIGHT;
341 _hlabel.set_label (_("<b>Sources</b>"));
342 _vlabel.set_label (_("<b>Destinations</b>"));
343 _vlabel.set_angle (-90);
345 _vbox.pack_end (_vspacer, true, true);
346 _vbox.pack_end (_vnotebook, false, false);
347 _vbox.pack_end (_vlabel, false, false);
349 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
350 attach (_vscroll, 3, 4, 2, 3, SHRINK);
351 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
352 attach (_vbox, 2, 3, 2, 3, SHRINK);
353 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
357 /** @return columns list */
358 PortGroupList const *
359 PortMatrix::columns () const
361 return &_ports[_column_index];
364 boost::shared_ptr<const PortGroup>
365 PortMatrix::visible_columns () const
367 return visible_ports (_column_index);
370 /* @return rows list */
371 PortGroupList const *
372 PortMatrix::rows () const
374 return &_ports[_row_index];
377 boost::shared_ptr<const PortGroup>
378 PortMatrix::visible_rows () const
380 return visible_ports (_row_index);
383 /** @param column Column; its bundle may be 0 if we are over a row heading.
384 * @param row Row; its bundle may be 0 if we are over a column heading.
387 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
389 using namespace Menu_Helpers;
394 _menu->set_name ("ArdourContextMenu");
396 MenuList& items = _menu->items ();
399 bc[_column_index] = column;
400 bc[_row_index] = row;
403 bool need_separator = false;
405 for (int dim = 0; dim < 2; ++dim) {
407 if (bc[dim].bundle) {
409 Menu* m = manage (new Menu);
410 MenuList& sub = m->items ();
412 boost::weak_ptr<Bundle> w (bc[dim].bundle);
414 /* Start off with options for the `natural' port type */
415 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
416 if (should_show (*i)) {
417 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
418 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
422 /* Now add other ones */
423 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
424 if (!should_show (*i)) {
425 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
426 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
430 if (can_rename_channels (bc[dim].bundle)) {
432 buf, sizeof (buf), _("Rename '%s'..."),
433 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
438 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
443 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
444 if (bc[dim].channel != -1) {
445 add_remove_option (sub, w, bc[dim].channel);
448 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
451 if (bc[dim].bundle->nchannels().n_total() > 1) {
452 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
453 if (should_show (bc[dim].bundle->channel_type(i))) {
454 add_remove_option (sub, w, i);
461 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
462 if ((_show_only_bundles && c > 0) || c == 1) {
464 /* we're looking just at bundles, or our bundle has only one channel, so just offer
465 to disassociate all on the bundle.
468 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
470 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
475 if (bc[dim].channel != -1) {
476 /* specific channel under the menu, so just offer to disassociate that */
477 add_disassociate_option (sub, w, dim, bc[dim].channel);
479 /* no specific channel; offer to disassociate all, or any one in particular */
480 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
482 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
485 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
486 if (should_show (bc[dim].bundle->channel_type(i))) {
487 add_disassociate_option (sub, w, dim, i);
493 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
494 need_separator = true;
499 if (need_separator) {
500 items.push_back (SeparatorElem ());
503 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
504 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
505 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
506 _inhibit_toggle_show_only_bundles = true;
507 i->set_active (!_show_only_bundles);
508 _inhibit_toggle_show_only_bundles = false;
514 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
516 boost::shared_ptr<Bundle> sb = b.lock ();
521 remove_channel (BundleChannel (sb, c));
526 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
528 boost::shared_ptr<Bundle> sb = b.lock ();
533 rename_channel (BundleChannel (sb, c));
537 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
539 boost::shared_ptr<Bundle> sb = bundle.lock ();
544 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
545 if (should_show (sb->channel_type(i))) {
546 disassociate_all_on_channel (bundle, i, dim);
552 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
554 boost::shared_ptr<Bundle> sb = bundle.lock ();
559 PortGroup::BundleList a = _ports[1-dim].bundles ();
561 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
562 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
564 if (!should_show ((*i)->bundle->channel_type(j))) {
569 c[dim] = BundleChannel (sb, channel);
570 c[1-dim] = BundleChannel ((*i)->bundle, j);
572 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
573 set_state (c, false);
578 _body->rebuild_and_draw_grid ();
582 PortMatrix::setup_global_ports ()
584 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
586 for (int i = 0; i < 2; ++i) {
587 if (list_is_global (i)) {
594 PortMatrix::setup_global_ports_proxy ()
596 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
600 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
604 PortMatrix::setup_all_ports ()
606 if (_session->deletion_in_progress()) {
610 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
617 PortMatrix::toggle_show_only_bundles ()
619 if (_inhibit_toggle_show_only_bundles) {
623 _show_only_bundles = !_show_only_bundles;
628 pair<uint32_t, uint32_t>
629 PortMatrix::max_size () const
631 pair<uint32_t, uint32_t> m = _body->max_size ();
633 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
634 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
640 PortMatrix::on_scroll_event (GdkEventScroll* ev)
642 double const h = _hscroll.get_value ();
643 double const v = _vscroll.get_value ();
645 switch (ev->direction) {
647 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
649 case GDK_SCROLL_DOWN:
650 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
652 case GDK_SCROLL_LEFT:
653 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
655 case GDK_SCROLL_RIGHT:
656 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
663 boost::shared_ptr<IO>
664 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
666 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
668 io = _ports[1].io_from_bundle (b);
675 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
677 return io_from_bundle (b);
681 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
683 boost::shared_ptr<IO> io = io_from_bundle (b);
686 int const r = io->add_port ("", this, t);
688 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
689 "support the new configuration."
691 msg.set_title (_("Cannot add port"));
698 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
700 return io_from_bundle (b);
704 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
706 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
709 boost::shared_ptr<Port> p = io->nth (b.channel);
711 int const r = io->remove_port (p, this);
713 ArdourDialog d (_("Port removal not allowed"));
714 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
715 d.get_vbox()->pack_start (l);
716 d.add_button (Stock::OK, RESPONSE_ACCEPT);
726 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
728 boost::shared_ptr<Bundle> b = w.lock ();
733 /* Remove channels backwards so that we don't renumber channels
734 that we are about to remove.
736 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
737 if (should_show (b->channel_type(i))) {
738 remove_channel (ARDOUR::BundleChannel (b, i));
744 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
746 boost::shared_ptr<Bundle> b = w.lock ();
755 PortMatrix::setup_notebooks ()
757 int const h_current_page = _hnotebook.get_current_page ();
758 int const v_current_page = _vnotebook.get_current_page ();
760 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
761 when adding or removing pages to or from notebooks, so ignore them */
763 _ignore_notebook_page_selected = true;
765 remove_notebook_pages (_hnotebook);
766 remove_notebook_pages (_vnotebook);
768 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
769 HBox* dummy = manage (new HBox);
771 Label* label = manage (new Label ((*i)->name));
772 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
774 if (_arrangement == LEFT_TO_BOTTOM) {
775 _vnotebook.prepend_page (*dummy, *label);
777 /* Reverse the order of vertical tabs when they are on the right hand side
778 so that from top to bottom it is the same order as that from left to right
781 _vnotebook.append_page (*dummy, *label);
785 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
786 HBox* dummy = manage (new HBox);
788 _hnotebook.append_page (*dummy, (*i)->name);
791 _ignore_notebook_page_selected = false;
793 if (_arrangement == TOP_TO_RIGHT) {
794 _vnotebook.set_tab_pos (POS_RIGHT);
795 _hnotebook.set_tab_pos (POS_TOP);
797 _vnotebook.set_tab_pos (POS_LEFT);
798 _hnotebook.set_tab_pos (POS_BOTTOM);
801 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
802 _hnotebook.set_current_page (h_current_page);
804 _hnotebook.set_current_page (0);
807 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
808 _vnotebook.set_current_page (v_current_page);
810 _vnotebook.set_current_page (0);
813 if (_hnotebook.get_n_pages() <= 1) {
819 if (_vnotebook.get_n_pages() <= 1) {
827 PortMatrix::remove_notebook_pages (Notebook& n)
829 int const N = n.get_n_pages ();
831 for (int i = 0; i < N; ++i) {
837 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
839 if (_ignore_notebook_page_selected) {
849 PortMatrix::session_going_away ()
855 PortMatrix::body_dimensions_changed ()
857 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
858 if (_arrangement == TOP_TO_RIGHT) {
859 _vspacer.set_size_request (-1, _body->column_labels_height ());
867 _parent->get_size (curr_width, curr_height);
869 pair<uint32_t, uint32_t> m = max_size ();
871 /* Don't shrink the window */
872 m.first = max (int (m.first), curr_width);
873 m.second = max (int (m.second), curr_height);
875 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
878 /** @return The PortGroup that is currently visible (ie selected by
879 * the notebook) along a given axis.
881 boost::shared_ptr<const PortGroup>
882 PortMatrix::visible_ports (int d) const
884 PortGroupList const & p = _ports[d];
885 PortGroupList::List::const_iterator j = p.begin ();
887 /* The logic to compute the index here is a bit twisty because for
888 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
889 tabs in setup_notebooks ().
893 if (d == _row_index) {
894 if (_arrangement == LEFT_TO_BOTTOM) {
895 n = p.size() - _vnotebook.get_current_page () - 1;
897 n = _vnotebook.get_current_page ();
900 n = _hnotebook.get_current_page ();
904 while (i != int (n) && j != p.end ()) {
910 return boost::shared_ptr<const PortGroup> ();
917 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
919 using namespace Menu_Helpers;
921 boost::shared_ptr<Bundle> b = w.lock ();
927 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
928 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
932 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
934 using namespace Menu_Helpers;
936 boost::shared_ptr<Bundle> b = w.lock ();
942 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
943 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
947 PortMatrix::port_connected_or_disconnected ()
949 _body->rebuild_and_draw_grid ();
953 PortMatrix::channel_noun () const
958 /** @return true if this matrix should show bundles / ports of type \t */
960 PortMatrix::should_show (DataType t) const
962 return (_type == DataType::NIL || t == _type);
966 PortMatrix::count_of_our_type (ChanCount c) const
968 if (_type == DataType::NIL) {
972 return c.get (_type);
975 /** @return The number of ports of our type in the given channel count,
976 * but returning 1 if there are no ports.
979 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
981 uint32_t n = count_of_our_type (c);
989 PortMatrixNode::State
990 PortMatrix::get_association (PortMatrixNode node) const
992 if (show_only_bundles ()) {
994 bool have_off_diagonal_association = false;
995 bool have_diagonal_association = false;
996 bool have_diagonal_not_association = false;
998 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1000 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1002 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1006 ARDOUR::BundleChannel c[2];
1007 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1008 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1010 PortMatrixNode::State const s = get_state (c);
1013 case PortMatrixNode::ASSOCIATED:
1015 have_diagonal_association = true;
1017 have_off_diagonal_association = true;
1021 case PortMatrixNode::NOT_ASSOCIATED:
1023 have_diagonal_not_association = true;
1033 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1034 return PortMatrixNode::ASSOCIATED;
1035 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1036 return PortMatrixNode::NOT_ASSOCIATED;
1039 return PortMatrixNode::PARTIAL;
1043 ARDOUR::BundleChannel c[2];
1044 c[column_index()] = node.column;
1045 c[row_index()] = node.row;
1046 return get_state (c);
1051 return PortMatrixNode::NOT_ASSOCIATED;
1054 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1056 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1058 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;