Zoom session when the mouse pointer is moved up and down during a playhead drag.
[ardour2.git] / gtk2_ardour / port_matrix.cc
blob696c625da0eeb352224bc024a99435a59ec27be7
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 if (bc[dim].bundle->nchannels().n_total() > 1) {
459 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
460 if (should_show (bc[dim].bundle->channel_type(i))) {
461 add_remove_option (sub, w, i);
468 if (_show_only_bundles || count_of_our_type (bc[dim].bundle->nchannels()) <= 1) {
469 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
470 sub.push_back (
471 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
474 } else {
476 if (bc[dim].channel != -1) {
477 add_disassociate_option (sub, w, dim, bc[dim].channel);
478 } else {
479 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
480 sub.push_back (
481 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
484 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
485 if (should_show (bc[dim].bundle->channel_type(i))) {
486 add_disassociate_option (sub, w, dim, i);
492 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
493 need_separator = true;
498 if (need_separator) {
499 items.push_back (SeparatorElem ());
502 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
503 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
504 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
505 _inhibit_toggle_show_only_bundles = true;
506 i->set_active (!_show_only_bundles);
507 _inhibit_toggle_show_only_bundles = false;
509 _menu->popup (1, t);
512 void
513 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
515 boost::shared_ptr<Bundle> sb = b.lock ();
516 if (!sb) {
517 return;
520 remove_channel (BundleChannel (sb, c));
524 void
525 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
527 boost::shared_ptr<Bundle> sb = b.lock ();
528 if (!sb) {
529 return;
532 rename_channel (BundleChannel (sb, c));
535 void
536 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
538 boost::shared_ptr<Bundle> sb = bundle.lock ();
539 if (!sb) {
540 return;
543 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
544 if (should_show (sb->channel_type(i))) {
545 disassociate_all_on_channel (bundle, i, dim);
550 void
551 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
553 boost::shared_ptr<Bundle> sb = bundle.lock ();
554 if (!sb) {
555 return;
558 PortGroup::BundleList a = _ports[1-dim].bundles ();
560 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
561 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
563 if (should_show ((*i)->bundle->channel_type(j))) {
564 continue;
567 BundleChannel c[2];
568 c[dim] = BundleChannel (sb, channel);
569 c[1-dim] = BundleChannel ((*i)->bundle, j);
571 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
572 set_state (c, false);
577 _body->rebuild_and_draw_grid ();
580 void
581 PortMatrix::setup_global_ports ()
583 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
585 for (int i = 0; i < 2; ++i) {
586 if (list_is_global (i)) {
587 setup_ports (i);
592 void
593 PortMatrix::setup_global_ports_proxy ()
595 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
596 for a discussion.
599 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
602 void
603 PortMatrix::setup_all_ports ()
605 if (_session->deletion_in_progress()) {
606 return;
609 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
611 setup_ports (0);
612 setup_ports (1);
615 void
616 PortMatrix::toggle_show_only_bundles ()
618 if (_inhibit_toggle_show_only_bundles) {
619 return;
622 _show_only_bundles = !_show_only_bundles;
624 setup ();
627 pair<uint32_t, uint32_t>
628 PortMatrix::max_size () const
630 pair<uint32_t, uint32_t> m = _body->max_size ();
632 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
633 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
635 return m;
638 bool
639 PortMatrix::on_scroll_event (GdkEventScroll* ev)
641 double const h = _hscroll.get_value ();
642 double const v = _vscroll.get_value ();
644 switch (ev->direction) {
645 case GDK_SCROLL_UP:
646 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
647 break;
648 case GDK_SCROLL_DOWN:
649 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
650 break;
651 case GDK_SCROLL_LEFT:
652 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
653 break;
654 case GDK_SCROLL_RIGHT:
655 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
656 break;
659 return true;
662 boost::shared_ptr<IO>
663 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
665 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
666 if (!io) {
667 io = _ports[1].io_from_bundle (b);
670 return io;
673 bool
674 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
676 return io_from_bundle (b);
679 void
680 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
682 boost::shared_ptr<IO> io = io_from_bundle (b);
684 if (io) {
685 io->add_port ("", this, t);
689 bool
690 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
692 return io_from_bundle (b);
695 void
696 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
698 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
700 if (io) {
701 Port* p = io->nth (b.channel);
702 if (p) {
703 int const r = io->remove_port (p, this);
704 if (r == -1) {
705 ArdourDialog d (_("Port removal not allowed"));
706 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
707 d.get_vbox()->pack_start (l);
708 d.add_button (Stock::OK, RESPONSE_ACCEPT);
709 d.set_modal (true);
710 d.show_all ();
711 d.run ();
717 void
718 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
720 boost::shared_ptr<Bundle> b = w.lock ();
721 if (!b) {
722 return;
725 for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
726 if (should_show (b->channel_type(i))) {
727 remove_channel (ARDOUR::BundleChannel (b, i));
732 void
733 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
735 boost::shared_ptr<Bundle> b = w.lock ();
736 if (!b) {
737 return;
740 add_channel (b, t);
743 void
744 PortMatrix::setup_notebooks ()
746 int const h_current_page = _hnotebook.get_current_page ();
747 int const v_current_page = _vnotebook.get_current_page ();
749 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
750 when adding or removing pages to or from notebooks, so ignore them */
752 _ignore_notebook_page_selected = true;
754 remove_notebook_pages (_hnotebook);
755 remove_notebook_pages (_vnotebook);
757 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
758 HBox* dummy = manage (new HBox);
759 dummy->show ();
760 Label* label = manage (new Label ((*i)->name));
761 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
762 label->show ();
763 _vnotebook.prepend_page (*dummy, *label);
766 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
767 HBox* dummy = manage (new HBox);
768 dummy->show ();
769 _hnotebook.append_page (*dummy, (*i)->name);
772 _ignore_notebook_page_selected = false;
774 if (_arrangement == TOP_TO_RIGHT) {
775 _vnotebook.set_tab_pos (POS_RIGHT);
776 _hnotebook.set_tab_pos (POS_TOP);
777 } else {
778 _vnotebook.set_tab_pos (POS_LEFT);
779 _hnotebook.set_tab_pos (POS_BOTTOM);
782 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
783 _hnotebook.set_current_page (h_current_page);
784 } else {
785 _hnotebook.set_current_page (0);
788 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
789 _vnotebook.set_current_page (v_current_page);
790 } else {
791 _vnotebook.set_current_page (0);
794 if (_hnotebook.get_n_pages() <= 1) {
795 _hbox.hide ();
796 } else {
797 _hbox.show ();
800 if (_vnotebook.get_n_pages() <= 1) {
801 _vbox.hide ();
802 } else {
803 _vbox.show ();
807 void
808 PortMatrix::remove_notebook_pages (Notebook& n)
810 int const N = n.get_n_pages ();
812 for (int i = 0; i < N; ++i) {
813 n.remove_page ();
817 void
818 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
820 if (_ignore_notebook_page_selected) {
821 return;
824 _body->setup ();
825 setup_scrollbars ();
826 queue_draw ();
829 void
830 PortMatrix::session_going_away ()
832 _session = 0;
835 void
836 PortMatrix::body_dimensions_changed ()
838 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
839 if (_arrangement == TOP_TO_RIGHT) {
840 _vspacer.set_size_request (-1, _body->column_labels_height ());
841 _vspacer.show ();
842 } else {
843 _vspacer.hide ();
846 int curr_width;
847 int curr_height;
848 _parent->get_size (curr_width, curr_height);
850 pair<uint32_t, uint32_t> m = max_size ();
852 /* Don't shrink the window */
853 m.first = max (int (m.first), curr_width);
854 m.second = max (int (m.second), curr_height);
856 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
860 boost::shared_ptr<const PortGroup>
861 PortMatrix::visible_ports (int d) const
863 PortGroupList const & p = _ports[d];
864 PortGroupList::List::const_iterator j = p.begin ();
866 int n = 0;
867 if (d == _row_index) {
868 n = p.size() - _vnotebook.get_current_page () - 1;
869 } else {
870 n = _hnotebook.get_current_page ();
873 int i = 0;
874 while (i != int (n) && j != p.end ()) {
875 ++i;
876 ++j;
879 if (j == p.end()) {
880 return boost::shared_ptr<const PortGroup> ();
883 return *j;
886 void
887 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
889 using namespace Menu_Helpers;
891 boost::shared_ptr<Bundle> b = w.lock ();
892 if (!b) {
893 return;
896 char buf [64];
897 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
898 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
901 void
902 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
904 using namespace Menu_Helpers;
906 boost::shared_ptr<Bundle> b = w.lock ();
907 if (!b) {
908 return;
911 char buf [64];
912 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
913 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
916 void
917 PortMatrix::port_connected_or_disconnected ()
919 _body->rebuild_and_draw_grid ();
922 string
923 PortMatrix::channel_noun () const
925 return _("channel");
928 /** @return true if this matrix should show bundles / ports of type \t */
929 bool
930 PortMatrix::should_show (DataType t) const
932 return (_type == DataType::NIL || t == _type);
935 uint32_t
936 PortMatrix::count_of_our_type (ChanCount c) const
938 if (_type == DataType::NIL) {
939 return c.n_total ();
942 return c.get (_type);
945 PortMatrixNode::State
946 PortMatrix::get_association (PortMatrixNode node) const
948 if (show_only_bundles ()) {
950 bool have_off_diagonal_association = false;
951 bool have_diagonal_association = false;
952 bool have_diagonal_not_association = false;
954 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
956 for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
958 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
959 continue;
962 ARDOUR::BundleChannel c[2];
963 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
964 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
966 PortMatrixNode::State const s = get_state (c);
968 switch (s) {
969 case PortMatrixNode::ASSOCIATED:
970 if (i == j) {
971 have_diagonal_association = true;
972 } else {
973 have_off_diagonal_association = true;
975 break;
977 case PortMatrixNode::NOT_ASSOCIATED:
978 if (i == j) {
979 have_diagonal_not_association = true;
981 break;
983 default:
984 break;
989 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
990 return PortMatrixNode::ASSOCIATED;
991 } else if (!have_diagonal_association && !have_off_diagonal_association) {
992 return PortMatrixNode::NOT_ASSOCIATED;
995 return PortMatrixNode::PARTIAL;
997 } else {
999 ARDOUR::BundleChannel c[2];
1000 c[column_index()] = node.column;
1001 c[row_index()] = node.row;
1002 return get_state (c);
1006 /* NOTREACHED */
1007 return PortMatrixNode::NOT_ASSOCIATED;