Allow port matrix to show both audio and midi ports at the same time, and use that...
[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
155         /* Part 3: other stuff */
156         
157         _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
158
159         _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
160         _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
161
162         reconnect_to_routes ();
163         
164         setup ();
165 }
166
167 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
168 void
169 PortMatrix::reconnect_to_routes ()
170 {
171         _route_connections.drop_connections ();
172
173         boost::shared_ptr<RouteList> routes = _session->get_routes ();
174         for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
175                 (*i)->processors_changed.connect (_route_connections, invalidator (*this), ui_bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
176         }
177 }
178
179 void
180 PortMatrix::route_processors_changed (RouteProcessorChange c)
181 {
182         if (c.type == RouteProcessorChange::MeterPointChange) {
183                 /* this change has no impact on the port matrix */
184                 return;
185         }
186
187         setup_global_ports ();
188 }
189
190 /** A route has been added to or removed from the session */
191 void
192 PortMatrix::routes_changed ()
193 {
194         reconnect_to_routes ();
195         setup_global_ports ();
196 }
197
198 /** Set up everything that depends on the content of _ports[] */
199 void
200 PortMatrix::setup ()
201 {
202         /* this needs to be done first, as the visible_ports() method uses the
203            notebook state to decide which ports are being shown */
204         
205         setup_notebooks ();
206         
207         _body->setup ();
208         setup_scrollbars ();
209         queue_draw ();
210 }
211
212 void
213 PortMatrix::set_type (DataType t)
214 {
215         _type = t;
216
217         setup_all_ports ();
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);
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);
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 ();
591         m.second += _hscroll.get_height ();
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 }
791
792
793 boost::shared_ptr<const PortGroup>
794 PortMatrix::visible_ports (int d) const
795 {
796         PortGroupList const & p = _ports[d];
797         PortGroupList::List::const_iterator j = p.begin ();
798
799         int n = 0;
800         if (d == _row_index) {
801                 n = p.size() - _vnotebook.get_current_page () - 1;
802         } else {
803                 n = _hnotebook.get_current_page ();
804         }
805
806         int i = 0;
807         while (i != int (n) && j != p.end ()) {
808                 ++i;
809                 ++j;
810         }
811                 
812         if (j == p.end()) {
813                 return boost::shared_ptr<const PortGroup> ();
814         }
815
816         return *j;
817 }
818
819 void
820 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
821 {
822         using namespace Menu_Helpers;
823
824         boost::shared_ptr<Bundle> b = w.lock ();
825         if (!b) {
826                 return;
827         }
828         
829         char buf [64];
830         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
831         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
832 }
833
834 void
835 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
836 {
837         using namespace Menu_Helpers;
838
839         boost::shared_ptr<Bundle> b = w.lock ();
840         if (!b) {
841                 return;
842         }
843         
844         char buf [64];
845         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
846         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
847 }
848
849 void
850 PortMatrix::port_connected_or_disconnected ()
851 {
852         _body->rebuild_and_draw_grid ();
853 }
854
855 string
856 PortMatrix::channel_noun () const
857 {
858         return _("channel");
859 }
860
861 /** @return true if this matrix should show bundles / ports of type \t */
862 bool
863 PortMatrix::should_show (DataType t) const
864 {
865         return (_type == DataType::NIL || t == _type);
866 }
867         
868 uint32_t
869 PortMatrix::count_of_our_type (ChanCount c) const
870 {
871         if (_type == DataType::NIL) {
872                 return c.n_total ();
873         }
874
875         return c.get (_type);
876 }