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