08000d9122dafe22137fc4282ca29489e878bf26
[ardour.git] / gtk2_ardour / port_matrix.cc
1 /*
2  * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
4  * Copyright (C) 2008-2016 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2008 Hans Baier <hansfbaier@googlemail.com>
6  * Copyright (C) 2013-2015 Nick Mainsbridge <mainsbridge@gmail.com>
7  * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 #include <iostream>
25
26 #include <gtkmm/scrolledwindow.h>
27 #include <gtkmm/adjustment.h>
28 #include <gtkmm/label.h>
29 #include <gtkmm/menu.h>
30 #include <gtkmm/menushell.h>
31 #include <gtkmm/menu_elems.h>
32 #include <gtkmm/window.h>
33 #include <gtkmm/stock.h>
34
35 #include "ardour/bundle.h"
36 #include "ardour/types.h"
37 #include "ardour/session.h"
38 #include "ardour/route.h"
39 #include "ardour/audioengine.h"
40
41 #include "gtkmm2ext/utils.h"
42
43 #include "ardour_dialog.h"
44 #include "ardour_message.h"
45 #include "gui_thread.h"
46 #include "port_matrix.h"
47 #include "port_matrix_body.h"
48 #include "port_matrix_component.h"
49 #include "utils.h"
50 #include "ui_config.h"
51
52 #include "pbd/i18n.h"
53
54 using namespace std;
55 using namespace Gtk;
56 using namespace ARDOUR;
57 using namespace ARDOUR_UI_UTILS;
58
59 /** PortMatrix constructor.
60  *  @param session Our session.
61  *  @param type Port type that we are handling.
62  */
63 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
64         : Table (4, 4)
65         , _parent (parent)
66         , _type (type)
67         , _menu (0)
68         , _arrangement (TOP_TO_RIGHT)
69         , _row_index (0)
70         , _column_index (1)
71         , _min_height_divisor (1)
72         , _show_only_bundles (false)
73         , _inhibit_toggle_show_only_bundles (false)
74         , _ignore_notebook_page_selected (false)
75 {
76         set_session (session);
77
78         _body = new PortMatrixBody (this);
79         _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
80
81         _hbox.pack_end (_hspacer, true, true);
82         _hbox.pack_end (_hnotebook, false, false);
83         _hbox.pack_end (_hlabel, false, false);
84
85         _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
86         _vnotebook.property_tab_border() = 4;
87         _vnotebook.set_name (X_("PortMatrixLabel"));
88         _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
89         _hnotebook.property_tab_border() = 4;
90         _hnotebook.set_name (X_("PortMatrixLabel"));
91
92         _vlabel.set_use_markup ();
93         _vlabel.set_alignment (1, 1);
94         _vlabel.set_padding (4, 16);
95         _vlabel.set_name (X_("PortMatrixLabel"));
96         _hlabel.set_use_markup ();
97         _hlabel.set_alignment (1, 0.5);
98         _hlabel.set_padding (16, 4);
99         _hlabel.set_name (X_("PortMatrixLabel"));
100
101         set_row_spacing (0, 8);
102         set_col_spacing (0, 8);
103         set_row_spacing (2, 8);
104         set_col_spacing (2, 8);
105
106         _body->show ();
107         _vbox.show ();
108         _hbox.show ();
109         _vscroll.show ();
110         _hscroll.show ();
111         _vlabel.show ();
112         _hlabel.show ();
113         _hspacer.show ();
114         _vspacer.show ();
115         _vnotebook.show ();
116         _hnotebook.show ();
117
118         UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &PortMatrix::parameter_changed));
119 }
120
121 PortMatrix::~PortMatrix ()
122 {
123         delete _body;
124         delete _menu;
125 }
126
127 /** Perform initial and once-only setup.  This must be called by
128  *  subclasses after they have set up _ports[] to at least some
129  *  reasonable extent.  Two-part initialisation is necessary because
130  *  setting up _ports is largely done by virtual functions in
131  *  subclasses.
132  */
133
134 void
135 PortMatrix::init ()
136 {
137         select_arrangement ();
138
139         /* Signal handling is kind of split into three parts:
140          *
141          * 1.  When _ports[] changes, we call setup().  This essentially sorts out our visual
142          *     representation of the information in _ports[].
143          *
144          * 2.  When certain other things change, we need to get our subclass to clear and
145          *     re-fill _ports[], which in turn causes appropriate signals to be raised to
146          *     hook into part (1).
147          *
148          * 3.  Assorted other signals.
149          */
150
151
152         /* Part 1: the basic _ports[] change -> reset visuals */
153
154         for (int i = 0; i < 2; ++i) {
155                 /* watch for the content of _ports[] changing */
156                 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
157
158                 /* and for bundles in _ports[] changing */
159                 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
160         }
161
162         /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
163
164         /* watch for routes being added or removed */
165         _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
166
167         /* and also bundles */
168         _session->BundleAddedOrRemoved.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
169
170         /* and also ports */
171         _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
172
173         /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
174         PresentationInfo::Change.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
175
176         /* Part 3: other stuff */
177
178         _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
179
180         _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
181         _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
182
183         reconnect_to_routes ();
184
185         setup ();
186 }
187
188 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
189 void
190 PortMatrix::reconnect_to_routes ()
191 {
192         _route_connections.drop_connections ();
193
194         boost::shared_ptr<RouteList> routes = _session->get_routes ();
195         for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
196                 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
197                 (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
198         }
199 }
200
201 void
202 PortMatrix::route_processors_changed (RouteProcessorChange c)
203 {
204         if (c.type == RouteProcessorChange::MeterPointChange) {
205                 /* this change has no impact on the port matrix */
206                 return;
207         }
208
209         setup_global_ports ();
210 }
211
212 /** A route has been added to or removed from the session */
213 void
214 PortMatrix::routes_changed ()
215 {
216         if (!_session) return;
217         reconnect_to_routes ();
218         setup_global_ports ();
219 }
220
221 /** Set up everything that depends on the content of _ports[] */
222 void
223 PortMatrix::setup ()
224 {
225         if (!_session) {
226                 _route_connections.drop_connections ();
227                 return; // session went away
228         }
229
230         /* this needs to be done first, as the visible_ports() method uses the
231            notebook state to decide which ports are being shown */
232
233         setup_notebooks ();
234
235         _body->setup ();
236         setup_scrollbars ();
237         update_tab_highlighting ();
238         queue_draw ();
239 }
240
241 void
242 PortMatrix::set_type (DataType t)
243 {
244         _type = t;
245 }
246
247 void
248 PortMatrix::hscroll_changed ()
249 {
250         _body->set_xoffset (_hscroll.get_adjustment()->get_value());
251 }
252
253 void
254 PortMatrix::vscroll_changed ()
255 {
256         _body->set_yoffset (_vscroll.get_adjustment()->get_value());
257 }
258
259 void
260 PortMatrix::setup_scrollbars ()
261 {
262         Adjustment* a = _hscroll.get_adjustment ();
263         a->set_lower (0);
264         a->set_page_size (_body->alloc_scroll_width());
265         a->set_step_increment (32);
266         a->set_page_increment (128);
267
268         /* Set the adjustment to zero if the size has changed.*/
269         if (a->get_upper() != _body->full_scroll_width()) {
270                 a->set_upper (_body->full_scroll_width());
271                 a->set_value (0);
272         }
273
274         a = _vscroll.get_adjustment ();
275         a->set_lower (0);
276         a->set_page_size (_body->alloc_scroll_height());
277         a->set_step_increment (32);
278         a->set_page_increment (128);
279
280         if (a->get_upper() != _body->full_scroll_height()) {
281                 a->set_upper (_body->full_scroll_height());
282                 a->set_value (0);
283         }
284 }
285
286 /** Disassociate all of our ports from each other */
287 void
288 PortMatrix::disassociate_all ()
289 {
290         PortGroup::BundleList a = _ports[0].bundles ();
291         PortGroup::BundleList b = _ports[1].bundles ();
292
293         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
294                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
295                         for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
296                                 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
297
298                                         if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
299                                                 continue;
300                                         }
301
302                                         BundleChannel c[2] = {
303                                                 BundleChannel ((*i)->bundle, j),
304                                                 BundleChannel ((*k)->bundle, l)
305                                                         };
306
307                                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
308                                                 set_state (c, false);
309                                         }
310
311                                 }
312                         }
313                 }
314         }
315
316         _body->rebuild_and_draw_grid ();
317 }
318
319 /* Decide how to arrange the components of the matrix */
320 void
321 PortMatrix::select_arrangement ()
322 {
323         uint32_t const N[2] = {
324                 count_of_our_type_min_1 (_ports[0].total_channels()),
325                 count_of_our_type_min_1 (_ports[1].total_channels())
326         };
327
328         /* XXX: shirley there's an easier way than this */
329
330         if (_vspacer.get_parent()) {
331                 _vbox.remove (_vspacer);
332         }
333
334         if (_vnotebook.get_parent()) {
335                 _vbox.remove (_vnotebook);
336         }
337
338         if (_vlabel.get_parent()) {
339                 _vbox.remove (_vlabel);
340         }
341
342         /* The list with the most channels goes on left or right, so that the most channel
343            names are printed horizontally and hence more readable.  However we also
344            maintain notional `signal flow' vaguely from left to right.  Subclasses
345            should choose where to put ports based on signal flowing from _ports[0]
346            to _ports[1] */
347
348         if (N[0] > N[1]) {
349
350                 _row_index = 0;
351                 _column_index = 1;
352                 _arrangement = LEFT_TO_BOTTOM;
353                 _vlabel.set_label (_("<b>Sources</b>"));
354                 _hlabel.set_label (_("<b>Destinations</b>"));
355                 _vlabel.set_angle (90);
356
357                 _vbox.pack_end (_vlabel, false, false);
358                 _vbox.pack_end (_vnotebook, false, false);
359                 _vbox.pack_end (_vspacer, true, true);
360
361 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
362                 REMOVE_FROM_GTK_PARENT(*_body)
363                 REMOVE_FROM_GTK_PARENT(_vscroll)
364                 REMOVE_FROM_GTK_PARENT(_hscroll)
365                 REMOVE_FROM_GTK_PARENT(_vbox)
366                 REMOVE_FROM_GTK_PARENT(_hbox)
367
368                 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
369                 attach (_vscroll, 3, 4, 1, 2, SHRINK);
370                 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
371                 attach (_vbox, 1, 2, 1, 2, SHRINK);
372                 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
373
374         } else {
375
376                 _row_index = 1;
377                 _column_index = 0;
378                 _arrangement = TOP_TO_RIGHT;
379                 _hlabel.set_label (_("<b>Sources</b>"));
380                 _vlabel.set_label (_("<b>Destinations</b>"));
381                 _vlabel.set_angle (-90);
382
383                 _vbox.pack_end (_vspacer, true, true);
384                 _vbox.pack_end (_vnotebook, false, false);
385                 _vbox.pack_end (_vlabel, false, false);
386
387                 REMOVE_FROM_GTK_PARENT(*_body)
388                 REMOVE_FROM_GTK_PARENT(_vscroll)
389                 REMOVE_FROM_GTK_PARENT(_hscroll)
390                 REMOVE_FROM_GTK_PARENT(_vbox)
391                 REMOVE_FROM_GTK_PARENT(_hbox)
392
393                 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
394                 attach (_vscroll, 3, 4, 2, 3, SHRINK);
395                 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
396                 attach (_vbox, 2, 3, 2, 3, SHRINK);
397                 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
398         }
399 }
400
401 /** @return columns list */
402 PortGroupList const *
403 PortMatrix::columns () const
404 {
405         return &_ports[_column_index];
406 }
407
408 boost::shared_ptr<const PortGroup>
409 PortMatrix::visible_columns () const
410 {
411         return visible_ports (_column_index);
412 }
413
414 /* @return rows list */
415 PortGroupList const *
416 PortMatrix::rows () const
417 {
418         return &_ports[_row_index];
419 }
420
421 boost::shared_ptr<const PortGroup>
422 PortMatrix::visible_rows () const
423 {
424         return visible_ports (_row_index);
425 }
426
427 /** @param column Column; its bundle may be 0 if we are over a row heading.
428  *  @param row Row; its bundle may be 0 if we are over a column heading.
429  */
430 void
431 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
432 {
433         using namespace Menu_Helpers;
434
435         delete _menu;
436
437         _menu = new Menu;
438         _menu->set_name ("ArdourContextMenu");
439
440         MenuList& items = _menu->items ();
441
442         BundleChannel bc[2];
443         bc[_column_index] = column;
444         bc[_row_index] = row;
445
446         char buf [64];
447         bool need_separator = false;
448
449         for (int dim = 0; dim < 2; ++dim) {
450
451                 if (bc[dim].bundle) {
452
453                         Menu* m = manage (new Menu);
454                         MenuList& sub = m->items ();
455
456                         boost::weak_ptr<Bundle> w (bc[dim].bundle);
457
458                         if (can_add_channels (bc[dim].bundle)) {
459                                 /* Start off with options for the `natural' port type */
460                                 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
461                                         if (should_show (*i) && can_add_channel_proxy (w, *i)) {
462                                                 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
463                                                 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
464                                         }
465                                 }
466
467                                 /* Now add other ones */
468                                 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
469                                         if (!should_show (*i) && can_add_channel_proxy (w, *i)) {
470                                                 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
471                                                 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
472                                         }
473                                 }
474                         }
475
476                         if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
477                                 snprintf (
478                                         buf, sizeof (buf), _("Rename '%s'..."),
479                                         escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
480                                         );
481                                 sub.push_back (
482                                         MenuElem (
483                                                 buf,
484                                                 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
485                                                 )
486                                         );
487                         }
488
489                         if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
490                                 if (bc[dim].channel != -1) {
491                                         add_remove_option (sub, w, bc[dim].channel);
492                                 } else {
493                                         sub.push_back (
494                                                 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
495                                                 );
496
497                                         if (bc[dim].bundle->nchannels().n_total() > 1) {
498                                                 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
499                                                         if (should_show (bc[dim].bundle->channel_type(i))) {
500                                                                 add_remove_option (sub, w, i);
501                                                         }
502                                                 }
503                                         }
504                                 }
505                         }
506
507                         uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
508                         if ((_show_only_bundles && c > 0) || c == 1) {
509
510                                 /* we're looking just at bundles, or our bundle has only one channel, so just offer
511                                    to disassociate all on the bundle.
512                                 */
513
514                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
515                                 sub.push_back (
516                                         MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
517                                         );
518
519                         } else if (c != 0) {
520
521                                 if (bc[dim].channel != -1) {
522                                         /* specific channel under the menu, so just offer to disassociate that */
523                                         add_disassociate_option (sub, w, dim, bc[dim].channel);
524                                 } else {
525                                         /* no specific channel; offer to disassociate all, or any one in particular */
526                                         snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
527                                         sub.push_back (
528                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
529                                                 );
530
531                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
532                                                 if (should_show (bc[dim].bundle->channel_type(i))) {
533                                                         add_disassociate_option (sub, w, dim, i);
534                                                 }
535                                         }
536                                 }
537                         }
538
539                         items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
540                         need_separator = true;
541                 }
542
543         }
544
545         if (need_separator) {
546                 items.push_back (SeparatorElem ());
547         }
548
549         items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
550
551         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
552         Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
553         _inhibit_toggle_show_only_bundles = true;
554         i->set_active (!_show_only_bundles);
555         _inhibit_toggle_show_only_bundles = false;
556
557         items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
558         items.back().set_sensitive (can_flip ());
559
560         _menu->popup (1, t);
561 }
562
563 void
564 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
565 {
566         boost::shared_ptr<Bundle> sb = b.lock ();
567         if (!sb) {
568                 return;
569         }
570
571         remove_channel (BundleChannel (sb, c));
572
573 }
574
575 void
576 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
577 {
578         boost::shared_ptr<Bundle> sb = b.lock ();
579         if (!sb) {
580                 return;
581         }
582
583         rename_channel (BundleChannel (sb, c));
584 }
585
586 void
587 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
588 {
589         boost::shared_ptr<Bundle> sb = bundle.lock ();
590         if (!sb) {
591                 return;
592         }
593
594         for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
595                 if (should_show (sb->channel_type(i))) {
596                         disassociate_all_on_channel (bundle, i, dim);
597                 }
598         }
599 }
600
601 void
602 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
603 {
604         boost::shared_ptr<Bundle> sb = bundle.lock ();
605         if (!sb) {
606                 return;
607         }
608
609         PortGroup::BundleList a = _ports[1-dim].bundles ();
610
611         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
612                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
613
614                         if (!should_show ((*i)->bundle->channel_type(j))) {
615                                 continue;
616                         }
617
618                         BundleChannel c[2];
619                         c[dim] = BundleChannel (sb, channel);
620                         c[1-dim] = BundleChannel ((*i)->bundle, j);
621
622                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
623                                 set_state (c, false);
624                         }
625                 }
626         }
627
628         _body->rebuild_and_draw_grid ();
629 }
630
631 void
632 PortMatrix::setup_global_ports ()
633 {
634         if (!_session || _session->deletion_in_progress()) return;
635         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
636
637         for (int i = 0; i < 2; ++i) {
638                 if (list_is_global (i)) {
639                         setup_ports (i);
640                 }
641         }
642 }
643
644 void
645 PortMatrix::setup_global_ports_proxy ()
646 {
647         /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
648            for a discussion.
649         */
650
651         Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
652 }
653
654 void
655 PortMatrix::setup_all_ports ()
656 {
657         if (_session->deletion_in_progress()) {
658                 return;
659         }
660
661         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
662
663         setup_ports (0);
664         setup_ports (1);
665 }
666
667 void
668 PortMatrix::toggle_show_only_bundles ()
669 {
670         if (_inhibit_toggle_show_only_bundles) {
671                 return;
672         }
673
674         _show_only_bundles = !_show_only_bundles;
675
676         setup ();
677
678         /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
679            setting, so we need to set things up again now.
680         */
681         setup_all_ports ();
682 }
683
684 pair<uint32_t, uint32_t>
685 PortMatrix::max_size () const
686 {
687         pair<uint32_t, uint32_t> m = _body->max_size ();
688
689         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
690         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
691
692         return m;
693 }
694
695 bool
696 PortMatrix::on_scroll_event (GdkEventScroll* ev)
697 {
698         double const h = _hscroll.get_value ();
699         double const v = _vscroll.get_value ();
700
701         switch (ev->direction) {
702         case GDK_SCROLL_UP:
703                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
704                 break;
705         case GDK_SCROLL_DOWN:
706                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
707                 break;
708         case GDK_SCROLL_LEFT:
709                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
710                 break;
711         case GDK_SCROLL_RIGHT:
712                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
713                 break;
714         }
715
716         return true;
717 }
718
719 boost::shared_ptr<IO>
720 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
721 {
722         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
723         if (!io) {
724                 io = _ports[1].io_from_bundle (b);
725         }
726
727         return io;
728 }
729
730 bool
731 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
732 {
733         return io_from_bundle (b) != 0;
734 }
735
736 void
737 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
738 {
739         boost::shared_ptr<IO> io = io_from_bundle (b);
740
741         if (io) {
742                 int const r = io->add_port ("", this, t);
743                 if (r == -1) {
744                         ArdourMessageDialog msg (_("It is not possible to add a port here."));
745                         msg.set_title (_("Cannot add port"));
746                         msg.run ();
747                 }
748         }
749 }
750
751 bool
752 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
753 {
754         return io_from_bundle (b) != 0;
755 }
756
757 void
758 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
759 {
760         std::string errmsg;
761         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
762         boost::shared_ptr<Port> p = io->nth (b.channel);
763
764         if (!io || !p) {
765                 return;
766         }
767
768         if (io->n_ports ().n_total () == 1) {
769                 errmsg = _("The last port cannot be removed");
770         } else {
771                 if (-1 == io->remove_port (p, this)) {
772                         errmsg = _("This port cannot be removed.");
773                 }
774         }
775
776         if (!errmsg.empty ()) {
777                 ArdourDialog d (_("Port removal not allowed"));
778                 Label l (errmsg);
779                 d.get_vbox()->pack_start (l);
780                 d.add_button (Stock::OK, RESPONSE_ACCEPT);
781                 d.set_modal (true);
782                 d.show_all ();
783                 d.run ();
784         }
785 }
786
787 void
788 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
789 {
790         boost::shared_ptr<Bundle> b = w.lock ();
791         if (!b) {
792                 return;
793         }
794
795         /* Remove channels backwards so that we don't renumber channels
796            that we are about to remove.
797         */
798         for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
799                 if (should_show (b->channel_type(i))) {
800                         remove_channel (ARDOUR::BundleChannel (b, i));
801                 }
802         }
803 }
804
805 bool
806 PortMatrix::can_add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t) const
807 {
808         boost::shared_ptr<Bundle> b = w.lock ();
809         if (!b) {
810                 return false;
811         }
812         boost::shared_ptr<IO> io = io_from_bundle (b);
813         return io->can_add_port (t);
814 }
815
816 void
817 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
818 {
819         boost::shared_ptr<Bundle> b = w.lock ();
820         if (!b) {
821                 return;
822         }
823
824         add_channel (b, t);
825 }
826
827 void
828 PortMatrix::setup_notebooks ()
829 {
830         int const h_current_page = _hnotebook.get_current_page ();
831         int const v_current_page = _vnotebook.get_current_page ();
832
833         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
834            when adding or removing pages to or from notebooks, so ignore them */
835
836         _ignore_notebook_page_selected = true;
837
838         remove_notebook_pages (_hnotebook);
839         remove_notebook_pages (_vnotebook);
840
841         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
842                 HBox* dummy = manage (new HBox);
843                 dummy->show ();
844                 Label* label = manage (new Label ((*i)->name));
845                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
846                 label->set_use_markup ();
847                 label->show ();
848                 if (_arrangement == LEFT_TO_BOTTOM) {
849                         _vnotebook.prepend_page (*dummy, *label);
850                 } else {
851                         /* Reverse the order of vertical tabs when they are on the right hand side
852                            so that from top to bottom it is the same order as that from left to right
853                            for the top tabs.
854                         */
855                         _vnotebook.append_page (*dummy, *label);
856                 }
857         }
858
859         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
860                 HBox* dummy = manage (new HBox);
861                 dummy->show ();
862                 Label* label = manage (new Label ((*i)->name));
863                 label->set_use_markup ();
864                 label->show ();
865                 _hnotebook.append_page (*dummy, *label);
866         }
867
868         _ignore_notebook_page_selected = false;
869
870         if (_arrangement == TOP_TO_RIGHT) {
871                 _vnotebook.set_tab_pos (POS_RIGHT);
872                 _hnotebook.set_tab_pos (POS_TOP);
873         } else {
874                 _vnotebook.set_tab_pos (POS_LEFT);
875                 _hnotebook.set_tab_pos (POS_BOTTOM);
876         }
877
878         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
879                 _hnotebook.set_current_page (h_current_page);
880         } else {
881                 _hnotebook.set_current_page (0);
882         }
883
884         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
885                 _vnotebook.set_current_page (v_current_page);
886         } else {
887                 _vnotebook.set_current_page (0);
888         }
889
890         if (_hnotebook.get_n_pages() <= 1) {
891                 _hbox.hide ();
892         } else {
893                 _hbox.show ();
894         }
895
896         if (_vnotebook.get_n_pages() <= 1) {
897                 _vbox.hide ();
898         } else {
899                 _vbox.show ();
900         }
901 }
902
903 void
904 PortMatrix::remove_notebook_pages (Notebook& n)
905 {
906         int const N = n.get_n_pages ();
907
908         for (int i = 0; i < N; ++i) {
909                 n.remove_page ();
910         }
911 }
912
913 void
914 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
915 {
916         if (_ignore_notebook_page_selected) {
917                 return;
918         }
919
920         _body->setup ();
921         setup_scrollbars ();
922         queue_draw ();
923 }
924
925 void
926 PortMatrix::session_going_away ()
927 {
928         _session = 0;
929 }
930
931 void
932 PortMatrix::body_dimensions_changed ()
933 {
934         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
935         if (_arrangement == TOP_TO_RIGHT) {
936                 _vspacer.set_size_request (-1, _body->column_labels_height ());
937                 _vspacer.show ();
938         } else {
939                 _vspacer.hide ();
940         }
941
942         int curr_width;
943         int curr_height;
944         _parent->get_size (curr_width, curr_height);
945
946         pair<uint32_t, uint32_t> m = max_size ();
947
948         /* Don't shrink the window */
949         m.first = max (int (m.first), curr_width);
950         m.second = max (int (m.second), curr_height);
951
952         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
953 }
954
955 /** @return The PortGroup that is currently visible (ie selected by
956  *  the notebook) along a given axis.
957  */
958 boost::shared_ptr<const PortGroup>
959 PortMatrix::visible_ports (int d) const
960 {
961         PortGroupList const & p = _ports[d];
962         PortGroupList::List::const_iterator j = p.begin ();
963
964         /* The logic to compute the index here is a bit twisty because for
965            the TOP_TO_RIGHT arrangement we reverse the order of the vertical
966            tabs in setup_notebooks ().
967         */
968
969         int n = 0;
970         if (d == _row_index) {
971                 if (_arrangement == LEFT_TO_BOTTOM) {
972                         n = p.size() - _vnotebook.get_current_page () - 1;
973                 } else {
974                         n = _vnotebook.get_current_page ();
975                 }
976         } else {
977                 n = _hnotebook.get_current_page ();
978         }
979
980         int i = 0;
981         while (i != int (n) && j != p.end ()) {
982                 ++i;
983                 ++j;
984         }
985
986         if (j == p.end()) {
987                 return boost::shared_ptr<const PortGroup> ();
988         }
989
990         return *j;
991 }
992
993 void
994 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
995 {
996         using namespace Menu_Helpers;
997
998         boost::shared_ptr<Bundle> b = w.lock ();
999         if (!b) {
1000                 return;
1001         }
1002
1003         char buf [64];
1004         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
1005         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
1006 }
1007
1008 void
1009 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
1010 {
1011         using namespace Menu_Helpers;
1012
1013         boost::shared_ptr<Bundle> b = w.lock ();
1014         if (!b) {
1015                 return;
1016         }
1017
1018         char buf [64];
1019         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
1020         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1021 }
1022
1023 void
1024 PortMatrix::port_connected_or_disconnected ()
1025 {
1026         _body->rebuild_and_draw_grid ();
1027         update_tab_highlighting ();
1028 }
1029
1030 /** Update the highlighting of tab names to reflect which ones
1031  *  have connections.  This is pretty inefficient, unfortunately,
1032  *  but maybe that doesn't matter too much.
1033  */
1034 void
1035 PortMatrix::update_tab_highlighting ()
1036 {
1037         if (!_session) {
1038                 return;
1039         }
1040
1041         for (int i = 0; i < 2; ++i) {
1042
1043                 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1044
1045                 PortGroupList const * gl = ports (i);
1046                 int p = 0;
1047                 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1048                         bool has_connection = false;
1049                         PortGroup::BundleList const & bl = (*j)->bundles ();
1050                         PortGroup::BundleList::const_iterator k = bl.begin ();
1051                         while (k != bl.end()) {
1052                                 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1053                                         has_connection = true;
1054                                         break;
1055                                 }
1056                                 ++k;
1057                         }
1058
1059                         /* Find the page index that we should update; this is backwards
1060                            for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1061                         */
1062                         int page = p;
1063                         if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1064                                 page = notebook->get_n_pages() - p - 1;
1065                         }
1066
1067                         Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1068                         string c = label->get_label ();
1069                         if (c.length() && c[0] == '<' && !has_connection) {
1070                                 /* this label is marked up with <b> but shouldn't be */
1071                                 label->set_text ((*j)->name);
1072                         } else if (c.length() && c[0] != '<' && has_connection) {
1073                                 /* this label is not marked up with <b> but should be */
1074                                 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1075                         }
1076
1077                         ++p;
1078                 }
1079         }
1080 }
1081
1082 string
1083 PortMatrix::channel_noun () const
1084 {
1085         return _("channel");
1086 }
1087
1088 /** @return true if this matrix should show bundles / ports of type \t */
1089 bool
1090 PortMatrix::should_show (DataType t) const
1091 {
1092         return (_type == DataType::NIL || t == _type);
1093 }
1094
1095 uint32_t
1096 PortMatrix::count_of_our_type (ChanCount c) const
1097 {
1098         if (_type == DataType::NIL) {
1099                 return c.n_total ();
1100         }
1101
1102         return c.get (_type);
1103 }
1104
1105 /** @return The number of ports of our type in the given channel count,
1106  *  but returning 1 if there are no ports.
1107  */
1108 uint32_t
1109 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1110 {
1111         uint32_t n = count_of_our_type (c);
1112         if (n == 0) {
1113                 n = 1;
1114         }
1115
1116         return n;
1117 }
1118
1119 PortMatrixNode::State
1120 PortMatrix::get_association (PortMatrixNode node) const
1121 {
1122         if (show_only_bundles ()) {
1123
1124                 bool have_off_diagonal_association = false;
1125                 bool have_diagonal_association = false;
1126                 bool have_diagonal_not_association = false;
1127
1128                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1129
1130                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1131
1132                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1133                                         continue;
1134                                 }
1135
1136                                 ARDOUR::BundleChannel c[2];
1137                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1138                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1139
1140                                 PortMatrixNode::State const s = get_state (c);
1141
1142                                 switch (s) {
1143                                 case PortMatrixNode::ASSOCIATED:
1144                                         if (i == j) {
1145                                                 have_diagonal_association = true;
1146                                         } else {
1147                                                 have_off_diagonal_association = true;
1148                                         }
1149                                         break;
1150
1151                                 case PortMatrixNode::NOT_ASSOCIATED:
1152                                         if (i == j) {
1153                                                 have_diagonal_not_association = true;
1154                                         }
1155                                         break;
1156
1157                                 default:
1158                                         break;
1159                                 }
1160                         }
1161                 }
1162
1163                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1164                         return PortMatrixNode::ASSOCIATED;
1165                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1166                         return PortMatrixNode::NOT_ASSOCIATED;
1167                 }
1168
1169                 return PortMatrixNode::PARTIAL;
1170
1171         } else {
1172
1173                 ARDOUR::BundleChannel c[2];
1174                 c[column_index()] = node.column;
1175                 c[row_index()] = node.row;
1176                 return get_state (c);
1177
1178         }
1179
1180         abort(); /* NOTREACHED */
1181         return PortMatrixNode::NOT_ASSOCIATED;
1182 }
1183
1184 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1185 bool
1186 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1187 {
1188         return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1189 }
1190
1191 /** See if a `flip' is possible.
1192  *  @return If flip is possible, the new (row, column) notebook indices that
1193  *  should be selected; otherwise, (-1, -1)
1194  */
1195 pair<int, int>
1196 PortMatrix::check_flip () const
1197 {
1198         /* Look for the row's port group name in the columns */
1199
1200         int new_column = 0;
1201         boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1202         PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1203         while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1204                 ++i;
1205                 ++new_column;
1206         }
1207
1208         if (i == _ports[_column_index].end ()) {
1209                 return make_pair (-1, -1);
1210         }
1211
1212         /* Look for the column's port group name in the rows */
1213
1214         int new_row = 0;
1215         boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1216         i = _ports[_row_index].begin();
1217         while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1218                 ++i;
1219                 ++new_row;
1220         }
1221
1222         if (i == _ports[_row_index].end ()) {
1223                 return make_pair (-1, -1);
1224         }
1225
1226         if (_arrangement == LEFT_TO_BOTTOM) {
1227                 new_row = _ports[_row_index].size() - new_row - 1;
1228         }
1229
1230         return make_pair (new_row, new_column);
1231 }
1232
1233 bool
1234 PortMatrix::can_flip () const
1235 {
1236         return check_flip().first != -1;
1237 }
1238
1239 /** Flip the column and row pages around, if possible */
1240 void
1241 PortMatrix::flip ()
1242 {
1243         pair<int, int> n = check_flip ();
1244         if (n.first == -1) {
1245                 return;
1246         }
1247
1248         _vnotebook.set_current_page (n.first);
1249         _hnotebook.set_current_page (n.second);
1250 }
1251
1252 bool
1253 PortMatrix::key_press (GdkEventKey* k)
1254 {
1255         if (k->keyval == GDK_f) {
1256                 flip ();
1257                 return true;
1258         }
1259
1260         return false;
1261 }
1262
1263 void
1264 PortMatrix::parameter_changed (string p)
1265 {
1266         if (p == "font-scale") {
1267                 setup ();
1268         }
1269 }