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)
54 _arrangement (TOP_TO_RIGHT),
57 _min_height_divisor (1),
58 _show_only_bundles (false),
59 _inhibit_toggle_show_only_bundles (false),
60 _ignore_notebook_page_selected (false)
62 _body = new PortMatrixBody (this);
63 _body->DimensionsChanged.connect (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 (mem_fun (*this, &PortMatrix::v_page_selected));
73 _vnotebook.property_tab_border() = 4;
74 _vnotebook.set_name (X_("PortMatrixLabel"));
75 _hnotebook.signal_switch_page().connect (mem_fun (*this, &PortMatrix::h_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"));
100 PortMatrix::~PortMatrix ()
106 /** Perform initial and once-only setup. This must be called by
107 * subclasses after they have set up _ports[] to at least some
108 * reasonable extent. Two-part initialisation is necessary because
109 * setting up _ports is largely done by virtual functions in
116 select_arrangement ();
118 if (!_ports[0].empty()) {
119 _visible_ports[0] = *_ports[0].begin();
122 if (!_ports[1].empty()) {
123 _visible_ports[1] = *_ports[1].begin();
126 /* Signal handling is kind of split into two parts:
128 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
129 * representation of the information in _ports[].
131 * 2. When certain other things change, we need to get our subclass to clear and
132 * re-fill _ports[], which in turn causes appropriate signals to be raised to
133 * hook into part (1).
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 (mem_fun (*this, &PortMatrix::setup));
143 /* and for bundles in _ports[] changing */
144 _ports[i].BundleChanged.connect (sigc::hide (mem_fun (*this, &PortMatrix::setup)));
147 /* scrolling stuff */
148 _hscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::hscroll_changed));
149 _vscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::vscroll_changed));
152 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
154 /* watch for routes being added or removed */
155 _session->RouteAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::routes_changed)));
157 /* and also bundles */
158 _session->BundleAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::setup_global_ports)));
161 _session->engine().PortRegisteredOrUnregistered.connect (mem_fun (*this, &PortMatrix::setup_global_ports));
163 _session->GoingAway.connect (mem_fun (*this, &PortMatrix::session_going_away));
165 reconnect_to_routes ();
170 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
172 PortMatrix::reconnect_to_routes ()
174 for (vector<connection>::iterator i = _route_connections.begin(); i != _route_connections.end(); ++i) {
177 _route_connections.clear ();
179 boost::shared_ptr<RouteList> routes = _session->get_routes ();
180 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
181 _route_connections.push_back (
182 (*i)->processors_changed.connect (mem_fun (*this, &PortMatrix::route_processors_changed))
188 PortMatrix::route_processors_changed (RouteProcessorChange c)
190 if (c.type == RouteProcessorChange::MeterPointChange) {
191 /* this change has no impact on the port matrix */
195 setup_global_ports ();
198 /** A route has been added to or removed from the session */
200 PortMatrix::routes_changed ()
202 reconnect_to_routes ();
203 setup_global_ports ();
206 /** Set up everything that depends on the content of _ports[] */
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<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<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, bind (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 bind (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 snprintf (buf, sizeof (buf), _("Remove '%s'"), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
421 bind (mem_fun (*this, &PortMatrix::remove_channel_proxy), w, bc[dim].channel)
426 if (_show_only_bundles || bc[dim].bundle->nchannels() <= 1) {
427 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
430 buf, sizeof (buf), _("%s all from '%s'"),
431 disassociation_verb().c_str(),
432 bc[dim].bundle->channel_name (bc[dim].channel).c_str()
437 MenuElem (buf, bind (mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
440 items.push_back (MenuElem (bc[dim].bundle->name().c_str(), *m));
441 need_separator = true;
446 if (need_separator) {
447 items.push_back (SeparatorElem ());
450 items.push_back (MenuElem (_("Rescan"), mem_fun (*this, &PortMatrix::setup_all_ports)));
451 items.push_back (CheckMenuElem (_("Show individual ports"), mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
452 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
453 _inhibit_toggle_show_only_bundles = true;
454 i->set_active (!_show_only_bundles);
455 _inhibit_toggle_show_only_bundles = false;
461 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
463 boost::shared_ptr<Bundle> sb = b.lock ();
468 remove_channel (BundleChannel (sb, c));
473 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
475 boost::shared_ptr<Bundle> sb = b.lock ();
480 rename_channel (BundleChannel (sb, c));
484 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
486 boost::shared_ptr<Bundle> sb = bundle.lock ();
491 PortGroup::BundleList a = _ports[1-dim].bundles ();
493 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
494 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
497 c[dim] = BundleChannel (sb, channel);
498 c[1-dim] = BundleChannel (i->bundle, j);
500 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
501 set_state (c, false);
506 _body->rebuild_and_draw_grid ();
510 PortMatrix::setup_global_ports ()
512 ENSURE_GUI_THREAD (mem_fun (*this, &PortMatrix::setup_global_ports));
514 for (int i = 0; i < 2; ++i) {
515 if (list_is_global (i)) {
522 PortMatrix::setup_all_ports ()
524 if (_session->deletion_in_progress()) {
528 ENSURE_GUI_THREAD (mem_fun (*this, &PortMatrix::setup_all_ports));
535 PortMatrix::toggle_show_only_bundles ()
537 if (_inhibit_toggle_show_only_bundles) {
541 _show_only_bundles = !_show_only_bundles;
546 pair<uint32_t, uint32_t>
547 PortMatrix::max_size () const
549 pair<uint32_t, uint32_t> m = _body->max_size ();
551 m.first += _vscroll.get_width ();
552 m.second += _hscroll.get_height ();
558 PortMatrix::on_scroll_event (GdkEventScroll* ev)
560 double const h = _hscroll.get_value ();
561 double const v = _vscroll.get_value ();
563 switch (ev->direction) {
565 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
567 case GDK_SCROLL_DOWN:
568 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
570 case GDK_SCROLL_LEFT:
571 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
573 case GDK_SCROLL_RIGHT:
574 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
581 boost::shared_ptr<IO>
582 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
584 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
586 io = _ports[1].io_from_bundle (b);
593 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
595 return io_from_bundle (b);
599 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
601 boost::shared_ptr<IO> io = io_from_bundle (b);
604 io->add_port ("", this, _type);
609 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
611 return io_from_bundle (b);
615 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
617 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
620 Port* p = io->nth (b.channel);
622 io->remove_port (p, this);
628 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
630 boost::shared_ptr<Bundle> b = w.lock ();
639 PortMatrix::setup_notebooks ()
641 int const h_current_page = _hnotebook.get_current_page ();
642 int const v_current_page = _vnotebook.get_current_page ();
644 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
645 when adding or removing pages to or from notebooks, so ignore them */
647 _ignore_notebook_page_selected = true;
649 remove_notebook_pages (_hnotebook);
650 remove_notebook_pages (_vnotebook);
652 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
653 HBox* dummy = manage (new HBox);
655 Label* label = manage (new Label ((*i)->name));
656 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
657 _vnotebook.prepend_page (*dummy, *label);
660 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
661 HBox* dummy = manage (new HBox);
663 _hnotebook.append_page (*dummy, (*i)->name);
666 _ignore_notebook_page_selected = false;
668 _vnotebook.set_tab_pos (POS_LEFT);
669 _hnotebook.set_tab_pos (POS_TOP);
671 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
672 _hnotebook.set_current_page (h_current_page);
674 _hnotebook.set_current_page (0);
677 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
678 _vnotebook.set_current_page (v_current_page);
680 _vnotebook.set_current_page (0);
683 if (_hnotebook.get_n_pages() <= 1) {
689 if (_vnotebook.get_n_pages() <= 1) {
697 PortMatrix::remove_notebook_pages (Notebook& n)
699 int const N = n.get_n_pages ();
701 for (int i = 0; i < N; ++i) {
707 PortMatrix::v_page_selected (GtkNotebookPage *, guint n)
709 if (_ignore_notebook_page_selected) {
713 PortGroupList& p = _ports[_row_index];
715 n = p.size() - n - 1;
718 PortGroupList::List::const_iterator j = p.begin();
719 while (i != int (n) && j != p.end()) {
725 _visible_ports[_row_index] = *j;
733 PortMatrix::h_page_selected (GtkNotebookPage *, guint n)
735 if (_ignore_notebook_page_selected) {
739 PortGroupList& p = _ports[_column_index];
742 PortGroupList::List::const_iterator j = p.begin();
743 while (i != int (n) && j != p.end()) {
749 _visible_ports[_column_index] = *j;
757 PortMatrix::session_going_away ()
763 PortMatrix::body_dimensions_changed ()
765 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
766 if (_arrangement == TOP_TO_RIGHT) {
767 _vspacer.set_size_request (-1, _body->column_labels_height ());