Plugin automation fixes from torbenh.
[ardour.git] / gtk2_ardour / port_matrix.cc
1 /*
2     Copyright (C) 2002-2007 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 <gtkmm/label.h>
21 #include <gtkmm/enums.h>
22 #include <gtkmm/image.h>
23 #include <gtkmm/stock.h>
24 #include <gtkmm/messagedialog.h>
25 #include <gtkmm/menu.h>
26 #include <gtkmm/menu_elems.h>
27 #include <gtkmm/menuitem.h>
28 #include <gtkmm/menushell.h>
29 #include <glibmm/objectbase.h>
30 #include <gtkmm2ext/doi.h>
31 #include <ardour/port_insert.h>
32 #include "ardour/session.h"
33 #include "ardour/io.h"
34 #include "ardour/audioengine.h"
35 #include "ardour/track.h"
36 #include "ardour/audio_track.h"
37 #include "ardour/midi_track.h"
38 #include "ardour/data_type.h"
39 #include "io_selector.h"
40 #include "utils.h"
41 #include "gui_thread.h"
42 #include "i18n.h"
43
44 /** Add a port to a group.
45  *  @param p Port name, with or without prefix.
46  */
47
48 void
49 PortGroup::add (std::string const & p)
50 {
51         if (prefix.empty() == false && p.substr (0, prefix.length()) == prefix) {
52                 ports.push_back (p.substr (prefix.length()));
53         } else {
54                 ports.push_back (p);
55         }
56 }
57
58 /** PortGroupUI constructor.
59  *  @param m PortMatrix to work for.
60  *  @Param g PortGroup to represent.
61  */
62
63 PortGroupUI::PortGroupUI (PortMatrix& m, PortGroup& g)
64         : _port_matrix (m), _port_group (g), _ignore_check_button_toggle (false),
65           _visibility_checkbutton (g.name)
66 {
67         int const ports = _port_group.ports.size();
68         int const rows = _port_matrix.n_rows ();
69
70         if (rows == 0 || ports == 0) {
71                 return;
72         }
73
74         /* Sort out the table and the checkbuttons inside it */
75         
76         _table.resize (rows, ports);
77         _port_checkbuttons.resize (rows);
78         for (int i = 0; i < rows; ++i) {
79                 _port_checkbuttons[i].resize (ports);
80         }
81
82         for (int i = 0; i < rows; ++i) {
83                 for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
84                         Gtk::CheckButton* b = new Gtk::CheckButton;
85                         
86                         b->signal_toggled().connect (
87                                 sigc::bind (sigc::mem_fun (*this, &PortGroupUI::port_checkbutton_toggled), b, i, j)
88                                 );
89                         
90                         _port_checkbuttons[i][j] = b;
91                         _table.attach (*b, j, j + 1, i, i + 1);
92                 }
93         }
94
95         _table_box.add (_table);
96
97         _ignore_check_button_toggle = true;
98
99         /* Set the state of the check boxes according to current connections */
100         for (int i = 0; i < rows; ++i) {
101                 for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
102                         std::string const t = _port_group.prefix + _port_group.ports[j];
103                         bool const s = _port_matrix.get_state (i, t);
104                         _port_checkbuttons[i][j]->set_active (s);
105                         if (s) {
106                                 _port_group.visible = true;
107                         }
108                 }
109         }
110
111         _ignore_check_button_toggle = false;
112
113         _visibility_checkbutton.signal_toggled().connect (sigc::mem_fun (*this, &PortGroupUI::visibility_checkbutton_toggled));
114 }
115
116 /** The visibility of a PortGroupUI has been toggled */
117 void
118 PortGroupUI::visibility_checkbutton_toggled ()
119 {
120         _port_group.visible = _visibility_checkbutton.get_active ();
121         setup_visibility ();
122 }
123
124 /** @return Width and height of a single checkbutton in a port group table */
125 std::pair<int, int>
126 PortGroupUI::unit_size () const
127 {
128         if (_port_checkbuttons.empty() || _port_checkbuttons[0].empty())
129         {
130                 return std::pair<int, int> (0, 0);
131         }
132
133         int r = 0;
134         /* We can't ask for row spacing unless there >1 rows, otherwise we get a warning */
135         if (_table.property_n_rows() > 1) {
136                 r = _table.get_row_spacing (0);
137         }
138
139         return std::make_pair (
140                 _port_checkbuttons[0][0]->get_width() + _table.get_col_spacing (0),
141                 _port_checkbuttons[0][0]->get_height() + r
142                 );
143 }
144
145 /** @return Table widget containing the port checkbuttons */
146 Gtk::Widget&
147 PortGroupUI::get_table ()
148 {
149         return _table_box;
150 }
151
152 /** @return Checkbutton used to toggle visibility */
153 Gtk::Widget&
154 PortGroupUI::get_visibility_checkbutton ()
155 {
156         return _visibility_checkbutton;
157 }
158
159
160 /** Handle a toggle of a port check button */
161 void
162 PortGroupUI::port_checkbutton_toggled (Gtk::CheckButton* b, int r, int c)
163 {
164         if (_ignore_check_button_toggle == false) {
165                 _port_matrix.set_state (r, _port_group.prefix + _port_group.ports[c], b->get_active());
166         }
167 }
168
169 /** Set up visibility of the port group according to PortGroup::visible */
170 void
171 PortGroupUI::setup_visibility ()
172 {
173         if (_port_group.visible) {
174                 _table_box.show ();
175         } else {
176                 _table_box.hide ();
177         }
178
179         if (_visibility_checkbutton.get_active () != _port_group.visible) {
180                 _visibility_checkbutton.set_active (_port_group.visible);
181         }
182 }
183
184
185 RotatedLabelSet::RotatedLabelSet (PortGroupList& g)
186         : Glib::ObjectBase ("RotatedLabelSet"), Gtk::Widget (), _port_group_list (g), _base_width (128)
187 {
188         set_flags (Gtk::NO_WINDOW);
189         set_angle (30);
190 }
191
192 RotatedLabelSet::~RotatedLabelSet ()
193 {
194         
195 }
196
197
198 /** Set the angle that the labels are drawn at.
199  * @param degrees New angle in degrees.
200  */
201
202 void
203 RotatedLabelSet::set_angle (int degrees)
204 {
205         _angle_degrees = degrees;
206         _angle_radians = M_PI * _angle_degrees / 180;
207
208         queue_resize ();
209 }
210
211 void
212 RotatedLabelSet::on_size_request (Gtk::Requisition* requisition)
213 {
214         *requisition = Gtk::Requisition ();
215
216         if (_pango_layout == 0) {
217                 return;
218         }
219
220         /* Our height is the highest label */
221         requisition->height = 0;
222         for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
223                 for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
224                         std::pair<int, int> const d = setup_layout (*j);
225                         if (d.second > requisition->height) {
226                                 requisition->height = d.second;
227                         }
228                 }
229         }
230
231         /* And our width is the base plus the width of the last label */
232         requisition->width = _base_width;
233         int const n = _port_group_list.n_visible_ports ();
234         if (n > 0) {
235                 std::pair<int, int> const d = setup_layout (_port_group_list.get_port_by_index (n - 1, false));
236                 requisition->width += d.first;
237         }
238 }
239
240 void
241 RotatedLabelSet::on_size_allocate (Gtk::Allocation& allocation)
242 {
243         set_allocation (allocation);
244
245         if (_gdk_window) {
246                 _gdk_window->move_resize (
247                         allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height()
248                         );
249         }
250 }
251
252 void
253 RotatedLabelSet::on_realize ()
254 {
255         Gtk::Widget::on_realize ();
256
257         Glib::RefPtr<Gtk::Style> style = get_style ();
258
259         if (!_gdk_window) {
260                 GdkWindowAttr attributes;
261                 memset (&attributes, 0, sizeof (attributes));
262
263                 Gtk::Allocation allocation = get_allocation ();
264                 attributes.x = allocation.get_x ();
265                 attributes.y = allocation.get_y ();
266                 attributes.width = allocation.get_width ();
267                 attributes.height = allocation.get_height ();
268
269                 attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK; 
270                 attributes.window_type = GDK_WINDOW_CHILD;
271                 attributes.wclass = GDK_INPUT_OUTPUT;
272
273                 _gdk_window = Gdk::Window::create (get_window (), &attributes, GDK_WA_X | GDK_WA_Y);
274                 unset_flags (Gtk::NO_WINDOW);
275                 set_window (_gdk_window);
276
277                 _bg_colour = style->get_bg (Gtk::STATE_NORMAL );
278                 modify_bg (Gtk::STATE_NORMAL, _bg_colour);
279                 _fg_colour = style->get_fg (Gtk::STATE_NORMAL);
280 ;
281                 _gdk_window->set_user_data (gobj ());
282
283                 /* Set up Pango stuff */
284                 _pango_context = create_pango_context ();
285
286                 Pango::Matrix matrix = PANGO_MATRIX_INIT;
287                 pango_matrix_rotate (&matrix, _angle_degrees);
288                 _pango_context->set_matrix (matrix);
289
290                 _pango_layout = Pango::Layout::create (_pango_context);
291                 _gc = Gdk::GC::create (get_window ());
292         }
293 }
294
295 void
296 RotatedLabelSet::on_unrealize()
297 {
298         _gdk_window.clear ();
299
300         Gtk::Widget::on_unrealize ();
301 }
302
303
304 /** Set up our Pango layout to plot a given string, and compute its dimensions once
305  *  it has been rotated.
306  *  @param s String to use.
307  *  @return width and height of the rotated string, in pixels.
308  */
309
310 std::pair<int, int>
311 RotatedLabelSet::setup_layout (std::string const & s)
312 {
313         _pango_layout->set_text (s);
314
315         /* Here's the unrotated size */
316         int w;
317         int h;
318         _pango_layout->get_pixel_size (w, h);
319
320         /* Rotate the width and height as appropriate.  I thought Pango might be able
321            to do this for us, but I can't find out how... */
322         std::pair<int, int> d;
323         d.first = int (w * cos (_angle_radians) - h * sin (_angle_radians));
324         d.second = int (w * sin (_angle_radians) + h * cos (_angle_radians));
325
326         return d;
327 }
328
329 bool
330 RotatedLabelSet::on_expose_event (GdkEventExpose* event)
331 {
332         if (!_gdk_window) {
333                 return true;
334         }
335
336         int const height = get_allocation().get_height ();
337         double const spacing = double (_base_width) / _port_group_list.n_visible_ports();
338
339         /* Plot all the visible labels; really we should clip for efficiency */
340         int n = 0;
341         for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
342                 if ((*i)->visible) {
343                         for (uint32_t j = 0; j < (*i)->ports.size(); ++j) {
344                                 std::pair<int, int> const d = setup_layout ((*i)->ports[j]);
345                                 get_window()->draw_layout (_gc, int ((n + 0.25) * spacing), height - d.second, _pango_layout, _fg_colour, _bg_colour);
346                                 ++n;
347                         }
348                 }
349         }
350
351         return true;
352 }
353
354 /** Set the `base width'.  This is the width of the base of the label set, ie:
355  *
356  *     L L L L
357  *    E E E E
358  *   B B B B
359  *  A A A A
360  * L L L L
361  * <--w-->
362  */
363     
364 void
365 RotatedLabelSet::set_base_width (int w)
366 {
367         _base_width = w;
368         queue_resize ();
369 }
370
371
372 PortMatrix::PortMatrix (ARDOUR::Session& session, ARDOUR::DataType type, bool offer_inputs, PortGroupList::Mask mask)
373         : _offer_inputs (offer_inputs), _port_group_list (session, type, offer_inputs, mask), _type (type),
374           _column_labels (_port_group_list)
375 {
376         _row_labels_vbox[0] = _row_labels_vbox[1] = 0;
377         _side_vbox_pad[0] = _side_vbox_pad[1] = 0;
378
379         pack_start (_visibility_checkbutton_box, false, false);
380         
381         _side_vbox[0].pack_start (*Gtk::manage (new Gtk::Label ("")));
382         _overall_hbox.pack_start (_side_vbox[0], false, false);
383         _scrolled_window.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER);
384         _scrolled_window.set_shadow_type (Gtk::SHADOW_NONE);
385         Gtk::VBox* b = new Gtk::VBox;
386         b->pack_start (_column_labels, false, false);
387         b->pack_start (_port_group_hbox, false, false);
388         Gtk::Alignment* a = new Gtk::Alignment (0, 1, 0, 0);
389         a->add (*Gtk::manage (b));
390         _scrolled_window.add (*Gtk::manage (a));
391         _overall_hbox.pack_start (_scrolled_window);
392         _side_vbox[1].pack_start (*Gtk::manage (new Gtk::Label ("")));
393         _overall_hbox.pack_start (_side_vbox[1]);
394         pack_start (_overall_hbox);
395
396         _port_group_hbox.signal_size_allocate().connect (sigc::hide (sigc::mem_fun (*this, &IOSelector::setup_dimensions)));
397 }
398
399 PortMatrix::~PortMatrix ()
400 {
401         clear ();
402 }
403
404 /** Clear out the things that change when the number of source or destination ports changes */
405 void
406 PortMatrix::clear ()
407 {
408         for (int i = 0; i < 2; ++i) {
409
410                 for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
411                         delete *j;
412                 }
413                 _row_labels[i].clear ();
414                 
415                 if (_row_labels_vbox[i]) {
416                         _side_vbox[i].remove (*_row_labels_vbox[i]);
417                 }
418                 delete _row_labels_vbox[i];
419                 _row_labels_vbox[i] = 0;
420                 
421                 if (_side_vbox_pad[i]) {
422                         _side_vbox[i].remove (*_side_vbox_pad[i]);
423                 }
424                 delete _side_vbox_pad[i];
425                 _side_vbox_pad[i] = 0;
426         }
427
428         for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
429                 _port_group_hbox.remove ((*i)->get_table());
430                 _visibility_checkbutton_box.remove ((*i)->get_visibility_checkbutton());
431                 delete *i;
432         }
433
434         _port_group_ui.clear ();
435 }
436
437
438 /** Set up dimensions of some of our widgets which depend on other dimensions
439  *  within the dialogue.
440  */
441 void
442 PortMatrix::setup_dimensions ()
443 {
444         /* Get some dimensions from various places */
445         int const scrollbar_height = _scrolled_window.get_hscrollbar()->get_height();
446
447         std::pair<int, int> unit_size (0, 0);
448         int port_group_tables_height = 0;
449         for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
450                 std::pair<int, int> const u = (*i)->unit_size ();
451                 unit_size.first = std::max (unit_size.first, u.first);
452                 unit_size.second = std::max (unit_size.second, u.second);
453                 port_group_tables_height = std::max (
454                         port_group_tables_height, (*i)->get_table().get_height()
455                         );
456         }
457
458         /* Column labels */
459         _column_labels.set_base_width (_port_group_list.n_visible_ports () * unit_size.first);
460
461         /* Scrolled window */
462         /* XXX: really shouldn't set a minimum horizontal size here, but if we don't
463            the window starts up very small */
464         _scrolled_window.set_size_request (
465                 std::min (_column_labels.get_width(), 640),
466                 _column_labels.get_height() + port_group_tables_height + scrollbar_height + 16
467                 );
468         
469         /* Row labels */
470         for (int i = 0; i < 2; ++i) {
471                 for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
472                         (*j)->get_child()->set_size_request (-1, unit_size.second);
473                 }
474
475                 if (_side_vbox_pad[i]) {
476                         _side_vbox_pad[i]->set_size_request (-1, scrollbar_height + unit_size.second / 4);
477                 }
478         }
479 }
480
481
482 /** Set up the dialogue */
483 void
484 PortMatrix::setup ()
485 {
486         clear ();
487
488         int const rows = n_rows ();
489         
490         /* Row labels */
491         for (int i = 0; i < 2; ++i) {
492                 _row_labels_vbox[i] = new Gtk::VBox;
493                 int const run_rows = std::max (1, rows);
494                 for (int j = 0; j < run_rows; ++j) {
495                         Gtk::Label* label = new Gtk::Label (rows == 0 ? "Quim" : row_name (j));
496                         Gtk::EventBox* b = new Gtk::EventBox;
497                         b->set_events (Gdk::BUTTON_PRESS_MASK);
498                         b->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::row_label_button_pressed), j));
499                         b->add (*Gtk::manage (label));
500                         _row_labels[i].push_back (b);
501                         _row_labels_vbox[i]->pack_start (*b, false, false);
502                 }
503
504                 _side_vbox[i].pack_start (*_row_labels_vbox[i], false, false);
505                 _side_vbox_pad[i] = new Gtk::Label ("");
506                 _side_vbox[i].pack_start (*_side_vbox_pad[i], false, false);
507         }
508
509         /* Checkbutton tables and visibility checkbuttons */
510         int n = 0;
511         for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
512                 if ((*i)->visible) {
513                         PortGroupUI* t = new PortGroupUI (*this, **i);
514
515                         /* XXX: this is a bit of a hack; should probably use a configurable colour here */
516                         Gdk::Color alt_bg = get_style()->get_bg (Gtk::STATE_NORMAL);
517                         alt_bg.set_rgb (alt_bg.get_red() + 4096, alt_bg.get_green() + 4096, alt_bg.get_blue () + 4096);
518                         if ((n % 2) == 0) {
519                                 t->get_table().modify_bg (Gtk::STATE_NORMAL, alt_bg);
520                         }
521
522                         _port_group_ui.push_back (t);
523                         _port_group_hbox.pack_start (t->get_table(), false, false);
524
525                         _visibility_checkbutton_box.pack_start (t->get_visibility_checkbutton(), false, false);
526                         ++n;
527                 }
528         }
529
530         show_all ();
531
532         for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
533                 (*i)->setup_visibility ();
534         }
535         
536 }
537
538 void
539 PortMatrix::redisplay ()
540 {
541         _port_group_list.refresh ();
542         setup ();
543 }
544
545
546 /** Handle a button press on a row label */
547 bool
548 PortMatrix::row_label_button_pressed (GdkEventButton* e, int r)
549 {
550         if (e->type != GDK_BUTTON_PRESS || e->button != 3) {
551                 return false;
552         }
553
554         Gtk::Menu* menu = Gtk::manage (new Gtk::Menu);
555         Gtk::Menu_Helpers::MenuList& items = menu->items ();
556         menu->set_name ("ArdourContextMenu");
557
558         bool const can_add = maximum_rows () > n_rows ();
559         bool const can_remove = minimum_rows () < n_rows ();
560         std::string const name = row_name (r);
561         
562         items.push_back (
563                 Gtk::Menu_Helpers::MenuElem (string_compose(_("Add %1"), row_descriptor()), sigc::mem_fun (*this, &PortMatrix::add_row))
564                 );
565
566         items.back().set_sensitive (can_add);
567
568         items.push_back (
569                 Gtk::Menu_Helpers::MenuElem (string_compose(_("Remove %1 \"%2\""), row_descriptor(), name), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_row), r))
570                 );
571
572         items.back().set_sensitive (can_remove);
573
574         menu->popup (e->button, e->time);
575         
576         return true;
577 }
578
579 void
580 PortMatrix::set_type (ARDOUR::DataType t)
581 {
582         _type = t;
583         _port_group_list.set_type (t);
584         redisplay ();
585 }
586
587 void
588 PortMatrix::set_offer_inputs (bool i)
589 {
590         _offer_inputs = i;
591         _port_group_list.set_offer_inputs (i);
592         redisplay ();
593 }
594
595 /** PortGroupList constructor.
596  *  @param session Session to get ports from.
597  *  @param type Type of ports to offer (audio or MIDI)
598  *  @param offer_inputs true to offer output ports, otherwise false.
599  *  @param mask Mask of groups to make visible by default.
600  */
601
602 PortGroupList::PortGroupList (ARDOUR::Session & session, ARDOUR::DataType type, bool offer_inputs, Mask mask)
603         : _session (session), _type (type), _offer_inputs (offer_inputs),
604           buss (_("Bus"), "ardour:", mask & BUSS),
605           track (_("Track"), "ardour:", mask & TRACK),
606           system (_("System"), "system:", mask & SYSTEM),
607           other (_("Other"), "", mask & OTHER)
608 {
609         refresh ();
610 }
611
612 void
613 PortGroupList::refresh ()
614 {
615         clear ();
616         
617         buss.ports.clear ();
618         track.ports.clear ();
619         system.ports.clear ();
620         other.ports.clear ();
621
622         /* Find the ports provided by ardour; we can't derive their type just from their
623            names, so we'll have to be more devious. */
624
625         boost::shared_ptr<ARDOUR::Session::RouteList> routes = _session.get_routes ();
626
627         for (ARDOUR::Session::RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) {
628
629                 PortGroup* g = 0;
630                 if (_type == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::AudioTrack*> ((*i).get())) {
631                         /* Audio track for an audio IO */
632                         g = &track;
633                 } else if (_type == ARDOUR::DataType::MIDI && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get())) {
634                         /* Midi track for a MIDI IO */
635                         g = &track;
636                 } else if (_type == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get()) == 0) {
637                         /* Non-MIDI track for an Audio IO; must be an audio buss */
638                         g = &buss;
639                 }
640
641                 if (g) {
642                         ARDOUR::PortSet const & p = _offer_inputs ? ((*i)->inputs()) : ((*i)->outputs());
643                         for (uint32_t j = 0; j < p.num_ports(); ++j) {
644                                 g->add (p.port(j)->name ());
645                         }
646
647                         std::sort (g->ports.begin(), g->ports.end());
648                 }
649         }
650         
651
652         /* XXX: inserts, sends, plugin inserts? */
653         
654         /* Now we need to find the non-ardour ports; we do this by first
655            finding all the ports that we can connect to. */
656         const char **ports = _session.engine().get_ports (
657                 "", _type.to_jack_type(), _offer_inputs ? JackPortIsInput : JackPortIsOutput
658                 );
659
660         if (ports) {
661
662                 int n = 0;
663                 while (ports[n]) {
664                         std::string const p = ports[n];
665
666                         if (p.substr(0, strlen ("system:")) == "system:") {
667                                 /* system: prefix */
668                                 system.add (p);
669                         } else {
670                                 if (p.substr(0, strlen("ardour:")) != "ardour:") {
671                                         /* other (non-ardour) prefix */
672                                         other.add (p);
673                                 }
674                         }
675
676                         ++n;
677                 }
678         }
679
680         push_back (&buss);
681         push_back (&track);
682         push_back (&system);
683         push_back (&other);
684 }
685
686 int
687 PortGroupList::n_visible_ports () const
688 {
689         int n = 0;
690         
691         for (const_iterator i = begin(); i != end(); ++i) {
692                 if ((*i)->visible) {
693                         n += (*i)->ports.size();
694                 }
695         }
696
697         return n;
698 }
699
700 std::string
701 PortGroupList::get_port_by_index (int n, bool with_prefix) const
702 {
703         /* XXX: slightly inefficient algorithm */
704
705         for (const_iterator i = begin(); i != end(); ++i) {
706                 for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
707                         if (n == 0) {
708                                 if (with_prefix) {
709                                         return (*i)->prefix + *j;
710                                 } else {
711                                         return *j;
712                                 }
713                         }
714                         --n;
715                 }
716         }
717
718         return "";
719 }
720
721 void
722 PortGroupList::set_type (ARDOUR::DataType t)
723 {
724         _type = t;
725 }
726
727 void
728 PortGroupList::set_offer_inputs (bool i)
729 {
730         _offer_inputs = i;
731 }
732