a variety of mostly unused parameter errors from OS X Lion's compiler
[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                                         sub.push_back (
448                                                 MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
449                                                 );
450
451                                         if (bc[dim].bundle->nchannels().n_total() > 1) {
452                                                 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
453                                                         if (should_show (bc[dim].bundle->channel_type(i))) {
454                                                                 add_remove_option (sub, w, i);
455                                                         }
456                                                 }
457                                         }
458                                 }
459                         }
460
461                         uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
462                         if ((_show_only_bundles && c > 0) || c == 1) {
463
464                                 /* we're looking just at bundles, or our bundle has only one channel, so just offer
465                                    to disassociate all on the bundle.
466                                 */
467                                 
468                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
469                                 sub.push_back (
470                                         MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
471                                         );
472                                         
473                         } else if (c != 0) {
474
475                                 if (bc[dim].channel != -1) {
476                                         /* specific channel under the menu, so just offer to disassociate that */
477                                         add_disassociate_option (sub, w, dim, bc[dim].channel);
478                                 } else {
479                                         /* no specific channel; offer to disassociate all, or any one in particular */
480                                         snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
481                                         sub.push_back (
482                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
483                                                 );
484
485                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
486                                                 if (should_show (bc[dim].bundle->channel_type(i))) {
487                                                         add_disassociate_option (sub, w, dim, i);
488                                                 }
489                                         }
490                                 }
491                         }
492
493                         items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
494                         need_separator = true;
495                 }
496
497         }
498
499         if (need_separator) {
500                 items.push_back (SeparatorElem ());
501         }
502
503         items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
504         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
505         CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
506         _inhibit_toggle_show_only_bundles = true;
507         i->set_active (!_show_only_bundles);
508         _inhibit_toggle_show_only_bundles = false;
509
510         _menu->popup (1, t);
511 }
512
513 void
514 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
515 {
516         boost::shared_ptr<Bundle> sb = b.lock ();
517         if (!sb) {
518                 return;
519         }
520
521         remove_channel (BundleChannel (sb, c));
522
523 }
524
525 void
526 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
527 {
528         boost::shared_ptr<Bundle> sb = b.lock ();
529         if (!sb) {
530                 return;
531         }
532
533         rename_channel (BundleChannel (sb, c));
534 }
535
536 void
537 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
538 {
539         boost::shared_ptr<Bundle> sb = bundle.lock ();
540         if (!sb) {
541                 return;
542         }
543
544         for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
545                 if (should_show (sb->channel_type(i))) {
546                         disassociate_all_on_channel (bundle, i, dim);
547                 }
548         }
549 }
550
551 void
552 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
553 {
554         boost::shared_ptr<Bundle> sb = bundle.lock ();
555         if (!sb) {
556                 return;
557         }
558
559         PortGroup::BundleList a = _ports[1-dim].bundles ();
560
561         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
562                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
563
564                         if (!should_show ((*i)->bundle->channel_type(j))) {
565                                 continue;
566                         }
567
568                         BundleChannel c[2];
569                         c[dim] = BundleChannel (sb, channel);
570                         c[1-dim] = BundleChannel ((*i)->bundle, j);
571
572                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
573                                 set_state (c, false);
574                         }
575                 }
576         }
577
578         _body->rebuild_and_draw_grid ();
579 }
580
581 void
582 PortMatrix::setup_global_ports ()
583 {
584         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
585
586         for (int i = 0; i < 2; ++i) {
587                 if (list_is_global (i)) {
588                         setup_ports (i);
589                 }
590         }
591 }
592
593 void
594 PortMatrix::setup_global_ports_proxy ()
595 {
596         /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
597            for a discussion.
598         */
599
600         Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
601 }
602
603 void
604 PortMatrix::setup_all_ports ()
605 {
606         if (_session->deletion_in_progress()) {
607                 return;
608         }
609
610         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
611
612         setup_ports (0);
613         setup_ports (1);
614 }
615
616 void
617 PortMatrix::toggle_show_only_bundles ()
618 {
619         if (_inhibit_toggle_show_only_bundles) {
620                 return;
621         }
622
623         _show_only_bundles = !_show_only_bundles;
624
625         setup ();
626 }
627
628 pair<uint32_t, uint32_t>
629 PortMatrix::max_size () const
630 {
631         pair<uint32_t, uint32_t> m = _body->max_size ();
632
633         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
634         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
635
636         return m;
637 }
638
639 bool
640 PortMatrix::on_scroll_event (GdkEventScroll* ev)
641 {
642         double const h = _hscroll.get_value ();
643         double const v = _vscroll.get_value ();
644
645         switch (ev->direction) {
646         case GDK_SCROLL_UP:
647                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
648                 break;
649         case GDK_SCROLL_DOWN:
650                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
651                 break;
652         case GDK_SCROLL_LEFT:
653                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
654                 break;
655         case GDK_SCROLL_RIGHT:
656                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
657                 break;
658         }
659
660         return true;
661 }
662
663 boost::shared_ptr<IO>
664 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
665 {
666         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
667         if (!io) {
668                 io = _ports[1].io_from_bundle (b);
669         }
670
671         return io;
672 }
673
674 bool
675 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
676 {
677         return io_from_bundle (b);
678 }
679
680 void
681 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
682 {
683         boost::shared_ptr<IO> io = io_from_bundle (b);
684
685         if (io) {
686                 int const r = io->add_port ("", this, t);
687                 if (r == -1) {
688                         Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
689                                                   "support the new configuration."
690                                                         ));
691                         msg.set_title (_("Cannot add port"));
692                         msg.run ();
693                 }
694         }
695 }
696
697 bool
698 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
699 {
700         return io_from_bundle (b);
701 }
702
703 void
704 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
705 {
706         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
707
708         if (io) {
709                 boost::shared_ptr<Port> p = io->nth (b.channel);
710                 if (p) {
711                         int const r = io->remove_port (p, this);
712                         if (r == -1) {
713                                 ArdourDialog d (_("Port removal not allowed"));
714                                 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
715                                 d.get_vbox()->pack_start (l);
716                                 d.add_button (Stock::OK, RESPONSE_ACCEPT);
717                                 d.set_modal (true);
718                                 d.show_all ();
719                                 d.run ();
720                         }
721                 }
722         }
723 }
724
725 void
726 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
727 {
728         boost::shared_ptr<Bundle> b = w.lock ();
729         if (!b) {
730                 return;
731         }
732
733         /* Remove channels backwards so that we don't renumber channels
734            that we are about to remove.
735         */
736         for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
737                 if (should_show (b->channel_type(i))) {
738                         remove_channel (ARDOUR::BundleChannel (b, i));
739                 }
740         }
741 }
742
743 void
744 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
745 {
746         boost::shared_ptr<Bundle> b = w.lock ();
747         if (!b) {
748                 return;
749         }
750
751         add_channel (b, t);
752 }
753
754 void
755 PortMatrix::setup_notebooks ()
756 {
757         int const h_current_page = _hnotebook.get_current_page ();
758         int const v_current_page = _vnotebook.get_current_page ();
759
760         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
761            when adding or removing pages to or from notebooks, so ignore them */
762
763         _ignore_notebook_page_selected = true;
764
765         remove_notebook_pages (_hnotebook);
766         remove_notebook_pages (_vnotebook);
767
768         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
769                 HBox* dummy = manage (new HBox);
770                 dummy->show ();
771                 Label* label = manage (new Label ((*i)->name));
772                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
773                 label->show ();
774                 if (_arrangement == LEFT_TO_BOTTOM) {
775                         _vnotebook.prepend_page (*dummy, *label);
776                 } else {
777                         /* Reverse the order of vertical tabs when they are on the right hand side
778                            so that from top to bottom it is the same order as that from left to right
779                            for the top tabs.
780                         */
781                         _vnotebook.append_page (*dummy, *label);
782                 }
783         }
784
785         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
786                 HBox* dummy = manage (new HBox);
787                 dummy->show ();
788                 _hnotebook.append_page (*dummy, (*i)->name);
789         }
790
791         _ignore_notebook_page_selected = false;
792
793         if (_arrangement == TOP_TO_RIGHT) {
794                 _vnotebook.set_tab_pos (POS_RIGHT);
795                 _hnotebook.set_tab_pos (POS_TOP);
796         } else {
797                 _vnotebook.set_tab_pos (POS_LEFT);
798                 _hnotebook.set_tab_pos (POS_BOTTOM);
799         }
800
801         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
802                 _hnotebook.set_current_page (h_current_page);
803         } else {
804                 _hnotebook.set_current_page (0);
805         }
806
807         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
808                 _vnotebook.set_current_page (v_current_page);
809         } else {
810                 _vnotebook.set_current_page (0);
811         }
812
813         if (_hnotebook.get_n_pages() <= 1) {
814                 _hbox.hide ();
815         } else {
816                 _hbox.show ();
817         }
818
819         if (_vnotebook.get_n_pages() <= 1) {
820                 _vbox.hide ();
821         } else {
822                 _vbox.show ();
823         }
824 }
825
826 void
827 PortMatrix::remove_notebook_pages (Notebook& n)
828 {
829         int const N = n.get_n_pages ();
830
831         for (int i = 0; i < N; ++i) {
832                 n.remove_page ();
833         }
834 }
835
836 void
837 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
838 {
839         if (_ignore_notebook_page_selected) {
840                 return;
841         }
842
843         _body->setup ();
844         setup_scrollbars ();
845         queue_draw ();
846 }
847
848 void
849 PortMatrix::session_going_away ()
850 {
851         _session = 0;
852 }
853
854 void
855 PortMatrix::body_dimensions_changed ()
856 {
857         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
858         if (_arrangement == TOP_TO_RIGHT) {
859                 _vspacer.set_size_request (-1, _body->column_labels_height ());
860                 _vspacer.show ();
861         } else {
862                 _vspacer.hide ();
863         }
864
865         int curr_width;
866         int curr_height;
867         _parent->get_size (curr_width, curr_height);
868
869         pair<uint32_t, uint32_t> m = max_size ();
870
871         /* Don't shrink the window */
872         m.first = max (int (m.first), curr_width);
873         m.second = max (int (m.second), curr_height);
874
875         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
876 }
877
878 /** @return The PortGroup that is currently visible (ie selected by
879  *  the notebook) along a given axis.
880  */
881 boost::shared_ptr<const PortGroup>
882 PortMatrix::visible_ports (int d) const
883 {
884         PortGroupList const & p = _ports[d];
885         PortGroupList::List::const_iterator j = p.begin ();
886
887         /* The logic to compute the index here is a bit twisty because for
888            the TOP_TO_RIGHT arrangement we reverse the order of the vertical
889            tabs in setup_notebooks ().
890         */
891            
892         int n = 0;
893         if (d == _row_index) {
894                 if (_arrangement == LEFT_TO_BOTTOM) {
895                         n = p.size() - _vnotebook.get_current_page () - 1;
896                 } else {
897                         n = _vnotebook.get_current_page ();
898                 }
899         } else {
900                 n = _hnotebook.get_current_page ();
901         }
902
903         int i = 0;
904         while (i != int (n) && j != p.end ()) {
905                 ++i;
906                 ++j;
907         }
908
909         if (j == p.end()) {
910                 return boost::shared_ptr<const PortGroup> ();
911         }
912
913         return *j;
914 }
915
916 void
917 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
918 {
919         using namespace Menu_Helpers;
920
921         boost::shared_ptr<Bundle> b = w.lock ();
922         if (!b) {
923                 return;
924         }
925
926         char buf [64];
927         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
928         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
929 }
930
931 void
932 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
933 {
934         using namespace Menu_Helpers;
935
936         boost::shared_ptr<Bundle> b = w.lock ();
937         if (!b) {
938                 return;
939         }
940
941         char buf [64];
942         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
943         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
944 }
945
946 void
947 PortMatrix::port_connected_or_disconnected ()
948 {
949         _body->rebuild_and_draw_grid ();
950 }
951
952 string
953 PortMatrix::channel_noun () const
954 {
955         return _("channel");
956 }
957
958 /** @return true if this matrix should show bundles / ports of type \t */
959 bool
960 PortMatrix::should_show (DataType t) const
961 {
962         return (_type == DataType::NIL || t == _type);
963 }
964
965 uint32_t
966 PortMatrix::count_of_our_type (ChanCount c) const
967 {
968         if (_type == DataType::NIL) {
969                 return c.n_total ();
970         }
971
972         return c.get (_type);
973 }
974
975 /** @return The number of ports of our type in the given channel count,
976  *  but returning 1 if there are no ports.
977  */
978 uint32_t
979 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
980 {
981         uint32_t n = count_of_our_type (c);
982         if (n == 0) {
983                 n = 1;
984         }
985
986         return n;
987 }
988
989 PortMatrixNode::State
990 PortMatrix::get_association (PortMatrixNode node) const
991 {
992         if (show_only_bundles ()) {
993
994                 bool have_off_diagonal_association = false;
995                 bool have_diagonal_association = false;
996                 bool have_diagonal_not_association = false;
997
998                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
999
1000                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1001
1002                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1003                                         continue;
1004                                 }
1005
1006                                 ARDOUR::BundleChannel c[2];
1007                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1008                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1009
1010                                 PortMatrixNode::State const s = get_state (c);
1011
1012                                 switch (s) {
1013                                 case PortMatrixNode::ASSOCIATED:
1014                                         if (i == j) {
1015                                                 have_diagonal_association = true;
1016                                         } else {
1017                                                 have_off_diagonal_association = true;
1018                                         }
1019                                         break;
1020
1021                                 case PortMatrixNode::NOT_ASSOCIATED:
1022                                         if (i == j) {
1023                                                 have_diagonal_not_association = true;
1024                                         }
1025                                         break;
1026
1027                                 default:
1028                                         break;
1029                                 }
1030                         }
1031                 }
1032
1033                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1034                         return PortMatrixNode::ASSOCIATED;
1035                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1036                         return PortMatrixNode::NOT_ASSOCIATED;
1037                 }
1038
1039                 return PortMatrixNode::PARTIAL;
1040
1041         } else {
1042
1043                 ARDOUR::BundleChannel c[2];
1044                 c[column_index()] = node.column;
1045                 c[row_index()] = node.row;
1046                 return get_state (c);
1047
1048         }
1049
1050         /* NOTREACHED */
1051         return PortMatrixNode::NOT_ASSOCIATED;
1052 }
1053
1054 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1055 bool
1056 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1057 {
1058         return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1059 }