Merge branch 'master' into windows
[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                 attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
340                 attach (_vscroll, 3, 4, 1, 2, SHRINK);
341                 attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
342                 attach (_vbox, 1, 2, 1, 2, SHRINK);
343                 attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
344
345         } else {
346
347                 _row_index = 1;
348                 _column_index = 0;
349                 _arrangement = TOP_TO_RIGHT;
350                 _hlabel.set_label (_("<b>Sources</b>"));
351                 _vlabel.set_label (_("<b>Destinations</b>"));
352                 _vlabel.set_angle (-90);
353
354                 _vbox.pack_end (_vspacer, true, true);
355                 _vbox.pack_end (_vnotebook, false, false);
356                 _vbox.pack_end (_vlabel, false, false);
357
358                 attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
359                 attach (_vscroll, 3, 4, 2, 3, SHRINK);
360                 attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
361                 attach (_vbox, 2, 3, 2, 3, SHRINK);
362                 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
363         }
364 }
365
366 /** @return columns list */
367 PortGroupList const *
368 PortMatrix::columns () const
369 {
370         return &_ports[_column_index];
371 }
372
373 boost::shared_ptr<const PortGroup>
374 PortMatrix::visible_columns () const
375 {
376         return visible_ports (_column_index);
377 }
378
379 /* @return rows list */
380 PortGroupList const *
381 PortMatrix::rows () const
382 {
383         return &_ports[_row_index];
384 }
385
386 boost::shared_ptr<const PortGroup>
387 PortMatrix::visible_rows () const
388 {
389         return visible_ports (_row_index);
390 }
391
392 /** @param column Column; its bundle may be 0 if we are over a row heading.
393  *  @param row Row; its bundle may be 0 if we are over a column heading.
394  */
395 void
396 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
397 {
398         using namespace Menu_Helpers;
399
400         delete _menu;
401
402         _menu = new Menu;
403         _menu->set_name ("ArdourContextMenu");
404
405         MenuList& items = _menu->items ();
406
407         BundleChannel bc[2];
408         bc[_column_index] = column;
409         bc[_row_index] = row;
410
411         char buf [64];
412         bool need_separator = false;
413
414         for (int dim = 0; dim < 2; ++dim) {
415
416                 if (bc[dim].bundle) {
417
418                         Menu* m = manage (new Menu);
419                         MenuList& sub = m->items ();
420
421                         boost::weak_ptr<Bundle> w (bc[dim].bundle);
422
423                         if (can_add_channels (bc[dim].bundle)) {
424                                 /* Start off with options for the `natural' port type */
425                                 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
426                                         if (should_show (*i)) {
427                                                 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
428                                                 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
429                                         }
430                                 }
431                                 
432                                 /* Now add other ones */
433                                 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
434                                         if (!should_show (*i)) {
435                                                 snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
436                                                 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
437                                         }
438                                 }
439                         }
440
441                         if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
442                                 snprintf (
443                                         buf, sizeof (buf), _("Rename '%s'..."),
444                                         escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
445                                         );
446                                 sub.push_back (
447                                         MenuElem (
448                                                 buf,
449                                                 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
450                                                 )
451                                         );
452                         }
453
454                         if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
455                                 if (bc[dim].channel != -1) {
456                                         add_remove_option (sub, w, bc[dim].channel);
457                                 } else {
458                                         sub.push_back (
459                                                 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
460                                                 );
461
462                                         if (bc[dim].bundle->nchannels().n_total() > 1) {
463                                                 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
464                                                         if (should_show (bc[dim].bundle->channel_type(i))) {
465                                                                 add_remove_option (sub, w, i);
466                                                         }
467                                                 }
468                                         }
469                                 }
470                         }
471
472                         uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
473                         if ((_show_only_bundles && c > 0) || c == 1) {
474
475                                 /* we're looking just at bundles, or our bundle has only one channel, so just offer
476                                    to disassociate all on the bundle.
477                                 */
478                                 
479                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
480                                 sub.push_back (
481                                         MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
482                                         );
483                                         
484                         } else if (c != 0) {
485
486                                 if (bc[dim].channel != -1) {
487                                         /* specific channel under the menu, so just offer to disassociate that */
488                                         add_disassociate_option (sub, w, dim, bc[dim].channel);
489                                 } else {
490                                         /* no specific channel; offer to disassociate all, or any one in particular */
491                                         snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
492                                         sub.push_back (
493                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
494                                                 );
495
496                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
497                                                 if (should_show (bc[dim].bundle->channel_type(i))) {
498                                                         add_disassociate_option (sub, w, dim, i);
499                                                 }
500                                         }
501                                 }
502                         }
503
504                         items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
505                         need_separator = true;
506                 }
507
508         }
509
510         if (need_separator) {
511                 items.push_back (SeparatorElem ());
512         }
513
514         items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
515
516         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
517         Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
518         _inhibit_toggle_show_only_bundles = true;
519         i->set_active (!_show_only_bundles);
520         _inhibit_toggle_show_only_bundles = false;
521
522         items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
523         items.back().set_sensitive (can_flip ());
524         
525         _menu->popup (1, t);
526 }
527
528 void
529 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
530 {
531         boost::shared_ptr<Bundle> sb = b.lock ();
532         if (!sb) {
533                 return;
534         }
535
536         remove_channel (BundleChannel (sb, c));
537
538 }
539
540 void
541 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
542 {
543         boost::shared_ptr<Bundle> sb = b.lock ();
544         if (!sb) {
545                 return;
546         }
547
548         rename_channel (BundleChannel (sb, c));
549 }
550
551 void
552 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
553 {
554         boost::shared_ptr<Bundle> sb = bundle.lock ();
555         if (!sb) {
556                 return;
557         }
558
559         for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
560                 if (should_show (sb->channel_type(i))) {
561                         disassociate_all_on_channel (bundle, i, dim);
562                 }
563         }
564 }
565
566 void
567 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
568 {
569         boost::shared_ptr<Bundle> sb = bundle.lock ();
570         if (!sb) {
571                 return;
572         }
573
574         PortGroup::BundleList a = _ports[1-dim].bundles ();
575
576         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
577                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
578
579                         if (!should_show ((*i)->bundle->channel_type(j))) {
580                                 continue;
581                         }
582
583                         BundleChannel c[2];
584                         c[dim] = BundleChannel (sb, channel);
585                         c[1-dim] = BundleChannel ((*i)->bundle, j);
586
587                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
588                                 set_state (c, false);
589                         }
590                 }
591         }
592
593         _body->rebuild_and_draw_grid ();
594 }
595
596 void
597 PortMatrix::setup_global_ports ()
598 {
599         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
600
601         for (int i = 0; i < 2; ++i) {
602                 if (list_is_global (i)) {
603                         setup_ports (i);
604                 }
605         }
606 }
607
608 void
609 PortMatrix::setup_global_ports_proxy (RouteSortOrderKey sk)
610 {
611         if (sk == EditorSort) {
612                 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
613                    for a discussion.
614                 */
615                 
616                 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
617         }
618 }
619
620 void
621 PortMatrix::setup_all_ports ()
622 {
623         if (_session->deletion_in_progress()) {
624                 return;
625         }
626
627         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
628
629         setup_ports (0);
630         setup_ports (1);
631 }
632
633 void
634 PortMatrix::toggle_show_only_bundles ()
635 {
636         if (_inhibit_toggle_show_only_bundles) {
637                 return;
638         }
639
640         _show_only_bundles = !_show_only_bundles;
641
642         setup ();
643
644         /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
645            setting, so we need to set things up again now.
646         */
647         setup_all_ports ();
648 }
649
650 pair<uint32_t, uint32_t>
651 PortMatrix::max_size () const
652 {
653         pair<uint32_t, uint32_t> m = _body->max_size ();
654
655         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
656         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
657
658         return m;
659 }
660
661 bool
662 PortMatrix::on_scroll_event (GdkEventScroll* ev)
663 {
664         double const h = _hscroll.get_value ();
665         double const v = _vscroll.get_value ();
666
667         switch (ev->direction) {
668         case GDK_SCROLL_UP:
669                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
670                 break;
671         case GDK_SCROLL_DOWN:
672                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
673                 break;
674         case GDK_SCROLL_LEFT:
675                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
676                 break;
677         case GDK_SCROLL_RIGHT:
678                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
679                 break;
680         }
681
682         return true;
683 }
684
685 boost::shared_ptr<IO>
686 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
687 {
688         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
689         if (!io) {
690                 io = _ports[1].io_from_bundle (b);
691         }
692
693         return io;
694 }
695
696 bool
697 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
698 {
699         return io_from_bundle (b);
700 }
701
702 void
703 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
704 {
705         boost::shared_ptr<IO> io = io_from_bundle (b);
706
707         if (io) {
708                 int const r = io->add_port ("", this, t);
709                 if (r == -1) {
710                         Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
711                                                   "support the new configuration."
712                                                         ));
713                         msg.set_title (_("Cannot add port"));
714                         msg.run ();
715                 }
716         }
717 }
718
719 bool
720 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
721 {
722         return io_from_bundle (b);
723 }
724
725 void
726 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
727 {
728         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
729
730         if (io) {
731                 boost::shared_ptr<Port> p = io->nth (b.channel);
732                 if (p) {
733                         int const r = io->remove_port (p, this);
734                         if (r == -1) {
735                                 ArdourDialog d (_("Port removal not allowed"));
736                                 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
737                                 d.get_vbox()->pack_start (l);
738                                 d.add_button (Stock::OK, RESPONSE_ACCEPT);
739                                 d.set_modal (true);
740                                 d.show_all ();
741                                 d.run ();
742                         }
743                 }
744         }
745 }
746
747 void
748 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
749 {
750         boost::shared_ptr<Bundle> b = w.lock ();
751         if (!b) {
752                 return;
753         }
754
755         /* Remove channels backwards so that we don't renumber channels
756            that we are about to remove.
757         */
758         for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
759                 if (should_show (b->channel_type(i))) {
760                         remove_channel (ARDOUR::BundleChannel (b, i));
761                 }
762         }
763 }
764
765 void
766 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
767 {
768         boost::shared_ptr<Bundle> b = w.lock ();
769         if (!b) {
770                 return;
771         }
772
773         add_channel (b, t);
774 }
775
776 void
777 PortMatrix::setup_notebooks ()
778 {
779         int const h_current_page = _hnotebook.get_current_page ();
780         int const v_current_page = _vnotebook.get_current_page ();
781
782         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
783            when adding or removing pages to or from notebooks, so ignore them */
784
785         _ignore_notebook_page_selected = true;
786
787         remove_notebook_pages (_hnotebook);
788         remove_notebook_pages (_vnotebook);
789
790         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
791                 HBox* dummy = manage (new HBox);
792                 dummy->show ();
793                 Label* label = manage (new Label ((*i)->name));
794                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
795                 label->set_use_markup ();
796                 label->show ();
797                 if (_arrangement == LEFT_TO_BOTTOM) {
798                         _vnotebook.prepend_page (*dummy, *label);
799                 } else {
800                         /* Reverse the order of vertical tabs when they are on the right hand side
801                            so that from top to bottom it is the same order as that from left to right
802                            for the top tabs.
803                         */
804                         _vnotebook.append_page (*dummy, *label);
805                 }
806         }
807
808         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
809                 HBox* dummy = manage (new HBox);
810                 dummy->show ();
811                 Label* label = manage (new Label ((*i)->name));
812                 label->set_use_markup ();
813                 label->show ();
814                 _hnotebook.append_page (*dummy, *label);
815         }
816
817         _ignore_notebook_page_selected = false;
818
819         if (_arrangement == TOP_TO_RIGHT) {
820                 _vnotebook.set_tab_pos (POS_RIGHT);
821                 _hnotebook.set_tab_pos (POS_TOP);
822         } else {
823                 _vnotebook.set_tab_pos (POS_LEFT);
824                 _hnotebook.set_tab_pos (POS_BOTTOM);
825         }
826
827         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
828                 _hnotebook.set_current_page (h_current_page);
829         } else {
830                 _hnotebook.set_current_page (0);
831         }
832
833         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
834                 _vnotebook.set_current_page (v_current_page);
835         } else {
836                 _vnotebook.set_current_page (0);
837         }
838
839         if (_hnotebook.get_n_pages() <= 1) {
840                 _hbox.hide ();
841         } else {
842                 _hbox.show ();
843         }
844
845         if (_vnotebook.get_n_pages() <= 1) {
846                 _vbox.hide ();
847         } else {
848                 _vbox.show ();
849         }
850 }
851
852 void
853 PortMatrix::remove_notebook_pages (Notebook& n)
854 {
855         int const N = n.get_n_pages ();
856
857         for (int i = 0; i < N; ++i) {
858                 n.remove_page ();
859         }
860 }
861
862 void
863 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
864 {
865         if (_ignore_notebook_page_selected) {
866                 return;
867         }
868
869         _body->setup ();
870         setup_scrollbars ();
871         queue_draw ();
872 }
873
874 void
875 PortMatrix::session_going_away ()
876 {
877         _session = 0;
878 }
879
880 void
881 PortMatrix::body_dimensions_changed ()
882 {
883         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
884         if (_arrangement == TOP_TO_RIGHT) {
885                 _vspacer.set_size_request (-1, _body->column_labels_height ());
886                 _vspacer.show ();
887         } else {
888                 _vspacer.hide ();
889         }
890
891         int curr_width;
892         int curr_height;
893         _parent->get_size (curr_width, curr_height);
894
895         pair<uint32_t, uint32_t> m = max_size ();
896
897         /* Don't shrink the window */
898         m.first = max (int (m.first), curr_width);
899         m.second = max (int (m.second), curr_height);
900
901         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
902 }
903
904 /** @return The PortGroup that is currently visible (ie selected by
905  *  the notebook) along a given axis.
906  */
907 boost::shared_ptr<const PortGroup>
908 PortMatrix::visible_ports (int d) const
909 {
910         PortGroupList const & p = _ports[d];
911         PortGroupList::List::const_iterator j = p.begin ();
912
913         /* The logic to compute the index here is a bit twisty because for
914            the TOP_TO_RIGHT arrangement we reverse the order of the vertical
915            tabs in setup_notebooks ().
916         */
917            
918         int n = 0;
919         if (d == _row_index) {
920                 if (_arrangement == LEFT_TO_BOTTOM) {
921                         n = p.size() - _vnotebook.get_current_page () - 1;
922                 } else {
923                         n = _vnotebook.get_current_page ();
924                 }
925         } else {
926                 n = _hnotebook.get_current_page ();
927         }
928
929         int i = 0;
930         while (i != int (n) && j != p.end ()) {
931                 ++i;
932                 ++j;
933         }
934
935         if (j == p.end()) {
936                 return boost::shared_ptr<const PortGroup> ();
937         }
938
939         return *j;
940 }
941
942 void
943 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
944 {
945         using namespace Menu_Helpers;
946
947         boost::shared_ptr<Bundle> b = w.lock ();
948         if (!b) {
949                 return;
950         }
951
952         char buf [64];
953         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
954         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
955 }
956
957 void
958 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
959 {
960         using namespace Menu_Helpers;
961
962         boost::shared_ptr<Bundle> b = w.lock ();
963         if (!b) {
964                 return;
965         }
966
967         char buf [64];
968         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
969         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
970 }
971
972 void
973 PortMatrix::port_connected_or_disconnected ()
974 {
975         _body->rebuild_and_draw_grid ();
976         update_tab_highlighting ();
977 }
978
979 /** Update the highlighting of tab names to reflect which ones
980  *  have connections.  This is pretty inefficient, unfortunately,
981  *  but maybe that doesn't matter too much.
982  */
983 void
984 PortMatrix::update_tab_highlighting ()
985 {
986         if (!_session) {
987                 return;
988         }
989         
990         for (int i = 0; i < 2; ++i) {
991
992                 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
993                 
994                 PortGroupList const * gl = ports (i);
995                 int p = 0;
996                 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
997                         bool has_connection = false;
998                         PortGroup::BundleList const & bl = (*j)->bundles ();
999                         PortGroup::BundleList::const_iterator k = bl.begin ();
1000                         while (k != bl.end()) {
1001                                 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1002                                         has_connection = true;
1003                                         break;
1004                                 }
1005                                 ++k;
1006                         }
1007
1008                         /* Find the page index that we should update; this is backwards
1009                            for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1010                         */
1011                         int page = p;
1012                         if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1013                                 page = notebook->get_n_pages() - p - 1;
1014                         }
1015
1016                         Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1017                         string c = label->get_label ();
1018                         if (c.length() && c[0] == '<' && !has_connection) {
1019                                 /* this label is marked up with <b> but shouldn't be */
1020                                 label->set_text ((*j)->name);
1021                         } else if (c.length() && c[0] != '<' && has_connection) {
1022                                 /* this label is not marked up with <b> but should be */
1023                                 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1024                         }
1025
1026                         ++p;
1027                 }
1028         }
1029 }
1030
1031 string
1032 PortMatrix::channel_noun () const
1033 {
1034         return _("channel");
1035 }
1036
1037 /** @return true if this matrix should show bundles / ports of type \t */
1038 bool
1039 PortMatrix::should_show (DataType t) const
1040 {
1041         return (_type == DataType::NIL || t == _type);
1042 }
1043
1044 uint32_t
1045 PortMatrix::count_of_our_type (ChanCount c) const
1046 {
1047         if (_type == DataType::NIL) {
1048                 return c.n_total ();
1049         }
1050
1051         return c.get (_type);
1052 }
1053
1054 /** @return The number of ports of our type in the given channel count,
1055  *  but returning 1 if there are no ports.
1056  */
1057 uint32_t
1058 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1059 {
1060         uint32_t n = count_of_our_type (c);
1061         if (n == 0) {
1062                 n = 1;
1063         }
1064
1065         return n;
1066 }
1067
1068 PortMatrixNode::State
1069 PortMatrix::get_association (PortMatrixNode node) const
1070 {
1071         if (show_only_bundles ()) {
1072
1073                 bool have_off_diagonal_association = false;
1074                 bool have_diagonal_association = false;
1075                 bool have_diagonal_not_association = false;
1076
1077                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1078
1079                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1080
1081                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1082                                         continue;
1083                                 }
1084
1085                                 ARDOUR::BundleChannel c[2];
1086                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1087                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1088
1089                                 PortMatrixNode::State const s = get_state (c);
1090
1091                                 switch (s) {
1092                                 case PortMatrixNode::ASSOCIATED:
1093                                         if (i == j) {
1094                                                 have_diagonal_association = true;
1095                                         } else {
1096                                                 have_off_diagonal_association = true;
1097                                         }
1098                                         break;
1099
1100                                 case PortMatrixNode::NOT_ASSOCIATED:
1101                                         if (i == j) {
1102                                                 have_diagonal_not_association = true;
1103                                         }
1104                                         break;
1105
1106                                 default:
1107                                         break;
1108                                 }
1109                         }
1110                 }
1111
1112                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1113                         return PortMatrixNode::ASSOCIATED;
1114                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1115                         return PortMatrixNode::NOT_ASSOCIATED;
1116                 }
1117
1118                 return PortMatrixNode::PARTIAL;
1119
1120         } else {
1121
1122                 ARDOUR::BundleChannel c[2];
1123                 c[column_index()] = node.column;
1124                 c[row_index()] = node.row;
1125                 return get_state (c);
1126
1127         }
1128
1129         /* NOTREACHED */
1130         return PortMatrixNode::NOT_ASSOCIATED;
1131 }
1132
1133 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1134 bool
1135 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1136 {
1137         return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1138 }
1139
1140 /** See if a `flip' is possible.
1141  *  @return If flip is possible, the new (row, column) notebook indices that
1142  *  should be selected; otherwise, (-1, -1)
1143  */
1144 pair<int, int>
1145 PortMatrix::check_flip () const
1146 {
1147         /* Look for the row's port group name in the columns */
1148         
1149         int new_column = 0;
1150         boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1151         PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1152         while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1153                 ++i;
1154                 ++new_column;
1155         }
1156
1157         if (i == _ports[_column_index].end ()) {
1158                 return make_pair (-1, -1);
1159         }
1160
1161         /* Look for the column's port group name in the rows */
1162         
1163         int new_row = 0;
1164         boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1165         i = _ports[_row_index].begin();
1166         while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1167                 ++i;
1168                 ++new_row;
1169         }
1170
1171         if (i == _ports[_row_index].end ()) {
1172                 return make_pair (-1, -1);
1173         }
1174
1175         if (_arrangement == LEFT_TO_BOTTOM) {
1176                 new_row = _ports[_row_index].size() - new_row - 1;
1177         }
1178
1179         return make_pair (new_row, new_column);
1180 }
1181
1182 bool
1183 PortMatrix::can_flip () const
1184 {
1185         return check_flip().first != -1;
1186 }
1187
1188 /** Flip the column and row pages around, if possible */
1189 void
1190 PortMatrix::flip ()
1191 {
1192         pair<int, int> n = check_flip ();
1193         if (n.first == -1) {
1194                 return;
1195         }
1196
1197         _vnotebook.set_current_page (n.first);
1198         _hnotebook.set_current_page (n.second);
1199 }
1200
1201 bool
1202 PortMatrix::key_press (GdkEventKey* k)
1203 {
1204         if (k->keyval == GDK_f) {
1205                 flip ();
1206                 return true;
1207         }
1208
1209         return false;
1210 }