50bade3a9e72e624c3d040328d4b7883cdf697a7
[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 #include "utils.h"
39
40 using namespace std;
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         , _parent (parent)
51         , _type (type)
52         , _menu (0)
53         , _arrangement (TOP_TO_RIGHT)
54         , _row_index (0)
55         , _column_index (1)
56         , _min_height_divisor (1)
57         , _show_only_bundles (false)
58         , _inhibit_toggle_show_only_bundles (false)
59         , _ignore_notebook_page_selected (false)
60 {
61         set_session (session);
62
63         _body = new PortMatrixBody (this);
64         _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
65
66         _hbox.pack_end (_hspacer, true, true);
67         _hbox.pack_end (_hnotebook, false, false);
68         _hbox.pack_end (_hlabel, false, false);
69
70         _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
71         _vnotebook.property_tab_border() = 4;
72         _vnotebook.set_name (X_("PortMatrixLabel"));
73         _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
74         _hnotebook.property_tab_border() = 4;
75         _hnotebook.set_name (X_("PortMatrixLabel"));
76
77         _vlabel.set_use_markup ();
78         _vlabel.set_alignment (1, 1);
79         _vlabel.set_padding (4, 16);
80         _vlabel.set_name (X_("PortMatrixLabel"));
81         _hlabel.set_use_markup ();
82         _hlabel.set_alignment (1, 0.5);
83         _hlabel.set_padding (16, 4);
84         _hlabel.set_name (X_("PortMatrixLabel"));
85
86         _body->show ();
87         _vbox.show ();
88         _hbox.show ();
89         _vscroll.show ();
90         _hscroll.show ();
91         _vlabel.show ();
92         _hlabel.show ();
93         _hspacer.show ();
94         _vspacer.show ();
95         _vnotebook.show ();
96         _hnotebook.show ();
97 }
98
99 PortMatrix::~PortMatrix ()
100 {
101         delete _body;
102         delete _menu;
103 }
104
105 /** Perform initial and once-only setup.  This must be called by
106  *  subclasses after they have set up _ports[] to at least some
107  *  reasonable extent.  Two-part initialisation is necessary because
108  *  setting up _ports is largely done by virtual functions in
109  *  subclasses.
110  */
111
112 void
113 PortMatrix::init ()
114 {
115         select_arrangement ();
116
117         /* Signal handling is kind of split into three parts:
118          *
119          * 1.  When _ports[] changes, we call setup().  This essentially sorts out our visual
120          *     representation of the information in _ports[].
121          *
122          * 2.  When certain other things change, we need to get our subclass to clear and
123          *     re-fill _ports[], which in turn causes appropriate signals to be raised to
124          *     hook into part (1).
125          *
126          * 3.  Assorted other signals.
127          */
128
129
130         /* Part 1: the basic _ports[] change -> reset visuals */
131
132         for (int i = 0; i < 2; ++i) {
133                 /* watch for the content of _ports[] changing */
134                 _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
135
136                 /* and for bundles in _ports[] changing */
137                 _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
138         }
139
140         /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
141         
142         /* watch for routes being added or removed */
143         _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
144
145         /* and also bundles */
146         _session->BundleAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
147
148         /* and also ports */
149         _session->engine().PortRegisteredOrUnregistered.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
150
151         /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
152         _session->RouteOrderKeyChanged.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports, this), gui_context());
153
154         /* Part 3: other stuff */
155         
156         _session->engine().PortConnectedOrDisconnected.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected, this), gui_context ());
157
158         _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
159         _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
160
161         reconnect_to_routes ();
162         
163         setup ();
164 }
165
166 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
167 void
168 PortMatrix::reconnect_to_routes ()
169 {
170         _route_connections.drop_connections ();
171
172         boost::shared_ptr<RouteList> routes = _session->get_routes ();
173         for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
174                 (*i)->processors_changed.connect (_route_connections, invalidator (*this), ui_bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
175         }
176 }
177
178 void
179 PortMatrix::route_processors_changed (RouteProcessorChange c)
180 {
181         if (c.type == RouteProcessorChange::MeterPointChange) {
182                 /* this change has no impact on the port matrix */
183                 return;
184         }
185
186         setup_global_ports ();
187 }
188
189 /** A route has been added to or removed from the session */
190 void
191 PortMatrix::routes_changed ()
192 {
193         reconnect_to_routes ();
194         setup_global_ports ();
195 }
196
197 /** Set up everything that depends on the content of _ports[] */
198 void
199 PortMatrix::setup ()
200 {
201         /* this needs to be done first, as the visible_ports() method uses the
202            notebook state to decide which ports are being shown */
203
204         setup_notebooks ();
205         
206         _body->setup ();
207         setup_scrollbars ();
208         queue_draw ();
209 }
210
211 void
212 PortMatrix::set_type (DataType t)
213 {
214         _type = t;
215 }
216
217 void
218 PortMatrix::hscroll_changed ()
219 {
220         _body->set_xoffset (_hscroll.get_adjustment()->get_value());
221 }
222
223 void
224 PortMatrix::vscroll_changed ()
225 {
226         _body->set_yoffset (_vscroll.get_adjustment()->get_value());
227 }
228
229 void
230 PortMatrix::setup_scrollbars ()
231 {
232         Adjustment* a = _hscroll.get_adjustment ();
233         a->set_lower (0);
234         a->set_upper (_body->full_scroll_width());
235         a->set_page_size (_body->alloc_scroll_width());
236         a->set_step_increment (32);
237         a->set_page_increment (128);
238
239         a = _vscroll.get_adjustment ();
240         a->set_lower (0);
241         a->set_upper (_body->full_scroll_height());
242         a->set_page_size (_body->alloc_scroll_height());
243         a->set_step_increment (32);
244         a->set_page_increment (128);
245 }
246
247 /** Disassociate all of our ports from each other */
248 void
249 PortMatrix::disassociate_all ()
250 {
251         PortGroup::BundleList a = _ports[0].bundles ();
252         PortGroup::BundleList b = _ports[1].bundles ();
253
254         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
255                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
256                         for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
257                                 for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
258
259                                         if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
260                                                 continue;
261                                         }
262
263                                         BundleChannel c[2] = {
264                                                 BundleChannel ((*i)->bundle, j),
265                                                 BundleChannel ((*k)->bundle, l)
266                                                         };
267
268                                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
269                                                 set_state (c, false);
270                                         }
271
272                                 }
273                         }
274                 }
275         }
276
277         _body->rebuild_and_draw_grid ();
278 }
279
280 /* Decide how to arrange the components of the matrix */
281 void
282 PortMatrix::select_arrangement ()
283 {
284         uint32_t const N[2] = {
285                 count_of_our_type (_ports[0].total_channels()),
286                 count_of_our_type (_ports[1].total_channels())
287         };
288
289         /* XXX: shirley there's an easier way than this */
290
291         if (_vspacer.get_parent()) {
292                 _vbox.remove (_vspacer);
293         }
294
295         if (_vnotebook.get_parent()) {
296                 _vbox.remove (_vnotebook);
297         }
298
299         if (_vlabel.get_parent()) {
300                 _vbox.remove (_vlabel);
301         }
302
303         /* The list with the most channels goes on left or right, so that the most channel
304            names are printed horizontally and hence more readable.  However we also
305            maintain notional `signal flow' vaguely from left to right.  Subclasses
306            should choose where to put ports based on signal flowing from _ports[0]
307            to _ports[1] */
308
309         if (N[0] > N[1]) {
310
311                 _row_index = 0;
312                 _column_index = 1;
313                 _arrangement = LEFT_TO_BOTTOM;
314                 _vlabel.set_label (_("<b>Sources</b>"));
315                 _hlabel.set_label (_("<b>Destinations</b>"));
316                 _vlabel.set_angle (90);
317
318                 _vbox.pack_end (_vlabel, false, false);
319                 _vbox.pack_end (_vnotebook, false, false);
320                 _vbox.pack_end (_vspacer, true, true);
321
322                 attach (*_body, 1, 2, 0, 1, FILL | EXPAND, FILL | EXPAND);
323                 attach (_vscroll, 2, 3, 0, 1, SHRINK);
324                 attach (_hscroll, 1, 2, 2, 3, FILL | EXPAND, SHRINK);
325                 attach (_vbox, 0, 1, 0, 1, SHRINK);
326                 attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
327
328                 set_col_spacing (0, 4);
329                 set_row_spacing (0, 4);
330                 
331         } else {
332
333                 _row_index = 1;
334                 _column_index = 0;
335                 _arrangement = TOP_TO_RIGHT;
336                 _hlabel.set_label (_("<b>Sources</b>"));
337                 _vlabel.set_label (_("<b>Destinations</b>"));
338                 _vlabel.set_angle (-90);
339
340                 _vbox.pack_end (_vspacer, true, true);
341                 _vbox.pack_end (_vnotebook, false, false);
342                 _vbox.pack_end (_vlabel, false, false);
343
344                 attach (*_body, 0, 1, 1, 2, FILL | EXPAND, FILL | EXPAND);
345                 attach (_vscroll, 2, 3, 1, 2, SHRINK);
346                 attach (_hscroll, 0, 1, 2, 3, FILL | EXPAND, SHRINK);
347                 attach (_vbox, 1, 2, 1, 2, SHRINK);
348                 attach (_hbox, 0, 1, 0, 1, FILL | EXPAND, SHRINK);
349
350                 set_col_spacing (1, 4);
351                 set_row_spacing (1, 4);
352         }
353 }
354
355 /** @return columns list */
356 PortGroupList const *
357 PortMatrix::columns () const
358 {
359         return &_ports[_column_index];
360 }
361
362 boost::shared_ptr<const PortGroup>
363 PortMatrix::visible_columns () const
364 {
365         return visible_ports (_column_index);
366 }
367
368 /* @return rows list */
369 PortGroupList const *
370 PortMatrix::rows () const
371 {
372         return &_ports[_row_index];
373 }
374
375 boost::shared_ptr<const PortGroup>
376 PortMatrix::visible_rows () const
377 {
378         return visible_ports (_row_index);
379 }
380
381 void
382 PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t)
383 {
384         using namespace Menu_Helpers;
385
386         delete _menu;
387
388         _menu = new Menu;
389         _menu->set_name ("ArdourContextMenu");
390
391         MenuList& items = _menu->items ();
392
393         BundleChannel bc[2];
394         bc[_column_index] = column;
395         bc[_row_index] = row;
396
397         char buf [64];
398         bool need_separator = false;
399
400         for (int dim = 0; dim < 2; ++dim) {
401
402                 if (bc[dim].bundle) {
403
404                         Menu* m = manage (new Menu);
405                         MenuList& sub = m->items ();
406
407                         boost::weak_ptr<Bundle> w (bc[dim].bundle);
408
409                         bool can_add_or_rename = false;
410
411                         for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
412                                 if (should_show (*i)) {
413                                         snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
414                                         sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
415                                         can_add_or_rename = true;
416                                 }
417                         }
418
419                         if (can_rename_channels (bc[dim].bundle)) {
420                                 snprintf (
421                                         buf, sizeof (buf), _("Rename '%s'..."),
422                                         escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
423                                         );
424                                 sub.push_back (
425                                         MenuElem (
426                                                 buf,
427                                                 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
428                                                 )
429                                         );
430                                 can_add_or_rename = true;
431                         }
432
433                         if (can_add_or_rename) {
434                                 sub.push_back (SeparatorElem ());
435                         }
436
437                         if (can_remove_channels (bc[dim].bundle)) {
438                                 if (bc[dim].channel != -1) {
439                                         add_remove_option (sub, w, bc[dim].channel);
440                                 } else {
441
442                                         snprintf (buf, sizeof (buf), _("Remove all"));
443                                         sub.push_back (
444                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
445                                                 );
446                                         
447                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
448                                                 if (should_show (bc[dim].bundle->channel_type(i))) {
449                                                         add_remove_option (sub, w, i);
450                                                 }
451                                         }
452                                 }
453                         }
454
455                         if (_show_only_bundles || count_of_our_type (bc[dim].bundle->nchannels()) <= 1) {
456                                 snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
457                                 sub.push_back (
458                                         MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, bc[dim].channel, dim))
459                                         );
460                                 
461                         } else {
462                                 
463                                 if (bc[dim].channel != -1) {
464                                         add_disassociate_option (sub, w, dim, bc[dim].channel);
465                                 } else {
466                                         snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
467                                         sub.push_back (
468                                                 MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
469                                                 );
470                                                         
471                                         for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
472                                                 if (should_show (bc[dim].bundle->channel_type(i))) {
473                                                         add_disassociate_option (sub, w, dim, i);
474                                                 }
475                                         }
476                                 }
477                         }
478
479                         items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
480                         need_separator = true;
481                 }
482
483         }
484
485         if (need_separator) {
486                 items.push_back (SeparatorElem ());
487         }
488
489         items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
490         items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
491         CheckMenuItem* i = dynamic_cast<CheckMenuItem*> (&items.back());
492         _inhibit_toggle_show_only_bundles = true;
493         i->set_active (!_show_only_bundles);
494         _inhibit_toggle_show_only_bundles = false;
495
496         _menu->popup (1, t);
497 }
498
499 void
500 PortMatrix::remove_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
501 {
502         boost::shared_ptr<Bundle> sb = b.lock ();
503         if (!sb) {
504                 return;
505         }
506
507         remove_channel (BundleChannel (sb, c));
508
509 }
510
511 void
512 PortMatrix::rename_channel_proxy (boost::weak_ptr<Bundle> b, uint32_t c)
513 {
514         boost::shared_ptr<Bundle> sb = b.lock ();
515         if (!sb) {
516                 return;
517         }
518
519         rename_channel (BundleChannel (sb, c));
520 }
521
522 void
523 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr<Bundle> bundle, int dim)
524 {
525         boost::shared_ptr<Bundle> sb = bundle.lock ();
526         if (!sb) {
527                 return;
528         }
529
530         for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
531                 if (should_show (sb->channel_type(i))) {
532                         disassociate_all_on_channel (bundle, i, dim);
533                 }
534         }
535 }
536
537 void
538 PortMatrix::disassociate_all_on_channel (boost::weak_ptr<Bundle> bundle, uint32_t channel, int dim)
539 {
540         boost::shared_ptr<Bundle> sb = bundle.lock ();
541         if (!sb) {
542                 return;
543         }
544
545         PortGroup::BundleList a = _ports[1-dim].bundles ();
546
547         for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
548                 for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
549
550                         if (should_show ((*i)->bundle->channel_type(j))) {
551                                 continue;
552                         }
553
554                         BundleChannel c[2];
555                         c[dim] = BundleChannel (sb, channel);
556                         c[1-dim] = BundleChannel ((*i)->bundle, j);
557
558                         if (get_state (c) == PortMatrixNode::ASSOCIATED) {
559                                 set_state (c, false);
560                         }
561                 }
562         }
563
564         _body->rebuild_and_draw_grid ();
565 }
566
567 void
568 PortMatrix::setup_global_ports ()
569 {
570         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports)
571
572         for (int i = 0; i < 2; ++i) {
573                 if (list_is_global (i)) {
574                         setup_ports (i);
575                 }
576         }
577 }
578
579 void
580 PortMatrix::setup_all_ports ()
581 {
582         if (_session->deletion_in_progress()) {
583                 return;
584         }
585
586         ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports)
587
588         setup_ports (0);
589         setup_ports (1);
590 }
591
592 void
593 PortMatrix::toggle_show_only_bundles ()
594 {
595         if (_inhibit_toggle_show_only_bundles) {
596                 return;
597         }
598
599         _show_only_bundles = !_show_only_bundles;
600         
601         setup ();
602 }
603
604 pair<uint32_t, uint32_t>
605 PortMatrix::max_size () const
606 {
607         pair<uint32_t, uint32_t> m = _body->max_size ();
608
609         m.first += _vscroll.get_width () + _vbox.get_width () + 4;
610         m.second += _hscroll.get_height () + _hbox.get_height () + 4;
611
612         return m;
613 }
614
615 bool
616 PortMatrix::on_scroll_event (GdkEventScroll* ev)
617 {
618         double const h = _hscroll.get_value ();
619         double const v = _vscroll.get_value ();
620
621         switch (ev->direction) {
622         case GDK_SCROLL_UP:
623                 _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
624                 break;
625         case GDK_SCROLL_DOWN:
626                 _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
627                 break;
628         case GDK_SCROLL_LEFT:
629                 _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
630                 break;
631         case GDK_SCROLL_RIGHT:
632                 _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
633                 break;
634         }
635
636         return true;
637 }
638
639 boost::shared_ptr<IO>
640 PortMatrix::io_from_bundle (boost::shared_ptr<Bundle> b) const
641 {
642         boost::shared_ptr<IO> io = _ports[0].io_from_bundle (b);
643         if (!io) {
644                 io = _ports[1].io_from_bundle (b);
645         }
646
647         return io;
648 }
649
650 bool
651 PortMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
652 {
653         return io_from_bundle (b);
654 }
655
656 void
657 PortMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
658 {
659         boost::shared_ptr<IO> io = io_from_bundle (b);
660
661         if (io) {
662                 io->add_port ("", this, t);
663         }
664 }
665
666 bool
667 PortMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
668 {
669         return io_from_bundle (b);
670 }
671
672 void
673 PortMatrix::remove_channel (ARDOUR::BundleChannel b)
674 {
675         boost::shared_ptr<IO> io = io_from_bundle (b.bundle);
676
677         if (io) {
678                 Port* p = io->nth (b.channel);
679                 if (p) {
680                         io->remove_port (p, this);
681                 }
682         }
683 }
684
685 void
686 PortMatrix::remove_all_channels (boost::weak_ptr<Bundle> w)
687 {
688         boost::shared_ptr<Bundle> b = w.lock ();
689         if (!b) {
690                 return;
691         }
692
693         for (uint32_t i = 0; i < b->nchannels().n_total(); ++i) {
694                 if (should_show (b->channel_type(i))) {
695                         remove_channel (ARDOUR::BundleChannel (b, i));
696                 }
697         }
698 }
699
700 void
701 PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
702 {
703         boost::shared_ptr<Bundle> b = w.lock ();
704         if (!b) {
705                 return;
706         }
707
708         add_channel (b, t);
709 }
710
711 void
712 PortMatrix::setup_notebooks ()
713 {
714         int const h_current_page = _hnotebook.get_current_page ();
715         int const v_current_page = _vnotebook.get_current_page ();
716         
717         /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
718            when adding or removing pages to or from notebooks, so ignore them */
719         
720         _ignore_notebook_page_selected = true;
721         
722         remove_notebook_pages (_hnotebook);
723         remove_notebook_pages (_vnotebook);
724
725         for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
726                 HBox* dummy = manage (new HBox);
727                 dummy->show ();
728                 Label* label = manage (new Label ((*i)->name));
729                 label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
730                 label->show ();
731                 _vnotebook.prepend_page (*dummy, *label);
732         }
733
734         for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
735                 HBox* dummy = manage (new HBox);
736                 dummy->show ();
737                 _hnotebook.append_page (*dummy, (*i)->name);
738         }
739
740         _ignore_notebook_page_selected = false;
741
742         _vnotebook.set_tab_pos (POS_LEFT);
743         _hnotebook.set_tab_pos (POS_TOP);
744
745         if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
746                 _hnotebook.set_current_page (h_current_page);
747         } else {
748                 _hnotebook.set_current_page (0);
749         }
750
751         if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
752                 _vnotebook.set_current_page (v_current_page);
753         } else {
754                 _vnotebook.set_current_page (0);
755         }
756
757         if (_hnotebook.get_n_pages() <= 1) {
758                 _hbox.hide ();
759         } else {
760                 _hbox.show ();
761         }
762
763         if (_vnotebook.get_n_pages() <= 1) {
764                 _vbox.hide ();
765         } else {
766                 _vbox.show ();
767         }
768 }
769
770 void
771 PortMatrix::remove_notebook_pages (Notebook& n)
772 {
773         int const N = n.get_n_pages ();
774         
775         for (int i = 0; i < N; ++i) {
776                 n.remove_page ();
777         }
778 }
779
780 void
781 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
782 {
783         if (_ignore_notebook_page_selected) {
784                 return;
785         }
786
787         _body->setup ();
788         setup_scrollbars ();
789         queue_draw ();
790 }
791
792 void
793 PortMatrix::session_going_away ()
794 {
795         _session = 0;
796 }
797
798 void
799 PortMatrix::body_dimensions_changed ()
800 {
801         _hspacer.set_size_request (_body->column_labels_border_x (), -1);
802         if (_arrangement == TOP_TO_RIGHT) {
803                 _vspacer.set_size_request (-1, _body->column_labels_height ());
804                 _vspacer.show ();
805         } else {
806                 _vspacer.hide ();
807         }
808
809         int curr_width;
810         int curr_height;
811         _parent->get_size (curr_width, curr_height);
812
813         pair<uint32_t, uint32_t> m = max_size ();
814
815         /* Don't shrink the window */
816         m.first = max (int (m.first), curr_width);
817         m.second = max (int (m.second), curr_height);
818         
819         resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
820 }
821
822
823 boost::shared_ptr<const PortGroup>
824 PortMatrix::visible_ports (int d) const
825 {
826         PortGroupList const & p = _ports[d];
827         PortGroupList::List::const_iterator j = p.begin ();
828
829         int n = 0;
830         if (d == _row_index) {
831                 n = p.size() - _vnotebook.get_current_page () - 1;
832         } else {
833                 n = _hnotebook.get_current_page ();
834         }
835
836         int i = 0;
837         while (i != int (n) && j != p.end ()) {
838                 ++i;
839                 ++j;
840         }
841                 
842         if (j == p.end()) {
843                 return boost::shared_ptr<const PortGroup> ();
844         }
845
846         return *j;
847 }
848
849 void
850 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
851 {
852         using namespace Menu_Helpers;
853
854         boost::shared_ptr<Bundle> b = w.lock ();
855         if (!b) {
856                 return;
857         }
858         
859         char buf [64];
860         snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
861         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
862 }
863
864 void
865 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
866 {
867         using namespace Menu_Helpers;
868
869         boost::shared_ptr<Bundle> b = w.lock ();
870         if (!b) {
871                 return;
872         }
873         
874         char buf [64];
875         snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
876         m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
877 }
878
879 void
880 PortMatrix::port_connected_or_disconnected ()
881 {
882         _body->rebuild_and_draw_grid ();
883 }
884
885 string
886 PortMatrix::channel_noun () const
887 {
888         return _("channel");
889 }
890
891 /** @return true if this matrix should show bundles / ports of type \t */
892 bool
893 PortMatrix::should_show (DataType t) const
894 {
895         return (_type == DataType::NIL || t == _type);
896 }
897         
898 uint32_t
899 PortMatrix::count_of_our_type (ChanCount c) const
900 {
901         if (_type == DataType::NIL) {
902                 return c.n_total ();
903         }
904
905         return c.get (_type);
906 }