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), ui_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 snprintf (buf, sizeof (buf), _("Remove all"));
450 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
453 if (bc[dim].bundle->nchannels().n_total() > 1) {
454 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
455 if (should_show (bc[dim].bundle->channel_type(i))) {
456 add_remove_option (sub, w, i);
463 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
464 if ((_show_only_bundles && c > 0) || c == 1) {
466 /* we're looking just at bundles, or our bundle has only one channel, so just offer
467 to disassociate all on the bundle.
470 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
472 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
477 if (bc[dim].channel != -1) {
478 /* specific channel under the menu, so just offer to disassociate that */
479 add_disassociate_option (sub, w, dim, bc[dim].channel);
481 /* no specific channel; offer to disassociate all, or any one in particular */
482 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
484 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
487 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
488 if (should_show (bc[dim].bundle->channel_type(i))) {
489 add_disassociate_option (sub, w, dim, i);
495 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
496 need_separator = true;
501 if (need_separator) {
502 items.push_back (SeparatorElem ());
505 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
506 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
507 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
508 _inhibit_toggle_show_only_bundles = true;
509 i->set_active (!_show_only_bundles);
510 _inhibit_toggle_show_only_bundles = false;
516 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
518 boost::shared_ptr<Bundle> sb = b.lock ();
523 remove_channel (BundleChannel (sb, c));
528 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
530 boost::shared_ptr<Bundle> sb = b.lock ();
535 rename_channel (BundleChannel (sb, c));
539 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
541 boost::shared_ptr<Bundle> sb = bundle.lock ();
546 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
547 if (should_show (sb->channel_type(i))) {
548 disassociate_all_on_channel (bundle, i, dim);
554 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
556 boost::shared_ptr<Bundle> sb = bundle.lock ();
561 PortGroup::BundleList a = _ports[1-dim].bundles ();
563 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
564 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
566 if (!should_show ((*i)->bundle->channel_type(j))) {
571 c[dim] = BundleChannel (sb, channel);
572 c[1-dim] = BundleChannel ((*i)->bundle, j);
574 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
575 set_state (c, false);
580 _body->rebuild_and_draw_grid ();
584 PortMatrix::setup_global_ports ()
586 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
588 for (int i = 0; i < 2; ++i) {
589 if (list_is_global (i)) {
596 PortMatrix::setup_global_ports_proxy ()
598 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
602 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
606 PortMatrix::setup_all_ports ()
608 if (_session->deletion_in_progress()) {
612 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
619 PortMatrix::toggle_show_only_bundles ()
621 if (_inhibit_toggle_show_only_bundles) {
625 _show_only_bundles = !_show_only_bundles;
630 pair<uint32_t, uint32_t>
631 PortMatrix::max_size () const
633 pair<uint32_t, uint32_t> m = _body->max_size ();
635 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
636 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
642 PortMatrix::on_scroll_event (GdkEventScroll* ev)
644 double const h = _hscroll.get_value ();
645 double const v = _vscroll.get_value ();
647 switch (ev->direction) {
649 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
651 case GDK_SCROLL_DOWN:
652 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
654 case GDK_SCROLL_LEFT:
655 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
657 case GDK_SCROLL_RIGHT:
658 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
665 boost::shared_ptr<IO>
666 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
668 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
670 io = _ports[1].io_from_bundle (b);
677 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
679 return io_from_bundle (b);
683 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
685 boost::shared_ptr<IO> io = io_from_bundle (b);
688 int const r = io->add_port ("", this, t);
690 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
691 "support the new configuration."
693 msg.set_title (_("Cannot add port"));
700 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
702 return io_from_bundle (b);
706 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
708 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
711 boost::shared_ptr<Port> p = io->nth (b.channel);
713 int const r = io->remove_port (p, this);
715 ArdourDialog d (_("Port removal not allowed"));
716 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
717 d.get_vbox()->pack_start (l);
718 d.add_button (Stock::OK, RESPONSE_ACCEPT);
728 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
730 boost::shared_ptr<Bundle> b = w.lock ();
735 /* Remove channels backwards so that we don't renumber channels
736 that we are about to remove.
738 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
739 if (should_show (b->channel_type(i))) {
740 remove_channel (ARDOUR::BundleChannel (b, i));
746 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
748 boost::shared_ptr<Bundle> b = w.lock ();
757 PortMatrix::setup_notebooks ()
759 int const h_current_page = _hnotebook.get_current_page ();
760 int const v_current_page = _vnotebook.get_current_page ();
762 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
763 when adding or removing pages to or from notebooks, so ignore them */
765 _ignore_notebook_page_selected = true;
767 remove_notebook_pages (_hnotebook);
768 remove_notebook_pages (_vnotebook);
770 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
771 HBox* dummy = manage (new HBox);
773 Label* label = manage (new Label ((*i)->name));
774 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
776 if (_arrangement == LEFT_TO_BOTTOM) {
777 _vnotebook.prepend_page (*dummy, *label);
779 /* Reverse the order of vertical tabs when they are on the right hand side
780 so that from top to bottom it is the same order as that from left to right
783 _vnotebook.append_page (*dummy, *label);
787 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
788 HBox* dummy = manage (new HBox);
790 _hnotebook.append_page (*dummy, (*i)->name);
793 _ignore_notebook_page_selected = false;
795 if (_arrangement == TOP_TO_RIGHT) {
796 _vnotebook.set_tab_pos (POS_RIGHT);
797 _hnotebook.set_tab_pos (POS_TOP);
799 _vnotebook.set_tab_pos (POS_LEFT);
800 _hnotebook.set_tab_pos (POS_BOTTOM);
803 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
804 _hnotebook.set_current_page (h_current_page);
806 _hnotebook.set_current_page (0);
809 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
810 _vnotebook.set_current_page (v_current_page);
812 _vnotebook.set_current_page (0);
815 if (_hnotebook.get_n_pages() <= 1) {
821 if (_vnotebook.get_n_pages() <= 1) {
829 PortMatrix::remove_notebook_pages (Notebook& n)
831 int const N = n.get_n_pages ();
833 for (int i = 0; i < N; ++i) {
839 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
841 if (_ignore_notebook_page_selected) {
851 PortMatrix::session_going_away ()
857 PortMatrix::body_dimensions_changed ()
859 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
860 if (_arrangement == TOP_TO_RIGHT) {
861 _vspacer.set_size_request (-1, _body->column_labels_height ());
869 _parent->get_size (curr_width, curr_height);
871 pair<uint32_t, uint32_t> m = max_size ();
873 /* Don't shrink the window */
874 m.first = max (int (m.first), curr_width);
875 m.second = max (int (m.second), curr_height);
877 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
880 /** @return The PortGroup that is currently visible (ie selected by
881 * the notebook) along a given axis.
883 boost::shared_ptr<const PortGroup>
884 PortMatrix::visible_ports (int d) const
886 PortGroupList const & p = _ports[d];
887 PortGroupList::List::const_iterator j = p.begin ();
889 /* The logic to compute the index here is a bit twisty because for
890 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
891 tabs in setup_notebooks ().
895 if (d == _row_index) {
896 if (_arrangement == LEFT_TO_BOTTOM) {
897 n = p.size() - _vnotebook.get_current_page () - 1;
899 n = _vnotebook.get_current_page ();
902 n = _hnotebook.get_current_page ();
906 while (i != int (n) && j != p.end ()) {
912 return boost::shared_ptr<const PortGroup> ();
919 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
921 using namespace Menu_Helpers;
923 boost::shared_ptr<Bundle> b = w.lock ();
929 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
930 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
934 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
936 using namespace Menu_Helpers;
938 boost::shared_ptr<Bundle> b = w.lock ();
944 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
945 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
949 PortMatrix::port_connected_or_disconnected ()
951 _body->rebuild_and_draw_grid ();
955 PortMatrix::channel_noun () const
960 /** @return true if this matrix should show bundles / ports of type \t */
962 PortMatrix::should_show (DataType t) const
964 return (_type == DataType::NIL || t == _type);
968 PortMatrix::count_of_our_type (ChanCount c) const
970 if (_type == DataType::NIL) {
974 return c.get (_type);
977 /** @return The number of ports of our type in the given channel count,
978 * but returning 1 if there are no ports.
981 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
983 uint32_t n = count_of_our_type (c);
991 PortMatrixNode::State
992 PortMatrix::get_association (PortMatrixNode node) const
994 if (show_only_bundles ()) {
996 bool have_off_diagonal_association = false;
997 bool have_diagonal_association = false;
998 bool have_diagonal_not_association = false;
1000 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1002 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1004 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1008 ARDOUR::BundleChannel c[2];
1009 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1010 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1012 PortMatrixNode::State const s = get_state (c);
1015 case PortMatrixNode::ASSOCIATED:
1017 have_diagonal_association = true;
1019 have_off_diagonal_association = true;
1023 case PortMatrixNode::NOT_ASSOCIATED:
1025 have_diagonal_not_association = true;
1035 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1036 return PortMatrixNode::ASSOCIATED;
1037 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1038 return PortMatrixNode::NOT_ASSOCIATED;
1041 return PortMatrixNode::PARTIAL;
1045 ARDOUR::BundleChannel c[2];
1046 c[column_index()] = node.column;
1047 c[row_index()] = node.row;
1048 return get_state (c);
1053 return PortMatrixNode::NOT_ASSOCIATED;
1056 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1058 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1060 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;