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 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
340 attach (_vscroll, 3, 4, 1, 2, SHRINK);
341 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
342 attach (_vbox, 1, 2, 1, 2, SHRINK);
343 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
349 _arrangement = TOP_TO_RIGHT;
350 _hlabel.set_label (_("<b>Sources</b>"));
351 _vlabel.set_label (_("<b>Destinations</b>"));
352 _vlabel.set_angle (-90);
354 _vbox.pack_end (_vspacer, true, true);
355 _vbox.pack_end (_vnotebook, false, false);
356 _vbox.pack_end (_vlabel, false, false);
358 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
359 attach (_vscroll, 3, 4, 2, 3, SHRINK);
360 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
361 attach (_vbox, 2, 3, 2, 3, SHRINK);
362 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
366 /** @return columns list */
367 PortGroupList const *
368 PortMatrix::columns () const
370 return &_ports[_column_index];
373 boost::shared_ptr<const PortGroup>
374 PortMatrix::visible_columns () const
376 return visible_ports (_column_index);
379 /* @return rows list */
380 PortGroupList const *
381 PortMatrix::rows () const
383 return &_ports[_row_index];
386 boost::shared_ptr<const PortGroup>
387 PortMatrix::visible_rows () const
389 return visible_ports (_row_index);
392 /** @param column Column; its bundle may be 0 if we are over a row heading.
393 * @param row Row; its bundle may be 0 if we are over a column heading.
396 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
398 using namespace Menu_Helpers;
403 _menu->set_name ("ArdourContextMenu");
405 MenuList& items = _menu->items ();
408 bc[_column_index] = column;
409 bc[_row_index] = row;
412 bool need_separator = false;
414 for (int dim = 0; dim < 2; ++dim) {
416 if (bc[dim].bundle) {
418 Menu* m = manage (new Menu);
419 MenuList& sub = m->items ();
421 boost::weak_ptr<Bundle> w (bc[dim].bundle);
423 if (can_add_channels (bc[dim].bundle)) {
424 /* Start off with options for the `natural' port type */
425 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
426 if (should_show (*i)) {
427 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
428 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
432 /* Now add other ones */
433 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
434 if (!should_show (*i)) {
435 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
436 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
441 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
443 buf, sizeof (buf), _("Rename '%s'..."),
444 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
449 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
454 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
455 if (bc[dim].channel != -1) {
456 add_remove_option (sub, w, bc[dim].channel);
459 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
462 if (bc[dim].bundle->nchannels().n_total() > 1) {
463 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
464 if (should_show (bc[dim].bundle->channel_type(i))) {
465 add_remove_option (sub, w, i);
472 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
473 if ((_show_only_bundles && c > 0) || c == 1) {
475 /* we're looking just at bundles, or our bundle has only one channel, so just offer
476 to disassociate all on the bundle.
479 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
481 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
486 if (bc[dim].channel != -1) {
487 /* specific channel under the menu, so just offer to disassociate that */
488 add_disassociate_option (sub, w, dim, bc[dim].channel);
490 /* no specific channel; offer to disassociate all, or any one in particular */
491 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
493 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
496 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
497 if (should_show (bc[dim].bundle->channel_type(i))) {
498 add_disassociate_option (sub, w, dim, i);
504 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
505 need_separator = true;
510 if (need_separator) {
511 items.push_back (SeparatorElem ());
514 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
516 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
517 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
518 _inhibit_toggle_show_only_bundles = true;
519 i->set_active (!_show_only_bundles);
520 _inhibit_toggle_show_only_bundles = false;
522 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
523 items.back().set_sensitive (can_flip ());
529 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
531 boost::shared_ptr<Bundle> sb = b.lock ();
536 remove_channel (BundleChannel (sb, c));
541 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
543 boost::shared_ptr<Bundle> sb = b.lock ();
548 rename_channel (BundleChannel (sb, c));
552 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
554 boost::shared_ptr<Bundle> sb = bundle.lock ();
559 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
560 if (should_show (sb->channel_type(i))) {
561 disassociate_all_on_channel (bundle, i, dim);
567 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
569 boost::shared_ptr<Bundle> sb = bundle.lock ();
574 PortGroup::BundleList a = _ports[1-dim].bundles ();
576 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
577 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
579 if (!should_show ((*i)->bundle->channel_type(j))) {
584 c[dim] = BundleChannel (sb, channel);
585 c[1-dim] = BundleChannel ((*i)->bundle, j);
587 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
588 set_state (c, false);
593 _body->rebuild_and_draw_grid ();
597 PortMatrix::setup_global_ports ()
599 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
601 for (int i = 0; i < 2; ++i) {
602 if (list_is_global (i)) {
609 PortMatrix::setup_global_ports_proxy (RouteSortOrderKey sk)
611 if (sk == EditorSort) {
612 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
616 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
621 PortMatrix::setup_all_ports ()
623 if (_session->deletion_in_progress()) {
627 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
634 PortMatrix::toggle_show_only_bundles ()
636 if (_inhibit_toggle_show_only_bundles) {
640 _show_only_bundles = !_show_only_bundles;
644 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
645 setting, so we need to set things up again now.
650 pair<uint32_t, uint32_t>
651 PortMatrix::max_size () const
653 pair<uint32_t, uint32_t> m = _body->max_size ();
655 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
656 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
662 PortMatrix::on_scroll_event (GdkEventScroll* ev)
664 double const h = _hscroll.get_value ();
665 double const v = _vscroll.get_value ();
667 switch (ev->direction) {
669 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
671 case GDK_SCROLL_DOWN:
672 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
674 case GDK_SCROLL_LEFT:
675 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
677 case GDK_SCROLL_RIGHT:
678 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
685 boost::shared_ptr<IO>
686 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
688 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
690 io = _ports[1].io_from_bundle (b);
697 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
699 return io_from_bundle (b);
703 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
705 boost::shared_ptr<IO> io = io_from_bundle (b);
708 int const r = io->add_port ("", this, t);
710 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
711 "support the new configuration."
713 msg.set_title (_("Cannot add port"));
720 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
722 return io_from_bundle (b);
726 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
728 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
731 boost::shared_ptr<Port> p = io->nth (b.channel);
733 int const r = io->remove_port (p, this);
735 ArdourDialog d (_("Port removal not allowed"));
736 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
737 d.get_vbox()->pack_start (l);
738 d.add_button (Stock::OK, RESPONSE_ACCEPT);
748 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
750 boost::shared_ptr<Bundle> b = w.lock ();
755 /* Remove channels backwards so that we don't renumber channels
756 that we are about to remove.
758 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
759 if (should_show (b->channel_type(i))) {
760 remove_channel (ARDOUR::BundleChannel (b, i));
766 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
768 boost::shared_ptr<Bundle> b = w.lock ();
777 PortMatrix::setup_notebooks ()
779 int const h_current_page = _hnotebook.get_current_page ();
780 int const v_current_page = _vnotebook.get_current_page ();
782 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
783 when adding or removing pages to or from notebooks, so ignore them */
785 _ignore_notebook_page_selected = true;
787 remove_notebook_pages (_hnotebook);
788 remove_notebook_pages (_vnotebook);
790 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
791 HBox* dummy = manage (new HBox);
793 Label* label = manage (new Label ((*i)->name));
794 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
795 label->set_use_markup ();
797 if (_arrangement == LEFT_TO_BOTTOM) {
798 _vnotebook.prepend_page (*dummy, *label);
800 /* Reverse the order of vertical tabs when they are on the right hand side
801 so that from top to bottom it is the same order as that from left to right
804 _vnotebook.append_page (*dummy, *label);
808 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
809 HBox* dummy = manage (new HBox);
811 Label* label = manage (new Label ((*i)->name));
812 label->set_use_markup ();
814 _hnotebook.append_page (*dummy, *label);
817 _ignore_notebook_page_selected = false;
819 if (_arrangement == TOP_TO_RIGHT) {
820 _vnotebook.set_tab_pos (POS_RIGHT);
821 _hnotebook.set_tab_pos (POS_TOP);
823 _vnotebook.set_tab_pos (POS_LEFT);
824 _hnotebook.set_tab_pos (POS_BOTTOM);
827 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
828 _hnotebook.set_current_page (h_current_page);
830 _hnotebook.set_current_page (0);
833 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
834 _vnotebook.set_current_page (v_current_page);
836 _vnotebook.set_current_page (0);
839 if (_hnotebook.get_n_pages() <= 1) {
845 if (_vnotebook.get_n_pages() <= 1) {
853 PortMatrix::remove_notebook_pages (Notebook& n)
855 int const N = n.get_n_pages ();
857 for (int i = 0; i < N; ++i) {
863 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
865 if (_ignore_notebook_page_selected) {
875 PortMatrix::session_going_away ()
881 PortMatrix::body_dimensions_changed ()
883 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
884 if (_arrangement == TOP_TO_RIGHT) {
885 _vspacer.set_size_request (-1, _body->column_labels_height ());
893 _parent->get_size (curr_width, curr_height);
895 pair<uint32_t, uint32_t> m = max_size ();
897 /* Don't shrink the window */
898 m.first = max (int (m.first), curr_width);
899 m.second = max (int (m.second), curr_height);
901 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
904 /** @return The PortGroup that is currently visible (ie selected by
905 * the notebook) along a given axis.
907 boost::shared_ptr<const PortGroup>
908 PortMatrix::visible_ports (int d) const
910 PortGroupList const & p = _ports[d];
911 PortGroupList::List::const_iterator j = p.begin ();
913 /* The logic to compute the index here is a bit twisty because for
914 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
915 tabs in setup_notebooks ().
919 if (d == _row_index) {
920 if (_arrangement == LEFT_TO_BOTTOM) {
921 n = p.size() - _vnotebook.get_current_page () - 1;
923 n = _vnotebook.get_current_page ();
926 n = _hnotebook.get_current_page ();
930 while (i != int (n) && j != p.end ()) {
936 return boost::shared_ptr<const PortGroup> ();
943 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
945 using namespace Menu_Helpers;
947 boost::shared_ptr<Bundle> b = w.lock ();
953 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
954 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
958 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
960 using namespace Menu_Helpers;
962 boost::shared_ptr<Bundle> b = w.lock ();
968 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
969 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
973 PortMatrix::port_connected_or_disconnected ()
975 _body->rebuild_and_draw_grid ();
976 update_tab_highlighting ();
979 /** Update the highlighting of tab names to reflect which ones
980 * have connections. This is pretty inefficient, unfortunately,
981 * but maybe that doesn't matter too much.
984 PortMatrix::update_tab_highlighting ()
990 for (int i = 0; i < 2; ++i) {
992 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
994 PortGroupList const * gl = ports (i);
996 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
997 bool has_connection = false;
998 PortGroup::BundleList const & bl = (*j)->bundles ();
999 PortGroup::BundleList::const_iterator k = bl.begin ();
1000 while (k != bl.end()) {
1001 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1002 has_connection = true;
1008 /* Find the page index that we should update; this is backwards
1009 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1012 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1013 page = notebook->get_n_pages() - p - 1;
1016 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1017 string c = label->get_label ();
1018 if (c.length() && c[0] == '<' && !has_connection) {
1019 /* this label is marked up with <b> but shouldn't be */
1020 label->set_text ((*j)->name);
1021 } else if (c.length() && c[0] != '<' && has_connection) {
1022 /* this label is not marked up with <b> but should be */
1023 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1032 PortMatrix::channel_noun () const
1034 return _("channel");
1037 /** @return true if this matrix should show bundles / ports of type \t */
1039 PortMatrix::should_show (DataType t) const
1041 return (_type == DataType::NIL || t == _type);
1045 PortMatrix::count_of_our_type (ChanCount c) const
1047 if (_type == DataType::NIL) {
1048 return c.n_total ();
1051 return c.get (_type);
1054 /** @return The number of ports of our type in the given channel count,
1055 * but returning 1 if there are no ports.
1058 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1060 uint32_t n = count_of_our_type (c);
1068 PortMatrixNode::State
1069 PortMatrix::get_association (PortMatrixNode node) const
1071 if (show_only_bundles ()) {
1073 bool have_off_diagonal_association = false;
1074 bool have_diagonal_association = false;
1075 bool have_diagonal_not_association = false;
1077 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1079 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1081 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1085 ARDOUR::BundleChannel c[2];
1086 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1087 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1089 PortMatrixNode::State const s = get_state (c);
1092 case PortMatrixNode::ASSOCIATED:
1094 have_diagonal_association = true;
1096 have_off_diagonal_association = true;
1100 case PortMatrixNode::NOT_ASSOCIATED:
1102 have_diagonal_not_association = true;
1112 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1113 return PortMatrixNode::ASSOCIATED;
1114 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1115 return PortMatrixNode::NOT_ASSOCIATED;
1118 return PortMatrixNode::PARTIAL;
1122 ARDOUR::BundleChannel c[2];
1123 c[column_index()] = node.column;
1124 c[row_index()] = node.row;
1125 return get_state (c);
1130 return PortMatrixNode::NOT_ASSOCIATED;
1133 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1135 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1137 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1140 /** See if a `flip' is possible.
1141 * @return If flip is possible, the new (row, column) notebook indices that
1142 * should be selected; otherwise, (-1, -1)
1145 PortMatrix::check_flip () const
1147 /* Look for the row's port group name in the columns */
1150 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1151 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1152 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1157 if (i == _ports[_column_index].end ()) {
1158 return make_pair (-1, -1);
1161 /* Look for the column's port group name in the rows */
1164 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1165 i = _ports[_row_index].begin();
1166 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1171 if (i == _ports[_row_index].end ()) {
1172 return make_pair (-1, -1);
1175 if (_arrangement == LEFT_TO_BOTTOM) {
1176 new_row = _ports[_row_index].size() - new_row - 1;
1179 return make_pair (new_row, new_column);
1183 PortMatrix::can_flip () const
1185 return check_flip().first != -1;
1188 /** Flip the column and row pages around, if possible */
1192 pair<int, int> n = check_flip ();
1193 if (n.first == -1) {
1197 _vnotebook.set_current_page (n.first);
1198 _hnotebook.set_current_page (n.second);
1202 PortMatrix::key_press (GdkEventKey* k)
1204 if (k->keyval == GDK_f) {