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