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