Considerable re-work to allow scrolling of the checkbutton area of the dialogue....
[ardour.git] / gtk2_ardour / io_selector.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
59 PortGroupTable::PortGroupTable (
60         PortGroup& g, boost::shared_ptr<ARDOUR::IO> io, bool for_input
61         )
62         : _port_group (g), _ignore_check_button_toggle (false),
63           _io (io), _for_input (for_input)
64 {
65         ARDOUR::DataType const t = _io->default_type();
66
67         int rows;
68         if (_for_input) {
69                 rows = _io->n_inputs().get(t);
70         } else {
71                 rows = _io->n_outputs().get(t);
72         }       
73         
74         int const ports = _port_group.ports.size();
75
76         if (rows == 0 || ports == 0) {
77                 return;
78         }
79
80         /* Sort out the table and the checkbuttons inside it */
81         
82         _table.resize (rows, ports);
83         _check_buttons.resize (rows);
84         for (int i = 0; i < rows; ++i) {
85                 _check_buttons[i].resize (ports);
86         }
87
88         for (int i = 0; i < rows; ++i) {
89                 for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
90                         Gtk::CheckButton* b = new Gtk::CheckButton;
91                         b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &PortGroupTable::check_button_toggled), b, i, _port_group.prefix + _port_group.ports[j]));
92                         _check_buttons[i][j] = b;
93                         _table.attach (*b, j, j + 1, i, i + 1);
94                 }
95         }
96
97         _box.add (_table);
98
99         _ignore_check_button_toggle = true;
100
101         /* Set the state of the check boxes according to current connections */
102         for (int i = 0; i < rows; ++i) {
103                 const char **connections = _for_input ? _io->input(i)->get_connections() : _io->output(i)->get_connections();
104                 for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
105
106                         std::string const t = _port_group.prefix + _port_group.ports[j];
107                         int k = 0;
108                         bool required_state = false;
109
110                         while (connections && connections[k]) {
111                                 if (std::string(connections[k]) == t) {
112                                         required_state = true;
113                                         break;
114                                 }
115                                 ++k;
116                         }
117
118                         _check_buttons[i][j]->set_active (required_state);
119                 }
120         }
121
122         _ignore_check_button_toggle = false;
123 }
124
125 /** @return Width and height of a single check button in a port group table */
126 std::pair<int, int>
127 PortGroupTable::unit_size () const
128 {
129         if (_check_buttons.empty() || _check_buttons[0].empty()) {
130                 return std::pair<int, int> (0, 0);
131         }
132
133         return std::make_pair (
134                 _check_buttons[0][0]->get_width() + _table.get_col_spacing (0),
135                 _check_buttons[0][0]->get_height() + _table.get_row_spacing (0)
136                 );
137 }
138
139 Gtk::Widget&
140 PortGroupTable::get_widget ()
141 {
142         return _box;
143 }
144
145
146 /** Handle a toggle of a check button */
147 void
148 PortGroupTable::check_button_toggled (Gtk::CheckButton* b, int r, std::string const & p)
149 {
150         if (_ignore_check_button_toggle) {
151                 return;
152         }
153         
154         bool const new_state = b->get_active ();
155
156         if (new_state) {
157                 if (_for_input) {
158                         _io->connect_input (_io->input(r), p, 0);
159                 } else {
160                         _io->connect_output (_io->output(r), p, 0);
161                 }
162         } else {
163                 if (_for_input) {
164                         _io->disconnect_input (_io->input(r), p, 0);
165                 } else {
166                         _io->disconnect_output (_io->output(r), p, 0);
167                 }
168         }
169 }
170
171
172 RotatedLabelSet::RotatedLabelSet (PortGroupList& g)
173         : Glib::ObjectBase ("RotatedLabelSet"), Gtk::Widget (), _port_group_list (g), _base_width (128)
174 {
175         set_flags (Gtk::NO_WINDOW);
176         set_angle (30);
177 }
178
179 RotatedLabelSet::~RotatedLabelSet ()
180 {
181         
182 }
183
184
185 /** Set the angle that the labels are drawn at.
186  * @param degrees New angle in degrees.
187  */
188
189 void
190 RotatedLabelSet::set_angle (int degrees)
191 {
192         _angle_degrees = degrees;
193         _angle_radians = M_PI * _angle_degrees / 180;
194
195         queue_resize ();
196 }
197
198 void
199 RotatedLabelSet::on_size_request (Gtk::Requisition* requisition)
200 {
201         *requisition = Gtk::Requisition ();
202
203         if (_pango_layout == 0) {
204                 return;
205         }
206
207         /* Our height is the highest label */
208         requisition->height = 0;
209         for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
210                 for (std::vector<std::string>::const_iterator j = i->ports.begin(); j != i->ports.end(); ++j) {
211                         std::pair<int, int> const d = setup_layout (*j);
212                         if (d.second > requisition->height) {
213                                 requisition->height = d.second;
214                         }
215                 }
216         }
217
218         /* And our width is the base plus the width of the last label */
219         requisition->width = _base_width;
220         int const n = _port_group_list.n_ports ();
221         if (n > 0) {
222                 std::pair<int, int> const d = setup_layout (_port_group_list.get_port_by_index (n - 1, false));
223                 requisition->width += d.first;
224         }
225 }
226
227 void
228 RotatedLabelSet::on_size_allocate (Gtk::Allocation& allocation)
229 {
230         set_allocation (allocation);
231
232         if (_gdk_window) {
233                 _gdk_window->move_resize (
234                         allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height()
235                         );
236         }
237 }
238
239 void
240 RotatedLabelSet::on_realize ()
241 {
242         Gtk::Widget::on_realize ();
243
244         Glib::RefPtr<Gtk::Style> style = get_style ();
245
246         if (!_gdk_window) {
247                 GdkWindowAttr attributes;
248                 memset (&attributes, 0, sizeof (attributes));
249
250                 Gtk::Allocation allocation = get_allocation ();
251                 attributes.x = allocation.get_x ();
252                 attributes.y = allocation.get_y ();
253                 attributes.width = allocation.get_width ();
254                 attributes.height = allocation.get_height ();
255
256                 attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK; 
257                 attributes.window_type = GDK_WINDOW_CHILD;
258                 attributes.wclass = GDK_INPUT_OUTPUT;
259
260                 _gdk_window = Gdk::Window::create (get_window (), &attributes, GDK_WA_X | GDK_WA_Y);
261                 unset_flags (Gtk::NO_WINDOW);
262                 set_window (_gdk_window);
263
264                 _bg_colour = style->get_bg (Gtk::STATE_NORMAL );
265                 modify_bg (Gtk::STATE_NORMAL, _bg_colour);
266                 _fg_colour = style->get_fg (Gtk::STATE_NORMAL);
267 ;
268                 _gdk_window->set_user_data (gobj ());
269
270                 /* Set up Pango stuff */
271                 _pango_context = create_pango_context ();
272
273                 Pango::Matrix matrix = PANGO_MATRIX_INIT;
274                 pango_matrix_rotate (&matrix, _angle_degrees);
275                 _pango_context->set_matrix (matrix);
276
277                 _pango_layout = Pango::Layout::create (_pango_context);
278                 _gc = Gdk::GC::create (get_window ());
279         }
280 }
281
282 void
283 RotatedLabelSet::on_unrealize()
284 {
285         _gdk_window.clear ();
286
287         Gtk::Widget::on_unrealize ();
288 }
289
290
291 /** Set up our Pango layout to plot a given string, and compute its dimensions once
292  *  it has been rotated.
293  *  @param s String to use.
294  *  @return width and height of the rotated string, in pixels.
295  */
296
297 std::pair<int, int>
298 RotatedLabelSet::setup_layout (std::string const & s)
299 {
300         _pango_layout->set_text (s);
301
302         /* Here's the unrotated size */
303         int w;
304         int h;
305         _pango_layout->get_pixel_size (w, h);
306
307         /* Rotate the width and height as appropriate.  I thought Pango might be able
308            to do this for us, but I can't find out how... */
309         std::pair<int, int> d;
310         d.first = int (w * cos (_angle_radians) - h * sin (_angle_radians));
311         d.second = int (w * sin (_angle_radians) + h * cos (_angle_radians));
312
313         return d;
314 }
315
316 bool
317 RotatedLabelSet::on_expose_event (GdkEventExpose* event)
318 {
319         if (!_gdk_window) {
320                 return true;
321         }
322
323         int const height = get_allocation().get_height ();
324         double const spacing = double (_base_width) / _port_group_list.n_ports();
325
326         /* Plot all the labels; really we should clip for efficiency */
327         int n = 0;
328         for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
329                 for (uint32_t j = 0; j < i->ports.size(); ++j) {
330                         std::pair<int, int> const d = setup_layout (i->ports[j]);
331                         get_window()->draw_layout (_gc, int ((n + 0.25) * spacing), height - d.second, _pango_layout, _fg_colour, _bg_colour);
332                         ++n;
333                 }
334         }
335
336         return true;
337 }
338
339 /** Set the `base width'.  This is the width of the base of the label set, ie:
340  *
341  *     L L L L
342  *    E E E E
343  *   B B B B
344  *  A A A A
345  * L L L L
346  * <--w-->
347  */
348     
349 void
350 RotatedLabelSet::set_base_width (int w)
351 {
352         _base_width = w;
353         queue_resize ();
354 }
355
356
357 /** Construct an IOSelector.
358  *  @param session Session to operate on.
359  *  @param io IO to operate on.
360  *  @param for_input true if the selector is for an input, otherwise false.
361  */
362  
363 IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input)
364         : _port_group_list (session, io, for_input), _io (io), _for_input (for_input),
365           _row_labels_vbox (0), _column_labels (_port_group_list), _left_vbox_pad (0)
366 {
367         _left_vbox.pack_start (*Gtk::manage (new Gtk::Label ("")));
368         _overall_hbox.pack_start (_left_vbox, false, false);
369         _scrolled_window.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER);
370         _scrolled_window.set_shadow_type (Gtk::SHADOW_NONE);
371         Gtk::VBox* b = new Gtk::VBox;
372         b->pack_start (_column_labels, false, false);
373         b->pack_start (_port_group_hbox, false, false);
374         Gtk::Alignment* a = new Gtk::Alignment (0, 1, 0, 0);
375         a->add (*Gtk::manage (b));
376         _scrolled_window.add (*Gtk::manage (a));
377         _overall_hbox.pack_start (_scrolled_window);
378         pack_start (_overall_hbox);
379
380         _port_group_hbox.signal_size_allocate().connect (sigc::hide (sigc::mem_fun (*this, &IOSelector::setup_dimensions)));
381
382         /* Listen for ports changing on the IO */
383         if (_for_input) {
384                 _io->input_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
385         } else {
386                 _io->output_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
387         }
388         
389 }
390
391 IOSelector::~IOSelector ()
392 {
393         clear ();
394 }
395
396 /** Clear out the things that change when the number of source or destination ports changes */
397 void
398 IOSelector::clear ()
399 {
400         for (std::vector<Gtk::EventBox*>::iterator i = _row_labels.begin(); i != _row_labels.end(); ++i) {
401                 delete *i;
402         }
403         _row_labels.clear ();
404         
405         if (_row_labels_vbox) {
406                 _left_vbox.remove (*_row_labels_vbox);
407         }
408         delete _row_labels_vbox;
409         _row_labels_vbox = 0;
410
411         if (_left_vbox_pad) {
412                 _left_vbox.remove (*_left_vbox_pad);
413         }
414         delete _left_vbox_pad;
415         _left_vbox_pad = 0;
416         
417         for (std::vector<PortGroupTable*>::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) {
418                 _port_group_hbox.remove ((*i)->get_widget());
419                 delete *i;
420         }
421
422         _port_group_tables.clear ();
423 }
424
425
426 /** Set up dimensions of some of our widgets which depend on other dimensions
427  *  within the dialogue.
428  */
429 void
430 IOSelector::setup_dimensions ()
431 {
432         /* Get some dimensions from various places */
433         int const scrollbar_height = _scrolled_window.get_hscrollbar()->get_height();
434
435         std::pair<int, int> unit_size (0, 0);
436         int port_group_tables_height = 0;
437         for (std::vector<PortGroupTable*>::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) {
438                 std::pair<int, int> const u = (*i)->unit_size ();
439                 unit_size.first = std::max (unit_size.first, u.first);
440                 unit_size.second = std::max (unit_size.second, u.second);
441                 port_group_tables_height = std::max (
442                         port_group_tables_height, (*i)->get_widget().get_height()
443                         );
444         }
445
446         /* Column labels */
447         _column_labels.set_base_width (_port_group_list.n_ports () * unit_size.first);
448
449         /* Scrolled window */
450         /* XXX: really shouldn't set a minimum horizontal size here, but if we don't
451            the window starts up very small */
452         _scrolled_window.set_size_request (
453                 std::min (_column_labels.get_width(), 640),
454                 _column_labels.get_height() + port_group_tables_height + scrollbar_height + 16
455                 );
456         
457         /* Row labels */
458         for (std::vector<Gtk::EventBox*>::iterator i = _row_labels.begin(); i != _row_labels.end(); ++i) {
459                 (*i)->get_child()->set_size_request (-1, unit_size.second);
460         }
461
462
463         if (_left_vbox_pad) {
464                 _left_vbox_pad->set_size_request (-1, scrollbar_height + unit_size.second / 4);
465         }
466 }
467
468
469 /** Set up the dialogue */
470 void
471 IOSelector::setup ()
472 {
473         clear ();
474
475         /* Work out how many rows we have */
476         ARDOUR::DataType const t = _io->default_type();
477
478         int rows;
479         if (_for_input) {
480                 rows = _io->n_inputs().get(t);
481         } else {
482                 rows = _io->n_outputs().get(t);
483         }       
484         
485         /* Row labels */
486         _row_labels_vbox = new Gtk::VBox;
487         for (int i = 0; i < rows; ++i) {
488                 Gtk::Label* label = new Gtk::Label (_for_input ? _io->input(i)->name() : _io->output(i)->name());
489                 Gtk::EventBox* b = new Gtk::EventBox;
490                 b->set_events (Gdk::BUTTON_PRESS_MASK);
491                 b->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::row_label_button_pressed), i));
492                 b->add (*Gtk::manage (label));
493                 _row_labels.push_back (b);
494                 _row_labels_vbox->pack_start (*b, false, false);
495         }
496         _left_vbox.pack_start (*_row_labels_vbox, false, false);
497         _left_vbox_pad = new Gtk::Label ("");
498         _left_vbox.pack_start (*_left_vbox_pad, false, false);
499
500         /* Checkbutton tables */
501         int n = 0;
502         for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
503                 PortGroupTable* t = new PortGroupTable (*i, _io, _for_input);
504
505                 /* XXX: this is a bit of a hack; should probably use a configurable colour here */
506                 Gdk::Color alt_bg = get_style()->get_bg (Gtk::STATE_NORMAL);
507                 alt_bg.set_rgb (alt_bg.get_red() + 4096, alt_bg.get_green() + 4096, alt_bg.get_blue () + 4096);
508                 if ((n % 2) == 0) {
509                         t->get_widget().modify_bg (Gtk::STATE_NORMAL, alt_bg);
510                 }
511
512                 _port_group_tables.push_back (t);
513                 _port_group_hbox.pack_start (t->get_widget(), false, false);
514                 ++n;
515         }
516
517         show_all ();
518 }
519
520 void
521 IOSelector::ports_changed (ARDOUR::IOChange change, void *src)
522 {
523         ENSURE_GUI_THREAD (bind (mem_fun (*this, &IOSelector::ports_changed), change, src));
524
525         redisplay ();
526 }
527
528
529 void
530 IOSelector::redisplay ()
531 {
532         _port_group_list.refresh ();
533         setup ();
534 }
535
536
537 /** Handle a button press on a row label */
538 bool
539 IOSelector::row_label_button_pressed (GdkEventButton* e, int r)
540 {
541         if (e->type != GDK_BUTTON_PRESS || e->button != 3) {
542                 return false;
543         }
544
545         Gtk::Menu* menu = Gtk::manage (new Gtk::Menu);
546         Gtk::Menu_Helpers::MenuList& items = menu->items ();
547         menu->set_name ("ArdourContextMenu");
548
549         bool can_add;
550         bool can_remove;
551         std::string name;
552         ARDOUR::DataType const t = _io->default_type();
553
554         if (_for_input) {
555                 can_add = _io->input_maximum().get(t) > _io->n_inputs().get(t);
556                 can_remove = _io->input_minimum().get(t) < _io->n_inputs().get(t);
557                 name = _io->input(r)->name();
558         } else {
559                 can_add = _io->output_maximum().get(t) > _io->n_outputs().get(t);
560                 can_remove = _io->output_minimum().get(t) < _io->n_outputs().get(t);
561                 name = _io->output(r)->name();
562         }
563         
564         items.push_back (
565                 Gtk::Menu_Helpers::MenuElem (_("Add port"), sigc::mem_fun (*this, &IOSelector::add_port))
566                 );
567
568         items.back().set_sensitive (can_add);
569
570         items.push_back (
571                 Gtk::Menu_Helpers::MenuElem (_("Remove port '") + name + _("'"), sigc::bind (sigc::mem_fun (*this, &IOSelector::remove_port), r))
572                 );
573
574         items.back().set_sensitive (can_remove);
575
576         menu->popup (e->button, e->time);
577         
578         return true;
579 }
580
581 void
582 IOSelector::add_port ()
583 {
584         // The IO selector only works for single typed IOs
585         const ARDOUR::DataType t = _io->default_type ();
586
587         if (_for_input) {
588
589                 try {
590                         _io->add_input_port ("", this);
591                 }
592
593                 catch (ARDOUR::AudioEngine::PortRegistrationFailure& err) {
594                         Gtk::MessageDialog msg (0,  _("There are no more JACK ports available."));
595                         msg.run ();
596                 }
597
598         } else {
599
600                 try {
601                         _io->add_output_port ("", this);
602                 }
603
604                 catch (ARDOUR::AudioEngine::PortRegistrationFailure& err) {
605                         Gtk::MessageDialog msg (0, _("There are no more JACK ports available."));
606                         msg.run ();
607                 }
608         }
609 }
610
611 void
612 IOSelector::remove_port (int r)
613 {
614         // The IO selector only works for single typed IOs
615         const ARDOUR::DataType t = _io->default_type ();
616         
617         if (_for_input) {
618                 _io->remove_input_port (_io->input (r), this);
619         } else {
620                 _io->remove_output_port (_io->output (r), this);
621         }
622 }
623
624
625 PortGroupList::PortGroupList (ARDOUR::Session & session, boost::shared_ptr<ARDOUR::IO> io, bool for_input)
626         : _session (session), _io (io), _for_input (for_input)
627 {
628         refresh ();
629 }
630
631 void
632 PortGroupList::refresh ()
633 {
634         clear ();
635
636         /* Find the ports provided by ardour; we can't derive their type just from their
637            names, so we'll have to be more devious. */
638
639         boost::shared_ptr<ARDOUR::Session::RouteList> routes = _session.get_routes ();
640
641         PortGroup buss ("ardour:");
642         PortGroup track ("ardour:");
643
644         for (ARDOUR::Session::RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) {
645
646                 PortGroup* g = 0;
647                 if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::AudioTrack*> ((*i).get())) {
648                         /* Audio track for an audio IO */
649                         g = &track;
650                 } else if (_io->default_type() == ARDOUR::DataType::MIDI && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get())) {
651                         /* Midi track for a MIDI IO */
652                         g = &track;
653                 } else if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get()) == 0) {
654                         /* Non-MIDI track for an Audio IO; must be an audio buss */
655                         g = &buss;
656                 }
657
658                 if (g) {
659                         ARDOUR::PortSet const & p = _for_input ? ((*i)->outputs()) : ((*i)->inputs());
660                         for (uint32_t j = 0; j < p.num_ports(); ++j) {
661                                 g->add (p.port(j)->name ());
662                         }
663
664                         std::sort (g->ports.begin(), g->ports.end());
665                 }
666         }
667         
668
669         /* XXX: inserts, sends, plugin inserts? */
670         
671         /* Now we need to find the non-ardour ports; we do this by first
672            finding all the ports that we can connect to. */
673         const char **ports = _session.engine().get_ports (
674                 "", _io->default_type().to_jack_type(), _for_input ? JackPortIsOutput : JackPortIsInput
675                 );
676
677         PortGroup system ("system:");
678         PortGroup other ("");
679         
680         if (ports) {
681
682                 int n = 0;
683                 while (ports[n]) {
684                         std::string const p = ports[n];
685
686                         if (p.substr(0, strlen ("system:")) == "system:") {
687                                 /* system: prefix */
688                                 system.add (p);
689                         } else {
690                                 if (p.substr(0, strlen("ardour:")) != "ardour:") {
691                                         /* other (non-ardour) prefix */
692                                         other.add (p);
693                                 }
694                         }
695
696                         ++n;
697                 }
698         }
699
700         push_back (buss);
701         push_back (track);
702         push_back (system);
703         push_back (other);
704 }
705
706 int
707 PortGroupList::n_ports () const
708 {
709         int n = 0;
710         
711         for (const_iterator i = begin(); i != end(); ++i) {
712                 for (std::vector<std::string>::const_iterator j = i->ports.begin(); j != i->ports.end(); ++j) {
713                         ++n;
714                 }
715         }
716
717         return n;
718 }
719
720 std::string
721 PortGroupList::get_port_by_index (int n, bool with_prefix) const
722 {
723         /* XXX: slightly inefficient algorithm */
724
725         for (const_iterator i = begin(); i != end(); ++i) {
726                 for (std::vector<std::string>::const_iterator j = i->ports.begin(); j != i->ports.end(); ++j) {
727                         if (n == 0) {
728                                 if (with_prefix) {
729                                         return i->prefix + *j;
730                                 } else {
731                                         return *j;
732                                 }
733                         }
734                         --n;
735                 }
736         }
737
738         return "";
739 }
740
741
742 IOSelectorWindow::IOSelectorWindow (
743         ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input, bool can_cancel
744         )
745         : ArdourDialog ("I/O selector"),
746           _selector (session, io, for_input),
747           ok_button (can_cancel ? _("OK"): _("Close")),
748           cancel_button (_("Cancel")),
749           rescan_button (_("Rescan"))
750
751 {
752         add_events (Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
753         set_name ("IOSelectorWindow2");
754
755         string title;
756         if (for_input) {
757                 title = string_compose(_("%1 input"), io->name());
758         } else {
759                 title = string_compose(_("%1 output"), io->name());
760         }
761
762         ok_button.set_name ("IOSelectorButton");
763         if (!can_cancel) {
764                 ok_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CLOSE, Gtk::ICON_SIZE_BUTTON)));
765         }
766         cancel_button.set_name ("IOSelectorButton");
767         rescan_button.set_name ("IOSelectorButton");
768         rescan_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::REFRESH, Gtk::ICON_SIZE_BUTTON)));
769
770         get_action_area()->pack_start (rescan_button, false, false);
771
772         if (can_cancel) {
773                 cancel_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CANCEL, Gtk::ICON_SIZE_BUTTON)));
774                 get_action_area()->pack_start (cancel_button, false, false);
775         } else {
776                 cancel_button.hide();
777         }
778                 
779         get_action_area()->pack_start (ok_button, false, false);
780
781         get_vbox()->set_spacing (8);
782         get_vbox()->pack_start (_selector);
783
784         ok_button.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::accept));
785         cancel_button.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::cancel));
786         rescan_button.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::rescan));
787
788         set_title (title);
789         set_position (Gtk::WIN_POS_MOUSE);
790
791         show_all ();
792
793         signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window *> (this)));
794 }
795
796 IOSelectorWindow::~IOSelectorWindow()
797 {
798         
799 }
800
801 void
802 IOSelectorWindow::rescan ()
803 {
804         _selector.redisplay ();
805 }
806
807 void
808 IOSelectorWindow::cancel ()
809 {
810         _selector.Finished (IOSelector::Cancelled);
811         hide ();
812 }
813
814 void
815 IOSelectorWindow::accept ()
816 {
817         _selector.Finished (IOSelector::Accepted);
818         hide ();
819 }
820
821 void
822 IOSelectorWindow::on_map ()
823 {
824         _selector.redisplay ();
825         Window::on_map ();
826 }
827
828
829 PortInsertUI::PortInsertUI (ARDOUR::Session& sess, boost::shared_ptr<ARDOUR::PortInsert> pi)
830         : input_selector (sess, pi->io(), true),
831           output_selector (sess, pi->io(), false)
832 {
833         hbox.pack_start (output_selector, true, true);
834         hbox.pack_start (input_selector, true, true);
835
836         pack_start (hbox);
837 }
838
839 void
840 PortInsertUI::redisplay ()
841 {
842         input_selector.redisplay();
843         output_selector.redisplay();
844 }
845
846 void
847 PortInsertUI::finished (IOSelector::Result r)
848 {
849         input_selector.Finished (r);
850         output_selector.Finished (r);
851 }
852
853
854 PortInsertWindow::PortInsertWindow (ARDOUR::Session& sess, boost::shared_ptr<ARDOUR::PortInsert> pi, bool can_cancel)
855         : ArdourDialog ("port insert dialog"),
856           _portinsertui (sess, pi),
857           ok_button (can_cancel ? _("OK"): _("Close")),
858           cancel_button (_("Cancel")),
859           rescan_button (_("Rescan"))
860 {
861
862         set_name ("IOSelectorWindow");
863         string title = _("ardour: ");
864         title += pi->name();
865         set_title (title);
866         
867         ok_button.set_name ("IOSelectorButton");
868         if (!can_cancel) {
869                 ok_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CLOSE, Gtk::ICON_SIZE_BUTTON)));
870         }
871         cancel_button.set_name ("IOSelectorButton");
872         rescan_button.set_name ("IOSelectorButton");
873         rescan_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::REFRESH, Gtk::ICON_SIZE_BUTTON)));
874
875         get_action_area()->pack_start (rescan_button, false, false);
876         if (can_cancel) {
877                 cancel_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CANCEL, Gtk::ICON_SIZE_BUTTON)));
878                 get_action_area()->pack_start (cancel_button, false, false);
879         } else {
880                 cancel_button.hide();
881         }
882         get_action_area()->pack_start (ok_button, false, false);
883
884         get_vbox()->pack_start (_portinsertui);
885
886         ok_button.signal_clicked().connect (mem_fun (*this, &PortInsertWindow::accept));
887         cancel_button.signal_clicked().connect (mem_fun (*this, &PortInsertWindow::cancel));
888         rescan_button.signal_clicked().connect (mem_fun (*this, &PortInsertWindow::rescan));
889
890         signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window *> (this))); 
891
892         going_away_connection = pi->GoingAway.connect (mem_fun (*this, &PortInsertWindow::plugin_going_away));
893 }
894
895 void
896 PortInsertWindow::plugin_going_away ()
897 {
898         ENSURE_GUI_THREAD (mem_fun (*this, &PortInsertWindow::plugin_going_away));
899         
900         going_away_connection.disconnect ();
901         delete_when_idle (this);
902 }
903
904 void
905 PortInsertWindow::on_map ()
906 {
907         _portinsertui.redisplay ();
908         Window::on_map ();
909 }
910
911
912 void
913 PortInsertWindow::rescan ()
914 {
915         _portinsertui.redisplay ();
916 }
917
918 void
919 PortInsertWindow::cancel ()
920 {
921         _portinsertui.finished (IOSelector::Cancelled);
922         hide ();
923 }
924
925 void
926 PortInsertWindow::accept ()
927 {
928         _portinsertui.finished (IOSelector::Accepted);
929         hide ();
930 }