Some thread safety for the previous commit.
[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 "ardour/bundle.h"
29 #include "ardour/types.h"
30 #include "ardour/session.h"
31 #include "ardour/route.h"
32 #include "ardour/audioengine.h"
33 #include "port_matrix.h"
34 #include "port_matrix_body.h"
35 #include "port_matrix_component.h"
36 #include "i18n.h"
37 #include "gui_thread.h"
38
39 using namespace std;
40 using namespace sigc;
41 using namespace Gtk;
42 using namespace ARDOUR;
43
44 /** PortMatrix constructor.
45  *  @param session Our session.
46  *  @param type Port type that we are handling.
47  */
48 PortMatrix::PortMatrix (Window* parent, Session& session, DataType type)
49         : Table (2, 2),
50           _session (session),
51           _parent (parent),
52           _type (type),
53           _menu (0),
54           _arrangement (TOP_TO_RIGHT),
55           _row_index (0),
56           _column_index (1),
57           _min_height_divisor (1),
58           _show_only_bundles (false),
59           _inhibit_toggle_show_only_bundles (false)
60 {
61         _body = new PortMatrixBody (this);
62
63         for (int i = 0; i < 2; ++i) {
64                 _ports[i].set_type (type);
65                 
66                 /* watch for the content of _ports[] changing */
67                 _ports[i].Changed.connect (mem_fun (*this, &PortMatrix::setup));
68         }
69
70         _hscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::hscroll_changed));
71         _vscroll.signal_value_changed().connect (mem_fun (*this, &PortMatrix::vscroll_changed));
72
73         /* watch for routes being added or removed */
74         _session.RouteAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::routes_changed)));
75
76         /* and also bundles */
77         _session.BundleAdded.connect (sigc::hide (mem_fun (*this, &PortMatrix::setup_global_ports)));
78
79         /* and also ports */
80         _session.engine().PortRegisteredOrUnregistered.connect (mem_fun (*this, &PortMatrix::setup_all_ports));
81         
82         reconnect_to_routes ();
83
84         attach (*_body, 0, 1, 0, 1);
85         attach (_vscroll, 1, 2, 0, 1, SHRINK);
86         attach (_hscroll, 0, 1, 1, 2, FILL | EXPAND, SHRINK);
87         
88         show_all ();
89 }
90
91 PortMatrix::~PortMatrix ()
92 {
93         delete _body;
94         delete _menu;
95 }
96
97 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
98 void
99 PortMatrix::reconnect_to_routes ()
100 {
101         for (vector<connection>::iterator i = _route_connections.begin(); i != _route_connections.end(); ++i) {
102                 i->disconnect ();
103         }
104         _route_connections.clear ();
105
106         boost::shared_ptr<RouteList> routes = _session.get_routes ();
107         for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
108                 _route_connections.push_back (
109                         (*i)->processors_changed.connect (mem_fun (*this, &PortMatrix::setup_global_ports))
110                         );
111         }
112 }
113
114 /** A route has been added to or removed from the session */
115 void
116 PortMatrix::routes_changed ()
117 {
118         reconnect_to_routes ();
119         setup_global_ports ();
120 }
121
122 /** Set up everything that depends on the content of _ports[] */
123 void
124 PortMatrix::setup ()
125 {
126         if ((get_flags () & Gtk::REALIZED) == 0) {
127                 select_arrangement ();
128         }
129
130         _body->setup ();
131         setup_scrollbars ();
132         queue_draw ();
133
134         show_all ();
135 }
136
137 void
138 PortMatrix::set_type (DataType t)
139 {
140         _type = t;
141         _ports[0].set_type (_type);
142         _ports[1].set_type (_type);
143         
144         setup_all_ports ();
145 }
146
147 void
148 PortMatrix::hscroll_changed ()
149 {
150         _body->set_xoffset (_hscroll.get_adjustment()->get_value());
151 }
152
153 void
154 PortMatrix::vscroll_changed ()
155 {
156         _body->set_yoffset (_vscroll.get_adjustment()->get_value());
157 }
158
159 void
160 PortMatrix::setup_scrollbars ()
161 {
162         Adjustment* a = _hscroll.get_adjustment ();
163         a->set_lower (0);
164         a->set_upper (_body->full_scroll_width());
165         a->set_page_size (_body->alloc_scroll_width());
166         a->set_step_increment (32);
167         a->set_page_increment (128);
168
169         a = _vscroll.get_adjustment ();
170         a->set_lower (0);
171         a->set_upper (_body->full_scroll_height());
172         a->set_page_size (_body->alloc_scroll_height());
173         a->set_step_increment (32);
174         a->set_page_increment (128);
175 }
176
177 /** Disassociate all of our ports from each other */
178 void
179 PortMatrix::disassociate_all ()
180 {
181         PortGroup::BundleList a = _ports[0].bundles ();
182         PortGroup::BundleList b = _ports[1].bundles ();
183
184         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
185                 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
186                         for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
187                                 for (uint32_t l = 0; l < k->bundle->nchannels(); ++l) {
188                                                 
189                                         BundleChannel c[2] = {
190                                                 BundleChannel (i->bundle, j),
191                                                 BundleChannel (k->bundle, l)
192                                                         };
193
194                                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
195                                                 set_state (c, false);
196                                         }
197
198                                 }
199                         }
200                 }
201         }
202
203         _body->rebuild_and_draw_grid ();
204 }
205
206 /* Decide how to arrange the components of the matrix */
207 void
208 PortMatrix::select_arrangement ()
209 {
210         uint32_t const N[2] = {
211                 _ports[0].total_visible_channels (),
212                 _ports[1].total_visible_channels ()
213         };
214
215         /* The list with the most channels goes on left or right, so that the most channel
216            names are printed horizontally and hence more readable.  However we also
217            maintain notional `signal flow' vaguely from left to right.  Subclasses
218            should choose where to put ports based on signal flowing from _ports[0]
219            to _ports[1] */
220         
221         if (N[0] > N[1]) {
222
223                 _row_index = 0;
224                 _column_index = 1;
225                 _arrangement = LEFT_TO_BOTTOM;
226
227         } else {
228
229                 _row_index = 1;
230                 _column_index = 0;
231                 _arrangement = TOP_TO_RIGHT;
232         }
233 }
234
235 /** @return columns list */
236 PortGroupList const *
237 PortMatrix::columns () const
238 {
239         return &_ports[_column_index];
240 }
241
242 /* @return rows list */
243 PortGroupList const *
244 PortMatrix::rows () const
245 {
246         return &_ports[_row_index];
247 }
248
249 void
250 PortMatrix::popup_menu (
251         pair<boost::shared_ptr<PortGroup>, BundleChannel> column,
252         pair<boost::shared_ptr<PortGroup>, BundleChannel> row,
253         uint32_t t
254         )
255 {
256         using namespace Menu_Helpers;
257         
258         delete _menu;
259
260         _menu = new Menu;
261         _menu->set_name ("ArdourContextMenu");
262         
263         MenuList& items = _menu->items ();
264
265         boost::shared_ptr<PortGroup> pg[2];
266         pg[_column_index] = column.first;
267         pg[_row_index] = row.first;
268
269         BundleChannel bc[2];
270         bc[_column_index] = column.second;
271         bc[_row_index] = row.second;
272
273         char buf [64];
274         bool need_separator = false;
275
276         for (int dim = 0; dim < 2; ++dim) {
277
278                 if (bc[dim].bundle) {
279
280                         Menu* m = manage (new Menu);
281                         MenuList& sub = m->items ();
282
283                         boost::weak_ptr<Bundle> w (bc[dim].bundle);
284
285                         if (can_add_channel (bc[dim].bundle)) {
286                                 snprintf (buf, sizeof (buf), _("Add %s"), channel_noun().c_str());
287                                 sub.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::add_channel_proxy), w)));
288                         }
289                         
290                         
291                         if (can_rename_channels (bc[dim].bundle)) {
292                                 snprintf (buf, sizeof (buf), _("Rename '%s'..."), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
293                                 sub.push_back (
294                                         MenuElem (
295                                                 buf,
296                                                 bind (mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
297                                                 )
298                                         );
299                         }
300
301                         sub.push_back (SeparatorElem ());
302                         
303                         if (can_remove_channels (bc[dim].bundle)) {
304                                 snprintf (buf, sizeof (buf), _("Remove '%s'"), bc[dim].bundle->channel_name (bc[dim].channel).c_str());
305                                 sub.push_back (
306                                         MenuElem (
307                                                 buf,
308                                                 bind (mem_fun (*this, &PortMatrix::remove_channel_proxy), w, bc[dim].channel)
309                                                 )
310                                         );
311                         }                       
312
313                         if (_show_only_bundles) {
314                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
315                         } else {
316                                 snprintf (
317                                         buf, sizeof (buf), _("%s all from '%s'"),
318                                         disassociation_verb().c_str(),
319                                         bc[dim].bundle->channel_name (bc[dim].channel).c_str()
320                                         );
321                         }
322                         
323                         sub.push_back (
324                                 MenuElem (buf, bind (mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
325                                 );
326
327                         items.push_back (MenuElem (bc[dim].bundle->name().c_str(), *m));
328                         need_separator = true;
329                 }
330
331         }
332
333         if (need_separator) {
334                 items.push_back (SeparatorElem ());
335         }
336
337         need_separator = false;
338         
339         for (int dim = 0; dim < 2; ++dim) {
340
341                 if (pg[dim]) {
342
343                         boost::weak_ptr<PortGroup> wp (pg[dim]);
344                         
345                         if (pg[dim]->visible()) {
346                                 if (dim == 0) {
347                                         if (pg[dim]->name.empty()) {
348                                                 snprintf (buf, sizeof (buf), _("Hide sources"));
349                                         } else {
350                                                 snprintf (buf, sizeof (buf), _("Hide '%s' sources"), pg[dim]->name.c_str());
351                                         }
352                                 } else {
353                                         if (pg[dim]->name.empty()) {
354                                                 snprintf (buf, sizeof (buf), _("Hide destinations"));
355                                         } else {
356                                                 snprintf (buf, sizeof (buf), _("Hide '%s' destinations"), pg[dim]->name.c_str());
357                                         }
358                                 }
359
360                                 items.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::hide_group), wp)));
361                         } else {
362                                 if (dim == 0) {
363                                         if (pg[dim]->name.empty()) {
364                                                 snprintf (buf, sizeof (buf), _("Show sources"));
365                                         } else {
366                                                 snprintf (buf, sizeof (buf), _("Show '%s' sources"), pg[dim]->name.c_str());
367                                         }
368                                 } else {
369                                         if (pg[dim]->name.empty()) {
370                                                 snprintf (buf, sizeof (buf), _("Show destinations"));
371                                         } else {
372                                                 snprintf (buf, sizeof (buf), _("Show '%s' destinations"), pg[dim]->name.c_str());
373                                         }
374                                 }
375                                 items.push_back (MenuElem (buf, bind (mem_fun (*this, &PortMatrix::show_group), wp)));
376                         }
377
378                         need_separator = true;
379                 }
380         }
381
382         if (need_separator) {
383                 items.push_back (SeparatorElem ());
384         }
385
386         items.push_back (MenuElem (_("Rescan"), mem_fun (*this, &PortMatrix::setup_all_ports)));
387         items.push_back (CheckMenuElem (_("Show individual ports"), mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
388         CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
389         _inhibit_toggle_show_only_bundles = true;
390         i->set_active (!_show_only_bundles);
391         _inhibit_toggle_show_only_bundles = false;
392         
393         _menu->popup (1, t);
394 }
395
396 void
397 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
398 {
399         boost::shared_ptr<Bundle> sb = b.lock ();
400         if (!sb) {
401                 return;
402         }
403
404         remove_channel (BundleChannel (sb, c));
405
406 }
407
408 void
409 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
410 {
411         boost::shared_ptr<Bundle> sb = b.lock ();
412         if (!sb) {
413                 return;
414         }
415
416         rename_channel (BundleChannel (sb, c));
417 }
418
419 void
420 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
421 {
422         boost::shared_ptr<Bundle> sb = bundle.lock ();
423         if (!sb) {
424                 return;
425         }
426
427         PortGroup::BundleList a = _ports[1-dim].bundles ();
428
429         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
430                 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
431
432                         BundleChannel c[2];
433                         c[dim] = BundleChannel (sb, channel);
434                         c[1-dim] = BundleChannel (i->bundle, j);
435
436                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
437                                 set_state (c, false);
438                         }
439                 }
440         }
441
442         _body->rebuild_and_draw_grid ();
443 }
444
445 void
446 PortMatrix::setup_global_ports ()
447 {
448         for (int i = 0; i < 2; ++i) {
449                 if (list_is_global (i)) {
450                         setup_ports (i);
451                 }
452         }
453 }
454
455 void
456 PortMatrix::setup_all_ports ()
457 {
458         ENSURE_GUI_THREAD (mem_fun (*this, &PortMatrix::setup_all_ports));
459         
460         setup_ports (0);
461         setup_ports (1);
462 }
463
464 void
465 PortMatrix::toggle_show_only_bundles ()
466 {
467         if (_inhibit_toggle_show_only_bundles) {
468                 return;
469         }
470         
471         _show_only_bundles = !_show_only_bundles;
472         _body->setup ();
473         setup_scrollbars ();
474         queue_draw ();
475 }
476
477 void
478 PortMatrix::hide_group (boost::weak_ptr<PortGroup> w)
479 {
480         boost::shared_ptr<PortGroup> g = w.lock ();
481         if (!g) {
482                 return;
483         }
484
485         g->set_visible (false);
486 }
487
488 void
489 PortMatrix::show_group (boost::weak_ptr<PortGroup> w)
490 {
491         boost::shared_ptr<PortGroup> g = w.lock ();
492         if (!g) {
493                 return;
494         }
495
496         g->set_visible (true);
497 }
498
499 pair<uint32_t, uint32_t>
500 PortMatrix::max_size () const
501 {
502         pair<uint32_t, uint32_t> m = _body->max_size ();
503
504         m.first += _vscroll.get_width ();
505         m.second += _hscroll.get_height ();
506
507         return m;
508 }
509
510 bool
511 PortMatrix::on_scroll_event (GdkEventScroll* ev)
512 {
513         double const h = _hscroll.get_value ();
514         double const v = _vscroll.get_value ();
515         
516         switch (ev->direction) {
517         case GDK_SCROLL_UP:
518                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
519                 break;
520         case GDK_SCROLL_DOWN:
521                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
522                 break;
523         case GDK_SCROLL_LEFT:
524                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
525                 break;
526         case GDK_SCROLL_RIGHT:
527                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
528                 break;
529         }
530
531         return true;
532 }
533
534 boost::shared_ptr<IO>
535 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
536 {
537         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
538         if (!io) {
539                 io = _ports[1].io_from_bundle (b);
540         }
541
542         return io;
543 }
544
545 bool
546 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
547 {
548         return io_from_bundle (b);
549 }
550
551 void
552 PortMatrix::add_channel (boost::shared_ptr<Bundle> b)
553 {
554         boost::shared_ptr<IO> io = io_from_bundle (b);
555
556         if (io) {
557                 io->add_port ("", this, _type);
558         }
559 }
560
561 bool
562 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
563 {
564         return io_from_bundle (b);
565 }
566
567 void
568 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
569 {
570         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
571
572         if (io) {
573                 Port* p = io->nth (b.channel);
574                 if (p) {
575                         io->remove_port (p, this);
576                 }
577         }
578 }
579
580 void
581 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w)
582 {
583         boost::shared_ptr<Bundle> b = w.lock ();
584         if (!b) {
585                 return;
586         }
587
588         add_channel (b);
589 }