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, 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_all_ports ()
581 if (_session->deletion_in_progress()) {
585 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
592 PortMatrix::toggle_show_only_bundles ()
594 if (_inhibit_toggle_show_only_bundles) {
598 _show_only_bundles = !_show_only_bundles;
603 pair<uint32_t, uint32_t>
604 PortMatrix::max_size () const
606 pair<uint32_t, uint32_t> m = _body->max_size ();
608 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
609 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
615 PortMatrix::on_scroll_event (GdkEventScroll* ev)
617 double const h = _hscroll.get_value ();
618 double const v = _vscroll.get_value ();
620 switch (ev->direction) {
622 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
624 case GDK_SCROLL_DOWN:
625 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
627 case GDK_SCROLL_LEFT:
628 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
630 case GDK_SCROLL_RIGHT:
631 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
638 boost::shared_ptr<IO>
639 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
641 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
643 io = _ports[1].io_from_bundle (b);
650 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
652 return io_from_bundle (b);
656 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
658 boost::shared_ptr<IO> io = io_from_bundle (b);
661 io->add_port ("", this, t);
666 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
668 return io_from_bundle (b);
672 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
674 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
677 Port* p = io->nth (b.channel);
679 io->remove_port (p, this);
685 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
687 boost::shared_ptr<Bundle> b = w.lock ();
692 for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
693 if (should_show (b->channel_type(i))) {
694 remove_channel (ARDOUR::BundleChannel (b, i));
700 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
702 boost::shared_ptr<Bundle> b = w.lock ();
711 PortMatrix::setup_notebooks ()
713 int const h_current_page = _hnotebook.get_current_page ();
714 int const v_current_page = _vnotebook.get_current_page ();
716 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
717 when adding or removing pages to or from notebooks, so ignore them */
719 _ignore_notebook_page_selected = true;
721 remove_notebook_pages (_hnotebook);
722 remove_notebook_pages (_vnotebook);
724 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
725 HBox* dummy = manage (new HBox);
727 Label* label = manage (new Label ((*i)->name));
728 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
730 _vnotebook.prepend_page (*dummy, *label);
733 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
734 HBox* dummy = manage (new HBox);
736 _hnotebook.append_page (*dummy, (*i)->name);
739 _ignore_notebook_page_selected = false;
741 if (_arrangement == TOP_TO_RIGHT) {
742 _vnotebook.set_tab_pos (POS_RIGHT);
743 _hnotebook.set_tab_pos (POS_TOP);
745 _vnotebook.set_tab_pos (POS_LEFT);
746 _hnotebook.set_tab_pos (POS_BOTTOM);
749 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
750 _hnotebook.set_current_page (h_current_page);
752 _hnotebook.set_current_page (0);
755 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
756 _vnotebook.set_current_page (v_current_page);
758 _vnotebook.set_current_page (0);
761 if (_hnotebook.get_n_pages() <= 1) {
767 if (_vnotebook.get_n_pages() <= 1) {
775 PortMatrix::remove_notebook_pages (Notebook& n)
777 int const N = n.get_n_pages ();
779 for (int i = 0; i < N; ++i) {
785 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
787 if (_ignore_notebook_page_selected) {
797 PortMatrix::session_going_away ()
803 PortMatrix::body_dimensions_changed ()
805 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
806 if (_arrangement == TOP_TO_RIGHT) {
807 _vspacer.set_size_request (-1, _body->column_labels_height ());
815 _parent->get_size (curr_width, curr_height);
817 pair<uint32_t, uint32_t> m = max_size ();
819 /* Don't shrink the window */
820 m.first = max (int (m.first), curr_width);
821 m.second = max (int (m.second), curr_height);
823 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
827 boost::shared_ptr<const PortGroup>
828 PortMatrix::visible_ports (int d) const
830 PortGroupList const & p = _ports[d];
831 PortGroupList::List::const_iterator j = p.begin ();
834 if (d == _row_index) {
835 n = p.size() - _vnotebook.get_current_page () - 1;
837 n = _hnotebook.get_current_page ();
841 while (i != int (n) && j != p.end ()) {
847 return boost::shared_ptr<const PortGroup> ();
854 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
856 using namespace Menu_Helpers;
858 boost::shared_ptr<Bundle> b = w.lock ();
864 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
865 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
869 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
871 using namespace Menu_Helpers;
873 boost::shared_ptr<Bundle> b = w.lock ();
879 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
880 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
884 PortMatrix::port_connected_or_disconnected ()
886 _body->rebuild_and_draw_grid ();
890 PortMatrix::channel_noun () const
895 /** @return true if this matrix should show bundles / ports of type \t */
897 PortMatrix::should_show (DataType t) const
899 return (_type == DataType::NIL || t == _type);
903 PortMatrix::count_of_our_type (ChanCount c) const
905 if (_type == DataType::NIL) {
909 return c.get (_type);