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