7125780ccf2a3be8714df48794a6f604a698d226
[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_min_1 (_ports[0].total_channels()),
293                 count_of_our_type_min_1 (_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 /** @param column Column; its bundle may be 0 if we are over a row heading.
383  *  @param row Row; its bundle may be 0 if we are over a column heading.
384  */
385 void
386 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
387 {
388         using namespace Menu_Helpers;
389
390         if ((row.bundle && row.bundle->nchannels().n_total() == 0) || (column.bundle && column.bundle->nchannels().n_total() == 0)) {
391                 /* One of the bundles has no channels, which means that it has none of the appropriate type,
392                    and is only being displayed to look pretty.  So we don't need to do anything.
393                 */
394                 return;
395         }
396         
397         delete _menu;
398
399         _menu = new Menu;
400         _menu->set_name ("ArdourContextMenu");
401
402         MenuList& items = _menu->items ();
403
404         BundleChannel bc[2];
405         bc[_column_index] = column;
406         bc[_row_index] = row;
407
408         char buf [64];
409         bool need_separator = false;
410
411         for (int dim = 0; dim < 2; ++dim) {
412
413                 if (bc[dim].bundle) {
414
415                         Menu* m = manage (new Menu);
416                         MenuList& sub = m->items ();
417
418                         boost::weak_ptr<Bundle> w (bc[dim].bundle);
419
420                         /* Start off with options for the `natural' port type */
421                         for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
422                                 if (should_show (*i)) {
423                                         snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
424                                         sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
425                                 }
426                         }
427
428                         /* Now add other ones */
429                         for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
430                                 if (!should_show (*i)) {
431                                         snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
432                                         sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
433                                 }
434                         }
435
436                         if (can_rename_channels (bc[dim].bundle)) {
437                                 snprintf (
438                                         buf, sizeof (buf), _("Rename '%s'..."),
439                                         escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
440                                         );
441                                 sub.push_back (
442                                         MenuElem (
443                                                 buf,
444                                                 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
445                                                 )
446                                         );
447                         }
448
449                         if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
450                                 if (bc[dim].channel != -1) {
451                                         add_remove_option (sub, w, bc[dim].channel);
452                                 } else {
453
454                                         snprintf (buf, sizeof (buf), _("Remove all"));
455                                         sub.push_back (
456                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
457                                                 );
458
459                                         if (bc[dim].bundle->nchannels().n_total() > 1) {
460                                                 for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
461                                                         if (should_show (bc[dim].bundle->channel_type(i))) {
462                                                                 add_remove_option (sub, w, i);
463                                                         }
464                                                 }
465                                         }
466                                 }
467                         }
468
469                         uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
470                         if ((_show_only_bundles && c > 0) || c == 1) {
471                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
472                                 sub.push_back (
473                                         MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
474                                         );
475
476                         } else {
477
478                                 if (bc[dim].channel != -1) {
479                                         add_disassociate_option (sub, w, dim, bc[dim].channel);
480                                 } else if (count_of_our_type (bc[dim].bundle->nchannels()) != 0) {
481                                         snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
482                                         sub.push_back (
483                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
484                                                 );
485
486                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
487                                                 if (should_show (bc[dim].bundle->channel_type(i))) {
488                                                         add_disassociate_option (sub, w, dim, i);
489                                                 }
490                                         }
491                                 }
492                         }
493
494                         items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
495                         need_separator = true;
496                 }
497
498         }
499
500         if (need_separator) {
501                 items.push_back (SeparatorElem ());
502         }
503
504         items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
505         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
506         CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
507         _inhibit_toggle_show_only_bundles = true;
508         i->set_active (!_show_only_bundles);
509         _inhibit_toggle_show_only_bundles = false;
510
511         _menu->popup (1, t);
512 }
513
514 void
515 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
516 {
517         boost::shared_ptr<Bundle> sb = b.lock ();
518         if (!sb) {
519                 return;
520         }
521
522         remove_channel (BundleChannel (sb, c));
523
524 }
525
526 void
527 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
528 {
529         boost::shared_ptr<Bundle> sb = b.lock ();
530         if (!sb) {
531                 return;
532         }
533
534         rename_channel (BundleChannel (sb, c));
535 }
536
537 void
538 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
539 {
540         boost::shared_ptr<Bundle> sb = bundle.lock ();
541         if (!sb) {
542                 return;
543         }
544
545         for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
546                 if (should_show (sb->channel_type(i))) {
547                         disassociate_all_on_channel (bundle, i, dim);
548                 }
549         }
550 }
551
552 void
553 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
554 {
555         boost::shared_ptr<Bundle> sb = bundle.lock ();
556         if (!sb) {
557                 return;
558         }
559
560         PortGroup::BundleList a = _ports[1-dim].bundles ();
561
562         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
563                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
564
565                         if (should_show ((*i)->bundle->channel_type(j))) {
566                                 continue;
567                         }
568
569                         BundleChannel c[2];
570                         c[dim] = BundleChannel (sb, channel);
571                         c[1-dim] = BundleChannel ((*i)->bundle, j);
572
573                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
574                                 set_state (c, false);
575                         }
576                 }
577         }
578
579         _body->rebuild_and_draw_grid ();
580 }
581
582 void
583 PortMatrix::setup_global_ports ()
584 {
585         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
586
587         for (int i = 0; i < 2; ++i) {
588                 if (list_is_global (i)) {
589                         setup_ports (i);
590                 }
591         }
592 }
593
594 void
595 PortMatrix::setup_global_ports_proxy ()
596 {
597         /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
598            for a discussion.
599         */
600
601         Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
602 }
603
604 void
605 PortMatrix::setup_all_ports ()
606 {
607         if (_session->deletion_in_progress()) {
608                 return;
609         }
610
611         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
612
613         setup_ports (0);
614         setup_ports (1);
615 }
616
617 void
618 PortMatrix::toggle_show_only_bundles ()
619 {
620         if (_inhibit_toggle_show_only_bundles) {
621                 return;
622         }
623
624         _show_only_bundles = !_show_only_bundles;
625
626         setup ();
627 }
628
629 pair<uint32_t, uint32_t>
630 PortMatrix::max_size () const
631 {
632         pair<uint32_t, uint32_t> m = _body->max_size ();
633
634         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
635         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
636
637         return m;
638 }
639
640 bool
641 PortMatrix::on_scroll_event (GdkEventScroll* ev)
642 {
643         double const h = _hscroll.get_value ();
644         double const v = _vscroll.get_value ();
645
646         switch (ev->direction) {
647         case GDK_SCROLL_UP:
648                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
649                 break;
650         case GDK_SCROLL_DOWN:
651                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
652                 break;
653         case GDK_SCROLL_LEFT:
654                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
655                 break;
656         case GDK_SCROLL_RIGHT:
657                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
658                 break;
659         }
660
661         return true;
662 }
663
664 boost::shared_ptr<IO>
665 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
666 {
667         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
668         if (!io) {
669                 io = _ports[1].io_from_bundle (b);
670         }
671
672         return io;
673 }
674
675 bool
676 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
677 {
678         return io_from_bundle (b);
679 }
680
681 void
682 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
683 {
684         boost::shared_ptr<IO> io = io_from_bundle (b);
685
686         if (io) {
687                 io->add_port ("", this, t);
688         }
689 }
690
691 bool
692 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
693 {
694         return io_from_bundle (b);
695 }
696
697 void
698 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
699 {
700         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
701
702         if (io) {
703                 boost::shared_ptr<Port> p = io->nth (b.channel);
704                 if (p) {
705                         int const r = io->remove_port (p, this);
706                         if (r == -1) {
707                                 ArdourDialog d (_("Port removal not allowed"));
708                                 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
709                                 d.get_vbox()->pack_start (l);
710                                 d.add_button (Stock::OK, RESPONSE_ACCEPT);
711                                 d.set_modal (true);
712                                 d.show_all ();
713                                 d.run ();
714                         }
715                 }
716         }
717 }
718
719 void
720 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
721 {
722         boost::shared_ptr<Bundle> b = w.lock ();
723         if (!b) {
724                 return;
725         }
726
727         /* Remove channels backwards so that we don't renumber channels
728            that we are about to remove.
729         */
730         for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
731                 if (should_show (b->channel_type(i))) {
732                         remove_channel (ARDOUR::BundleChannel (b, i));
733                 }
734         }
735 }
736
737 void
738 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
739 {
740         boost::shared_ptr<Bundle> b = w.lock ();
741         if (!b) {
742                 return;
743         }
744
745         add_channel (b, t);
746 }
747
748 void
749 PortMatrix::setup_notebooks ()
750 {
751         int const h_current_page = _hnotebook.get_current_page ();
752         int const v_current_page = _vnotebook.get_current_page ();
753
754         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
755            when adding or removing pages to or from notebooks, so ignore them */
756
757         _ignore_notebook_page_selected = true;
758
759         remove_notebook_pages (_hnotebook);
760         remove_notebook_pages (_vnotebook);
761
762         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
763                 HBox* dummy = manage (new HBox);
764                 dummy->show ();
765                 Label* label = manage (new Label ((*i)->name));
766                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
767                 label->show ();
768                 if (_arrangement == LEFT_TO_BOTTOM) {
769                         _vnotebook.prepend_page (*dummy, *label);
770                 } else {
771                         /* Reverse the order of vertical tabs when they are on the right hand side
772                            so that from top to bottom it is the same order as that from left to right
773                            for the top tabs.
774                         */
775                         _vnotebook.append_page (*dummy, *label);
776                 }
777         }
778
779         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
780                 HBox* dummy = manage (new HBox);
781                 dummy->show ();
782                 _hnotebook.append_page (*dummy, (*i)->name);
783         }
784
785         _ignore_notebook_page_selected = false;
786
787         if (_arrangement == TOP_TO_RIGHT) {
788                 _vnotebook.set_tab_pos (POS_RIGHT);
789                 _hnotebook.set_tab_pos (POS_TOP);
790         } else {
791                 _vnotebook.set_tab_pos (POS_LEFT);
792                 _hnotebook.set_tab_pos (POS_BOTTOM);
793         }
794
795         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
796                 _hnotebook.set_current_page (h_current_page);
797         } else {
798                 _hnotebook.set_current_page (0);
799         }
800
801         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
802                 _vnotebook.set_current_page (v_current_page);
803         } else {
804                 _vnotebook.set_current_page (0);
805         }
806
807         if (_hnotebook.get_n_pages() <= 1) {
808                 _hbox.hide ();
809         } else {
810                 _hbox.show ();
811         }
812
813         if (_vnotebook.get_n_pages() <= 1) {
814                 _vbox.hide ();
815         } else {
816                 _vbox.show ();
817         }
818 }
819
820 void
821 PortMatrix::remove_notebook_pages (Notebook& n)
822 {
823         int const N = n.get_n_pages ();
824
825         for (int i = 0; i < N; ++i) {
826                 n.remove_page ();
827         }
828 }
829
830 void
831 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
832 {
833         if (_ignore_notebook_page_selected) {
834                 return;
835         }
836
837         _body->setup ();
838         setup_scrollbars ();
839         queue_draw ();
840 }
841
842 void
843 PortMatrix::session_going_away ()
844 {
845         _session = 0;
846 }
847
848 void
849 PortMatrix::body_dimensions_changed ()
850 {
851         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
852         if (_arrangement == TOP_TO_RIGHT) {
853                 _vspacer.set_size_request (-1, _body->column_labels_height ());
854                 _vspacer.show ();
855         } else {
856                 _vspacer.hide ();
857         }
858
859         int curr_width;
860         int curr_height;
861         _parent->get_size (curr_width, curr_height);
862
863         pair<uint32_t, uint32_t> m = max_size ();
864
865         /* Don't shrink the window */
866         m.first = max (int (m.first), curr_width);
867         m.second = max (int (m.second), curr_height);
868
869         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
870 }
871
872 /** @return The PortGroup that is currently visible (ie selected by
873  *  the notebook) along a given axis.
874  */
875 boost::shared_ptr<const PortGroup>
876 PortMatrix::visible_ports (int d) const
877 {
878         PortGroupList const & p = _ports[d];
879         PortGroupList::List::const_iterator j = p.begin ();
880
881         /* The logic to compute the index here is a bit twisty because for
882            the TOP_TO_RIGHT arrangement we reverse the order of the vertical
883            tabs in setup_notebooks ().
884         */
885            
886         int n = 0;
887         if (d == _row_index) {
888                 if (_arrangement == LEFT_TO_BOTTOM) {
889                         n = p.size() - _vnotebook.get_current_page () - 1;
890                 } else {
891                         n = _vnotebook.get_current_page ();
892                 }
893         } else {
894                 n = _hnotebook.get_current_page ();
895         }
896
897         int i = 0;
898         while (i != int (n) && j != p.end ()) {
899                 ++i;
900                 ++j;
901         }
902
903         if (j == p.end()) {
904                 return boost::shared_ptr<const PortGroup> ();
905         }
906
907         return *j;
908 }
909
910 void
911 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
912 {
913         using namespace Menu_Helpers;
914
915         boost::shared_ptr<Bundle> b = w.lock ();
916         if (!b) {
917                 return;
918         }
919
920         char buf [64];
921         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
922         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
923 }
924
925 void
926 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
927 {
928         using namespace Menu_Helpers;
929
930         boost::shared_ptr<Bundle> b = w.lock ();
931         if (!b) {
932                 return;
933         }
934
935         char buf [64];
936         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
937         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
938 }
939
940 void
941 PortMatrix::port_connected_or_disconnected ()
942 {
943         _body->rebuild_and_draw_grid ();
944 }
945
946 string
947 PortMatrix::channel_noun () const
948 {
949         return _("channel");
950 }
951
952 /** @return true if this matrix should show bundles / ports of type \t */
953 bool
954 PortMatrix::should_show (DataType t) const
955 {
956         return (_type == DataType::NIL || t == _type);
957 }
958
959 uint32_t
960 PortMatrix::count_of_our_type (ChanCount c) const
961 {
962         if (_type == DataType::NIL) {
963                 return c.n_total ();
964         }
965
966         return c.get (_type);
967 }
968
969 /** @return The number of ports of our type in the given channel count,
970  *  but returning 1 if there are no ports.
971  */
972 uint32_t
973 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
974 {
975         uint32_t n = count_of_our_type (c);
976         if (n == 0) {
977                 n = 1;
978         }
979
980         return n;
981 }
982
983 PortMatrixNode::State
984 PortMatrix::get_association (PortMatrixNode node) const
985 {
986         if (show_only_bundles ()) {
987
988                 bool have_off_diagonal_association = false;
989                 bool have_diagonal_association = false;
990                 bool have_diagonal_not_association = false;
991
992                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
993
994                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
995
996                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
997                                         continue;
998                                 }
999
1000                                 ARDOUR::BundleChannel c[2];
1001                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1002                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1003
1004                                 PortMatrixNode::State const s = get_state (c);
1005
1006                                 switch (s) {
1007                                 case PortMatrixNode::ASSOCIATED:
1008                                         if (i == j) {
1009                                                 have_diagonal_association = true;
1010                                         } else {
1011                                                 have_off_diagonal_association = true;
1012                                         }
1013                                         break;
1014
1015                                 case PortMatrixNode::NOT_ASSOCIATED:
1016                                         if (i == j) {
1017                                                 have_diagonal_not_association = true;
1018                                         }
1019                                         break;
1020
1021                                 default:
1022                                         break;
1023                                 }
1024                         }
1025                 }
1026
1027                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1028                         return PortMatrixNode::ASSOCIATED;
1029                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1030                         return PortMatrixNode::NOT_ASSOCIATED;
1031                 }
1032
1033                 return PortMatrixNode::PARTIAL;
1034
1035         } else {
1036
1037                 ARDOUR::BundleChannel c[2];
1038                 c[column_index()] = node.column;
1039                 c[row_index()] = node.row;
1040                 return get_state (c);
1041
1042         }
1043
1044         /* NOTREACHED */
1045         return PortMatrixNode::NOT_ASSOCIATED;
1046 }
1047
1048 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1049 bool
1050 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1051 {
1052         return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1053 }