Merge branch 'master' into windows+cc
[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         _session->BundleRemoved.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
156
157         /* and also ports */
158         _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
159
160         /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
161         Route::SyncOrderKeys.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this, _1), gui_context());
162
163         /* Part 3: other stuff */
164
165         _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
166
167         _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
168         _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
169
170         reconnect_to_routes ();
171
172         setup ();
173 }
174
175 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
176 void
177 PortMatrix::reconnect_to_routes ()
178 {
179         _route_connections.drop_connections ();
180
181         boost::shared_ptr<RouteList> routes = _session->get_routes ();
182         for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
183                 (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
184                 (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
185         }
186 }
187
188 void
189 PortMatrix::route_processors_changed (RouteProcessorChange c)
190 {
191         if (c.type == RouteProcessorChange::MeterPointChange) {
192                 /* this change has no impact on the port matrix */
193                 return;
194         }
195
196         setup_global_ports ();
197 }
198
199 /** A route has been added to or removed from the session */
200 void
201 PortMatrix::routes_changed ()
202 {
203         if (!_session) return;
204         reconnect_to_routes ();
205         setup_global_ports ();
206 }
207
208 /** Set up everything that depends on the content of _ports[] */
209 void
210 PortMatrix::setup ()
211 {
212         if (!_session) {
213                 _route_connections.drop_connections ();
214                 return; // session went away
215         }
216
217         /* this needs to be done first, as the visible_ports() method uses the
218            notebook state to decide which ports are being shown */
219
220         setup_notebooks ();
221
222         _body->setup ();
223         setup_scrollbars ();
224         update_tab_highlighting ();
225         queue_draw ();
226 }
227
228 void
229 PortMatrix::set_type (DataType t)
230 {
231         _type = t;
232 }
233
234 void
235 PortMatrix::hscroll_changed ()
236 {
237         _body->set_xoffset (_hscroll.get_adjustment()->get_value());
238 }
239
240 void
241 PortMatrix::vscroll_changed ()
242 {
243         _body->set_yoffset (_vscroll.get_adjustment()->get_value());
244 }
245
246 void
247 PortMatrix::setup_scrollbars ()
248 {
249         Adjustment* a = _hscroll.get_adjustment ();
250         a->set_lower (0);
251         a->set_upper (_body->full_scroll_width());
252         a->set_page_size (_body->alloc_scroll_width());
253         a->set_step_increment (32);
254         a->set_page_increment (128);
255
256         a = _vscroll.get_adjustment ();
257         a->set_lower (0);
258         a->set_upper (_body->full_scroll_height());
259         a->set_page_size (_body->alloc_scroll_height());
260         a->set_step_increment (32);
261         a->set_page_increment (128);
262 }
263
264 /** Disassociate all of our ports from each other */
265 void
266 PortMatrix::disassociate_all ()
267 {
268         PortGroup::BundleList a = _ports[0].bundles ();
269         PortGroup::BundleList b = _ports[1].bundles ();
270
271         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
272                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
273                         for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
274                                 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
275
276                                         if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
277                                                 continue;
278                                         }
279
280                                         BundleChannel c[2] = {
281                                                 BundleChannel ((*i)->bundle, j),
282                                                 BundleChannel ((*k)->bundle, l)
283                                                         };
284
285                                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
286                                                 set_state (c, false);
287                                         }
288
289                                 }
290                         }
291                 }
292         }
293
294         _body->rebuild_and_draw_grid ();
295 }
296
297 /* Decide how to arrange the components of the matrix */
298 void
299 PortMatrix::select_arrangement ()
300 {
301         uint32_t const N[2] = {
302                 count_of_our_type_min_1 (_ports[0].total_channels()),
303                 count_of_our_type_min_1 (_ports[1].total_channels())
304         };
305
306         /* XXX: shirley there's an easier way than this */
307
308         if (_vspacer.get_parent()) {
309                 _vbox.remove (_vspacer);
310         }
311
312         if (_vnotebook.get_parent()) {
313                 _vbox.remove (_vnotebook);
314         }
315
316         if (_vlabel.get_parent()) {
317                 _vbox.remove (_vlabel);
318         }
319
320         /* The list with the most channels goes on left or right, so that the most channel
321            names are printed horizontally and hence more readable.  However we also
322            maintain notional `signal flow' vaguely from left to right.  Subclasses
323            should choose where to put ports based on signal flowing from _ports[0]
324            to _ports[1] */
325
326         if (N[0] > N[1]) {
327
328                 _row_index = 0;
329                 _column_index = 1;
330                 _arrangement = LEFT_TO_BOTTOM;
331                 _vlabel.set_label (_("<b>Sources</b>"));
332                 _hlabel.set_label (_("<b>Destinations</b>"));
333                 _vlabel.set_angle (90);
334
335                 _vbox.pack_end (_vlabel, false, false);
336                 _vbox.pack_end (_vnotebook, false, false);
337                 _vbox.pack_end (_vspacer, true, true);
338
339 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
340                 REMOVE_FROM_GTK_PARENT(*_body)
341                 REMOVE_FROM_GTK_PARENT(_vscroll)
342                 REMOVE_FROM_GTK_PARENT(_hscroll)
343                 REMOVE_FROM_GTK_PARENT(_vbox)
344                 REMOVE_FROM_GTK_PARENT(_hbox)
345
346                 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
347                 attach (_vscroll, 3, 4, 1, 2, SHRINK);
348                 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
349                 attach (_vbox, 1, 2, 1, 2, SHRINK);
350                 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
351
352         } else {
353
354                 _row_index = 1;
355                 _column_index = 0;
356                 _arrangement = TOP_TO_RIGHT;
357                 _hlabel.set_label (_("<b>Sources</b>"));
358                 _vlabel.set_label (_("<b>Destinations</b>"));
359                 _vlabel.set_angle (-90);
360
361                 _vbox.pack_end (_vspacer, true, true);
362                 _vbox.pack_end (_vnotebook, false, false);
363                 _vbox.pack_end (_vlabel, false, false);
364
365                 REMOVE_FROM_GTK_PARENT(*_body)
366                 REMOVE_FROM_GTK_PARENT(_vscroll)
367                 REMOVE_FROM_GTK_PARENT(_hscroll)
368                 REMOVE_FROM_GTK_PARENT(_vbox)
369                 REMOVE_FROM_GTK_PARENT(_hbox)
370
371                 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
372                 attach (_vscroll, 3, 4, 2, 3, SHRINK);
373                 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
374                 attach (_vbox, 2, 3, 2, 3, SHRINK);
375                 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
376         }
377 }
378
379 /** @return columns list */
380 PortGroupList const *
381 PortMatrix::columns () const
382 {
383         return &_ports[_column_index];
384 }
385
386 boost::shared_ptr<const PortGroup>
387 PortMatrix::visible_columns () const
388 {
389         return visible_ports (_column_index);
390 }
391
392 /* @return rows list */
393 PortGroupList const *
394 PortMatrix::rows () const
395 {
396         return &_ports[_row_index];
397 }
398
399 boost::shared_ptr<const PortGroup>
400 PortMatrix::visible_rows () const
401 {
402         return visible_ports (_row_index);
403 }
404
405 /** @param column Column; its bundle may be 0 if we are over a row heading.
406  *  @param row Row; its bundle may be 0 if we are over a column heading.
407  */
408 void
409 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
410 {
411         using namespace Menu_Helpers;
412
413         delete _menu;
414
415         _menu = new Menu;
416         _menu->set_name ("ArdourContextMenu");
417
418         MenuList& items = _menu->items ();
419
420         BundleChannel bc[2];
421         bc[_column_index] = column;
422         bc[_row_index] = row;
423
424         char buf [64];
425         bool need_separator = false;
426
427         for (int dim = 0; dim < 2; ++dim) {
428
429                 if (bc[dim].bundle) {
430
431                         Menu* m = manage (new Menu);
432                         MenuList& sub = m->items ();
433
434                         boost::weak_ptr<Bundle> w (bc[dim].bundle);
435
436                         if (can_add_channels (bc[dim].bundle)) {
437                                 /* Start off with options for the `natural' port type */
438                                 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
439                                         if (should_show (*i)) {
440                                                 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
441                                                 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
442                                         }
443                                 }
444                                 
445                                 /* Now add other ones */
446                                 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
447                                         if (!should_show (*i)) {
448                                                 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
449                                                 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
450                                         }
451                                 }
452                         }
453
454                         if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
455                                 snprintf (
456                                         buf, sizeof (buf), _("Rename '%s'..."),
457                                         escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
458                                         );
459                                 sub.push_back (
460                                         MenuElem (
461                                                 buf,
462                                                 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
463                                                 )
464                                         );
465                         }
466
467                         if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
468                                 if (bc[dim].channel != -1) {
469                                         add_remove_option (sub, w, bc[dim].channel);
470                                 } else {
471                                         sub.push_back (
472                                                 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
473                                                 );
474
475                                         if (bc[dim].bundle->nchannels().n_total() > 1) {
476                                                 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
477                                                         if (should_show (bc[dim].bundle->channel_type(i))) {
478                                                                 add_remove_option (sub, w, i);
479                                                         }
480                                                 }
481                                         }
482                                 }
483                         }
484
485                         uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
486                         if ((_show_only_bundles && c > 0) || c == 1) {
487
488                                 /* we're looking just at bundles, or our bundle has only one channel, so just offer
489                                    to disassociate all on the bundle.
490                                 */
491                                 
492                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
493                                 sub.push_back (
494                                         MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
495                                         );
496                                         
497                         } else if (c != 0) {
498
499                                 if (bc[dim].channel != -1) {
500                                         /* specific channel under the menu, so just offer to disassociate that */
501                                         add_disassociate_option (sub, w, dim, bc[dim].channel);
502                                 } else {
503                                         /* no specific channel; offer to disassociate all, or any one in particular */
504                                         snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
505                                         sub.push_back (
506                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
507                                                 );
508
509                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
510                                                 if (should_show (bc[dim].bundle->channel_type(i))) {
511                                                         add_disassociate_option (sub, w, dim, i);
512                                                 }
513                                         }
514                                 }
515                         }
516
517                         items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
518                         need_separator = true;
519                 }
520
521         }
522
523         if (need_separator) {
524                 items.push_back (SeparatorElem ());
525         }
526
527         items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
528
529         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
530         Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
531         _inhibit_toggle_show_only_bundles = true;
532         i->set_active (!_show_only_bundles);
533         _inhibit_toggle_show_only_bundles = false;
534
535         items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
536         items.back().set_sensitive (can_flip ());
537         
538         _menu->popup (1, t);
539 }
540
541 void
542 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
543 {
544         boost::shared_ptr<Bundle> sb = b.lock ();
545         if (!sb) {
546                 return;
547         }
548
549         remove_channel (BundleChannel (sb, c));
550
551 }
552
553 void
554 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
555 {
556         boost::shared_ptr<Bundle> sb = b.lock ();
557         if (!sb) {
558                 return;
559         }
560
561         rename_channel (BundleChannel (sb, c));
562 }
563
564 void
565 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
566 {
567         boost::shared_ptr<Bundle> sb = bundle.lock ();
568         if (!sb) {
569                 return;
570         }
571
572         for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
573                 if (should_show (sb->channel_type(i))) {
574                         disassociate_all_on_channel (bundle, i, dim);
575                 }
576         }
577 }
578
579 void
580 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
581 {
582         boost::shared_ptr<Bundle> sb = bundle.lock ();
583         if (!sb) {
584                 return;
585         }
586
587         PortGroup::BundleList a = _ports[1-dim].bundles ();
588
589         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
590                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
591
592                         if (!should_show ((*i)->bundle->channel_type(j))) {
593                                 continue;
594                         }
595
596                         BundleChannel c[2];
597                         c[dim] = BundleChannel (sb, channel);
598                         c[1-dim] = BundleChannel ((*i)->bundle, j);
599
600                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
601                                 set_state (c, false);
602                         }
603                 }
604         }
605
606         _body->rebuild_and_draw_grid ();
607 }
608
609 void
610 PortMatrix::setup_global_ports ()
611 {
612         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
613
614         for (int i = 0; i < 2; ++i) {
615                 if (list_is_global (i)) {
616                         setup_ports (i);
617                 }
618         }
619 }
620
621 void
622 PortMatrix::setup_global_ports_proxy (RouteSortOrderKey sk)
623 {
624         if (sk == EditorSort) {
625                 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
626                    for a discussion.
627                 */
628                 
629                 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
630         }
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);
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);
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         /* 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 }