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)
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 _body = new PortMatrixBody (this);
62 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
64 _vbox.pack_start (_vspacer, false, false);
65 _vbox.pack_start (_vnotebook, false, false);
66 _vbox.pack_start (_vlabel, true, true);
67 _hbox.pack_start (_hspacer, false, false);
68 _hbox.pack_start (_hnotebook, false, false);
69 _hbox.pack_start (_hlabel, true, true);
71 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
72 _vnotebook.property_tab_border() = 4;
73 _vnotebook.set_name (X_("PortMatrixLabel"));
74 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
75 _hnotebook.property_tab_border() = 4;
76 _hnotebook.set_name (X_("PortMatrixLabel"));
78 for (int i = 0; i < 2; ++i) {
79 _ports[i].set_type (type);
82 _vlabel.set_use_markup ();
83 _vlabel.set_alignment (1, 1);
84 _vlabel.set_padding (4, 16);
85 _vlabel.set_name (X_("PortMatrixLabel"));
86 _hlabel.set_use_markup ();
87 _hlabel.set_alignment (1, 0.5);
88 _hlabel.set_padding (16, 4);
89 _hlabel.set_name (X_("PortMatrixLabel"));
104 PortMatrix::~PortMatrix ()
110 /** Perform initial and once-only setup. This must be called by
111 * subclasses after they have set up _ports[] to at least some
112 * reasonable extent. Two-part initialisation is necessary because
113 * setting up _ports is largely done by virtual functions in
120 select_arrangement ();
122 /* Signal handling is kind of split into two parts:
124 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
125 * representation of the information in _ports[].
127 * 2. When certain other things change, we need to get our subclass to clear and
128 * re-fill _ports[], which in turn causes appropriate signals to be raised to
129 * hook into part (1).
133 /* Part 1: the basic _ports[] change -> reset visuals */
135 for (int i = 0; i < 2; ++i) {
136 /* watch for the content of _ports[] changing */
137 _ports[i].Changed.connect (sigc::mem_fun (*this, &PortMatrix::setup));
139 /* and for bundles in _ports[] changing */
140 _ports[i].BundleChanged.connect (sigc::hide (sigc::mem_fun (*this, &PortMatrix::setup)));
143 /* scrolling stuff */
144 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
145 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
148 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
150 /* watch for routes being added or removed */
151 _session->RouteAdded.connect (sigc::hide (sigc::mem_fun (*this, &PortMatrix::routes_changed)));
153 /* and also bundles */
154 _session->BundleAdded.connect (sigc::hide (sigc::mem_fun (*this, &PortMatrix::setup_global_ports)));
157 _session->engine().PortRegisteredOrUnregistered.connect (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
159 _session->GoingAway.connect (sigc::mem_fun (*this, &PortMatrix::session_going_away));
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 for (vector<sigc::connection>::iterator i = _route_connections.begin(); i != _route_connections.end(); ++i) {
173 _route_connections.clear ();
175 boost::shared_ptr<RouteList> routes = _session->get_routes ();
176 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
177 _route_connections.push_back (
178 (*i)->processors_changed.connect (sigc::mem_fun (*this, &PortMatrix::route_processors_changed))
184 PortMatrix::route_processors_changed (RouteProcessorChange c)
186 if (c.type == RouteProcessorChange::MeterPointChange) {
187 /* this change has no impact on the port matrix */
191 setup_global_ports ();
194 /** A route has been added to or removed from the session */
196 PortMatrix::routes_changed ()
198 reconnect_to_routes ();
199 setup_global_ports ();
202 /** Set up everything that depends on the content of _ports[] */
206 /* this needs to be done first, as the visible_ports() method uses the
207 notebook state to decide which ports are being shown */
217 PortMatrix::set_type (DataType t)
220 _ports[0].set_type (_type);
221 _ports[1].set_type (_type);
227 PortMatrix::hscroll_changed ()
229 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
233 PortMatrix::vscroll_changed ()
235 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
239 PortMatrix::setup_scrollbars ()
241 Adjustment* a = _hscroll.get_adjustment ();
243 a->set_upper (_body->full_scroll_width());
244 a->set_page_size (_body->alloc_scroll_width());
245 a->set_step_increment (32);
246 a->set_page_increment (128);
248 a = _vscroll.get_adjustment ();
250 a->set_upper (_body->full_scroll_height());
251 a->set_page_size (_body->alloc_scroll_height());
252 a->set_step_increment (32);
253 a->set_page_increment (128);
256 /** Disassociate all of our ports from each other */
258 PortMatrix::disassociate_all ()
260 PortGroup::BundleList a = _ports[0].bundles ();
261 PortGroup::BundleList b = _ports[1].bundles ();
263 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
264 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
265 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
266 for (uint32_t l = 0; l < k->bundle->nchannels(); ++l) {
268 BundleChannel c[2] = {
269 BundleChannel (i->bundle, j),
270 BundleChannel (k->bundle, l)
273 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
274 set_state (c, false);
282 _body->rebuild_and_draw_grid ();
285 /* Decide how to arrange the components of the matrix */
287 PortMatrix::select_arrangement ()
289 uint32_t const N[2] = {
290 _ports[0].total_channels (),
291 _ports[1].total_channels ()
294 /* The list with the most channels goes on left or right, so that the most channel
295 names are printed horizontally and hence more readable. However we also
296 maintain notional `signal flow' vaguely from left to right. Subclasses
297 should choose where to put ports based on signal flowing from _ports[0]
304 _arrangement = LEFT_TO_BOTTOM;
305 _vlabel.set_label (_("<b>Sources</b>"));
306 _hlabel.set_label (_("<b>Destinations</b>"));
307 _vlabel.set_angle (90);
309 attach (*_body, 1, 2, 0, 1);
310 attach (_vscroll, 2, 3, 0, 1, SHRINK);
311 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
312 attach (_vbox, 0, 1, 0, 1, SHRINK);
313 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
315 set_col_spacing (0, 4);
316 set_row_spacing (0, 4);
322 _arrangement = TOP_TO_RIGHT;
323 _hlabel.set_label (_("<b>Sources</b>"));
324 _vlabel.set_label (_("<b>Destinations</b>"));
325 _vlabel.set_angle (-90);
327 attach (*_body, 0, 1, 1, 2);
328 attach (_vscroll, 2, 3, 1, 2, SHRINK);
329 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
330 attach (_vbox, 1, 2, 1, 2, SHRINK);
331 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
333 set_col_spacing (1, 4);
334 set_row_spacing (1, 4);
338 /** @return columns list */
339 PortGroupList const *
340 PortMatrix::columns () const
342 return &_ports[_column_index];
345 boost::shared_ptr<const PortGroup>
346 PortMatrix::visible_columns () const
348 return visible_ports (_column_index);
351 /* @return rows list */
352 PortGroupList const *
353 PortMatrix::rows () const
355 return &_ports[_row_index];
358 boost::shared_ptr<const PortGroup>
359 PortMatrix::visible_rows () const
361 return visible_ports (_row_index);
365 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
367 using namespace Menu_Helpers;
372 _menu->set_name ("ArdourContextMenu");
374 MenuList& items = _menu->items ();
377 bc[_column_index] = column;
378 bc[_row_index] = row;
381 bool need_separator = false;
383 for (int dim = 0; dim < 2; ++dim) {
385 if (bc[dim].bundle) {
387 Menu* m = manage (new Menu);
388 MenuList& sub = m->items ();
390 boost::weak_ptr<Bundle> w (bc[dim].bundle);
392 bool can_add_or_rename = false;
394 if (can_add_channel (bc[dim].bundle)) {
395 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
396 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
397 can_add_or_rename = true;
401 if (can_rename_channels (bc[dim].bundle)) {
402 snprintf (buf, sizeof (buf), _("Rename '%s'..."), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
406 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
409 can_add_or_rename = true;
412 if (can_add_or_rename) {
413 sub.push_back (SeparatorElem ());
416 if (can_remove_channels (bc[dim].bundle)) {
417 if (bc[dim].channel != -1) {
418 add_remove_option (sub, w, bc[dim].channel);
420 for (uint32_t i = 0; i < bc[dim].bundle->nchannels(); ++i) {
421 add_remove_option (sub, w, i);
426 if (_show_only_bundles || bc[dim].bundle->nchannels() <= 1) {
427 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
429 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
433 if (bc[dim].channel != -1) {
434 add_disassociate_option (sub, w, dim, bc[dim].channel);
436 for (uint32_t i = 0; i < bc[dim].bundle->nchannels(); ++i) {
437 add_disassociate_option (sub, w, dim, i);
442 items.push_back (MenuElem (bc[dim].bundle->name().c_str(), *m));
443 need_separator = true;
448 if (need_separator) {
449 items.push_back (SeparatorElem ());
452 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
453 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
454 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
455 _inhibit_toggle_show_only_bundles = true;
456 i->set_active (!_show_only_bundles);
457 _inhibit_toggle_show_only_bundles = false;
463 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
465 boost::shared_ptr<Bundle> sb = b.lock ();
470 remove_channel (BundleChannel (sb, c));
475 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
477 boost::shared_ptr<Bundle> sb = b.lock ();
482 rename_channel (BundleChannel (sb, c));
486 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
488 boost::shared_ptr<Bundle> sb = bundle.lock ();
493 PortGroup::BundleList a = _ports[1-dim].bundles ();
495 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
496 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
499 c[dim] = BundleChannel (sb, channel);
500 c[1-dim] = BundleChannel (i->bundle, j);
502 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
503 set_state (c, false);
508 _body->rebuild_and_draw_grid ();
512 PortMatrix::setup_global_ports ()
514 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
516 for (int i = 0; i < 2; ++i) {
517 if (list_is_global (i)) {
524 PortMatrix::setup_all_ports ()
526 if (_session->deletion_in_progress()) {
530 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
537 PortMatrix::toggle_show_only_bundles ()
539 if (_inhibit_toggle_show_only_bundles) {
543 _show_only_bundles = !_show_only_bundles;
548 pair<uint32_t, uint32_t>
549 PortMatrix::max_size () const
551 pair<uint32_t, uint32_t> m = _body->max_size ();
553 m.first += _vscroll.get_width ();
554 m.second += _hscroll.get_height ();
560 PortMatrix::on_scroll_event (GdkEventScroll* ev)
562 double const h = _hscroll.get_value ();
563 double const v = _vscroll.get_value ();
565 switch (ev->direction) {
567 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
569 case GDK_SCROLL_DOWN:
570 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
572 case GDK_SCROLL_LEFT:
573 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
575 case GDK_SCROLL_RIGHT:
576 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
583 boost::shared_ptr<IO>
584 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
586 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
588 io = _ports[1].io_from_bundle (b);
595 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
597 return io_from_bundle (b);
601 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
603 boost::shared_ptr<IO> io = io_from_bundle (b);
606 io->add_port ("", this, _type);
611 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
613 return io_from_bundle (b);
617 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
619 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
622 Port* p = io->nth (b.channel);
624 io->remove_port (p, this);
630 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
632 boost::shared_ptr<Bundle> b = w.lock ();
641 PortMatrix::setup_notebooks ()
643 int const h_current_page = _hnotebook.get_current_page ();
644 int const v_current_page = _vnotebook.get_current_page ();
646 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
647 when adding or removing pages to or from notebooks, so ignore them */
649 _ignore_notebook_page_selected = true;
651 remove_notebook_pages (_hnotebook);
652 remove_notebook_pages (_vnotebook);
654 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
655 HBox* dummy = manage (new HBox);
657 Label* label = manage (new Label ((*i)->name));
658 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
660 _vnotebook.prepend_page (*dummy, *label);
663 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
664 HBox* dummy = manage (new HBox);
666 _hnotebook.append_page (*dummy, (*i)->name);
669 _ignore_notebook_page_selected = false;
671 _vnotebook.set_tab_pos (POS_LEFT);
672 _hnotebook.set_tab_pos (POS_TOP);
674 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
675 _hnotebook.set_current_page (h_current_page);
677 _hnotebook.set_current_page (0);
680 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
681 _vnotebook.set_current_page (v_current_page);
683 _vnotebook.set_current_page (0);
686 if (_hnotebook.get_n_pages() <= 1) {
692 if (_vnotebook.get_n_pages() <= 1) {
700 PortMatrix::remove_notebook_pages (Notebook& n)
702 int const N = n.get_n_pages ();
704 for (int i = 0; i < N; ++i) {
710 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
712 if (_ignore_notebook_page_selected) {
722 PortMatrix::session_going_away ()
728 PortMatrix::body_dimensions_changed ()
730 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
731 if (_arrangement == TOP_TO_RIGHT) {
732 _vspacer.set_size_request (-1, _body->column_labels_height ());
741 boost::shared_ptr<const PortGroup>
742 PortMatrix::visible_ports (int d) const
744 PortGroupList const & p = _ports[d];
745 PortGroupList::List::const_iterator j = p.begin ();
748 if (d == _row_index) {
749 n = p.size() - _vnotebook.get_current_page () - 1;
751 n = _hnotebook.get_current_page ();
755 while (i != int (n) && j != p.end ()) {
761 return boost::shared_ptr<const PortGroup> ();
768 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
770 using namespace Menu_Helpers;
772 boost::shared_ptr<Bundle> b = w.lock ();
778 snprintf (buf, sizeof (buf), _("Remove '%s'"), b->channel_name (c).c_str());
779 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
783 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
785 using namespace Menu_Helpers;
787 boost::shared_ptr<Bundle> b = w.lock ();
793 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), b->channel_name (c).c_str());
794 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));