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