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