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