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