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 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
615 for (int i = 0; i < 2; ++i) {
616 if (list_is_global (i)) {
623 PortMatrix::setup_global_ports_proxy ()
625 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
629 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
633 PortMatrix::setup_all_ports ()
635 if (_session->deletion_in_progress()) {
639 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
646 PortMatrix::toggle_show_only_bundles ()
648 if (_inhibit_toggle_show_only_bundles) {
652 _show_only_bundles = !_show_only_bundles;
656 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
657 setting, so we need to set things up again now.
662 pair<uint32_t, uint32_t>
663 PortMatrix::max_size () const
665 pair<uint32_t, uint32_t> m = _body->max_size ();
667 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
668 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
674 PortMatrix::on_scroll_event (GdkEventScroll* ev)
676 double const h = _hscroll.get_value ();
677 double const v = _vscroll.get_value ();
679 switch (ev->direction) {
681 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
683 case GDK_SCROLL_DOWN:
684 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
686 case GDK_SCROLL_LEFT:
687 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
689 case GDK_SCROLL_RIGHT:
690 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
697 boost::shared_ptr<IO>
698 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
700 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
702 io = _ports[1].io_from_bundle (b);
709 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
711 return io_from_bundle (b);
715 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
717 boost::shared_ptr<IO> io = io_from_bundle (b);
720 int const r = io->add_port ("", this, t);
722 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
723 "support the new configuration."
725 msg.set_title (_("Cannot add port"));
732 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
734 return io_from_bundle (b);
738 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
740 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
743 boost::shared_ptr<Port> p = io->nth (b.channel);
745 int const r = io->remove_port (p, this);
747 ArdourDialog d (_("Port removal not allowed"));
748 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."));
749 d.get_vbox()->pack_start (l);
750 d.add_button (Stock::OK, RESPONSE_ACCEPT);
760 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
762 boost::shared_ptr<Bundle> b = w.lock ();
767 /* Remove channels backwards so that we don't renumber channels
768 that we are about to remove.
770 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
771 if (should_show (b->channel_type(i))) {
772 remove_channel (ARDOUR::BundleChannel (b, i));
778 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
780 boost::shared_ptr<Bundle> b = w.lock ();
789 PortMatrix::setup_notebooks ()
791 int const h_current_page = _hnotebook.get_current_page ();
792 int const v_current_page = _vnotebook.get_current_page ();
794 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
795 when adding or removing pages to or from notebooks, so ignore them */
797 _ignore_notebook_page_selected = true;
799 remove_notebook_pages (_hnotebook);
800 remove_notebook_pages (_vnotebook);
802 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
803 HBox* dummy = manage (new HBox);
805 Label* label = manage (new Label ((*i)->name));
806 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
807 label->set_use_markup ();
809 if (_arrangement == LEFT_TO_BOTTOM) {
810 _vnotebook.prepend_page (*dummy, *label);
812 /* Reverse the order of vertical tabs when they are on the right hand side
813 so that from top to bottom it is the same order as that from left to right
816 _vnotebook.append_page (*dummy, *label);
820 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
821 HBox* dummy = manage (new HBox);
823 Label* label = manage (new Label ((*i)->name));
824 label->set_use_markup ();
826 _hnotebook.append_page (*dummy, *label);
829 _ignore_notebook_page_selected = false;
831 if (_arrangement == TOP_TO_RIGHT) {
832 _vnotebook.set_tab_pos (POS_RIGHT);
833 _hnotebook.set_tab_pos (POS_TOP);
835 _vnotebook.set_tab_pos (POS_LEFT);
836 _hnotebook.set_tab_pos (POS_BOTTOM);
839 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
840 _hnotebook.set_current_page (h_current_page);
842 _hnotebook.set_current_page (0);
845 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
846 _vnotebook.set_current_page (v_current_page);
848 _vnotebook.set_current_page (0);
851 if (_hnotebook.get_n_pages() <= 1) {
857 if (_vnotebook.get_n_pages() <= 1) {
865 PortMatrix::remove_notebook_pages (Notebook& n)
867 int const N = n.get_n_pages ();
869 for (int i = 0; i < N; ++i) {
875 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
877 if (_ignore_notebook_page_selected) {
887 PortMatrix::session_going_away ()
893 PortMatrix::body_dimensions_changed ()
895 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
896 if (_arrangement == TOP_TO_RIGHT) {
897 _vspacer.set_size_request (-1, _body->column_labels_height ());
905 _parent->get_size (curr_width, curr_height);
907 pair<uint32_t, uint32_t> m = max_size ();
909 /* Don't shrink the window */
910 m.first = max (int (m.first), curr_width);
911 m.second = max (int (m.second), curr_height);
913 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
916 /** @return The PortGroup that is currently visible (ie selected by
917 * the notebook) along a given axis.
919 boost::shared_ptr<const PortGroup>
920 PortMatrix::visible_ports (int d) const
922 PortGroupList const & p = _ports[d];
923 PortGroupList::List::const_iterator j = p.begin ();
925 /* The logic to compute the index here is a bit twisty because for
926 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
927 tabs in setup_notebooks ().
931 if (d == _row_index) {
932 if (_arrangement == LEFT_TO_BOTTOM) {
933 n = p.size() - _vnotebook.get_current_page () - 1;
935 n = _vnotebook.get_current_page ();
938 n = _hnotebook.get_current_page ();
942 while (i != int (n) && j != p.end ()) {
948 return boost::shared_ptr<const PortGroup> ();
955 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
957 using namespace Menu_Helpers;
959 boost::shared_ptr<Bundle> b = w.lock ();
965 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
966 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
970 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
972 using namespace Menu_Helpers;
974 boost::shared_ptr<Bundle> b = w.lock ();
980 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
981 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
985 PortMatrix::port_connected_or_disconnected ()
987 _body->rebuild_and_draw_grid ();
988 update_tab_highlighting ();
991 /** Update the highlighting of tab names to reflect which ones
992 * have connections. This is pretty inefficient, unfortunately,
993 * but maybe that doesn't matter too much.
996 PortMatrix::update_tab_highlighting ()
1002 for (int i = 0; i < 2; ++i) {
1004 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1006 PortGroupList const * gl = ports (i);
1008 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1009 bool has_connection = false;
1010 PortGroup::BundleList const & bl = (*j)->bundles ();
1011 PortGroup::BundleList::const_iterator k = bl.begin ();
1012 while (k != bl.end()) {
1013 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1014 has_connection = true;
1020 /* Find the page index that we should update; this is backwards
1021 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1024 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1025 page = notebook->get_n_pages() - p - 1;
1028 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1029 string c = label->get_label ();
1030 if (c.length() && c[0] == '<' && !has_connection) {
1031 /* this label is marked up with <b> but shouldn't be */
1032 label->set_text ((*j)->name);
1033 } else if (c.length() && c[0] != '<' && has_connection) {
1034 /* this label is not marked up with <b> but should be */
1035 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1044 PortMatrix::channel_noun () const
1046 return _("channel");
1049 /** @return true if this matrix should show bundles / ports of type \t */
1051 PortMatrix::should_show (DataType t) const
1053 return (_type == DataType::NIL || t == _type);
1057 PortMatrix::count_of_our_type (ChanCount c) const
1059 if (_type == DataType::NIL) {
1060 return c.n_total ();
1063 return c.get (_type);
1066 /** @return The number of ports of our type in the given channel count,
1067 * but returning 1 if there are no ports.
1070 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1072 uint32_t n = count_of_our_type (c);
1080 PortMatrixNode::State
1081 PortMatrix::get_association (PortMatrixNode node) const
1083 if (show_only_bundles ()) {
1085 bool have_off_diagonal_association = false;
1086 bool have_diagonal_association = false;
1087 bool have_diagonal_not_association = false;
1089 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1091 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1093 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1097 ARDOUR::BundleChannel c[2];
1098 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1099 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1101 PortMatrixNode::State const s = get_state (c);
1104 case PortMatrixNode::ASSOCIATED:
1106 have_diagonal_association = true;
1108 have_off_diagonal_association = true;
1112 case PortMatrixNode::NOT_ASSOCIATED:
1114 have_diagonal_not_association = true;
1124 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1125 return PortMatrixNode::ASSOCIATED;
1126 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1127 return PortMatrixNode::NOT_ASSOCIATED;
1130 return PortMatrixNode::PARTIAL;
1134 ARDOUR::BundleChannel c[2];
1135 c[column_index()] = node.column;
1136 c[row_index()] = node.row;
1137 return get_state (c);
1142 return PortMatrixNode::NOT_ASSOCIATED;
1145 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1147 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1149 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1152 /** See if a `flip' is possible.
1153 * @return If flip is possible, the new (row, column) notebook indices that
1154 * should be selected; otherwise, (-1, -1)
1157 PortMatrix::check_flip () const
1159 /* Look for the row's port group name in the columns */
1162 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1163 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1164 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1169 if (i == _ports[_column_index].end ()) {
1170 return make_pair (-1, -1);
1173 /* Look for the column's port group name in the rows */
1176 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1177 i = _ports[_row_index].begin();
1178 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1183 if (i == _ports[_row_index].end ()) {
1184 return make_pair (-1, -1);
1187 if (_arrangement == LEFT_TO_BOTTOM) {
1188 new_row = _ports[_row_index].size() - new_row - 1;
1191 return make_pair (new_row, new_column);
1195 PortMatrix::can_flip () const
1197 return check_flip().first != -1;
1200 /** Flip the column and row pages around, if possible */
1204 pair<int, int> n = check_flip ();
1205 if (n.first == -1) {
1209 _vnotebook.set_current_page (n.first);
1210 _hnotebook.set_current_page (n.second);
1214 PortMatrix::key_press (GdkEventKey* k)
1216 if (k->keyval == GDK_f) {