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