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"));
99 PortMatrix::~PortMatrix ()
105 /** Perform initial and once-only setup. This must be called by
106 * subclasses after they have set up _ports[] to at least some
107 * reasonable extent. Two-part initialisation is necessary because
108 * setting up _ports is largely done by virtual functions in
115 select_arrangement ();
117 /* Signal handling is kind of split into three parts:
119 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
120 * representation of the information in _ports[].
122 * 2. When certain other things change, we need to get our subclass to clear and
123 * re-fill _ports[], which in turn causes appropriate signals to be raised to
124 * hook into part (1).
126 * 3. Assorted other signals.
130 /* Part 1: the basic _ports[] change -> reset visuals */
132 for (int i = 0; i < 2; ++i) {
133 /* watch for the content of _ports[] changing */
134 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
136 /* and for bundles in _ports[] changing */
137 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
140 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
142 /* watch for routes being added or removed */
143 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
145 /* and also bundles */
146 _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
149 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
151 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
152 _session->RouteOrderKeyChanged.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
154 /* Part 3: other stuff */
156 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
158 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
159 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
161 reconnect_to_routes ();
166 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
168 PortMatrix::reconnect_to_routes ()
170 _route_connections.drop_connections ();
172 boost::shared_ptr<RouteList> routes = _session->get_routes ();
173 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
174 (*i)->processors_changed.connect (_route_connections, invalidator (*this), ui_bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
179 PortMatrix::route_processors_changed (RouteProcessorChange c)
181 if (c.type == RouteProcessorChange::MeterPointChange) {
182 /* this change has no impact on the port matrix */
186 setup_global_ports ();
189 /** A route has been added to or removed from the session */
191 PortMatrix::routes_changed ()
193 reconnect_to_routes ();
194 setup_global_ports ();
197 /** Set up everything that depends on the content of _ports[] */
201 /* this needs to be done first, as the visible_ports() method uses the
202 notebook state to decide which ports are being shown */
212 PortMatrix::set_type (DataType t)
218 PortMatrix::hscroll_changed ()
220 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
224 PortMatrix::vscroll_changed ()
226 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
230 PortMatrix::setup_scrollbars ()
232 Adjustment* a = _hscroll.get_adjustment ();
234 a->set_upper (_body->full_scroll_width());
235 a->set_page_size (_body->alloc_scroll_width());
236 a->set_step_increment (32);
237 a->set_page_increment (128);
239 a = _vscroll.get_adjustment ();
241 a->set_upper (_body->full_scroll_height());
242 a->set_page_size (_body->alloc_scroll_height());
243 a->set_step_increment (32);
244 a->set_page_increment (128);
247 /** Disassociate all of our ports from each other */
249 PortMatrix::disassociate_all ()
251 PortGroup::BundleList a = _ports[0].bundles ();
252 PortGroup::BundleList b = _ports[1].bundles ();
254 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
255 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
256 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
257 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
259 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
263 BundleChannel c[2] = {
264 BundleChannel ((*i)->bundle, j),
265 BundleChannel ((*k)->bundle, l)
268 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
269 set_state (c, false);
277 _body->rebuild_and_draw_grid ();
280 /* Decide how to arrange the components of the matrix */
282 PortMatrix::select_arrangement ()
284 uint32_t const N[2] = {
285 count_of_our_type (_ports[0].total_channels()),
286 count_of_our_type (_ports[1].total_channels())
289 /* XXX: shirley there's an easier way than this */
291 if (_vspacer.get_parent()) {
292 _vbox.remove (_vspacer);
295 if (_vnotebook.get_parent()) {
296 _vbox.remove (_vnotebook);
299 if (_vlabel.get_parent()) {
300 _vbox.remove (_vlabel);
303 /* The list with the most channels goes on left or right, so that the most channel
304 names are printed horizontally and hence more readable. However we also
305 maintain notional `signal flow' vaguely from left to right. Subclasses
306 should choose where to put ports based on signal flowing from _ports[0]
313 _arrangement = LEFT_TO_BOTTOM;
314 _vlabel.set_label (_("<b>Sources</b>"));
315 _hlabel.set_label (_("<b>Destinations</b>"));
316 _vlabel.set_angle (90);
318 _vbox.pack_end (_vlabel, false, false);
319 _vbox.pack_end (_vnotebook, false, false);
320 _vbox.pack_end (_vspacer, true, true);
322 attach (*_body, 1, 2, 0, 1, FILL | EXPAND, FILL | EXPAND);
323 attach (_vscroll, 2, 3, 0, 1, SHRINK);
324 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
325 attach (_vbox, 0, 1, 0, 1, SHRINK);
326 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
328 set_col_spacing (0, 4);
329 set_row_spacing (0, 4);
335 _arrangement = TOP_TO_RIGHT;
336 _hlabel.set_label (_("<b>Sources</b>"));
337 _vlabel.set_label (_("<b>Destinations</b>"));
338 _vlabel.set_angle (-90);
340 _vbox.pack_end (_vspacer, true, true);
341 _vbox.pack_end (_vnotebook, false, false);
342 _vbox.pack_end (_vlabel, false, false);
344 attach (*_body, 0, 1, 1, 2, FILL | EXPAND, FILL | EXPAND);
345 attach (_vscroll, 2, 3, 1, 2, SHRINK);
346 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
347 attach (_vbox, 1, 2, 1, 2, SHRINK);
348 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
350 set_col_spacing (1, 4);
351 set_row_spacing (1, 4);
355 /** @return columns list */
356 PortGroupList const *
357 PortMatrix::columns () const
359 return &_ports[_column_index];
362 boost::shared_ptr<const PortGroup>
363 PortMatrix::visible_columns () const
365 return visible_ports (_column_index);
368 /* @return rows list */
369 PortGroupList const *
370 PortMatrix::rows () const
372 return &_ports[_row_index];
375 boost::shared_ptr<const PortGroup>
376 PortMatrix::visible_rows () const
378 return visible_ports (_row_index);
382 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
384 using namespace Menu_Helpers;
389 _menu->set_name ("ArdourContextMenu");
391 MenuList& items = _menu->items ();
394 bc[_column_index] = column;
395 bc[_row_index] = row;
398 bool need_separator = false;
400 for (int dim = 0; dim < 2; ++dim) {
402 if (bc[dim].bundle) {
404 Menu* m = manage (new Menu);
405 MenuList& sub = m->items ();
407 boost::weak_ptr<Bundle> w (bc[dim].bundle);
409 bool can_add_or_rename = false;
411 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
412 if (should_show (*i)) {
413 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
414 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
415 can_add_or_rename = true;
419 if (can_rename_channels (bc[dim].bundle)) {
421 buf, sizeof (buf), _("Rename '%s'..."),
422 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
427 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
430 can_add_or_rename = true;
433 if (can_add_or_rename) {
434 sub.push_back (SeparatorElem ());
437 if (can_remove_channels (bc[dim].bundle)) {
438 if (bc[dim].channel != -1) {
439 add_remove_option (sub, w, bc[dim].channel);
442 snprintf (buf, sizeof (buf), _("Remove all"));
444 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
447 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
448 if (should_show (bc[dim].bundle->channel_type(i))) {
449 add_remove_option (sub, w, i);
455 if (_show_only_bundles || count_of_our_type (bc[dim].bundle->nchannels()) <= 1) {
456 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
458 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
463 if (bc[dim].channel != -1) {
464 add_disassociate_option (sub, w, dim, bc[dim].channel);
466 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
468 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
471 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
472 if (should_show (bc[dim].bundle->channel_type(i))) {
473 add_disassociate_option (sub, w, dim, i);
479 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
480 need_separator = true;
485 if (need_separator) {
486 items.push_back (SeparatorElem ());
489 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
490 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
491 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
492 _inhibit_toggle_show_only_bundles = true;
493 i->set_active (!_show_only_bundles);
494 _inhibit_toggle_show_only_bundles = false;
500 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
502 boost::shared_ptr<Bundle> sb = b.lock ();
507 remove_channel (BundleChannel (sb, c));
512 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
514 boost::shared_ptr<Bundle> sb = b.lock ();
519 rename_channel (BundleChannel (sb, c));
523 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
525 boost::shared_ptr<Bundle> sb = bundle.lock ();
530 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
531 if (should_show (sb->channel_type(i))) {
532 disassociate_all_on_channel (bundle, i, dim);
538 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
540 boost::shared_ptr<Bundle> sb = bundle.lock ();
545 PortGroup::BundleList a = _ports[1-dim].bundles ();
547 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
548 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
550 if (should_show ((*i)->bundle->channel_type(j))) {
555 c[dim] = BundleChannel (sb, channel);
556 c[1-dim] = BundleChannel ((*i)->bundle, j);
558 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
559 set_state (c, false);
564 _body->rebuild_and_draw_grid ();
568 PortMatrix::setup_global_ports ()
570 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
572 for (int i = 0; i < 2; ++i) {
573 if (list_is_global (i)) {
580 PortMatrix::setup_all_ports ()
582 if (_session->deletion_in_progress()) {
586 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
593 PortMatrix::toggle_show_only_bundles ()
595 if (_inhibit_toggle_show_only_bundles) {
599 _show_only_bundles = !_show_only_bundles;
604 pair<uint32_t, uint32_t>
605 PortMatrix::max_size () const
607 pair<uint32_t, uint32_t> m = _body->max_size ();
609 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
610 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
616 PortMatrix::on_scroll_event (GdkEventScroll* ev)
618 double const h = _hscroll.get_value ();
619 double const v = _vscroll.get_value ();
621 switch (ev->direction) {
623 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
625 case GDK_SCROLL_DOWN:
626 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
628 case GDK_SCROLL_LEFT:
629 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
631 case GDK_SCROLL_RIGHT:
632 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
639 boost::shared_ptr<IO>
640 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
642 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
644 io = _ports[1].io_from_bundle (b);
651 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
653 return io_from_bundle (b);
657 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
659 boost::shared_ptr<IO> io = io_from_bundle (b);
662 io->add_port ("", this, t);
667 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
669 return io_from_bundle (b);
673 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
675 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
678 Port* p = io->nth (b.channel);
680 io->remove_port (p, this);
686 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
688 boost::shared_ptr<Bundle> b = w.lock ();
693 for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
694 if (should_show (b->channel_type(i))) {
695 remove_channel (ARDOUR::BundleChannel (b, i));
701 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
703 boost::shared_ptr<Bundle> b = w.lock ();
712 PortMatrix::setup_notebooks ()
714 int const h_current_page = _hnotebook.get_current_page ();
715 int const v_current_page = _vnotebook.get_current_page ();
717 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
718 when adding or removing pages to or from notebooks, so ignore them */
720 _ignore_notebook_page_selected = true;
722 remove_notebook_pages (_hnotebook);
723 remove_notebook_pages (_vnotebook);
725 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
726 HBox* dummy = manage (new HBox);
728 Label* label = manage (new Label ((*i)->name));
729 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
731 _vnotebook.prepend_page (*dummy, *label);
734 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
735 HBox* dummy = manage (new HBox);
737 _hnotebook.append_page (*dummy, (*i)->name);
740 _ignore_notebook_page_selected = false;
742 _vnotebook.set_tab_pos (POS_LEFT);
743 _hnotebook.set_tab_pos (POS_TOP);
745 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
746 _hnotebook.set_current_page (h_current_page);
748 _hnotebook.set_current_page (0);
751 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
752 _vnotebook.set_current_page (v_current_page);
754 _vnotebook.set_current_page (0);
757 if (_hnotebook.get_n_pages() <= 1) {
763 if (_vnotebook.get_n_pages() <= 1) {
771 PortMatrix::remove_notebook_pages (Notebook& n)
773 int const N = n.get_n_pages ();
775 for (int i = 0; i < N; ++i) {
781 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
783 if (_ignore_notebook_page_selected) {
793 PortMatrix::session_going_away ()
799 PortMatrix::body_dimensions_changed ()
801 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
802 if (_arrangement == TOP_TO_RIGHT) {
803 _vspacer.set_size_request (-1, _body->column_labels_height ());
811 _parent->get_size (curr_width, curr_height);
813 pair<uint32_t, uint32_t> m = max_size ();
815 /* Don't shrink the window */
816 m.first = max (int (m.first), curr_width);
817 m.second = max (int (m.second), curr_height);
819 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
823 boost::shared_ptr<const PortGroup>
824 PortMatrix::visible_ports (int d) const
826 PortGroupList const & p = _ports[d];
827 PortGroupList::List::const_iterator j = p.begin ();
830 if (d == _row_index) {
831 n = p.size() - _vnotebook.get_current_page () - 1;
833 n = _hnotebook.get_current_page ();
837 while (i != int (n) && j != p.end ()) {
843 return boost::shared_ptr<const PortGroup> ();
850 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
852 using namespace Menu_Helpers;
854 boost::shared_ptr<Bundle> b = w.lock ();
860 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
861 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
865 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
867 using namespace Menu_Helpers;
869 boost::shared_ptr<Bundle> b = w.lock ();
875 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
876 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
880 PortMatrix::port_connected_or_disconnected ()
882 _body->rebuild_and_draw_grid ();
886 PortMatrix::channel_noun () const
891 /** @return true if this matrix should show bundles / ports of type \t */
893 PortMatrix::should_show (DataType t) const
895 return (_type == DataType::NIL || t == _type);
899 PortMatrix::count_of_our_type (ChanCount c) const
901 if (_type == DataType::NIL) {
905 return c.get (_type);