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