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