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 _in_setup_notebooks (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);
88 PortMatrix::~PortMatrix ()
97 select_arrangement ();
100 if (!_ports[0].empty()) {
101 _visible_ports[0] = *_ports[0].begin();
104 if (!_ports[1].empty()) {
105 _visible_ports[1] = *_ports[1].begin();
108 for (int i = 0; i < 2; ++i) {
109 /* watch for the content of _ports[] changing */
110 _ports[i].Changed.connect (mem_fun (*this, &PortMatrix::setup));
112 /* and for bundles in _ports[] changing */
113 _ports[i].BundleChanged.connect (mem_fun (*this, &PortMatrix::bundle_changed));
116 _hscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::hscroll_changed));
117 _vscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::vscroll_changed));
119 /* watch for routes being added or removed */
120 _session.RouteAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::routes_changed)));
122 /* and also bundles */
123 _session.BundleAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::setup_global_ports)));
126 _session.engine().PortRegisteredOrUnregistered.connect (mem_fun (*this, &PortMatrix::setup_all_ports));
128 reconnect_to_routes ();
133 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
135 PortMatrix::reconnect_to_routes ()
137 for (vector<connection>::iterator i = _route_connections.begin(); i != _route_connections.end(); ++i) {
140 _route_connections.clear ();
142 boost::shared_ptr<RouteList> routes = _session.get_routes ();
143 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
144 _route_connections.push_back (
145 (*i)->processors_changed.connect (mem_fun (*this, &PortMatrix::route_processors_changed))
151 PortMatrix::route_processors_changed (RouteProcessorChange c)
153 if (c.type == RouteProcessorChange::MeterPointChange) {
154 /* this change has no impact on the port matrix */
158 setup_global_ports ();
161 /** A route has been added to or removed from the session */
163 PortMatrix::routes_changed ()
165 reconnect_to_routes ();
166 setup_global_ports ();
169 /** Set up everything that depends on the content of _ports[] */
182 PortMatrix::set_type (DataType t)
185 _ports[0].set_type (_type);
186 _ports[1].set_type (_type);
192 PortMatrix::hscroll_changed ()
194 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
198 PortMatrix::vscroll_changed ()
200 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
204 PortMatrix::setup_scrollbars ()
206 Adjustment* a = _hscroll.get_adjustment ();
208 a->set_upper (_body->full_scroll_width());
209 a->set_page_size (_body->alloc_scroll_width());
210 a->set_step_increment (32);
211 a->set_page_increment (128);
213 a = _vscroll.get_adjustment ();
215 a->set_upper (_body->full_scroll_height());
216 a->set_page_size (_body->alloc_scroll_height());
217 a->set_step_increment (32);
218 a->set_page_increment (128);
221 /** Disassociate all of our ports from each other */
223 PortMatrix::disassociate_all ()
225 PortGroup::BundleList a = _ports[0].bundles ();
226 PortGroup::BundleList b = _ports[1].bundles ();
228 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
229 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
230 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
231 for (uint32_t l = 0; l < k->bundle->nchannels(); ++l) {
233 BundleChannel c[2] = {
234 BundleChannel (i->bundle, j),
235 BundleChannel (k->bundle, l)
238 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
239 set_state (c, false);
247 _body->rebuild_and_draw_grid ();
250 /* Decide how to arrange the components of the matrix */
252 PortMatrix::select_arrangement ()
254 uint32_t const N[2] = {
255 _ports[0].total_channels (),
256 _ports[1].total_channels ()
259 /* The list with the most channels goes on left or right, so that the most channel
260 names are printed horizontally and hence more readable. However we also
261 maintain notional `signal flow' vaguely from left to right. Subclasses
262 should choose where to put ports based on signal flowing from _ports[0]
269 _arrangement = LEFT_TO_BOTTOM;
270 _vlabel.set_label (_("<b>Sources</b>"));
271 _hlabel.set_label (_("<b>Destinations</b>"));
272 _vlabel.set_angle (90);
274 attach (*_body, 1, 2, 0, 1);
275 attach (_vscroll, 2, 3, 0, 1, SHRINK);
276 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
277 attach (_vbox, 0, 1, 0, 1, SHRINK);
278 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
280 set_col_spacing (0, 4);
281 set_row_spacing (0, 4);
287 _arrangement = TOP_TO_RIGHT;
288 _hlabel.set_label (_("<b>Sources</b>"));
289 _vlabel.set_label (_("<b>Destinations</b>"));
290 _vlabel.set_angle (-90);
292 attach (*_body, 0, 1, 1, 2);
293 attach (_vscroll, 2, 3, 1, 2, SHRINK);
294 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
295 attach (_vbox, 1, 2, 1, 2, SHRINK);
296 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
298 set_col_spacing (1, 4);
299 set_row_spacing (1, 4);
303 /** @return columns list */
304 PortGroupList const *
305 PortMatrix::columns () const
307 return &_ports[_column_index];
310 boost::shared_ptr<PortGroup>
311 PortMatrix::visible_columns () const
313 return _visible_ports[_column_index];
316 /* @return rows list */
317 PortGroupList const *
318 PortMatrix::rows () const
320 return &_ports[_row_index];
323 boost::shared_ptr<PortGroup>
324 PortMatrix::visible_rows () const
326 return _visible_ports[_row_index];
330 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
332 using namespace Menu_Helpers;
337 _menu->set_name ("ArdourContextMenu");
339 MenuList& items = _menu->items ();
342 bc[_column_index] = column;
343 bc[_row_index] = row;
346 bool need_separator = false;
348 for (int dim = 0; dim < 2; ++dim) {
350 if (bc[dim].bundle) {
352 Menu* m = manage (new Menu);
353 MenuList& sub = m->items ();
355 boost::weak_ptr<Bundle> w (bc[dim].bundle);
357 bool can_add_or_rename = false;
359 if (can_add_channel (bc[dim].bundle)) {
360 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
361 sub.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
362 can_add_or_rename = true;
366 if (can_rename_channels (bc[dim].bundle)) {
367 snprintf (buf, sizeof (buf), _("Rename '%s'..."), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
371 bind (mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
374 can_add_or_rename = true;
377 if (can_add_or_rename) {
378 sub.push_back (SeparatorElem ());
381 if (can_remove_channels (bc[dim].bundle)) {
382 snprintf (buf, sizeof (buf), _("Remove '%s'"), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
386 bind (mem_fun (*this, &PortMatrix::remove_channel_proxy), w, bc[dim].channel)
391 if (_show_only_bundles || bc[dim].bundle->nchannels() <= 1) {
392 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
395 buf, sizeof (buf), _("%s all from '%s'"),
396 disassociation_verb().c_str(),
397 bc[dim].bundle->channel_name (bc[dim].channel).c_str()
402 MenuElem (buf, bind (mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
405 items.push_back (MenuElem (bc[dim].bundle->name().c_str(), *m));
406 need_separator = true;
411 if (need_separator) {
412 items.push_back (SeparatorElem ());
415 items.push_back (MenuElem (_("Rescan"), mem_fun (*this, &PortMatrix::setup_all_ports)));
416 items.push_back (CheckMenuElem (_("Show individual ports"), mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
417 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
418 _inhibit_toggle_show_only_bundles = true;
419 i->set_active (!_show_only_bundles);
420 _inhibit_toggle_show_only_bundles = false;
426 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
428 boost::shared_ptr<Bundle> sb = b.lock ();
433 remove_channel (BundleChannel (sb, c));
438 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
440 boost::shared_ptr<Bundle> sb = b.lock ();
445 rename_channel (BundleChannel (sb, c));
449 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
451 boost::shared_ptr<Bundle> sb = bundle.lock ();
456 PortGroup::BundleList a = _ports[1-dim].bundles ();
458 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
459 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
462 c[dim] = BundleChannel (sb, channel);
463 c[1-dim] = BundleChannel (i->bundle, j);
465 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
466 set_state (c, false);
471 _body->rebuild_and_draw_grid ();
475 PortMatrix::setup_global_ports ()
477 for (int i = 0; i < 2; ++i) {
478 if (list_is_global (i)) {
485 PortMatrix::setup_all_ports ()
487 if (_session.deletion_in_progress()) {
491 ENSURE_GUI_THREAD (mem_fun (*this, &PortMatrix::setup_all_ports));
498 PortMatrix::toggle_show_only_bundles ()
500 if (_inhibit_toggle_show_only_bundles) {
504 _show_only_bundles = !_show_only_bundles;
510 pair<uint32_t, uint32_t>
511 PortMatrix::max_size () const
513 pair<uint32_t, uint32_t> m = _body->max_size ();
515 m.first += _vscroll.get_width ();
516 m.second += _hscroll.get_height ();
522 PortMatrix::on_scroll_event (GdkEventScroll* ev)
524 double const h = _hscroll.get_value ();
525 double const v = _vscroll.get_value ();
527 switch (ev->direction) {
529 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
531 case GDK_SCROLL_DOWN:
532 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
534 case GDK_SCROLL_LEFT:
535 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
537 case GDK_SCROLL_RIGHT:
538 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
545 boost::shared_ptr<IO>
546 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
548 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
550 io = _ports[1].io_from_bundle (b);
557 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
559 return io_from_bundle (b);
563 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
565 boost::shared_ptr<IO> io = io_from_bundle (b);
568 io->add_port ("", this, _type);
573 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
575 return io_from_bundle (b);
579 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
581 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
584 Port* p = io->nth (b.channel);
586 io->remove_port (p, this);
592 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
594 boost::shared_ptr<Bundle> b = w.lock ();
603 PortMatrix::bundle_changed (ARDOUR::Bundle::Change c)
605 if (c != Bundle::NameChanged) {
613 PortMatrix::setup_notebooks ()
615 _in_setup_notebooks = true;
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 (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
624 HBox* dummy = manage (new HBox);
626 Label* label = manage (new Label ((*i)->name));
627 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
628 _vnotebook.prepend_page (*dummy, *label);
631 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
632 HBox* dummy = manage (new HBox);
634 _hnotebook.append_page (*dummy, (*i)->name);
637 _vnotebook.set_tab_pos (POS_LEFT);
638 _hnotebook.set_tab_pos (POS_TOP);
640 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
641 _hnotebook.set_current_page (h_current_page);
644 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
645 _vnotebook.set_current_page (v_current_page);
648 _in_setup_notebooks = false;
652 PortMatrix::remove_notebook_pages (Notebook& n)
654 int const N = n.get_n_pages ();
656 for (int i = 0; i < N; ++i) {
662 PortMatrix::v_page_selected (GtkNotebookPage *, guint n)
664 if (_in_setup_notebooks) {
668 PortGroupList& p = _ports[_row_index];
670 n = p.size() - n - 1;
673 PortGroupList::List::const_iterator j = p.begin();
674 while (i != int (n) && j != p.end()) {
680 _visible_ports[_row_index] = *j;
688 PortMatrix::h_page_selected (GtkNotebookPage *, guint n)
690 if (_in_setup_notebooks) {
694 PortGroupList& p = _ports[_column_index];
697 PortGroupList::List::const_iterator j = p.begin();
698 while (i != int (n) && j != p.end()) {
704 _visible_ports[_column_index] = *j;