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 _vbox.pack_start (_vspacer, false, false);
67 _vbox.pack_start (_vnotebook, false, false);
68 _vbox.pack_start (_vlabel, true, true);
69 _hbox.pack_start (_hspacer, false, false);
70 _hbox.pack_start (_hnotebook, false, false);
71 _hbox.pack_start (_hlabel, true, true);
73 _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
74 _vnotebook.property_tab_border() = 4;
75 _vnotebook.set_name (X_("PortMatrixLabel"));
76 _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
77 _hnotebook.property_tab_border() = 4;
78 _hnotebook.set_name (X_("PortMatrixLabel"));
80 _vlabel.set_use_markup ();
81 _vlabel.set_alignment (1, 1);
82 _vlabel.set_padding (4, 16);
83 _vlabel.set_name (X_("PortMatrixLabel"));
84 _hlabel.set_use_markup ();
85 _hlabel.set_alignment (1, 0.5);
86 _hlabel.set_padding (16, 4);
87 _hlabel.set_name (X_("PortMatrixLabel"));
102 PortMatrix::~PortMatrix ()
108 /** Perform initial and once-only setup. This must be called by
109 * subclasses after they have set up _ports[] to at least some
110 * reasonable extent. Two-part initialisation is necessary because
111 * setting up _ports is largely done by virtual functions in
118 select_arrangement ();
120 /* Signal handling is kind of split into three parts:
122 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
123 * representation of the information in _ports[].
125 * 2. When certain other things change, we need to get our subclass to clear and
126 * re-fill _ports[], which in turn causes appropriate signals to be raised to
127 * hook into part (1).
129 * 3. Assorted other signals.
133 /* Part 1: the basic _ports[] change -> reset visuals */
135 for (int i = 0; i < 2; ++i) {
136 /* watch for the content of _ports[] changing */
137 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
139 /* and for bundles in _ports[] changing */
140 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
143 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
145 /* watch for routes being added or removed */
146 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
148 /* and also bundles */
149 _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
152 _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
154 /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
155 _session->RouteOrderKeyChanged.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
157 /* Part 3: other stuff */
159 _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
161 _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
162 _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
164 reconnect_to_routes ();
169 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
171 PortMatrix::reconnect_to_routes ()
173 _route_connections.drop_connections ();
175 boost::shared_ptr<RouteList> routes = _session->get_routes ();
176 for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
177 (*i)->processors_changed.connect (_route_connections, invalidator (*this), ui_bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
182 PortMatrix::route_processors_changed (RouteProcessorChange c)
184 if (c.type == RouteProcessorChange::MeterPointChange) {
185 /* this change has no impact on the port matrix */
189 setup_global_ports ();
192 /** A route has been added to or removed from the session */
194 PortMatrix::routes_changed ()
196 reconnect_to_routes ();
197 setup_global_ports ();
200 /** Set up everything that depends on the content of _ports[] */
204 /* this needs to be done first, as the visible_ports() method uses the
205 notebook state to decide which ports are being shown */
215 PortMatrix::set_type (DataType t)
221 PortMatrix::hscroll_changed ()
223 _body->set_xoffset (_hscroll.get_adjustment()->get_value());
227 PortMatrix::vscroll_changed ()
229 _body->set_yoffset (_vscroll.get_adjustment()->get_value());
233 PortMatrix::setup_scrollbars ()
235 Adjustment* a = _hscroll.get_adjustment ();
237 a->set_upper (_body->full_scroll_width());
238 a->set_page_size (_body->alloc_scroll_width());
239 a->set_step_increment (32);
240 a->set_page_increment (128);
242 a = _vscroll.get_adjustment ();
244 a->set_upper (_body->full_scroll_height());
245 a->set_page_size (_body->alloc_scroll_height());
246 a->set_step_increment (32);
247 a->set_page_increment (128);
250 /** Disassociate all of our ports from each other */
252 PortMatrix::disassociate_all ()
254 PortGroup::BundleList a = _ports[0].bundles ();
255 PortGroup::BundleList b = _ports[1].bundles ();
257 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
258 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
259 for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
260 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
262 if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
266 BundleChannel c[2] = {
267 BundleChannel ((*i)->bundle, j),
268 BundleChannel ((*k)->bundle, l)
271 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
272 set_state (c, false);
280 _body->rebuild_and_draw_grid ();
283 /* Decide how to arrange the components of the matrix */
285 PortMatrix::select_arrangement ()
287 uint32_t const N[2] = {
288 count_of_our_type (_ports[0].total_channels()),
289 count_of_our_type (_ports[1].total_channels())
292 /* The list with the most channels goes on left or right, so that the most channel
293 names are printed horizontally and hence more readable. However we also
294 maintain notional `signal flow' vaguely from left to right. Subclasses
295 should choose where to put ports based on signal flowing from _ports[0]
302 _arrangement = LEFT_TO_BOTTOM;
303 _vlabel.set_label (_("<b>Sources</b>"));
304 _hlabel.set_label (_("<b>Destinations</b>"));
305 _vlabel.set_angle (90);
307 attach (*_body, 1, 2, 0, 1, FILL | EXPAND, FILL | EXPAND);
308 attach (_vscroll, 2, 3, 0, 1, SHRINK);
309 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
310 attach (_vbox, 0, 1, 0, 1, SHRINK);
311 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
313 set_col_spacing (0, 4);
314 set_row_spacing (0, 4);
320 _arrangement = TOP_TO_RIGHT;
321 _hlabel.set_label (_("<b>Sources</b>"));
322 _vlabel.set_label (_("<b>Destinations</b>"));
323 _vlabel.set_angle (-90);
325 attach (*_body, 0, 1, 1, 2, FILL | EXPAND, FILL | EXPAND);
326 attach (_vscroll, 2, 3, 1, 2, SHRINK);
327 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
328 attach (_vbox, 1, 2, 1, 2, SHRINK);
329 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
331 set_col_spacing (1, 4);
332 set_row_spacing (1, 4);
336 /** @return columns list */
337 PortGroupList const *
338 PortMatrix::columns () const
340 return &_ports[_column_index];
343 boost::shared_ptr<const PortGroup>
344 PortMatrix::visible_columns () const
346 return visible_ports (_column_index);
349 /* @return rows list */
350 PortGroupList const *
351 PortMatrix::rows () const
353 return &_ports[_row_index];
356 boost::shared_ptr<const PortGroup>
357 PortMatrix::visible_rows () const
359 return visible_ports (_row_index);
363 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
365 using namespace Menu_Helpers;
370 _menu->set_name ("ArdourContextMenu");
372 MenuList& items = _menu->items ();
375 bc[_column_index] = column;
376 bc[_row_index] = row;
379 bool need_separator = false;
381 for (int dim = 0; dim < 2; ++dim) {
383 if (bc[dim].bundle) {
385 Menu* m = manage (new Menu);
386 MenuList& sub = m->items ();
388 boost::weak_ptr<Bundle> w (bc[dim].bundle);
390 bool can_add_or_rename = false;
392 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
393 if (should_show (*i)) {
394 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
395 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
396 can_add_or_rename = true;
400 if (can_rename_channels (bc[dim].bundle)) {
402 buf, sizeof (buf), _("Rename '%s'..."),
403 escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
408 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
411 can_add_or_rename = true;
414 if (can_add_or_rename) {
415 sub.push_back (SeparatorElem ());
418 if (can_remove_channels (bc[dim].bundle)) {
419 if (bc[dim].channel != -1) {
420 add_remove_option (sub, w, bc[dim].channel);
423 snprintf (buf, sizeof (buf), _("Remove all"));
425 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
428 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
429 if (should_show (bc[dim].bundle->channel_type(i))) {
430 add_remove_option (sub, w, i);
436 if (_show_only_bundles || count_of_our_type (bc[dim].bundle->nchannels()) <= 1) {
437 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
439 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
444 if (bc[dim].channel != -1) {
445 add_disassociate_option (sub, w, dim, bc[dim].channel);
447 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
449 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
452 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
453 if (should_show (bc[dim].bundle->channel_type(i))) {
454 add_disassociate_option (sub, w, dim, i);
460 items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
461 need_separator = true;
466 if (need_separator) {
467 items.push_back (SeparatorElem ());
470 items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
471 items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
472 CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
473 _inhibit_toggle_show_only_bundles = true;
474 i->set_active (!_show_only_bundles);
475 _inhibit_toggle_show_only_bundles = false;
481 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
483 boost::shared_ptr<Bundle> sb = b.lock ();
488 remove_channel (BundleChannel (sb, c));
493 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
495 boost::shared_ptr<Bundle> sb = b.lock ();
500 rename_channel (BundleChannel (sb, c));
504 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
506 boost::shared_ptr<Bundle> sb = bundle.lock ();
511 for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
512 if (should_show (sb->channel_type(i))) {
513 disassociate_all_on_channel (bundle, i, dim);
519 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
521 boost::shared_ptr<Bundle> sb = bundle.lock ();
526 PortGroup::BundleList a = _ports[1-dim].bundles ();
528 for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
529 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
531 if (should_show ((*i)->bundle->channel_type(j))) {
536 c[dim] = BundleChannel (sb, channel);
537 c[1-dim] = BundleChannel ((*i)->bundle, j);
539 if (get_state (c) == PortMatrixNode::ASSOCIATED) {
540 set_state (c, false);
545 _body->rebuild_and_draw_grid ();
549 PortMatrix::setup_global_ports ()
551 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
553 for (int i = 0; i < 2; ++i) {
554 if (list_is_global (i)) {
561 PortMatrix::setup_all_ports ()
563 if (_session->deletion_in_progress()) {
567 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
574 PortMatrix::toggle_show_only_bundles ()
576 if (_inhibit_toggle_show_only_bundles) {
580 _show_only_bundles = !_show_only_bundles;
585 pair<uint32_t, uint32_t>
586 PortMatrix::max_size () const
588 pair<uint32_t, uint32_t> m = _body->max_size ();
590 m.first += _vscroll.get_width () + _vbox.get_width () + 4;
591 m.second += _hscroll.get_height () + _hbox.get_height () + 4;
597 PortMatrix::on_scroll_event (GdkEventScroll* ev)
599 double const h = _hscroll.get_value ();
600 double const v = _vscroll.get_value ();
602 switch (ev->direction) {
604 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
606 case GDK_SCROLL_DOWN:
607 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
609 case GDK_SCROLL_LEFT:
610 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
612 case GDK_SCROLL_RIGHT:
613 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
620 boost::shared_ptr<IO>
621 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
623 boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
625 io = _ports[1].io_from_bundle (b);
632 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
634 return io_from_bundle (b);
638 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
640 boost::shared_ptr<IO> io = io_from_bundle (b);
643 io->add_port ("", this, t);
648 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
650 return io_from_bundle (b);
654 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
656 boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
659 Port* p = io->nth (b.channel);
661 io->remove_port (p, this);
667 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
669 boost::shared_ptr<Bundle> b = w.lock ();
674 for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
675 if (should_show (b->channel_type(i))) {
676 remove_channel (ARDOUR::BundleChannel (b, i));
682 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
684 boost::shared_ptr<Bundle> b = w.lock ();
693 PortMatrix::setup_notebooks ()
695 int const h_current_page = _hnotebook.get_current_page ();
696 int const v_current_page = _vnotebook.get_current_page ();
698 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
699 when adding or removing pages to or from notebooks, so ignore them */
701 _ignore_notebook_page_selected = true;
703 remove_notebook_pages (_hnotebook);
704 remove_notebook_pages (_vnotebook);
706 for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
707 HBox* dummy = manage (new HBox);
709 Label* label = manage (new Label ((*i)->name));
710 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
712 _vnotebook.prepend_page (*dummy, *label);
715 for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
716 HBox* dummy = manage (new HBox);
718 _hnotebook.append_page (*dummy, (*i)->name);
721 _ignore_notebook_page_selected = false;
723 _vnotebook.set_tab_pos (POS_LEFT);
724 _hnotebook.set_tab_pos (POS_TOP);
726 if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
727 _hnotebook.set_current_page (h_current_page);
729 _hnotebook.set_current_page (0);
732 if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
733 _vnotebook.set_current_page (v_current_page);
735 _vnotebook.set_current_page (0);
738 if (_hnotebook.get_n_pages() <= 1) {
744 if (_vnotebook.get_n_pages() <= 1) {
752 PortMatrix::remove_notebook_pages (Notebook& n)
754 int const N = n.get_n_pages ();
756 for (int i = 0; i < N; ++i) {
762 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
764 if (_ignore_notebook_page_selected) {
774 PortMatrix::session_going_away ()
780 PortMatrix::body_dimensions_changed ()
782 _hspacer.set_size_request (_body->column_labels_border_x (), -1);
783 if (_arrangement == TOP_TO_RIGHT) {
784 _vspacer.set_size_request (-1, _body->column_labels_height ());
792 _parent->get_size (curr_width, curr_height);
794 pair<uint32_t, uint32_t> m = max_size ();
796 /* Don't shrink the window */
797 m.first = max (int (m.first), curr_width);
798 m.second = max (int (m.second), curr_height);
800 resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
804 boost::shared_ptr<const PortGroup>
805 PortMatrix::visible_ports (int d) const
807 PortGroupList const & p = _ports[d];
808 PortGroupList::List::const_iterator j = p.begin ();
811 if (d == _row_index) {
812 n = p.size() - _vnotebook.get_current_page () - 1;
814 n = _hnotebook.get_current_page ();
818 while (i != int (n) && j != p.end ()) {
824 return boost::shared_ptr<const PortGroup> ();
831 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
833 using namespace Menu_Helpers;
835 boost::shared_ptr<Bundle> b = w.lock ();
841 snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
842 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
846 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
848 using namespace Menu_Helpers;
850 boost::shared_ptr<Bundle> b = w.lock ();
856 snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
857 m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
861 PortMatrix::port_connected_or_disconnected ()
863 _body->rebuild_and_draw_grid ();
867 PortMatrix::channel_noun () const
872 /** @return true if this matrix should show bundles / ports of type \t */
874 PortMatrix::should_show (DataType t) const
876 return (_type == DataType::NIL || t == _type);
880 PortMatrix::count_of_our_type (ChanCount c) const
882 if (_type == DataType::NIL) {
886 return c.get (_type);