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)
750 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
753 boost::shared_ptr<Port> p = io->nth (b.channel);
755 int const r = io->remove_port (p, this);
757 ArdourDialog d (_("Port removal not allowed"));
758 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."));
759 d.get_vbox()->pack_start (l);
760 d.add_button (Stock::OK, RESPONSE_ACCEPT);
770 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
772 boost::shared_ptr<Bundle> b = w.lock ();
777 /* Remove channels backwards so that we don't renumber channels
778 that we are about to remove.
780 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
781 if (should_show (b->channel_type(i))) {
782 remove_channel (ARDOUR::BundleChannel (b, i));
788 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
790 boost::shared_ptr<Bundle> b = w.lock ();
799 PortMatrix::setup_notebooks ()
801 int const h_current_page = _hnotebook.get_current_page ();
802 int const v_current_page = _vnotebook.get_current_page ();
804 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
805 when adding or removing pages to or from notebooks, so ignore them */
807 _ignore_notebook_page_selected = true;
809 remove_notebook_pages (_hnotebook);
810 remove_notebook_pages (_vnotebook);
812 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
813 HBox* dummy = manage (new HBox);
815 Label* label = manage (new Label ((*i)->name));
816 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
817 label->set_use_markup ();
819 if (_arrangement == LEFT_TO_BOTTOM) {
820 _vnotebook.prepend_page (*dummy, *label);
822 /* Reverse the order of vertical tabs when they are on the right hand side
823 so that from top to bottom it is the same order as that from left to right
826 _vnotebook.append_page (*dummy, *label);
830 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
831 HBox* dummy = manage (new HBox);
833 Label* label = manage (new Label ((*i)->name));
834 label->set_use_markup ();
836 _hnotebook.append_page (*dummy, *label);
839 _ignore_notebook_page_selected = false;
841 if (_arrangement == TOP_TO_RIGHT) {
842 _vnotebook.set_tab_pos (POS_RIGHT);
843 _hnotebook.set_tab_pos (POS_TOP);
845 _vnotebook.set_tab_pos (POS_LEFT);
846 _hnotebook.set_tab_pos (POS_BOTTOM);
849 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
850 _hnotebook.set_current_page (h_current_page);
852 _hnotebook.set_current_page (0);
855 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
856 _vnotebook.set_current_page (v_current_page);
858 _vnotebook.set_current_page (0);
861 if (_hnotebook.get_n_pages() <= 1) {
867 if (_vnotebook.get_n_pages() <= 1) {
875 PortMatrix::remove_notebook_pages (Notebook& n)
877 int const N = n.get_n_pages ();
879 for (int i = 0; i < N; ++i) {
885 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
887 if (_ignore_notebook_page_selected) {
897 PortMatrix::session_going_away ()
903 PortMatrix::body_dimensions_changed ()
905 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
906 if (_arrangement == TOP_TO_RIGHT) {
907 _vspacer.set_size_request (-1, _body->column_labels_height ());
915 _parent->get_size (curr_width, curr_height);
917 pair<uint32_t, uint32_t> m = max_size ();
919 /* Don't shrink the window */
920 m.first = max (int (m.first), curr_width);
921 m.second = max (int (m.second), curr_height);
923 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
926 /** @return The PortGroup that is currently visible (ie selected by
927 * the notebook) along a given axis.
929 boost::shared_ptr<const PortGroup>
930 PortMatrix::visible_ports (int d) const
932 PortGroupList const & p = _ports[d];
933 PortGroupList::List::const_iterator j = p.begin ();
935 /* The logic to compute the index here is a bit twisty because for
936 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
937 tabs in setup_notebooks ().
941 if (d == _row_index) {
942 if (_arrangement == LEFT_TO_BOTTOM) {
943 n = p.size() - _vnotebook.get_current_page () - 1;
945 n = _vnotebook.get_current_page ();
948 n = _hnotebook.get_current_page ();
952 while (i != int (n) && j != p.end ()) {
958 return boost::shared_ptr<const PortGroup> ();
965 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
967 using namespace Menu_Helpers;
969 boost::shared_ptr<Bundle> b = w.lock ();
975 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
976 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
980 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
982 using namespace Menu_Helpers;
984 boost::shared_ptr<Bundle> b = w.lock ();
990 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
991 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
995 PortMatrix::port_connected_or_disconnected ()
997 _body->rebuild_and_draw_grid ();
998 update_tab_highlighting ();
1001 /** Update the highlighting of tab names to reflect which ones
1002 * have connections. This is pretty inefficient, unfortunately,
1003 * but maybe that doesn't matter too much.
1006 PortMatrix::update_tab_highlighting ()
1012 for (int i = 0; i < 2; ++i) {
1014 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1016 PortGroupList const * gl = ports (i);
1018 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1019 bool has_connection = false;
1020 PortGroup::BundleList const & bl = (*j)->bundles ();
1021 PortGroup::BundleList::const_iterator k = bl.begin ();
1022 while (k != bl.end()) {
1023 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1024 has_connection = true;
1030 /* Find the page index that we should update; this is backwards
1031 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1034 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1035 page = notebook->get_n_pages() - p - 1;
1038 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1039 string c = label->get_label ();
1040 if (c.length() && c[0] == '<' && !has_connection) {
1041 /* this label is marked up with <b> but shouldn't be */
1042 label->set_text ((*j)->name);
1043 } else if (c.length() && c[0] != '<' && has_connection) {
1044 /* this label is not marked up with <b> but should be */
1045 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1054 PortMatrix::channel_noun () const
1056 return _("channel");
1059 /** @return true if this matrix should show bundles / ports of type \t */
1061 PortMatrix::should_show (DataType t) const
1063 return (_type == DataType::NIL || t == _type);
1067 PortMatrix::count_of_our_type (ChanCount c) const
1069 if (_type == DataType::NIL) {
1070 return c.n_total ();
1073 return c.get (_type);
1076 /** @return The number of ports of our type in the given channel count,
1077 * but returning 1 if there are no ports.
1080 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1082 uint32_t n = count_of_our_type (c);
1090 PortMatrixNode::State
1091 PortMatrix::get_association (PortMatrixNode node) const
1093 if (show_only_bundles ()) {
1095 bool have_off_diagonal_association = false;
1096 bool have_diagonal_association = false;
1097 bool have_diagonal_not_association = false;
1099 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1101 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1103 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1107 ARDOUR::BundleChannel c[2];
1108 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1109 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1111 PortMatrixNode::State const s = get_state (c);
1114 case PortMatrixNode::ASSOCIATED:
1116 have_diagonal_association = true;
1118 have_off_diagonal_association = true;
1122 case PortMatrixNode::NOT_ASSOCIATED:
1124 have_diagonal_not_association = true;
1134 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1135 return PortMatrixNode::ASSOCIATED;
1136 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1137 return PortMatrixNode::NOT_ASSOCIATED;
1140 return PortMatrixNode::PARTIAL;
1144 ARDOUR::BundleChannel c[2];
1145 c[column_index()] = node.column;
1146 c[row_index()] = node.row;
1147 return get_state (c);
1151 abort(); /* NOTREACHED */
1152 return PortMatrixNode::NOT_ASSOCIATED;
1155 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1157 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1159 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1162 /** See if a `flip' is possible.
1163 * @return If flip is possible, the new (row, column) notebook indices that
1164 * should be selected; otherwise, (-1, -1)
1167 PortMatrix::check_flip () const
1169 /* Look for the row's port group name in the columns */
1172 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1173 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1174 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1179 if (i == _ports[_column_index].end ()) {
1180 return make_pair (-1, -1);
1183 /* Look for the column's port group name in the rows */
1186 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1187 i = _ports[_row_index].begin();
1188 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1193 if (i == _ports[_row_index].end ()) {
1194 return make_pair (-1, -1);
1197 if (_arrangement == LEFT_TO_BOTTOM) {
1198 new_row = _ports[_row_index].size() - new_row - 1;
1201 return make_pair (new_row, new_column);
1205 PortMatrix::can_flip () const
1207 return check_flip().first != -1;
1210 /** Flip the column and row pages around, if possible */
1214 pair<int, int> n = check_flip ();
1215 if (n.first == -1) {
1219 _vnotebook.set_current_page (n.first);
1220 _hnotebook.set_current_page (n.second);
1224 PortMatrix::key_press (GdkEventKey* k)
1226 if (k->keyval == GDK_f) {