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), 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 ()
624 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
628 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
632 PortMatrix::setup_all_ports ()
634 if (_session->deletion_in_progress()) {
638 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
645 PortMatrix::toggle_show_only_bundles ()
647 if (_inhibit_toggle_show_only_bundles) {
651 _show_only_bundles = !_show_only_bundles;
655 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
656 setting, so we need to set things up again now.
661 pair<uint32_t, uint32_t>
662 PortMatrix::max_size () const
664 pair<uint32_t, uint32_t> m = _body->max_size ();
666 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
667 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
673 PortMatrix::on_scroll_event (GdkEventScroll* ev)
675 double const h = _hscroll.get_value ();
676 double const v = _vscroll.get_value ();
678 switch (ev->direction) {
680 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
682 case GDK_SCROLL_DOWN:
683 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
685 case GDK_SCROLL_LEFT:
686 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
688 case GDK_SCROLL_RIGHT:
689 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
696 boost::shared_ptr<IO>
697 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
699 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
701 io = _ports[1].io_from_bundle (b);
708 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
710 return io_from_bundle (b);
714 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
716 boost::shared_ptr<IO> io = io_from_bundle (b);
719 int const r = io->add_port ("", this, t);
721 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
722 "support the new configuration."
724 msg.set_title (_("Cannot add port"));
731 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
733 return io_from_bundle (b);
737 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
739 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
742 boost::shared_ptr<Port> p = io->nth (b.channel);
744 int const r = io->remove_port (p, this);
746 ArdourDialog d (_("Port removal not allowed"));
747 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."));
748 d.get_vbox()->pack_start (l);
749 d.add_button (Stock::OK, RESPONSE_ACCEPT);
759 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
761 boost::shared_ptr<Bundle> b = w.lock ();
766 /* Remove channels backwards so that we don't renumber channels
767 that we are about to remove.
769 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
770 if (should_show (b->channel_type(i))) {
771 remove_channel (ARDOUR::BundleChannel (b, i));
777 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
779 boost::shared_ptr<Bundle> b = w.lock ();
788 PortMatrix::setup_notebooks ()
790 int const h_current_page = _hnotebook.get_current_page ();
791 int const v_current_page = _vnotebook.get_current_page ();
793 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
794 when adding or removing pages to or from notebooks, so ignore them */
796 _ignore_notebook_page_selected = true;
798 remove_notebook_pages (_hnotebook);
799 remove_notebook_pages (_vnotebook);
801 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
802 HBox* dummy = manage (new HBox);
804 Label* label = manage (new Label ((*i)->name));
805 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
806 label->set_use_markup ();
808 if (_arrangement == LEFT_TO_BOTTOM) {
809 _vnotebook.prepend_page (*dummy, *label);
811 /* Reverse the order of vertical tabs when they are on the right hand side
812 so that from top to bottom it is the same order as that from left to right
815 _vnotebook.append_page (*dummy, *label);
819 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
820 HBox* dummy = manage (new HBox);
822 Label* label = manage (new Label ((*i)->name));
823 label->set_use_markup ();
825 _hnotebook.append_page (*dummy, *label);
828 _ignore_notebook_page_selected = false;
830 if (_arrangement == TOP_TO_RIGHT) {
831 _vnotebook.set_tab_pos (POS_RIGHT);
832 _hnotebook.set_tab_pos (POS_TOP);
834 _vnotebook.set_tab_pos (POS_LEFT);
835 _hnotebook.set_tab_pos (POS_BOTTOM);
838 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
839 _hnotebook.set_current_page (h_current_page);
841 _hnotebook.set_current_page (0);
844 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
845 _vnotebook.set_current_page (v_current_page);
847 _vnotebook.set_current_page (0);
850 if (_hnotebook.get_n_pages() <= 1) {
856 if (_vnotebook.get_n_pages() <= 1) {
864 PortMatrix::remove_notebook_pages (Notebook& n)
866 int const N = n.get_n_pages ();
868 for (int i = 0; i < N; ++i) {
874 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
876 if (_ignore_notebook_page_selected) {
886 PortMatrix::session_going_away ()
892 PortMatrix::body_dimensions_changed ()
894 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
895 if (_arrangement == TOP_TO_RIGHT) {
896 _vspacer.set_size_request (-1, _body->column_labels_height ());
904 _parent->get_size (curr_width, curr_height);
906 pair<uint32_t, uint32_t> m = max_size ();
908 /* Don't shrink the window */
909 m.first = max (int (m.first), curr_width);
910 m.second = max (int (m.second), curr_height);
912 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
915 /** @return The PortGroup that is currently visible (ie selected by
916 * the notebook) along a given axis.
918 boost::shared_ptr<const PortGroup>
919 PortMatrix::visible_ports (int d) const
921 PortGroupList const & p = _ports[d];
922 PortGroupList::List::const_iterator j = p.begin ();
924 /* The logic to compute the index here is a bit twisty because for
925 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
926 tabs in setup_notebooks ().
930 if (d == _row_index) {
931 if (_arrangement == LEFT_TO_BOTTOM) {
932 n = p.size() - _vnotebook.get_current_page () - 1;
934 n = _vnotebook.get_current_page ();
937 n = _hnotebook.get_current_page ();
941 while (i != int (n) && j != p.end ()) {
947 return boost::shared_ptr<const PortGroup> ();
954 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
956 using namespace Menu_Helpers;
958 boost::shared_ptr<Bundle> b = w.lock ();
964 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
965 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
969 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
971 using namespace Menu_Helpers;
973 boost::shared_ptr<Bundle> b = w.lock ();
979 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
980 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
984 PortMatrix::port_connected_or_disconnected ()
986 _body->rebuild_and_draw_grid ();
987 update_tab_highlighting ();
990 /** Update the highlighting of tab names to reflect which ones
991 * have connections. This is pretty inefficient, unfortunately,
992 * but maybe that doesn't matter too much.
995 PortMatrix::update_tab_highlighting ()
1001 for (int i = 0; i < 2; ++i) {
1003 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1005 PortGroupList const * gl = ports (i);
1007 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1008 bool has_connection = false;
1009 PortGroup::BundleList const & bl = (*j)->bundles ();
1010 PortGroup::BundleList::const_iterator k = bl.begin ();
1011 while (k != bl.end()) {
1012 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1013 has_connection = true;
1019 /* Find the page index that we should update; this is backwards
1020 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1023 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1024 page = notebook->get_n_pages() - p - 1;
1027 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1028 string c = label->get_label ();
1029 if (c.length() && c[0] == '<' && !has_connection) {
1030 /* this label is marked up with <b> but shouldn't be */
1031 label->set_text ((*j)->name);
1032 } else if (c.length() && c[0] != '<' && has_connection) {
1033 /* this label is not marked up with <b> but should be */
1034 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1043 PortMatrix::channel_noun () const
1045 return _("channel");
1048 /** @return true if this matrix should show bundles / ports of type \t */
1050 PortMatrix::should_show (DataType t) const
1052 return (_type == DataType::NIL || t == _type);
1056 PortMatrix::count_of_our_type (ChanCount c) const
1058 if (_type == DataType::NIL) {
1059 return c.n_total ();
1062 return c.get (_type);
1065 /** @return The number of ports of our type in the given channel count,
1066 * but returning 1 if there are no ports.
1069 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1071 uint32_t n = count_of_our_type (c);
1079 PortMatrixNode::State
1080 PortMatrix::get_association (PortMatrixNode node) const
1082 if (show_only_bundles ()) {
1084 bool have_off_diagonal_association = false;
1085 bool have_diagonal_association = false;
1086 bool have_diagonal_not_association = false;
1088 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1090 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1092 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1096 ARDOUR::BundleChannel c[2];
1097 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1098 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1100 PortMatrixNode::State const s = get_state (c);
1103 case PortMatrixNode::ASSOCIATED:
1105 have_diagonal_association = true;
1107 have_off_diagonal_association = true;
1111 case PortMatrixNode::NOT_ASSOCIATED:
1113 have_diagonal_not_association = true;
1123 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1124 return PortMatrixNode::ASSOCIATED;
1125 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1126 return PortMatrixNode::NOT_ASSOCIATED;
1129 return PortMatrixNode::PARTIAL;
1133 ARDOUR::BundleChannel c[2];
1134 c[column_index()] = node.column;
1135 c[row_index()] = node.row;
1136 return get_state (c);
1141 return PortMatrixNode::NOT_ASSOCIATED;
1144 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1146 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1148 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1151 /** See if a `flip' is possible.
1152 * @return If flip is possible, the new (row, column) notebook indices that
1153 * should be selected; otherwise, (-1, -1)
1156 PortMatrix::check_flip () const
1158 /* Look for the row's port group name in the columns */
1161 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1162 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1163 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1168 if (i == _ports[_column_index].end ()) {
1169 return make_pair (-1, -1);
1172 /* Look for the column's port group name in the rows */
1175 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1176 i = _ports[_row_index].begin();
1177 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1182 if (i == _ports[_row_index].end ()) {
1183 return make_pair (-1, -1);
1186 if (_arrangement == LEFT_TO_BOTTOM) {
1187 new_row = _ports[_row_index].size() - new_row - 1;
1190 return make_pair (new_row, new_column);
1194 PortMatrix::can_flip () const
1196 return check_flip().first != -1;
1199 /** Flip the column and row pages around, if possible */
1203 pair<int, int> n = check_flip ();
1204 if (n.first == -1) {
1208 _vnotebook.set_current_page (n.first);
1209 _hnotebook.set_current_page (n.second);
1213 PortMatrix::key_press (GdkEventKey* k)
1215 if (k->keyval == GDK_f) {