Merge branch 'master' into cairocanvas
[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), 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 ()
623 {
624         /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
625            for a discussion.
626         */
627                 
628         Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
629 }
630
631 void
632 PortMatrix::setup_all_ports ()
633 {
634         if (_session->deletion_in_progress()) {
635                 return;
636         }
637
638         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
639
640         setup_ports (0);
641         setup_ports (1);
642 }
643
644 void
645 PortMatrix::toggle_show_only_bundles ()
646 {
647         if (_inhibit_toggle_show_only_bundles) {
648                 return;
649         }
650
651         _show_only_bundles = !_show_only_bundles;
652
653         setup ();
654
655         /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
656            setting, so we need to set things up again now.
657         */
658         setup_all_ports ();
659 }
660
661 pair<uint32_t, uint32_t>
662 PortMatrix::max_size () const
663 {
664         pair<uint32_t, uint32_t> m = _body->max_size ();
665
666         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
667         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
668
669         return m;
670 }
671
672 bool
673 PortMatrix::on_scroll_event (GdkEventScroll* ev)
674 {
675         double const h = _hscroll.get_value ();
676         double const v = _vscroll.get_value ();
677
678         switch (ev->direction) {
679         case GDK_SCROLL_UP:
680                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
681                 break;
682         case GDK_SCROLL_DOWN:
683                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
684                 break;
685         case GDK_SCROLL_LEFT:
686                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
687                 break;
688         case GDK_SCROLL_RIGHT:
689                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
690                 break;
691         }
692
693         return true;
694 }
695
696 boost::shared_ptr<IO>
697 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
698 {
699         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
700         if (!io) {
701                 io = _ports[1].io_from_bundle (b);
702         }
703
704         return io;
705 }
706
707 bool
708 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
709 {
710         return io_from_bundle (b);
711 }
712
713 void
714 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
715 {
716         boost::shared_ptr<IO> io = io_from_bundle (b);
717
718         if (io) {
719                 int const r = io->add_port ("", this, t);
720                 if (r == -1) {
721                         Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
722                                                   "support the new configuration."
723                                                         ));
724                         msg.set_title (_("Cannot add port"));
725                         msg.run ();
726                 }
727         }
728 }
729
730 bool
731 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
732 {
733         return io_from_bundle (b);
734 }
735
736 void
737 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
738 {
739         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
740
741         if (io) {
742                 boost::shared_ptr<Port> p = io->nth (b.channel);
743                 if (p) {
744                         int const r = io->remove_port (p, this);
745                         if (r == -1) {
746                                 ArdourDialog d (_("Port removal not allowed"));
747                                 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."));
748                                 d.get_vbox()->pack_start (l);
749                                 d.add_button (Stock::OK, RESPONSE_ACCEPT);
750                                 d.set_modal (true);
751                                 d.show_all ();
752                                 d.run ();
753                         }
754                 }
755         }
756 }
757
758 void
759 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
760 {
761         boost::shared_ptr<Bundle> b = w.lock ();
762         if (!b) {
763                 return;
764         }
765
766         /* Remove channels backwards so that we don't renumber channels
767            that we are about to remove.
768         */
769         for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
770                 if (should_show (b->channel_type(i))) {
771                         remove_channel (ARDOUR::BundleChannel (b, i));
772                 }
773         }
774 }
775
776 void
777 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
778 {
779         boost::shared_ptr<Bundle> b = w.lock ();
780         if (!b) {
781                 return;
782         }
783
784         add_channel (b, t);
785 }
786
787 void
788 PortMatrix::setup_notebooks ()
789 {
790         int const h_current_page = _hnotebook.get_current_page ();
791         int const v_current_page = _vnotebook.get_current_page ();
792
793         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
794            when adding or removing pages to or from notebooks, so ignore them */
795
796         _ignore_notebook_page_selected = true;
797
798         remove_notebook_pages (_hnotebook);
799         remove_notebook_pages (_vnotebook);
800
801         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
802                 HBox* dummy = manage (new HBox);
803                 dummy->show ();
804                 Label* label = manage (new Label ((*i)->name));
805                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
806                 label->set_use_markup ();
807                 label->show ();
808                 if (_arrangement == LEFT_TO_BOTTOM) {
809                         _vnotebook.prepend_page (*dummy, *label);
810                 } else {
811                         /* Reverse the order of vertical tabs when they are on the right hand side
812                            so that from top to bottom it is the same order as that from left to right
813                            for the top tabs.
814                         */
815                         _vnotebook.append_page (*dummy, *label);
816                 }
817         }
818
819         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
820                 HBox* dummy = manage (new HBox);
821                 dummy->show ();
822                 Label* label = manage (new Label ((*i)->name));
823                 label->set_use_markup ();
824                 label->show ();
825                 _hnotebook.append_page (*dummy, *label);
826         }
827
828         _ignore_notebook_page_selected = false;
829
830         if (_arrangement == TOP_TO_RIGHT) {
831                 _vnotebook.set_tab_pos (POS_RIGHT);
832                 _hnotebook.set_tab_pos (POS_TOP);
833         } else {
834                 _vnotebook.set_tab_pos (POS_LEFT);
835                 _hnotebook.set_tab_pos (POS_BOTTOM);
836         }
837
838         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
839                 _hnotebook.set_current_page (h_current_page);
840         } else {
841                 _hnotebook.set_current_page (0);
842         }
843
844         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
845                 _vnotebook.set_current_page (v_current_page);
846         } else {
847                 _vnotebook.set_current_page (0);
848         }
849
850         if (_hnotebook.get_n_pages() <= 1) {
851                 _hbox.hide ();
852         } else {
853                 _hbox.show ();
854         }
855
856         if (_vnotebook.get_n_pages() <= 1) {
857                 _vbox.hide ();
858         } else {
859                 _vbox.show ();
860         }
861 }
862
863 void
864 PortMatrix::remove_notebook_pages (Notebook& n)
865 {
866         int const N = n.get_n_pages ();
867
868         for (int i = 0; i < N; ++i) {
869                 n.remove_page ();
870         }
871 }
872
873 void
874 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
875 {
876         if (_ignore_notebook_page_selected) {
877                 return;
878         }
879
880         _body->setup ();
881         setup_scrollbars ();
882         queue_draw ();
883 }
884
885 void
886 PortMatrix::session_going_away ()
887 {
888         _session = 0;
889 }
890
891 void
892 PortMatrix::body_dimensions_changed ()
893 {
894         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
895         if (_arrangement == TOP_TO_RIGHT) {
896                 _vspacer.set_size_request (-1, _body->column_labels_height ());
897                 _vspacer.show ();
898         } else {
899                 _vspacer.hide ();
900         }
901
902         int curr_width;
903         int curr_height;
904         _parent->get_size (curr_width, curr_height);
905
906         pair<uint32_t, uint32_t> m = max_size ();
907
908         /* Don't shrink the window */
909         m.first = max (int (m.first), curr_width);
910         m.second = max (int (m.second), curr_height);
911
912         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
913 }
914
915 /** @return The PortGroup that is currently visible (ie selected by
916  *  the notebook) along a given axis.
917  */
918 boost::shared_ptr<const PortGroup>
919 PortMatrix::visible_ports (int d) const
920 {
921         PortGroupList const & p = _ports[d];
922         PortGroupList::List::const_iterator j = p.begin ();
923
924         /* The logic to compute the index here is a bit twisty because for
925            the TOP_TO_RIGHT arrangement we reverse the order of the vertical
926            tabs in setup_notebooks ().
927         */
928            
929         int n = 0;
930         if (d == _row_index) {
931                 if (_arrangement == LEFT_TO_BOTTOM) {
932                         n = p.size() - _vnotebook.get_current_page () - 1;
933                 } else {
934                         n = _vnotebook.get_current_page ();
935                 }
936         } else {
937                 n = _hnotebook.get_current_page ();
938         }
939
940         int i = 0;
941         while (i != int (n) && j != p.end ()) {
942                 ++i;
943                 ++j;
944         }
945
946         if (j == p.end()) {
947                 return boost::shared_ptr<const PortGroup> ();
948         }
949
950         return *j;
951 }
952
953 void
954 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
955 {
956         using namespace Menu_Helpers;
957
958         boost::shared_ptr<Bundle> b = w.lock ();
959         if (!b) {
960                 return;
961         }
962
963         char buf [64];
964         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
965         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
966 }
967
968 void
969 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
970 {
971         using namespace Menu_Helpers;
972
973         boost::shared_ptr<Bundle> b = w.lock ();
974         if (!b) {
975                 return;
976         }
977
978         char buf [64];
979         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
980         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
981 }
982
983 void
984 PortMatrix::port_connected_or_disconnected ()
985 {
986         _body->rebuild_and_draw_grid ();
987         update_tab_highlighting ();
988 }
989
990 /** Update the highlighting of tab names to reflect which ones
991  *  have connections.  This is pretty inefficient, unfortunately,
992  *  but maybe that doesn't matter too much.
993  */
994 void
995 PortMatrix::update_tab_highlighting ()
996 {
997         if (!_session) {
998                 return;
999         }
1000         
1001         for (int i = 0; i < 2; ++i) {
1002
1003                 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1004                 
1005                 PortGroupList const * gl = ports (i);
1006                 int p = 0;
1007                 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1008                         bool has_connection = false;
1009                         PortGroup::BundleList const & bl = (*j)->bundles ();
1010                         PortGroup::BundleList::const_iterator k = bl.begin ();
1011                         while (k != bl.end()) {
1012                                 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1013                                         has_connection = true;
1014                                         break;
1015                                 }
1016                                 ++k;
1017                         }
1018
1019                         /* Find the page index that we should update; this is backwards
1020                            for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1021                         */
1022                         int page = p;
1023                         if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1024                                 page = notebook->get_n_pages() - p - 1;
1025                         }
1026
1027                         Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1028                         string c = label->get_label ();
1029                         if (c.length() && c[0] == '<' && !has_connection) {
1030                                 /* this label is marked up with <b> but shouldn't be */
1031                                 label->set_text ((*j)->name);
1032                         } else if (c.length() && c[0] != '<' && has_connection) {
1033                                 /* this label is not marked up with <b> but should be */
1034                                 label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1035                         }
1036
1037                         ++p;
1038                 }
1039         }
1040 }
1041
1042 string
1043 PortMatrix::channel_noun () const
1044 {
1045         return _("channel");
1046 }
1047
1048 /** @return true if this matrix should show bundles / ports of type \t */
1049 bool
1050 PortMatrix::should_show (DataType t) const
1051 {
1052         return (_type == DataType::NIL || t == _type);
1053 }
1054
1055 uint32_t
1056 PortMatrix::count_of_our_type (ChanCount c) const
1057 {
1058         if (_type == DataType::NIL) {
1059                 return c.n_total ();
1060         }
1061
1062         return c.get (_type);
1063 }
1064
1065 /** @return The number of ports of our type in the given channel count,
1066  *  but returning 1 if there are no ports.
1067  */
1068 uint32_t
1069 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1070 {
1071         uint32_t n = count_of_our_type (c);
1072         if (n == 0) {
1073                 n = 1;
1074         }
1075
1076         return n;
1077 }
1078
1079 PortMatrixNode::State
1080 PortMatrix::get_association (PortMatrixNode node) const
1081 {
1082         if (show_only_bundles ()) {
1083
1084                 bool have_off_diagonal_association = false;
1085                 bool have_diagonal_association = false;
1086                 bool have_diagonal_not_association = false;
1087
1088                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1089
1090                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1091
1092                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1093                                         continue;
1094                                 }
1095
1096                                 ARDOUR::BundleChannel c[2];
1097                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1098                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1099
1100                                 PortMatrixNode::State const s = get_state (c);
1101
1102                                 switch (s) {
1103                                 case PortMatrixNode::ASSOCIATED:
1104                                         if (i == j) {
1105                                                 have_diagonal_association = true;
1106                                         } else {
1107                                                 have_off_diagonal_association = true;
1108                                         }
1109                                         break;
1110
1111                                 case PortMatrixNode::NOT_ASSOCIATED:
1112                                         if (i == j) {
1113                                                 have_diagonal_not_association = true;
1114                                         }
1115                                         break;
1116
1117                                 default:
1118                                         break;
1119                                 }
1120                         }
1121                 }
1122
1123                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1124                         return PortMatrixNode::ASSOCIATED;
1125                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1126                         return PortMatrixNode::NOT_ASSOCIATED;
1127                 }
1128
1129                 return PortMatrixNode::PARTIAL;
1130
1131         } else {
1132
1133                 ARDOUR::BundleChannel c[2];
1134                 c[column_index()] = node.column;
1135                 c[row_index()] = node.row;
1136                 return get_state (c);
1137
1138         }
1139
1140         /* NOTREACHED */
1141         return PortMatrixNode::NOT_ASSOCIATED;
1142 }
1143
1144 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1145 bool
1146 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1147 {
1148         return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1149 }
1150
1151 /** See if a `flip' is possible.
1152  *  @return If flip is possible, the new (row, column) notebook indices that
1153  *  should be selected; otherwise, (-1, -1)
1154  */
1155 pair<int, int>
1156 PortMatrix::check_flip () const
1157 {
1158         /* Look for the row's port group name in the columns */
1159         
1160         int new_column = 0;
1161         boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1162         PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1163         while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1164                 ++i;
1165                 ++new_column;
1166         }
1167
1168         if (i == _ports[_column_index].end ()) {
1169                 return make_pair (-1, -1);
1170         }
1171
1172         /* Look for the column's port group name in the rows */
1173         
1174         int new_row = 0;
1175         boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1176         i = _ports[_row_index].begin();
1177         while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1178                 ++i;
1179                 ++new_row;
1180         }
1181
1182         if (i == _ports[_row_index].end ()) {
1183                 return make_pair (-1, -1);
1184         }
1185
1186         if (_arrangement == LEFT_TO_BOTTOM) {
1187                 new_row = _ports[_row_index].size() - new_row - 1;
1188         }
1189
1190         return make_pair (new_row, new_column);
1191 }
1192
1193 bool
1194 PortMatrix::can_flip () const
1195 {
1196         return check_flip().first != -1;
1197 }
1198
1199 /** Flip the column and row pages around, if possible */
1200 void
1201 PortMatrix::flip ()
1202 {
1203         pair<int, int> n = check_flip ();
1204         if (n.first == -1) {
1205                 return;
1206         }
1207
1208         _vnotebook.set_current_page (n.first);
1209         _hnotebook.set_current_page (n.second);
1210 }
1211
1212 bool
1213 PortMatrix::key_press (GdkEventKey* k)
1214 {
1215         if (k->keyval == GDK_f) {
1216                 flip ();
1217                 return true;
1218         }
1219
1220         return false;
1221 }