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;
47 /** PortMatrix constructor.
48 * @param session Our session.
49 * @param type Port type that we are handling.
51 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
56 , _arrangement (TOP_TO_RIGHT)
59 , _min_height_divisor (1)
60 , _show_only_bundles (false)
61 , _inhibit_toggle_show_only_bundles (false)
62 , _ignore_notebook_page_selected (false)
64 set_session (session);
66 _body = new PortMatrixBody (this);
67 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
69 _hbox.pack_end (_hspacer, true, true);
70 _hbox.pack_end (_hnotebook, false, false);
71 _hbox.pack_end (_hlabel, false, false);
73 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
74 _vnotebook.property_tab_border() = 4;
75 _vnotebook.set_name (X_("PortMatrixLabel"));
76 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
77 _hnotebook.property_tab_border() = 4;
78 _hnotebook.set_name (X_("PortMatrixLabel"));
80 _vlabel.set_use_markup ();
81 _vlabel.set_alignment (1, 1);
82 _vlabel.set_padding (4, 16);
83 _vlabel.set_name (X_("PortMatrixLabel"));
84 _hlabel.set_use_markup ();
85 _hlabel.set_alignment (1, 0.5);
86 _hlabel.set_padding (16, 4);
87 _hlabel.set_name (X_("PortMatrixLabel"));
89 set_row_spacing (0, 8);
90 set_col_spacing (0, 8);
91 set_row_spacing (2, 8);
92 set_col_spacing (2, 8);
107 PortMatrix::~PortMatrix ()
113 /** Perform initial and once-only setup. This must be called by
114 * subclasses after they have set up _ports[] to at least some
115 * reasonable extent. Two-part initialisation is necessary because
116 * setting up _ports is largely done by virtual functions in
123 select_arrangement ();
125 /* Signal handling is kind of split into three parts:
127 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
128 * representation of the information in _ports[].
130 * 2. When certain other things change, we need to get our subclass to clear and
131 * re-fill _ports[], which in turn causes appropriate signals to be raised to
132 * hook into part (1).
134 * 3. Assorted other signals.
138 /* Part 1: the basic _ports[] change -> reset visuals */
140 for (int i = 0; i < 2; ++i) {
141 /* watch for the content of _ports[] changing */
142 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
144 /* and for bundles in _ports[] changing */
145 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
148 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
150 /* watch for routes being added or removed */
151 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
153 /* and also bundles */
154 _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
155 _session->BundleRemoved.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, _1), 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_upper (_body->full_scroll_width());
252 a->set_page_size (_body->alloc_scroll_width());
253 a->set_step_increment (32);
254 a->set_page_increment (128);
256 a = _vscroll.get_adjustment ();
258 a->set_upper (_body->full_scroll_height());
259 a->set_page_size (_body->alloc_scroll_height());
260 a->set_step_increment (32);
261 a->set_page_increment (128);
264 /** Disassociate all of our ports from each other */
266 PortMatrix::disassociate_all ()
268 PortGroup::BundleList a = _ports[0].bundles ();
269 PortGroup::BundleList b = _ports[1].bundles ();
271 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
272 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
273 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
274 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
276 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
280 BundleChannel c[2] = {
281 BundleChannel ((*i)->bundle, j),
282 BundleChannel ((*k)->bundle, l)
285 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
286 set_state (c, false);
294 _body->rebuild_and_draw_grid ();
297 /* Decide how to arrange the components of the matrix */
299 PortMatrix::select_arrangement ()
301 uint32_t const N[2] = {
302 count_of_our_type_min_1 (_ports[0].total_channels()),
303 count_of_our_type_min_1 (_ports[1].total_channels())
306 /* XXX: shirley there's an easier way than this */
308 if (_vspacer.get_parent()) {
309 _vbox.remove (_vspacer);
312 if (_vnotebook.get_parent()) {
313 _vbox.remove (_vnotebook);
316 if (_vlabel.get_parent()) {
317 _vbox.remove (_vlabel);
320 /* The list with the most channels goes on left or right, so that the most channel
321 names are printed horizontally and hence more readable. However we also
322 maintain notional `signal flow' vaguely from left to right. Subclasses
323 should choose where to put ports based on signal flowing from _ports[0]
330 _arrangement = LEFT_TO_BOTTOM;
331 _vlabel.set_label (_("<b>Sources</b>"));
332 _hlabel.set_label (_("<b>Destinations</b>"));
333 _vlabel.set_angle (90);
335 _vbox.pack_end (_vlabel, false, false);
336 _vbox.pack_end (_vnotebook, false, false);
337 _vbox.pack_end (_vspacer, true, true);
339 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
340 REMOVE_FROM_GTK_PARENT(*_body)
341 REMOVE_FROM_GTK_PARENT(_vscroll)
342 REMOVE_FROM_GTK_PARENT(_hscroll)
343 REMOVE_FROM_GTK_PARENT(_vbox)
344 REMOVE_FROM_GTK_PARENT(_hbox)
346 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
347 attach (_vscroll, 3, 4, 1, 2, SHRINK);
348 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
349 attach (_vbox, 1, 2, 1, 2, SHRINK);
350 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
356 _arrangement = TOP_TO_RIGHT;
357 _hlabel.set_label (_("<b>Sources</b>"));
358 _vlabel.set_label (_("<b>Destinations</b>"));
359 _vlabel.set_angle (-90);
361 _vbox.pack_end (_vspacer, true, true);
362 _vbox.pack_end (_vnotebook, false, false);
363 _vbox.pack_end (_vlabel, false, false);
365 REMOVE_FROM_GTK_PARENT(*_body)
366 REMOVE_FROM_GTK_PARENT(_vscroll)
367 REMOVE_FROM_GTK_PARENT(_hscroll)
368 REMOVE_FROM_GTK_PARENT(_vbox)
369 REMOVE_FROM_GTK_PARENT(_hbox)
371 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
372 attach (_vscroll, 3, 4, 2, 3, SHRINK);
373 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
374 attach (_vbox, 2, 3, 2, 3, SHRINK);
375 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
379 /** @return columns list */
380 PortGroupList const *
381 PortMatrix::columns () const
383 return &_ports[_column_index];
386 boost::shared_ptr<const PortGroup>
387 PortMatrix::visible_columns () const
389 return visible_ports (_column_index);
392 /* @return rows list */
393 PortGroupList const *
394 PortMatrix::rows () const
396 return &_ports[_row_index];
399 boost::shared_ptr<const PortGroup>
400 PortMatrix::visible_rows () const
402 return visible_ports (_row_index);
405 /** @param column Column; its bundle may be 0 if we are over a row heading.
406 * @param row Row; its bundle may be 0 if we are over a column heading.
409 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
411 using namespace Menu_Helpers;
416 _menu->set_name ("ArdourContextMenu");
418 MenuList& items = _menu->items ();
421 bc[_column_index] = column;
422 bc[_row_index] = row;
425 bool need_separator = false;
427 for (int dim = 0; dim < 2; ++dim) {
429 if (bc[dim].bundle) {
431 Menu* m = manage (new Menu);
432 MenuList& sub = m->items ();
434 boost::weak_ptr<Bundle> w (bc[dim].bundle);
436 if (can_add_channels (bc[dim].bundle)) {
437 /* Start off with options for the `natural' port type */
438 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
439 if (should_show (*i)) {
440 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
441 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
445 /* Now add other ones */
446 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
447 if (!should_show (*i)) {
448 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
449 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
454 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
456 buf, sizeof (buf), _("Rename '%s'..."),
457 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
462 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
467 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
468 if (bc[dim].channel != -1) {
469 add_remove_option (sub, w, bc[dim].channel);
472 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
475 if (bc[dim].bundle->nchannels().n_total() > 1) {
476 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
477 if (should_show (bc[dim].bundle->channel_type(i))) {
478 add_remove_option (sub, w, i);
485 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
486 if ((_show_only_bundles && c > 0) || c == 1) {
488 /* we're looking just at bundles, or our bundle has only one channel, so just offer
489 to disassociate all on the bundle.
492 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
494 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
499 if (bc[dim].channel != -1) {
500 /* specific channel under the menu, so just offer to disassociate that */
501 add_disassociate_option (sub, w, dim, bc[dim].channel);
503 /* no specific channel; offer to disassociate all, or any one in particular */
504 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
506 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
509 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
510 if (should_show (bc[dim].bundle->channel_type(i))) {
511 add_disassociate_option (sub, w, dim, i);
517 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
518 need_separator = true;
523 if (need_separator) {
524 items.push_back (SeparatorElem ());
527 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
529 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
530 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
531 _inhibit_toggle_show_only_bundles = true;
532 i->set_active (!_show_only_bundles);
533 _inhibit_toggle_show_only_bundles = false;
535 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
536 items.back().set_sensitive (can_flip ());
542 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
544 boost::shared_ptr<Bundle> sb = b.lock ();
549 remove_channel (BundleChannel (sb, c));
554 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
556 boost::shared_ptr<Bundle> sb = b.lock ();
561 rename_channel (BundleChannel (sb, c));
565 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
567 boost::shared_ptr<Bundle> sb = bundle.lock ();
572 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
573 if (should_show (sb->channel_type(i))) {
574 disassociate_all_on_channel (bundle, i, dim);
580 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
582 boost::shared_ptr<Bundle> sb = bundle.lock ();
587 PortGroup::BundleList a = _ports[1-dim].bundles ();
589 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
590 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
592 if (!should_show ((*i)->bundle->channel_type(j))) {
597 c[dim] = BundleChannel (sb, channel);
598 c[1-dim] = BundleChannel ((*i)->bundle, j);
600 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
601 set_state (c, false);
606 _body->rebuild_and_draw_grid ();
610 PortMatrix::setup_global_ports ()
612 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
614 for (int i = 0; i < 2; ++i) {
615 if (list_is_global (i)) {
622 PortMatrix::setup_global_ports_proxy (RouteSortOrderKey sk)
624 if (sk == EditorSort) {
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));
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);
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);
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);
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) {