hide add/remove output buttons on mixer-strip.
[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         std::string errmsg;
751         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
752         boost::shared_ptr<Port> p = io->nth (b.channel);
753
754         if (!io || !p) {
755                 return;
756         }
757
758         if (io->n_ports ().n_total () == 1) {
759                 errmsg = _("The last port cannot be removed");
760         } else {
761                 if (-1 == io->remove_port (p, this)) {
762                         errmsg = _("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.");
763                 }
764         }
765
766         if (!errmsg.empty ()) {
767                 ArdourDialog d (_("Port removal not allowed"));
768                 Label l (errmsg);
769                 d.get_vbox()->pack_start (l);
770                 d.add_button (Stock::OK, RESPONSE_ACCEPT);
771                 d.set_modal (true);
772                 d.show_all ();
773                 d.run ();
774         }
775 }
776
777 void
778 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
779 {
780         boost::shared_ptr<Bundle> b = w.lock ();
781         if (!b) {
782                 return;
783         }
784
785         /* Remove channels backwards so that we don't renumber channels
786            that we are about to remove.
787         */
788         for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
789                 if (should_show (b->channel_type(i))) {
790                         remove_channel (ARDOUR::BundleChannel (b, i));
791                 }
792         }
793 }
794
795 void
796 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
797 {
798         boost::shared_ptr<Bundle> b = w.lock ();
799         if (!b) {
800                 return;
801         }
802
803         add_channel (b, t);
804 }
805
806 void
807 PortMatrix::setup_notebooks ()
808 {
809         int const h_current_page = _hnotebook.get_current_page ();
810         int const v_current_page = _vnotebook.get_current_page ();
811
812         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
813            when adding or removing pages to or from notebooks, so ignore them */
814
815         _ignore_notebook_page_selected = true;
816
817         remove_notebook_pages (_hnotebook);
818         remove_notebook_pages (_vnotebook);
819
820         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
821                 HBox* dummy = manage (new HBox);
822                 dummy->show ();
823                 Label* label = manage (new Label ((*i)->name));
824                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
825                 label->set_use_markup ();
826                 label->show ();
827                 if (_arrangement == LEFT_TO_BOTTOM) {
828                         _vnotebook.prepend_page (*dummy, *label);
829                 } else {
830                         /* Reverse the order of vertical tabs when they are on the right hand side
831                            so that from top to bottom it is the same order as that from left to right
832                            for the top tabs.
833                         */
834                         _vnotebook.append_page (*dummy, *label);
835                 }
836         }
837
838         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
839                 HBox* dummy = manage (new HBox);
840                 dummy->show ();
841                 Label* label = manage (new Label ((*i)->name));
842                 label->set_use_markup ();
843                 label->show ();
844                 _hnotebook.append_page (*dummy, *label);
845         }
846
847         _ignore_notebook_page_selected = false;
848
849         if (_arrangement == TOP_TO_RIGHT) {
850                 _vnotebook.set_tab_pos (POS_RIGHT);
851                 _hnotebook.set_tab_pos (POS_TOP);
852         } else {
853                 _vnotebook.set_tab_pos (POS_LEFT);
854                 _hnotebook.set_tab_pos (POS_BOTTOM);
855         }
856
857         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
858                 _hnotebook.set_current_page (h_current_page);
859         } else {
860                 _hnotebook.set_current_page (0);
861         }
862
863         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
864                 _vnotebook.set_current_page (v_current_page);
865         } else {
866                 _vnotebook.set_current_page (0);
867         }
868
869         if (_hnotebook.get_n_pages() <= 1) {
870                 _hbox.hide ();
871         } else {
872                 _hbox.show ();
873         }
874
875         if (_vnotebook.get_n_pages() <= 1) {
876                 _vbox.hide ();
877         } else {
878                 _vbox.show ();
879         }
880 }
881
882 void
883 PortMatrix::remove_notebook_pages (Notebook& n)
884 {
885         int const N = n.get_n_pages ();
886
887         for (int i = 0; i < N; ++i) {
888                 n.remove_page ();
889         }
890 }
891
892 void
893 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
894 {
895         if (_ignore_notebook_page_selected) {
896                 return;
897         }
898
899         _body->setup ();
900         setup_scrollbars ();
901         queue_draw ();
902 }
903
904 void
905 PortMatrix::session_going_away ()
906 {
907         _session = 0;
908 }
909
910 void
911 PortMatrix::body_dimensions_changed ()
912 {
913         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
914         if (_arrangement == TOP_TO_RIGHT) {
915                 _vspacer.set_size_request (-1, _body->column_labels_height ());
916                 _vspacer.show ();
917         } else {
918                 _vspacer.hide ();
919         }
920
921         int curr_width;
922         int curr_height;
923         _parent->get_size (curr_width, curr_height);
924
925         pair<uint32_t, uint32_t> m = max_size ();
926
927         /* Don't shrink the window */
928         m.first = max (int (m.first), curr_width);
929         m.second = max (int (m.second), curr_height);
930
931         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
932 }
933
934 /** @return The PortGroup that is currently visible (ie selected by
935  *  the notebook) along a given axis.
936  */
937 boost::shared_ptr<const PortGroup>
938 PortMatrix::visible_ports (int d) const
939 {
940         PortGroupList const & p = _ports[d];
941         PortGroupList::List::const_iterator j = p.begin ();
942
943         /* The logic to compute the index here is a bit twisty because for
944            the TOP_TO_RIGHT arrangement we reverse the order of the vertical
945            tabs in setup_notebooks ().
946         */
947
948         int n = 0;
949         if (d == _row_index) {
950                 if (_arrangement == LEFT_TO_BOTTOM) {
951                         n = p.size() - _vnotebook.get_current_page () - 1;
952                 } else {
953                         n = _vnotebook.get_current_page ();
954                 }
955         } else {
956                 n = _hnotebook.get_current_page ();
957         }
958
959         int i = 0;
960         while (i != int (n) && j != p.end ()) {
961                 ++i;
962                 ++j;
963         }
964
965         if (j == p.end()) {
966                 return boost::shared_ptr<const PortGroup> ();
967         }
968
969         return *j;
970 }
971
972 void
973 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
974 {
975         using namespace Menu_Helpers;
976
977         boost::shared_ptr<Bundle> b = w.lock ();
978         if (!b) {
979                 return;
980         }
981
982         char buf [64];
983         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
984         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
985 }
986
987 void
988 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
989 {
990         using namespace Menu_Helpers;
991
992         boost::shared_ptr<Bundle> b = w.lock ();
993         if (!b) {
994                 return;
995         }
996
997         char buf [64];
998         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
999         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
1000 }
1001
1002 void
1003 PortMatrix::port_connected_or_disconnected ()
1004 {
1005         _body->rebuild_and_draw_grid ();
1006         update_tab_highlighting ();
1007 }
1008
1009 /** Update the highlighting of tab names to reflect which ones
1010  *  have connections.  This is pretty inefficient, unfortunately,
1011  *  but maybe that doesn't matter too much.
1012  */
1013 void
1014 PortMatrix::update_tab_highlighting ()
1015 {
1016         if (!_session) {
1017                 return;
1018         }
1019
1020         for (int i = 0; i < 2; ++i) {
1021
1022                 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1023
1024                 PortGroupList const * gl = ports (i);
1025                 int p = 0;
1026                 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1027                         bool has_connection = false;
1028                         PortGroup::BundleList const & bl = (*j)->bundles ();
1029                         PortGroup::BundleList::const_iterator k = bl.begin ();
1030                         while (k != bl.end()) {
1031                                 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1032                                         has_connection = true;
1033                                         break;
1034                                 }
1035                                 ++k;
1036                         }
1037
1038                         /* Find the page index that we should update; this is backwards
1039                            for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1040                         */
1041                         int page = p;
1042                         if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1043                                 page = notebook->get_n_pages() - p - 1;
1044                         }
1045
1046                         Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1047                         string c = label->get_label ();
1048                         if (c.length() && c[0] == '<' && !has_connection) {
1049                                 /* this label is marked up with <b> but shouldn't be */
1050                                 label->set_text ((*j)->name);
1051                         } else if (c.length() && c[0] != '<' && has_connection) {
1052                                 /* this label is not marked up with <b> but should be */
1053                                 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1054                         }
1055
1056                         ++p;
1057                 }
1058         }
1059 }
1060
1061 string
1062 PortMatrix::channel_noun () const
1063 {
1064         return _("channel");
1065 }
1066
1067 /** @return true if this matrix should show bundles / ports of type \t */
1068 bool
1069 PortMatrix::should_show (DataType t) const
1070 {
1071         return (_type == DataType::NIL || t == _type);
1072 }
1073
1074 uint32_t
1075 PortMatrix::count_of_our_type (ChanCount c) const
1076 {
1077         if (_type == DataType::NIL) {
1078                 return c.n_total ();
1079         }
1080
1081         return c.get (_type);
1082 }
1083
1084 /** @return The number of ports of our type in the given channel count,
1085  *  but returning 1 if there are no ports.
1086  */
1087 uint32_t
1088 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1089 {
1090         uint32_t n = count_of_our_type (c);
1091         if (n == 0) {
1092                 n = 1;
1093         }
1094
1095         return n;
1096 }
1097
1098 PortMatrixNode::State
1099 PortMatrix::get_association (PortMatrixNode node) const
1100 {
1101         if (show_only_bundles ()) {
1102
1103                 bool have_off_diagonal_association = false;
1104                 bool have_diagonal_association = false;
1105                 bool have_diagonal_not_association = false;
1106
1107                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1108
1109                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1110
1111                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1112                                         continue;
1113                                 }
1114
1115                                 ARDOUR::BundleChannel c[2];
1116                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1117                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1118
1119                                 PortMatrixNode::State const s = get_state (c);
1120
1121                                 switch (s) {
1122                                 case PortMatrixNode::ASSOCIATED:
1123                                         if (i == j) {
1124                                                 have_diagonal_association = true;
1125                                         } else {
1126                                                 have_off_diagonal_association = true;
1127                                         }
1128                                         break;
1129
1130                                 case PortMatrixNode::NOT_ASSOCIATED:
1131                                         if (i == j) {
1132                                                 have_diagonal_not_association = true;
1133                                         }
1134                                         break;
1135
1136                                 default:
1137                                         break;
1138                                 }
1139                         }
1140                 }
1141
1142                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1143                         return PortMatrixNode::ASSOCIATED;
1144                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1145                         return PortMatrixNode::NOT_ASSOCIATED;
1146                 }
1147
1148                 return PortMatrixNode::PARTIAL;
1149
1150         } else {
1151
1152                 ARDOUR::BundleChannel c[2];
1153                 c[column_index()] = node.column;
1154                 c[row_index()] = node.row;
1155                 return get_state (c);
1156
1157         }
1158
1159         abort(); /* NOTREACHED */
1160         return PortMatrixNode::NOT_ASSOCIATED;
1161 }
1162
1163 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1164 bool
1165 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1166 {
1167         return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1168 }
1169
1170 /** See if a `flip' is possible.
1171  *  @return If flip is possible, the new (row, column) notebook indices that
1172  *  should be selected; otherwise, (-1, -1)
1173  */
1174 pair<int, int>
1175 PortMatrix::check_flip () const
1176 {
1177         /* Look for the row's port group name in the columns */
1178
1179         int new_column = 0;
1180         boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1181         PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1182         while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1183                 ++i;
1184                 ++new_column;
1185         }
1186
1187         if (i == _ports[_column_index].end ()) {
1188                 return make_pair (-1, -1);
1189         }
1190
1191         /* Look for the column's port group name in the rows */
1192
1193         int new_row = 0;
1194         boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1195         i = _ports[_row_index].begin();
1196         while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1197                 ++i;
1198                 ++new_row;
1199         }
1200
1201         if (i == _ports[_row_index].end ()) {
1202                 return make_pair (-1, -1);
1203         }
1204
1205         if (_arrangement == LEFT_TO_BOTTOM) {
1206                 new_row = _ports[_row_index].size() - new_row - 1;
1207         }
1208
1209         return make_pair (new_row, new_column);
1210 }
1211
1212 bool
1213 PortMatrix::can_flip () const
1214 {
1215         return check_flip().first != -1;
1216 }
1217
1218 /** Flip the column and row pages around, if possible */
1219 void
1220 PortMatrix::flip ()
1221 {
1222         pair<int, int> n = check_flip ();
1223         if (n.first == -1) {
1224                 return;
1225         }
1226
1227         _vnotebook.set_current_page (n.first);
1228         _hnotebook.set_current_page (n.second);
1229 }
1230
1231 bool
1232 PortMatrix::key_press (GdkEventKey* k)
1233 {
1234         if (k->keyval == GDK_f) {
1235                 flip ();
1236                 return true;
1237         }
1238
1239         return false;
1240 }