Stop bundles disappearing from the port matrix when they
[ardour2.git] / gtk2_ardour / port_matrix.cc
blob35cfea651f67623595ffc8e5d1ac982285caa895
1 /*
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.
20 #include <iostream>
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 "ardour/bundle.h"
30 #include "ardour/types.h"
31 #include "ardour/session.h"
32 #include "ardour/route.h"
33 #include "ardour/audioengine.h"
34 #include "port_matrix.h"
35 #include "port_matrix_body.h"
36 #include "port_matrix_component.h"
37 #include "ardour_dialog.h"
38 #include "i18n.h"
39 #include "gui_thread.h"
40 #include "utils.h"
42 using namespace std;
43 using namespace Gtk;
44 using namespace ARDOUR;
46 /** PortMatrix constructor.
47 * @param session Our session.
48 * @param type Port type that we are handling.
50 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
51 : Table (4, 4)
52 , _parent (parent)
53 , _type (type)
54 , _menu (0)
55 , _arrangement (TOP_TO_RIGHT)
56 , _row_index (0)
57 , _column_index (1)
58 , _min_height_divisor (1)
59 , _show_only_bundles (false)
60 , _inhibit_toggle_show_only_bundles (false)
61 , _ignore_notebook_page_selected (false)
63 set_session (session);
65 _body = new PortMatrixBody (this);
66 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
68 _hbox.pack_end (_hspacer, true, true);
69 _hbox.pack_end (_hnotebook, false, false);
70 _hbox.pack_end (_hlabel, false, false);
72 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
73 _vnotebook.property_tab_border() = 4;
74 _vnotebook.set_name (X_("PortMatrixLabel"));
75 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
76 _hnotebook.property_tab_border() = 4;
77 _hnotebook.set_name (X_("PortMatrixLabel"));
79 _vlabel.set_use_markup ();
80 _vlabel.set_alignment (1, 1);
81 _vlabel.set_padding (4, 16);
82 _vlabel.set_name (X_("PortMatrixLabel"));
83 _hlabel.set_use_markup ();
84 _hlabel.set_alignment (1, 0.5);
85 _hlabel.set_padding (16, 4);
86 _hlabel.set_name (X_("PortMatrixLabel"));
88 set_row_spacing (0, 8);
89 set_col_spacing (0, 8);
90 set_row_spacing (2, 8);
91 set_col_spacing (2, 8);
93 _body->show ();
94 _vbox.show ();
95 _hbox.show ();
96 _vscroll.show ();
97 _hscroll.show ();
98 _vlabel.show ();
99 _hlabel.show ();
100 _hspacer.show ();
101 _vspacer.show ();
102 _vnotebook.show ();
103 _hnotebook.show ();
106 PortMatrix::~PortMatrix ()
108 delete _body;
109 delete _menu;
112 /** Perform initial and once-only setup. This must be called by
113 * subclasses after they have set up _ports[] to at least some
114 * reasonable extent. Two-part initialisation is necessary because
115 * setting up _ports is largely done by virtual functions in
116 * subclasses.
119 void
120 PortMatrix::init ()
122 select_arrangement ();
124 /* Signal handling is kind of split into three parts:
126 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
127 * representation of the information in _ports[].
129 * 2. When certain other things change, we need to get our subclass to clear and
130 * re-fill _ports[], which in turn causes appropriate signals to be raised to
131 * hook into part (1).
133 * 3. Assorted other signals.
137 /* Part 1: the basic _ports[] change -> reset visuals */
139 for (int i = 0; i < 2; ++i) {
140 /* watch for the content of _ports[] changing */
141 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
143 /* and for bundles in _ports[] changing */
144 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
147 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
149 /* watch for routes being added or removed */
150 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
152 /* and also bundles */
153 _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
155 /* and also ports */
156 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
158 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
159 _session->RouteOrderKeyChanged.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
161 /* Part 3: other stuff */
163 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
165 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
166 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
168 reconnect_to_routes ();
170 setup ();
173 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
174 void
175 PortMatrix::reconnect_to_routes ()
177 _route_connections.drop_connections ();
179 boost::shared_ptr<RouteList> routes = _session->get_routes ();
180 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
181 (*i)->processors_changed.connect (_route_connections, invalidator (*this), ui_bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
185 void
186 PortMatrix::route_processors_changed (RouteProcessorChange c)
188 if (c.type == RouteProcessorChange::MeterPointChange) {
189 /* this change has no impact on the port matrix */
190 return;
193 setup_global_ports ();
196 /** A route has been added to or removed from the session */
197 void
198 PortMatrix::routes_changed ()
200 reconnect_to_routes ();
201 setup_global_ports ();
204 /** Set up everything that depends on the content of _ports[] */
205 void
206 PortMatrix::setup ()
208 /* this needs to be done first, as the visible_ports() method uses the
209 notebook state to decide which ports are being shown */
211 setup_notebooks ();
213 _body->setup ();
214 setup_scrollbars ();
215 queue_draw ();
218 void
219 PortMatrix::set_type (DataType t)
221 _type = t;
224 void
225 PortMatrix::hscroll_changed ()
227 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
230 void
231 PortMatrix::vscroll_changed ()
233 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
236 void
237 PortMatrix::setup_scrollbars ()
239 Adjustment* a = _hscroll.get_adjustment ();
240 a->set_lower (0);
241 a->set_upper (_body->full_scroll_width());
242 a->set_page_size (_body->alloc_scroll_width());
243 a->set_step_increment (32);
244 a->set_page_increment (128);
246 a = _vscroll.get_adjustment ();
247 a->set_lower (0);
248 a->set_upper (_body->full_scroll_height());
249 a->set_page_size (_body->alloc_scroll_height());
250 a->set_step_increment (32);
251 a->set_page_increment (128);
254 /** Disassociate all of our ports from each other */
255 void
256 PortMatrix::disassociate_all ()
258 PortGroup::BundleList a = _ports[0].bundles ();
259 PortGroup::BundleList b = _ports[1].bundles ();
261 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
262 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
263 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
264 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
266 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
267 continue;
270 BundleChannel c[2] = {
271 BundleChannel ((*i)->bundle, j),
272 BundleChannel ((*k)->bundle, l)
275 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
276 set_state (c, false);
284 _body->rebuild_and_draw_grid ();
287 /* Decide how to arrange the components of the matrix */
288 void
289 PortMatrix::select_arrangement ()
291 uint32_t const N[2] = {
292 count_of_our_type_min_1 (_ports[0].total_channels()),
293 count_of_our_type_min_1 (_ports[1].total_channels())
296 /* XXX: shirley there's an easier way than this */
298 if (_vspacer.get_parent()) {
299 _vbox.remove (_vspacer);
302 if (_vnotebook.get_parent()) {
303 _vbox.remove (_vnotebook);
306 if (_vlabel.get_parent()) {
307 _vbox.remove (_vlabel);
310 /* The list with the most channels goes on left or right, so that the most channel
311 names are printed horizontally and hence more readable. However we also
312 maintain notional `signal flow' vaguely from left to right. Subclasses
313 should choose where to put ports based on signal flowing from _ports[0]
314 to _ports[1] */
316 if (N[0] > N[1]) {
318 _row_index = 0;
319 _column_index = 1;
320 _arrangement = LEFT_TO_BOTTOM;
321 _vlabel.set_label (_("<b>Sources</b>"));
322 _hlabel.set_label (_("<b>Destinations</b>"));
323 _vlabel.set_angle (90);
325 _vbox.pack_end (_vlabel, false, false);
326 _vbox.pack_end (_vnotebook, false, false);
327 _vbox.pack_end (_vspacer, true, true);
329 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
330 attach (_vscroll, 3, 4, 1, 2, SHRINK);
331 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
332 attach (_vbox, 1, 2, 1, 2, SHRINK);
333 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
335 } else {
337 _row_index = 1;
338 _column_index = 0;
339 _arrangement = TOP_TO_RIGHT;
340 _hlabel.set_label (_("<b>Sources</b>"));
341 _vlabel.set_label (_("<b>Destinations</b>"));
342 _vlabel.set_angle (-90);
344 _vbox.pack_end (_vspacer, true, true);
345 _vbox.pack_end (_vnotebook, false, false);
346 _vbox.pack_end (_vlabel, false, false);
348 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
349 attach (_vscroll, 3, 4, 2, 3, SHRINK);
350 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
351 attach (_vbox, 2, 3, 2, 3, SHRINK);
352 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
356 /** @return columns list */
357 PortGroupList const *
358 PortMatrix::columns () const
360 return &_ports[_column_index];
363 boost::shared_ptr<const PortGroup>
364 PortMatrix::visible_columns () const
366 return visible_ports (_column_index);
369 /* @return rows list */
370 PortGroupList const *
371 PortMatrix::rows () const
373 return &_ports[_row_index];
376 boost::shared_ptr<const PortGroup>
377 PortMatrix::visible_rows () const
379 return visible_ports (_row_index);
382 void
383 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
385 using namespace Menu_Helpers;
387 delete _menu;
389 _menu = new Menu;
390 _menu->set_name ("ArdourContextMenu");
392 MenuList& items = _menu->items ();
394 BundleChannel bc[2];
395 bc[_column_index] = column;
396 bc[_row_index] = row;
398 char buf [64];
399 bool need_separator = false;
401 for (int dim = 0; dim < 2; ++dim) {
403 if (bc[dim].bundle) {
405 Menu* m = manage (new Menu);
406 MenuList& sub = m->items ();
408 boost::weak_ptr<Bundle> w (bc[dim].bundle);
410 /* Start off with options for the `natural' port type */
411 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
412 if (should_show (*i)) {
413 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
414 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
418 /* Now add other ones */
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 if (can_rename_channels (bc[dim].bundle)) {
427 snprintf (
428 buf, sizeof (buf), _("Rename '%s'..."),
429 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
431 sub.push_back (
432 MenuElem (
433 buf,
434 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
439 if (can_remove_channels (bc[dim].bundle)) {
440 if (bc[dim].channel != -1) {
441 add_remove_option (sub, w, bc[dim].channel);
442 } else if (bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
444 snprintf (buf, sizeof (buf), _("Remove all"));
445 sub.push_back (
446 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
449 if (bc[dim].bundle->nchannels().n_total() > 1) {
450 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
451 if (should_show (bc[dim].bundle->channel_type(i))) {
452 add_remove_option (sub, w, i);
459 uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
460 if ((_show_only_bundles && c > 0) || c == 1) {
461 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
462 sub.push_back (
463 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
466 } else {
468 if (bc[dim].channel != -1) {
469 add_disassociate_option (sub, w, dim, bc[dim].channel);
470 } else if (count_of_our_type (bc[dim].bundle->nchannels()) != 0) {
471 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
472 sub.push_back (
473 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
476 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
477 if (should_show (bc[dim].bundle->channel_type(i))) {
478 add_disassociate_option (sub, w, dim, i);
484 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
485 need_separator = true;
490 if (need_separator) {
491 items.push_back (SeparatorElem ());
494 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
495 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
496 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
497 _inhibit_toggle_show_only_bundles = true;
498 i->set_active (!_show_only_bundles);
499 _inhibit_toggle_show_only_bundles = false;
501 _menu->popup (1, t);
504 void
505 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
507 boost::shared_ptr<Bundle> sb = b.lock ();
508 if (!sb) {
509 return;
512 remove_channel (BundleChannel (sb, c));
516 void
517 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
519 boost::shared_ptr<Bundle> sb = b.lock ();
520 if (!sb) {
521 return;
524 rename_channel (BundleChannel (sb, c));
527 void
528 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
530 boost::shared_ptr<Bundle> sb = bundle.lock ();
531 if (!sb) {
532 return;
535 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
536 if (should_show (sb->channel_type(i))) {
537 disassociate_all_on_channel (bundle, i, dim);
542 void
543 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
545 boost::shared_ptr<Bundle> sb = bundle.lock ();
546 if (!sb) {
547 return;
550 PortGroup::BundleList a = _ports[1-dim].bundles ();
552 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
553 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
555 if (should_show ((*i)->bundle->channel_type(j))) {
556 continue;
559 BundleChannel c[2];
560 c[dim] = BundleChannel (sb, channel);
561 c[1-dim] = BundleChannel ((*i)->bundle, j);
563 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
564 set_state (c, false);
569 _body->rebuild_and_draw_grid ();
572 void
573 PortMatrix::setup_global_ports ()
575 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
577 for (int i = 0; i < 2; ++i) {
578 if (list_is_global (i)) {
579 setup_ports (i);
584 void
585 PortMatrix::setup_global_ports_proxy ()
587 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
588 for a discussion.
591 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
594 void
595 PortMatrix::setup_all_ports ()
597 if (_session->deletion_in_progress()) {
598 return;
601 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
603 setup_ports (0);
604 setup_ports (1);
607 void
608 PortMatrix::toggle_show_only_bundles ()
610 if (_inhibit_toggle_show_only_bundles) {
611 return;
614 _show_only_bundles = !_show_only_bundles;
616 setup ();
619 pair<uint32_t, uint32_t>
620 PortMatrix::max_size () const
622 pair<uint32_t, uint32_t> m = _body->max_size ();
624 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
625 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
627 return m;
630 bool
631 PortMatrix::on_scroll_event (GdkEventScroll* ev)
633 double const h = _hscroll.get_value ();
634 double const v = _vscroll.get_value ();
636 switch (ev->direction) {
637 case GDK_SCROLL_UP:
638 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
639 break;
640 case GDK_SCROLL_DOWN:
641 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
642 break;
643 case GDK_SCROLL_LEFT:
644 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
645 break;
646 case GDK_SCROLL_RIGHT:
647 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
648 break;
651 return true;
654 boost::shared_ptr<IO>
655 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
657 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
658 if (!io) {
659 io = _ports[1].io_from_bundle (b);
662 return io;
665 bool
666 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
668 return io_from_bundle (b);
671 void
672 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
674 boost::shared_ptr<IO> io = io_from_bundle (b);
676 if (io) {
677 io->add_port ("", this, t);
681 bool
682 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
684 return io_from_bundle (b);
687 void
688 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
690 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
692 if (io) {
693 Port* p = io->nth (b.channel);
694 if (p) {
695 int const r = io->remove_port (p, this);
696 if (r == -1) {
697 ArdourDialog d (_("Port removal not allowed"));
698 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
699 d.get_vbox()->pack_start (l);
700 d.add_button (Stock::OK, RESPONSE_ACCEPT);
701 d.set_modal (true);
702 d.show_all ();
703 d.run ();
709 void
710 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
712 boost::shared_ptr<Bundle> b = w.lock ();
713 if (!b) {
714 return;
717 /* Remove channels backwards so that we don't renumber channels
718 that we are about to remove.
720 for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
721 if (should_show (b->channel_type(i))) {
722 remove_channel (ARDOUR::BundleChannel (b, i));
727 void
728 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
730 boost::shared_ptr<Bundle> b = w.lock ();
731 if (!b) {
732 return;
735 add_channel (b, t);
738 void
739 PortMatrix::setup_notebooks ()
741 int const h_current_page = _hnotebook.get_current_page ();
742 int const v_current_page = _vnotebook.get_current_page ();
744 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
745 when adding or removing pages to or from notebooks, so ignore them */
747 _ignore_notebook_page_selected = true;
749 remove_notebook_pages (_hnotebook);
750 remove_notebook_pages (_vnotebook);
752 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
753 HBox* dummy = manage (new HBox);
754 dummy->show ();
755 Label* label = manage (new Label ((*i)->name));
756 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
757 label->show ();
758 _vnotebook.prepend_page (*dummy, *label);
761 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
762 HBox* dummy = manage (new HBox);
763 dummy->show ();
764 _hnotebook.append_page (*dummy, (*i)->name);
767 _ignore_notebook_page_selected = false;
769 if (_arrangement == TOP_TO_RIGHT) {
770 _vnotebook.set_tab_pos (POS_RIGHT);
771 _hnotebook.set_tab_pos (POS_TOP);
772 } else {
773 _vnotebook.set_tab_pos (POS_LEFT);
774 _hnotebook.set_tab_pos (POS_BOTTOM);
777 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
778 _hnotebook.set_current_page (h_current_page);
779 } else {
780 _hnotebook.set_current_page (0);
783 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
784 _vnotebook.set_current_page (v_current_page);
785 } else {
786 _vnotebook.set_current_page (0);
789 if (_hnotebook.get_n_pages() <= 1) {
790 _hbox.hide ();
791 } else {
792 _hbox.show ();
795 if (_vnotebook.get_n_pages() <= 1) {
796 _vbox.hide ();
797 } else {
798 _vbox.show ();
802 void
803 PortMatrix::remove_notebook_pages (Notebook& n)
805 int const N = n.get_n_pages ();
807 for (int i = 0; i < N; ++i) {
808 n.remove_page ();
812 void
813 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
815 if (_ignore_notebook_page_selected) {
816 return;
819 _body->setup ();
820 setup_scrollbars ();
821 queue_draw ();
824 void
825 PortMatrix::session_going_away ()
827 _session = 0;
830 void
831 PortMatrix::body_dimensions_changed ()
833 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
834 if (_arrangement == TOP_TO_RIGHT) {
835 _vspacer.set_size_request (-1, _body->column_labels_height ());
836 _vspacer.show ();
837 } else {
838 _vspacer.hide ();
841 int curr_width;
842 int curr_height;
843 _parent->get_size (curr_width, curr_height);
845 pair<uint32_t, uint32_t> m = max_size ();
847 /* Don't shrink the window */
848 m.first = max (int (m.first), curr_width);
849 m.second = max (int (m.second), curr_height);
851 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
854 /** @return The PortGroup that is currently visible (ie selected by
855 * the notebook) along a given axis.
857 boost::shared_ptr<const PortGroup>
858 PortMatrix::visible_ports (int d) const
860 PortGroupList const & p = _ports[d];
861 PortGroupList::List::const_iterator j = p.begin ();
863 int n = 0;
864 if (d == _row_index) {
865 n = p.size() - _vnotebook.get_current_page () - 1;
866 } else {
867 n = _hnotebook.get_current_page ();
870 int i = 0;
871 while (i != int (n) && j != p.end ()) {
872 ++i;
873 ++j;
876 if (j == p.end()) {
877 return boost::shared_ptr<const PortGroup> ();
880 return *j;
883 void
884 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
886 using namespace Menu_Helpers;
888 boost::shared_ptr<Bundle> b = w.lock ();
889 if (!b) {
890 return;
893 char buf [64];
894 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
895 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
898 void
899 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
901 using namespace Menu_Helpers;
903 boost::shared_ptr<Bundle> b = w.lock ();
904 if (!b) {
905 return;
908 char buf [64];
909 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
910 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
913 void
914 PortMatrix::port_connected_or_disconnected ()
916 _body->rebuild_and_draw_grid ();
919 string
920 PortMatrix::channel_noun () const
922 return _("channel");
925 /** @return true if this matrix should show bundles / ports of type \t */
926 bool
927 PortMatrix::should_show (DataType t) const
929 return (_type == DataType::NIL || t == _type);
932 uint32_t
933 PortMatrix::count_of_our_type (ChanCount c) const
935 if (_type == DataType::NIL) {
936 return c.n_total ();
939 return c.get (_type);
942 /** @return The number of ports of our type in the given channel count,
943 * but returning 1 if there are no ports.
945 uint32_t
946 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
948 uint32_t n = count_of_our_type (c);
949 if (n == 0) {
950 n = 1;
953 return n;
956 PortMatrixNode::State
957 PortMatrix::get_association (PortMatrixNode node) const
959 if (show_only_bundles ()) {
961 bool have_off_diagonal_association = false;
962 bool have_diagonal_association = false;
963 bool have_diagonal_not_association = false;
965 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
967 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
969 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
970 continue;
973 ARDOUR::BundleChannel c[2];
974 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
975 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
977 PortMatrixNode::State const s = get_state (c);
979 switch (s) {
980 case PortMatrixNode::ASSOCIATED:
981 if (i == j) {
982 have_diagonal_association = true;
983 } else {
984 have_off_diagonal_association = true;
986 break;
988 case PortMatrixNode::NOT_ASSOCIATED:
989 if (i == j) {
990 have_diagonal_not_association = true;
992 break;
994 default:
995 break;
1000 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1001 return PortMatrixNode::ASSOCIATED;
1002 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1003 return PortMatrixNode::NOT_ASSOCIATED;
1006 return PortMatrixNode::PARTIAL;
1008 } else {
1010 ARDOUR::BundleChannel c[2];
1011 c[column_index()] = node.column;
1012 c[row_index()] = node.row;
1013 return get_state (c);
1017 /* NOTREACHED */
1018 return PortMatrixNode::NOT_ASSOCIATED;
1021 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1022 bool
1023 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1025 return b && b->nchannels() != ARDOUR::ChanCount::ZERO;