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 "ardour/bundle.h"
29 #include "ardour/types.h"
30 #include "ardour/session.h"
31 #include "ardour/route.h"
32 #include "ardour/audioengine.h"
33 #include "port_matrix.h"
34 #include "port_matrix_body.h"
35 #include "port_matrix_component.h"
37 #include "gui_thread.h"
41 using namespace ARDOUR;
43 /** PortMatrix constructor.
44 * @param session Our session.
45 * @param type Port type that we are handling.
47 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
52 , _arrangement (TOP_TO_RIGHT)
55 , _min_height_divisor (1)
56 , _show_only_bundles (false)
57 , _inhibit_toggle_show_only_bundles (false)
58 , _ignore_notebook_page_selected (false)
60 set_session (session);
62 _body = new PortMatrixBody (this);
63 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
65 _vbox.pack_start (_vspacer, false, false);
66 _vbox.pack_start (_vnotebook, false, false);
67 _vbox.pack_start (_vlabel, true, true);
68 _hbox.pack_start (_hspacer, false, false);
69 _hbox.pack_start (_hnotebook, false, false);
70 _hbox.pack_start (_hlabel, true, true);
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 for (int i = 0; i < 2; ++i) {
80 _ports[i].set_type (type);
83 _vlabel.set_use_markup ();
84 _vlabel.set_alignment (1, 1);
85 _vlabel.set_padding (4, 16);
86 _vlabel.set_name (X_("PortMatrixLabel"));
87 _hlabel.set_use_markup ();
88 _hlabel.set_alignment (1, 0.5);
89 _hlabel.set_padding (16, 4);
90 _hlabel.set_name (X_("PortMatrixLabel"));
105 PortMatrix::~PortMatrix ()
111 /** Perform initial and once-only setup. This must be called by
112 * subclasses after they have set up _ports[] to at least some
113 * reasonable extent. Two-part initialisation is necessary because
114 * setting up _ports is largely done by virtual functions in
121 select_arrangement ();
123 /* Signal handling is kind of split into two parts:
125 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
126 * representation of the information in _ports[].
128 * 2. When certain other things change, we need to get our subclass to clear and
129 * re-fill _ports[], which in turn causes appropriate signals to be raised to
130 * hook into part (1).
134 /* Part 1: the basic _ports[] change -> reset visuals */
136 for (int i = 0; i < 2; ++i) {
137 /* watch for the content of _ports[] changing */
138 _ports[i].Changed.connect (sigc::mem_fun (*this, &PortMatrix::setup));
140 /* and for bundles in _ports[] changing */
141 _ports[i].BundleChanged.connect (sigc::hide (sigc::mem_fun (*this, &PortMatrix::setup)));
144 /* scrolling stuff */
145 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
146 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
149 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
151 /* watch for routes being added or removed */
152 _session_connections.add_connection (_session->RouteAdded.connect (boost::bind (&PortMatrix::routes_changed, this)));
154 /* and also bundles */
155 _session_connections.add_connection (_session->BundleAdded.connect (boost::bind (&PortMatrix::setup_global_ports, this)));
158 _session_connections.add_connection (_session->engine().PortRegisteredOrUnregistered.connect (boost::bind (&PortMatrix::setup_global_ports, this)));
160 reconnect_to_routes ();
165 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
167 PortMatrix::reconnect_to_routes ()
169 _route_connections.drop_connections ();
171 boost::shared_ptr<RouteList> routes = _session->get_routes ();
172 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
173 _route_connections.add_connection ((*i)->processors_changed.connect (sigc::mem_fun (*this, &PortMatrix::route_processors_changed)));
178 PortMatrix::route_processors_changed (RouteProcessorChange c)
180 if (c.type == RouteProcessorChange::MeterPointChange) {
181 /* this change has no impact on the port matrix */
185 setup_global_ports ();
188 /** A route has been added to or removed from the session */
190 PortMatrix::routes_changed ()
192 reconnect_to_routes ();
193 setup_global_ports ();
196 /** Set up everything that depends on the content of _ports[] */
200 /* this needs to be done first, as the visible_ports() method uses the
201 notebook state to decide which ports are being shown */
211 PortMatrix::set_type (DataType t)
214 _ports[0].set_type (_type);
215 _ports[1].set_type (_type);
221 PortMatrix::hscroll_changed ()
223 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
227 PortMatrix::vscroll_changed ()
229 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
233 PortMatrix::setup_scrollbars ()
235 Adjustment* a = _hscroll.get_adjustment ();
237 a->set_upper (_body->full_scroll_width());
238 a->set_page_size (_body->alloc_scroll_width());
239 a->set_step_increment (32);
240 a->set_page_increment (128);
242 a = _vscroll.get_adjustment ();
244 a->set_upper (_body->full_scroll_height());
245 a->set_page_size (_body->alloc_scroll_height());
246 a->set_step_increment (32);
247 a->set_page_increment (128);
250 /** Disassociate all of our ports from each other */
252 PortMatrix::disassociate_all ()
254 PortGroup::BundleList a = _ports[0].bundles ();
255 PortGroup::BundleList b = _ports[1].bundles ();
257 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
258 for (uint32_t j = 0; j < (*i)->bundle->nchannels(); ++j) {
259 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
260 for (uint32_t l = 0; l < (*k)->bundle->nchannels(); ++l) {
262 BundleChannel c[2] = {
263 BundleChannel ((*i)->bundle, j),
264 BundleChannel ((*k)->bundle, l)
267 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
268 set_state (c, false);
276 _body->rebuild_and_draw_grid ();
279 /* Decide how to arrange the components of the matrix */
281 PortMatrix::select_arrangement ()
283 uint32_t const N[2] = {
284 _ports[0].total_channels (),
285 _ports[1].total_channels ()
288 /* The list with the most channels goes on left or right, so that the most channel
289 names are printed horizontally and hence more readable. However we also
290 maintain notional `signal flow' vaguely from left to right. Subclasses
291 should choose where to put ports based on signal flowing from _ports[0]
298 _arrangement = LEFT_TO_BOTTOM;
299 _vlabel.set_label (_("<b>Sources</b>"));
300 _hlabel.set_label (_("<b>Destinations</b>"));
301 _vlabel.set_angle (90);
303 attach (*_body, 1, 2, 0, 1);
304 attach (_vscroll, 2, 3, 0, 1, SHRINK);
305 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
306 attach (_vbox, 0, 1, 0, 1, SHRINK);
307 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
309 set_col_spacing (0, 4);
310 set_row_spacing (0, 4);
316 _arrangement = TOP_TO_RIGHT;
317 _hlabel.set_label (_("<b>Sources</b>"));
318 _vlabel.set_label (_("<b>Destinations</b>"));
319 _vlabel.set_angle (-90);
321 attach (*_body, 0, 1, 1, 2);
322 attach (_vscroll, 2, 3, 1, 2, SHRINK);
323 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
324 attach (_vbox, 1, 2, 1, 2, SHRINK);
325 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
327 set_col_spacing (1, 4);
328 set_row_spacing (1, 4);
332 /** @return columns list */
333 PortGroupList const *
334 PortMatrix::columns () const
336 return &_ports[_column_index];
339 boost::shared_ptr<const PortGroup>
340 PortMatrix::visible_columns () const
342 return visible_ports (_column_index);
345 /* @return rows list */
346 PortGroupList const *
347 PortMatrix::rows () const
349 return &_ports[_row_index];
352 boost::shared_ptr<const PortGroup>
353 PortMatrix::visible_rows () const
355 return visible_ports (_row_index);
359 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
361 using namespace Menu_Helpers;
366 _menu->set_name ("ArdourContextMenu");
368 MenuList& items = _menu->items ();
371 bc[_column_index] = column;
372 bc[_row_index] = row;
375 bool need_separator = false;
377 for (int dim = 0; dim < 2; ++dim) {
379 if (bc[dim].bundle) {
381 Menu* m = manage (new Menu);
382 MenuList& sub = m->items ();
384 boost::weak_ptr<Bundle> w (bc[dim].bundle);
386 bool can_add_or_rename = false;
388 if (can_add_channel (bc[dim].bundle)) {
389 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
390 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
391 can_add_or_rename = true;
395 if (can_rename_channels (bc[dim].bundle)) {
396 snprintf (buf, sizeof (buf), _("Rename '%s'..."), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
400 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
403 can_add_or_rename = true;
406 if (can_add_or_rename) {
407 sub.push_back (SeparatorElem ());
410 if (can_remove_channels (bc[dim].bundle)) {
411 if (bc[dim].channel != -1) {
412 add_remove_option (sub, w, bc[dim].channel);
414 for (uint32_t i = 0; i < bc[dim].bundle->nchannels(); ++i) {
415 add_remove_option (sub, w, i);
420 if (_show_only_bundles || bc[dim].bundle->nchannels() <= 1) {
421 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
423 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
427 if (bc[dim].channel != -1) {
428 add_disassociate_option (sub, w, dim, bc[dim].channel);
430 for (uint32_t i = 0; i < bc[dim].bundle->nchannels(); ++i) {
431 add_disassociate_option (sub, w, dim, i);
436 items.push_back (MenuElem (bc[dim].bundle->name().c_str(), *m));
437 need_separator = true;
442 if (need_separator) {
443 items.push_back (SeparatorElem ());
446 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
447 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
448 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
449 _inhibit_toggle_show_only_bundles = true;
450 i->set_active (!_show_only_bundles);
451 _inhibit_toggle_show_only_bundles = false;
457 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
459 boost::shared_ptr<Bundle> sb = b.lock ();
464 remove_channel (BundleChannel (sb, c));
469 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
471 boost::shared_ptr<Bundle> sb = b.lock ();
476 rename_channel (BundleChannel (sb, c));
480 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
482 boost::shared_ptr<Bundle> sb = bundle.lock ();
487 PortGroup::BundleList a = _ports[1-dim].bundles ();
489 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
490 for (uint32_t j = 0; j < (*i)->bundle->nchannels(); ++j) {
493 c[dim] = BundleChannel (sb, channel);
494 c[1-dim] = BundleChannel ((*i)->bundle, j);
496 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
497 set_state (c, false);
502 _body->rebuild_and_draw_grid ();
506 PortMatrix::setup_global_ports ()
508 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
510 for (int i = 0; i < 2; ++i) {
511 if (list_is_global (i)) {
518 PortMatrix::setup_all_ports ()
520 if (_session->deletion_in_progress()) {
524 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
531 PortMatrix::toggle_show_only_bundles ()
533 if (_inhibit_toggle_show_only_bundles) {
537 _show_only_bundles = !_show_only_bundles;
542 pair<uint32_t, uint32_t>
543 PortMatrix::max_size () const
545 pair<uint32_t, uint32_t> m = _body->max_size ();
547 m.first += _vscroll.get_width ();
548 m.second += _hscroll.get_height ();
554 PortMatrix::on_scroll_event (GdkEventScroll* ev)
556 double const h = _hscroll.get_value ();
557 double const v = _vscroll.get_value ();
559 switch (ev->direction) {
561 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
563 case GDK_SCROLL_DOWN:
564 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
566 case GDK_SCROLL_LEFT:
567 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
569 case GDK_SCROLL_RIGHT:
570 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
577 boost::shared_ptr<IO>
578 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
580 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
582 io = _ports[1].io_from_bundle (b);
589 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
591 return io_from_bundle (b);
595 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
597 boost::shared_ptr<IO> io = io_from_bundle (b);
600 io->add_port ("", this, _type);
605 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
607 return io_from_bundle (b);
611 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
613 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
616 Port* p = io->nth (b.channel);
618 io->remove_port (p, this);
624 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
626 boost::shared_ptr<Bundle> b = w.lock ();
635 PortMatrix::setup_notebooks ()
637 int const h_current_page = _hnotebook.get_current_page ();
638 int const v_current_page = _vnotebook.get_current_page ();
640 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
641 when adding or removing pages to or from notebooks, so ignore them */
643 _ignore_notebook_page_selected = true;
645 remove_notebook_pages (_hnotebook);
646 remove_notebook_pages (_vnotebook);
648 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
649 HBox* dummy = manage (new HBox);
651 Label* label = manage (new Label ((*i)->name));
652 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
654 _vnotebook.prepend_page (*dummy, *label);
657 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
658 HBox* dummy = manage (new HBox);
660 _hnotebook.append_page (*dummy, (*i)->name);
663 _ignore_notebook_page_selected = false;
665 _vnotebook.set_tab_pos (POS_LEFT);
666 _hnotebook.set_tab_pos (POS_TOP);
668 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
669 _hnotebook.set_current_page (h_current_page);
671 _hnotebook.set_current_page (0);
674 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
675 _vnotebook.set_current_page (v_current_page);
677 _vnotebook.set_current_page (0);
680 if (_hnotebook.get_n_pages() <= 1) {
686 if (_vnotebook.get_n_pages() <= 1) {
694 PortMatrix::remove_notebook_pages (Notebook& n)
696 int const N = n.get_n_pages ();
698 for (int i = 0; i < N; ++i) {
704 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
706 if (_ignore_notebook_page_selected) {
716 PortMatrix::session_going_away ()
722 PortMatrix::body_dimensions_changed ()
724 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
725 if (_arrangement == TOP_TO_RIGHT) {
726 _vspacer.set_size_request (-1, _body->column_labels_height ());
735 boost::shared_ptr<const PortGroup>
736 PortMatrix::visible_ports (int d) const
738 PortGroupList const & p = _ports[d];
739 PortGroupList::List::const_iterator j = p.begin ();
742 if (d == _row_index) {
743 n = p.size() - _vnotebook.get_current_page () - 1;
745 n = _hnotebook.get_current_page ();
749 while (i != int (n) && j != p.end ()) {
755 return boost::shared_ptr<const PortGroup> ();
762 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
764 using namespace Menu_Helpers;
766 boost::shared_ptr<Bundle> b = w.lock ();
772 snprintf (buf, sizeof (buf), _("Remove '%s'"), b->channel_name (c).c_str());
773 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
777 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
779 using namespace Menu_Helpers;
781 boost::shared_ptr<Bundle> b = w.lock ();
787 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), b->channel_name (c).c_str());
788 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));