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::setup_global_ports))
150 /** A route has been added to or removed from the session */
152 PortMatrix::routes_changed ()
154 reconnect_to_routes ();
155 setup_global_ports ();
158 /** Set up everything that depends on the content of _ports[] */
171 PortMatrix::set_type (DataType t)
174 _ports[0].set_type (_type);
175 _ports[1].set_type (_type);
181 PortMatrix::hscroll_changed ()
183 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
187 PortMatrix::vscroll_changed ()
189 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
193 PortMatrix::setup_scrollbars ()
195 Adjustment* a = _hscroll.get_adjustment ();
197 a->set_upper (_body->full_scroll_width());
198 a->set_page_size (_body->alloc_scroll_width());
199 a->set_step_increment (32);
200 a->set_page_increment (128);
202 a = _vscroll.get_adjustment ();
204 a->set_upper (_body->full_scroll_height());
205 a->set_page_size (_body->alloc_scroll_height());
206 a->set_step_increment (32);
207 a->set_page_increment (128);
210 /** Disassociate all of our ports from each other */
212 PortMatrix::disassociate_all ()
214 PortGroup::BundleList a = _ports[0].bundles ();
215 PortGroup::BundleList b = _ports[1].bundles ();
217 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
218 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
219 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
220 for (uint32_t l = 0; l < k->bundle->nchannels(); ++l) {
222 BundleChannel c[2] = {
223 BundleChannel (i->bundle, j),
224 BundleChannel (k->bundle, l)
227 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
228 set_state (c, false);
236 _body->rebuild_and_draw_grid ();
239 /* Decide how to arrange the components of the matrix */
241 PortMatrix::select_arrangement ()
243 uint32_t const N[2] = {
244 _ports[0].total_channels (),
245 _ports[1].total_channels ()
248 /* The list with the most channels goes on left or right, so that the most channel
249 names are printed horizontally and hence more readable. However we also
250 maintain notional `signal flow' vaguely from left to right. Subclasses
251 should choose where to put ports based on signal flowing from _ports[0]
258 _arrangement = LEFT_TO_BOTTOM;
259 _vlabel.set_label (_("<b>Sources</b>"));
260 _hlabel.set_label (_("<b>Destinations</b>"));
261 _vlabel.set_angle (90);
263 attach (*_body, 1, 2, 0, 1);
264 attach (_vscroll, 2, 3, 0, 1, SHRINK);
265 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
266 attach (_vbox, 0, 1, 0, 1, SHRINK);
267 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
269 set_col_spacing (0, 4);
270 set_row_spacing (0, 4);
276 _arrangement = TOP_TO_RIGHT;
277 _hlabel.set_label (_("<b>Sources</b>"));
278 _vlabel.set_label (_("<b>Destinations</b>"));
279 _vlabel.set_angle (-90);
281 attach (*_body, 0, 1, 1, 2);
282 attach (_vscroll, 2, 3, 1, 2, SHRINK);
283 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
284 attach (_vbox, 1, 2, 1, 2, SHRINK);
285 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
287 set_col_spacing (1, 4);
288 set_row_spacing (1, 4);
292 /** @return columns list */
293 PortGroupList const *
294 PortMatrix::columns () const
296 return &_ports[_column_index];
299 boost::shared_ptr<PortGroup>
300 PortMatrix::visible_columns () const
302 return _visible_ports[_column_index];
305 /* @return rows list */
306 PortGroupList const *
307 PortMatrix::rows () const
309 return &_ports[_row_index];
312 boost::shared_ptr<PortGroup>
313 PortMatrix::visible_rows () const
315 return _visible_ports[_row_index];
319 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
321 using namespace Menu_Helpers;
326 _menu->set_name ("ArdourContextMenu");
328 MenuList& items = _menu->items ();
331 bc[_column_index] = column;
332 bc[_row_index] = row;
335 bool need_separator = false;
337 for (int dim = 0; dim < 2; ++dim) {
339 if (bc[dim].bundle) {
341 Menu* m = manage (new Menu);
342 MenuList& sub = m->items ();
344 boost::weak_ptr<Bundle> w (bc[dim].bundle);
346 bool can_add_or_rename = false;
348 if (can_add_channel (bc[dim].bundle)) {
349 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
350 sub.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
351 can_add_or_rename = true;
355 if (can_rename_channels (bc[dim].bundle)) {
356 snprintf (buf, sizeof (buf), _("Rename '%s'..."), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
360 bind (mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
363 can_add_or_rename = true;
366 if (can_add_or_rename) {
367 sub.push_back (SeparatorElem ());
370 if (can_remove_channels (bc[dim].bundle)) {
371 snprintf (buf, sizeof (buf), _("Remove '%s'"), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
375 bind (mem_fun (*this, &PortMatrix::remove_channel_proxy), w, bc[dim].channel)
380 if (_show_only_bundles || bc[dim].bundle->nchannels() <= 1) {
381 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
384 buf, sizeof (buf), _("%s all from '%s'"),
385 disassociation_verb().c_str(),
386 bc[dim].bundle->channel_name (bc[dim].channel).c_str()
391 MenuElem (buf, bind (mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
394 items.push_back (MenuElem (bc[dim].bundle->name().c_str(), *m));
395 need_separator = true;
400 if (need_separator) {
401 items.push_back (SeparatorElem ());
404 items.push_back (MenuElem (_("Rescan"), mem_fun (*this, &PortMatrix::setup_all_ports)));
405 items.push_back (CheckMenuElem (_("Show individual ports"), mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
406 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
407 _inhibit_toggle_show_only_bundles = true;
408 i->set_active (!_show_only_bundles);
409 _inhibit_toggle_show_only_bundles = false;
415 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
417 boost::shared_ptr<Bundle> sb = b.lock ();
422 remove_channel (BundleChannel (sb, c));
427 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
429 boost::shared_ptr<Bundle> sb = b.lock ();
434 rename_channel (BundleChannel (sb, c));
438 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
440 boost::shared_ptr<Bundle> sb = bundle.lock ();
445 PortGroup::BundleList a = _ports[1-dim].bundles ();
447 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
448 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
451 c[dim] = BundleChannel (sb, channel);
452 c[1-dim] = BundleChannel (i->bundle, j);
454 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
455 set_state (c, false);
460 _body->rebuild_and_draw_grid ();
464 PortMatrix::setup_global_ports ()
466 for (int i = 0; i < 2; ++i) {
467 if (list_is_global (i)) {
474 PortMatrix::setup_all_ports ()
476 ENSURE_GUI_THREAD (mem_fun (*this, &PortMatrix::setup_all_ports));
483 PortMatrix::toggle_show_only_bundles ()
485 if (_inhibit_toggle_show_only_bundles) {
489 _show_only_bundles = !_show_only_bundles;
495 pair<uint32_t, uint32_t>
496 PortMatrix::max_size () const
498 pair<uint32_t, uint32_t> m = _body->max_size ();
500 m.first += _vscroll.get_width ();
501 m.second += _hscroll.get_height ();
507 PortMatrix::on_scroll_event (GdkEventScroll* ev)
509 double const h = _hscroll.get_value ();
510 double const v = _vscroll.get_value ();
512 switch (ev->direction) {
514 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
516 case GDK_SCROLL_DOWN:
517 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
519 case GDK_SCROLL_LEFT:
520 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
522 case GDK_SCROLL_RIGHT:
523 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
530 boost::shared_ptr<IO>
531 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
533 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
535 io = _ports[1].io_from_bundle (b);
542 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
544 return io_from_bundle (b);
548 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
550 boost::shared_ptr<IO> io = io_from_bundle (b);
553 io->add_port ("", this, _type);
558 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
560 return io_from_bundle (b);
564 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
566 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
569 Port* p = io->nth (b.channel);
571 io->remove_port (p, this);
577 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
579 boost::shared_ptr<Bundle> b = w.lock ();
588 PortMatrix::bundle_changed (ARDOUR::Bundle::Change c)
590 if (c != Bundle::NameChanged) {
598 PortMatrix::setup_notebooks ()
600 _in_setup_notebooks = true;
602 int const h_current_page = _hnotebook.get_current_page ();
603 int const v_current_page = _vnotebook.get_current_page ();
605 remove_notebook_pages (_hnotebook);
606 remove_notebook_pages (_vnotebook);
608 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
609 HBox* dummy = manage (new HBox);
611 Label* label = manage (new Label ((*i)->name));
612 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
613 _vnotebook.prepend_page (*dummy, *label);
616 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
617 HBox* dummy = manage (new HBox);
619 _hnotebook.append_page (*dummy, (*i)->name);
622 _vnotebook.set_tab_pos (POS_LEFT);
623 _hnotebook.set_tab_pos (POS_TOP);
625 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
626 _hnotebook.set_current_page (h_current_page);
629 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
630 _vnotebook.set_current_page (v_current_page);
633 _in_setup_notebooks = false;
637 PortMatrix::remove_notebook_pages (Notebook& n)
639 int const N = n.get_n_pages ();
641 for (int i = 0; i < N; ++i) {
647 PortMatrix::v_page_selected (GtkNotebookPage *, guint n)
649 if (_in_setup_notebooks) {
653 PortGroupList& p = _ports[_row_index];
655 n = p.size() - n - 1;
658 PortGroupList::List::const_iterator j = p.begin();
659 while (i != int (n) && j != p.end()) {
665 _visible_ports[_row_index] = *j;
673 PortMatrix::h_page_selected (GtkNotebookPage *, guint n)
675 if (_in_setup_notebooks) {
679 PortGroupList& p = _ports[_column_index];
682 PortGroupList::List::const_iterator j = p.begin();
683 while (i != int (n) && j != p.end()) {
689 _visible_ports[_column_index] = *j;