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