ardour
port_matrix.cc
Go to the documentation of this file.
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 <gtkmm/stock.h>
29 #include <gtkmm/messagedialog.h>
30 #include "ardour/bundle.h"
31 #include "ardour/types.h"
32 #include "ardour/session.h"
33 #include "ardour/route.h"
34 #include "ardour/audioengine.h"
35 #include "port_matrix.h"
36 #include "port_matrix_body.h"
37 #include "port_matrix_component.h"
38 #include "ardour_dialog.h"
39 #include "i18n.h"
40 #include "gui_thread.h"
41 #include "utils.h"
42 
43 using namespace std;
44 using namespace Gtk;
45 using namespace ARDOUR;
46 using namespace ARDOUR_UI_UTILS;
47 
52 PortMatrix::PortMatrix (Window* parent, Session* session, DataType type)
53  : Table (4, 4)
54  , _parent (parent)
55  , _type (type)
56  , _menu (0)
57  , _arrangement (TOP_TO_RIGHT)
58  , _row_index (0)
59  , _column_index (1)
60  , _min_height_divisor (1)
61  , _show_only_bundles (false)
62  , _inhibit_toggle_show_only_bundles (false)
63  , _ignore_notebook_page_selected (false)
64 {
65  set_session (session);
66 
67  _body = new PortMatrixBody (this);
68  _body->DimensionsChanged.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed));
69 
70  _hbox.pack_end (_hspacer, true, true);
71  _hbox.pack_end (_hnotebook, false, false);
72  _hbox.pack_end (_hlabel, false, false);
73 
74  _vnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
75  _vnotebook.property_tab_border() = 4;
76  _vnotebook.set_name (X_("PortMatrixLabel"));
77  _hnotebook.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected));
78  _hnotebook.property_tab_border() = 4;
79  _hnotebook.set_name (X_("PortMatrixLabel"));
80 
81  _vlabel.set_use_markup ();
82  _vlabel.set_alignment (1, 1);
83  _vlabel.set_padding (4, 16);
84  _vlabel.set_name (X_("PortMatrixLabel"));
85  _hlabel.set_use_markup ();
86  _hlabel.set_alignment (1, 0.5);
87  _hlabel.set_padding (16, 4);
88  _hlabel.set_name (X_("PortMatrixLabel"));
89 
90  set_row_spacing (0, 8);
91  set_col_spacing (0, 8);
92  set_row_spacing (2, 8);
93  set_col_spacing (2, 8);
94 
95  _body->show ();
96  _vbox.show ();
97  _hbox.show ();
98  _vscroll.show ();
99  _hscroll.show ();
100  _vlabel.show ();
101  _hlabel.show ();
102  _hspacer.show ();
103  _vspacer.show ();
104  _vnotebook.show ();
105  _hnotebook.show ();
106 }
107 
109 {
110  delete _body;
111  delete _menu;
112 }
113 
121 void
123 {
125 
126  /* Signal handling is kind of split into three 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  * 3. Assorted other signals.
136  */
137 
138 
139  /* Part 1: the basic _ports[] change -> reset visuals */
140 
141  for (int i = 0; i < 2; ++i) {
142  /* watch for the content of _ports[] changing */
143  _ports[i].Changed.connect (_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
144 
145  /* and for bundles in _ports[] changing */
146  _ports[i].BundleChanged.connect (_bundle_changed_connections, invalidator (*this), boost::bind (&PortMatrix::setup, this), gui_context());
147  }
148 
149  /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
150 
151  /* watch for routes being added or removed */
152  _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
153 
154  /* and also bundles */
156 
157  /* and also ports */
159 
160  /* watch for route order keys changing, which changes the order of things in our global ports list(s) */
161  Route::SyncOrderKeys.connect (_session_connections, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports_proxy, this), gui_context());
162 
163  /* Part 3: other stuff */
164 
166 
167  _hscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed));
168  _vscroll.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed));
169 
171 
172  setup ();
173 }
174 
176 void
178 {
180 
182  for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
183  (*i)->processors_changed.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::route_processors_changed, this, _1), gui_context());
184  (*i)->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&PortMatrix::routes_changed, this), gui_context());
185  }
186 }
187 
188 void
190 {
191  if (c.type == RouteProcessorChange::MeterPointChange) {
192  /* this change has no impact on the port matrix */
193  return;
194  }
195 
197 }
198 
200 void
202 {
203  if (!_session) return;
206 }
207 
209 void
211 {
212  if (!_session) {
214  return; // session went away
215  }
216 
217  /* this needs to be done first, as the visible_ports() method uses the
218  notebook state to decide which ports are being shown */
219 
220  setup_notebooks ();
221 
222  _body->setup ();
223  setup_scrollbars ();
225  queue_draw ();
226 }
227 
228 void
230 {
231  _type = t;
232 }
233 
234 void
236 {
237  _body->set_xoffset (_hscroll.get_adjustment()->get_value());
238 }
239 
240 void
242 {
243  _body->set_yoffset (_vscroll.get_adjustment()->get_value());
244 }
245 
246 void
248 {
249  Adjustment* a = _hscroll.get_adjustment ();
250  a->set_lower (0);
251  a->set_page_size (_body->alloc_scroll_width());
252  a->set_step_increment (32);
253  a->set_page_increment (128);
254 
255  /* Set the adjustment to zero if the size has changed.*/
256  if (a->get_upper() != _body->full_scroll_width()) {
257  a->set_upper (_body->full_scroll_width());
258  a->set_value (0);
259  }
260 
261  a = _vscroll.get_adjustment ();
262  a->set_lower (0);
263  a->set_page_size (_body->alloc_scroll_height());
264  a->set_step_increment (32);
265  a->set_page_increment (128);
266 
267  if (a->get_upper() != _body->full_scroll_height()) {
268  a->set_upper (_body->full_scroll_height());
269  a->set_value (0);
270  }
271 }
272 
274 void
276 {
279 
280  for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
281  for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
282  for (PortGroup::BundleList::iterator k = b.begin(); k != b.end(); ++k) {
283  for (uint32_t l = 0; l < (*k)->bundle->nchannels().n_total(); ++l) {
284 
285  if (!should_show ((*i)->bundle->channel_type(j)) || !should_show ((*k)->bundle->channel_type(l))) {
286  continue;
287  }
288 
289  BundleChannel c[2] = {
290  BundleChannel ((*i)->bundle, j),
291  BundleChannel ((*k)->bundle, l)
292  };
293 
295  set_state (c, false);
296  }
297 
298  }
299  }
300  }
301  }
302 
304 }
305 
306 /* Decide how to arrange the components of the matrix */
307 void
309 {
310  uint32_t const N[2] = {
311  count_of_our_type_min_1 (_ports[0].total_channels()),
312  count_of_our_type_min_1 (_ports[1].total_channels())
313  };
314 
315  /* XXX: shirley there's an easier way than this */
316 
317  if (_vspacer.get_parent()) {
318  _vbox.remove (_vspacer);
319  }
320 
321  if (_vnotebook.get_parent()) {
322  _vbox.remove (_vnotebook);
323  }
324 
325  if (_vlabel.get_parent()) {
326  _vbox.remove (_vlabel);
327  }
328 
329  /* The list with the most channels goes on left or right, so that the most channel
330  names are printed horizontally and hence more readable. However we also
331  maintain notional `signal flow' vaguely from left to right. Subclasses
332  should choose where to put ports based on signal flowing from _ports[0]
333  to _ports[1] */
334 
335  if (N[0] > N[1]) {
336 
337  _row_index = 0;
338  _column_index = 1;
340  _vlabel.set_label (_("<b>Sources</b>"));
341  _hlabel.set_label (_("<b>Destinations</b>"));
342  _vlabel.set_angle (90);
343 
344  _vbox.pack_end (_vlabel, false, false);
345  _vbox.pack_end (_vnotebook, false, false);
346  _vbox.pack_end (_vspacer, true, true);
347 
348 #define REMOVE_FROM_GTK_PARENT(WGT) if ((WGT).get_parent()) { (WGT).get_parent()->remove(WGT);}
354 
355  attach (*_body, 2, 3, 1, 2, FILL | EXPAND, FILL | EXPAND);
356  attach (_vscroll, 3, 4, 1, 2, SHRINK);
357  attach (_hscroll, 2, 3, 3, 4, FILL | EXPAND, SHRINK);
358  attach (_vbox, 1, 2, 1, 2, SHRINK);
359  attach (_hbox, 2, 3, 2, 3, FILL | EXPAND, SHRINK);
360 
361  } else {
362 
363  _row_index = 1;
364  _column_index = 0;
366  _hlabel.set_label (_("<b>Sources</b>"));
367  _vlabel.set_label (_("<b>Destinations</b>"));
368  _vlabel.set_angle (-90);
369 
370  _vbox.pack_end (_vspacer, true, true);
371  _vbox.pack_end (_vnotebook, false, false);
372  _vbox.pack_end (_vlabel, false, false);
373 
379 
380  attach (*_body, 1, 2, 2, 3, FILL | EXPAND, FILL | EXPAND);
381  attach (_vscroll, 3, 4, 2, 3, SHRINK);
382  attach (_hscroll, 1, 2, 3, 4, FILL | EXPAND, SHRINK);
383  attach (_vbox, 2, 3, 2, 3, SHRINK);
384  attach (_hbox, 1, 2, 1, 2, FILL | EXPAND, SHRINK);
385  }
386 }
387 
389 PortGroupList const *
391 {
392  return &_ports[_column_index];
393 }
394 
397 {
398  return visible_ports (_column_index);
399 }
400 
401 /* @return rows list */
402 PortGroupList const *
404 {
405  return &_ports[_row_index];
406 }
407 
410 {
411  return visible_ports (_row_index);
412 }
413 
417 void
419 {
420  using namespace Menu_Helpers;
421 
422  delete _menu;
423 
424  _menu = new Menu;
425  _menu->set_name ("ArdourContextMenu");
426 
427  MenuList& items = _menu->items ();
428 
429  BundleChannel bc[2];
430  bc[_column_index] = column;
431  bc[_row_index] = row;
432 
433  char buf [64];
434  bool need_separator = false;
435 
436  for (int dim = 0; dim < 2; ++dim) {
437 
438  if (bc[dim].bundle) {
439 
440  Menu* m = manage (new Menu);
441  MenuList& sub = m->items ();
442 
443  boost::weak_ptr<Bundle> w (bc[dim].bundle);
444 
445  if (can_add_channels (bc[dim].bundle)) {
446  /* Start off with options for the `natural' port type */
447  for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
448  if (should_show (*i)) {
449  snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
450  sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
451  }
452  }
453 
454  /* Now add other ones */
455  for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
456  if (!should_show (*i)) {
457  snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str());
458  sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i)));
459  }
460  }
461  }
462 
463  if (can_rename_channels (bc[dim].bundle) && bc[dim].channel != -1) {
464  snprintf (
465  buf, sizeof (buf), _("Rename '%s'..."),
466  escape_underscores (bc[dim].bundle->channel_name (bc[dim].channel)).c_str()
467  );
468  sub.push_back (
469  MenuElem (
470  buf,
471  sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy), w, bc[dim].channel)
472  )
473  );
474  }
475 
476  if (can_remove_channels (bc[dim].bundle) && bc[dim].bundle->nchannels() != ARDOUR::ChanCount::ZERO) {
477  if (bc[dim].channel != -1) {
478  add_remove_option (sub, w, bc[dim].channel);
479  } else {
480  sub.push_back (
481  MenuElem (_("Remove all"), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels), w))
482  );
483 
484  if (bc[dim].bundle->nchannels().n_total() > 1) {
485  for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
486  if (should_show (bc[dim].bundle->channel_type(i))) {
487  add_remove_option (sub, w, i);
488  }
489  }
490  }
491  }
492  }
493 
494  uint32_t c = count_of_our_type (bc[dim].bundle->nchannels ());
495  if ((_show_only_bundles && c > 0) || c == 1) {
496 
497  /* we're looking just at bundles, or our bundle has only one channel, so just offer
498  to disassociate all on the bundle.
499  */
500 
501  snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
502  sub.push_back (
503  MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
504  );
505 
506  } else if (c != 0) {
507 
508  if (bc[dim].channel != -1) {
509  /* specific channel under the menu, so just offer to disassociate that */
510  add_disassociate_option (sub, w, dim, bc[dim].channel);
511  } else {
512  /* no specific channel; offer to disassociate all, or any one in particular */
513  snprintf (buf, sizeof (buf), _("%s all"), disassociation_verb().c_str());
514  sub.push_back (
515  MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle), w, dim))
516  );
517 
518  for (uint32_t i = 0; i < bc[dim].bundle->nchannels().n_total(); ++i) {
519  if (should_show (bc[dim].bundle->channel_type(i))) {
520  add_disassociate_option (sub, w, dim, i);
521  }
522  }
523  }
524  }
525 
526  items.push_back (MenuElem (escape_underscores (bc[dim].bundle->name()).c_str(), *m));
527  need_separator = true;
528  }
529 
530  }
531 
532  if (need_separator) {
533  items.push_back (SeparatorElem ());
534  }
535 
536  items.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports)));
537 
538  items.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles)));
539  Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
541  i->set_active (!_show_only_bundles);
543 
544  items.push_back (MenuElem (_("Flip"), sigc::mem_fun (*this, &PortMatrix::flip)));
545  items.back().set_sensitive (can_flip ());
546 
547  _menu->popup (1, t);
548 }
549 
550 void
552 {
553  boost::shared_ptr<Bundle> sb = b.lock ();
554  if (!sb) {
555  return;
556  }
557 
558  remove_channel (BundleChannel (sb, c));
559 
560 }
561 
562 void
564 {
565  boost::shared_ptr<Bundle> sb = b.lock ();
566  if (!sb) {
567  return;
568  }
569 
570  rename_channel (BundleChannel (sb, c));
571 }
572 
573 void
575 {
576  boost::shared_ptr<Bundle> sb = bundle.lock ();
577  if (!sb) {
578  return;
579  }
580 
581  for (uint32_t i = 0; i < sb->nchannels().n_total(); ++i) {
582  if (should_show (sb->channel_type(i))) {
583  disassociate_all_on_channel (bundle, i, dim);
584  }
585  }
586 }
587 
588 void
590 {
591  boost::shared_ptr<Bundle> sb = bundle.lock ();
592  if (!sb) {
593  return;
594  }
595 
596  PortGroup::BundleList a = _ports[1-dim].bundles ();
597 
598  for (PortGroup::BundleList::iterator i = a.begin(); i != a.end(); ++i) {
599  for (uint32_t j = 0; j < (*i)->bundle->nchannels().n_total(); ++j) {
600 
601  if (!should_show ((*i)->bundle->channel_type(j))) {
602  continue;
603  }
604 
605  BundleChannel c[2];
606  c[dim] = BundleChannel (sb, channel);
607  c[1-dim] = BundleChannel ((*i)->bundle, j);
608 
610  set_state (c, false);
611  }
612  }
613  }
614 
616 }
617 
618 void
620 {
621  if (!_session || _session->deletion_in_progress()) return;
623 
624  for (int i = 0; i < 2; ++i) {
625  if (list_is_global (i)) {
626  setup_ports (i);
627  }
628  }
629 }
630 
631 void
633 {
634  /* Avoid a deadlock by calling this in an idle handler: see IOSelector::io_changed_proxy
635  for a discussion.
636  */
637 
638  Glib::signal_idle().connect_once (sigc::mem_fun (*this, &PortMatrix::setup_global_ports));
639 }
640 
641 void
643 {
645  return;
646  }
647 
649 
650  setup_ports (0);
651  setup_ports (1);
652 }
653 
654 void
656 {
658  return;
659  }
660 
662 
663  setup ();
664 
665  /* The way in which hardware ports are grouped changes depending on the _show_only_bundles
666  setting, so we need to set things up again now.
667  */
668  setup_all_ports ();
669 }
670 
671 pair<uint32_t, uint32_t>
673 {
674  pair<uint32_t, uint32_t> m = _body->max_size ();
675 
676  m.first += _vscroll.get_width () + _vbox.get_width () + 4;
677  m.second += _hscroll.get_height () + _hbox.get_height () + 4;
678 
679  return m;
680 }
681 
682 bool
683 PortMatrix::on_scroll_event (GdkEventScroll* ev)
684 {
685  double const h = _hscroll.get_value ();
686  double const v = _vscroll.get_value ();
687 
688  switch (ev->direction) {
689  case GDK_SCROLL_UP:
690  _vscroll.set_value (v - PortMatrixComponent::grid_spacing ());
691  break;
692  case GDK_SCROLL_DOWN:
693  _vscroll.set_value (v + PortMatrixComponent::grid_spacing ());
694  break;
695  case GDK_SCROLL_LEFT:
696  _hscroll.set_value (h - PortMatrixComponent::grid_spacing ());
697  break;
698  case GDK_SCROLL_RIGHT:
699  _hscroll.set_value (h + PortMatrixComponent::grid_spacing ());
700  break;
701  }
702 
703  return true;
704 }
705 
708 {
710  if (!io) {
711  io = _ports[1].io_from_bundle (b);
712  }
713 
714  return io;
715 }
716 
717 bool
719 {
720  return io_from_bundle (b) != 0;
721 }
722 
723 void
725 {
727 
728  if (io) {
729  int const r = io->add_port ("", this, t);
730  if (r == -1) {
731  Gtk::MessageDialog msg (_("It is not possible to add a port here, as the first processor in the track or buss cannot "
732  "support the new configuration."
733  ));
734  msg.set_title (_("Cannot add port"));
735  msg.run ();
736  }
737  }
738 }
739 
740 bool
742 {
743  return io_from_bundle (b) != 0;
744 }
745 
746 void
748 {
750 
751  if (io) {
752  boost::shared_ptr<Port> p = io->nth (b.channel);
753  if (p) {
754  int const r = io->remove_port (p, this);
755  if (r == -1) {
756  ArdourDialog d (_("Port removal not allowed"));
757  Label l (_("This port cannot be removed.\nEither the first plugin in the track or buss cannot accept\nthe new number of inputs or the last plugin has more outputs."));
758  d.get_vbox()->pack_start (l);
759  d.add_button (Stock::OK, RESPONSE_ACCEPT);
760  d.set_modal (true);
761  d.show_all ();
762  d.run ();
763  }
764  }
765  }
766 }
767 
768 void
770 {
771  boost::shared_ptr<Bundle> b = w.lock ();
772  if (!b) {
773  return;
774  }
775 
776  /* Remove channels backwards so that we don't renumber channels
777  that we are about to remove.
778  */
779  for (int i = (b->nchannels().n_total() - 1); i >= 0; --i) {
780  if (should_show (b->channel_type(i))) {
782  }
783  }
784 }
785 
786 void
788 {
789  boost::shared_ptr<Bundle> b = w.lock ();
790  if (!b) {
791  return;
792  }
793 
794  add_channel (b, t);
795 }
796 
797 void
799 {
800  int const h_current_page = _hnotebook.get_current_page ();
801  int const v_current_page = _vnotebook.get_current_page ();
802 
803  /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
804  when adding or removing pages to or from notebooks, so ignore them */
805 
807 
810 
811  for (PortGroupList::List::const_iterator i = _ports[_row_index].begin(); i != _ports[_row_index].end(); ++i) {
812  HBox* dummy = manage (new HBox);
813  dummy->show ();
814  Label* label = manage (new Label ((*i)->name));
815  label->set_angle (_arrangement == LEFT_TO_BOTTOM ? 90 : -90);
816  label->set_use_markup ();
817  label->show ();
818  if (_arrangement == LEFT_TO_BOTTOM) {
819  _vnotebook.prepend_page (*dummy, *label);
820  } else {
821  /* Reverse the order of vertical tabs when they are on the right hand side
822  so that from top to bottom it is the same order as that from left to right
823  for the top tabs.
824  */
825  _vnotebook.append_page (*dummy, *label);
826  }
827  }
828 
829  for (PortGroupList::List::const_iterator i = _ports[_column_index].begin(); i != _ports[_column_index].end(); ++i) {
830  HBox* dummy = manage (new HBox);
831  dummy->show ();
832  Label* label = manage (new Label ((*i)->name));
833  label->set_use_markup ();
834  label->show ();
835  _hnotebook.append_page (*dummy, *label);
836  }
837 
839 
840  if (_arrangement == TOP_TO_RIGHT) {
841  _vnotebook.set_tab_pos (POS_RIGHT);
842  _hnotebook.set_tab_pos (POS_TOP);
843  } else {
844  _vnotebook.set_tab_pos (POS_LEFT);
845  _hnotebook.set_tab_pos (POS_BOTTOM);
846  }
847 
848  if (h_current_page != -1 && _hnotebook.get_n_pages() > h_current_page) {
849  _hnotebook.set_current_page (h_current_page);
850  } else {
851  _hnotebook.set_current_page (0);
852  }
853 
854  if (v_current_page != -1 && _vnotebook.get_n_pages() > v_current_page) {
855  _vnotebook.set_current_page (v_current_page);
856  } else {
857  _vnotebook.set_current_page (0);
858  }
859 
860  if (_hnotebook.get_n_pages() <= 1) {
861  _hbox.hide ();
862  } else {
863  _hbox.show ();
864  }
865 
866  if (_vnotebook.get_n_pages() <= 1) {
867  _vbox.hide ();
868  } else {
869  _vbox.show ();
870  }
871 }
872 
873 void
875 {
876  int const N = n.get_n_pages ();
877 
878  for (int i = 0; i < N; ++i) {
879  n.remove_page ();
880  }
881 }
882 
883 void
884 PortMatrix::notebook_page_selected (GtkNotebookPage *, guint)
885 {
887  return;
888  }
889 
890  _body->setup ();
891  setup_scrollbars ();
892  queue_draw ();
893 }
894 
895 void
897 {
898  _session = 0;
899 }
900 
901 void
903 {
904  _hspacer.set_size_request (_body->column_labels_border_x (), -1);
905  if (_arrangement == TOP_TO_RIGHT) {
906  _vspacer.set_size_request (-1, _body->column_labels_height ());
907  _vspacer.show ();
908  } else {
909  _vspacer.hide ();
910  }
911 
912  int curr_width;
913  int curr_height;
914  _parent->get_size (curr_width, curr_height);
915 
916  pair<uint32_t, uint32_t> m = max_size ();
917 
918  /* Don't shrink the window */
919  m.first = max (int (m.first), curr_width);
920  m.second = max (int (m.second), curr_height);
921 
922  resize_window_to_proportion_of_monitor (_parent, m.first, m.second);
923 }
924 
930 {
931  PortGroupList const & p = _ports[d];
932  PortGroupList::List::const_iterator j = p.begin ();
933 
934  /* The logic to compute the index here is a bit twisty because for
935  the TOP_TO_RIGHT arrangement we reverse the order of the vertical
936  tabs in setup_notebooks ().
937  */
938 
939  int n = 0;
940  if (d == _row_index) {
941  if (_arrangement == LEFT_TO_BOTTOM) {
942  n = p.size() - _vnotebook.get_current_page () - 1;
943  } else {
944  n = _vnotebook.get_current_page ();
945  }
946  } else {
947  n = _hnotebook.get_current_page ();
948  }
949 
950  int i = 0;
951  while (i != int (n) && j != p.end ()) {
952  ++i;
953  ++j;
954  }
955 
956  if (j == p.end()) {
958  }
959 
960  return *j;
961 }
962 
963 void
964 PortMatrix::add_remove_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int c)
965 {
966  using namespace Menu_Helpers;
967 
968  boost::shared_ptr<Bundle> b = w.lock ();
969  if (!b) {
970  return;
971  }
972 
973  char buf [64];
974  snprintf (buf, sizeof (buf), _("Remove '%s'"), escape_underscores (b->channel_name (c)).c_str());
975  m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy), w, c)));
976 }
977 
978 void
979 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList& m, boost::weak_ptr<Bundle> w, int d, int c)
980 {
981  using namespace Menu_Helpers;
982 
983  boost::shared_ptr<Bundle> b = w.lock ();
984  if (!b) {
985  return;
986  }
987 
988  char buf [64];
989  snprintf (buf, sizeof (buf), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b->channel_name (c)).c_str());
990  m.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel), w, c, d)));
991 }
992 
993 void
995 {
998 }
999 
1004 void
1006 {
1007  if (!_session) {
1008  return;
1009  }
1010 
1011  for (int i = 0; i < 2; ++i) {
1012 
1013  Gtk::Notebook* notebook = row_index() == i ? &_vnotebook : &_hnotebook;
1014 
1015  PortGroupList const * gl = ports (i);
1016  int p = 0;
1017  for (PortGroupList::List::const_iterator j = gl->begin(); j != gl->end(); ++j) {
1018  bool has_connection = false;
1019  PortGroup::BundleList const & bl = (*j)->bundles ();
1020  PortGroup::BundleList::const_iterator k = bl.begin ();
1021  while (k != bl.end()) {
1022  if ((*k)->bundle->connected_to_anything (_session->engine())) {
1023  has_connection = true;
1024  break;
1025  }
1026  ++k;
1027  }
1028 
1029  /* Find the page index that we should update; this is backwards
1030  for the vertical tabs in the LEFT_TO_BOTTOM arrangement.
1031  */
1032  int page = p;
1033  if (i == row_index() && _arrangement == LEFT_TO_BOTTOM) {
1034  page = notebook->get_n_pages() - p - 1;
1035  }
1036 
1037  Gtk::Label* label = dynamic_cast<Gtk::Label*> (notebook->get_tab_label(*notebook->get_nth_page (page)));
1038  string c = label->get_label ();
1039  if (c.length() && c[0] == '<' && !has_connection) {
1040  /* this label is marked up with <b> but shouldn't be */
1041  label->set_text ((*j)->name);
1042  } else if (c.length() && c[0] != '<' && has_connection) {
1043  /* this label is not marked up with <b> but should be */
1044  label->set_markup (string_compose ("<b>%1</b>", Glib::Markup::escape_text ((*j)->name)));
1045  }
1046 
1047  ++p;
1048  }
1049  }
1050 }
1051 
1052 string
1054 {
1055  return _("channel");
1056 }
1057 
1059 bool
1061 {
1062  return (_type == DataType::NIL || t == _type);
1063 }
1064 
1065 uint32_t
1067 {
1068  if (_type == DataType::NIL) {
1069  return c.n_total ();
1070  }
1071 
1072  return c.get (_type);
1073 }
1074 
1078 uint32_t
1080 {
1081  uint32_t n = count_of_our_type (c);
1082  if (n == 0) {
1083  n = 1;
1084  }
1085 
1086  return n;
1087 }
1088 
1091 {
1092  if (show_only_bundles ()) {
1093 
1094  bool have_off_diagonal_association = false;
1095  bool have_diagonal_association = false;
1096  bool have_diagonal_not_association = false;
1097 
1098  for (uint32_t i = 0; i < node.row.bundle->nchannels().n_total(); ++i) {
1099 
1100  for (uint32_t j = 0; j < node.column.bundle->nchannels().n_total(); ++j) {
1101 
1102  if (!should_show (node.row.bundle->channel_type(i)) || !should_show (node.column.bundle->channel_type(j))) {
1103  continue;
1104  }
1105 
1106  ARDOUR::BundleChannel c[2];
1107  c[row_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
1109 
1110  PortMatrixNode::State const s = get_state (c);
1111 
1112  switch (s) {
1114  if (i == j) {
1115  have_diagonal_association = true;
1116  } else {
1117  have_off_diagonal_association = true;
1118  }
1119  break;
1120 
1122  if (i == j) {
1123  have_diagonal_not_association = true;
1124  }
1125  break;
1126 
1127  default:
1128  break;
1129  }
1130  }
1131  }
1132 
1133  if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
1135  } else if (!have_diagonal_association && !have_off_diagonal_association) {
1137  }
1138 
1139  return PortMatrixNode::PARTIAL;
1140 
1141  } else {
1142 
1143  ARDOUR::BundleChannel c[2];
1144  c[column_index()] = node.column;
1145  c[row_index()] = node.row;
1146  return get_state (c);
1147 
1148  }
1149 
1150  abort(); /* NOTREACHED */
1152 }
1153 
1155 bool
1157 {
1158  return b && b->nchannels() != ARDOUR::ChanCount::ZERO;
1159 }
1160 
1165 pair<int, int>
1167 {
1168  /* Look for the row's port group name in the columns */
1169 
1170  int new_column = 0;
1172  PortGroupList::List::const_iterator i = _ports[_column_index].begin();
1173  while (i != _ports[_column_index].end() && (*i)->name != r->name) {
1174  ++i;
1175  ++new_column;
1176  }
1177 
1178  if (i == _ports[_column_index].end ()) {
1179  return make_pair (-1, -1);
1180  }
1181 
1182  /* Look for the column's port group name in the rows */
1183 
1184  int new_row = 0;
1186  i = _ports[_row_index].begin();
1187  while (i != _ports[_row_index].end() && (*i)->name != c->name) {
1188  ++i;
1189  ++new_row;
1190  }
1191 
1192  if (i == _ports[_row_index].end ()) {
1193  return make_pair (-1, -1);
1194  }
1195 
1196  if (_arrangement == LEFT_TO_BOTTOM) {
1197  new_row = _ports[_row_index].size() - new_row - 1;
1198  }
1199 
1200  return make_pair (new_row, new_column);
1201 }
1202 
1203 bool
1205 {
1206  return check_flip().first != -1;
1207 }
1208 
1210 void
1212 {
1213  pair<int, int> n = check_flip ();
1214  if (n.first == -1) {
1215  return;
1216  }
1217 
1218  _vnotebook.set_current_page (n.first);
1219  _hnotebook.set_current_page (n.second);
1220 }
1221 
1222 bool
1223 PortMatrix::key_press (GdkEventKey* k)
1224 {
1225  if (k->keyval == GDK_f) {
1226  flip ();
1227  return true;
1228  }
1229 
1230  return false;
1231 }
virtual void set_state(ARDOUR::BundleChannel c[2], bool s)=0
void popup_menu(ARDOUR::BundleChannel, ARDOUR::BundleChannel, uint32_t)
Definition: port_matrix.cc:418
void remove_channel_proxy(boost::weak_ptr< ARDOUR::Bundle >, uint32_t)
Definition: port_matrix.cc:551
void resize_window_to_proportion_of_monitor(Gtk::Window *, int, int)
Definition: utils.cc:831
void setup_all_ports()
Definition: port_matrix.cc:642
void setup_scrollbars()
Definition: port_matrix.cc:247
void update_tab_highlighting()
void setup()
Definition: port_matrix.cc:210
PortMatrixBody * _body
Definition: port_matrix.h:216
Gtk::HBox _hbox
Definition: port_matrix.h:224
virtual std::string channel_noun() const
Gtk::Label _hlabel
Definition: port_matrix.h:222
void setup_notebooks()
Definition: port_matrix.cc:798
boost::shared_ptr< ARDOUR::IO > io_from_bundle(boost::shared_ptr< ARDOUR::Bundle >) const
Definition: port_matrix.cc:707
List::const_iterator begin() const
Definition: port_group.h:123
std::string channel_name(uint32_t) const
Definition: bundle.cc:266
void set_xoffset(uint32_t)
the ports are not associated
virtual bool can_add_channels(boost::shared_ptr< ARDOUR::Bundle >) const
Definition: port_matrix.cc:718
void vscroll_changed()
Definition: port_matrix.cc:241
Definition: ardour_ui.h:130
void remove_notebook_pages(Gtk::Notebook &)
Definition: port_matrix.cc:874
virtual bool can_rename_channels(boost::shared_ptr< ARDOUR::Bundle >) const
Definition: port_matrix.h:152
virtual void remove_channel(ARDOUR::BundleChannel)
Definition: port_matrix.cc:747
void add_remove_option(Gtk::Menu_Helpers::MenuList &, boost::weak_ptr< ARDOUR::Bundle >, int)
Definition: port_matrix.cc:964
void notebook_page_selected(GtkNotebookPage *, guint)
Definition: port_matrix.cc:884
ARDOUR::DataType _type
Definition: port_matrix.h:211
Gtk::HScrollbar _hscroll
Definition: port_matrix.h:217
std::list< BundleRecord * > BundleList
Definition: port_group.h:86
uint32_t full_scroll_width()
static int N
Definition: signals_test.cc:27
bool should_show(ARDOUR::DataType) const
Gtk::Menu * _menu
Definition: port_matrix.h:227
std::string name
name for the group
Definition: port_group.h:64
Definition: Beats.hpp:239
static uint32_t grid_spacing()
virtual PortMatrixNode::State get_state(ARDOUR::BundleChannel c[2]) const =0
uint32_t count_of_our_type(ARDOUR::ChanCount) const
uint32_t column_labels_border_x() const
int add_port(std::string connection, void *src, DataType type=DataType::NIL)
Definition: io.cc:312
bool _inhibit_toggle_show_only_bundles
Definition: port_matrix.h:233
PBD::Signal0< void > BundleAddedOrRemoved
Definition: session.h:758
bool _show_only_bundles
Definition: port_matrix.h:232
#define ENSURE_GUI_THREAD(obj, method,...)
Definition: gui_thread.h:34
std::pair< uint32_t, uint32_t > max_size() const
#define invalidator(x)
Definition: gui_thread.h:40
Gtk::Label _hspacer
Definition: port_matrix.h:225
boost::shared_ptr< ARDOUR::IO > io_from_bundle(boost::shared_ptr< ARDOUR::Bundle >) const
Definition: port_group.cc:780
the ports are associated
ARDOUR::BundleChannel row
uint32_t n_total() const
Definition: chan_count.h:69
virtual bool list_is_global(int) const =0
List::const_iterator end() const
Definition: port_group.h:127
void disassociate_all_on_bundle(boost::weak_ptr< ARDOUR::Bundle >, int)
Definition: port_matrix.cc:574
#define _(Text)
Definition: i18n.h:11
int remove_port(boost::shared_ptr< Port >, void *src)
Definition: io.cc:251
column labels on top, row labels to the right
Definition: port_matrix.h:84
A list of PortGroups.
Definition: port_group.h:100
PBD::Signal5< void, boost::weak_ptr< Port >, std::string, boost::weak_ptr< Port >, std::string, bool > PortConnectedOrDisconnected
Definition: port_manager.h:136
void rebuild_and_draw_grid()
Gtk::Window * _parent
Definition: port_matrix.h:208
PortMatrixNode::State get_association(PortMatrixNode) const
#define X_(Text)
Definition: i18n.h:13
void session_going_away()
Definition: port_matrix.cc:896
void add_disassociate_option(Gtk::Menu_Helpers::MenuList &, boost::weak_ptr< ARDOUR::Bundle >, int, int)
Definition: port_matrix.cc:979
PBD::ScopedConnectionList _route_connections
Definition: port_matrix.h:212
virtual bool can_remove_channels(boost::shared_ptr< ARDOUR::Bundle >) const
Definition: port_matrix.cc:741
void setup_global_ports_proxy()
Definition: port_matrix.cc:632
bool on_scroll_event(GdkEventScroll *)
Definition: port_matrix.cc:683
#define REMOVE_FROM_GTK_PARENT(WGT)
Definition: amp.h:29
PortGroup::BundleList const & bundles() const
Definition: port_group.cc:686
PBD::ScopedConnectionList _bundle_changed_connections
Definition: port_matrix.h:214
PortGroupList const * ports(int d) const
Definition: port_matrix.h:114
PBD::Signal0< void > Changed
Definition: port_group.h:134
virtual std::string disassociation_verb() const =0
uint32_t size() const
Definition: port_group.h:115
#define gui_context()
Definition: gui_thread.h:36
ChanCount nchannels() const
Definition: bundle.cc:68
bool deletion_in_progress() const
Definition: session.h:179
PBD::Signal1< void, ARDOUR::Bundle::Change > BundleChanged
Definition: port_group.h:137
uint32_t full_scroll_height()
uint32_t count_of_our_type_min_1(ARDOUR::ChanCount) const
boost::shared_ptr< RouteList > get_routes() const
Definition: session.h:229
sigc::signal< void > DimensionsChanged
PortMatrix(Gtk::Window *, ARDOUR::Session *, ARDOUR::DataType)
Definition: port_matrix.cc:52
void hscroll_changed()
Definition: port_matrix.cc:235
DataType channel_type(uint32_t) const
Definition: bundle.cc:521
PBD::ScopedConnectionList _session_connections
void set_type(ARDOUR::DataType)
Definition: port_matrix.cc:229
void set_yoffset(uint32_t)
std::pair< int, int > check_flip() const
row labels to the left, column labels on the bottom
Definition: port_matrix.h:85
bool show_only_bundles() const
Definition: port_matrix.h:94
ARDOUR::BundleChannel column
boost::shared_ptr< Port > nth(uint32_t n) const
Definition: io.h:122
void select_arrangement()
Definition: port_matrix.cc:308
Gtk::Label _vspacer
Definition: port_matrix.h:226
std::pair< uint32_t, uint32_t > max_size() const
Definition: port_matrix.cc:672
Gtk::Notebook _vnotebook
Definition: port_matrix.h:219
uint32_t get(DataType t) const
Definition: chan_count.h:59
virtual void add_channel(boost::shared_ptr< ARDOUR::Bundle >, ARDOUR::DataType)
Definition: port_matrix.cc:724
int row_index() const
Definition: port_matrix.h:110
boost::shared_ptr< Bundle > bundle
Definition: bundle.h:168
Gtk::Notebook _hnotebook
Definition: port_matrix.h:220
void disassociate_all()
Definition: port_matrix.cc:275
boost::shared_ptr< const PortGroup > visible_ports(int d) const
Definition: port_matrix.cc:929
used when we are examining bundles; the bundles are partially associated
PBD::Signal0< void > PortRegisteredOrUnregistered
Definition: port_manager.h:129
boost::shared_ptr< const PortGroup > visible_columns() const
Definition: port_matrix.cc:396
bool key_press(GdkEventKey *)
int channel
channel index, or -1 for "all"
Definition: bundle.h:169
void rename_channel_proxy(boost::weak_ptr< ARDOUR::Bundle >, uint32_t)
Definition: port_matrix.cc:563
boost::shared_ptr< const PortGroup > visible_rows() const
Definition: port_matrix.cc:409
virtual void set_session(ARDOUR::Session *)
void body_dimensions_changed()
Definition: port_matrix.cc:902
Gtk::VScrollbar _vscroll
Definition: port_matrix.h:218
virtual void rename_channel(ARDOUR::BundleChannel)
Definition: port_matrix.h:155
int _column_index
Definition: port_matrix.h:230
virtual void setup_ports(int)=0
uint32_t column_labels_height() const
std::string escape_underscores(std::string const &)
static bool bundle_with_channels(boost::shared_ptr< ARDOUR::Bundle >)
Gtk::VBox _vbox
Definition: port_matrix.h:223
void route_processors_changed(ARDOUR::RouteProcessorChange)
Definition: port_matrix.cc:189
Gtk::Label _vlabel
Definition: port_matrix.h:221
bool _ignore_notebook_page_selected
Definition: port_matrix.h:234
Arrangement _arrangement
Definition: port_matrix.h:228
void setup_global_ports()
Definition: port_matrix.cc:619
static const ChanCount ZERO
Definition: chan_count.h:149
void routes_changed()
Definition: port_matrix.cc:201
void port_connected_or_disconnected()
Definition: port_matrix.cc:994
virtual void remove_all_channels(boost::weak_ptr< ARDOUR::Bundle >)
Definition: port_matrix.cc:769
PortGroupList const * columns() const
Definition: port_matrix.cc:390
ARDOUR::Session * _session
uint32_t alloc_scroll_height()
PBD::ScopedConnectionList _changed_connections
Definition: port_matrix.h:213
AudioEngine & engine()
Definition: session.h:546
void init()
Definition: port_matrix.cc:122
std::string string_compose(const std::string &fmt, const T1 &o1)
Definition: compose.h:208
int column_index() const
Definition: port_matrix.h:102
void add_channel_proxy(boost::weak_ptr< ARDOUR::Bundle >, ARDOUR::DataType)
Definition: port_matrix.cc:787
void toggle_show_only_bundles()
Definition: port_matrix.cc:655
PortGroupList _ports[2]
Definition: port_matrix.h:176
PBD::Signal1< void, RouteList & > RouteAdded
Definition: session.h:317
bool can_flip() const
void reconnect_to_routes()
Definition: port_matrix.cc:177
PortGroupList const * rows() const
Definition: port_matrix.cc:403
int _row_index
Definition: port_matrix.h:229
uint32_t alloc_scroll_width()
void disassociate_all_on_channel(boost::weak_ptr< ARDOUR::Bundle >, uint32_t, int)
Definition: port_matrix.cc:589