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;
46 using namespace ARDOUR_UI_UTILS;
48 /** PortMatrix constructor.
49 * @param session Our session.
50 * @param type Port type that we are handling.
52 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
57 , _arrangement (TOP_TO_RIGHT)
60 , _min_height_divisor (1)
61 , _show_only_bundles (false)
62 , _inhibit_toggle_show_only_bundles (false)
63 , _ignore_notebook_page_selected (false)
65 set_session (session);
67 _body = new PortMatrixBody (this);
68 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
70 _hbox.pack_end (_hspacer, true, true);
71 _hbox.pack_end (_hnotebook, false, false);
72 _hbox.pack_end (_hlabel, false, false);
74 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
75 _vnotebook.property_tab_border() = 4;
76 _vnotebook.set_name (X_("PortMatrixLabel"));
77 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
78 _hnotebook.property_tab_border() = 4;
79 _hnotebook.set_name (X_("PortMatrixLabel"));
81 _vlabel.set_use_markup ();
82 _vlabel.set_alignment (1, 1);
83 _vlabel.set_padding (4, 16);
84 _vlabel.set_name (X_("PortMatrixLabel"));
85 _hlabel.set_use_markup ();
86 _hlabel.set_alignment (1, 0.5);
87 _hlabel.set_padding (16, 4);
88 _hlabel.set_name (X_("PortMatrixLabel"));
90 set_row_spacing (0, 8);
91 set_col_spacing (0, 8);
92 set_row_spacing (2, 8);
93 set_col_spacing (2, 8);
108 PortMatrix::~PortMatrix ()
114 /** Perform initial and once-only setup. This must be called by
115 * subclasses after they have set up _ports[] to at least some
116 * reasonable extent. Two-part initialisation is necessary because
117 * setting up _ports is largely done by virtual functions in
124 select_arrangement ();
126 /* Signal handling is kind of split into three parts:
128 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
129 * representation of the information in _ports[].
131 * 2. When certain other things change, we need to get our subclass to clear and
132 * re-fill _ports[], which in turn causes appropriate signals to be raised to
133 * hook into part (1).
135 * 3. Assorted other signals.
139 /* Part 1: the basic _ports[] change -> reset visuals */
141 for (int i = 0; i < 2; ++i) {
142 /* watch for the content of _ports[] changing */
143 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
145 /* and for bundles in _ports[] changing */
146 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
149 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
151 /* watch for routes being added or removed */
152 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
154 /* and also bundles */
155 _session->BundleAddedOrRemoved.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
158 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
160 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
161 Route::SyncOrderKeys.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
163 /* Part 3: other stuff */
165 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
167 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
168 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
170 reconnect_to_routes ();
175 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
177 PortMatrix::reconnect_to_routes ()
179 _route_connections.drop_connections ();
181 boost::shared_ptr<RouteList> routes = _session->get_routes ();
182 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
183 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
184 (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
189 PortMatrix::route_processors_changed (RouteProcessorChange c)
191 if (c.type == RouteProcessorChange::MeterPointChange) {
192 /* this change has no impact on the port matrix */
196 setup_global_ports ();
199 /** A route has been added to or removed from the session */
201 PortMatrix::routes_changed ()
203 if (!_session) return;
204 reconnect_to_routes ();
205 setup_global_ports ();
208 /** Set up everything that depends on the content of _ports[] */
213 _route_connections.drop_connections ();
214 return; // session went away
217 /* this needs to be done first, as the visible_ports() method uses the
218 notebook state to decide which ports are being shown */
224 update_tab_highlighting ();
229 PortMatrix::set_type (DataType t)
235 PortMatrix::hscroll_changed ()
237 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
241 PortMatrix::vscroll_changed ()
243 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
247 PortMatrix::setup_scrollbars ()
249 Adjustment* a = _hscroll.get_adjustment ();
251 a->set_page_size (_body->alloc_scroll_width());
252 a->set_step_increment (32);
253 a->set_page_increment (128);
255 /* Set the adjustment to zero if the size has changed.*/
256 if (a->get_upper() != _body->full_scroll_width()) {
257 a->set_upper (_body->full_scroll_width());
261 a = _vscroll.get_adjustment ();
263 a->set_page_size (_body->alloc_scroll_height());
264 a->set_step_increment (32);
265 a->set_page_increment (128);
267 if (a->get_upper() != _body->full_scroll_height()) {
268 a->set_upper (_body->full_scroll_height());
273 /** Disassociate all of our ports from each other */
275 PortMatrix::disassociate_all ()
277 PortGroup::BundleList a = _ports[0].bundles ();
278 PortGroup::BundleList b = _ports[1].bundles ();
280 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
281 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
282 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
283 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
285 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
289 BundleChannel c[2] = {
290 BundleChannel ((*i)->bundle, j),
291 BundleChannel ((*k)->bundle, l)
294 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
295 set_state (c, false);
303 _body->rebuild_and_draw_grid ();
306 /* Decide how to arrange the components of the matrix */
308 PortMatrix::select_arrangement ()
310 uint32_t const N[2] = {
311 count_of_our_type_min_1 (_ports[0].total_channels()),
312 count_of_our_type_min_1 (_ports[1].total_channels())
315 /* XXX: shirley there's an easier way than this */
317 if (_vspacer.get_parent()) {
318 _vbox.remove (_vspacer);
321 if (_vnotebook.get_parent()) {
322 _vbox.remove (_vnotebook);
325 if (_vlabel.get_parent()) {
326 _vbox.remove (_vlabel);
329 /* The list with the most channels goes on left or right, so that the most channel
330 names are printed horizontally and hence more readable. However we also
331 maintain notional `signal flow' vaguely from left to right. Subclasses
332 should choose where to put ports based on signal flowing from _ports[0]
339 _arrangement = LEFT_TO_BOTTOM;
340 _vlabel.set_label (_("<b>Sources</b>"));
341 _hlabel.set_label (_("<b>Destinations</b>"));
342 _vlabel.set_angle (90);
344 _vbox.pack_end (_vlabel, false, false);
345 _vbox.pack_end (_vnotebook, false, false);
346 _vbox.pack_end (_vspacer, true, true);
348 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
349 REMOVE_FROM_GTK_PARENT(*_body)
350 REMOVE_FROM_GTK_PARENT(_vscroll)
351 REMOVE_FROM_GTK_PARENT(_hscroll)
352 REMOVE_FROM_GTK_PARENT(_vbox)
353 REMOVE_FROM_GTK_PARENT(_hbox)
355 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
356 attach (_vscroll, 3, 4, 1, 2, SHRINK);
357 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
358 attach (_vbox, 1, 2, 1, 2, SHRINK);
359 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
365 _arrangement = TOP_TO_RIGHT;
366 _hlabel.set_label (_("<b>Sources</b>"));
367 _vlabel.set_label (_("<b>Destinations</b>"));
368 _vlabel.set_angle (-90);
370 _vbox.pack_end (_vspacer, true, true);
371 _vbox.pack_end (_vnotebook, false, false);
372 _vbox.pack_end (_vlabel, false, false);
374 REMOVE_FROM_GTK_PARENT(*_body)
375 REMOVE_FROM_GTK_PARENT(_vscroll)
376 REMOVE_FROM_GTK_PARENT(_hscroll)
377 REMOVE_FROM_GTK_PARENT(_vbox)
378 REMOVE_FROM_GTK_PARENT(_hbox)
380 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
381 attach (_vscroll, 3, 4, 2, 3, SHRINK);
382 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
383 attach (_vbox, 2, 3, 2, 3, SHRINK);
384 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
388 /** @return columns list */
389 PortGroupList const *
390 PortMatrix::columns () const
392 return &_ports[_column_index];
395 boost::shared_ptr<const PortGroup>
396 PortMatrix::visible_columns () const
398 return visible_ports (_column_index);
401 /* @return rows list */
402 PortGroupList const *
403 PortMatrix::rows () const
405 return &_ports[_row_index];
408 boost::shared_ptr<const PortGroup>
409 PortMatrix::visible_rows () const
411 return visible_ports (_row_index);
414 /** @param column Column; its bundle may be 0 if we are over a row heading.
415 * @param row Row; its bundle may be 0 if we are over a column heading.
418 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
420 using namespace Menu_Helpers;
425 _menu->set_name ("ArdourContextMenu");
427 MenuList& items = _menu->items ();
430 bc[_column_index] = column;
431 bc[_row_index] = row;
434 bool need_separator = false;
436 for (int dim = 0; dim < 2; ++dim) {
438 if (bc[dim].bundle) {
440 Menu* m = manage (new Menu);
441 MenuList& sub = m->items ();
443 boost::weak_ptr<Bundle> w (bc[dim].bundle);
445 if (can_add_channels (bc[dim].bundle)) {
446 /* Start off with options for the `natural' port type */
447 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
448 if (should_show (*i)) {
449 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
450 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
454 /* Now add other ones */
455 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
456 if (!should_show (*i)) {
457 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
458 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
463 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
465 buf, sizeof (buf), _("Rename '%s'..."),
466 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
471 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
476 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
477 if (bc[dim].channel != -1) {
478 add_remove_option (sub, w, bc[dim].channel);
481 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
484 if (bc[dim].bundle->nchannels().n_total() > 1) {
485 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
486 if (should_show (bc[dim].bundle->channel_type(i))) {
487 add_remove_option (sub, w, i);
494 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
495 if ((_show_only_bundles && c > 0) || c == 1) {
497 /* we're looking just at bundles, or our bundle has only one channel, so just offer
498 to disassociate all on the bundle.
501 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
503 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
508 if (bc[dim].channel != -1) {
509 /* specific channel under the menu, so just offer to disassociate that */
510 add_disassociate_option (sub, w, dim, bc[dim].channel);
512 /* no specific channel; offer to disassociate all, or any one in particular */
513 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
515 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
518 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
519 if (should_show (bc[dim].bundle->channel_type(i))) {
520 add_disassociate_option (sub, w, dim, i);
526 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
527 need_separator = true;
532 if (need_separator) {
533 items.push_back (SeparatorElem ());
536 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
538 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
539 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
540 _inhibit_toggle_show_only_bundles = true;
541 i->set_active (!_show_only_bundles);
542 _inhibit_toggle_show_only_bundles = false;
544 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
545 items.back().set_sensitive (can_flip ());
551 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
553 boost::shared_ptr<Bundle> sb = b.lock ();
558 remove_channel (BundleChannel (sb, c));
563 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
565 boost::shared_ptr<Bundle> sb = b.lock ();
570 rename_channel (BundleChannel (sb, c));
574 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
576 boost::shared_ptr<Bundle> sb = bundle.lock ();
581 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
582 if (should_show (sb->channel_type(i))) {
583 disassociate_all_on_channel (bundle, i, dim);
589 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
591 boost::shared_ptr<Bundle> sb = bundle.lock ();
596 PortGroup::BundleList a = _ports[1-dim].bundles ();
598 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
599 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
601 if (!should_show ((*i)->bundle->channel_type(j))) {
606 c[dim] = BundleChannel (sb, channel);
607 c[1-dim] = BundleChannel ((*i)->bundle, j);
609 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
610 set_state (c, false);
615 _body->rebuild_and_draw_grid ();
619 PortMatrix::setup_global_ports ()
621 if (!_session || _session->deletion_in_progress()) return;
622 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
624 for (int i = 0; i < 2; ++i) {
625 if (list_is_global (i)) {
632 PortMatrix::setup_global_ports_proxy ()
634 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
638 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
642 PortMatrix::setup_all_ports ()
644 if (_session->deletion_in_progress()) {
648 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
655 PortMatrix::toggle_show_only_bundles ()
657 if (_inhibit_toggle_show_only_bundles) {
661 _show_only_bundles = !_show_only_bundles;
665 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
666 setting, so we need to set things up again now.
671 pair<uint32_t, uint32_t>
672 PortMatrix::max_size () const
674 pair<uint32_t, uint32_t> m = _body->max_size ();
676 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
677 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
683 PortMatrix::on_scroll_event (GdkEventScroll* ev)
685 double const h = _hscroll.get_value ();
686 double const v = _vscroll.get_value ();
688 switch (ev->direction) {
690 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
692 case GDK_SCROLL_DOWN:
693 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
695 case GDK_SCROLL_LEFT:
696 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
698 case GDK_SCROLL_RIGHT:
699 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
706 boost::shared_ptr<IO>
707 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
709 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
711 io = _ports[1].io_from_bundle (b);
718 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
720 return io_from_bundle (b) != 0;
724 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
726 boost::shared_ptr<IO> io = io_from_bundle (b);
729 int const r = io->add_port ("", this, t);
731 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
732 "support the new configuration."
734 msg.set_title (_("Cannot add port"));
741 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
743 return io_from_bundle (b) != 0;
747 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
749 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
752 boost::shared_ptr<Port> p = io->nth (b.channel);
754 int const r = io->remove_port (p, this);
756 ArdourDialog d (_("Port removal not allowed"));
757 Label l (_("This port cannot be removed.\nEither the first plugin in the track or buss cannot accept\nthe new number of inputs or the last plugin has more outputs."));
758 d.get_vbox()->pack_start (l);
759 d.add_button (Stock::OK, RESPONSE_ACCEPT);
769 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
771 boost::shared_ptr<Bundle> b = w.lock ();
776 /* Remove channels backwards so that we don't renumber channels
777 that we are about to remove.
779 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
780 if (should_show (b->channel_type(i))) {
781 remove_channel (ARDOUR::BundleChannel (b, i));
787 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
789 boost::shared_ptr<Bundle> b = w.lock ();
798 PortMatrix::setup_notebooks ()
800 int const h_current_page = _hnotebook.get_current_page ();
801 int const v_current_page = _vnotebook.get_current_page ();
803 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
804 when adding or removing pages to or from notebooks, so ignore them */
806 _ignore_notebook_page_selected = true;
808 remove_notebook_pages (_hnotebook);
809 remove_notebook_pages (_vnotebook);
811 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
812 HBox* dummy = manage (new HBox);
814 Label* label = manage (new Label ((*i)->name));
815 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
816 label->set_use_markup ();
818 if (_arrangement == LEFT_TO_BOTTOM) {
819 _vnotebook.prepend_page (*dummy, *label);
821 /* Reverse the order of vertical tabs when they are on the right hand side
822 so that from top to bottom it is the same order as that from left to right
825 _vnotebook.append_page (*dummy, *label);
829 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
830 HBox* dummy = manage (new HBox);
832 Label* label = manage (new Label ((*i)->name));
833 label->set_use_markup ();
835 _hnotebook.append_page (*dummy, *label);
838 _ignore_notebook_page_selected = false;
840 if (_arrangement == TOP_TO_RIGHT) {
841 _vnotebook.set_tab_pos (POS_RIGHT);
842 _hnotebook.set_tab_pos (POS_TOP);
844 _vnotebook.set_tab_pos (POS_LEFT);
845 _hnotebook.set_tab_pos (POS_BOTTOM);
848 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
849 _hnotebook.set_current_page (h_current_page);
851 _hnotebook.set_current_page (0);
854 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
855 _vnotebook.set_current_page (v_current_page);
857 _vnotebook.set_current_page (0);
860 if (_hnotebook.get_n_pages() <= 1) {
866 if (_vnotebook.get_n_pages() <= 1) {
874 PortMatrix::remove_notebook_pages (Notebook& n)
876 int const N = n.get_n_pages ();
878 for (int i = 0; i < N; ++i) {
884 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
886 if (_ignore_notebook_page_selected) {
896 PortMatrix::session_going_away ()
902 PortMatrix::body_dimensions_changed ()
904 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
905 if (_arrangement == TOP_TO_RIGHT) {
906 _vspacer.set_size_request (-1, _body->column_labels_height ());
914 _parent->get_size (curr_width, curr_height);
916 pair<uint32_t, uint32_t> m = max_size ();
918 /* Don't shrink the window */
919 m.first = max (int (m.first), curr_width);
920 m.second = max (int (m.second), curr_height);
922 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
925 /** @return The PortGroup that is currently visible (ie selected by
926 * the notebook) along a given axis.
928 boost::shared_ptr<const PortGroup>
929 PortMatrix::visible_ports (int d) const
931 PortGroupList const & p = _ports[d];
932 PortGroupList::List::const_iterator j = p.begin ();
934 /* The logic to compute the index here is a bit twisty because for
935 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
936 tabs in setup_notebooks ().
940 if (d == _row_index) {
941 if (_arrangement == LEFT_TO_BOTTOM) {
942 n = p.size() - _vnotebook.get_current_page () - 1;
944 n = _vnotebook.get_current_page ();
947 n = _hnotebook.get_current_page ();
951 while (i != int (n) && j != p.end ()) {
957 return boost::shared_ptr<const PortGroup> ();
964 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
966 using namespace Menu_Helpers;
968 boost::shared_ptr<Bundle> b = w.lock ();
974 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
975 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
979 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
981 using namespace Menu_Helpers;
983 boost::shared_ptr<Bundle> b = w.lock ();
989 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
990 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
994 PortMatrix::port_connected_or_disconnected ()
996 _body->rebuild_and_draw_grid ();
997 update_tab_highlighting ();
1000 /** Update the highlighting of tab names to reflect which ones
1001 * have connections. This is pretty inefficient, unfortunately,
1002 * but maybe that doesn't matter too much.
1005 PortMatrix::update_tab_highlighting ()
1011 for (int i = 0; i < 2; ++i) {
1013 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1015 PortGroupList const * gl = ports (i);
1017 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1018 bool has_connection = false;
1019 PortGroup::BundleList const & bl = (*j)->bundles ();
1020 PortGroup::BundleList::const_iterator k = bl.begin ();
1021 while (k != bl.end()) {
1022 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1023 has_connection = true;
1029 /* Find the page index that we should update; this is backwards
1030 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1033 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1034 page = notebook->get_n_pages() - p - 1;
1037 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1038 string c = label->get_label ();
1039 if (c.length() && c[0] == '<' && !has_connection) {
1040 /* this label is marked up with <b> but shouldn't be */
1041 label->set_text ((*j)->name);
1042 } else if (c.length() && c[0] != '<' && has_connection) {
1043 /* this label is not marked up with <b> but should be */
1044 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1053 PortMatrix::channel_noun () const
1055 return _("channel");
1058 /** @return true if this matrix should show bundles / ports of type \t */
1060 PortMatrix::should_show (DataType t) const
1062 return (_type == DataType::NIL || t == _type);
1066 PortMatrix::count_of_our_type (ChanCount c) const
1068 if (_type == DataType::NIL) {
1069 return c.n_total ();
1072 return c.get (_type);
1075 /** @return The number of ports of our type in the given channel count,
1076 * but returning 1 if there are no ports.
1079 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1081 uint32_t n = count_of_our_type (c);
1089 PortMatrixNode::State
1090 PortMatrix::get_association (PortMatrixNode node) const
1092 if (show_only_bundles ()) {
1094 bool have_off_diagonal_association = false;
1095 bool have_diagonal_association = false;
1096 bool have_diagonal_not_association = false;
1098 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1100 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1102 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1106 ARDOUR::BundleChannel c[2];
1107 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1108 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1110 PortMatrixNode::State const s = get_state (c);
1113 case PortMatrixNode::ASSOCIATED:
1115 have_diagonal_association = true;
1117 have_off_diagonal_association = true;
1121 case PortMatrixNode::NOT_ASSOCIATED:
1123 have_diagonal_not_association = true;
1133 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1134 return PortMatrixNode::ASSOCIATED;
1135 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1136 return PortMatrixNode::NOT_ASSOCIATED;
1139 return PortMatrixNode::PARTIAL;
1143 ARDOUR::BundleChannel c[2];
1144 c[column_index()] = node.column;
1145 c[row_index()] = node.row;
1146 return get_state (c);
1150 abort(); /* NOTREACHED */
1151 return PortMatrixNode::NOT_ASSOCIATED;
1154 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1156 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1158 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1161 /** See if a `flip' is possible.
1162 * @return If flip is possible, the new (row, column) notebook indices that
1163 * should be selected; otherwise, (-1, -1)
1166 PortMatrix::check_flip () const
1168 /* Look for the row's port group name in the columns */
1171 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1172 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1173 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1178 if (i == _ports[_column_index].end ()) {
1179 return make_pair (-1, -1);
1182 /* Look for the column's port group name in the rows */
1185 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1186 i = _ports[_row_index].begin();
1187 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1192 if (i == _ports[_row_index].end ()) {
1193 return make_pair (-1, -1);
1196 if (_arrangement == LEFT_TO_BOTTOM) {
1197 new_row = _ports[_row_index].size() - new_row - 1;
1200 return make_pair (new_row, new_column);
1204 PortMatrix::can_flip () const
1206 return check_flip().first != -1;
1209 /** Flip the column and row pages around, if possible */
1213 pair<int, int> n = check_flip ();
1214 if (n.first == -1) {
1218 _vnotebook.set_current_page (n.first);
1219 _hnotebook.set_current_page (n.second);
1223 PortMatrix::key_press (GdkEventKey* k)
1225 if (k->keyval == GDK_f) {