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