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