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)
61 _body = new PortMatrixBody (this);
63 for (int i = 0; i < 2; ++i) {
64 _ports[i].set_type (type);
66 /* watch for the content of _ports[] changing */
67 _ports[i].Changed.connect (mem_fun (*this, &PortMatrix::setup));
70 _hscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::hscroll_changed));
71 _vscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::vscroll_changed));
73 /* watch for routes being added or removed */
74 _session.RouteAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::routes_changed)));
76 /* and also bundles */
77 _session.BundleAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::setup_global_ports)));
80 _session.engine().PortRegisteredOrUnregistered.connect (mem_fun (*this, &PortMatrix::setup_all_ports));
82 reconnect_to_routes ();
84 attach (*_body, 0, 1, 0, 1);
85 attach (_vscroll, 1, 2, 0, 1, SHRINK);
86 attach (_hscroll, 0, 1, 1, 2, FILL | EXPAND, SHRINK);
91 PortMatrix::~PortMatrix ()
97 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
99 PortMatrix::reconnect_to_routes ()
101 for (vector<connection>::iterator i = _route_connections.begin(); i != _route_connections.end(); ++i) {
104 _route_connections.clear ();
106 boost::shared_ptr<RouteList> routes = _session.get_routes ();
107 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
108 _route_connections.push_back (
109 (*i)->processors_changed.connect (mem_fun (*this, &PortMatrix::setup_global_ports))
114 /** A route has been added to or removed from the session */
116 PortMatrix::routes_changed ()
118 reconnect_to_routes ();
119 setup_global_ports ();
122 /** Set up everything that depends on the content of _ports[] */
126 if ((get_flags () & Gtk::REALIZED) == 0) {
127 select_arrangement ();
138 PortMatrix::set_type (DataType t)
141 _ports[0].set_type (_type);
142 _ports[1].set_type (_type);
148 PortMatrix::hscroll_changed ()
150 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
154 PortMatrix::vscroll_changed ()
156 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
160 PortMatrix::setup_scrollbars ()
162 Adjustment* a = _hscroll.get_adjustment ();
164 a->set_upper (_body->full_scroll_width());
165 a->set_page_size (_body->alloc_scroll_width());
166 a->set_step_increment (32);
167 a->set_page_increment (128);
169 a = _vscroll.get_adjustment ();
171 a->set_upper (_body->full_scroll_height());
172 a->set_page_size (_body->alloc_scroll_height());
173 a->set_step_increment (32);
174 a->set_page_increment (128);
177 /** Disassociate all of our ports from each other */
179 PortMatrix::disassociate_all ()
181 PortGroup::BundleList a = _ports[0].bundles ();
182 PortGroup::BundleList b = _ports[1].bundles ();
184 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
185 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
186 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
187 for (uint32_t l = 0; l < k->bundle->nchannels(); ++l) {
189 BundleChannel c[2] = {
190 BundleChannel (i->bundle, j),
191 BundleChannel (k->bundle, l)
194 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
195 set_state (c, false);
203 _body->rebuild_and_draw_grid ();
206 /* Decide how to arrange the components of the matrix */
208 PortMatrix::select_arrangement ()
210 uint32_t const N[2] = {
211 _ports[0].total_visible_channels (),
212 _ports[1].total_visible_channels ()
215 /* The list with the most channels goes on left or right, so that the most channel
216 names are printed horizontally and hence more readable. However we also
217 maintain notional `signal flow' vaguely from left to right. Subclasses
218 should choose where to put ports based on signal flowing from _ports[0]
225 _arrangement = LEFT_TO_BOTTOM;
231 _arrangement = TOP_TO_RIGHT;
235 /** @return columns list */
236 PortGroupList const *
237 PortMatrix::columns () const
239 return &_ports[_column_index];
242 /* @return rows list */
243 PortGroupList const *
244 PortMatrix::rows () const
246 return &_ports[_row_index];
250 PortMatrix::popup_menu (
251 pair<boost::shared_ptr<PortGroup>, BundleChannel> column,
252 pair<boost::shared_ptr<PortGroup>, BundleChannel> row,
256 using namespace Menu_Helpers;
261 _menu->set_name ("ArdourContextMenu");
263 MenuList& items = _menu->items ();
265 boost::shared_ptr<PortGroup> pg[2];
266 pg[_column_index] = column.first;
267 pg[_row_index] = row.first;
270 bc[_column_index] = column.second;
271 bc[_row_index] = row.second;
274 bool need_separator = false;
276 for (int dim = 0; dim < 2; ++dim) {
278 if (bc[dim].bundle) {
280 Menu* m = manage (new Menu);
281 MenuList& sub = m->items ();
283 boost::weak_ptr<Bundle> w (bc[dim].bundle);
285 if (can_add_channel (bc[dim].bundle)) {
286 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
287 sub.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
291 if (can_rename_channels (bc[dim].bundle)) {
292 snprintf (buf, sizeof (buf), _("Rename '%s'..."), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
296 bind (mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
301 sub.push_back (SeparatorElem ());
303 if (can_remove_channels (bc[dim].bundle)) {
304 snprintf (buf, sizeof (buf), _("Remove '%s'"), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
308 bind (mem_fun (*this, &PortMatrix::remove_channel_proxy), w, bc[dim].channel)
313 if (_show_only_bundles) {
314 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
317 buf, sizeof (buf), _("%s all from '%s'"),
318 disassociation_verb().c_str(),
319 bc[dim].bundle->channel_name (bc[dim].channel).c_str()
324 MenuElem (buf, bind (mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
327 items.push_back (MenuElem (bc[dim].bundle->name().c_str(), *m));
328 need_separator = true;
333 if (need_separator) {
334 items.push_back (SeparatorElem ());
337 need_separator = false;
339 for (int dim = 0; dim < 2; ++dim) {
343 boost::weak_ptr<PortGroup> wp (pg[dim]);
345 if (pg[dim]->visible()) {
347 if (pg[dim]->name.empty()) {
348 snprintf (buf, sizeof (buf), _("Hide sources"));
350 snprintf (buf, sizeof (buf), _("Hide '%s' sources"), pg[dim]->name.c_str());
353 if (pg[dim]->name.empty()) {
354 snprintf (buf, sizeof (buf), _("Hide destinations"));
356 snprintf (buf, sizeof (buf), _("Hide '%s' destinations"), pg[dim]->name.c_str());
360 items.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::hide_group), wp)));
363 if (pg[dim]->name.empty()) {
364 snprintf (buf, sizeof (buf), _("Show sources"));
366 snprintf (buf, sizeof (buf), _("Show '%s' sources"), pg[dim]->name.c_str());
369 if (pg[dim]->name.empty()) {
370 snprintf (buf, sizeof (buf), _("Show destinations"));
372 snprintf (buf, sizeof (buf), _("Show '%s' destinations"), pg[dim]->name.c_str());
375 items.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::show_group), wp)));
378 need_separator = true;
382 if (need_separator) {
383 items.push_back (SeparatorElem ());
386 items.push_back (MenuElem (_("Rescan"), mem_fun (*this, &PortMatrix::setup_all_ports)));
387 items.push_back (CheckMenuElem (_("Show individual ports"), mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
388 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
389 _inhibit_toggle_show_only_bundles = true;
390 i->set_active (!_show_only_bundles);
391 _inhibit_toggle_show_only_bundles = false;
397 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
399 boost::shared_ptr<Bundle> sb = b.lock ();
404 remove_channel (BundleChannel (sb, c));
409 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
411 boost::shared_ptr<Bundle> sb = b.lock ();
416 rename_channel (BundleChannel (sb, c));
420 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
422 boost::shared_ptr<Bundle> sb = bundle.lock ();
427 PortGroup::BundleList a = _ports[1-dim].bundles ();
429 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
430 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
433 c[dim] = BundleChannel (sb, channel);
434 c[1-dim] = BundleChannel (i->bundle, j);
436 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
437 set_state (c, false);
442 _body->rebuild_and_draw_grid ();
446 PortMatrix::setup_global_ports ()
448 for (int i = 0; i < 2; ++i) {
449 if (list_is_global (i)) {
456 PortMatrix::setup_all_ports ()
458 ENSURE_GUI_THREAD (mem_fun (*this, &PortMatrix::setup_all_ports));
465 PortMatrix::toggle_show_only_bundles ()
467 if (_inhibit_toggle_show_only_bundles) {
471 _show_only_bundles = !_show_only_bundles;
478 PortMatrix::hide_group (boost::weak_ptr<PortGroup> w)
480 boost::shared_ptr<PortGroup> g = w.lock ();
485 g->set_visible (false);
489 PortMatrix::show_group (boost::weak_ptr<PortGroup> w)
491 boost::shared_ptr<PortGroup> g = w.lock ();
496 g->set_visible (true);
499 pair<uint32_t, uint32_t>
500 PortMatrix::max_size () const
502 pair<uint32_t, uint32_t> m = _body->max_size ();
504 m.first += _vscroll.get_width ();
505 m.second += _hscroll.get_height ();
511 PortMatrix::on_scroll_event (GdkEventScroll* ev)
513 double const h = _hscroll.get_value ();
514 double const v = _vscroll.get_value ();
516 switch (ev->direction) {
518 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
520 case GDK_SCROLL_DOWN:
521 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
523 case GDK_SCROLL_LEFT:
524 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
526 case GDK_SCROLL_RIGHT:
527 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
534 boost::shared_ptr<IO>
535 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
537 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
539 io = _ports[1].io_from_bundle (b);
546 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
548 return io_from_bundle (b);
552 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
554 boost::shared_ptr<IO> io = io_from_bundle (b);
557 io->add_port ("", this, _type);
562 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
564 return io_from_bundle (b);
568 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
570 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
573 Port* p = io->nth (b.channel);
575 io->remove_port (p, this);
581 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
583 boost::shared_ptr<Bundle> b = w.lock ();