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 /* this needs to be done first, as the visible_ports() method uses the
210 notebook state to decide which ports are being shown */
216 update_tab_highlighting ();
221 PortMatrix::set_type (DataType t)
227 PortMatrix::hscroll_changed ()
229 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
233 PortMatrix::vscroll_changed ()
235 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
239 PortMatrix::setup_scrollbars ()
241 Adjustment* a = _hscroll.get_adjustment ();
243 a->set_upper (_body->full_scroll_width());
244 a->set_page_size (_body->alloc_scroll_width());
245 a->set_step_increment (32);
246 a->set_page_increment (128);
248 a = _vscroll.get_adjustment ();
250 a->set_upper (_body->full_scroll_height());
251 a->set_page_size (_body->alloc_scroll_height());
252 a->set_step_increment (32);
253 a->set_page_increment (128);
256 /** Disassociate all of our ports from each other */
258 PortMatrix::disassociate_all ()
260 PortGroup::BundleList a = _ports[0].bundles ();
261 PortGroup::BundleList b = _ports[1].bundles ();
263 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
264 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
265 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
266 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
268 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
272 BundleChannel c[2] = {
273 BundleChannel ((*i)->bundle, j),
274 BundleChannel ((*k)->bundle, l)
277 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
278 set_state (c, false);
286 _body->rebuild_and_draw_grid ();
289 /* Decide how to arrange the components of the matrix */
291 PortMatrix::select_arrangement ()
293 uint32_t const N[2] = {
294 count_of_our_type_min_1 (_ports[0].total_channels()),
295 count_of_our_type_min_1 (_ports[1].total_channels())
298 /* XXX: shirley there's an easier way than this */
300 if (_vspacer.get_parent()) {
301 _vbox.remove (_vspacer);
304 if (_vnotebook.get_parent()) {
305 _vbox.remove (_vnotebook);
308 if (_vlabel.get_parent()) {
309 _vbox.remove (_vlabel);
312 /* The list with the most channels goes on left or right, so that the most channel
313 names are printed horizontally and hence more readable. However we also
314 maintain notional `signal flow' vaguely from left to right. Subclasses
315 should choose where to put ports based on signal flowing from _ports[0]
322 _arrangement = LEFT_TO_BOTTOM;
323 _vlabel.set_label (_("<b>Sources</b>"));
324 _hlabel.set_label (_("<b>Destinations</b>"));
325 _vlabel.set_angle (90);
327 _vbox.pack_end (_vlabel, false, false);
328 _vbox.pack_end (_vnotebook, false, false);
329 _vbox.pack_end (_vspacer, true, true);
331 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
332 attach (_vscroll, 3, 4, 1, 2, SHRINK);
333 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
334 attach (_vbox, 1, 2, 1, 2, SHRINK);
335 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
341 _arrangement = TOP_TO_RIGHT;
342 _hlabel.set_label (_("<b>Sources</b>"));
343 _vlabel.set_label (_("<b>Destinations</b>"));
344 _vlabel.set_angle (-90);
346 _vbox.pack_end (_vspacer, true, true);
347 _vbox.pack_end (_vnotebook, false, false);
348 _vbox.pack_end (_vlabel, false, false);
350 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
351 attach (_vscroll, 3, 4, 2, 3, SHRINK);
352 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
353 attach (_vbox, 2, 3, 2, 3, SHRINK);
354 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
358 /** @return columns list */
359 PortGroupList const *
360 PortMatrix::columns () const
362 return &_ports[_column_index];
365 boost::shared_ptr<const PortGroup>
366 PortMatrix::visible_columns () const
368 return visible_ports (_column_index);
371 /* @return rows list */
372 PortGroupList const *
373 PortMatrix::rows () const
375 return &_ports[_row_index];
378 boost::shared_ptr<const PortGroup>
379 PortMatrix::visible_rows () const
381 return visible_ports (_row_index);
384 /** @param column Column; its bundle may be 0 if we are over a row heading.
385 * @param row Row; its bundle may be 0 if we are over a column heading.
388 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
390 using namespace Menu_Helpers;
395 _menu->set_name ("ArdourContextMenu");
397 MenuList& items = _menu->items ();
400 bc[_column_index] = column;
401 bc[_row_index] = row;
404 bool need_separator = false;
406 for (int dim = 0; dim < 2; ++dim) {
408 if (bc[dim].bundle) {
410 Menu* m = manage (new Menu);
411 MenuList& sub = m->items ();
413 boost::weak_ptr<Bundle> w (bc[dim].bundle);
415 if (can_add_channels (bc[dim].bundle)) {
416 /* Start off with options for the `natural' port type */
417 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
418 if (should_show (*i)) {
419 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
420 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
424 /* Now add other ones */
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)));
433 if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
435 buf, sizeof (buf), _("Rename '%s'..."),
436 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
441 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
446 if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
447 if (bc[dim].channel != -1) {
448 add_remove_option (sub, w, bc[dim].channel);
451 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
454 if (bc[dim].bundle->nchannels().n_total() > 1) {
455 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
456 if (should_show (bc[dim].bundle->channel_type(i))) {
457 add_remove_option (sub, w, i);
464 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
465 if ((_show_only_bundles && c > 0) || c == 1) {
467 /* we're looking just at bundles, or our bundle has only one channel, so just offer
468 to disassociate all on the bundle.
471 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
473 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
478 if (bc[dim].channel != -1) {
479 /* specific channel under the menu, so just offer to disassociate that */
480 add_disassociate_option (sub, w, dim, bc[dim].channel);
482 /* no specific channel; offer to disassociate all, or any one in particular */
483 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
485 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
488 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
489 if (should_show (bc[dim].bundle->channel_type(i))) {
490 add_disassociate_option (sub, w, dim, i);
496 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
497 need_separator = true;
502 if (need_separator) {
503 items.push_back (SeparatorElem ());
506 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
508 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
509 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
510 _inhibit_toggle_show_only_bundles = true;
511 i->set_active (!_show_only_bundles);
512 _inhibit_toggle_show_only_bundles = false;
514 items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
515 items.back().set_sensitive (can_flip ());
521 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
523 boost::shared_ptr<Bundle> sb = b.lock ();
528 remove_channel (BundleChannel (sb, c));
533 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
535 boost::shared_ptr<Bundle> sb = b.lock ();
540 rename_channel (BundleChannel (sb, c));
544 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
546 boost::shared_ptr<Bundle> sb = bundle.lock ();
551 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
552 if (should_show (sb->channel_type(i))) {
553 disassociate_all_on_channel (bundle, i, dim);
559 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
561 boost::shared_ptr<Bundle> sb = bundle.lock ();
566 PortGroup::BundleList a = _ports[1-dim].bundles ();
568 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
569 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
571 if (!should_show ((*i)->bundle->channel_type(j))) {
576 c[dim] = BundleChannel (sb, channel);
577 c[1-dim] = BundleChannel ((*i)->bundle, j);
579 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
580 set_state (c, false);
585 _body->rebuild_and_draw_grid ();
589 PortMatrix::setup_global_ports ()
591 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
593 for (int i = 0; i < 2; ++i) {
594 if (list_is_global (i)) {
601 PortMatrix::setup_global_ports_proxy (RouteSortOrderKey sk)
603 if (sk == EditorSort) {
604 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
608 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
613 PortMatrix::setup_all_ports ()
615 if (_session->deletion_in_progress()) {
619 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
626 PortMatrix::toggle_show_only_bundles ()
628 if (_inhibit_toggle_show_only_bundles) {
632 _show_only_bundles = !_show_only_bundles;
636 /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
637 setting, so we need to set things up again now.
642 pair<uint32_t, uint32_t>
643 PortMatrix::max_size () const
645 pair<uint32_t, uint32_t> m = _body->max_size ();
647 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
648 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
654 PortMatrix::on_scroll_event (GdkEventScroll* ev)
656 double const h = _hscroll.get_value ();
657 double const v = _vscroll.get_value ();
659 switch (ev->direction) {
661 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
663 case GDK_SCROLL_DOWN:
664 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
666 case GDK_SCROLL_LEFT:
667 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
669 case GDK_SCROLL_RIGHT:
670 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
677 boost::shared_ptr<IO>
678 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
680 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
682 io = _ports[1].io_from_bundle (b);
689 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
691 return io_from_bundle (b);
695 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
697 boost::shared_ptr<IO> io = io_from_bundle (b);
700 int const r = io->add_port ("", this, t);
702 Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
703 "support the new configuration."
705 msg.set_title (_("Cannot add port"));
712 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
714 return io_from_bundle (b);
718 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
720 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
723 boost::shared_ptr<Port> p = io->nth (b.channel);
725 int const r = io->remove_port (p, this);
727 ArdourDialog d (_("Port removal not allowed"));
728 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
729 d.get_vbox()->pack_start (l);
730 d.add_button (Stock::OK, RESPONSE_ACCEPT);
740 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
742 boost::shared_ptr<Bundle> b = w.lock ();
747 /* Remove channels backwards so that we don't renumber channels
748 that we are about to remove.
750 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
751 if (should_show (b->channel_type(i))) {
752 remove_channel (ARDOUR::BundleChannel (b, i));
758 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
760 boost::shared_ptr<Bundle> b = w.lock ();
769 PortMatrix::setup_notebooks ()
771 int const h_current_page = _hnotebook.get_current_page ();
772 int const v_current_page = _vnotebook.get_current_page ();
774 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
775 when adding or removing pages to or from notebooks, so ignore them */
777 _ignore_notebook_page_selected = true;
779 remove_notebook_pages (_hnotebook);
780 remove_notebook_pages (_vnotebook);
782 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
783 HBox* dummy = manage (new HBox);
785 Label* label = manage (new Label ((*i)->name));
786 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
787 label->set_use_markup ();
789 if (_arrangement == LEFT_TO_BOTTOM) {
790 _vnotebook.prepend_page (*dummy, *label);
792 /* Reverse the order of vertical tabs when they are on the right hand side
793 so that from top to bottom it is the same order as that from left to right
796 _vnotebook.append_page (*dummy, *label);
800 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
801 HBox* dummy = manage (new HBox);
803 Label* label = manage (new Label ((*i)->name));
804 label->set_use_markup ();
806 _hnotebook.append_page (*dummy, *label);
809 _ignore_notebook_page_selected = false;
811 if (_arrangement == TOP_TO_RIGHT) {
812 _vnotebook.set_tab_pos (POS_RIGHT);
813 _hnotebook.set_tab_pos (POS_TOP);
815 _vnotebook.set_tab_pos (POS_LEFT);
816 _hnotebook.set_tab_pos (POS_BOTTOM);
819 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
820 _hnotebook.set_current_page (h_current_page);
822 _hnotebook.set_current_page (0);
825 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
826 _vnotebook.set_current_page (v_current_page);
828 _vnotebook.set_current_page (0);
831 if (_hnotebook.get_n_pages() <= 1) {
837 if (_vnotebook.get_n_pages() <= 1) {
845 PortMatrix::remove_notebook_pages (Notebook& n)
847 int const N = n.get_n_pages ();
849 for (int i = 0; i < N; ++i) {
855 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
857 if (_ignore_notebook_page_selected) {
867 PortMatrix::session_going_away ()
873 PortMatrix::body_dimensions_changed ()
875 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
876 if (_arrangement == TOP_TO_RIGHT) {
877 _vspacer.set_size_request (-1, _body->column_labels_height ());
885 _parent->get_size (curr_width, curr_height);
887 pair<uint32_t, uint32_t> m = max_size ();
889 /* Don't shrink the window */
890 m.first = max (int (m.first), curr_width);
891 m.second = max (int (m.second), curr_height);
893 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
896 /** @return The PortGroup that is currently visible (ie selected by
897 * the notebook) along a given axis.
899 boost::shared_ptr<const PortGroup>
900 PortMatrix::visible_ports (int d) const
902 PortGroupList const & p = _ports[d];
903 PortGroupList::List::const_iterator j = p.begin ();
905 /* The logic to compute the index here is a bit twisty because for
906 the TOP_TO_RIGHT arrangement we reverse the order of the vertical
907 tabs in setup_notebooks ().
911 if (d == _row_index) {
912 if (_arrangement == LEFT_TO_BOTTOM) {
913 n = p.size() - _vnotebook.get_current_page () - 1;
915 n = _vnotebook.get_current_page ();
918 n = _hnotebook.get_current_page ();
922 while (i != int (n) && j != p.end ()) {
928 return boost::shared_ptr<const PortGroup> ();
935 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
937 using namespace Menu_Helpers;
939 boost::shared_ptr<Bundle> b = w.lock ();
945 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
946 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
950 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
952 using namespace Menu_Helpers;
954 boost::shared_ptr<Bundle> b = w.lock ();
960 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
961 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
965 PortMatrix::port_connected_or_disconnected ()
967 _body->rebuild_and_draw_grid ();
968 update_tab_highlighting ();
971 /** Update the highlighting of tab names to reflect which ones
972 * have connections. This is pretty inefficient, unfortunately,
973 * but maybe that doesn't matter too much.
976 PortMatrix::update_tab_highlighting ()
982 for (int i = 0; i < 2; ++i) {
984 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
986 PortGroupList const * gl = ports (i);
988 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
989 bool has_connection = false;
990 PortGroup::BundleList const & bl = (*j)->bundles ();
991 PortGroup::BundleList::const_iterator k = bl.begin ();
992 while (k != bl.end()) {
993 if ((*k)->bundle->connected_to_anything (_session->engine())) {
994 has_connection = true;
1000 /* Find the page index that we should update; this is backwards
1001 for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1004 if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1005 page = notebook->get_n_pages() - p - 1;
1008 Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1009 string c = label->get_label ();
1010 if (c.length() && c[0] == '<' && !has_connection) {
1011 /* this label is marked up with <b> but shouldn't be */
1012 label->set_text ((*j)->name);
1013 } else if (c.length() && c[0] != '<' && has_connection) {
1014 /* this label is not marked up with <b> but should be */
1015 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1024 PortMatrix::channel_noun () const
1026 return _("channel");
1029 /** @return true if this matrix should show bundles / ports of type \t */
1031 PortMatrix::should_show (DataType t) const
1033 return (_type == DataType::NIL || t == _type);
1037 PortMatrix::count_of_our_type (ChanCount c) const
1039 if (_type == DataType::NIL) {
1040 return c.n_total ();
1043 return c.get (_type);
1046 /** @return The number of ports of our type in the given channel count,
1047 * but returning 1 if there are no ports.
1050 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1052 uint32_t n = count_of_our_type (c);
1060 PortMatrixNode::State
1061 PortMatrix::get_association (PortMatrixNode node) const
1063 if (show_only_bundles ()) {
1065 bool have_off_diagonal_association = false;
1066 bool have_diagonal_association = false;
1067 bool have_diagonal_not_association = false;
1069 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1071 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1073 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1077 ARDOUR::BundleChannel c[2];
1078 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1079 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1081 PortMatrixNode::State const s = get_state (c);
1084 case PortMatrixNode::ASSOCIATED:
1086 have_diagonal_association = true;
1088 have_off_diagonal_association = true;
1092 case PortMatrixNode::NOT_ASSOCIATED:
1094 have_diagonal_not_association = true;
1104 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1105 return PortMatrixNode::ASSOCIATED;
1106 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1107 return PortMatrixNode::NOT_ASSOCIATED;
1110 return PortMatrixNode::PARTIAL;
1114 ARDOUR::BundleChannel c[2];
1115 c[column_index()] = node.column;
1116 c[row_index()] = node.row;
1117 return get_state (c);
1122 return PortMatrixNode::NOT_ASSOCIATED;
1125 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1127 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1129 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1132 /** See if a `flip' is possible.
1133 * @return If flip is possible, the new (row, column) notebook indices that
1134 * should be selected; otherwise, (-1, -1)
1137 PortMatrix::check_flip () const
1139 /* Look for the row's port group name in the columns */
1142 boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1143 PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1144 while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1149 if (i == _ports[_column_index].end ()) {
1150 return make_pair (-1, -1);
1153 /* Look for the column's port group name in the rows */
1156 boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1157 i = _ports[_row_index].begin();
1158 while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1163 if (i == _ports[_row_index].end ()) {
1164 return make_pair (-1, -1);
1167 if (_arrangement == LEFT_TO_BOTTOM) {
1168 new_row = _ports[_row_index].size() - new_row - 1;
1171 return make_pair (new_row, new_column);
1175 PortMatrix::can_flip () const
1177 return check_flip().first != -1;
1180 /** Flip the column and row pages around, if possible */
1184 pair<int, int> n = check_flip ();
1185 if (n.first == -1) {
1189 _vnotebook.set_current_page (n.first);
1190 _hnotebook.set_current_page (n.second);
1194 PortMatrix::key_press (GdkEventKey* k)
1196 if (k->keyval == GDK_f) {