new transparent-background versions of track/bus list icons from thorsten
[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                                         if (bc[dim].bundle->nchannels().n_total() > 1) {
459                                                 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
460                                                         if (should_show (bc[dim].bundle->channel_type(i))) {
461                                                                 add_remove_option (sub, w, i);
462                                                         }
463                                                 }
464                                         }
465                                 }
466                         }
467
468                         if (_show_only_bundles || count_of_our_type (bc[dim].bundle->nchannels()) <= 1) {
469                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
470                                 sub.push_back (
471                                         MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
472                                         );
473
474                         } else {
475
476                                 if (bc[dim].channel != -1) {
477                                         add_disassociate_option (sub, w, dim, bc[dim].channel);
478                                 } else {
479                                         snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
480                                         sub.push_back (
481                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
482                                                 );
483
484                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
485                                                 if (should_show (bc[dim].bundle->channel_type(i))) {
486                                                         add_disassociate_option (sub, w, dim, i);
487                                                 }
488                                         }
489                                 }
490                         }
491
492                         items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
493                         need_separator = true;
494                 }
495
496         }
497
498         if (need_separator) {
499                 items.push_back (SeparatorElem ());
500         }
501
502         items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
503         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
504         CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
505         _inhibit_toggle_show_only_bundles = true;
506         i->set_active (!_show_only_bundles);
507         _inhibit_toggle_show_only_bundles = false;
508
509         _menu->popup (1, t);
510 }
511
512 void
513 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
514 {
515         boost::shared_ptr<Bundle> sb = b.lock ();
516         if (!sb) {
517                 return;
518         }
519
520         remove_channel (BundleChannel (sb, c));
521
522 }
523
524 void
525 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
526 {
527         boost::shared_ptr<Bundle> sb = b.lock ();
528         if (!sb) {
529                 return;
530         }
531
532         rename_channel (BundleChannel (sb, c));
533 }
534
535 void
536 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
537 {
538         boost::shared_ptr<Bundle> sb = bundle.lock ();
539         if (!sb) {
540                 return;
541         }
542
543         for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
544                 if (should_show (sb->channel_type(i))) {
545                         disassociate_all_on_channel (bundle, i, dim);
546                 }
547         }
548 }
549
550 void
551 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
552 {
553         boost::shared_ptr<Bundle> sb = bundle.lock ();
554         if (!sb) {
555                 return;
556         }
557
558         PortGroup::BundleList a = _ports[1-dim].bundles ();
559
560         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
561                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
562
563                         if (should_show ((*i)->bundle->channel_type(j))) {
564                                 continue;
565                         }
566
567                         BundleChannel c[2];
568                         c[dim] = BundleChannel (sb, channel);
569                         c[1-dim] = BundleChannel ((*i)->bundle, j);
570
571                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
572                                 set_state (c, false);
573                         }
574                 }
575         }
576
577         _body->rebuild_and_draw_grid ();
578 }
579
580 void
581 PortMatrix::setup_global_ports ()
582 {
583         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
584
585         for (int i = 0; i < 2; ++i) {
586                 if (list_is_global (i)) {
587                         setup_ports (i);
588                 }
589         }
590 }
591
592 void
593 PortMatrix::setup_global_ports_proxy ()
594 {
595         /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
596            for a discussion.
597         */
598
599         Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
600 }
601
602 void
603 PortMatrix::setup_all_ports ()
604 {
605         if (_session->deletion_in_progress()) {
606                 return;
607         }
608
609         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
610
611         setup_ports (0);
612         setup_ports (1);
613 }
614
615 void
616 PortMatrix::toggle_show_only_bundles ()
617 {
618         if (_inhibit_toggle_show_only_bundles) {
619                 return;
620         }
621
622         _show_only_bundles = !_show_only_bundles;
623
624         setup ();
625 }
626
627 pair<uint32_t, uint32_t>
628 PortMatrix::max_size () const
629 {
630         pair<uint32_t, uint32_t> m = _body->max_size ();
631
632         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
633         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
634
635         return m;
636 }
637
638 bool
639 PortMatrix::on_scroll_event (GdkEventScroll* ev)
640 {
641         double const h = _hscroll.get_value ();
642         double const v = _vscroll.get_value ();
643
644         switch (ev->direction) {
645         case GDK_SCROLL_UP:
646                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
647                 break;
648         case GDK_SCROLL_DOWN:
649                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
650                 break;
651         case GDK_SCROLL_LEFT:
652                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
653                 break;
654         case GDK_SCROLL_RIGHT:
655                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
656                 break;
657         }
658
659         return true;
660 }
661
662 boost::shared_ptr<IO>
663 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
664 {
665         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
666         if (!io) {
667                 io = _ports[1].io_from_bundle (b);
668         }
669
670         return io;
671 }
672
673 bool
674 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
675 {
676         return io_from_bundle (b);
677 }
678
679 void
680 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
681 {
682         boost::shared_ptr<IO> io = io_from_bundle (b);
683
684         if (io) {
685                 io->add_port ("", this, t);
686         }
687 }
688
689 bool
690 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
691 {
692         return io_from_bundle (b);
693 }
694
695 void
696 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
697 {
698         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
699
700         if (io) {
701                 Port* p = io->nth (b.channel);
702                 if (p) {
703                         int const r = io->remove_port (p, this);
704                         if (r == -1) {
705                                 ArdourDialog d (_("Port removal not allowed"));
706                                 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
707                                 d.get_vbox()->pack_start (l);
708                                 d.add_button (Stock::OK, RESPONSE_ACCEPT);
709                                 d.set_modal (true);
710                                 d.show_all ();
711                                 d.run ();
712                         }
713                 }
714         }
715 }
716
717 void
718 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
719 {
720         boost::shared_ptr<Bundle> b = w.lock ();
721         if (!b) {
722                 return;
723         }
724
725         for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
726                 if (should_show (b->channel_type(i))) {
727                         remove_channel (ARDOUR::BundleChannel (b, i));
728                 }
729         }
730 }
731
732 void
733 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
734 {
735         boost::shared_ptr<Bundle> b = w.lock ();
736         if (!b) {
737                 return;
738         }
739
740         add_channel (b, t);
741 }
742
743 void
744 PortMatrix::setup_notebooks ()
745 {
746         int const h_current_page = _hnotebook.get_current_page ();
747         int const v_current_page = _vnotebook.get_current_page ();
748
749         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
750            when adding or removing pages to or from notebooks, so ignore them */
751
752         _ignore_notebook_page_selected = true;
753
754         remove_notebook_pages (_hnotebook);
755         remove_notebook_pages (_vnotebook);
756
757         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
758                 HBox* dummy = manage (new HBox);
759                 dummy->show ();
760                 Label* label = manage (new Label ((*i)->name));
761                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
762                 label->show ();
763                 _vnotebook.prepend_page (*dummy, *label);
764         }
765
766         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
767                 HBox* dummy = manage (new HBox);
768                 dummy->show ();
769                 _hnotebook.append_page (*dummy, (*i)->name);
770         }
771
772         _ignore_notebook_page_selected = false;
773
774         if (_arrangement == TOP_TO_RIGHT) {
775                 _vnotebook.set_tab_pos (POS_RIGHT);
776                 _hnotebook.set_tab_pos (POS_TOP);
777         } else {
778                 _vnotebook.set_tab_pos (POS_LEFT);
779                 _hnotebook.set_tab_pos (POS_BOTTOM);
780         }
781
782         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
783                 _hnotebook.set_current_page (h_current_page);
784         } else {
785                 _hnotebook.set_current_page (0);
786         }
787
788         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
789                 _vnotebook.set_current_page (v_current_page);
790         } else {
791                 _vnotebook.set_current_page (0);
792         }
793
794         if (_hnotebook.get_n_pages() <= 1) {
795                 _hbox.hide ();
796         } else {
797                 _hbox.show ();
798         }
799
800         if (_vnotebook.get_n_pages() <= 1) {
801                 _vbox.hide ();
802         } else {
803                 _vbox.show ();
804         }
805 }
806
807 void
808 PortMatrix::remove_notebook_pages (Notebook& n)
809 {
810         int const N = n.get_n_pages ();
811
812         for (int i = 0; i < N; ++i) {
813                 n.remove_page ();
814         }
815 }
816
817 void
818 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
819 {
820         if (_ignore_notebook_page_selected) {
821                 return;
822         }
823
824         _body->setup ();
825         setup_scrollbars ();
826         queue_draw ();
827 }
828
829 void
830 PortMatrix::session_going_away ()
831 {
832         _session = 0;
833 }
834
835 void
836 PortMatrix::body_dimensions_changed ()
837 {
838         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
839         if (_arrangement == TOP_TO_RIGHT) {
840                 _vspacer.set_size_request (-1, _body->column_labels_height ());
841                 _vspacer.show ();
842         } else {
843                 _vspacer.hide ();
844         }
845
846         int curr_width;
847         int curr_height;
848         _parent->get_size (curr_width, curr_height);
849
850         pair<uint32_t, uint32_t> m = max_size ();
851
852         /* Don't shrink the window */
853         m.first = max (int (m.first), curr_width);
854         m.second = max (int (m.second), curr_height);
855
856         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
857 }
858
859
860 boost::shared_ptr<const PortGroup>
861 PortMatrix::visible_ports (int d) const
862 {
863         PortGroupList const & p = _ports[d];
864         PortGroupList::List::const_iterator j = p.begin ();
865
866         int n = 0;
867         if (d == _row_index) {
868                 n = p.size() - _vnotebook.get_current_page () - 1;
869         } else {
870                 n = _hnotebook.get_current_page ();
871         }
872
873         int i = 0;
874         while (i != int (n) && j != p.end ()) {
875                 ++i;
876                 ++j;
877         }
878
879         if (j == p.end()) {
880                 return boost::shared_ptr<const PortGroup> ();
881         }
882
883         return *j;
884 }
885
886 void
887 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
888 {
889         using namespace Menu_Helpers;
890
891         boost::shared_ptr<Bundle> b = w.lock ();
892         if (!b) {
893                 return;
894         }
895
896         char buf [64];
897         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
898         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
899 }
900
901 void
902 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
903 {
904         using namespace Menu_Helpers;
905
906         boost::shared_ptr<Bundle> b = w.lock ();
907         if (!b) {
908                 return;
909         }
910
911         char buf [64];
912         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
913         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
914 }
915
916 void
917 PortMatrix::port_connected_or_disconnected ()
918 {
919         _body->rebuild_and_draw_grid ();
920 }
921
922 string
923 PortMatrix::channel_noun () const
924 {
925         return _("channel");
926 }
927
928 /** @return true if this matrix should show bundles / ports of type \t */
929 bool
930 PortMatrix::should_show (DataType t) const
931 {
932         return (_type == DataType::NIL || t == _type);
933 }
934
935 uint32_t
936 PortMatrix::count_of_our_type (ChanCount c) const
937 {
938         if (_type == DataType::NIL) {
939                 return c.n_total ();
940         }
941
942         return c.get (_type);
943 }
944
945 PortMatrixNode::State
946 PortMatrix::get_association (PortMatrixNode node) const
947 {
948         if (show_only_bundles ()) {
949
950                 bool have_off_diagonal_association = false;
951                 bool have_diagonal_association = false;
952                 bool have_diagonal_not_association = false;
953
954                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
955
956                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
957
958                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
959                                         continue;
960                                 }
961
962                                 ARDOUR::BundleChannel c[2];
963                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
964                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
965
966                                 PortMatrixNode::State const s = get_state (c);
967
968                                 switch (s) {
969                                 case PortMatrixNode::ASSOCIATED:
970                                         if (i == j) {
971                                                 have_diagonal_association = true;
972                                         } else {
973                                                 have_off_diagonal_association = true;
974                                         }
975                                         break;
976
977                                 case PortMatrixNode::NOT_ASSOCIATED:
978                                         if (i == j) {
979                                                 have_diagonal_not_association = true;
980                                         }
981                                         break;
982
983                                 default:
984                                         break;
985                                 }
986                         }
987                 }
988
989                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
990                         return PortMatrixNode::ASSOCIATED;
991                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
992                         return PortMatrixNode::NOT_ASSOCIATED;
993                 }
994
995                 return PortMatrixNode::PARTIAL;
996
997         } else {
998
999                 ARDOUR::BundleChannel c[2];
1000                 c[column_index()] = node.column;
1001                 c[row_index()] = node.row;
1002                 return get_state (c);
1003
1004         }
1005
1006         /* NOTREACHED */
1007         return PortMatrixNode::NOT_ASSOCIATED;
1008 }
1009