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