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 if (can_add_channels (bc[dim].bundle)) {
415 /* Start off with options for the `natural' port type */
416 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
417 if (should_show (*i)) {
418 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
419 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
423 /* Now add other ones */
424 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
425 if (!should_show (*i)) {
426 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
427 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
432 if (can_rename_channels (bc[dim].bundle)) {
434 buf, sizeof (buf), _("Rename '%s'..."),
435 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
440 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
445 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
446 if (bc[dim].channel != -1) {
447 add_remove_option (sub, w, bc[dim].channel);
450 MenuElem (_("Remove all"), 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;
629 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
630 setting, so we need to set things up again now.
635 pair<uint32_t, uint32_t>
636 PortMatrix::max_size () const
638 pair<uint32_t, uint32_t> m = _body->max_size ();
640 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
641 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
647 PortMatrix::on_scroll_event (GdkEventScroll* ev)
649 double const h = _hscroll.get_value ();
650 double const v = _vscroll.get_value ();
652 switch (ev->direction) {
654 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
656 case GDK_SCROLL_DOWN:
657 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
659 case GDK_SCROLL_LEFT:
660 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
662 case GDK_SCROLL_RIGHT:
663 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
670 boost::shared_ptr<IO>
671 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
673 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
675 io = _ports[1].io_from_bundle (b);
682 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
684 return io_from_bundle (b);
688 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
690 boost::shared_ptr<IO> io = io_from_bundle (b);
693 int const r = io->add_port ("", this, t);
695 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
696 "support the new configuration."
698 msg.set_title (_("Cannot add port"));
705 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
707 return io_from_bundle (b);
711 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
713 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
716 boost::shared_ptr<Port> p = io->nth (b.channel);
718 int const r = io->remove_port (p, this);
720 ArdourDialog d (_("Port removal not allowed"));
721 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
722 d.get_vbox()->pack_start (l);
723 d.add_button (Stock::OK, RESPONSE_ACCEPT);
733 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
735 boost::shared_ptr<Bundle> b = w.lock ();
740 /* Remove channels backwards so that we don't renumber channels
741 that we are about to remove.
743 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
744 if (should_show (b->channel_type(i))) {
745 remove_channel (ARDOUR::BundleChannel (b, i));
751 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
753 boost::shared_ptr<Bundle> b = w.lock ();
762 PortMatrix::setup_notebooks ()
764 int const h_current_page = _hnotebook.get_current_page ();
765 int const v_current_page = _vnotebook.get_current_page ();
767 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
768 when adding or removing pages to or from notebooks, so ignore them */
770 _ignore_notebook_page_selected = true;
772 remove_notebook_pages (_hnotebook);
773 remove_notebook_pages (_vnotebook);
775 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
776 HBox* dummy = manage (new HBox);
778 Label* label = manage (new Label ((*i)->name));
779 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
781 if (_arrangement == LEFT_TO_BOTTOM) {
782 _vnotebook.prepend_page (*dummy, *label);
784 /* Reverse the order of vertical tabs when they are on the right hand side
785 so that from top to bottom it is the same order as that from left to right
788 _vnotebook.append_page (*dummy, *label);
792 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
793 HBox* dummy = manage (new HBox);
795 _hnotebook.append_page (*dummy, (*i)->name);
798 _ignore_notebook_page_selected = false;
800 if (_arrangement == TOP_TO_RIGHT) {
801 _vnotebook.set_tab_pos (POS_RIGHT);
802 _hnotebook.set_tab_pos (POS_TOP);
804 _vnotebook.set_tab_pos (POS_LEFT);
805 _hnotebook.set_tab_pos (POS_BOTTOM);
808 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
809 _hnotebook.set_current_page (h_current_page);
811 _hnotebook.set_current_page (0);
814 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
815 _vnotebook.set_current_page (v_current_page);
817 _vnotebook.set_current_page (0);
820 if (_hnotebook.get_n_pages() <= 1) {
826 if (_vnotebook.get_n_pages() <= 1) {
834 PortMatrix::remove_notebook_pages (Notebook& n)
836 int const N = n.get_n_pages ();
838 for (int i = 0; i < N; ++i) {
844 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
846 if (_ignore_notebook_page_selected) {
856 PortMatrix::session_going_away ()
862 PortMatrix::body_dimensions_changed ()
864 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
865 if (_arrangement == TOP_TO_RIGHT) {
866 _vspacer.set_size_request (-1, _body->column_labels_height ());
874 _parent->get_size (curr_width, curr_height);
876 pair<uint32_t, uint32_t> m = max_size ();
878 /* Don't shrink the window */
879 m.first = max (int (m.first), curr_width);
880 m.second = max (int (m.second), curr_height);
882 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
885 /** @return The PortGroup that is currently visible (ie selected by
886 * the notebook) along a given axis.
888 boost::shared_ptr<const PortGroup>
889 PortMatrix::visible_ports (int d) const
891 PortGroupList const & p = _ports[d];
892 PortGroupList::List::const_iterator j = p.begin ();
894 /* The logic to compute the index here is a bit twisty because for
895 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
896 tabs in setup_notebooks ().
900 if (d == _row_index) {
901 if (_arrangement == LEFT_TO_BOTTOM) {
902 n = p.size() - _vnotebook.get_current_page () - 1;
904 n = _vnotebook.get_current_page ();
907 n = _hnotebook.get_current_page ();
911 while (i != int (n) && j != p.end ()) {
917 return boost::shared_ptr<const PortGroup> ();
924 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
926 using namespace Menu_Helpers;
928 boost::shared_ptr<Bundle> b = w.lock ();
934 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
935 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
939 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
941 using namespace Menu_Helpers;
943 boost::shared_ptr<Bundle> b = w.lock ();
949 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
950 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
954 PortMatrix::port_connected_or_disconnected ()
956 _body->rebuild_and_draw_grid ();
960 PortMatrix::channel_noun () const
965 /** @return true if this matrix should show bundles / ports of type \t */
967 PortMatrix::should_show (DataType t) const
969 return (_type == DataType::NIL || t == _type);
973 PortMatrix::count_of_our_type (ChanCount c) const
975 if (_type == DataType::NIL) {
979 return c.get (_type);
982 /** @return The number of ports of our type in the given channel count,
983 * but returning 1 if there are no ports.
986 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
988 uint32_t n = count_of_our_type (c);
996 PortMatrixNode::State
997 PortMatrix::get_association (PortMatrixNode node) const
999 if (show_only_bundles ()) {
1001 bool have_off_diagonal_association = false;
1002 bool have_diagonal_association = false;
1003 bool have_diagonal_not_association = false;
1005 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1007 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1009 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1013 ARDOUR::BundleChannel c[2];
1014 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1015 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1017 PortMatrixNode::State const s = get_state (c);
1020 case PortMatrixNode::ASSOCIATED:
1022 have_diagonal_association = true;
1024 have_off_diagonal_association = true;
1028 case PortMatrixNode::NOT_ASSOCIATED:
1030 have_diagonal_not_association = true;
1040 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1041 return PortMatrixNode::ASSOCIATED;
1042 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1043 return PortMatrixNode::NOT_ASSOCIATED;
1046 return PortMatrixNode::PARTIAL;
1050 ARDOUR::BundleChannel c[2];
1051 c[column_index()] = node.column;
1052 c[row_index()] = node.row;
1053 return get_state (c);
1058 return PortMatrixNode::NOT_ASSOCIATED;
1061 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1063 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1065 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;