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 "gtkmm2ext/utils.h"
36 #include "port_matrix.h"
37 #include "port_matrix_body.h"
38 #include "port_matrix_component.h"
39 #include "ardour_dialog.h"
41 #include "gui_thread.h"
46 using namespace ARDOUR;
47 using namespace ARDOUR_UI_UTILS;
49 /** PortMatrix constructor.
50 * @param session Our session.
51 * @param type Port type that we are handling.
53 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
58 , _arrangement (TOP_TO_RIGHT)
61 , _min_height_divisor (1)
62 , _show_only_bundles (false)
63 , _inhibit_toggle_show_only_bundles (false)
64 , _ignore_notebook_page_selected (false)
66 set_session (session);
68 _body = new PortMatrixBody (this);
69 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
71 _hbox.pack_end (_hspacer, true, true);
72 _hbox.pack_end (_hnotebook, false, false);
73 _hbox.pack_end (_hlabel, false, false);
75 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
76 _vnotebook.property_tab_border() = 4;
77 _vnotebook.set_name (X_("PortMatrixLabel"));
78 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
79 _hnotebook.property_tab_border() = 4;
80 _hnotebook.set_name (X_("PortMatrixLabel"));
82 _vlabel.set_use_markup ();
83 _vlabel.set_alignment (1, 1);
84 _vlabel.set_padding (4, 16);
85 _vlabel.set_name (X_("PortMatrixLabel"));
86 _hlabel.set_use_markup ();
87 _hlabel.set_alignment (1, 0.5);
88 _hlabel.set_padding (16, 4);
89 _hlabel.set_name (X_("PortMatrixLabel"));
91 set_row_spacing (0, 8);
92 set_col_spacing (0, 8);
93 set_row_spacing (2, 8);
94 set_col_spacing (2, 8);
109 PortMatrix::~PortMatrix ()
115 /** Perform initial and once-only setup. This must be called by
116 * subclasses after they have set up _ports[] to at least some
117 * reasonable extent. Two-part initialisation is necessary because
118 * setting up _ports is largely done by virtual functions in
125 select_arrangement ();
127 /* Signal handling is kind of split into three parts:
129 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
130 * representation of the information in _ports[].
132 * 2. When certain other things change, we need to get our subclass to clear and
133 * re-fill _ports[], which in turn causes appropriate signals to be raised to
134 * hook into part (1).
136 * 3. Assorted other signals.
140 /* Part 1: the basic _ports[] change -> reset visuals */
142 for (int i = 0; i < 2; ++i) {
143 /* watch for the content of _ports[] changing */
144 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
146 /* and for bundles in _ports[] changing */
147 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
150 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
152 /* watch for routes being added or removed */
153 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
155 /* and also bundles */
156 _session->BundleAddedOrRemoved.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_page_size (_body->alloc_scroll_width());
253 a->set_step_increment (32);
254 a->set_page_increment (128);
256 /* Set the adjustment to zero if the size has changed.*/
257 if (a->get_upper() != _body->full_scroll_width()) {
258 a->set_upper (_body->full_scroll_width());
262 a = _vscroll.get_adjustment ();
264 a->set_page_size (_body->alloc_scroll_height());
265 a->set_step_increment (32);
266 a->set_page_increment (128);
268 if (a->get_upper() != _body->full_scroll_height()) {
269 a->set_upper (_body->full_scroll_height());
274 /** Disassociate all of our ports from each other */
276 PortMatrix::disassociate_all ()
278 PortGroup::BundleList a = _ports[0].bundles ();
279 PortGroup::BundleList b = _ports[1].bundles ();
281 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
282 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
283 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
284 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
286 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
290 BundleChannel c[2] = {
291 BundleChannel ((*i)->bundle, j),
292 BundleChannel ((*k)->bundle, l)
295 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
296 set_state (c, false);
304 _body->rebuild_and_draw_grid ();
307 /* Decide how to arrange the components of the matrix */
309 PortMatrix::select_arrangement ()
311 uint32_t const N[2] = {
312 count_of_our_type_min_1 (_ports[0].total_channels()),
313 count_of_our_type_min_1 (_ports[1].total_channels())
316 /* XXX: shirley there's an easier way than this */
318 if (_vspacer.get_parent()) {
319 _vbox.remove (_vspacer);
322 if (_vnotebook.get_parent()) {
323 _vbox.remove (_vnotebook);
326 if (_vlabel.get_parent()) {
327 _vbox.remove (_vlabel);
330 /* The list with the most channels goes on left or right, so that the most channel
331 names are printed horizontally and hence more readable. However we also
332 maintain notional `signal flow' vaguely from left to right. Subclasses
333 should choose where to put ports based on signal flowing from _ports[0]
340 _arrangement = LEFT_TO_BOTTOM;
341 _vlabel.set_label (_("<b>Sources</b>"));
342 _hlabel.set_label (_("<b>Destinations</b>"));
343 _vlabel.set_angle (90);
345 _vbox.pack_end (_vlabel, false, false);
346 _vbox.pack_end (_vnotebook, false, false);
347 _vbox.pack_end (_vspacer, true, true);
349 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
350 REMOVE_FROM_GTK_PARENT(*_body)
351 REMOVE_FROM_GTK_PARENT(_vscroll)
352 REMOVE_FROM_GTK_PARENT(_hscroll)
353 REMOVE_FROM_GTK_PARENT(_vbox)
354 REMOVE_FROM_GTK_PARENT(_hbox)
356 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
357 attach (_vscroll, 3, 4, 1, 2, SHRINK);
358 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
359 attach (_vbox, 1, 2, 1, 2, SHRINK);
360 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
366 _arrangement = TOP_TO_RIGHT;
367 _hlabel.set_label (_("<b>Sources</b>"));
368 _vlabel.set_label (_("<b>Destinations</b>"));
369 _vlabel.set_angle (-90);
371 _vbox.pack_end (_vspacer, true, true);
372 _vbox.pack_end (_vnotebook, false, false);
373 _vbox.pack_end (_vlabel, false, false);
375 REMOVE_FROM_GTK_PARENT(*_body)
376 REMOVE_FROM_GTK_PARENT(_vscroll)
377 REMOVE_FROM_GTK_PARENT(_hscroll)
378 REMOVE_FROM_GTK_PARENT(_vbox)
379 REMOVE_FROM_GTK_PARENT(_hbox)
381 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
382 attach (_vscroll, 3, 4, 2, 3, SHRINK);
383 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
384 attach (_vbox, 2, 3, 2, 3, SHRINK);
385 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
389 /** @return columns list */
390 PortGroupList const *
391 PortMatrix::columns () const
393 return &_ports[_column_index];
396 boost::shared_ptr<const PortGroup>
397 PortMatrix::visible_columns () const
399 return visible_ports (_column_index);
402 /* @return rows list */
403 PortGroupList const *
404 PortMatrix::rows () const
406 return &_ports[_row_index];
409 boost::shared_ptr<const PortGroup>
410 PortMatrix::visible_rows () const
412 return visible_ports (_row_index);
415 /** @param column Column; its bundle may be 0 if we are over a row heading.
416 * @param row Row; its bundle may be 0 if we are over a column heading.
419 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
421 using namespace Menu_Helpers;
426 _menu->set_name ("ArdourContextMenu");
428 MenuList& items = _menu->items ();
431 bc[_column_index] = column;
432 bc[_row_index] = row;
435 bool need_separator = false;
437 for (int dim = 0; dim < 2; ++dim) {
439 if (bc[dim].bundle) {
441 Menu* m = manage (new Menu);
442 MenuList& sub = m->items ();
444 boost::weak_ptr<Bundle> w (bc[dim].bundle);
446 if (can_add_channels (bc[dim].bundle)) {
447 /* Start off with options for the `natural' port type */
448 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
449 if (should_show (*i)) {
450 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
451 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
455 /* Now add other ones */
456 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
457 if (!should_show (*i)) {
458 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
459 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
464 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
466 buf, sizeof (buf), _("Rename '%s'..."),
467 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
472 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
477 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
478 if (bc[dim].channel != -1) {
479 add_remove_option (sub, w, bc[dim].channel);
482 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
485 if (bc[dim].bundle->nchannels().n_total() > 1) {
486 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
487 if (should_show (bc[dim].bundle->channel_type(i))) {
488 add_remove_option (sub, w, i);
495 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
496 if ((_show_only_bundles && c > 0) || c == 1) {
498 /* we're looking just at bundles, or our bundle has only one channel, so just offer
499 to disassociate all on the bundle.
502 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
504 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
509 if (bc[dim].channel != -1) {
510 /* specific channel under the menu, so just offer to disassociate that */
511 add_disassociate_option (sub, w, dim, bc[dim].channel);
513 /* no specific channel; offer to disassociate all, or any one in particular */
514 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
516 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
519 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
520 if (should_show (bc[dim].bundle->channel_type(i))) {
521 add_disassociate_option (sub, w, dim, i);
527 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
528 need_separator = true;
533 if (need_separator) {
534 items.push_back (SeparatorElem ());
537 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
539 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
540 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
541 _inhibit_toggle_show_only_bundles = true;
542 i->set_active (!_show_only_bundles);
543 _inhibit_toggle_show_only_bundles = false;
545 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
546 items.back().set_sensitive (can_flip ());
552 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
554 boost::shared_ptr<Bundle> sb = b.lock ();
559 remove_channel (BundleChannel (sb, c));
564 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
566 boost::shared_ptr<Bundle> sb = b.lock ();
571 rename_channel (BundleChannel (sb, c));
575 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
577 boost::shared_ptr<Bundle> sb = bundle.lock ();
582 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
583 if (should_show (sb->channel_type(i))) {
584 disassociate_all_on_channel (bundle, i, dim);
590 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
592 boost::shared_ptr<Bundle> sb = bundle.lock ();
597 PortGroup::BundleList a = _ports[1-dim].bundles ();
599 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
600 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
602 if (!should_show ((*i)->bundle->channel_type(j))) {
607 c[dim] = BundleChannel (sb, channel);
608 c[1-dim] = BundleChannel ((*i)->bundle, j);
610 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
611 set_state (c, false);
616 _body->rebuild_and_draw_grid ();
620 PortMatrix::setup_global_ports ()
622 if (!_session || _session->deletion_in_progress()) return;
623 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
625 for (int i = 0; i < 2; ++i) {
626 if (list_is_global (i)) {
633 PortMatrix::setup_global_ports_proxy ()
635 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
639 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
643 PortMatrix::setup_all_ports ()
645 if (_session->deletion_in_progress()) {
649 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
656 PortMatrix::toggle_show_only_bundles ()
658 if (_inhibit_toggle_show_only_bundles) {
662 _show_only_bundles = !_show_only_bundles;
666 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
667 setting, so we need to set things up again now.
672 pair<uint32_t, uint32_t>
673 PortMatrix::max_size () const
675 pair<uint32_t, uint32_t> m = _body->max_size ();
677 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
678 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
684 PortMatrix::on_scroll_event (GdkEventScroll* ev)
686 double const h = _hscroll.get_value ();
687 double const v = _vscroll.get_value ();
689 switch (ev->direction) {
691 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
693 case GDK_SCROLL_DOWN:
694 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
696 case GDK_SCROLL_LEFT:
697 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
699 case GDK_SCROLL_RIGHT:
700 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
707 boost::shared_ptr<IO>
708 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
710 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
712 io = _ports[1].io_from_bundle (b);
719 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
721 return io_from_bundle (b) != 0;
725 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
727 boost::shared_ptr<IO> io = io_from_bundle (b);
730 int const r = io->add_port ("", this, t);
732 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
733 "support the new configuration."
735 msg.set_title (_("Cannot add port"));
742 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
744 return io_from_bundle (b) != 0;
748 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
751 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
752 boost::shared_ptr<Port> p = io->nth (b.channel);
758 if (io->n_ports ().n_total () == 1) {
759 errmsg = _("The last port cannot be removed");
761 if (-1 == io->remove_port (p, this)) {
762 errmsg = _("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.");
766 if (!errmsg.empty ()) {
767 ArdourDialog d (_("Port removal not allowed"));
769 d.get_vbox()->pack_start (l);
770 d.add_button (Stock::OK, RESPONSE_ACCEPT);
778 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
780 boost::shared_ptr<Bundle> b = w.lock ();
785 /* Remove channels backwards so that we don't renumber channels
786 that we are about to remove.
788 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
789 if (should_show (b->channel_type(i))) {
790 remove_channel (ARDOUR::BundleChannel (b, i));
796 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
798 boost::shared_ptr<Bundle> b = w.lock ();
807 PortMatrix::setup_notebooks ()
809 int const h_current_page = _hnotebook.get_current_page ();
810 int const v_current_page = _vnotebook.get_current_page ();
812 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
813 when adding or removing pages to or from notebooks, so ignore them */
815 _ignore_notebook_page_selected = true;
817 remove_notebook_pages (_hnotebook);
818 remove_notebook_pages (_vnotebook);
820 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
821 HBox* dummy = manage (new HBox);
823 Label* label = manage (new Label ((*i)->name));
824 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
825 label->set_use_markup ();
827 if (_arrangement == LEFT_TO_BOTTOM) {
828 _vnotebook.prepend_page (*dummy, *label);
830 /* Reverse the order of vertical tabs when they are on the right hand side
831 so that from top to bottom it is the same order as that from left to right
834 _vnotebook.append_page (*dummy, *label);
838 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
839 HBox* dummy = manage (new HBox);
841 Label* label = manage (new Label ((*i)->name));
842 label->set_use_markup ();
844 _hnotebook.append_page (*dummy, *label);
847 _ignore_notebook_page_selected = false;
849 if (_arrangement == TOP_TO_RIGHT) {
850 _vnotebook.set_tab_pos (POS_RIGHT);
851 _hnotebook.set_tab_pos (POS_TOP);
853 _vnotebook.set_tab_pos (POS_LEFT);
854 _hnotebook.set_tab_pos (POS_BOTTOM);
857 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
858 _hnotebook.set_current_page (h_current_page);
860 _hnotebook.set_current_page (0);
863 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
864 _vnotebook.set_current_page (v_current_page);
866 _vnotebook.set_current_page (0);
869 if (_hnotebook.get_n_pages() <= 1) {
875 if (_vnotebook.get_n_pages() <= 1) {
883 PortMatrix::remove_notebook_pages (Notebook& n)
885 int const N = n.get_n_pages ();
887 for (int i = 0; i < N; ++i) {
893 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
895 if (_ignore_notebook_page_selected) {
905 PortMatrix::session_going_away ()
911 PortMatrix::body_dimensions_changed ()
913 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
914 if (_arrangement == TOP_TO_RIGHT) {
915 _vspacer.set_size_request (-1, _body->column_labels_height ());
923 _parent->get_size (curr_width, curr_height);
925 pair<uint32_t, uint32_t> m = max_size ();
927 /* Don't shrink the window */
928 m.first = max (int (m.first), curr_width);
929 m.second = max (int (m.second), curr_height);
931 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
934 /** @return The PortGroup that is currently visible (ie selected by
935 * the notebook) along a given axis.
937 boost::shared_ptr<const PortGroup>
938 PortMatrix::visible_ports (int d) const
940 PortGroupList const & p = _ports[d];
941 PortGroupList::List::const_iterator j = p.begin ();
943 /* The logic to compute the index here is a bit twisty because for
944 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
945 tabs in setup_notebooks ().
949 if (d == _row_index) {
950 if (_arrangement == LEFT_TO_BOTTOM) {
951 n = p.size() - _vnotebook.get_current_page () - 1;
953 n = _vnotebook.get_current_page ();
956 n = _hnotebook.get_current_page ();
960 while (i != int (n) && j != p.end ()) {
966 return boost::shared_ptr<const PortGroup> ();
973 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
975 using namespace Menu_Helpers;
977 boost::shared_ptr<Bundle> b = w.lock ();
983 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
984 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
988 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
990 using namespace Menu_Helpers;
992 boost::shared_ptr<Bundle> b = w.lock ();
998 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
999 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1003 PortMatrix::port_connected_or_disconnected ()
1005 _body->rebuild_and_draw_grid ();
1006 update_tab_highlighting ();
1009 /** Update the highlighting of tab names to reflect which ones
1010 * have connections. This is pretty inefficient, unfortunately,
1011 * but maybe that doesn't matter too much.
1014 PortMatrix::update_tab_highlighting ()
1020 for (int i = 0; i < 2; ++i) {
1022 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1024 PortGroupList const * gl = ports (i);
1026 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1027 bool has_connection = false;
1028 PortGroup::BundleList const & bl = (*j)->bundles ();
1029 PortGroup::BundleList::const_iterator k = bl.begin ();
1030 while (k != bl.end()) {
1031 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1032 has_connection = true;
1038 /* Find the page index that we should update; this is backwards
1039 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1042 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1043 page = notebook->get_n_pages() - p - 1;
1046 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1047 string c = label->get_label ();
1048 if (c.length() && c[0] == '<' && !has_connection) {
1049 /* this label is marked up with <b> but shouldn't be */
1050 label->set_text ((*j)->name);
1051 } else if (c.length() && c[0] != '<' && has_connection) {
1052 /* this label is not marked up with <b> but should be */
1053 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1062 PortMatrix::channel_noun () const
1064 return _("channel");
1067 /** @return true if this matrix should show bundles / ports of type \t */
1069 PortMatrix::should_show (DataType t) const
1071 return (_type == DataType::NIL || t == _type);
1075 PortMatrix::count_of_our_type (ChanCount c) const
1077 if (_type == DataType::NIL) {
1078 return c.n_total ();
1081 return c.get (_type);
1084 /** @return The number of ports of our type in the given channel count,
1085 * but returning 1 if there are no ports.
1088 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1090 uint32_t n = count_of_our_type (c);
1098 PortMatrixNode::State
1099 PortMatrix::get_association (PortMatrixNode node) const
1101 if (show_only_bundles ()) {
1103 bool have_off_diagonal_association = false;
1104 bool have_diagonal_association = false;
1105 bool have_diagonal_not_association = false;
1107 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1109 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1111 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1115 ARDOUR::BundleChannel c[2];
1116 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1117 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1119 PortMatrixNode::State const s = get_state (c);
1122 case PortMatrixNode::ASSOCIATED:
1124 have_diagonal_association = true;
1126 have_off_diagonal_association = true;
1130 case PortMatrixNode::NOT_ASSOCIATED:
1132 have_diagonal_not_association = true;
1142 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1143 return PortMatrixNode::ASSOCIATED;
1144 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1145 return PortMatrixNode::NOT_ASSOCIATED;
1148 return PortMatrixNode::PARTIAL;
1152 ARDOUR::BundleChannel c[2];
1153 c[column_index()] = node.column;
1154 c[row_index()] = node.row;
1155 return get_state (c);
1159 abort(); /* NOTREACHED */
1160 return PortMatrixNode::NOT_ASSOCIATED;
1163 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1165 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1167 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1170 /** See if a `flip' is possible.
1171 * @return If flip is possible, the new (row, column) notebook indices that
1172 * should be selected; otherwise, (-1, -1)
1175 PortMatrix::check_flip () const
1177 /* Look for the row's port group name in the columns */
1180 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1181 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1182 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1187 if (i == _ports[_column_index].end ()) {
1188 return make_pair (-1, -1);
1191 /* Look for the column's port group name in the rows */
1194 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1195 i = _ports[_row_index].begin();
1196 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1201 if (i == _ports[_row_index].end ()) {
1202 return make_pair (-1, -1);
1205 if (_arrangement == LEFT_TO_BOTTOM) {
1206 new_row = _ports[_row_index].size() - new_row - 1;
1209 return make_pair (new_row, new_column);
1213 PortMatrix::can_flip () const
1215 return check_flip().first != -1;
1218 /** Flip the column and row pages around, if possible */
1222 pair<int, int> n = check_flip ();
1223 if (n.first == -1) {
1227 _vnotebook.set_current_page (n.first);
1228 _hnotebook.set_current_page (n.second);
1232 PortMatrix::key_press (GdkEventKey* k)
1234 if (k->keyval == GDK_f) {