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 "ardour/bundle.h"
30 #include "ardour/types.h"
31 #include "ardour/session.h"
32 #include "ardour/route.h"
33 #include "ardour/audioengine.h"
34 #include "port_matrix.h"
35 #include "port_matrix_body.h"
36 #include "port_matrix_component.h"
37 #include "ardour_dialog.h"
39 #include "gui_thread.h"
44 using namespace ARDOUR;
46 /** PortMatrix constructor.
47 * @param session Our session.
48 * @param type Port type that we are handling.
50 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
55 , _arrangement (TOP_TO_RIGHT)
58 , _min_height_divisor (1)
59 , _show_only_bundles (false)
60 , _inhibit_toggle_show_only_bundles (false)
61 , _ignore_notebook_page_selected (false)
63 set_session (session);
65 _body = new PortMatrixBody (this);
66 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
68 _hbox.pack_end (_hspacer, true, true);
69 _hbox.pack_end (_hnotebook, false, false);
70 _hbox.pack_end (_hlabel, false, false);
72 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
73 _vnotebook.property_tab_border() = 4;
74 _vnotebook.set_name (X_("PortMatrixLabel"));
75 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
76 _hnotebook.property_tab_border() = 4;
77 _hnotebook.set_name (X_("PortMatrixLabel"));
79 _vlabel.set_use_markup ();
80 _vlabel.set_alignment (1, 1);
81 _vlabel.set_padding (4, 16);
82 _vlabel.set_name (X_("PortMatrixLabel"));
83 _hlabel.set_use_markup ();
84 _hlabel.set_alignment (1, 0.5);
85 _hlabel.set_padding (16, 4);
86 _hlabel.set_name (X_("PortMatrixLabel"));
88 set_row_spacing (0, 8);
89 set_col_spacing (0, 8);
90 set_row_spacing (2, 8);
91 set_col_spacing (2, 8);
106 PortMatrix::~PortMatrix ()
112 /** Perform initial and once-only setup. This must be called by
113 * subclasses after they have set up _ports[] to at least some
114 * reasonable extent. Two-part initialisation is necessary because
115 * setting up _ports is largely done by virtual functions in
122 select_arrangement ();
124 /* Signal handling is kind of split into three parts:
126 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
127 * representation of the information in _ports[].
129 * 2. When certain other things change, we need to get our subclass to clear and
130 * re-fill _ports[], which in turn causes appropriate signals to be raised to
131 * hook into part (1).
133 * 3. Assorted other signals.
137 /* Part 1: the basic _ports[] change -> reset visuals */
139 for (int i = 0; i < 2; ++i) {
140 /* watch for the content of _ports[] changing */
141 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
143 /* and for bundles in _ports[] changing */
144 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
147 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
149 /* watch for routes being added or removed */
150 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
152 /* and also bundles */
153 _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
156 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
158 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
159 _session->RouteOrderKeyChanged.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
161 /* Part 3: other stuff */
163 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
165 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
166 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
168 reconnect_to_routes ();
173 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
175 PortMatrix::reconnect_to_routes ()
177 _route_connections.drop_connections ();
179 boost::shared_ptr<RouteList> routes = _session->get_routes ();
180 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
181 (*i)->processors_changed.connect (_route_connections, invalidator (*this), ui_bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
186 PortMatrix::route_processors_changed (RouteProcessorChange c)
188 if (c.type == RouteProcessorChange::MeterPointChange) {
189 /* this change has no impact on the port matrix */
193 setup_global_ports ();
196 /** A route has been added to or removed from the session */
198 PortMatrix::routes_changed ()
200 reconnect_to_routes ();
201 setup_global_ports ();
204 /** Set up everything that depends on the content of _ports[] */
208 /* this needs to be done first, as the visible_ports() method uses the
209 notebook state to decide which ports are being shown */
219 PortMatrix::set_type (DataType t)
225 PortMatrix::hscroll_changed ()
227 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
231 PortMatrix::vscroll_changed ()
233 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
237 PortMatrix::setup_scrollbars ()
239 Adjustment* a = _hscroll.get_adjustment ();
241 a->set_upper (_body->full_scroll_width());
242 a->set_page_size (_body->alloc_scroll_width());
243 a->set_step_increment (32);
244 a->set_page_increment (128);
246 a = _vscroll.get_adjustment ();
248 a->set_upper (_body->full_scroll_height());
249 a->set_page_size (_body->alloc_scroll_height());
250 a->set_step_increment (32);
251 a->set_page_increment (128);
254 /** Disassociate all of our ports from each other */
256 PortMatrix::disassociate_all ()
258 PortGroup::BundleList a = _ports[0].bundles ();
259 PortGroup::BundleList b = _ports[1].bundles ();
261 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
262 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
263 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
264 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
266 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
270 BundleChannel c[2] = {
271 BundleChannel ((*i)->bundle, j),
272 BundleChannel ((*k)->bundle, l)
275 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
276 set_state (c, false);
284 _body->rebuild_and_draw_grid ();
287 /* Decide how to arrange the components of the matrix */
289 PortMatrix::select_arrangement ()
291 uint32_t const N[2] = {
292 count_of_our_type_min_1 (_ports[0].total_channels()),
293 count_of_our_type_min_1 (_ports[1].total_channels())
296 /* XXX: shirley there's an easier way than this */
298 if (_vspacer.get_parent()) {
299 _vbox.remove (_vspacer);
302 if (_vnotebook.get_parent()) {
303 _vbox.remove (_vnotebook);
306 if (_vlabel.get_parent()) {
307 _vbox.remove (_vlabel);
310 /* The list with the most channels goes on left or right, so that the most channel
311 names are printed horizontally and hence more readable. However we also
312 maintain notional `signal flow' vaguely from left to right. Subclasses
313 should choose where to put ports based on signal flowing from _ports[0]
320 _arrangement = LEFT_TO_BOTTOM;
321 _vlabel.set_label (_("<b>Sources</b>"));
322 _hlabel.set_label (_("<b>Destinations</b>"));
323 _vlabel.set_angle (90);
325 _vbox.pack_end (_vlabel, false, false);
326 _vbox.pack_end (_vnotebook, false, false);
327 _vbox.pack_end (_vspacer, true, true);
329 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
330 attach (_vscroll, 3, 4, 1, 2, SHRINK);
331 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
332 attach (_vbox, 1, 2, 1, 2, SHRINK);
333 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
339 _arrangement = TOP_TO_RIGHT;
340 _hlabel.set_label (_("<b>Sources</b>"));
341 _vlabel.set_label (_("<b>Destinations</b>"));
342 _vlabel.set_angle (-90);
344 _vbox.pack_end (_vspacer, true, true);
345 _vbox.pack_end (_vnotebook, false, false);
346 _vbox.pack_end (_vlabel, false, false);
348 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
349 attach (_vscroll, 3, 4, 2, 3, SHRINK);
350 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
351 attach (_vbox, 2, 3, 2, 3, SHRINK);
352 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
356 /** @return columns list */
357 PortGroupList const *
358 PortMatrix::columns () const
360 return &_ports[_column_index];
363 boost::shared_ptr<const PortGroup>
364 PortMatrix::visible_columns () const
366 return visible_ports (_column_index);
369 /* @return rows list */
370 PortGroupList const *
371 PortMatrix::rows () const
373 return &_ports[_row_index];
376 boost::shared_ptr<const PortGroup>
377 PortMatrix::visible_rows () const
379 return visible_ports (_row_index);
382 /** @param column Column; its bundle may be 0 if we are over a row heading.
383 * @param row Row; its bundle may be 0 if we are over a column heading.
386 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
388 using namespace Menu_Helpers;
393 _menu->set_name ("ArdourContextMenu");
395 MenuList& items = _menu->items ();
398 bc[_column_index] = column;
399 bc[_row_index] = row;
402 bool need_separator = false;
404 for (int dim = 0; dim < 2; ++dim) {
406 if (bc[dim].bundle) {
408 Menu* m = manage (new Menu);
409 MenuList& sub = m->items ();
411 boost::weak_ptr<Bundle> w (bc[dim].bundle);
413 /* Start off with options for the `natural' port type */
414 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
415 if (should_show (*i)) {
416 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
417 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
421 /* Now add other ones */
422 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
423 if (!should_show (*i)) {
424 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
425 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
429 if (can_rename_channels (bc[dim].bundle)) {
431 buf, sizeof (buf), _("Rename '%s'..."),
432 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
437 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
442 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
443 if (bc[dim].channel != -1) {
444 add_remove_option (sub, w, bc[dim].channel);
447 snprintf (buf, sizeof (buf), _("Remove all"));
449 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
452 if (bc[dim].bundle->nchannels().n_total() > 1) {
453 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
454 if (should_show (bc[dim].bundle->channel_type(i))) {
455 add_remove_option (sub, w, i);
462 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
463 if ((_show_only_bundles && c > 0) || c == 1) {
465 /* we're looking just at bundles, or our bundle has only one channel, so just offer
466 to disassociate all on the bundle.
469 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
471 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
476 if (bc[dim].channel != -1) {
477 /* specific channel under the menu, so just offer to disassociate that */
478 add_disassociate_option (sub, w, dim, bc[dim].channel);
480 /* no specific channel; offer to disassociate all, or any one in particular */
481 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
483 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
486 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
487 if (should_show (bc[dim].bundle->channel_type(i))) {
488 add_disassociate_option (sub, w, dim, i);
494 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
495 need_separator = true;
500 if (need_separator) {
501 items.push_back (SeparatorElem ());
504 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
505 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
506 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
507 _inhibit_toggle_show_only_bundles = true;
508 i->set_active (!_show_only_bundles);
509 _inhibit_toggle_show_only_bundles = false;
515 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
517 boost::shared_ptr<Bundle> sb = b.lock ();
522 remove_channel (BundleChannel (sb, c));
527 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
529 boost::shared_ptr<Bundle> sb = b.lock ();
534 rename_channel (BundleChannel (sb, c));
538 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
540 boost::shared_ptr<Bundle> sb = bundle.lock ();
545 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
546 if (should_show (sb->channel_type(i))) {
547 disassociate_all_on_channel (bundle, i, dim);
553 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
555 boost::shared_ptr<Bundle> sb = bundle.lock ();
560 PortGroup::BundleList a = _ports[1-dim].bundles ();
562 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
563 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
565 if (!should_show ((*i)->bundle->channel_type(j))) {
570 c[dim] = BundleChannel (sb, channel);
571 c[1-dim] = BundleChannel ((*i)->bundle, j);
573 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
574 set_state (c, false);
579 _body->rebuild_and_draw_grid ();
583 PortMatrix::setup_global_ports ()
585 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
587 for (int i = 0; i < 2; ++i) {
588 if (list_is_global (i)) {
595 PortMatrix::setup_global_ports_proxy ()
597 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
601 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
605 PortMatrix::setup_all_ports ()
607 if (_session->deletion_in_progress()) {
611 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
618 PortMatrix::toggle_show_only_bundles ()
620 if (_inhibit_toggle_show_only_bundles) {
624 _show_only_bundles = !_show_only_bundles;
629 pair<uint32_t, uint32_t>
630 PortMatrix::max_size () const
632 pair<uint32_t, uint32_t> m = _body->max_size ();
634 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
635 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
641 PortMatrix::on_scroll_event (GdkEventScroll* ev)
643 double const h = _hscroll.get_value ();
644 double const v = _vscroll.get_value ();
646 switch (ev->direction) {
648 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
650 case GDK_SCROLL_DOWN:
651 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
653 case GDK_SCROLL_LEFT:
654 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
656 case GDK_SCROLL_RIGHT:
657 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
664 boost::shared_ptr<IO>
665 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
667 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
669 io = _ports[1].io_from_bundle (b);
676 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
678 return io_from_bundle (b);
682 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
684 boost::shared_ptr<IO> io = io_from_bundle (b);
687 io->add_port ("", this, t);
692 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
694 return io_from_bundle (b);
698 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
700 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
703 boost::shared_ptr<Port> p = io->nth (b.channel);
705 int const r = io->remove_port (p, this);
707 ArdourDialog d (_("Port removal not allowed"));
708 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
709 d.get_vbox()->pack_start (l);
710 d.add_button (Stock::OK, RESPONSE_ACCEPT);
720 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
722 boost::shared_ptr<Bundle> b = w.lock ();
727 /* Remove channels backwards so that we don't renumber channels
728 that we are about to remove.
730 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
731 if (should_show (b->channel_type(i))) {
732 remove_channel (ARDOUR::BundleChannel (b, i));
738 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
740 boost::shared_ptr<Bundle> b = w.lock ();
749 PortMatrix::setup_notebooks ()
751 int const h_current_page = _hnotebook.get_current_page ();
752 int const v_current_page = _vnotebook.get_current_page ();
754 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
755 when adding or removing pages to or from notebooks, so ignore them */
757 _ignore_notebook_page_selected = true;
759 remove_notebook_pages (_hnotebook);
760 remove_notebook_pages (_vnotebook);
762 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
763 HBox* dummy = manage (new HBox);
765 Label* label = manage (new Label ((*i)->name));
766 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
768 if (_arrangement == LEFT_TO_BOTTOM) {
769 _vnotebook.prepend_page (*dummy, *label);
771 /* Reverse the order of vertical tabs when they are on the right hand side
772 so that from top to bottom it is the same order as that from left to right
775 _vnotebook.append_page (*dummy, *label);
779 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
780 HBox* dummy = manage (new HBox);
782 _hnotebook.append_page (*dummy, (*i)->name);
785 _ignore_notebook_page_selected = false;
787 if (_arrangement == TOP_TO_RIGHT) {
788 _vnotebook.set_tab_pos (POS_RIGHT);
789 _hnotebook.set_tab_pos (POS_TOP);
791 _vnotebook.set_tab_pos (POS_LEFT);
792 _hnotebook.set_tab_pos (POS_BOTTOM);
795 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
796 _hnotebook.set_current_page (h_current_page);
798 _hnotebook.set_current_page (0);
801 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
802 _vnotebook.set_current_page (v_current_page);
804 _vnotebook.set_current_page (0);
807 if (_hnotebook.get_n_pages() <= 1) {
813 if (_vnotebook.get_n_pages() <= 1) {
821 PortMatrix::remove_notebook_pages (Notebook& n)
823 int const N = n.get_n_pages ();
825 for (int i = 0; i < N; ++i) {
831 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
833 if (_ignore_notebook_page_selected) {
843 PortMatrix::session_going_away ()
849 PortMatrix::body_dimensions_changed ()
851 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
852 if (_arrangement == TOP_TO_RIGHT) {
853 _vspacer.set_size_request (-1, _body->column_labels_height ());
861 _parent->get_size (curr_width, curr_height);
863 pair<uint32_t, uint32_t> m = max_size ();
865 /* Don't shrink the window */
866 m.first = max (int (m.first), curr_width);
867 m.second = max (int (m.second), curr_height);
869 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
872 /** @return The PortGroup that is currently visible (ie selected by
873 * the notebook) along a given axis.
875 boost::shared_ptr<const PortGroup>
876 PortMatrix::visible_ports (int d) const
878 PortGroupList const & p = _ports[d];
879 PortGroupList::List::const_iterator j = p.begin ();
881 /* The logic to compute the index here is a bit twisty because for
882 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
883 tabs in setup_notebooks ().
887 if (d == _row_index) {
888 if (_arrangement == LEFT_TO_BOTTOM) {
889 n = p.size() - _vnotebook.get_current_page () - 1;
891 n = _vnotebook.get_current_page ();
894 n = _hnotebook.get_current_page ();
898 while (i != int (n) && j != p.end ()) {
904 return boost::shared_ptr<const PortGroup> ();
911 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
913 using namespace Menu_Helpers;
915 boost::shared_ptr<Bundle> b = w.lock ();
921 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
922 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
926 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
928 using namespace Menu_Helpers;
930 boost::shared_ptr<Bundle> b = w.lock ();
936 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
937 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
941 PortMatrix::port_connected_or_disconnected ()
943 _body->rebuild_and_draw_grid ();
947 PortMatrix::channel_noun () const
952 /** @return true if this matrix should show bundles / ports of type \t */
954 PortMatrix::should_show (DataType t) const
956 return (_type == DataType::NIL || t == _type);
960 PortMatrix::count_of_our_type (ChanCount c) const
962 if (_type == DataType::NIL) {
966 return c.get (_type);
969 /** @return The number of ports of our type in the given channel count,
970 * but returning 1 if there are no ports.
973 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
975 uint32_t n = count_of_our_type (c);
983 PortMatrixNode::State
984 PortMatrix::get_association (PortMatrixNode node) const
986 if (show_only_bundles ()) {
988 bool have_off_diagonal_association = false;
989 bool have_diagonal_association = false;
990 bool have_diagonal_not_association = false;
992 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
994 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
996 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1000 ARDOUR::BundleChannel c[2];
1001 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1002 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1004 PortMatrixNode::State const s = get_state (c);
1007 case PortMatrixNode::ASSOCIATED:
1009 have_diagonal_association = true;
1011 have_off_diagonal_association = true;
1015 case PortMatrixNode::NOT_ASSOCIATED:
1017 have_diagonal_not_association = true;
1027 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1028 return PortMatrixNode::ASSOCIATED;
1029 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1030 return PortMatrixNode::NOT_ASSOCIATED;
1033 return PortMatrixNode::PARTIAL;
1037 ARDOUR::BundleChannel c[2];
1038 c[column_index()] = node.column;
1039 c[row_index()] = node.row;
1040 return get_state (c);
1045 return PortMatrixNode::NOT_ASSOCIATED;
1048 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1050 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1052 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;