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);
64 _vbox.pack_start (_vnotebook);
65 _vbox.pack_start (_vlabel);
66 _hbox.pack_start (_hnotebook);
67 _hbox.pack_start (_hlabel);
69 _vnotebook.signal_switch_page().connect (mem_fun (*this, &PortMatrix::v_page_selected));
70 _vnotebook.property_tab_border() = 4;
71 _hnotebook.signal_switch_page().connect (mem_fun (*this, &PortMatrix::h_page_selected));
72 _hnotebook.property_tab_border() = 4;
74 for (int i = 0; i < 2; ++i) {
75 _ports[i].set_type (type);
78 _vlabel.set_use_markup ();
79 _vlabel.set_alignment (0.5, 0);
80 _vlabel.set_padding (4, 16);
81 _hlabel.set_use_markup ();
82 _hlabel.set_alignment (0, 0.5);
83 _hlabel.set_padding (16, 4);
92 PortMatrix::~PortMatrix ()
101 select_arrangement ();
104 if (!_ports[0].empty()) {
105 _visible_ports[0] = *_ports[0].begin();
108 if (!_ports[1].empty()) {
109 _visible_ports[1] = *_ports[1].begin();
112 for (int i = 0; i < 2; ++i) {
113 /* watch for the content of _ports[] changing */
114 _ports[i].Changed.connect (mem_fun (*this, &PortMatrix::setup));
116 /* and for bundles in _ports[] changing */
117 _ports[i].BundleChanged.connect (mem_fun (*this, &PortMatrix::bundle_changed));
120 _hscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::hscroll_changed));
121 _vscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::vscroll_changed));
123 /* watch for routes being added or removed */
124 _session.RouteAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::routes_changed)));
126 /* and also bundles */
127 _session.BundleAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::setup_global_ports)));
130 _session.engine().PortRegisteredOrUnregistered.connect (mem_fun (*this, &PortMatrix::setup_all_ports));
132 reconnect_to_routes ();
137 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
139 PortMatrix::reconnect_to_routes ()
141 for (vector<connection>::iterator i = _route_connections.begin(); i != _route_connections.end(); ++i) {
144 _route_connections.clear ();
146 boost::shared_ptr<RouteList> routes = _session.get_routes ();
147 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
148 _route_connections.push_back (
149 (*i)->processors_changed.connect (mem_fun (*this, &PortMatrix::route_processors_changed))
155 PortMatrix::route_processors_changed (RouteProcessorChange c)
157 if (c.type == RouteProcessorChange::MeterPointChange) {
158 /* this change has no impact on the port matrix */
162 setup_global_ports ();
165 /** A route has been added to or removed from the session */
167 PortMatrix::routes_changed ()
169 reconnect_to_routes ();
170 setup_global_ports ();
173 /** Set up everything that depends on the content of _ports[] */
184 PortMatrix::set_type (DataType t)
187 _ports[0].set_type (_type);
188 _ports[1].set_type (_type);
194 PortMatrix::hscroll_changed ()
196 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
200 PortMatrix::vscroll_changed ()
202 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
206 PortMatrix::setup_scrollbars ()
208 Adjustment* a = _hscroll.get_adjustment ();
210 a->set_upper (_body->full_scroll_width());
211 a->set_page_size (_body->alloc_scroll_width());
212 a->set_step_increment (32);
213 a->set_page_increment (128);
215 a = _vscroll.get_adjustment ();
217 a->set_upper (_body->full_scroll_height());
218 a->set_page_size (_body->alloc_scroll_height());
219 a->set_step_increment (32);
220 a->set_page_increment (128);
223 /** Disassociate all of our ports from each other */
225 PortMatrix::disassociate_all ()
227 PortGroup::BundleList a = _ports[0].bundles ();
228 PortGroup::BundleList b = _ports[1].bundles ();
230 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
231 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
232 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
233 for (uint32_t l = 0; l < k->bundle->nchannels(); ++l) {
235 BundleChannel c[2] = {
236 BundleChannel (i->bundle, j),
237 BundleChannel (k->bundle, l)
240 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
241 set_state (c, false);
249 _body->rebuild_and_draw_grid ();
252 /* Decide how to arrange the components of the matrix */
254 PortMatrix::select_arrangement ()
256 uint32_t const N[2] = {
257 _ports[0].total_channels (),
258 _ports[1].total_channels ()
261 /* The list with the most channels goes on left or right, so that the most channel
262 names are printed horizontally and hence more readable. However we also
263 maintain notional `signal flow' vaguely from left to right. Subclasses
264 should choose where to put ports based on signal flowing from _ports[0]
271 _arrangement = LEFT_TO_BOTTOM;
272 _vlabel.set_label (_("<b>Sources</b>"));
273 _hlabel.set_label (_("<b>Destinations</b>"));
274 _vlabel.set_angle (90);
276 attach (*_body, 1, 2, 0, 1);
277 attach (_vscroll, 2, 3, 0, 1, SHRINK);
278 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
279 attach (_vbox, 0, 1, 0, 1, SHRINK);
280 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
282 set_col_spacing (0, 4);
283 set_row_spacing (0, 4);
289 _arrangement = TOP_TO_RIGHT;
290 _hlabel.set_label (_("<b>Sources</b>"));
291 _vlabel.set_label (_("<b>Destinations</b>"));
292 _vlabel.set_angle (-90);
294 attach (*_body, 0, 1, 1, 2);
295 attach (_vscroll, 2, 3, 1, 2, SHRINK);
296 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
297 attach (_vbox, 1, 2, 1, 2, SHRINK);
298 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
300 set_col_spacing (1, 4);
301 set_row_spacing (1, 4);
305 /** @return columns list */
306 PortGroupList const *
307 PortMatrix::columns () const
309 return &_ports[_column_index];
312 boost::shared_ptr<PortGroup>
313 PortMatrix::visible_columns () const
315 return _visible_ports[_column_index];
318 /* @return rows list */
319 PortGroupList const *
320 PortMatrix::rows () const
322 return &_ports[_row_index];
325 boost::shared_ptr<PortGroup>
326 PortMatrix::visible_rows () const
328 return _visible_ports[_row_index];
332 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
334 using namespace Menu_Helpers;
339 _menu->set_name ("ArdourContextMenu");
341 MenuList& items = _menu->items ();
344 bc[_column_index] = column;
345 bc[_row_index] = row;
348 bool need_separator = false;
350 for (int dim = 0; dim < 2; ++dim) {
352 if (bc[dim].bundle) {
354 Menu* m = manage (new Menu);
355 MenuList& sub = m->items ();
357 boost::weak_ptr<Bundle> w (bc[dim].bundle);
359 bool can_add_or_rename = false;
361 if (can_add_channel (bc[dim].bundle)) {
362 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
363 sub.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
364 can_add_or_rename = true;
368 if (can_rename_channels (bc[dim].bundle)) {
369 snprintf (buf, sizeof (buf), _("Rename '%s'..."), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
373 bind (mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
376 can_add_or_rename = true;
379 if (can_add_or_rename) {
380 sub.push_back (SeparatorElem ());
383 if (can_remove_channels (bc[dim].bundle)) {
384 snprintf (buf, sizeof (buf), _("Remove '%s'"), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
388 bind (mem_fun (*this, &PortMatrix::remove_channel_proxy), w, bc[dim].channel)
393 if (_show_only_bundles || bc[dim].bundle->nchannels() <= 1) {
394 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
397 buf, sizeof (buf), _("%s all from '%s'"),
398 disassociation_verb().c_str(),
399 bc[dim].bundle->channel_name (bc[dim].channel).c_str()
404 MenuElem (buf, bind (mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
407 items.push_back (MenuElem (bc[dim].bundle->name().c_str(), *m));
408 need_separator = true;
413 if (need_separator) {
414 items.push_back (SeparatorElem ());
417 items.push_back (MenuElem (_("Rescan"), mem_fun (*this, &PortMatrix::setup_all_ports)));
418 items.push_back (CheckMenuElem (_("Show individual ports"), mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
419 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
420 _inhibit_toggle_show_only_bundles = true;
421 i->set_active (!_show_only_bundles);
422 _inhibit_toggle_show_only_bundles = false;
428 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
430 boost::shared_ptr<Bundle> sb = b.lock ();
435 remove_channel (BundleChannel (sb, c));
440 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
442 boost::shared_ptr<Bundle> sb = b.lock ();
447 rename_channel (BundleChannel (sb, c));
451 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
453 boost::shared_ptr<Bundle> sb = bundle.lock ();
458 PortGroup::BundleList a = _ports[1-dim].bundles ();
460 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
461 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
464 c[dim] = BundleChannel (sb, channel);
465 c[1-dim] = BundleChannel (i->bundle, j);
467 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
468 set_state (c, false);
473 _body->rebuild_and_draw_grid ();
477 PortMatrix::setup_global_ports ()
479 for (int i = 0; i < 2; ++i) {
480 if (list_is_global (i)) {
487 PortMatrix::setup_all_ports ()
489 if (_session.deletion_in_progress()) {
493 ENSURE_GUI_THREAD (mem_fun (*this, &PortMatrix::setup_all_ports));
500 PortMatrix::toggle_show_only_bundles ()
502 if (_inhibit_toggle_show_only_bundles) {
506 _show_only_bundles = !_show_only_bundles;
512 pair<uint32_t, uint32_t>
513 PortMatrix::max_size () const
515 pair<uint32_t, uint32_t> m = _body->max_size ();
517 m.first += _vscroll.get_width ();
518 m.second += _hscroll.get_height ();
524 PortMatrix::on_scroll_event (GdkEventScroll* ev)
526 double const h = _hscroll.get_value ();
527 double const v = _vscroll.get_value ();
529 switch (ev->direction) {
531 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
533 case GDK_SCROLL_DOWN:
534 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
536 case GDK_SCROLL_LEFT:
537 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
539 case GDK_SCROLL_RIGHT:
540 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
547 boost::shared_ptr<IO>
548 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
550 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
552 io = _ports[1].io_from_bundle (b);
559 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
561 return io_from_bundle (b);
565 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
567 boost::shared_ptr<IO> io = io_from_bundle (b);
570 io->add_port ("", this, _type);
575 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
577 return io_from_bundle (b);
581 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
583 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
586 Port* p = io->nth (b.channel);
588 io->remove_port (p, this);
594 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
596 boost::shared_ptr<Bundle> b = w.lock ();
605 PortMatrix::bundle_changed (ARDOUR::Bundle::Change c)
607 if (c != Bundle::NameChanged) {
615 PortMatrix::setup_notebooks ()
617 int const h_current_page = _hnotebook.get_current_page ();
618 int const v_current_page = _vnotebook.get_current_page ();
620 remove_notebook_pages (_hnotebook);
621 remove_notebook_pages (_vnotebook);
623 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
624 when adding pages to notebooks, so ignore them */
626 _ignore_notebook_page_selected = true;
628 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
629 HBox* dummy = manage (new HBox);
631 Label* label = manage (new Label ((*i)->name));
632 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
633 _vnotebook.prepend_page (*dummy, *label);
636 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
637 HBox* dummy = manage (new HBox);
639 _hnotebook.append_page (*dummy, (*i)->name);
642 _ignore_notebook_page_selected = false;
644 _vnotebook.set_tab_pos (POS_LEFT);
645 _hnotebook.set_tab_pos (POS_TOP);
647 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
648 _hnotebook.set_current_page (h_current_page);
650 _hnotebook.set_current_page (0);
653 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
654 _vnotebook.set_current_page (v_current_page);
656 _vnotebook.set_current_page (0);
659 if (_hnotebook.get_n_pages() <= 1) {
665 if (_vnotebook.get_n_pages() <= 1) {
673 PortMatrix::remove_notebook_pages (Notebook& n)
675 int const N = n.get_n_pages ();
677 for (int i = 0; i < N; ++i) {
683 PortMatrix::v_page_selected (GtkNotebookPage *, guint n)
685 if (_ignore_notebook_page_selected) {
689 PortGroupList& p = _ports[_row_index];
691 n = p.size() - n - 1;
694 PortGroupList::List::const_iterator j = p.begin();
695 while (i != int (n) && j != p.end()) {
701 _visible_ports[_row_index] = *j;
709 PortMatrix::h_page_selected (GtkNotebookPage *, guint n)
711 if (_ignore_notebook_page_selected) {
715 PortGroupList& p = _ports[_column_index];
718 PortGroupList::List::const_iterator j = p.begin();
719 while (i != int (n) && j != p.end()) {
725 _visible_ports[_column_index] = *j;