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 "gtkmm2ext/utils.h"
36 #include "port_matrix.h"
37 #include "port_matrix_body.h"
38 #include "port_matrix_component.h"
39 #include "ardour_dialog.h"
41 #include "gui_thread.h"
46 using namespace ARDOUR;
47 using namespace ARDOUR_UI_UTILS;
49 /** PortMatrix constructor.
50 * @param session Our session.
51 * @param type Port type that we are handling.
53 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
58 , _arrangement (TOP_TO_RIGHT)
61 , _min_height_divisor (1)
62 , _show_only_bundles (false)
63 , _inhibit_toggle_show_only_bundles (false)
64 , _ignore_notebook_page_selected (false)
66 set_session (session);
68 _body = new PortMatrixBody (this);
69 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
71 _hbox.pack_end (_hspacer, true, true);
72 _hbox.pack_end (_hnotebook, false, false);
73 _hbox.pack_end (_hlabel, false, false);
75 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
76 _vnotebook.property_tab_border() = 4;
77 _vnotebook.set_name (X_("PortMatrixLabel"));
78 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
79 _hnotebook.property_tab_border() = 4;
80 _hnotebook.set_name (X_("PortMatrixLabel"));
82 _vlabel.set_use_markup ();
83 _vlabel.set_alignment (1, 1);
84 _vlabel.set_padding (4, 16);
85 _vlabel.set_name (X_("PortMatrixLabel"));
86 _hlabel.set_use_markup ();
87 _hlabel.set_alignment (1, 0.5);
88 _hlabel.set_padding (16, 4);
89 _hlabel.set_name (X_("PortMatrixLabel"));
91 set_row_spacing (0, 8);
92 set_col_spacing (0, 8);
93 set_row_spacing (2, 8);
94 set_col_spacing (2, 8);
109 PortMatrix::~PortMatrix ()
115 /** Perform initial and once-only setup. This must be called by
116 * subclasses after they have set up _ports[] to at least some
117 * reasonable extent. Two-part initialisation is necessary because
118 * setting up _ports is largely done by virtual functions in
125 select_arrangement ();
127 /* Signal handling is kind of split into three parts:
129 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
130 * representation of the information in _ports[].
132 * 2. When certain other things change, we need to get our subclass to clear and
133 * re-fill _ports[], which in turn causes appropriate signals to be raised to
134 * hook into part (1).
136 * 3. Assorted other signals.
140 /* Part 1: the basic _ports[] change -> reset visuals */
142 for (int i = 0; i < 2; ++i) {
143 /* watch for the content of _ports[] changing */
144 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
146 /* and for bundles in _ports[] changing */
147 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
150 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
152 /* watch for routes being added or removed */
153 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
155 /* and also bundles */
156 _session->BundleAddedOrRemoved.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
159 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
161 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
162 PresentationInfo::Change.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
164 /* Part 3: other stuff */
166 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
168 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
169 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
171 reconnect_to_routes ();
176 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
178 PortMatrix::reconnect_to_routes ()
180 _route_connections.drop_connections ();
182 boost::shared_ptr<RouteList> routes = _session->get_routes ();
183 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
184 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
185 (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
190 PortMatrix::route_processors_changed (RouteProcessorChange c)
192 if (c.type == RouteProcessorChange::MeterPointChange) {
193 /* this change has no impact on the port matrix */
197 setup_global_ports ();
200 /** A route has been added to or removed from the session */
202 PortMatrix::routes_changed ()
204 if (!_session) return;
205 reconnect_to_routes ();
206 setup_global_ports ();
209 /** Set up everything that depends on the content of _ports[] */
214 _route_connections.drop_connections ();
215 return; // session went away
218 /* this needs to be done first, as the visible_ports() method uses the
219 notebook state to decide which ports are being shown */
225 update_tab_highlighting ();
230 PortMatrix::set_type (DataType t)
236 PortMatrix::hscroll_changed ()
238 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
242 PortMatrix::vscroll_changed ()
244 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
248 PortMatrix::setup_scrollbars ()
250 Adjustment* a = _hscroll.get_adjustment ();
252 a->set_page_size (_body->alloc_scroll_width());
253 a->set_step_increment (32);
254 a->set_page_increment (128);
256 /* Set the adjustment to zero if the size has changed.*/
257 if (a->get_upper() != _body->full_scroll_width()) {
258 a->set_upper (_body->full_scroll_width());
262 a = _vscroll.get_adjustment ();
264 a->set_page_size (_body->alloc_scroll_height());
265 a->set_step_increment (32);
266 a->set_page_increment (128);
268 if (a->get_upper() != _body->full_scroll_height()) {
269 a->set_upper (_body->full_scroll_height());
274 /** Disassociate all of our ports from each other */
276 PortMatrix::disassociate_all ()
278 PortGroup::BundleList a = _ports[0].bundles ();
279 PortGroup::BundleList b = _ports[1].bundles ();
281 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
282 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
283 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
284 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
286 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
290 BundleChannel c[2] = {
291 BundleChannel ((*i)->bundle, j),
292 BundleChannel ((*k)->bundle, l)
295 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
296 set_state (c, false);
304 _body->rebuild_and_draw_grid ();
307 /* Decide how to arrange the components of the matrix */
309 PortMatrix::select_arrangement ()
311 uint32_t const N[2] = {
312 count_of_our_type_min_1 (_ports[0].total_channels()),
313 count_of_our_type_min_1 (_ports[1].total_channels())
316 /* XXX: shirley there's an easier way than this */
318 if (_vspacer.get_parent()) {
319 _vbox.remove (_vspacer);
322 if (_vnotebook.get_parent()) {
323 _vbox.remove (_vnotebook);
326 if (_vlabel.get_parent()) {
327 _vbox.remove (_vlabel);
330 /* The list with the most channels goes on left or right, so that the most channel
331 names are printed horizontally and hence more readable. However we also
332 maintain notional `signal flow' vaguely from left to right. Subclasses
333 should choose where to put ports based on signal flowing from _ports[0]
340 _arrangement = LEFT_TO_BOTTOM;
341 _vlabel.set_label (_("<b>Sources</b>"));
342 _hlabel.set_label (_("<b>Destinations</b>"));
343 _vlabel.set_angle (90);
345 _vbox.pack_end (_vlabel, false, false);
346 _vbox.pack_end (_vnotebook, false, false);
347 _vbox.pack_end (_vspacer, true, true);
349 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
350 REMOVE_FROM_GTK_PARENT(*_body)
351 REMOVE_FROM_GTK_PARENT(_vscroll)
352 REMOVE_FROM_GTK_PARENT(_hscroll)
353 REMOVE_FROM_GTK_PARENT(_vbox)
354 REMOVE_FROM_GTK_PARENT(_hbox)
356 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
357 attach (_vscroll, 3, 4, 1, 2, SHRINK);
358 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
359 attach (_vbox, 1, 2, 1, 2, SHRINK);
360 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
366 _arrangement = TOP_TO_RIGHT;
367 _hlabel.set_label (_("<b>Sources</b>"));
368 _vlabel.set_label (_("<b>Destinations</b>"));
369 _vlabel.set_angle (-90);
371 _vbox.pack_end (_vspacer, true, true);
372 _vbox.pack_end (_vnotebook, false, false);
373 _vbox.pack_end (_vlabel, false, false);
375 REMOVE_FROM_GTK_PARENT(*_body)
376 REMOVE_FROM_GTK_PARENT(_vscroll)
377 REMOVE_FROM_GTK_PARENT(_hscroll)
378 REMOVE_FROM_GTK_PARENT(_vbox)
379 REMOVE_FROM_GTK_PARENT(_hbox)
381 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
382 attach (_vscroll, 3, 4, 2, 3, SHRINK);
383 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
384 attach (_vbox, 2, 3, 2, 3, SHRINK);
385 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
389 /** @return columns list */
390 PortGroupList const *
391 PortMatrix::columns () const
393 return &_ports[_column_index];
396 boost::shared_ptr<const PortGroup>
397 PortMatrix::visible_columns () const
399 return visible_ports (_column_index);
402 /* @return rows list */
403 PortGroupList const *
404 PortMatrix::rows () const
406 return &_ports[_row_index];
409 boost::shared_ptr<const PortGroup>
410 PortMatrix::visible_rows () const
412 return visible_ports (_row_index);
415 /** @param column Column; its bundle may be 0 if we are over a row heading.
416 * @param row Row; its bundle may be 0 if we are over a column heading.
419 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
421 using namespace Menu_Helpers;
426 _menu->set_name ("ArdourContextMenu");
428 MenuList& items = _menu->items ();
431 bc[_column_index] = column;
432 bc[_row_index] = row;
435 bool need_separator = false;
437 for (int dim = 0; dim < 2; ++dim) {
439 if (bc[dim].bundle) {
441 Menu* m = manage (new Menu);
442 MenuList& sub = m->items ();
444 boost::weak_ptr<Bundle> w (bc[dim].bundle);
446 if (can_add_channels (bc[dim].bundle)) {
447 /* Start off with options for the `natural' port type */
448 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
449 if (should_show (*i) && can_add_channel_proxy (w, *i)) {
450 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
451 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
455 /* Now add other ones */
456 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
457 if (!should_show (*i) && can_add_channel_proxy (w, *i)) {
458 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
459 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
464 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
466 buf, sizeof (buf), _("Rename '%s'..."),
467 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
472 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
477 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
478 if (bc[dim].channel != -1) {
479 add_remove_option (sub, w, bc[dim].channel);
482 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
485 if (bc[dim].bundle->nchannels().n_total() > 1) {
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_remove_option (sub, w, i);
495 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
496 if ((_show_only_bundles && c > 0) || c == 1) {
498 /* we're looking just at bundles, or our bundle has only one channel, so just offer
499 to disassociate all on the bundle.
502 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
504 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
509 if (bc[dim].channel != -1) {
510 /* specific channel under the menu, so just offer to disassociate that */
511 add_disassociate_option (sub, w, dim, bc[dim].channel);
513 /* no specific channel; offer to disassociate all, or any one in particular */
514 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
516 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
519 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
520 if (should_show (bc[dim].bundle->channel_type(i))) {
521 add_disassociate_option (sub, w, dim, i);
527 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
528 need_separator = true;
533 if (need_separator) {
534 items.push_back (SeparatorElem ());
537 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
539 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
540 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
541 _inhibit_toggle_show_only_bundles = true;
542 i->set_active (!_show_only_bundles);
543 _inhibit_toggle_show_only_bundles = false;
545 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
546 items.back().set_sensitive (can_flip ());
552 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
554 boost::shared_ptr<Bundle> sb = b.lock ();
559 remove_channel (BundleChannel (sb, c));
564 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
566 boost::shared_ptr<Bundle> sb = b.lock ();
571 rename_channel (BundleChannel (sb, c));
575 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
577 boost::shared_ptr<Bundle> sb = bundle.lock ();
582 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
583 if (should_show (sb->channel_type(i))) {
584 disassociate_all_on_channel (bundle, i, dim);
590 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
592 boost::shared_ptr<Bundle> sb = bundle.lock ();
597 PortGroup::BundleList a = _ports[1-dim].bundles ();
599 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
600 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
602 if (!should_show ((*i)->bundle->channel_type(j))) {
607 c[dim] = BundleChannel (sb, channel);
608 c[1-dim] = BundleChannel ((*i)->bundle, j);
610 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
611 set_state (c, false);
616 _body->rebuild_and_draw_grid ();
620 PortMatrix::setup_global_ports ()
622 if (!_session || _session->deletion_in_progress()) return;
623 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
625 for (int i = 0; i < 2; ++i) {
626 if (list_is_global (i)) {
633 PortMatrix::setup_global_ports_proxy ()
635 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
639 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
643 PortMatrix::setup_all_ports ()
645 if (_session->deletion_in_progress()) {
649 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
656 PortMatrix::toggle_show_only_bundles ()
658 if (_inhibit_toggle_show_only_bundles) {
662 _show_only_bundles = !_show_only_bundles;
666 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
667 setting, so we need to set things up again now.
672 pair<uint32_t, uint32_t>
673 PortMatrix::max_size () const
675 pair<uint32_t, uint32_t> m = _body->max_size ();
677 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
678 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
684 PortMatrix::on_scroll_event (GdkEventScroll* ev)
686 double const h = _hscroll.get_value ();
687 double const v = _vscroll.get_value ();
689 switch (ev->direction) {
691 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
693 case GDK_SCROLL_DOWN:
694 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
696 case GDK_SCROLL_LEFT:
697 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
699 case GDK_SCROLL_RIGHT:
700 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
707 boost::shared_ptr<IO>
708 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
710 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
712 io = _ports[1].io_from_bundle (b);
719 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
721 return io_from_bundle (b) != 0;
725 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
727 boost::shared_ptr<IO> io = io_from_bundle (b);
730 int const r = io->add_port ("", this, t);
732 Gtk::MessageDialog msg (_("It is not possible to add a port here."));
733 msg.set_title (_("Cannot add port"));
740 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
742 return io_from_bundle (b) != 0;
746 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
749 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
750 boost::shared_ptr<Port> p = io->nth (b.channel);
756 if (io->n_ports ().n_total () == 1) {
757 errmsg = _("The last port cannot be removed");
759 if (-1 == io->remove_port (p, this)) {
760 errmsg = _("This port cannot be removed.");
764 if (!errmsg.empty ()) {
765 ArdourDialog d (_("Port removal not allowed"));
767 d.get_vbox()->pack_start (l);
768 d.add_button (Stock::OK, RESPONSE_ACCEPT);
776 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
778 boost::shared_ptr<Bundle> b = w.lock ();
783 /* Remove channels backwards so that we don't renumber channels
784 that we are about to remove.
786 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
787 if (should_show (b->channel_type(i))) {
788 remove_channel (ARDOUR::BundleChannel (b, i));
794 PortMatrix::can_add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t) const
796 boost::shared_ptr<Bundle> b = w.lock ();
800 boost::shared_ptr<IO> io = io_from_bundle (b);
801 return io->can_add_port (t);
805 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
807 boost::shared_ptr<Bundle> b = w.lock ();
816 PortMatrix::setup_notebooks ()
818 int const h_current_page = _hnotebook.get_current_page ();
819 int const v_current_page = _vnotebook.get_current_page ();
821 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
822 when adding or removing pages to or from notebooks, so ignore them */
824 _ignore_notebook_page_selected = true;
826 remove_notebook_pages (_hnotebook);
827 remove_notebook_pages (_vnotebook);
829 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
830 HBox* dummy = manage (new HBox);
832 Label* label = manage (new Label ((*i)->name));
833 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
834 label->set_use_markup ();
836 if (_arrangement == LEFT_TO_BOTTOM) {
837 _vnotebook.prepend_page (*dummy, *label);
839 /* Reverse the order of vertical tabs when they are on the right hand side
840 so that from top to bottom it is the same order as that from left to right
843 _vnotebook.append_page (*dummy, *label);
847 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
848 HBox* dummy = manage (new HBox);
850 Label* label = manage (new Label ((*i)->name));
851 label->set_use_markup ();
853 _hnotebook.append_page (*dummy, *label);
856 _ignore_notebook_page_selected = false;
858 if (_arrangement == TOP_TO_RIGHT) {
859 _vnotebook.set_tab_pos (POS_RIGHT);
860 _hnotebook.set_tab_pos (POS_TOP);
862 _vnotebook.set_tab_pos (POS_LEFT);
863 _hnotebook.set_tab_pos (POS_BOTTOM);
866 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
867 _hnotebook.set_current_page (h_current_page);
869 _hnotebook.set_current_page (0);
872 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
873 _vnotebook.set_current_page (v_current_page);
875 _vnotebook.set_current_page (0);
878 if (_hnotebook.get_n_pages() <= 1) {
884 if (_vnotebook.get_n_pages() <= 1) {
892 PortMatrix::remove_notebook_pages (Notebook& n)
894 int const N = n.get_n_pages ();
896 for (int i = 0; i < N; ++i) {
902 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
904 if (_ignore_notebook_page_selected) {
914 PortMatrix::session_going_away ()
920 PortMatrix::body_dimensions_changed ()
922 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
923 if (_arrangement == TOP_TO_RIGHT) {
924 _vspacer.set_size_request (-1, _body->column_labels_height ());
932 _parent->get_size (curr_width, curr_height);
934 pair<uint32_t, uint32_t> m = max_size ();
936 /* Don't shrink the window */
937 m.first = max (int (m.first), curr_width);
938 m.second = max (int (m.second), curr_height);
940 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
943 /** @return The PortGroup that is currently visible (ie selected by
944 * the notebook) along a given axis.
946 boost::shared_ptr<const PortGroup>
947 PortMatrix::visible_ports (int d) const
949 PortGroupList const & p = _ports[d];
950 PortGroupList::List::const_iterator j = p.begin ();
952 /* The logic to compute the index here is a bit twisty because for
953 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
954 tabs in setup_notebooks ().
958 if (d == _row_index) {
959 if (_arrangement == LEFT_TO_BOTTOM) {
960 n = p.size() - _vnotebook.get_current_page () - 1;
962 n = _vnotebook.get_current_page ();
965 n = _hnotebook.get_current_page ();
969 while (i != int (n) && j != p.end ()) {
975 return boost::shared_ptr<const PortGroup> ();
982 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
984 using namespace Menu_Helpers;
986 boost::shared_ptr<Bundle> b = w.lock ();
992 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
993 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
997 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
999 using namespace Menu_Helpers;
1001 boost::shared_ptr<Bundle> b = w.lock ();
1007 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
1008 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1012 PortMatrix::port_connected_or_disconnected ()
1014 _body->rebuild_and_draw_grid ();
1015 update_tab_highlighting ();
1018 /** Update the highlighting of tab names to reflect which ones
1019 * have connections. This is pretty inefficient, unfortunately,
1020 * but maybe that doesn't matter too much.
1023 PortMatrix::update_tab_highlighting ()
1029 for (int i = 0; i < 2; ++i) {
1031 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1033 PortGroupList const * gl = ports (i);
1035 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1036 bool has_connection = false;
1037 PortGroup::BundleList const & bl = (*j)->bundles ();
1038 PortGroup::BundleList::const_iterator k = bl.begin ();
1039 while (k != bl.end()) {
1040 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1041 has_connection = true;
1047 /* Find the page index that we should update; this is backwards
1048 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1051 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1052 page = notebook->get_n_pages() - p - 1;
1055 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1056 string c = label->get_label ();
1057 if (c.length() && c[0] == '<' && !has_connection) {
1058 /* this label is marked up with <b> but shouldn't be */
1059 label->set_text ((*j)->name);
1060 } else if (c.length() && c[0] != '<' && has_connection) {
1061 /* this label is not marked up with <b> but should be */
1062 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1071 PortMatrix::channel_noun () const
1073 return _("channel");
1076 /** @return true if this matrix should show bundles / ports of type \t */
1078 PortMatrix::should_show (DataType t) const
1080 return (_type == DataType::NIL || t == _type);
1084 PortMatrix::count_of_our_type (ChanCount c) const
1086 if (_type == DataType::NIL) {
1087 return c.n_total ();
1090 return c.get (_type);
1093 /** @return The number of ports of our type in the given channel count,
1094 * but returning 1 if there are no ports.
1097 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1099 uint32_t n = count_of_our_type (c);
1107 PortMatrixNode::State
1108 PortMatrix::get_association (PortMatrixNode node) const
1110 if (show_only_bundles ()) {
1112 bool have_off_diagonal_association = false;
1113 bool have_diagonal_association = false;
1114 bool have_diagonal_not_association = false;
1116 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1118 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1120 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1124 ARDOUR::BundleChannel c[2];
1125 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1126 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1128 PortMatrixNode::State const s = get_state (c);
1131 case PortMatrixNode::ASSOCIATED:
1133 have_diagonal_association = true;
1135 have_off_diagonal_association = true;
1139 case PortMatrixNode::NOT_ASSOCIATED:
1141 have_diagonal_not_association = true;
1151 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1152 return PortMatrixNode::ASSOCIATED;
1153 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1154 return PortMatrixNode::NOT_ASSOCIATED;
1157 return PortMatrixNode::PARTIAL;
1161 ARDOUR::BundleChannel c[2];
1162 c[column_index()] = node.column;
1163 c[row_index()] = node.row;
1164 return get_state (c);
1168 abort(); /* NOTREACHED */
1169 return PortMatrixNode::NOT_ASSOCIATED;
1172 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1174 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1176 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1179 /** See if a `flip' is possible.
1180 * @return If flip is possible, the new (row, column) notebook indices that
1181 * should be selected; otherwise, (-1, -1)
1184 PortMatrix::check_flip () const
1186 /* Look for the row's port group name in the columns */
1189 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1190 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1191 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1196 if (i == _ports[_column_index].end ()) {
1197 return make_pair (-1, -1);
1200 /* Look for the column's port group name in the rows */
1203 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1204 i = _ports[_row_index].begin();
1205 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1210 if (i == _ports[_row_index].end ()) {
1211 return make_pair (-1, -1);
1214 if (_arrangement == LEFT_TO_BOTTOM) {
1215 new_row = _ports[_row_index].size() - new_row - 1;
1218 return make_pair (new_row, new_column);
1222 PortMatrix::can_flip () const
1224 return check_flip().first != -1;
1227 /** Flip the column and row pages around, if possible */
1231 pair<int, int> n = check_flip ();
1232 if (n.first == -1) {
1236 _vnotebook.set_current_page (n.first);
1237 _hnotebook.set_current_page (n.second);
1241 PortMatrix::key_press (GdkEventKey* k)
1243 if (k->keyval == GDK_f) {