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