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 Route::SyncOrderKeys.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)) {
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)) {
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::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
796 boost::shared_ptr<Bundle> b = w.lock ();
805 PortMatrix::setup_notebooks ()
807 int const h_current_page = _hnotebook.get_current_page ();
808 int const v_current_page = _vnotebook.get_current_page ();
810 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
811 when adding or removing pages to or from notebooks, so ignore them */
813 _ignore_notebook_page_selected = true;
815 remove_notebook_pages (_hnotebook);
816 remove_notebook_pages (_vnotebook);
818 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
819 HBox* dummy = manage (new HBox);
821 Label* label = manage (new Label ((*i)->name));
822 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
823 label->set_use_markup ();
825 if (_arrangement == LEFT_TO_BOTTOM) {
826 _vnotebook.prepend_page (*dummy, *label);
828 /* Reverse the order of vertical tabs when they are on the right hand side
829 so that from top to bottom it is the same order as that from left to right
832 _vnotebook.append_page (*dummy, *label);
836 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
837 HBox* dummy = manage (new HBox);
839 Label* label = manage (new Label ((*i)->name));
840 label->set_use_markup ();
842 _hnotebook.append_page (*dummy, *label);
845 _ignore_notebook_page_selected = false;
847 if (_arrangement == TOP_TO_RIGHT) {
848 _vnotebook.set_tab_pos (POS_RIGHT);
849 _hnotebook.set_tab_pos (POS_TOP);
851 _vnotebook.set_tab_pos (POS_LEFT);
852 _hnotebook.set_tab_pos (POS_BOTTOM);
855 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
856 _hnotebook.set_current_page (h_current_page);
858 _hnotebook.set_current_page (0);
861 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
862 _vnotebook.set_current_page (v_current_page);
864 _vnotebook.set_current_page (0);
867 if (_hnotebook.get_n_pages() <= 1) {
873 if (_vnotebook.get_n_pages() <= 1) {
881 PortMatrix::remove_notebook_pages (Notebook& n)
883 int const N = n.get_n_pages ();
885 for (int i = 0; i < N; ++i) {
891 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
893 if (_ignore_notebook_page_selected) {
903 PortMatrix::session_going_away ()
909 PortMatrix::body_dimensions_changed ()
911 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
912 if (_arrangement == TOP_TO_RIGHT) {
913 _vspacer.set_size_request (-1, _body->column_labels_height ());
921 _parent->get_size (curr_width, curr_height);
923 pair<uint32_t, uint32_t> m = max_size ();
925 /* Don't shrink the window */
926 m.first = max (int (m.first), curr_width);
927 m.second = max (int (m.second), curr_height);
929 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
932 /** @return The PortGroup that is currently visible (ie selected by
933 * the notebook) along a given axis.
935 boost::shared_ptr<const PortGroup>
936 PortMatrix::visible_ports (int d) const
938 PortGroupList const & p = _ports[d];
939 PortGroupList::List::const_iterator j = p.begin ();
941 /* The logic to compute the index here is a bit twisty because for
942 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
943 tabs in setup_notebooks ().
947 if (d == _row_index) {
948 if (_arrangement == LEFT_TO_BOTTOM) {
949 n = p.size() - _vnotebook.get_current_page () - 1;
951 n = _vnotebook.get_current_page ();
954 n = _hnotebook.get_current_page ();
958 while (i != int (n) && j != p.end ()) {
964 return boost::shared_ptr<const PortGroup> ();
971 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
973 using namespace Menu_Helpers;
975 boost::shared_ptr<Bundle> b = w.lock ();
981 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
982 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
986 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
988 using namespace Menu_Helpers;
990 boost::shared_ptr<Bundle> b = w.lock ();
996 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
997 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1001 PortMatrix::port_connected_or_disconnected ()
1003 _body->rebuild_and_draw_grid ();
1004 update_tab_highlighting ();
1007 /** Update the highlighting of tab names to reflect which ones
1008 * have connections. This is pretty inefficient, unfortunately,
1009 * but maybe that doesn't matter too much.
1012 PortMatrix::update_tab_highlighting ()
1018 for (int i = 0; i < 2; ++i) {
1020 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1022 PortGroupList const * gl = ports (i);
1024 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1025 bool has_connection = false;
1026 PortGroup::BundleList const & bl = (*j)->bundles ();
1027 PortGroup::BundleList::const_iterator k = bl.begin ();
1028 while (k != bl.end()) {
1029 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1030 has_connection = true;
1036 /* Find the page index that we should update; this is backwards
1037 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1040 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1041 page = notebook->get_n_pages() - p - 1;
1044 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1045 string c = label->get_label ();
1046 if (c.length() && c[0] == '<' && !has_connection) {
1047 /* this label is marked up with <b> but shouldn't be */
1048 label->set_text ((*j)->name);
1049 } else if (c.length() && c[0] != '<' && has_connection) {
1050 /* this label is not marked up with <b> but should be */
1051 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1060 PortMatrix::channel_noun () const
1062 return _("channel");
1065 /** @return true if this matrix should show bundles / ports of type \t */
1067 PortMatrix::should_show (DataType t) const
1069 return (_type == DataType::NIL || t == _type);
1073 PortMatrix::count_of_our_type (ChanCount c) const
1075 if (_type == DataType::NIL) {
1076 return c.n_total ();
1079 return c.get (_type);
1082 /** @return The number of ports of our type in the given channel count,
1083 * but returning 1 if there are no ports.
1086 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1088 uint32_t n = count_of_our_type (c);
1096 PortMatrixNode::State
1097 PortMatrix::get_association (PortMatrixNode node) const
1099 if (show_only_bundles ()) {
1101 bool have_off_diagonal_association = false;
1102 bool have_diagonal_association = false;
1103 bool have_diagonal_not_association = false;
1105 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1107 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1109 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1113 ARDOUR::BundleChannel c[2];
1114 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1115 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1117 PortMatrixNode::State const s = get_state (c);
1120 case PortMatrixNode::ASSOCIATED:
1122 have_diagonal_association = true;
1124 have_off_diagonal_association = true;
1128 case PortMatrixNode::NOT_ASSOCIATED:
1130 have_diagonal_not_association = true;
1140 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1141 return PortMatrixNode::ASSOCIATED;
1142 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1143 return PortMatrixNode::NOT_ASSOCIATED;
1146 return PortMatrixNode::PARTIAL;
1150 ARDOUR::BundleChannel c[2];
1151 c[column_index()] = node.column;
1152 c[row_index()] = node.row;
1153 return get_state (c);
1157 abort(); /* NOTREACHED */
1158 return PortMatrixNode::NOT_ASSOCIATED;
1161 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1163 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1165 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1168 /** See if a `flip' is possible.
1169 * @return If flip is possible, the new (row, column) notebook indices that
1170 * should be selected; otherwise, (-1, -1)
1173 PortMatrix::check_flip () const
1175 /* Look for the row's port group name in the columns */
1178 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1179 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1180 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1185 if (i == _ports[_column_index].end ()) {
1186 return make_pair (-1, -1);
1189 /* Look for the column's port group name in the rows */
1192 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1193 i = _ports[_row_index].begin();
1194 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1199 if (i == _ports[_row_index].end ()) {
1200 return make_pair (-1, -1);
1203 if (_arrangement == LEFT_TO_BOTTOM) {
1204 new_row = _ports[_row_index].size() - new_row - 1;
1207 return make_pair (new_row, new_column);
1211 PortMatrix::can_flip () const
1213 return check_flip().first != -1;
1216 /** Flip the column and row pages around, if possible */
1220 pair<int, int> n = check_flip ();
1221 if (n.first == -1) {
1225 _vnotebook.set_current_page (n.first);
1226 _hnotebook.set_current_page (n.second);
1230 PortMatrix::key_press (GdkEventKey* k)
1232 if (k->keyval == GDK_f) {