Fix another crash on session creation if the stored selection no longer exists in...
[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, 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_all_ports ()
580 {
581         if (_session->deletion_in_progress()) {
582                 return;
583         }
584
585         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
586
587         setup_ports (0);
588         setup_ports (1);
589 }
590
591 void
592 PortMatrix::toggle_show_only_bundles ()
593 {
594         if (_inhibit_toggle_show_only_bundles) {
595                 return;
596         }
597
598         _show_only_bundles = !_show_only_bundles;
599         
600         setup ();
601 }
602
603 pair<uint32_t, uint32_t>
604 PortMatrix::max_size () const
605 {
606         pair<uint32_t, uint32_t> m = _body->max_size ();
607
608         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
609         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
610
611         return m;
612 }
613
614 bool
615 PortMatrix::on_scroll_event (GdkEventScroll* ev)
616 {
617         double const h = _hscroll.get_value ();
618         double const v = _vscroll.get_value ();
619
620         switch (ev->direction) {
621         case GDK_SCROLL_UP:
622                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
623                 break;
624         case GDK_SCROLL_DOWN:
625                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
626                 break;
627         case GDK_SCROLL_LEFT:
628                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
629                 break;
630         case GDK_SCROLL_RIGHT:
631                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
632                 break;
633         }
634
635         return true;
636 }
637
638 boost::shared_ptr<IO>
639 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
640 {
641         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
642         if (!io) {
643                 io = _ports[1].io_from_bundle (b);
644         }
645
646         return io;
647 }
648
649 bool
650 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
651 {
652         return io_from_bundle (b);
653 }
654
655 void
656 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
657 {
658         boost::shared_ptr<IO> io = io_from_bundle (b);
659
660         if (io) {
661                 io->add_port ("", this, t);
662         }
663 }
664
665 bool
666 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
667 {
668         return io_from_bundle (b);
669 }
670
671 void
672 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
673 {
674         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
675
676         if (io) {
677                 Port* p = io->nth (b.channel);
678                 if (p) {
679                         io->remove_port (p, this);
680                 }
681         }
682 }
683
684 void
685 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
686 {
687         boost::shared_ptr<Bundle> b = w.lock ();
688         if (!b) {
689                 return;
690         }
691
692         for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
693                 if (should_show (b->channel_type(i))) {
694                         remove_channel (ARDOUR::BundleChannel (b, i));
695                 }
696         }
697 }
698
699 void
700 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
701 {
702         boost::shared_ptr<Bundle> b = w.lock ();
703         if (!b) {
704                 return;
705         }
706
707         add_channel (b, t);
708 }
709
710 void
711 PortMatrix::setup_notebooks ()
712 {
713         int const h_current_page = _hnotebook.get_current_page ();
714         int const v_current_page = _vnotebook.get_current_page ();
715         
716         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
717            when adding or removing pages to or from notebooks, so ignore them */
718         
719         _ignore_notebook_page_selected = true;
720         
721         remove_notebook_pages (_hnotebook);
722         remove_notebook_pages (_vnotebook);
723
724         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
725                 HBox* dummy = manage (new HBox);
726                 dummy->show ();
727                 Label* label = manage (new Label ((*i)->name));
728                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
729                 label->show ();
730                 _vnotebook.prepend_page (*dummy, *label);
731         }
732
733         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
734                 HBox* dummy = manage (new HBox);
735                 dummy->show ();
736                 _hnotebook.append_page (*dummy, (*i)->name);
737         }
738
739         _ignore_notebook_page_selected = false;
740
741         if (_arrangement == TOP_TO_RIGHT) {
742                 _vnotebook.set_tab_pos (POS_RIGHT);
743                 _hnotebook.set_tab_pos (POS_TOP);
744         } else {
745                 _vnotebook.set_tab_pos (POS_LEFT);
746                 _hnotebook.set_tab_pos (POS_BOTTOM);
747         }
748
749         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
750                 _hnotebook.set_current_page (h_current_page);
751         } else {
752                 _hnotebook.set_current_page (0);
753         }
754
755         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
756                 _vnotebook.set_current_page (v_current_page);
757         } else {
758                 _vnotebook.set_current_page (0);
759         }
760
761         if (_hnotebook.get_n_pages() <= 1) {
762                 _hbox.hide ();
763         } else {
764                 _hbox.show ();
765         }
766
767         if (_vnotebook.get_n_pages() <= 1) {
768                 _vbox.hide ();
769         } else {
770                 _vbox.show ();
771         }
772 }
773
774 void
775 PortMatrix::remove_notebook_pages (Notebook& n)
776 {
777         int const N = n.get_n_pages ();
778         
779         for (int i = 0; i < N; ++i) {
780                 n.remove_page ();
781         }
782 }
783
784 void
785 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
786 {
787         if (_ignore_notebook_page_selected) {
788                 return;
789         }
790
791         _body->setup ();
792         setup_scrollbars ();
793         queue_draw ();
794 }
795
796 void
797 PortMatrix::session_going_away ()
798 {
799         _session = 0;
800 }
801
802 void
803 PortMatrix::body_dimensions_changed ()
804 {
805         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
806         if (_arrangement == TOP_TO_RIGHT) {
807                 _vspacer.set_size_request (-1, _body->column_labels_height ());
808                 _vspacer.show ();
809         } else {
810                 _vspacer.hide ();
811         }
812
813         int curr_width;
814         int curr_height;
815         _parent->get_size (curr_width, curr_height);
816
817         pair<uint32_t, uint32_t> m = max_size ();
818
819         /* Don't shrink the window */
820         m.first = max (int (m.first), curr_width);
821         m.second = max (int (m.second), curr_height);
822         
823         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
824 }
825
826
827 boost::shared_ptr<const PortGroup>
828 PortMatrix::visible_ports (int d) const
829 {
830         PortGroupList const & p = _ports[d];
831         PortGroupList::List::const_iterator j = p.begin ();
832
833         int n = 0;
834         if (d == _row_index) {
835                 n = p.size() - _vnotebook.get_current_page () - 1;
836         } else {
837                 n = _hnotebook.get_current_page ();
838         }
839
840         int i = 0;
841         while (i != int (n) && j != p.end ()) {
842                 ++i;
843                 ++j;
844         }
845                 
846         if (j == p.end()) {
847                 return boost::shared_ptr<const PortGroup> ();
848         }
849
850         return *j;
851 }
852
853 void
854 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
855 {
856         using namespace Menu_Helpers;
857
858         boost::shared_ptr<Bundle> b = w.lock ();
859         if (!b) {
860                 return;
861         }
862         
863         char buf [64];
864         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
865         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
866 }
867
868 void
869 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
870 {
871         using namespace Menu_Helpers;
872
873         boost::shared_ptr<Bundle> b = w.lock ();
874         if (!b) {
875                 return;
876         }
877         
878         char buf [64];
879         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
880         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
881 }
882
883 void
884 PortMatrix::port_connected_or_disconnected ()
885 {
886         _body->rebuild_and_draw_grid ();
887 }
888
889 string
890 PortMatrix::channel_noun () const
891 {
892         return _("channel");
893 }
894
895 /** @return true if this matrix should show bundles / ports of type \t */
896 bool
897 PortMatrix::should_show (DataType t) const
898 {
899         return (_type == DataType::NIL || t == _type);
900 }
901         
902 uint32_t
903 PortMatrix::count_of_our_type (ChanCount c) const
904 {
905         if (_type == DataType::NIL) {
906                 return c.n_total ();
907         }
908
909         return c.get (_type);
910 }