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