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->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
156 _session->BundleRemoved.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_upper (_body->full_scroll_width());
253 a->set_page_size (_body->alloc_scroll_width());
254 a->set_step_increment (32);
255 a->set_page_increment (128);
257 a = _vscroll.get_adjustment ();
259 a->set_upper (_body->full_scroll_height());
260 a->set_page_size (_body->alloc_scroll_height());
261 a->set_step_increment (32);
262 a->set_page_increment (128);
265 /** Disassociate all of our ports from each other */
267 PortMatrix::disassociate_all ()
269 PortGroup::BundleList a = _ports[0].bundles ();
270 PortGroup::BundleList b = _ports[1].bundles ();
272 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
273 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
274 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
275 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
277 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
281 BundleChannel c[2] = {
282 BundleChannel ((*i)->bundle, j),
283 BundleChannel ((*k)->bundle, l)
286 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
287 set_state (c, false);
295 _body->rebuild_and_draw_grid ();
298 /* Decide how to arrange the components of the matrix */
300 PortMatrix::select_arrangement ()
302 uint32_t const N[2] = {
303 count_of_our_type_min_1 (_ports[0].total_channels()),
304 count_of_our_type_min_1 (_ports[1].total_channels())
307 /* XXX: shirley there's an easier way than this */
309 if (_vspacer.get_parent()) {
310 _vbox.remove (_vspacer);
313 if (_vnotebook.get_parent()) {
314 _vbox.remove (_vnotebook);
317 if (_vlabel.get_parent()) {
318 _vbox.remove (_vlabel);
321 /* The list with the most channels goes on left or right, so that the most channel
322 names are printed horizontally and hence more readable. However we also
323 maintain notional `signal flow' vaguely from left to right. Subclasses
324 should choose where to put ports based on signal flowing from _ports[0]
331 _arrangement = LEFT_TO_BOTTOM;
332 _vlabel.set_label (_("<b>Sources</b>"));
333 _hlabel.set_label (_("<b>Destinations</b>"));
334 _vlabel.set_angle (90);
336 _vbox.pack_end (_vlabel, false, false);
337 _vbox.pack_end (_vnotebook, false, false);
338 _vbox.pack_end (_vspacer, true, true);
340 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
341 REMOVE_FROM_GTK_PARENT(*_body)
342 REMOVE_FROM_GTK_PARENT(_vscroll)
343 REMOVE_FROM_GTK_PARENT(_hscroll)
344 REMOVE_FROM_GTK_PARENT(_vbox)
345 REMOVE_FROM_GTK_PARENT(_hbox)
347 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
348 attach (_vscroll, 3, 4, 1, 2, SHRINK);
349 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
350 attach (_vbox, 1, 2, 1, 2, SHRINK);
351 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
357 _arrangement = TOP_TO_RIGHT;
358 _hlabel.set_label (_("<b>Sources</b>"));
359 _vlabel.set_label (_("<b>Destinations</b>"));
360 _vlabel.set_angle (-90);
362 _vbox.pack_end (_vspacer, true, true);
363 _vbox.pack_end (_vnotebook, false, false);
364 _vbox.pack_end (_vlabel, false, false);
366 REMOVE_FROM_GTK_PARENT(*_body)
367 REMOVE_FROM_GTK_PARENT(_vscroll)
368 REMOVE_FROM_GTK_PARENT(_hscroll)
369 REMOVE_FROM_GTK_PARENT(_vbox)
370 REMOVE_FROM_GTK_PARENT(_hbox)
372 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
373 attach (_vscroll, 3, 4, 2, 3, SHRINK);
374 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
375 attach (_vbox, 2, 3, 2, 3, SHRINK);
376 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
380 /** @return columns list */
381 PortGroupList const *
382 PortMatrix::columns () const
384 return &_ports[_column_index];
387 boost::shared_ptr<const PortGroup>
388 PortMatrix::visible_columns () const
390 return visible_ports (_column_index);
393 /* @return rows list */
394 PortGroupList const *
395 PortMatrix::rows () const
397 return &_ports[_row_index];
400 boost::shared_ptr<const PortGroup>
401 PortMatrix::visible_rows () const
403 return visible_ports (_row_index);
406 /** @param column Column; its bundle may be 0 if we are over a row heading.
407 * @param row Row; its bundle may be 0 if we are over a column heading.
410 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
412 using namespace Menu_Helpers;
417 _menu->set_name ("ArdourContextMenu");
419 MenuList& items = _menu->items ();
422 bc[_column_index] = column;
423 bc[_row_index] = row;
426 bool need_separator = false;
428 for (int dim = 0; dim < 2; ++dim) {
430 if (bc[dim].bundle) {
432 Menu* m = manage (new Menu);
433 MenuList& sub = m->items ();
435 boost::weak_ptr<Bundle> w (bc[dim].bundle);
437 if (can_add_channels (bc[dim].bundle)) {
438 /* Start off with options for the `natural' port type */
439 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
440 if (should_show (*i)) {
441 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
442 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
446 /* Now add other ones */
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)));
455 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
457 buf, sizeof (buf), _("Rename '%s'..."),
458 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
463 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
468 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
469 if (bc[dim].channel != -1) {
470 add_remove_option (sub, w, bc[dim].channel);
473 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
476 if (bc[dim].bundle->nchannels().n_total() > 1) {
477 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
478 if (should_show (bc[dim].bundle->channel_type(i))) {
479 add_remove_option (sub, w, i);
486 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
487 if ((_show_only_bundles && c > 0) || c == 1) {
489 /* we're looking just at bundles, or our bundle has only one channel, so just offer
490 to disassociate all on the bundle.
493 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
495 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
500 if (bc[dim].channel != -1) {
501 /* specific channel under the menu, so just offer to disassociate that */
502 add_disassociate_option (sub, w, dim, bc[dim].channel);
504 /* no specific channel; offer to disassociate all, or any one in particular */
505 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
507 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
510 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
511 if (should_show (bc[dim].bundle->channel_type(i))) {
512 add_disassociate_option (sub, w, dim, i);
518 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
519 need_separator = true;
524 if (need_separator) {
525 items.push_back (SeparatorElem ());
528 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
530 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
531 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
532 _inhibit_toggle_show_only_bundles = true;
533 i->set_active (!_show_only_bundles);
534 _inhibit_toggle_show_only_bundles = false;
536 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
537 items.back().set_sensitive (can_flip ());
543 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
545 boost::shared_ptr<Bundle> sb = b.lock ();
550 remove_channel (BundleChannel (sb, c));
555 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
557 boost::shared_ptr<Bundle> sb = b.lock ();
562 rename_channel (BundleChannel (sb, c));
566 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
568 boost::shared_ptr<Bundle> sb = bundle.lock ();
573 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
574 if (should_show (sb->channel_type(i))) {
575 disassociate_all_on_channel (bundle, i, dim);
581 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
583 boost::shared_ptr<Bundle> sb = bundle.lock ();
588 PortGroup::BundleList a = _ports[1-dim].bundles ();
590 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
591 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
593 if (!should_show ((*i)->bundle->channel_type(j))) {
598 c[dim] = BundleChannel (sb, channel);
599 c[1-dim] = BundleChannel ((*i)->bundle, j);
601 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
602 set_state (c, false);
607 _body->rebuild_and_draw_grid ();
611 PortMatrix::setup_global_ports ()
613 if (!_session || _session->deletion_in_progress()) return;
614 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
616 for (int i = 0; i < 2; ++i) {
617 if (list_is_global (i)) {
624 PortMatrix::setup_global_ports_proxy ()
626 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
630 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
634 PortMatrix::setup_all_ports ()
636 if (_session->deletion_in_progress()) {
640 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
647 PortMatrix::toggle_show_only_bundles ()
649 if (_inhibit_toggle_show_only_bundles) {
653 _show_only_bundles = !_show_only_bundles;
657 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
658 setting, so we need to set things up again now.
663 pair<uint32_t, uint32_t>
664 PortMatrix::max_size () const
666 pair<uint32_t, uint32_t> m = _body->max_size ();
668 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
669 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
675 PortMatrix::on_scroll_event (GdkEventScroll* ev)
677 double const h = _hscroll.get_value ();
678 double const v = _vscroll.get_value ();
680 switch (ev->direction) {
682 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
684 case GDK_SCROLL_DOWN:
685 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
687 case GDK_SCROLL_LEFT:
688 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
690 case GDK_SCROLL_RIGHT:
691 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
698 boost::shared_ptr<IO>
699 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
701 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
703 io = _ports[1].io_from_bundle (b);
710 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
712 return io_from_bundle (b) != 0;
716 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
718 boost::shared_ptr<IO> io = io_from_bundle (b);
721 int const r = io->add_port ("", this, t);
723 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
724 "support the new configuration."
726 msg.set_title (_("Cannot add port"));
733 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
735 return io_from_bundle (b) != 0;
739 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
741 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
744 boost::shared_ptr<Port> p = io->nth (b.channel);
746 int const r = io->remove_port (p, this);
748 ArdourDialog d (_("Port removal not allowed"));
749 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."));
750 d.get_vbox()->pack_start (l);
751 d.add_button (Stock::OK, RESPONSE_ACCEPT);
761 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
763 boost::shared_ptr<Bundle> b = w.lock ();
768 /* Remove channels backwards so that we don't renumber channels
769 that we are about to remove.
771 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
772 if (should_show (b->channel_type(i))) {
773 remove_channel (ARDOUR::BundleChannel (b, i));
779 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
781 boost::shared_ptr<Bundle> b = w.lock ();
790 PortMatrix::setup_notebooks ()
792 int const h_current_page = _hnotebook.get_current_page ();
793 int const v_current_page = _vnotebook.get_current_page ();
795 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
796 when adding or removing pages to or from notebooks, so ignore them */
798 _ignore_notebook_page_selected = true;
800 remove_notebook_pages (_hnotebook);
801 remove_notebook_pages (_vnotebook);
803 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
804 HBox* dummy = manage (new HBox);
806 Label* label = manage (new Label ((*i)->name));
807 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
808 label->set_use_markup ();
810 if (_arrangement == LEFT_TO_BOTTOM) {
811 _vnotebook.prepend_page (*dummy, *label);
813 /* Reverse the order of vertical tabs when they are on the right hand side
814 so that from top to bottom it is the same order as that from left to right
817 _vnotebook.append_page (*dummy, *label);
821 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
822 HBox* dummy = manage (new HBox);
824 Label* label = manage (new Label ((*i)->name));
825 label->set_use_markup ();
827 _hnotebook.append_page (*dummy, *label);
830 _ignore_notebook_page_selected = false;
832 if (_arrangement == TOP_TO_RIGHT) {
833 _vnotebook.set_tab_pos (POS_RIGHT);
834 _hnotebook.set_tab_pos (POS_TOP);
836 _vnotebook.set_tab_pos (POS_LEFT);
837 _hnotebook.set_tab_pos (POS_BOTTOM);
840 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
841 _hnotebook.set_current_page (h_current_page);
843 _hnotebook.set_current_page (0);
846 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
847 _vnotebook.set_current_page (v_current_page);
849 _vnotebook.set_current_page (0);
852 if (_hnotebook.get_n_pages() <= 1) {
858 if (_vnotebook.get_n_pages() <= 1) {
866 PortMatrix::remove_notebook_pages (Notebook& n)
868 int const N = n.get_n_pages ();
870 for (int i = 0; i < N; ++i) {
876 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
878 if (_ignore_notebook_page_selected) {
888 PortMatrix::session_going_away ()
894 PortMatrix::body_dimensions_changed ()
896 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
897 if (_arrangement == TOP_TO_RIGHT) {
898 _vspacer.set_size_request (-1, _body->column_labels_height ());
906 _parent->get_size (curr_width, curr_height);
908 pair<uint32_t, uint32_t> m = max_size ();
910 /* Don't shrink the window */
911 m.first = max (int (m.first), curr_width);
912 m.second = max (int (m.second), curr_height);
914 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
917 /** @return The PortGroup that is currently visible (ie selected by
918 * the notebook) along a given axis.
920 boost::shared_ptr<const PortGroup>
921 PortMatrix::visible_ports (int d) const
923 PortGroupList const & p = _ports[d];
924 PortGroupList::List::const_iterator j = p.begin ();
926 /* The logic to compute the index here is a bit twisty because for
927 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
928 tabs in setup_notebooks ().
932 if (d == _row_index) {
933 if (_arrangement == LEFT_TO_BOTTOM) {
934 n = p.size() - _vnotebook.get_current_page () - 1;
936 n = _vnotebook.get_current_page ();
939 n = _hnotebook.get_current_page ();
943 while (i != int (n) && j != p.end ()) {
949 return boost::shared_ptr<const PortGroup> ();
956 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
958 using namespace Menu_Helpers;
960 boost::shared_ptr<Bundle> b = w.lock ();
966 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
967 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
971 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
973 using namespace Menu_Helpers;
975 boost::shared_ptr<Bundle> b = w.lock ();
981 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
982 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
986 PortMatrix::port_connected_or_disconnected ()
988 _body->rebuild_and_draw_grid ();
989 update_tab_highlighting ();
992 /** Update the highlighting of tab names to reflect which ones
993 * have connections. This is pretty inefficient, unfortunately,
994 * but maybe that doesn't matter too much.
997 PortMatrix::update_tab_highlighting ()
1003 for (int i = 0; i < 2; ++i) {
1005 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1007 PortGroupList const * gl = ports (i);
1009 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1010 bool has_connection = false;
1011 PortGroup::BundleList const & bl = (*j)->bundles ();
1012 PortGroup::BundleList::const_iterator k = bl.begin ();
1013 while (k != bl.end()) {
1014 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1015 has_connection = true;
1021 /* Find the page index that we should update; this is backwards
1022 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1025 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1026 page = notebook->get_n_pages() - p - 1;
1029 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1030 string c = label->get_label ();
1031 if (c.length() && c[0] == '<' && !has_connection) {
1032 /* this label is marked up with <b> but shouldn't be */
1033 label->set_text ((*j)->name);
1034 } else if (c.length() && c[0] != '<' && has_connection) {
1035 /* this label is not marked up with <b> but should be */
1036 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1045 PortMatrix::channel_noun () const
1047 return _("channel");
1050 /** @return true if this matrix should show bundles / ports of type \t */
1052 PortMatrix::should_show (DataType t) const
1054 return (_type == DataType::NIL || t == _type);
1058 PortMatrix::count_of_our_type (ChanCount c) const
1060 if (_type == DataType::NIL) {
1061 return c.n_total ();
1064 return c.get (_type);
1067 /** @return The number of ports of our type in the given channel count,
1068 * but returning 1 if there are no ports.
1071 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1073 uint32_t n = count_of_our_type (c);
1081 PortMatrixNode::State
1082 PortMatrix::get_association (PortMatrixNode node) const
1084 if (show_only_bundles ()) {
1086 bool have_off_diagonal_association = false;
1087 bool have_diagonal_association = false;
1088 bool have_diagonal_not_association = false;
1090 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1092 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1094 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1098 ARDOUR::BundleChannel c[2];
1099 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1100 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1102 PortMatrixNode::State const s = get_state (c);
1105 case PortMatrixNode::ASSOCIATED:
1107 have_diagonal_association = true;
1109 have_off_diagonal_association = true;
1113 case PortMatrixNode::NOT_ASSOCIATED:
1115 have_diagonal_not_association = true;
1125 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1126 return PortMatrixNode::ASSOCIATED;
1127 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1128 return PortMatrixNode::NOT_ASSOCIATED;
1131 return PortMatrixNode::PARTIAL;
1135 ARDOUR::BundleChannel c[2];
1136 c[column_index()] = node.column;
1137 c[row_index()] = node.row;
1138 return get_state (c);
1142 abort(); /* NOTREACHED */
1143 return PortMatrixNode::NOT_ASSOCIATED;
1146 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1148 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1150 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1153 /** See if a `flip' is possible.
1154 * @return If flip is possible, the new (row, column) notebook indices that
1155 * should be selected; otherwise, (-1, -1)
1158 PortMatrix::check_flip () const
1160 /* Look for the row's port group name in the columns */
1163 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1164 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1165 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1170 if (i == _ports[_column_index].end ()) {
1171 return make_pair (-1, -1);
1174 /* Look for the column's port group name in the rows */
1177 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1178 i = _ports[_row_index].begin();
1179 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1184 if (i == _ports[_row_index].end ()) {
1185 return make_pair (-1, -1);
1188 if (_arrangement == LEFT_TO_BOTTOM) {
1189 new_row = _ports[_row_index].size() - new_row - 1;
1192 return make_pair (new_row, new_column);
1196 PortMatrix::can_flip () const
1198 return check_flip().first != -1;
1201 /** Flip the column and row pages around, if possible */
1205 pair<int, int> n = check_flip ();
1206 if (n.first == -1) {
1210 _vnotebook.set_current_page (n.first);
1211 _hnotebook.set_current_page (n.second);
1215 PortMatrix::key_press (GdkEventKey* k)
1217 if (k->keyval == GDK_f) {