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