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