Remove use of i18n macros in headers. Prevents our gettext.h being included before...
[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 three 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          * 3.  Assorted other signals.
134          */
135
136
137         /* Part 1: the basic _ports[] change -> reset visuals */
138
139         for (int i = 0; i < 2; ++i) {
140                 /* watch for the content of _ports[] changing */
141                 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
142
143                 /* and for bundles in _ports[] changing */
144                 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
145         }
146
147         /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
148         
149         /* watch for routes being added or removed */
150         _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
151
152         /* and also bundles */
153         _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
154
155         /* and also ports */
156         _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
157
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         _ports[0].set_type (_type);
221         _ports[1].set_type (_type);
222
223         setup_all_ports ();
224 }
225
226 void
227 PortMatrix::hscroll_changed ()
228 {
229         _body->set_xoffset (_hscroll.get_adjustment()->get_value());
230 }
231
232 void
233 PortMatrix::vscroll_changed ()
234 {
235         _body->set_yoffset (_vscroll.get_adjustment()->get_value());
236 }
237
238 void
239 PortMatrix::setup_scrollbars ()
240 {
241         Adjustment* a = _hscroll.get_adjustment ();
242         a->set_lower (0);
243         a->set_upper (_body->full_scroll_width());
244         a->set_page_size (_body->alloc_scroll_width());
245         a->set_step_increment (32);
246         a->set_page_increment (128);
247
248         a = _vscroll.get_adjustment ();
249         a->set_lower (0);
250         a->set_upper (_body->full_scroll_height());
251         a->set_page_size (_body->alloc_scroll_height());
252         a->set_step_increment (32);
253         a->set_page_increment (128);
254 }
255
256 /** Disassociate all of our ports from each other */
257 void
258 PortMatrix::disassociate_all ()
259 {
260         PortGroup::BundleList a = _ports[0].bundles ();
261         PortGroup::BundleList b = _ports[1].bundles ();
262
263         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
264                 for (uint32_t j = 0; j < (*i)->bundle->nchannels(); ++j) {
265                         for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
266                                 for (uint32_t l = 0; l < (*k)->bundle->nchannels(); ++l) {
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                 _ports[0].total_channels (),
291                 _ports[1].total_channels ()
292         };
293
294         /* The list with the most channels goes on left or right, so that the most channel
295            names are printed horizontally and hence more readable.  However we also
296            maintain notional `signal flow' vaguely from left to right.  Subclasses
297            should choose where to put ports based on signal flowing from _ports[0]
298            to _ports[1] */
299
300         if (N[0] > N[1]) {
301
302                 _row_index = 0;
303                 _column_index = 1;
304                 _arrangement = LEFT_TO_BOTTOM;
305                 _vlabel.set_label (_("<b>Sources</b>"));
306                 _hlabel.set_label (_("<b>Destinations</b>"));
307                 _vlabel.set_angle (90);
308
309                 attach (*_body, 1, 2, 0, 1);
310                 attach (_vscroll, 2, 3, 0, 1, SHRINK);
311                 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
312                 attach (_vbox, 0, 1, 0, 1, SHRINK);
313                 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
314
315                 set_col_spacing (0, 4);
316                 set_row_spacing (0, 4);
317                 
318         } else {
319
320                 _row_index = 1;
321                 _column_index = 0;
322                 _arrangement = TOP_TO_RIGHT;
323                 _hlabel.set_label (_("<b>Sources</b>"));
324                 _vlabel.set_label (_("<b>Destinations</b>"));
325                 _vlabel.set_angle (-90);
326
327                 attach (*_body, 0, 1, 1, 2);
328                 attach (_vscroll, 2, 3, 1, 2, SHRINK);
329                 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
330                 attach (_vbox, 1, 2, 1, 2, SHRINK);
331                 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
332
333                 set_col_spacing (1, 4);
334                 set_row_spacing (1, 4);
335         }
336 }
337
338 /** @return columns list */
339 PortGroupList const *
340 PortMatrix::columns () const
341 {
342         return &_ports[_column_index];
343 }
344
345 boost::shared_ptr<const PortGroup>
346 PortMatrix::visible_columns () const
347 {
348         return visible_ports (_column_index);
349 }
350
351 /* @return rows list */
352 PortGroupList const *
353 PortMatrix::rows () const
354 {
355         return &_ports[_row_index];
356 }
357
358 boost::shared_ptr<const PortGroup>
359 PortMatrix::visible_rows () const
360 {
361         return visible_ports (_row_index);
362 }
363
364 void
365 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
366 {
367         using namespace Menu_Helpers;
368
369         delete _menu;
370
371         _menu = new Menu;
372         _menu->set_name ("ArdourContextMenu");
373
374         MenuList& items = _menu->items ();
375
376         BundleChannel bc[2];
377         bc[_column_index] = column;
378         bc[_row_index] = row;
379
380         char buf [64];
381         bool need_separator = false;
382
383         for (int dim = 0; dim < 2; ++dim) {
384
385                 if (bc[dim].bundle) {
386
387                         Menu* m = manage (new Menu);
388                         MenuList& sub = m->items ();
389
390                         boost::weak_ptr<Bundle> w (bc[dim].bundle);
391
392                         bool can_add_or_rename = false;
393
394                         if (can_add_channel (bc[dim].bundle)) {
395                                 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
396                                 sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
397                                 can_add_or_rename = true;
398                         }
399
400
401                         if (can_rename_channels (bc[dim].bundle)) {
402                                 snprintf (
403                                         buf, sizeof (buf), _("Rename '%s'..."),
404                                         escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
405                                         );
406                                 sub.push_back (
407                                         MenuElem (
408                                                 buf,
409                                                 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
410                                                 )
411                                         );
412                                 can_add_or_rename = true;
413                         }
414
415                         if (can_add_or_rename) {
416                                 sub.push_back (SeparatorElem ());
417                         }
418
419                         if (can_remove_channels (bc[dim].bundle)) {
420                                 if (bc[dim].channel != -1) {
421                                         add_remove_option (sub, w, bc[dim].channel);
422                                 } else {
423
424                                         snprintf (buf, sizeof (buf), _("Remove all"));
425                                         sub.push_back (
426                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
427                                                 );
428                                         
429                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels(); ++i) {
430                                                 add_remove_option (sub, w, i);
431                                         }
432                                 }
433                         }
434
435                         if (_show_only_bundles || bc[dim].bundle->nchannels() <= 1) {
436                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
437                                 sub.push_back (
438                                         MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
439                                         );
440                                 
441                         } else {
442                                 
443                                 if (bc[dim].channel != -1) {
444                                         add_disassociate_option (sub, w, dim, bc[dim].channel);
445                                 } else {
446                                         snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
447                                         sub.push_back (
448                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
449                                                 );
450                                                         
451                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels(); ++i) {
452                                                 add_disassociate_option (sub, w, dim, i);
453                                         }
454                                 }
455                         }
456
457                         items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
458                         need_separator = true;
459                 }
460
461         }
462
463         if (need_separator) {
464                 items.push_back (SeparatorElem ());
465         }
466
467         items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
468         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
469         CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
470         _inhibit_toggle_show_only_bundles = true;
471         i->set_active (!_show_only_bundles);
472         _inhibit_toggle_show_only_bundles = false;
473
474         _menu->popup (1, t);
475 }
476
477 void
478 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
479 {
480         boost::shared_ptr<Bundle> sb = b.lock ();
481         if (!sb) {
482                 return;
483         }
484
485         remove_channel (BundleChannel (sb, c));
486
487 }
488
489 void
490 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
491 {
492         boost::shared_ptr<Bundle> sb = b.lock ();
493         if (!sb) {
494                 return;
495         }
496
497         rename_channel (BundleChannel (sb, c));
498 }
499
500 void
501 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
502 {
503         boost::shared_ptr<Bundle> sb = bundle.lock ();
504         if (!sb) {
505                 return;
506         }
507
508         for (uint32_t i = 0; i < sb->nchannels(); ++i) {
509                 disassociate_all_on_channel (bundle, i, dim);
510         }
511 }
512
513 void
514 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
515 {
516         boost::shared_ptr<Bundle> sb = bundle.lock ();
517         if (!sb) {
518                 return;
519         }
520
521         PortGroup::BundleList a = _ports[1-dim].bundles ();
522
523         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
524                 for (uint32_t j = 0; j < (*i)->bundle->nchannels(); ++j) {
525
526                         BundleChannel c[2];
527                         c[dim] = BundleChannel (sb, channel);
528                         c[1-dim] = BundleChannel ((*i)->bundle, j);
529
530                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
531                                 set_state (c, false);
532                         }
533                 }
534         }
535
536         _body->rebuild_and_draw_grid ();
537 }
538
539 void
540 PortMatrix::setup_global_ports ()
541 {
542         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
543
544         for (int i = 0; i < 2; ++i) {
545                 if (list_is_global (i)) {
546                         setup_ports (i);
547                 }
548         }
549 }
550
551 void
552 PortMatrix::setup_all_ports ()
553 {
554         if (_session->deletion_in_progress()) {
555                 return;
556         }
557
558         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
559
560         setup_ports (0);
561         setup_ports (1);
562 }
563
564 void
565 PortMatrix::toggle_show_only_bundles ()
566 {
567         if (_inhibit_toggle_show_only_bundles) {
568                 return;
569         }
570
571         _show_only_bundles = !_show_only_bundles;
572         
573         setup ();
574 }
575
576 pair<uint32_t, uint32_t>
577 PortMatrix::max_size () const
578 {
579         pair<uint32_t, uint32_t> m = _body->max_size ();
580
581         m.first += _vscroll.get_width ();
582         m.second += _hscroll.get_height ();
583
584         return m;
585 }
586
587 bool
588 PortMatrix::on_scroll_event (GdkEventScroll* ev)
589 {
590         double const h = _hscroll.get_value ();
591         double const v = _vscroll.get_value ();
592
593         switch (ev->direction) {
594         case GDK_SCROLL_UP:
595                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
596                 break;
597         case GDK_SCROLL_DOWN:
598                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
599                 break;
600         case GDK_SCROLL_LEFT:
601                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
602                 break;
603         case GDK_SCROLL_RIGHT:
604                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
605                 break;
606         }
607
608         return true;
609 }
610
611 boost::shared_ptr<IO>
612 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
613 {
614         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
615         if (!io) {
616                 io = _ports[1].io_from_bundle (b);
617         }
618
619         return io;
620 }
621
622 bool
623 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
624 {
625         return io_from_bundle (b);
626 }
627
628 void
629 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
630 {
631         boost::shared_ptr<IO> io = io_from_bundle (b);
632
633         if (io) {
634                 io->add_port ("", this, _type);
635         }
636 }
637
638 bool
639 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
640 {
641         return io_from_bundle (b);
642 }
643
644 void
645 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
646 {
647         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
648
649         if (io) {
650                 Port* p = io->nth (b.channel);
651                 if (p) {
652                         io->remove_port (p, this);
653                 }
654         }
655 }
656
657 void
658 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
659 {
660         boost::shared_ptr<Bundle> b = w.lock ();
661         if (!b) {
662                 return;
663         }
664
665         for (uint32_t i = 0; i < b->nchannels(); ++i) {
666                 remove_channel (ARDOUR::BundleChannel (b, i));
667         }
668 }
669
670 void
671 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
672 {
673         boost::shared_ptr<Bundle> b = w.lock ();
674         if (!b) {
675                 return;
676         }
677
678         add_channel (b);
679 }
680
681 void
682 PortMatrix::setup_notebooks ()
683 {
684         int const h_current_page = _hnotebook.get_current_page ();
685         int const v_current_page = _vnotebook.get_current_page ();
686         
687         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
688            when adding or removing pages to or from notebooks, so ignore them */
689         
690         _ignore_notebook_page_selected = true;
691         
692         remove_notebook_pages (_hnotebook);
693         remove_notebook_pages (_vnotebook);
694
695         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
696                 HBox* dummy = manage (new HBox);
697                 dummy->show ();
698                 Label* label = manage (new Label ((*i)->name));
699                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
700                 label->show ();
701                 _vnotebook.prepend_page (*dummy, *label);
702         }
703
704         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
705                 HBox* dummy = manage (new HBox);
706                 dummy->show ();
707                 _hnotebook.append_page (*dummy, (*i)->name);
708         }
709
710         _ignore_notebook_page_selected = false;
711
712         _vnotebook.set_tab_pos (POS_LEFT);
713         _hnotebook.set_tab_pos (POS_TOP);
714
715         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
716                 _hnotebook.set_current_page (h_current_page);
717         } else {
718                 _hnotebook.set_current_page (0);
719         }
720
721         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
722                 _vnotebook.set_current_page (v_current_page);
723         } else {
724                 _vnotebook.set_current_page (0);
725         }
726
727         if (_hnotebook.get_n_pages() <= 1) {
728                 _hbox.hide ();
729         } else {
730                 _hbox.show ();
731         }
732
733         if (_vnotebook.get_n_pages() <= 1) {
734                 _vbox.hide ();
735         } else {
736                 _vbox.show ();
737         }
738 }
739
740 void
741 PortMatrix::remove_notebook_pages (Notebook& n)
742 {
743         int const N = n.get_n_pages ();
744         
745         for (int i = 0; i < N; ++i) {
746                 n.remove_page ();
747         }
748 }
749
750 void
751 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
752 {
753         if (_ignore_notebook_page_selected) {
754                 return;
755         }
756
757         _body->setup ();
758         setup_scrollbars ();
759         queue_draw ();
760 }
761
762 void
763 PortMatrix::session_going_away ()
764 {
765         _session = 0;
766 }
767
768 void
769 PortMatrix::body_dimensions_changed ()
770 {
771         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
772         if (_arrangement == TOP_TO_RIGHT) {
773                 _vspacer.set_size_request (-1, _body->column_labels_height ());
774                 _vspacer.show ();
775         } else {
776                 _vspacer.hide ();
777         }
778
779 }
780
781
782 boost::shared_ptr<const PortGroup>
783 PortMatrix::visible_ports (int d) const
784 {
785         PortGroupList const & p = _ports[d];
786         PortGroupList::List::const_iterator j = p.begin ();
787
788         int n = 0;
789         if (d == _row_index) {
790                 n = p.size() - _vnotebook.get_current_page () - 1;
791         } else {
792                 n = _hnotebook.get_current_page ();
793         }
794
795         int i = 0;
796         while (i != int (n) && j != p.end ()) {
797                 ++i;
798                 ++j;
799         }
800                 
801         if (j == p.end()) {
802                 return boost::shared_ptr<const PortGroup> ();
803         }
804
805         return *j;
806 }
807
808 void
809 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
810 {
811         using namespace Menu_Helpers;
812
813         boost::shared_ptr<Bundle> b = w.lock ();
814         if (!b) {
815                 return;
816         }
817         
818         char buf [64];
819         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
820         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
821 }
822
823 void
824 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
825 {
826         using namespace Menu_Helpers;
827
828         boost::shared_ptr<Bundle> b = w.lock ();
829         if (!b) {
830                 return;
831         }
832         
833         char buf [64];
834         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
835         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
836 }
837
838 void
839 PortMatrix::port_connected_or_disconnected ()
840 {
841         _body->rebuild_and_draw_grid ();
842 }
843
844 string
845 PortMatrix::channel_noun () const
846 {
847         return _("channel");
848 }