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