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