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"
42 using namespace ARDOUR;
44 /** PortMatrix constructor.
45 * @param session Our session.
46 * @param type Port type that we are handling.
48 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
53 , _arrangement (TOP_TO_RIGHT)
56 , _min_height_divisor (1)
57 , _show_only_bundles (false)
58 , _inhibit_toggle_show_only_bundles (false)
59 , _ignore_notebook_page_selected (false)
61 set_session (session);
63 _body = new PortMatrixBody (this);
64 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
66 _vbox.pack_start (_vspacer, false, false);
67 _vbox.pack_start (_vnotebook, false, false);
68 _vbox.pack_start (_vlabel, true, true);
69 _hbox.pack_start (_hspacer, false, false);
70 _hbox.pack_start (_hnotebook, false, false);
71 _hbox.pack_start (_hlabel, true, true);
73 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
74 _vnotebook.property_tab_border() = 4;
75 _vnotebook.set_name (X_("PortMatrixLabel"));
76 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
77 _hnotebook.property_tab_border() = 4;
78 _hnotebook.set_name (X_("PortMatrixLabel"));
80 for (int i = 0; i < 2; ++i) {
81 _ports[i].set_type (type);
84 _vlabel.set_use_markup ();
85 _vlabel.set_alignment (1, 1);
86 _vlabel.set_padding (4, 16);
87 _vlabel.set_name (X_("PortMatrixLabel"));
88 _hlabel.set_use_markup ();
89 _hlabel.set_alignment (1, 0.5);
90 _hlabel.set_padding (16, 4);
91 _hlabel.set_name (X_("PortMatrixLabel"));
106 PortMatrix::~PortMatrix ()
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
122 select_arrangement ();
124 /* Signal handling is kind of split into two 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).
135 /* Part 1: the basic _ports[] change -> reset visuals */
137 for (int i = 0; i < 2; ++i) {
138 /* watch for the content of _ports[] changing */
139 _ports[i].Changed.connect (_changed_connections, boost::bind (&PortMatrix::setup, this), gui_context());
141 /* and for bundles in _ports[] changing */
142 _ports[i].BundleChanged.connect (_bundle_changed_connections, boost::bind (&PortMatrix::setup, this), gui_context());
145 /* scrolling stuff */
146 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
147 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
150 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
152 /* watch for routes being added or removed */
153 _session->RouteAdded.connect (_session_connections, boost::bind (&PortMatrix::routes_changed, this), gui_context());
155 /* and also bundles */
156 _session->BundleAdded.connect (_session_connections, boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
159 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
161 reconnect_to_routes ();
166 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
168 PortMatrix::reconnect_to_routes ()
170 _route_connections.drop_connections ();
172 boost::shared_ptr<RouteList> routes = _session->get_routes ();
173 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
174 (*i)->processors_changed.connect (_route_connections, ui_bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
179 PortMatrix::route_processors_changed (RouteProcessorChange c)
181 if (c.type == RouteProcessorChange::MeterPointChange) {
182 /* this change has no impact on the port matrix */
186 setup_global_ports ();
189 /** A route has been added to or removed from the session */
191 PortMatrix::routes_changed ()
193 reconnect_to_routes ();
194 setup_global_ports ();
197 /** Set up everything that depends on the content of _ports[] */
201 /* this needs to be done first, as the visible_ports() method uses the
202 notebook state to decide which ports are being shown */
212 PortMatrix::set_type (DataType t)
215 _ports[0].set_type (_type);
216 _ports[1].set_type (_type);
222 PortMatrix::hscroll_changed ()
224 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
228 PortMatrix::vscroll_changed ()
230 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
234 PortMatrix::setup_scrollbars ()
236 Adjustment* a = _hscroll.get_adjustment ();
238 a->set_upper (_body->full_scroll_width());
239 a->set_page_size (_body->alloc_scroll_width());
240 a->set_step_increment (32);
241 a->set_page_increment (128);
243 a = _vscroll.get_adjustment ();
245 a->set_upper (_body->full_scroll_height());
246 a->set_page_size (_body->alloc_scroll_height());
247 a->set_step_increment (32);
248 a->set_page_increment (128);
251 /** Disassociate all of our ports from each other */
253 PortMatrix::disassociate_all ()
255 PortGroup::BundleList a = _ports[0].bundles ();
256 PortGroup::BundleList b = _ports[1].bundles ();
258 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
259 for (uint32_t j = 0; j < (*i)->bundle->nchannels(); ++j) {
260 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
261 for (uint32_t l = 0; l < (*k)->bundle->nchannels(); ++l) {
263 BundleChannel c[2] = {
264 BundleChannel ((*i)->bundle, j),
265 BundleChannel ((*k)->bundle, l)
268 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
269 set_state (c, false);
277 _body->rebuild_and_draw_grid ();
280 /* Decide how to arrange the components of the matrix */
282 PortMatrix::select_arrangement ()
284 uint32_t const N[2] = {
285 _ports[0].total_channels (),
286 _ports[1].total_channels ()
289 /* The list with the most channels goes on left or right, so that the most channel
290 names are printed horizontally and hence more readable. However we also
291 maintain notional `signal flow' vaguely from left to right. Subclasses
292 should choose where to put ports based on signal flowing from _ports[0]
299 _arrangement = LEFT_TO_BOTTOM;
300 _vlabel.set_label (_("<b>Sources</b>"));
301 _hlabel.set_label (_("<b>Destinations</b>"));
302 _vlabel.set_angle (90);
304 attach (*_body, 1, 2, 0, 1);
305 attach (_vscroll, 2, 3, 0, 1, SHRINK);
306 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
307 attach (_vbox, 0, 1, 0, 1, SHRINK);
308 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
310 set_col_spacing (0, 4);
311 set_row_spacing (0, 4);
317 _arrangement = TOP_TO_RIGHT;
318 _hlabel.set_label (_("<b>Sources</b>"));
319 _vlabel.set_label (_("<b>Destinations</b>"));
320 _vlabel.set_angle (-90);
322 attach (*_body, 0, 1, 1, 2);
323 attach (_vscroll, 2, 3, 1, 2, SHRINK);
324 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
325 attach (_vbox, 1, 2, 1, 2, SHRINK);
326 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
328 set_col_spacing (1, 4);
329 set_row_spacing (1, 4);
333 /** @return columns list */
334 PortGroupList const *
335 PortMatrix::columns () const
337 return &_ports[_column_index];
340 boost::shared_ptr<const PortGroup>
341 PortMatrix::visible_columns () const
343 return visible_ports (_column_index);
346 /* @return rows list */
347 PortGroupList const *
348 PortMatrix::rows () const
350 return &_ports[_row_index];
353 boost::shared_ptr<const PortGroup>
354 PortMatrix::visible_rows () const
356 return visible_ports (_row_index);
360 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
362 using namespace Menu_Helpers;
367 _menu->set_name ("ArdourContextMenu");
369 MenuList& items = _menu->items ();
372 bc[_column_index] = column;
373 bc[_row_index] = row;
376 bool need_separator = false;
378 for (int dim = 0; dim < 2; ++dim) {
380 if (bc[dim].bundle) {
382 Menu* m = manage (new Menu);
383 MenuList& sub = m->items ();
385 boost::weak_ptr<Bundle> w (bc[dim].bundle);
387 bool can_add_or_rename = false;
389 if (can_add_channel (bc[dim].bundle)) {
390 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
391 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
392 can_add_or_rename = true;
396 if (can_rename_channels (bc[dim].bundle)) {
398 buf, sizeof (buf), _("Rename '%s'..."),
399 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
404 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
407 can_add_or_rename = true;
410 if (can_add_or_rename) {
411 sub.push_back (SeparatorElem ());
414 if (can_remove_channels (bc[dim].bundle)) {
415 if (bc[dim].channel != -1) {
416 add_remove_option (sub, w, bc[dim].channel);
419 snprintf (buf, sizeof (buf), _("Remove all"));
421 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
424 for (uint32_t i = 0; i < bc[dim].bundle->nchannels(); ++i) {
425 add_remove_option (sub, w, i);
430 if (_show_only_bundles || bc[dim].bundle->nchannels() <= 1) {
431 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
433 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
438 if (bc[dim].channel != -1) {
439 add_disassociate_option (sub, w, dim, bc[dim].channel);
441 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
443 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
446 for (uint32_t i = 0; i < bc[dim].bundle->nchannels(); ++i) {
447 add_disassociate_option (sub, w, dim, i);
452 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
453 need_separator = true;
458 if (need_separator) {
459 items.push_back (SeparatorElem ());
462 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
463 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
464 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
465 _inhibit_toggle_show_only_bundles = true;
466 i->set_active (!_show_only_bundles);
467 _inhibit_toggle_show_only_bundles = false;
473 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
475 boost::shared_ptr<Bundle> sb = b.lock ();
480 remove_channel (BundleChannel (sb, c));
485 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
487 boost::shared_ptr<Bundle> sb = b.lock ();
492 rename_channel (BundleChannel (sb, c));
496 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
498 boost::shared_ptr<Bundle> sb = bundle.lock ();
503 for (uint32_t i = 0; i < sb->nchannels(); ++i) {
504 disassociate_all_on_channel (bundle, i, dim);
509 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
511 boost::shared_ptr<Bundle> sb = bundle.lock ();
516 PortGroup::BundleList a = _ports[1-dim].bundles ();
518 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
519 for (uint32_t j = 0; j < (*i)->bundle->nchannels(); ++j) {
522 c[dim] = BundleChannel (sb, channel);
523 c[1-dim] = BundleChannel ((*i)->bundle, j);
525 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
526 set_state (c, false);
531 _body->rebuild_and_draw_grid ();
535 PortMatrix::setup_global_ports ()
537 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
539 for (int i = 0; i < 2; ++i) {
540 if (list_is_global (i)) {
547 PortMatrix::setup_all_ports ()
549 if (_session->deletion_in_progress()) {
553 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
560 PortMatrix::toggle_show_only_bundles ()
562 if (_inhibit_toggle_show_only_bundles) {
566 _show_only_bundles = !_show_only_bundles;
571 pair<uint32_t, uint32_t>
572 PortMatrix::max_size () const
574 pair<uint32_t, uint32_t> m = _body->max_size ();
576 m.first += _vscroll.get_width ();
577 m.second += _hscroll.get_height ();
583 PortMatrix::on_scroll_event (GdkEventScroll* ev)
585 double const h = _hscroll.get_value ();
586 double const v = _vscroll.get_value ();
588 switch (ev->direction) {
590 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
592 case GDK_SCROLL_DOWN:
593 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
595 case GDK_SCROLL_LEFT:
596 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
598 case GDK_SCROLL_RIGHT:
599 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
606 boost::shared_ptr<IO>
607 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
609 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
611 io = _ports[1].io_from_bundle (b);
618 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
620 return io_from_bundle (b);
624 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
626 boost::shared_ptr<IO> io = io_from_bundle (b);
629 io->add_port ("", this, _type);
634 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
636 return io_from_bundle (b);
640 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
642 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
645 Port* p = io->nth (b.channel);
647 io->remove_port (p, this);
653 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
655 boost::shared_ptr<Bundle> b = w.lock ();
660 for (uint32_t i = 0; i < b->nchannels(); ++i) {
661 remove_channel (ARDOUR::BundleChannel (b, i));
666 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
668 boost::shared_ptr<Bundle> b = w.lock ();
677 PortMatrix::setup_notebooks ()
679 int const h_current_page = _hnotebook.get_current_page ();
680 int const v_current_page = _vnotebook.get_current_page ();
682 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
683 when adding or removing pages to or from notebooks, so ignore them */
685 _ignore_notebook_page_selected = true;
687 remove_notebook_pages (_hnotebook);
688 remove_notebook_pages (_vnotebook);
690 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
691 HBox* dummy = manage (new HBox);
693 Label* label = manage (new Label ((*i)->name));
694 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
696 _vnotebook.prepend_page (*dummy, *label);
699 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
700 HBox* dummy = manage (new HBox);
702 _hnotebook.append_page (*dummy, (*i)->name);
705 _ignore_notebook_page_selected = false;
707 _vnotebook.set_tab_pos (POS_LEFT);
708 _hnotebook.set_tab_pos (POS_TOP);
710 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
711 _hnotebook.set_current_page (h_current_page);
713 _hnotebook.set_current_page (0);
716 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
717 _vnotebook.set_current_page (v_current_page);
719 _vnotebook.set_current_page (0);
722 if (_hnotebook.get_n_pages() <= 1) {
728 if (_vnotebook.get_n_pages() <= 1) {
736 PortMatrix::remove_notebook_pages (Notebook& n)
738 int const N = n.get_n_pages ();
740 for (int i = 0; i < N; ++i) {
746 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
748 if (_ignore_notebook_page_selected) {
758 PortMatrix::session_going_away ()
764 PortMatrix::body_dimensions_changed ()
766 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
767 if (_arrangement == TOP_TO_RIGHT) {
768 _vspacer.set_size_request (-1, _body->column_labels_height ());
777 boost::shared_ptr<const PortGroup>
778 PortMatrix::visible_ports (int d) const
780 PortGroupList const & p = _ports[d];
781 PortGroupList::List::const_iterator j = p.begin ();
784 if (d == _row_index) {
785 n = p.size() - _vnotebook.get_current_page () - 1;
787 n = _hnotebook.get_current_page ();
791 while (i != int (n) && j != p.end ()) {
797 return boost::shared_ptr<const PortGroup> ();
804 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
806 using namespace Menu_Helpers;
808 boost::shared_ptr<Bundle> b = w.lock ();
814 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
815 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
819 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
821 using namespace Menu_Helpers;
823 boost::shared_ptr<Bundle> b = w.lock ();
829 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
830 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));