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