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