first vaguely working version using PresentationInfo
[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 "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         Stripable::PresentationInfoChange.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)) {
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)) {
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 void
794 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
795 {
796         boost::shared_ptr<Bundle> b = w.lock ();
797         if (!b) {
798                 return;
799         }
800
801         add_channel (b, t);
802 }
803
804 void
805 PortMatrix::setup_notebooks ()
806 {
807         int const h_current_page = _hnotebook.get_current_page ();
808         int const v_current_page = _vnotebook.get_current_page ();
809
810         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
811            when adding or removing pages to or from notebooks, so ignore them */
812
813         _ignore_notebook_page_selected = true;
814
815         remove_notebook_pages (_hnotebook);
816         remove_notebook_pages (_vnotebook);
817
818         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
819                 HBox* dummy = manage (new HBox);
820                 dummy->show ();
821                 Label* label = manage (new Label ((*i)->name));
822                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
823                 label->set_use_markup ();
824                 label->show ();
825                 if (_arrangement == LEFT_TO_BOTTOM) {
826                         _vnotebook.prepend_page (*dummy, *label);
827                 } else {
828                         /* Reverse the order of vertical tabs when they are on the right hand side
829                            so that from top to bottom it is the same order as that from left to right
830                            for the top tabs.
831                         */
832                         _vnotebook.append_page (*dummy, *label);
833                 }
834         }
835
836         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
837                 HBox* dummy = manage (new HBox);
838                 dummy->show ();
839                 Label* label = manage (new Label ((*i)->name));
840                 label->set_use_markup ();
841                 label->show ();
842                 _hnotebook.append_page (*dummy, *label);
843         }
844
845         _ignore_notebook_page_selected = false;
846
847         if (_arrangement == TOP_TO_RIGHT) {
848                 _vnotebook.set_tab_pos (POS_RIGHT);
849                 _hnotebook.set_tab_pos (POS_TOP);
850         } else {
851                 _vnotebook.set_tab_pos (POS_LEFT);
852                 _hnotebook.set_tab_pos (POS_BOTTOM);
853         }
854
855         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
856                 _hnotebook.set_current_page (h_current_page);
857         } else {
858                 _hnotebook.set_current_page (0);
859         }
860
861         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
862                 _vnotebook.set_current_page (v_current_page);
863         } else {
864                 _vnotebook.set_current_page (0);
865         }
866
867         if (_hnotebook.get_n_pages() <= 1) {
868                 _hbox.hide ();
869         } else {
870                 _hbox.show ();
871         }
872
873         if (_vnotebook.get_n_pages() <= 1) {
874                 _vbox.hide ();
875         } else {
876                 _vbox.show ();
877         }
878 }
879
880 void
881 PortMatrix::remove_notebook_pages (Notebook& n)
882 {
883         int const N = n.get_n_pages ();
884
885         for (int i = 0; i < N; ++i) {
886                 n.remove_page ();
887         }
888 }
889
890 void
891 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
892 {
893         if (_ignore_notebook_page_selected) {
894                 return;
895         }
896
897         _body->setup ();
898         setup_scrollbars ();
899         queue_draw ();
900 }
901
902 void
903 PortMatrix::session_going_away ()
904 {
905         _session = 0;
906 }
907
908 void
909 PortMatrix::body_dimensions_changed ()
910 {
911         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
912         if (_arrangement == TOP_TO_RIGHT) {
913                 _vspacer.set_size_request (-1, _body->column_labels_height ());
914                 _vspacer.show ();
915         } else {
916                 _vspacer.hide ();
917         }
918
919         int curr_width;
920         int curr_height;
921         _parent->get_size (curr_width, curr_height);
922
923         pair<uint32_t, uint32_t> m = max_size ();
924
925         /* Don't shrink the window */
926         m.first = max (int (m.first), curr_width);
927         m.second = max (int (m.second), curr_height);
928
929         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
930 }
931
932 /** @return The PortGroup that is currently visible (ie selected by
933  *  the notebook) along a given axis.
934  */
935 boost::shared_ptr<const PortGroup>
936 PortMatrix::visible_ports (int d) const
937 {
938         PortGroupList const & p = _ports[d];
939         PortGroupList::List::const_iterator j = p.begin ();
940
941         /* The logic to compute the index here is a bit twisty because for
942            the TOP_TO_RIGHT arrangement we reverse the order of the vertical
943            tabs in setup_notebooks ().
944         */
945
946         int n = 0;
947         if (d == _row_index) {
948                 if (_arrangement == LEFT_TO_BOTTOM) {
949                         n = p.size() - _vnotebook.get_current_page () - 1;
950                 } else {
951                         n = _vnotebook.get_current_page ();
952                 }
953         } else {
954                 n = _hnotebook.get_current_page ();
955         }
956
957         int i = 0;
958         while (i != int (n) && j != p.end ()) {
959                 ++i;
960                 ++j;
961         }
962
963         if (j == p.end()) {
964                 return boost::shared_ptr<const PortGroup> ();
965         }
966
967         return *j;
968 }
969
970 void
971 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
972 {
973         using namespace Menu_Helpers;
974
975         boost::shared_ptr<Bundle> b = w.lock ();
976         if (!b) {
977                 return;
978         }
979
980         char buf [64];
981         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
982         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
983 }
984
985 void
986 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
987 {
988         using namespace Menu_Helpers;
989
990         boost::shared_ptr<Bundle> b = w.lock ();
991         if (!b) {
992                 return;
993         }
994
995         char buf [64];
996         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
997         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
998 }
999
1000 void
1001 PortMatrix::port_connected_or_disconnected ()
1002 {
1003         _body->rebuild_and_draw_grid ();
1004         update_tab_highlighting ();
1005 }
1006
1007 /** Update the highlighting of tab names to reflect which ones
1008  *  have connections.  This is pretty inefficient, unfortunately,
1009  *  but maybe that doesn't matter too much.
1010  */
1011 void
1012 PortMatrix::update_tab_highlighting ()
1013 {
1014         if (!_session) {
1015                 return;
1016         }
1017
1018         for (int i = 0; i < 2; ++i) {
1019
1020                 Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1021
1022                 PortGroupList const * gl = ports (i);
1023                 int p = 0;
1024                 for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1025                         bool has_connection = false;
1026                         PortGroup::BundleList const & bl = (*j)->bundles ();
1027                         PortGroup::BundleList::const_iterator k = bl.begin ();
1028                         while (k != bl.end()) {
1029                                 if ((*k)->bundle->connected_to_anything (_session->engine())) {
1030                                         has_connection = true;
1031                                         break;
1032                                 }
1033                                 ++k;
1034                         }
1035
1036                         /* Find the page index that we should update; this is backwards
1037                            for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1038                         */
1039                         int page = p;
1040                         if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1041                                 page = notebook->get_n_pages() - p - 1;
1042                         }
1043
1044                         Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1045                         string c = label->get_label ();
1046                         if (c.length() && c[0] == '<' && !has_connection) {
1047                                 /* this label is marked up with <b> but shouldn't be */
1048                                 label->set_text ((*j)->name);
1049                         } else if (c.length() && c[0] != '<' && has_connection) {
1050                                 /* this label is not marked up with <b> but should be */
1051                                 label->set_markup (string_compose ("<b>%1</b>", Gtkmm2ext::markup_escape_text ((*j)->name)));
1052                         }
1053
1054                         ++p;
1055                 }
1056         }
1057 }
1058
1059 string
1060 PortMatrix::channel_noun () const
1061 {
1062         return _("channel");
1063 }
1064
1065 /** @return true if this matrix should show bundles / ports of type \t */
1066 bool
1067 PortMatrix::should_show (DataType t) const
1068 {
1069         return (_type == DataType::NIL || t == _type);
1070 }
1071
1072 uint32_t
1073 PortMatrix::count_of_our_type (ChanCount c) const
1074 {
1075         if (_type == DataType::NIL) {
1076                 return c.n_total ();
1077         }
1078
1079         return c.get (_type);
1080 }
1081
1082 /** @return The number of ports of our type in the given channel count,
1083  *  but returning 1 if there are no ports.
1084  */
1085 uint32_t
1086 PortMatrix::count_of_our_type_min_1 (ChanCount c) const
1087 {
1088         uint32_t n = count_of_our_type (c);
1089         if (n == 0) {
1090                 n = 1;
1091         }
1092
1093         return n;
1094 }
1095
1096 PortMatrixNode::State
1097 PortMatrix::get_association (PortMatrixNode node) const
1098 {
1099         if (show_only_bundles ()) {
1100
1101                 bool have_off_diagonal_association = false;
1102                 bool have_diagonal_association = false;
1103                 bool have_diagonal_not_association = false;
1104
1105                 for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1106
1107                         for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1108
1109                                 if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1110                                         continue;
1111                                 }
1112
1113                                 ARDOUR::BundleChannel c[2];
1114                                 c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1115                                 c[column_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
1116
1117                                 PortMatrixNode::State const s = get_state (c);
1118
1119                                 switch (s) {
1120                                 case PortMatrixNode::ASSOCIATED:
1121                                         if (i == j) {
1122                                                 have_diagonal_association = true;
1123                                         } else {
1124                                                 have_off_diagonal_association = true;
1125                                         }
1126                                         break;
1127
1128                                 case PortMatrixNode::NOT_ASSOCIATED:
1129                                         if (i == j) {
1130                                                 have_diagonal_not_association = true;
1131                                         }
1132                                         break;
1133
1134                                 default:
1135                                         break;
1136                                 }
1137                         }
1138                 }
1139
1140                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1141                         return PortMatrixNode::ASSOCIATED;
1142                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
1143                         return PortMatrixNode::NOT_ASSOCIATED;
1144                 }
1145
1146                 return PortMatrixNode::PARTIAL;
1147
1148         } else {
1149
1150                 ARDOUR::BundleChannel c[2];
1151                 c[column_index()] = node.column;
1152                 c[row_index()] = node.row;
1153                 return get_state (c);
1154
1155         }
1156
1157         abort(); /* NOTREACHED */
1158         return PortMatrixNode::NOT_ASSOCIATED;
1159 }
1160
1161 /** @return true if b is a non-zero pointer and the bundle it points to has some channels */
1162 bool
1163 PortMatrix::bundle_with_channels (boost::shared_ptr<ARDOUR::Bundle> b)
1164 {
1165         return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1166 }
1167
1168 /** See if a `flip' is possible.
1169  *  @return If flip is possible, the new (row, column) notebook indices that
1170  *  should be selected; otherwise, (-1, -1)
1171  */
1172 pair<int, int>
1173 PortMatrix::check_flip () const
1174 {
1175         /* Look for the row's port group name in the columns */
1176
1177         int new_column = 0;
1178         boost::shared_ptr<const PortGroup> r = visible_ports (_row_index);
1179         PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1180         while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1181                 ++i;
1182                 ++new_column;
1183         }
1184
1185         if (i == _ports[_column_index].end ()) {
1186                 return make_pair (-1, -1);
1187         }
1188
1189         /* Look for the column's port group name in the rows */
1190
1191         int new_row = 0;
1192         boost::shared_ptr<const PortGroup> c = visible_ports (_column_index);
1193         i = _ports[_row_index].begin();
1194         while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1195                 ++i;
1196                 ++new_row;
1197         }
1198
1199         if (i == _ports[_row_index].end ()) {
1200                 return make_pair (-1, -1);
1201         }
1202
1203         if (_arrangement == LEFT_TO_BOTTOM) {
1204                 new_row = _ports[_row_index].size() - new_row - 1;
1205         }
1206
1207         return make_pair (new_row, new_column);
1208 }
1209
1210 bool
1211 PortMatrix::can_flip () const
1212 {
1213         return check_flip().first != -1;
1214 }
1215
1216 /** Flip the column and row pages around, if possible */
1217 void
1218 PortMatrix::flip ()
1219 {
1220         pair<int, int> n = check_flip ();
1221         if (n.first == -1) {
1222                 return;
1223         }
1224
1225         _vnotebook.set_current_page (n.first);
1226         _hnotebook.set_current_page (n.second);
1227 }
1228
1229 bool
1230 PortMatrix::key_press (GdkEventKey* k)
1231 {
1232         if (k->keyval == GDK_f) {
1233                 flip ();
1234                 return true;
1235         }
1236
1237         return false;
1238 }