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());
157 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
159 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
160 Route::SyncOrderKeys.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this, _1), gui_context());
162 /* Part 3: other stuff */
164 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
166 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
167 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
169 reconnect_to_routes ();
174 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
176 PortMatrix::reconnect_to_routes ()
178 _route_connections.drop_connections ();
180 boost::shared_ptr<RouteList> routes = _session->get_routes ();
181 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
182 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
187 PortMatrix::route_processors_changed (RouteProcessorChange c)
189 if (c.type == RouteProcessorChange::MeterPointChange) {
190 /* this change has no impact on the port matrix */
194 setup_global_ports ();
197 /** A route has been added to or removed from the session */
199 PortMatrix::routes_changed ()
201 reconnect_to_routes ();
202 setup_global_ports ();
205 /** Set up everything that depends on the content of _ports[] */
209 if (!_session) return; // session went away
211 /* this needs to be done first, as the visible_ports() method uses the
212 notebook state to decide which ports are being shown */
218 update_tab_highlighting ();
223 PortMatrix::set_type (DataType t)
229 PortMatrix::hscroll_changed ()
231 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
235 PortMatrix::vscroll_changed ()
237 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
241 PortMatrix::setup_scrollbars ()
243 Adjustment* a = _hscroll.get_adjustment ();
245 a->set_upper (_body->full_scroll_width());
246 a->set_page_size (_body->alloc_scroll_width());
247 a->set_step_increment (32);
248 a->set_page_increment (128);
250 a = _vscroll.get_adjustment ();
252 a->set_upper (_body->full_scroll_height());
253 a->set_page_size (_body->alloc_scroll_height());
254 a->set_step_increment (32);
255 a->set_page_increment (128);
258 /** Disassociate all of our ports from each other */
260 PortMatrix::disassociate_all ()
262 PortGroup::BundleList a = _ports[0].bundles ();
263 PortGroup::BundleList b = _ports[1].bundles ();
265 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
266 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
267 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
268 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
270 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
274 BundleChannel c[2] = {
275 BundleChannel ((*i)->bundle, j),
276 BundleChannel ((*k)->bundle, l)
279 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
280 set_state (c, false);
288 _body->rebuild_and_draw_grid ();
291 /* Decide how to arrange the components of the matrix */
293 PortMatrix::select_arrangement ()
295 uint32_t const N[2] = {
296 count_of_our_type_min_1 (_ports[0].total_channels()),
297 count_of_our_type_min_1 (_ports[1].total_channels())
300 /* XXX: shirley there's an easier way than this */
302 if (_vspacer.get_parent()) {
303 _vbox.remove (_vspacer);
306 if (_vnotebook.get_parent()) {
307 _vbox.remove (_vnotebook);
310 if (_vlabel.get_parent()) {
311 _vbox.remove (_vlabel);
314 /* The list with the most channels goes on left or right, so that the most channel
315 names are printed horizontally and hence more readable. However we also
316 maintain notional `signal flow' vaguely from left to right. Subclasses
317 should choose where to put ports based on signal flowing from _ports[0]
324 _arrangement = LEFT_TO_BOTTOM;
325 _vlabel.set_label (_("<b>Sources</b>"));
326 _hlabel.set_label (_("<b>Destinations</b>"));
327 _vlabel.set_angle (90);
329 _vbox.pack_end (_vlabel, false, false);
330 _vbox.pack_end (_vnotebook, false, false);
331 _vbox.pack_end (_vspacer, true, true);
333 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
334 attach (_vscroll, 3, 4, 1, 2, SHRINK);
335 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
336 attach (_vbox, 1, 2, 1, 2, SHRINK);
337 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
343 _arrangement = TOP_TO_RIGHT;
344 _hlabel.set_label (_("<b>Sources</b>"));
345 _vlabel.set_label (_("<b>Destinations</b>"));
346 _vlabel.set_angle (-90);
348 _vbox.pack_end (_vspacer, true, true);
349 _vbox.pack_end (_vnotebook, false, false);
350 _vbox.pack_end (_vlabel, false, false);
352 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
353 attach (_vscroll, 3, 4, 2, 3, SHRINK);
354 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
355 attach (_vbox, 2, 3, 2, 3, SHRINK);
356 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
360 /** @return columns list */
361 PortGroupList const *
362 PortMatrix::columns () const
364 return &_ports[_column_index];
367 boost::shared_ptr<const PortGroup>
368 PortMatrix::visible_columns () const
370 return visible_ports (_column_index);
373 /* @return rows list */
374 PortGroupList const *
375 PortMatrix::rows () const
377 return &_ports[_row_index];
380 boost::shared_ptr<const PortGroup>
381 PortMatrix::visible_rows () const
383 return visible_ports (_row_index);
386 /** @param column Column; its bundle may be 0 if we are over a row heading.
387 * @param row Row; its bundle may be 0 if we are over a column heading.
390 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
392 using namespace Menu_Helpers;
397 _menu->set_name ("ArdourContextMenu");
399 MenuList& items = _menu->items ();
402 bc[_column_index] = column;
403 bc[_row_index] = row;
406 bool need_separator = false;
408 for (int dim = 0; dim < 2; ++dim) {
410 if (bc[dim].bundle) {
412 Menu* m = manage (new Menu);
413 MenuList& sub = m->items ();
415 boost::weak_ptr<Bundle> w (bc[dim].bundle);
417 if (can_add_channels (bc[dim].bundle)) {
418 /* Start off with options for the `natural' port type */
419 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
420 if (should_show (*i)) {
421 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
422 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
426 /* Now add other ones */
427 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
428 if (!should_show (*i)) {
429 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
430 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
435 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
437 buf, sizeof (buf), _("Rename '%s'..."),
438 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
443 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
448 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
449 if (bc[dim].channel != -1) {
450 add_remove_option (sub, w, bc[dim].channel);
453 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
456 if (bc[dim].bundle->nchannels().n_total() > 1) {
457 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
458 if (should_show (bc[dim].bundle->channel_type(i))) {
459 add_remove_option (sub, w, i);
466 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
467 if ((_show_only_bundles && c > 0) || c == 1) {
469 /* we're looking just at bundles, or our bundle has only one channel, so just offer
470 to disassociate all on the bundle.
473 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
475 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
480 if (bc[dim].channel != -1) {
481 /* specific channel under the menu, so just offer to disassociate that */
482 add_disassociate_option (sub, w, dim, bc[dim].channel);
484 /* no specific channel; offer to disassociate all, or any one in particular */
485 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
487 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
490 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
491 if (should_show (bc[dim].bundle->channel_type(i))) {
492 add_disassociate_option (sub, w, dim, i);
498 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
499 need_separator = true;
504 if (need_separator) {
505 items.push_back (SeparatorElem ());
508 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
510 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
511 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
512 _inhibit_toggle_show_only_bundles = true;
513 i->set_active (!_show_only_bundles);
514 _inhibit_toggle_show_only_bundles = false;
516 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
517 items.back().set_sensitive (can_flip ());
523 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
525 boost::shared_ptr<Bundle> sb = b.lock ();
530 remove_channel (BundleChannel (sb, c));
535 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
537 boost::shared_ptr<Bundle> sb = b.lock ();
542 rename_channel (BundleChannel (sb, c));
546 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
548 boost::shared_ptr<Bundle> sb = bundle.lock ();
553 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
554 if (should_show (sb->channel_type(i))) {
555 disassociate_all_on_channel (bundle, i, dim);
561 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
563 boost::shared_ptr<Bundle> sb = bundle.lock ();
568 PortGroup::BundleList a = _ports[1-dim].bundles ();
570 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
571 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
573 if (!should_show ((*i)->bundle->channel_type(j))) {
578 c[dim] = BundleChannel (sb, channel);
579 c[1-dim] = BundleChannel ((*i)->bundle, j);
581 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
582 set_state (c, false);
587 _body->rebuild_and_draw_grid ();
591 PortMatrix::setup_global_ports ()
593 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
595 for (int i = 0; i < 2; ++i) {
596 if (list_is_global (i)) {
603 PortMatrix::setup_global_ports_proxy (RouteSortOrderKey sk)
605 if (sk == EditorSort) {
606 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
610 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
615 PortMatrix::setup_all_ports ()
617 if (_session->deletion_in_progress()) {
621 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
628 PortMatrix::toggle_show_only_bundles ()
630 if (_inhibit_toggle_show_only_bundles) {
634 _show_only_bundles = !_show_only_bundles;
638 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
639 setting, so we need to set things up again now.
644 pair<uint32_t, uint32_t>
645 PortMatrix::max_size () const
647 pair<uint32_t, uint32_t> m = _body->max_size ();
649 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
650 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
656 PortMatrix::on_scroll_event (GdkEventScroll* ev)
658 double const h = _hscroll.get_value ();
659 double const v = _vscroll.get_value ();
661 switch (ev->direction) {
663 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
665 case GDK_SCROLL_DOWN:
666 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
668 case GDK_SCROLL_LEFT:
669 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
671 case GDK_SCROLL_RIGHT:
672 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
679 boost::shared_ptr<IO>
680 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
682 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
684 io = _ports[1].io_from_bundle (b);
691 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
693 return io_from_bundle (b);
697 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
699 boost::shared_ptr<IO> io = io_from_bundle (b);
702 int const r = io->add_port ("", this, t);
704 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
705 "support the new configuration."
707 msg.set_title (_("Cannot add port"));
714 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
716 return io_from_bundle (b);
720 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
722 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
725 boost::shared_ptr<Port> p = io->nth (b.channel);
727 int const r = io->remove_port (p, this);
729 ArdourDialog d (_("Port removal not allowed"));
730 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
731 d.get_vbox()->pack_start (l);
732 d.add_button (Stock::OK, RESPONSE_ACCEPT);
742 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
744 boost::shared_ptr<Bundle> b = w.lock ();
749 /* Remove channels backwards so that we don't renumber channels
750 that we are about to remove.
752 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
753 if (should_show (b->channel_type(i))) {
754 remove_channel (ARDOUR::BundleChannel (b, i));
760 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
762 boost::shared_ptr<Bundle> b = w.lock ();
771 PortMatrix::setup_notebooks ()
773 int const h_current_page = _hnotebook.get_current_page ();
774 int const v_current_page = _vnotebook.get_current_page ();
776 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
777 when adding or removing pages to or from notebooks, so ignore them */
779 _ignore_notebook_page_selected = true;
781 remove_notebook_pages (_hnotebook);
782 remove_notebook_pages (_vnotebook);
784 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
785 HBox* dummy = manage (new HBox);
787 Label* label = manage (new Label ((*i)->name));
788 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
789 label->set_use_markup ();
791 if (_arrangement == LEFT_TO_BOTTOM) {
792 _vnotebook.prepend_page (*dummy, *label);
794 /* Reverse the order of vertical tabs when they are on the right hand side
795 so that from top to bottom it is the same order as that from left to right
798 _vnotebook.append_page (*dummy, *label);
802 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
803 HBox* dummy = manage (new HBox);
805 Label* label = manage (new Label ((*i)->name));
806 label->set_use_markup ();
808 _hnotebook.append_page (*dummy, *label);
811 _ignore_notebook_page_selected = false;
813 if (_arrangement == TOP_TO_RIGHT) {
814 _vnotebook.set_tab_pos (POS_RIGHT);
815 _hnotebook.set_tab_pos (POS_TOP);
817 _vnotebook.set_tab_pos (POS_LEFT);
818 _hnotebook.set_tab_pos (POS_BOTTOM);
821 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
822 _hnotebook.set_current_page (h_current_page);
824 _hnotebook.set_current_page (0);
827 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
828 _vnotebook.set_current_page (v_current_page);
830 _vnotebook.set_current_page (0);
833 if (_hnotebook.get_n_pages() <= 1) {
839 if (_vnotebook.get_n_pages() <= 1) {
847 PortMatrix::remove_notebook_pages (Notebook& n)
849 int const N = n.get_n_pages ();
851 for (int i = 0; i < N; ++i) {
857 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
859 if (_ignore_notebook_page_selected) {
869 PortMatrix::session_going_away ()
875 PortMatrix::body_dimensions_changed ()
877 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
878 if (_arrangement == TOP_TO_RIGHT) {
879 _vspacer.set_size_request (-1, _body->column_labels_height ());
887 _parent->get_size (curr_width, curr_height);
889 pair<uint32_t, uint32_t> m = max_size ();
891 /* Don't shrink the window */
892 m.first = max (int (m.first), curr_width);
893 m.second = max (int (m.second), curr_height);
895 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
898 /** @return The PortGroup that is currently visible (ie selected by
899 * the notebook) along a given axis.
901 boost::shared_ptr<const PortGroup>
902 PortMatrix::visible_ports (int d) const
904 PortGroupList const & p = _ports[d];
905 PortGroupList::List::const_iterator j = p.begin ();
907 /* The logic to compute the index here is a bit twisty because for
908 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
909 tabs in setup_notebooks ().
913 if (d == _row_index) {
914 if (_arrangement == LEFT_TO_BOTTOM) {
915 n = p.size() - _vnotebook.get_current_page () - 1;
917 n = _vnotebook.get_current_page ();
920 n = _hnotebook.get_current_page ();
924 while (i != int (n) && j != p.end ()) {
930 return boost::shared_ptr<const PortGroup> ();
937 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
939 using namespace Menu_Helpers;
941 boost::shared_ptr<Bundle> b = w.lock ();
947 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
948 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
952 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
954 using namespace Menu_Helpers;
956 boost::shared_ptr<Bundle> b = w.lock ();
962 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
963 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
967 PortMatrix::port_connected_or_disconnected ()
969 _body->rebuild_and_draw_grid ();
970 update_tab_highlighting ();
973 /** Update the highlighting of tab names to reflect which ones
974 * have connections. This is pretty inefficient, unfortunately,
975 * but maybe that doesn't matter too much.
978 PortMatrix::update_tab_highlighting ()
984 for (int i = 0; i < 2; ++i) {
986 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
988 PortGroupList const * gl = ports (i);
990 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
991 bool has_connection = false;
992 PortGroup::BundleList const & bl = (*j)->bundles ();
993 PortGroup::BundleList::const_iterator k = bl.begin ();
994 while (k != bl.end()) {
995 if ((*k)->bundle->connected_to_anything (_session->engine())) {
996 has_connection = true;
1002 /* Find the page index that we should update; this is backwards
1003 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1006 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1007 page = notebook->get_n_pages() - p - 1;
1010 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1011 string c = label->get_label ();
1012 if (c.length() && c[0] == '<' && !has_connection) {
1013 /* this label is marked up with <b> but shouldn't be */
1014 label->set_text ((*j)->name);
1015 } else if (c.length() && c[0] != '<' && has_connection) {
1016 /* this label is not marked up with <b> but should be */
1017 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1026 PortMatrix::channel_noun () const
1028 return _("channel");
1031 /** @return true if this matrix should show bundles / ports of type \t */
1033 PortMatrix::should_show (DataType t) const
1035 return (_type == DataType::NIL || t == _type);
1039 PortMatrix::count_of_our_type (ChanCount c) const
1041 if (_type == DataType::NIL) {
1042 return c.n_total ();
1045 return c.get (_type);
1048 /** @return The number of ports of our type in the given channel count,
1049 * but returning 1 if there are no ports.
1052 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1054 uint32_t n = count_of_our_type (c);
1062 PortMatrixNode::State
1063 PortMatrix::get_association (PortMatrixNode node) const
1065 if (show_only_bundles ()) {
1067 bool have_off_diagonal_association = false;
1068 bool have_diagonal_association = false;
1069 bool have_diagonal_not_association = false;
1071 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1073 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1075 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1079 ARDOUR::BundleChannel c[2];
1080 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1081 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1083 PortMatrixNode::State const s = get_state (c);
1086 case PortMatrixNode::ASSOCIATED:
1088 have_diagonal_association = true;
1090 have_off_diagonal_association = true;
1094 case PortMatrixNode::NOT_ASSOCIATED:
1096 have_diagonal_not_association = true;
1106 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1107 return PortMatrixNode::ASSOCIATED;
1108 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1109 return PortMatrixNode::NOT_ASSOCIATED;
1112 return PortMatrixNode::PARTIAL;
1116 ARDOUR::BundleChannel c[2];
1117 c[column_index()] = node.column;
1118 c[row_index()] = node.row;
1119 return get_state (c);
1124 return PortMatrixNode::NOT_ASSOCIATED;
1127 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1129 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1131 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1134 /** See if a `flip' is possible.
1135 * @return If flip is possible, the new (row, column) notebook indices that
1136 * should be selected; otherwise, (-1, -1)
1139 PortMatrix::check_flip () const
1141 /* Look for the row's port group name in the columns */
1144 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1145 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1146 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1151 if (i == _ports[_column_index].end ()) {
1152 return make_pair (-1, -1);
1155 /* Look for the column's port group name in the rows */
1158 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1159 i = _ports[_row_index].begin();
1160 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1165 if (i == _ports[_row_index].end ()) {
1166 return make_pair (-1, -1);
1169 if (_arrangement == LEFT_TO_BOTTOM) {
1170 new_row = _ports[_row_index].size() - new_row - 1;
1173 return make_pair (new_row, new_column);
1177 PortMatrix::can_flip () const
1179 return check_flip().first != -1;
1182 /** Flip the column and row pages around, if possible */
1186 pair<int, int> n = check_flip ();
1187 if (n.first == -1) {
1191 _vnotebook.set_current_page (n.first);
1192 _hnotebook.set_current_page (n.second);
1196 PortMatrix::key_press (GdkEventKey* k)
1198 if (k->keyval == GDK_f) {