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