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