first pass at the big rethink of managing sort order keys for editor and mixer. this...
[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         Route::SyncOrderKeys.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this, _1), 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
508         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
509         CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
510         _inhibit_toggle_show_only_bundles = true;
511         i->set_active (!_show_only_bundles);
512         _inhibit_toggle_show_only_bundles = false;
513
514         items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
515         items.back().set_sensitive (can_flip ());
516         
517         _menu->popup (1, t);
518 }
519
520 void
521 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
522 {
523         boost::shared_ptr<Bundle> sb = b.lock ();
524         if (!sb) {
525                 return;
526         }
527
528         remove_channel (BundleChannel (sb, c));
529
530 }
531
532 void
533 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
534 {
535         boost::shared_ptr<Bundle> sb = b.lock ();
536         if (!sb) {
537                 return;
538         }
539
540         rename_channel (BundleChannel (sb, c));
541 }
542
543 void
544 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
545 {
546         boost::shared_ptr<Bundle> sb = bundle.lock ();
547         if (!sb) {
548                 return;
549         }
550
551         for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
552                 if (should_show (sb->channel_type(i))) {
553                         disassociate_all_on_channel (bundle, i, dim);
554                 }
555         }
556 }
557
558 void
559 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
560 {
561         boost::shared_ptr<Bundle> sb = bundle.lock ();
562         if (!sb) {
563                 return;
564         }
565
566         PortGroup::BundleList a = _ports[1-dim].bundles ();
567
568         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
569                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
570
571                         if (!should_show ((*i)->bundle->channel_type(j))) {
572                                 continue;
573                         }
574
575                         BundleChannel c[2];
576                         c[dim] = BundleChannel (sb, channel);
577                         c[1-dim] = BundleChannel ((*i)->bundle, j);
578
579                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
580                                 set_state (c, false);
581                         }
582                 }
583         }
584
585         _body->rebuild_and_draw_grid ();
586 }
587
588 void
589 PortMatrix::setup_global_ports ()
590 {
591         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
592
593         for (int i = 0; i < 2; ++i) {
594                 if (list_is_global (i)) {
595                         setup_ports (i);
596                 }
597         }
598 }
599
600 void
601 PortMatrix::setup_global_ports_proxy (RouteSortOrderKey sk)
602 {
603         if (sk == EditorSort) {
604                 /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
605                    for a discussion.
606                 */
607                 
608                 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
609         }
610 }
611
612 void
613 PortMatrix::setup_all_ports ()
614 {
615         if (_session->deletion_in_progress()) {
616                 return;
617         }
618
619         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
620
621         setup_ports (0);
622         setup_ports (1);
623 }
624
625 void
626 PortMatrix::toggle_show_only_bundles ()
627 {
628         if (_inhibit_toggle_show_only_bundles) {
629                 return;
630         }
631
632         _show_only_bundles = !_show_only_bundles;
633
634         setup ();
635
636         /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
637            setting, so we need to set things up again now.
638         */
639         setup_all_ports ();
640 }
641
642 pair<uint32_t, uint32_t>
643 PortMatrix::max_size () const
644 {
645         pair<uint32_t, uint32_t> m = _body->max_size ();
646
647         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
648         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
649
650         return m;
651 }
652
653 bool
654 PortMatrix::on_scroll_event (GdkEventScroll* ev)
655 {
656         double const h = _hscroll.get_value ();
657         double const v = _vscroll.get_value ();
658
659         switch (ev->direction) {
660         case GDK_SCROLL_UP:
661                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
662                 break;
663         case GDK_SCROLL_DOWN:
664                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
665                 break;
666         case GDK_SCROLL_LEFT:
667                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
668                 break;
669         case GDK_SCROLL_RIGHT:
670                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
671                 break;
672         }
673
674         return true;
675 }
676
677 boost::shared_ptr<IO>
678 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
679 {
680         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
681         if (!io) {
682                 io = _ports[1].io_from_bundle (b);
683         }
684
685         return io;
686 }
687
688 bool
689 PortMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
690 {
691         return io_from_bundle (b);
692 }
693
694 void
695 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
696 {
697         boost::shared_ptr<IO> io = io_from_bundle (b);
698
699         if (io) {
700                 int const r = io->add_port ("", this, t);
701                 if (r == -1) {
702                         Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
703                                                   "support the new configuration."
704                                                         ));
705                         msg.set_title (_("Cannot add port"));
706                         msg.run ();
707                 }
708         }
709 }
710
711 bool
712 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
713 {
714         return io_from_bundle (b);
715 }
716
717 void
718 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
719 {
720         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
721
722         if (io) {
723                 boost::shared_ptr<Port> p = io->nth (b.channel);
724                 if (p) {
725                         int const r = io->remove_port (p, this);
726                         if (r == -1) {
727                                 ArdourDialog d (_("Port removal not allowed"));
728                                 Label l (_("This port cannot be removed, as the first plugin in the track or buss cannot accept the new number of inputs."));
729                                 d.get_vbox()->pack_start (l);
730                                 d.add_button (Stock::OK, RESPONSE_ACCEPT);
731                                 d.set_modal (true);
732                                 d.show_all ();
733                                 d.run ();
734                         }
735                 }
736         }
737 }
738
739 void
740 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
741 {
742         boost::shared_ptr<Bundle> b = w.lock ();
743         if (!b) {
744                 return;
745         }
746
747         /* Remove channels backwards so that we don't renumber channels
748            that we are about to remove.
749         */
750         for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
751                 if (should_show (b->channel_type(i))) {
752                         remove_channel (ARDOUR::BundleChannel (b, i));
753                 }
754         }
755 }
756
757 void
758 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
759 {
760         boost::shared_ptr<Bundle> b = w.lock ();
761         if (!b) {
762                 return;
763         }
764
765         add_channel (b, t);
766 }
767
768 void
769 PortMatrix::setup_notebooks ()
770 {
771         int const h_current_page = _hnotebook.get_current_page ();
772         int const v_current_page = _vnotebook.get_current_page ();
773
774         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
775            when adding or removing pages to or from notebooks, so ignore them */
776
777         _ignore_notebook_page_selected = true;
778
779         remove_notebook_pages (_hnotebook);
780         remove_notebook_pages (_vnotebook);
781
782         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
783                 HBox* dummy = manage (new HBox);
784                 dummy->show ();
785                 Label* label = manage (new Label ((*i)->name));
786                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
787                 label->set_use_markup ();
788                 label->show ();
789                 if (_arrangement == LEFT_TO_BOTTOM) {
790                         _vnotebook.prepend_page (*dummy, *label);
791                 } else {
792                         /* Reverse the order of vertical tabs when they are on the right hand side
793                            so that from top to bottom it is the same order as that from left to right
794                            for the top tabs.
795                         */
796                         _vnotebook.append_page (*dummy, *label);
797                 }
798         }
799
800         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
801                 HBox* dummy = manage (new HBox);
802                 dummy->show ();
803                 Label* label = manage (new Label ((*i)->name));
804                 label->set_use_markup ();
805                 label->show ();
806                 _hnotebook.append_page (*dummy, *label);
807         }
808
809         _ignore_notebook_page_selected = false;
810
811         if (_arrangement == TOP_TO_RIGHT) {
812                 _vnotebook.set_tab_pos (POS_RIGHT);
813                 _hnotebook.set_tab_pos (POS_TOP);
814         } else {
815                 _vnotebook.set_tab_pos (POS_LEFT);
816                 _hnotebook.set_tab_pos (POS_BOTTOM);
817         }
818
819         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
820                 _hnotebook.set_current_page (h_current_page);
821         } else {
822                 _hnotebook.set_current_page (0);
823         }
824
825         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
826                 _vnotebook.set_current_page (v_current_page);
827         } else {
828                 _vnotebook.set_current_page (0);
829         }
830
831         if (_hnotebook.get_n_pages() <= 1) {
832                 _hbox.hide ();
833         } else {
834                 _hbox.show ();
835         }
836
837         if (_vnotebook.get_n_pages() <= 1) {
838                 _vbox.hide ();
839         } else {
840                 _vbox.show ();
841         }
842 }
843
844 void
845 PortMatrix::remove_notebook_pages (Notebook& n)
846 {
847         int const N = n.get_n_pages ();
848
849         for (int i = 0; i < N; ++i) {
850                 n.remove_page ();
851         }
852 }
853
854 void
855 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
856 {
857         if (_ignore_notebook_page_selected) {
858                 return;
859         }
860
861         _body->setup ();
862         setup_scrollbars ();
863         queue_draw ();
864 }
865
866 void
867 PortMatrix::session_going_away ()
868 {
869         _session = 0;
870 }
871
872 void
873 PortMatrix::body_dimensions_changed ()
874 {
875         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
876         if (_arrangement == TOP_TO_RIGHT) {
877                 _vspacer.set_size_request (-1, _body->column_labels_height ());
878                 _vspacer.show ();
879         } else {
880                 _vspacer.hide ();
881         }
882
883         int curr_width;
884         int curr_height;
885         _parent->get_size (curr_width, curr_height);
886
887         pair<uint32_t, uint32_t> m = max_size ();
888
889         /* Don't shrink the window */
890         m.first = max (int (m.first), curr_width);
891         m.second = max (int (m.second), curr_height);
892
893         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
894 }
895
896 /** @return The PortGroup that is currently visible (ie selected by
897  *  the notebook) along a given axis.
898  */
899 boost::shared_ptr<const PortGroup>
900 PortMatrix::visible_ports (int d) const
901 {
902         PortGroupList const & p = _ports[d];
903         PortGroupList::List::const_iterator j = p.begin ();
904
905         /* The logic to compute the index here is a bit twisty because for
906            the TOP_TO_RIGHT arrangement we reverse the order of the vertical
907            tabs in setup_notebooks ().
908         */
909            
910         int n = 0;
911         if (d == _row_index) {
912                 if (_arrangement == LEFT_TO_BOTTOM) {
913                         n = p.size() - _vnotebook.get_current_page () - 1;
914                 } else {
915                         n = _vnotebook.get_current_page ();
916                 }
917         } else {
918                 n = _hnotebook.get_current_page ();
919         }
920
921         int i = 0;
922         while (i != int (n) && j != p.end ()) {
923                 ++i;
924                 ++j;
925         }
926
927         if (j == p.end()) {
928                 return boost::shared_ptr<const PortGroup> ();
929         }
930
931         return *j;
932 }
933
934 void
935 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
936 {
937         using namespace Menu_Helpers;
938
939         boost::shared_ptr<Bundle> b = w.lock ();
940         if (!b) {
941                 return;
942         }
943
944         char buf [64];
945         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
946         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
947 }
948
949 void
950 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
951 {
952         using namespace Menu_Helpers;
953
954         boost::shared_ptr<Bundle> b = w.lock ();
955         if (!b) {
956                 return;
957         }
958
959         char buf [64];
960         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
961         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
962 }
963
964 void
965 PortMatrix::port_connected_or_disconnected ()
966 {
967         _body->rebuild_and_draw_grid ();
968         update_tab_highlighting ();
969 }
970
971 /** Update the highlighting of tab names to reflect which ones
972  *  have connections.  This is pretty inefficient, unfortunately,
973  *  but maybe that doesn't matter too much.
974  */
975 void
976 PortMatrix::update_tab_highlighting ()
977 {
978         if (!_session) {
979                 return;
980         }
981         
982         for (int i = 0; i < 2; ++i) {
983
984                 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
985                 
986                 PortGroupList const * gl = ports (i);
987                 int p = 0;
988                 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
989                         bool has_connection = false;
990                         PortGroup::BundleList const & bl = (*j)->bundles ();
991                         PortGroup::BundleList::const_iterator k = bl.begin ();
992                         while (k != bl.end()) {
993                                 if ((*k)->bundle->connected_to_anything (_session->engine())) {
994                                         has_connection = true;
995                                         break;
996                                 }
997                                 ++k;
998                         }
999
1000                         /* Find the page index that we should update; this is backwards
1001                            for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1002                         */
1003                         int page = p;
1004                         if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1005                                 page = notebook->get_n_pages() - p - 1;
1006                         }
1007
1008                         Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1009                         string c = label->get_label ();
1010                         if (c.length() && c[0] == '<' && !has_connection) {
1011                                 /* this label is marked up with <b> but shouldn't be */
1012                                 label->set_markup ((*j)->name);
1013                         } else if (c.length() && c[0] != '<' && has_connection) {
1014                                 /* this label is not marked up with <b> but should be */
1015                                 label->set_markup (string_compose ("<b>%1</b>", (*j)->name));
1016                         }
1017
1018                         ++p;
1019                 }
1020         }
1021 }
1022
1023 string
1024 PortMatrix::channel_noun () const
1025 {
1026         return _("channel");
1027 }
1028
1029 /** @return true if this matrix should show bundles / ports of type \t */
1030 bool
1031 PortMatrix::should_show (DataType t) const
1032 {
1033         return (_type == DataType::NIL || t == _type);
1034 }
1035
1036 uint32_t
1037 PortMatrix::count_of_our_type (ChanCount c) const
1038 {
1039         if (_type == DataType::NIL) {
1040                 return c.n_total ();
1041         }
1042
1043         return c.get (_type);
1044 }
1045
1046 /** @return The number of ports of our type in the given channel count,
1047  *  but returning 1 if there are no ports.
1048  */
1049 uint32_t
1050 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1051 {
1052         uint32_t n = count_of_our_type (c);
1053         if (n == 0) {
1054                 n = 1;
1055         }
1056
1057         return n;
1058 }
1059
1060 PortMatrixNode::State
1061 PortMatrix::get_association (PortMatrixNode node) const
1062 {
1063         if (show_only_bundles ()) {
1064
1065                 bool have_off_diagonal_association = false;
1066                 bool have_diagonal_association = false;
1067                 bool have_diagonal_not_association = false;
1068
1069                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1070
1071                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1072
1073                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1074                                         continue;
1075                                 }
1076
1077                                 ARDOUR::BundleChannel c[2];
1078                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1079                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1080
1081                                 PortMatrixNode::State const s = get_state (c);
1082
1083                                 switch (s) {
1084                                 case PortMatrixNode::ASSOCIATED:
1085                                         if (i == j) {
1086                                                 have_diagonal_association = true;
1087                                         } else {
1088                                                 have_off_diagonal_association = true;
1089                                         }
1090                                         break;
1091
1092                                 case PortMatrixNode::NOT_ASSOCIATED:
1093                                         if (i == j) {
1094                                                 have_diagonal_not_association = true;
1095                                         }
1096                                         break;
1097
1098                                 default:
1099                                         break;
1100                                 }
1101                         }
1102                 }
1103
1104                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1105                         return PortMatrixNode::ASSOCIATED;
1106                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1107                         return PortMatrixNode::NOT_ASSOCIATED;
1108                 }
1109
1110                 return PortMatrixNode::PARTIAL;
1111
1112         } else {
1113
1114                 ARDOUR::BundleChannel c[2];
1115                 c[column_index()] = node.column;
1116                 c[row_index()] = node.row;
1117                 return get_state (c);
1118
1119         }
1120
1121         /* NOTREACHED */
1122         return PortMatrixNode::NOT_ASSOCIATED;
1123 }
1124
1125 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1126 bool
1127 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1128 {
1129         return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1130 }
1131
1132 /** See if a `flip' is possible.
1133  *  @return If flip is possible, the new (row, column) notebook indices that
1134  *  should be selected; otherwise, (-1, -1)
1135  */
1136 pair<int, int>
1137 PortMatrix::check_flip () const
1138 {
1139         /* Look for the row's port group name in the columns */
1140         
1141         int new_column = 0;
1142         boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1143         PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1144         while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1145                 ++i;
1146                 ++new_column;
1147         }
1148
1149         if (i == _ports[_column_index].end ()) {
1150                 return make_pair (-1, -1);
1151         }
1152
1153         /* Look for the column's port group name in the rows */
1154         
1155         int new_row = 0;
1156         boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1157         i = _ports[_row_index].begin();
1158         while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1159                 ++i;
1160                 ++new_row;
1161         }
1162
1163         if (i == _ports[_row_index].end ()) {
1164                 return make_pair (-1, -1);
1165         }
1166
1167         if (_arrangement == LEFT_TO_BOTTOM) {
1168                 new_row = _ports[_row_index].size() - new_row - 1;
1169         }
1170
1171         return make_pair (new_row, new_column);
1172 }
1173
1174 bool
1175 PortMatrix::can_flip () const
1176 {
1177         return check_flip().first != -1;
1178 }
1179
1180 /** Flip the column and row pages around, if possible */
1181 void
1182 PortMatrix::flip ()
1183 {
1184         pair<int, int> n = check_flip ();
1185         if (n.first == -1) {
1186                 return;
1187         }
1188
1189         _vnotebook.set_current_page (n.first);
1190         _hnotebook.set_current_page (n.second);
1191 }
1192
1193 bool
1194 PortMatrix::key_press (GdkEventKey* k)
1195 {
1196         if (k->keyval == GDK_f) {
1197                 flip ();
1198                 return true;
1199         }
1200
1201         return false;
1202 }