make panner data popups more contrasty and appear in a better position
[ardour2.git] / gtk2_ardour / port_matrix.cc
blob5b747d66935ec0b1ee3fe658e23b75be64e58273
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 (_ports[0].total_channels()),
293 count_of_our_type (_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 bool can_add_or_rename = false;
412 /* Start off with options for the `natural' port type */
413 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
414 if (should_show (*i)) {
415 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
416 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
417 can_add_or_rename = true;
421 /* Now add other ones */
422 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
423 if (!should_show (*i)) {
424 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
425 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
426 can_add_or_rename = true;
430 if (can_rename_channels (bc[dim].bundle)) {
431 snprintf (
432 buf, sizeof (buf), _("Rename '%s'..."),
433 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
435 sub.push_back (
436 MenuElem (
437 buf,
438 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
441 can_add_or_rename = true;
444 if (can_add_or_rename) {
445 sub.push_back (SeparatorElem ());
448 if (can_remove_channels (bc[dim].bundle)) {
449 if (bc[dim].channel != -1) {
450 add_remove_option (sub, w, bc[dim].channel);
451 } else {
453 snprintf (buf, sizeof (buf), _("Remove all"));
454 sub.push_back (
455 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
458 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
459 if (should_show (bc[dim].bundle->channel_type(i))) {
460 add_remove_option (sub, w, i);
466 if (_show_only_bundles || count_of_our_type (bc[dim].bundle->nchannels()) <= 1) {
467 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
468 sub.push_back (
469 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
472 } else {
474 if (bc[dim].channel != -1) {
475 add_disassociate_option (sub, w, dim, bc[dim].channel);
476 } else {
477 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
478 sub.push_back (
479 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
482 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
483 if (should_show (bc[dim].bundle->channel_type(i))) {
484 add_disassociate_option (sub, w, dim, i);
490 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
491 need_separator = true;
496 if (need_separator) {
497 items.push_back (SeparatorElem ());
500 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
501 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
502 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
503 _inhibit_toggle_show_only_bundles = true;
504 i->set_active (!_show_only_bundles);
505 _inhibit_toggle_show_only_bundles = false;
507 _menu->popup (1, t);
510 void
511 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
513 boost::shared_ptr<Bundle> sb = b.lock ();
514 if (!sb) {
515 return;
518 remove_channel (BundleChannel (sb, c));
522 void
523 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
525 boost::shared_ptr<Bundle> sb = b.lock ();
526 if (!sb) {
527 return;
530 rename_channel (BundleChannel (sb, c));
533 void
534 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
536 boost::shared_ptr<Bundle> sb = bundle.lock ();
537 if (!sb) {
538 return;
541 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
542 if (should_show (sb->channel_type(i))) {
543 disassociate_all_on_channel (bundle, i, dim);
548 void
549 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
551 boost::shared_ptr<Bundle> sb = bundle.lock ();
552 if (!sb) {
553 return;
556 PortGroup::BundleList a = _ports[1-dim].bundles ();
558 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
559 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
561 if (should_show ((*i)->bundle->channel_type(j))) {
562 continue;
565 BundleChannel c[2];
566 c[dim] = BundleChannel (sb, channel);
567 c[1-dim] = BundleChannel ((*i)->bundle, j);
569 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
570 set_state (c, false);
575 _body->rebuild_and_draw_grid ();
578 void
579 PortMatrix::setup_global_ports ()
581 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
583 for (int i = 0; i < 2; ++i) {
584 if (list_is_global (i)) {
585 setup_ports (i);
590 void
591 PortMatrix::setup_global_ports_proxy ()
593 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
594 for a discussion.
597 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
600 void
601 PortMatrix::setup_all_ports ()
603 if (_session->deletion_in_progress()) {
604 return;
607 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
609 setup_ports (0);
610 setup_ports (1);
613 void
614 PortMatrix::toggle_show_only_bundles ()
616 if (_inhibit_toggle_show_only_bundles) {
617 return;
620 _show_only_bundles = !_show_only_bundles;
622 setup ();
625 pair<uint32_t, uint32_t>
626 PortMatrix::max_size () const
628 pair<uint32_t, uint32_t> m = _body->max_size ();
630 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
631 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
633 return m;
636 bool
637 PortMatrix::on_scroll_event (GdkEventScroll* ev)
639 double const h = _hscroll.get_value ();
640 double const v = _vscroll.get_value ();
642 switch (ev->direction) {
643 case GDK_SCROLL_UP:
644 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
645 break;
646 case GDK_SCROLL_DOWN:
647 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
648 break;
649 case GDK_SCROLL_LEFT:
650 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
651 break;
652 case GDK_SCROLL_RIGHT:
653 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
654 break;
657 return true;
660 boost::shared_ptr<IO>
661 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
663 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
664 if (!io) {
665 io = _ports[1].io_from_bundle (b);
668 return io;
671 bool
672 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
674 return io_from_bundle (b);
677 void
678 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
680 boost::shared_ptr<IO> io = io_from_bundle (b);
682 if (io) {
683 io->add_port ("", this, t);
687 bool
688 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
690 return io_from_bundle (b);
693 void
694 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
696 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
698 if (io) {
699 Port* p = io->nth (b.channel);
700 if (p) {
701 int const r = io->remove_port (p, this);
702 if (r == -1) {
703 ArdourDialog d (_("Port removal not allowed"));
704 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
705 d.get_vbox()->pack_start (l);
706 d.add_button (Stock::OK, RESPONSE_ACCEPT);
707 d.set_modal (true);
708 d.show_all ();
709 d.run ();
715 void
716 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
718 boost::shared_ptr<Bundle> b = w.lock ();
719 if (!b) {
720 return;
723 for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
724 if (should_show (b->channel_type(i))) {
725 remove_channel (ARDOUR::BundleChannel (b, i));
730 void
731 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
733 boost::shared_ptr<Bundle> b = w.lock ();
734 if (!b) {
735 return;
738 add_channel (b, t);
741 void
742 PortMatrix::setup_notebooks ()
744 int const h_current_page = _hnotebook.get_current_page ();
745 int const v_current_page = _vnotebook.get_current_page ();
747 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
748 when adding or removing pages to or from notebooks, so ignore them */
750 _ignore_notebook_page_selected = true;
752 remove_notebook_pages (_hnotebook);
753 remove_notebook_pages (_vnotebook);
755 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
756 HBox* dummy = manage (new HBox);
757 dummy->show ();
758 Label* label = manage (new Label ((*i)->name));
759 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
760 label->show ();
761 _vnotebook.prepend_page (*dummy, *label);
764 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
765 HBox* dummy = manage (new HBox);
766 dummy->show ();
767 _hnotebook.append_page (*dummy, (*i)->name);
770 _ignore_notebook_page_selected = false;
772 if (_arrangement == TOP_TO_RIGHT) {
773 _vnotebook.set_tab_pos (POS_RIGHT);
774 _hnotebook.set_tab_pos (POS_TOP);
775 } else {
776 _vnotebook.set_tab_pos (POS_LEFT);
777 _hnotebook.set_tab_pos (POS_BOTTOM);
780 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
781 _hnotebook.set_current_page (h_current_page);
782 } else {
783 _hnotebook.set_current_page (0);
786 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
787 _vnotebook.set_current_page (v_current_page);
788 } else {
789 _vnotebook.set_current_page (0);
792 if (_hnotebook.get_n_pages() <= 1) {
793 _hbox.hide ();
794 } else {
795 _hbox.show ();
798 if (_vnotebook.get_n_pages() <= 1) {
799 _vbox.hide ();
800 } else {
801 _vbox.show ();
805 void
806 PortMatrix::remove_notebook_pages (Notebook& n)
808 int const N = n.get_n_pages ();
810 for (int i = 0; i < N; ++i) {
811 n.remove_page ();
815 void
816 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
818 if (_ignore_notebook_page_selected) {
819 return;
822 _body->setup ();
823 setup_scrollbars ();
824 queue_draw ();
827 void
828 PortMatrix::session_going_away ()
830 _session = 0;
833 void
834 PortMatrix::body_dimensions_changed ()
836 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
837 if (_arrangement == TOP_TO_RIGHT) {
838 _vspacer.set_size_request (-1, _body->column_labels_height ());
839 _vspacer.show ();
840 } else {
841 _vspacer.hide ();
844 int curr_width;
845 int curr_height;
846 _parent->get_size (curr_width, curr_height);
848 pair<uint32_t, uint32_t> m = max_size ();
850 /* Don't shrink the window */
851 m.first = max (int (m.first), curr_width);
852 m.second = max (int (m.second), curr_height);
854 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
858 boost::shared_ptr<const PortGroup>
859 PortMatrix::visible_ports (int d) const
861 PortGroupList const & p = _ports[d];
862 PortGroupList::List::const_iterator j = p.begin ();
864 int n = 0;
865 if (d == _row_index) {
866 n = p.size() - _vnotebook.get_current_page () - 1;
867 } else {
868 n = _hnotebook.get_current_page ();
871 int i = 0;
872 while (i != int (n) && j != p.end ()) {
873 ++i;
874 ++j;
877 if (j == p.end()) {
878 return boost::shared_ptr<const PortGroup> ();
881 return *j;
884 void
885 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
887 using namespace Menu_Helpers;
889 boost::shared_ptr<Bundle> b = w.lock ();
890 if (!b) {
891 return;
894 char buf [64];
895 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
896 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
899 void
900 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
902 using namespace Menu_Helpers;
904 boost::shared_ptr<Bundle> b = w.lock ();
905 if (!b) {
906 return;
909 char buf [64];
910 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
911 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
914 void
915 PortMatrix::port_connected_or_disconnected ()
917 _body->rebuild_and_draw_grid ();
920 string
921 PortMatrix::channel_noun () const
923 return _("channel");
926 /** @return true if this matrix should show bundles / ports of type \t */
927 bool
928 PortMatrix::should_show (DataType t) const
930 return (_type == DataType::NIL || t == _type);
933 uint32_t
934 PortMatrix::count_of_our_type (ChanCount c) const
936 if (_type == DataType::NIL) {
937 return c.n_total ();
940 return c.get (_type);
943 PortMatrixNode::State
944 PortMatrix::get_association (PortMatrixNode node) const
946 if (show_only_bundles ()) {
948 bool have_off_diagonal_association = false;
949 bool have_diagonal_association = false;
950 bool have_diagonal_not_association = false;
952 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
954 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
956 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
957 continue;
960 ARDOUR::BundleChannel c[2];
961 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
962 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
964 PortMatrixNode::State const s = get_state (c);
966 switch (s) {
967 case PortMatrixNode::ASSOCIATED:
968 if (i == j) {
969 have_diagonal_association = true;
970 } else {
971 have_off_diagonal_association = true;
973 break;
975 case PortMatrixNode::NOT_ASSOCIATED:
976 if (i == j) {
977 have_diagonal_not_association = true;
979 break;
981 default:
982 break;
987 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
988 return PortMatrixNode::ASSOCIATED;
989 } else if (!have_diagonal_association && !have_off_diagonal_association) {
990 return PortMatrixNode::NOT_ASSOCIATED;
993 return PortMatrixNode::PARTIAL;
995 } else {
997 ARDOUR::BundleChannel c[2];
998 c[column_index()] = node.column;
999 c[row_index()] = node.row;
1000 return get_state (c);
1004 /* NOTREACHED */
1005 return PortMatrixNode::NOT_ASSOCIATED;