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