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)
53 , _arrangement (TOP_TO_RIGHT)
56 , _min_height_divisor (1)
57 , _show_only_bundles (false)
58 , _inhibit_toggle_show_only_bundles (false)
59 , _ignore_notebook_page_selected (false)
61 set_session (session);
63 _body = new PortMatrixBody (this);
64 _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
66 _hbox.pack_end (_hspacer, true, true);
67 _hbox.pack_end (_hnotebook, false, false);
68 _hbox.pack_end (_hlabel, false, false);
70 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
71 _vnotebook.property_tab_border() = 4;
72 _vnotebook.set_name (X_("PortMatrixLabel"));
73 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
74 _hnotebook.property_tab_border() = 4;
75 _hnotebook.set_name (X_("PortMatrixLabel"));
77 _vlabel.set_use_markup ();
78 _vlabel.set_alignment (1, 1);
79 _vlabel.set_padding (4, 16);
80 _vlabel.set_name (X_("PortMatrixLabel"));
81 _hlabel.set_use_markup ();
82 _hlabel.set_alignment (1, 0.5);
83 _hlabel.set_padding (16, 4);
84 _hlabel.set_name (X_("PortMatrixLabel"));
86 set_row_spacing (0, 8);
87 set_col_spacing (0, 8);
88 set_row_spacing (2, 8);
89 set_col_spacing (2, 8);
104 PortMatrix::~PortMatrix ()
110 /** Perform initial and once-only setup. This must be called by
111 * subclasses after they have set up _ports[] to at least some
112 * reasonable extent. Two-part initialisation is necessary because
113 * setting up _ports is largely done by virtual functions in
120 select_arrangement ();
122 /* Signal handling is kind of split into three parts:
124 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
125 * representation of the information in _ports[].
127 * 2. When certain other things change, we need to get our subclass to clear and
128 * re-fill _ports[], which in turn causes appropriate signals to be raised to
129 * hook into part (1).
131 * 3. Assorted other signals.
135 /* Part 1: the basic _ports[] change -> reset visuals */
137 for (int i = 0; i < 2; ++i) {
138 /* watch for the content of _ports[] changing */
139 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
141 /* and for bundles in _ports[] changing */
142 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
145 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
147 /* watch for routes being added or removed */
148 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
150 /* and also bundles */
151 _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
154 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
156 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
157 _session->RouteOrderKeyChanged.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
159 /* Part 3: other stuff */
161 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
163 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
164 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
166 reconnect_to_routes ();
171 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
173 PortMatrix::reconnect_to_routes ()
175 _route_connections.drop_connections ();
177 boost::shared_ptr<RouteList> routes = _session->get_routes ();
178 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
179 (*i)->processors_changed.connect (_route_connections, invalidator (*this), ui_bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
184 PortMatrix::route_processors_changed (RouteProcessorChange c)
186 if (c.type == RouteProcessorChange::MeterPointChange) {
187 /* this change has no impact on the port matrix */
191 setup_global_ports ();
194 /** A route has been added to or removed from the session */
196 PortMatrix::routes_changed ()
198 reconnect_to_routes ();
199 setup_global_ports ();
202 /** Set up everything that depends on the content of _ports[] */
206 /* this needs to be done first, as the visible_ports() method uses the
207 notebook state to decide which ports are being shown */
217 PortMatrix::set_type (DataType t)
223 PortMatrix::hscroll_changed ()
225 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
229 PortMatrix::vscroll_changed ()
231 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
235 PortMatrix::setup_scrollbars ()
237 Adjustment* a = _hscroll.get_adjustment ();
239 a->set_upper (_body->full_scroll_width());
240 a->set_page_size (_body->alloc_scroll_width());
241 a->set_step_increment (32);
242 a->set_page_increment (128);
244 a = _vscroll.get_adjustment ();
246 a->set_upper (_body->full_scroll_height());
247 a->set_page_size (_body->alloc_scroll_height());
248 a->set_step_increment (32);
249 a->set_page_increment (128);
252 /** Disassociate all of our ports from each other */
254 PortMatrix::disassociate_all ()
256 PortGroup::BundleList a = _ports[0].bundles ();
257 PortGroup::BundleList b = _ports[1].bundles ();
259 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
260 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
261 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
262 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
264 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(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 count_of_our_type (_ports[0].total_channels()),
291 count_of_our_type (_ports[1].total_channels())
294 /* XXX: shirley there's an easier way than this */
296 if (_vspacer.get_parent()) {
297 _vbox.remove (_vspacer);
300 if (_vnotebook.get_parent()) {
301 _vbox.remove (_vnotebook);
304 if (_vlabel.get_parent()) {
305 _vbox.remove (_vlabel);
308 /* The list with the most channels goes on left or right, so that the most channel
309 names are printed horizontally and hence more readable. However we also
310 maintain notional `signal flow' vaguely from left to right. Subclasses
311 should choose where to put ports based on signal flowing from _ports[0]
318 _arrangement = LEFT_TO_BOTTOM;
319 _vlabel.set_label (_("<b>Sources</b>"));
320 _hlabel.set_label (_("<b>Destinations</b>"));
321 _vlabel.set_angle (90);
323 _vbox.pack_end (_vlabel, false, false);
324 _vbox.pack_end (_vnotebook, false, false);
325 _vbox.pack_end (_vspacer, true, true);
327 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
328 attach (_vscroll, 3, 4, 1, 2, SHRINK);
329 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
330 attach (_vbox, 1, 2, 1, 2, SHRINK);
331 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
337 _arrangement = TOP_TO_RIGHT;
338 _hlabel.set_label (_("<b>Sources</b>"));
339 _vlabel.set_label (_("<b>Destinations</b>"));
340 _vlabel.set_angle (-90);
342 _vbox.pack_end (_vspacer, true, true);
343 _vbox.pack_end (_vnotebook, false, false);
344 _vbox.pack_end (_vlabel, false, false);
346 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
347 attach (_vscroll, 3, 4, 2, 3, SHRINK);
348 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
349 attach (_vbox, 2, 3, 2, 3, SHRINK);
350 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
354 /** @return columns list */
355 PortGroupList const *
356 PortMatrix::columns () const
358 return &_ports[_column_index];
361 boost::shared_ptr<const PortGroup>
362 PortMatrix::visible_columns () const
364 return visible_ports (_column_index);
367 /* @return rows list */
368 PortGroupList const *
369 PortMatrix::rows () const
371 return &_ports[_row_index];
374 boost::shared_ptr<const PortGroup>
375 PortMatrix::visible_rows () const
377 return visible_ports (_row_index);
381 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
383 using namespace Menu_Helpers;
388 _menu->set_name ("ArdourContextMenu");
390 MenuList& items = _menu->items ();
393 bc[_column_index] = column;
394 bc[_row_index] = row;
397 bool need_separator = false;
399 for (int dim = 0; dim < 2; ++dim) {
401 if (bc[dim].bundle) {
403 Menu* m = manage (new Menu);
404 MenuList& sub = m->items ();
406 boost::weak_ptr<Bundle> w (bc[dim].bundle);
408 bool can_add_or_rename = false;
410 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
411 if (should_show (*i)) {
412 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
413 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
414 can_add_or_rename = true;
418 if (can_rename_channels (bc[dim].bundle)) {
420 buf, sizeof (buf), _("Rename '%s'..."),
421 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
426 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
429 can_add_or_rename = true;
432 if (can_add_or_rename) {
433 sub.push_back (SeparatorElem ());
436 if (can_remove_channels (bc[dim].bundle)) {
437 if (bc[dim].channel != -1) {
438 add_remove_option (sub, w, bc[dim].channel);
441 snprintf (buf, sizeof (buf), _("Remove all"));
443 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
446 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
447 if (should_show (bc[dim].bundle->channel_type(i))) {
448 add_remove_option (sub, w, i);
454 if (_show_only_bundles || count_of_our_type (bc[dim].bundle->nchannels()) <= 1) {
455 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
457 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
462 if (bc[dim].channel != -1) {
463 add_disassociate_option (sub, w, dim, bc[dim].channel);
465 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
467 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
470 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
471 if (should_show (bc[dim].bundle->channel_type(i))) {
472 add_disassociate_option (sub, w, dim, i);
478 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
479 need_separator = true;
484 if (need_separator) {
485 items.push_back (SeparatorElem ());
488 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
489 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
490 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
491 _inhibit_toggle_show_only_bundles = true;
492 i->set_active (!_show_only_bundles);
493 _inhibit_toggle_show_only_bundles = false;
499 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
501 boost::shared_ptr<Bundle> sb = b.lock ();
506 remove_channel (BundleChannel (sb, c));
511 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
513 boost::shared_ptr<Bundle> sb = b.lock ();
518 rename_channel (BundleChannel (sb, c));
522 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
524 boost::shared_ptr<Bundle> sb = bundle.lock ();
529 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
530 if (should_show (sb->channel_type(i))) {
531 disassociate_all_on_channel (bundle, i, dim);
537 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
539 boost::shared_ptr<Bundle> sb = bundle.lock ();
544 PortGroup::BundleList a = _ports[1-dim].bundles ();
546 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
547 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
549 if (should_show ((*i)->bundle->channel_type(j))) {
554 c[dim] = BundleChannel (sb, channel);
555 c[1-dim] = BundleChannel ((*i)->bundle, j);
557 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
558 set_state (c, false);
563 _body->rebuild_and_draw_grid ();
567 PortMatrix::setup_global_ports ()
569 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
571 for (int i = 0; i < 2; ++i) {
572 if (list_is_global (i)) {
579 PortMatrix::setup_global_ports_proxy ()
581 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
585 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
589 PortMatrix::setup_all_ports ()
591 if (_session->deletion_in_progress()) {
595 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
602 PortMatrix::toggle_show_only_bundles ()
604 if (_inhibit_toggle_show_only_bundles) {
608 _show_only_bundles = !_show_only_bundles;
613 pair<uint32_t, uint32_t>
614 PortMatrix::max_size () const
616 pair<uint32_t, uint32_t> m = _body->max_size ();
618 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
619 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
625 PortMatrix::on_scroll_event (GdkEventScroll* ev)
627 double const h = _hscroll.get_value ();
628 double const v = _vscroll.get_value ();
630 switch (ev->direction) {
632 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
634 case GDK_SCROLL_DOWN:
635 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
637 case GDK_SCROLL_LEFT:
638 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
640 case GDK_SCROLL_RIGHT:
641 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
648 boost::shared_ptr<IO>
649 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
651 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
653 io = _ports[1].io_from_bundle (b);
660 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
662 return io_from_bundle (b);
666 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
668 boost::shared_ptr<IO> io = io_from_bundle (b);
671 io->add_port ("", this, t);
676 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
678 return io_from_bundle (b);
682 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
684 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
687 Port* p = io->nth (b.channel);
689 io->remove_port (p, this);
695 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
697 boost::shared_ptr<Bundle> b = w.lock ();
702 for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
703 if (should_show (b->channel_type(i))) {
704 remove_channel (ARDOUR::BundleChannel (b, i));
710 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
712 boost::shared_ptr<Bundle> b = w.lock ();
721 PortMatrix::setup_notebooks ()
723 int const h_current_page = _hnotebook.get_current_page ();
724 int const v_current_page = _vnotebook.get_current_page ();
726 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
727 when adding or removing pages to or from notebooks, so ignore them */
729 _ignore_notebook_page_selected = true;
731 remove_notebook_pages (_hnotebook);
732 remove_notebook_pages (_vnotebook);
734 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
735 HBox* dummy = manage (new HBox);
737 Label* label = manage (new Label ((*i)->name));
738 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
740 _vnotebook.prepend_page (*dummy, *label);
743 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
744 HBox* dummy = manage (new HBox);
746 _hnotebook.append_page (*dummy, (*i)->name);
749 _ignore_notebook_page_selected = false;
751 if (_arrangement == TOP_TO_RIGHT) {
752 _vnotebook.set_tab_pos (POS_RIGHT);
753 _hnotebook.set_tab_pos (POS_TOP);
755 _vnotebook.set_tab_pos (POS_LEFT);
756 _hnotebook.set_tab_pos (POS_BOTTOM);
759 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
760 _hnotebook.set_current_page (h_current_page);
762 _hnotebook.set_current_page (0);
765 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
766 _vnotebook.set_current_page (v_current_page);
768 _vnotebook.set_current_page (0);
771 if (_hnotebook.get_n_pages() <= 1) {
777 if (_vnotebook.get_n_pages() <= 1) {
785 PortMatrix::remove_notebook_pages (Notebook& n)
787 int const N = n.get_n_pages ();
789 for (int i = 0; i < N; ++i) {
795 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
797 if (_ignore_notebook_page_selected) {
807 PortMatrix::session_going_away ()
813 PortMatrix::body_dimensions_changed ()
815 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
816 if (_arrangement == TOP_TO_RIGHT) {
817 _vspacer.set_size_request (-1, _body->column_labels_height ());
825 _parent->get_size (curr_width, curr_height);
827 pair<uint32_t, uint32_t> m = max_size ();
829 /* Don't shrink the window */
830 m.first = max (int (m.first), curr_width);
831 m.second = max (int (m.second), curr_height);
833 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
837 boost::shared_ptr<const PortGroup>
838 PortMatrix::visible_ports (int d) const
840 PortGroupList const & p = _ports[d];
841 PortGroupList::List::const_iterator j = p.begin ();
844 if (d == _row_index) {
845 n = p.size() - _vnotebook.get_current_page () - 1;
847 n = _hnotebook.get_current_page ();
851 while (i != int (n) && j != p.end ()) {
857 return boost::shared_ptr<const PortGroup> ();
864 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
866 using namespace Menu_Helpers;
868 boost::shared_ptr<Bundle> b = w.lock ();
874 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
875 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
879 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
881 using namespace Menu_Helpers;
883 boost::shared_ptr<Bundle> b = w.lock ();
889 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
890 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
894 PortMatrix::port_connected_or_disconnected ()
896 _body->rebuild_and_draw_grid ();
900 PortMatrix::channel_noun () const
905 /** @return true if this matrix should show bundles / ports of type \t */
907 PortMatrix::should_show (DataType t) const
909 return (_type == DataType::NIL || t == _type);
913 PortMatrix::count_of_our_type (ChanCount c) const
915 if (_type == DataType::NIL) {
919 return c.get (_type);