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