fix connection updates
[ardour.git] / gtk2_ardour / plugin_pin_dialog.cc
1 /*
2  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2011 Paul Davis
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19
20 #include <gtkmm/table.h>
21 #include <gtkmm/box.h>
22 #include <gtkmm/label.h>
23
24 #include "pbd/replace_all.h"
25
26 #include "gtkmm2ext/utils.h"
27 #include "gtkmm2ext/rgb_macros.h"
28
29 #include "ardour/audioengine.h"
30 #include "ardour/plugin.h"
31 #include "ardour/port.h"
32 #include "ardour/session.h"
33
34 #include "plugin_pin_dialog.h"
35 #include "gui_thread.h"
36 #include "tooltips.h"
37 #include "ui_config.h"
38
39 #include "i18n.h"
40
41 using namespace ARDOUR;
42 using namespace PBD;
43 using namespace std;
44 using namespace Gtk;
45 using namespace Gtkmm2ext;
46
47 PluginPinDialog::PluginPinDialog (boost::shared_ptr<ARDOUR::PluginInsert> pi)
48         : ArdourWindow (string_compose (_("Pin Configuration: %1"), pi->name ()))
49         , _rst_config (_("Reset"))
50         , _rst_mapping (_("Reset"))
51         , _tgl_sidechain (_("Side Chain"))
52         , _add_plugin (_("+"))
53         , _del_plugin (_("-"))
54         , _add_output_audio (_("+"))
55         , _del_output_audio (_("-"))
56         , _add_output_midi (_("+"))
57         , _del_output_midi (_("-"))
58         , _add_sc_audio (_("A+"))
59         , _add_sc_midi (_("M+"))
60         , _pi (pi)
61         , _pin_box_size (10)
62         , _width (0)
63         , _height (0)
64         , _innerwidth (0)
65         , _margin_x (28)
66         , _margin_y (40)
67         , _min_width (300)
68         , _min_height (200)
69         , _n_inputs (0)
70         , _n_sidechains (0)
71         , _position_valid (false)
72         , _ignore_updates (false)
73         , _sidechain_selector (0)
74 {
75         assert (pi->owner ()); // Route
76
77         _pi->PluginIoReConfigure.connect (
78                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
79                         );
80
81         _pi->PluginMapChanged.connect (
82                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
83                         );
84
85         _pi->PluginConfigChanged.connect (
86                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
87                         );
88
89         _pin_box_size = 2 * ceil (max (8., 10. * UIConfiguration::instance ().get_ui_scale ()) * .5);
90         _margin_x = 2 * ceil (max (24., 28. * UIConfiguration::instance ().get_ui_scale ()) * .5);
91         _margin_y = 2 * ceil (max (36., 40. * UIConfiguration::instance ().get_ui_scale ()) * .5);
92
93         _tgl_sidechain.set_name ("pinrouting sidechain");
94
95         _pm_size_group  = SizeGroup::create (SIZE_GROUP_BOTH);
96         _add_plugin.set_tweaks (ArdourButton::Square);
97         _del_plugin.set_tweaks (ArdourButton::Square);
98         _pm_size_group->add_widget (_add_plugin);
99         _pm_size_group->add_widget (_del_plugin);
100         _pm_size_group->add_widget (_add_output_audio);
101         _pm_size_group->add_widget (_del_output_audio);
102         _pm_size_group->add_widget (_add_output_midi);
103         _pm_size_group->add_widget (_del_output_midi);
104
105         _sc_size_group  = SizeGroup::create (SIZE_GROUP_BOTH);
106         _sc_size_group->add_widget (_add_sc_audio);
107         _sc_size_group->add_widget (_add_sc_midi);
108
109         Label* l;
110         Gtk::Separator *sep;
111         int r = 0;
112         Table* tl = manage (new Table (9, 2));
113         tl->set_border_width (0);
114         tl->set_spacings (2);
115
116         Table* tr = manage (new Table (4, 3));
117         tr->set_border_width (0);
118         tr->set_spacings (2);
119
120         /* left side table */
121         l = manage (new Label (_("<b>Config</b>"), ALIGN_CENTER));
122         l->set_use_markup ();
123         tl->attach (*l, 0, 2, r, r + 1, FILL, SHRINK);
124         ++r;
125         tl->attach (_rst_config, 0, 2, r, r + 1, FILL, SHRINK);
126         ++r;
127
128         sep = manage (new HSeparator ());
129         tl->attach (*sep, 0, 2, r, r + 1, FILL|EXPAND, FILL|EXPAND, 0, 4);
130         ++r;
131
132         l = manage (new Label (_("Instances"), ALIGN_CENTER));
133         tl->attach (*l, 0, 2, r, r + 1, FILL, SHRINK);
134         ++r;
135         tl->attach (_add_plugin, 0, 1, r, r + 1, SHRINK, SHRINK);
136         tl->attach (_del_plugin, 1, 2, r, r + 1, SHRINK, SHRINK);
137         ++r;
138
139         l = manage (new Label (_("Audio Out"), ALIGN_CENTER));
140         tl->attach (*l, 0, 2, r, r + 1, FILL, SHRINK);
141         ++r;
142         tl->attach (_add_output_audio, 0, 1, r, r + 1, SHRINK, SHRINK);
143         tl->attach (_del_output_audio, 1, 2, r, r + 1, SHRINK, SHRINK);
144         ++r;
145
146         l = manage (new Label (_("Midi Out"), ALIGN_CENTER));
147         tl->attach (*l, 0, 2, r, r + 1, FILL, SHRINK);
148         ++r;
149         tl->attach (_add_output_midi, 0, 1, r, r + 1, SHRINK, SHRINK);
150         tl->attach (_del_output_midi, 1, 2, r, r + 1, SHRINK, SHRINK);
151         ++r;
152
153         /* right side table */
154         r = 0;
155         l = manage (new Label (_("<b>Connections</b>"), ALIGN_CENTER));
156         l->set_use_markup ();
157         tr->attach (*l, 0, 2, r, r + 1, FILL, SHRINK);
158         ++r;
159         tr->attach (_rst_mapping, 0, 2, r, r + 1, FILL, SHRINK);
160         ++r;
161
162         sep = manage (new HSeparator ());
163         tr->attach (*sep, 0, 2, r, r + 1, FILL|EXPAND, SHRINK, 0, 4);
164         ++r;
165
166         tr->attach (_tgl_sidechain, 0, 2, r, r + 1, FILL, SHRINK);
167         ++r;
168
169         _sidechain_tbl = manage (new Gtk::Table ());
170         _sidechain_tbl->set_spacings (2);
171         tr->attach (*_sidechain_tbl, 0, 2, r, r + 1, EXPAND|FILL, EXPAND|FILL, 0, 2);
172         ++r;
173
174         tr->attach (_add_sc_audio, 0, 1, r, r + 1, FILL, SHRINK);
175         tr->attach (_add_sc_midi, 1, 2, r, r + 1, FILL, SHRINK);
176         ++r;
177
178         HBox* hbox = manage (new HBox);
179         hbox->set_spacing (4);
180         hbox->pack_start (*tl, false, false);
181         hbox->pack_start (darea, true, true);
182         hbox->pack_start (*tr, false, false);
183
184         VBox* vbox = manage (new VBox);
185         vbox->pack_start (*hbox, true, true);
186         set_border_width (4);
187         add (*vbox);
188         vbox->show_all ();
189
190         plugin_reconfigured ();
191
192         darea.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
193         darea.signal_size_request ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_request));
194         darea.signal_size_allocate ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_allocate));
195         darea.signal_expose_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_expose_event));
196         darea.signal_button_press_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_press_event));
197         darea.signal_button_release_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_release_event));
198         darea.signal_motion_notify_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_motion_notify_event));
199
200         _tgl_sidechain.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::toggle_sidechain));
201
202         _rst_mapping.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::reset_mapping));
203         _rst_config.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::reset_configuration));
204         _add_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_plugin_clicked), true));
205         _del_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_plugin_clicked), false));
206
207         _add_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::AUDIO));
208         _del_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::AUDIO));
209         _add_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::MIDI));
210         _del_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::MIDI));
211         _add_sc_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_sidechain_port), DataType::AUDIO));
212         _add_sc_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_sidechain_port), DataType::MIDI));
213
214         AudioEngine::instance()->PortConnectedOrDisconnected.connect (
215                         _io_connection, invalidator (*this), boost::bind (&PluginPinDialog::port_connected_or_disconnected, this, _1, _3), gui_context ()
216                         );
217 }
218
219 PluginPinDialog::~PluginPinDialog ()
220 {
221         delete _sidechain_selector;
222 }
223
224 void
225 PluginPinDialog::plugin_reconfigured ()
226 {
227         ENSURE_GUI_THREAD (*this, &PluginPinDialog::plugin_reconfigured);
228         if (_ignore_updates) {
229                 return;
230         }
231         _n_plugins = _pi->get_count ();
232         _pi->configured_io (_in, _out);
233         _ins = _pi->internal_streams (); // with sidechain
234         _sinks = _pi->natural_input_streams ();
235         _sources = _pi->natural_output_streams ();
236
237         _del_plugin.set_sensitive (_n_plugins > 1);
238         _del_output_audio.set_sensitive (_out.n_audio () > 0 && _out.n_total () > 1);
239         _del_output_midi.set_sensitive (_out.n_midi () > 0 && _out.n_total () > 1);
240         _tgl_sidechain.set_active (_pi->has_sidechain ());
241         _add_sc_audio.set_sensitive (_pi->has_sidechain ());
242         _add_sc_midi.set_sensitive (_pi->has_sidechain ());
243
244         if (_pi->custom_cfg ()) {
245                 _rst_config.set_name ("pinrouting custom");
246         } else {
247                 _rst_config.set_name ("generic button");
248         }
249
250         if (!_pi->has_sidechain () && _sidechain_selector) {
251                 delete _sidechain_selector;
252                 _sidechain_selector = 0;
253         }
254
255         refill_sidechain_table ();
256
257         /* update elements */
258
259         _elements.clear ();
260         _hover.reset ();
261         _actor.reset ();
262         _selection.reset ();
263
264         _n_inputs = _n_sidechains = 0;
265
266         for (uint32_t i = 0; i < _ins.n_total (); ++i) {
267                 DataType dt = i < _ins.n_midi () ? DataType::MIDI : DataType::AUDIO;
268                 uint32_t id = dt == DataType::MIDI ? i : i - _ins.n_midi ();
269                 bool sidechain = id >= _in.get (dt) ? true : false;
270                 if (sidechain) {
271                         ++_n_sidechains;
272                 } else {
273                         ++_n_inputs;
274                 }
275
276                 CtrlWidget cw (CtrlWidget (Input, dt, id, 0, sidechain));
277                 _elements.push_back (cw);
278         }
279
280         for (uint32_t i = 0; i < _out.n_total (); ++i) {
281                 int id = (i < _out.n_midi ()) ? i : i - _out.n_midi ();
282                 _elements.push_back (CtrlWidget (Output, (i < _out.n_midi () ? DataType::MIDI : DataType::AUDIO), id));
283         }
284
285         for (uint32_t n = 0; n < _n_plugins; ++n) {
286                 boost::shared_ptr<Plugin> plugin = _pi->plugin (n);
287                 for (uint32_t i = 0; i < _sinks.n_total (); ++i) {
288                         DataType dt (_sinks.n_midi () ? DataType::MIDI : DataType::AUDIO);
289                         int idx = (i < _sinks.n_midi ()) ? i : i - _sinks.n_midi ();
290                         const Plugin::IOPortDescription& iod (plugin->describe_io_port (dt, true, idx));
291                         CtrlWidget cw (CtrlWidget (Sink, dt, i, n, iod.is_sidechain));
292                         _elements.push_back (cw);
293                 }
294                 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
295                         _elements.push_back (CtrlWidget (Source, (i < _sources.n_midi () ? DataType::MIDI : DataType::AUDIO), i, n));
296                 }
297         }
298
299         /* calc minimum size */
300         const uint32_t max_ports = std::max (_ins.n_total (), _out.n_total ());
301         const uint32_t max_pins = std::max ((_sinks * _n_plugins).n_total (), (_sources * _n_plugins).n_total ());
302         uint32_t min_width = std::max (25 * max_ports, (uint32_t)(20 + _pin_box_size) * max_pins);
303         min_width = std::max (min_width, (uint32_t)ceilf (_margin_y * .45 * _n_plugins * 16. / 9.)); // 16 : 9 aspect
304         min_width = std::max ((uint32_t)300, min_width);
305
306         min_width = 50 + 10 * ceilf (min_width / 10.f);
307
308         uint32_t min_height = 3.5 * _margin_y + 2 * (_n_sidechains + 1) * _pin_box_size;
309         min_height = std::max ((uint32_t)200, min_height);
310         min_height = 4 * ceilf (min_height / 4.f);
311
312         if (min_width != _min_width || min_height != _min_height) {
313                 _min_width = min_width;
314                 _min_height = min_height;
315                 darea.queue_resize ();
316         }
317
318         _position_valid = false;
319         darea.queue_draw ();
320 }
321
322 void
323 PluginPinDialog::refill_sidechain_table ()
324 {
325         Table_Helpers::TableList& kids = _sidechain_tbl->children ();
326         for (Table_Helpers::TableList::iterator i = kids.begin (); i != kids.end ();) {
327                 i = kids.erase (i);
328         }
329         _sidechain_tbl->resize (1, 1);
330         if (!_pi->has_sidechain () && _sidechain_selector) {
331                 return;
332         }
333         boost::shared_ptr<IO> io = _pi->sidechain_input ();
334         if (!io) {
335                 return;
336         }
337
338         uint32_t r = 0;
339         PortSet& p (io->ports ());
340         bool can_remove = p.num_ports () > 1;
341         for (PortSet::iterator i = p.begin (DataType::MIDI); i != p.end (DataType::MIDI); ++i, ++r) {
342                 add_port_to_table (*i, r, can_remove);
343         }
344         for (PortSet::iterator i = p.begin (DataType::AUDIO); i != p.end (DataType::AUDIO); ++i, ++r) {
345                 add_port_to_table (*i, r, can_remove);
346         }
347         _sidechain_tbl->show_all ();
348 }
349
350 void
351 PluginPinDialog::add_port_to_table (boost::shared_ptr<Port> p, uint32_t r, bool can_remove)
352 {
353         std::string lbl;
354         std::string tip = p->name ();
355         std::vector<std::string> cns;
356         p->get_connections (cns);
357
358         // TODO proper labels, see MixerStrip::update_io_button()
359         if (cns.size () == 0) {
360                 lbl = "-";
361         } else if (cns.size () > 1) {
362                 lbl = "...";
363                 tip += " &lt;- ";
364         } else {
365                 lbl = cns[0];
366                 tip += " &lt;- ";
367                 if (lbl.find ("system:") == 0) {
368                         lbl = AudioEngine::instance ()->get_pretty_name_by_name (lbl);
369                         if (lbl.empty ()) {
370                                 lbl = cns[0].substr (7);
371                         }
372                 }
373         }
374         for (std::vector<std::string>::const_iterator i = cns.begin(); i != cns.end(); ++i) {
375                 tip += *i;
376                 tip += " ";
377         }
378
379         ArdourButton *pb = manage (new ArdourButton (lbl));
380         pb->set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE);
381         pb->set_layout_ellipsize_width (56 * PANGO_SCALE);
382         ARDOUR_UI_UTILS::set_tooltip (*pb, tip);
383         _sidechain_tbl->attach (*pb, 0, 1, r, r +1 , EXPAND|FILL, SHRINK);
384
385         pb->signal_button_press_event ().connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::sc_input_press), boost::weak_ptr<Port> (p)), false);
386         pb->signal_button_release_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::sc_input_release), false);
387
388         pb = manage (new ArdourButton ("-"));
389         _sidechain_tbl->attach (*pb, 1, 2, r, r +1 , FILL, SHRINK);
390         if (can_remove) {
391                 pb->signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::remove_port), boost::weak_ptr<Port> (p)));
392         } else {
393                 pb->set_sensitive (false);
394         }
395 }
396
397 void
398 PluginPinDialog::update_element_pos ()
399 {
400         /* layout sizes */
401         _innerwidth = _width - 2. * _margin_x;
402
403         const double yc   = rint (_height * .5);
404         const double bxh2 = rint (_margin_y * .45); // TODO grow?
405         const double bxw  = rint ((_innerwidth * .95) / ((_n_plugins) + .2 * (_n_plugins - 1)));
406         const double bxw2 = rint (bxw * .5);
407         const double y_in = _margin_y;
408         const double y_out = _height - _margin_y;
409
410         _bxw2 = bxw2;
411         _bxh2 = bxh2;
412
413         const double dx = _pin_box_size * .5;
414
415         uint32_t sc_cnt = 0;
416         for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
417                 switch (i->e->ct) {
418                         case Input:
419                                 if (i->e->sc) {
420                                         i->x = _innerwidth + _margin_x - dx;
421                                         i->y = y_in + (sc_cnt + .5) * _pin_box_size;
422                                         i->h = _pin_box_size;
423                                         i->w = 1.5 * _pin_box_size;
424                                         ++ sc_cnt;
425                                 } else {
426                                         uint32_t idx = i->e->id;
427                                         if (i->e->dt == DataType::AUDIO) { idx += _in.n_midi (); }
428                                         i->x = rint ((idx + 1) * _width / (1. + _n_inputs)) - 0.5 - dx;
429                                         i->w = _pin_box_size;
430                                         i->h = 1.5 * _pin_box_size;
431                                         i->y = y_in - i->h;
432                                 }
433                                 break;
434                         case Output:
435                                 {
436                                         uint32_t idx = i->e->id;
437                                         if (i->e->dt == DataType::AUDIO) { idx += _out.n_midi (); }
438                                         i->x = rint ((idx + 1) * _width / (1. + _out.n_total ())) - 0.5 - dx;
439                                         i->y = y_out;
440                                         i->w = _pin_box_size;
441                                         i->h = 1.5 * _pin_box_size;
442                                 }
443                                 break;
444                         case Sink:
445                                 {
446                                         const double x0 = rint ((i->e->ip + .5) * _innerwidth / (double)(_n_plugins)) - .5 - bxw2;
447                                         i->x = _margin_x + rint (x0 + (i->e->id + 1) * bxw / (1. + _sinks.n_total ())) - .5 - dx;
448                                         i->y = yc - bxh2 - dx;
449                                         i->w = _pin_box_size;
450                                         i->h = _pin_box_size;
451                                 }
452                                 break;
453                         case Source:
454                                 {
455                                         const double x0 = rint ((i->e->ip + .5) * _innerwidth / (double)(_n_plugins)) - .5 - bxw2;
456                                         i->x = _margin_x + rint (x0 + (i->e->id + 1) * bxw / (1. + _sources.n_total ())) - .5 - dx;
457                                         i->y = yc + bxh2 - dx;
458                                         i->w = _pin_box_size;
459                                         i->h = _pin_box_size;
460                                 }
461                                 break;
462                 }
463         }
464 }
465
466 void
467 PluginPinDialog::set_color (cairo_t* cr, bool midi)
468 {
469         // see also gtk2_ardour/processor_box.cc
470         static const uint32_t audio_port_color = 0x4A8A0EFF; // Green
471         static const uint32_t midi_port_color = 0x960909FF; //Red
472
473         if (midi) {
474                 cairo_set_source_rgb (cr,
475                                 UINT_RGBA_R_FLT (midi_port_color),
476                                 UINT_RGBA_G_FLT (midi_port_color),
477                                 UINT_RGBA_B_FLT (midi_port_color));
478         } else {
479                 cairo_set_source_rgb (cr,
480                                 UINT_RGBA_R_FLT (audio_port_color),
481                                 UINT_RGBA_G_FLT (audio_port_color),
482                                 UINT_RGBA_B_FLT (audio_port_color));
483         }
484 }
485
486 void
487 PluginPinDialog::draw_io_pin (cairo_t* cr, const CtrlWidget& w)
488 {
489
490         if (w.e->sc) {
491                 const double dy = w.h * .5;
492                 const double dx = w.w - dy;
493                 cairo_move_to (cr, w.x, w.y + dy);
494                 cairo_rel_line_to (cr,  dy, -dy);
495                 cairo_rel_line_to (cr,  dx,  0);
496                 cairo_rel_line_to (cr,   0,  w.h);
497                 cairo_rel_line_to (cr, -dx,  0);
498         } else {
499                 const double dir = (w.e->ct == Input) ? 1 : -1;
500                 const double dx = w.w * .5;
501                 const double dy = w.h - dx;
502
503                 cairo_move_to (cr, w.x + dx, w.y + ((w.e->ct == Input) ? w.h : 0));
504                 cairo_rel_line_to (cr,     -dx, -dx * dir);
505                 cairo_rel_line_to (cr,      0., -dy * dir);
506                 cairo_rel_line_to (cr, 2. * dx,        0.);
507                 cairo_rel_line_to (cr,      0.,  dy * dir);
508         }
509         cairo_close_path  (cr);
510
511         cairo_set_line_width (cr, 1.0);
512         cairo_set_source_rgb (cr, 0, 0, 0);
513         cairo_stroke_preserve (cr);
514
515         set_color (cr, w.e->dt == DataType::MIDI);
516
517         if (w.e->sc) {
518                 assert (w.e->ct == Input);
519                 cairo_fill_preserve (cr);
520                 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.4);
521         }
522
523         if (w.e == _selection || w.e == _actor) {
524                 cairo_fill_preserve (cr);
525                 cairo_set_source_rgba (cr, 0.9, 0.9, 1.0, 0.6);
526         } else if (w.prelight) {
527                 cairo_fill_preserve (cr);
528                 cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 0.3);
529         }
530         cairo_fill (cr);
531 }
532
533 void
534 PluginPinDialog::draw_plugin_pin (cairo_t* cr, const CtrlWidget& w)
535 {
536         const double dx = w.w * .5;
537         const double dy = w.h * .5;
538
539         cairo_move_to (cr, w.x + dx, w.y);
540         cairo_rel_line_to (cr, -dx,  dy);
541         cairo_rel_line_to (cr,  dx,  dy);
542         cairo_rel_line_to (cr,  dx, -dy);
543         cairo_close_path  (cr);
544
545         cairo_set_line_width (cr, 1.0);
546         cairo_set_source_rgb (cr, 0, 0, 0);
547         cairo_stroke_preserve (cr);
548
549         set_color (cr, w.e->dt == DataType::MIDI);
550
551         if (w.e->sc) {
552                 assert (w.e->ct == Sink);
553                 cairo_fill_preserve (cr);
554                 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.4);
555         }
556
557         if (w.e == _selection || w.e == _actor) {
558                 cairo_fill_preserve (cr);
559                 cairo_set_source_rgba (cr, 0.9, 0.9, 1.0, 0.6);
560         } else if (w.prelight) {
561                 cairo_fill_preserve (cr);
562                 cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 0.3);
563         }
564         cairo_fill (cr);
565 }
566
567 double
568 PluginPinDialog::pin_x_pos (uint32_t i, double x0, double width, uint32_t n_total, uint32_t n_midi, bool midi)
569 {
570         if (!midi) { i += n_midi; }
571         return rint (x0 + (i + 1) * width / (1. + n_total)) - .5;
572 }
573
574 const PluginPinDialog::CtrlWidget&
575 PluginPinDialog::get_io_ctrl (CtrlType ct, DataType dt, uint32_t id, uint32_t ip) const
576 {
577         for (CtrlElemList::const_iterator i = _elements.begin (); i != _elements.end (); ++i) {
578                 if (i->e->ct == ct && i->e->dt == dt && i->e->id == id && i->e->ip == ip) {
579                         return *i;
580                 }
581         }
582         fatal << string_compose (_("programming error: %1"),
583                         X_("Invalid Plugin I/O Port."))
584                 << endmsg;
585         abort (); /*NOTREACHED*/
586         static CtrlWidget screw_old_compilers (Input, DataType::NIL, 0);
587         return screw_old_compilers;
588 }
589
590 void
591 PluginPinDialog::edge_coordinates (const CtrlWidget& w, double &x, double &y)
592 {
593         switch (w.e->ct) {
594                 case Input:
595                         if (w.e->sc) {
596                                 x = w.x;
597                                 y = w.y + w.h * .5;
598                         } else {
599                                 x = w.x + w.w * .5;
600                                 y = w.y + w.h;
601                         }
602                         break;
603                 case Output:
604                         x = w.x + w.w * .5;
605                         y = w.y;
606                         break;
607                 case Sink:
608                         x = w.x + w.w * .5;
609                         y = w.y;
610                         break;
611                 case Source:
612                         x = w.x + w.w * .5;
613                         y = w.y + w.h;
614                         break;
615         }
616 }
617
618 void
619 PluginPinDialog::draw_connection (cairo_t* cr, double x0, double x1, double y0, double y1, bool midi, bool horiz, bool dashed)
620 {
621         const double bz = 2 * _pin_box_size;
622         const double bc = (dashed && x0 == x1) ? 1.25 * _pin_box_size : 0;
623
624         cairo_move_to (cr, x0, y0);
625         if (horiz) {
626                 cairo_curve_to (cr, x0 - bz, y0 + bc, x1 - bc, y1 - bz, x1, y1);
627         } else {
628                 cairo_curve_to (cr, x0 - bc, y0 + bz, x1 - bc, y1 - bz, x1, y1);
629         }
630         cairo_set_line_width (cr, 3.0);
631         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
632         cairo_set_source_rgb (cr, 1, 0, 0);
633         if (dashed) {
634                 const double dashes[] = { 5, 7 };
635                 cairo_set_dash (cr, dashes, 2, 0);
636         }
637         set_color (cr, midi);
638         cairo_stroke (cr);
639         if (dashed) {
640                 cairo_set_dash (cr, 0, 0, 0);
641         }
642 }
643
644 void
645 PluginPinDialog::draw_connection (cairo_t* cr, const CtrlWidget& w0, const CtrlWidget& w1, bool dashed)
646 {
647         double x0, x1, y0, y1;
648         edge_coordinates (w0, x0, y0);
649         edge_coordinates (w1, x1, y1);
650         assert (w0.e->dt == w1.e->dt);
651         draw_connection (cr, x0, x1, y0, y1, w0.e->dt == DataType::MIDI, w0.e->sc, dashed);
652 }
653
654
655 bool
656 PluginPinDialog::darea_expose_event (GdkEventExpose* ev)
657 {
658         Gtk::Allocation a = darea.get_allocation ();
659         double const width = a.get_width ();
660         double const height = a.get_height ();
661
662         if (!_position_valid) {
663                 _width = width;
664                 _height = height;
665                 update_element_pos ();
666                 _position_valid = true;
667         }
668
669         cairo_t* cr = gdk_cairo_create (darea.get_window ()->gobj ());
670         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
671         cairo_clip (cr);
672
673         Gdk::Color const bg = get_style ()->get_bg (STATE_NORMAL);
674         cairo_set_source_rgb (cr, bg.get_red_p (), bg.get_green_p (), bg.get_blue_p ());
675         cairo_rectangle (cr, 0, 0, width, height);
676         cairo_fill (cr);
677
678         const double yc = rint (_height * .5);
679
680         /* processor box */
681         rounded_rectangle (cr, _margin_x, _margin_y - _pin_box_size * .5, _innerwidth, _height - 2 * _margin_y + _pin_box_size, 7);
682         cairo_set_line_width (cr, 1.0);
683         cairo_set_source_rgb (cr, .1, .1, .3);
684         cairo_stroke_preserve (cr);
685         cairo_set_source_rgb (cr, .3, .3, .3);
686         cairo_fill (cr);
687
688         /* draw midi-bypass (behind) */
689         if (_pi->has_midi_bypass ()) {
690                 const CtrlWidget& cw0 = get_io_ctrl (Input, DataType::MIDI, 0);
691                 const CtrlWidget& cw1 = get_io_ctrl (Output, DataType::MIDI, 0);
692                 draw_connection (cr, cw0, cw1, true);
693         }
694
695         /* labels */
696         Glib::RefPtr<Pango::Layout> layout;
697         layout = Pango::Layout::create (get_pango_context ());
698
699         layout->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
700         layout->set_width (_height * PANGO_SCALE);
701
702         int text_width;
703         int text_height;
704
705         layout->set_text (_route ()->name ());
706         layout->get_pixel_size (text_width, text_height);
707         cairo_save (cr);
708         cairo_move_to (cr, .5 * (_margin_x - text_height), .5 * (_height + text_width));
709         cairo_rotate (cr, M_PI * -.5);
710         cairo_set_source_rgba (cr, 1., 1., 1., 1.);
711         pango_cairo_show_layout (cr, layout->gobj ());
712         cairo_new_path (cr);
713         cairo_restore (cr);
714
715         layout->set_width ((_innerwidth - 2 * _pin_box_size) * PANGO_SCALE);
716         layout->set_text (_pi->name ());
717         layout->get_pixel_size (text_width, text_height);
718         cairo_move_to (cr, _margin_x + _innerwidth - text_width - _pin_box_size * .5, _height - _margin_y - text_height);
719         cairo_set_source_rgba (cr, 1., 1., 1., 1.);
720         pango_cairo_show_layout (cr, layout->gobj ());
721
722         if (_pi->strict_io ()) {
723                 layout->set_text (_("Strict I/O"));
724                 layout->get_pixel_size (text_width, text_height);
725                 const double sx0 = _margin_x + .5 * (_innerwidth - text_width);
726                 const double sy0 = _height - 3 - text_height;
727
728                 rounded_rectangle (cr, sx0 - 2, sy0 - 1, text_width + 4, text_height + 2, 7);
729                 cairo_set_source_rgba (cr, .4, .3, .1, 1.);
730                 cairo_fill (cr);
731
732                 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
733                 cairo_move_to (cr, sx0, sy0);
734                 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
735                 pango_cairo_show_layout (cr, layout->gobj ());
736         }
737
738
739         /* plugins & connection wires */
740         for (uint32_t i = 0; i < _n_plugins; ++i) {
741                 double x0 = _margin_x + rint ((i + .5) * _innerwidth / (double)(_n_plugins)) - .5;
742
743                 /* plugin box */
744                 cairo_set_source_rgb (cr, .5, .5, .5);
745                 rounded_rectangle (cr, x0 - _bxw2, yc - _bxh2, 2 * _bxw2, 2 * _bxh2, 7);
746                 cairo_fill (cr);
747
748                 const ChanMapping::Mappings in_map = _pi->input_map (i).mappings ();
749                 const ChanMapping::Mappings out_map = _pi->output_map (i).mappings ();
750
751                 for (ChanMapping::Mappings::const_iterator t = in_map.begin (); t != in_map.end (); ++t) {
752                         for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
753                                 const CtrlWidget& cw0 = get_io_ctrl (Input, t->first, c->second);
754                                 const CtrlWidget& cw1 = get_io_ctrl (Sink, t->first, c->first, i);
755                                 draw_connection (cr, cw0, cw1);
756                         }
757                 }
758
759                 for (ChanMapping::Mappings::const_iterator t = out_map.begin (); t != out_map.end (); ++t) {
760                         for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
761                                 const CtrlWidget& cw0 = get_io_ctrl (Source, t->first, c->first, i);
762                                 const CtrlWidget& cw1 = get_io_ctrl (Output, t->first, c->second);
763                                 draw_connection (cr, cw0, cw1);
764                         }
765                 }
766         }
767
768         /* pins and ports */
769         for (CtrlElemList::const_iterator i = _elements.begin (); i != _elements.end (); ++i) {
770                 switch (i->e->ct) {
771                         case Input:
772                         case Output:
773                                 draw_io_pin (cr, *i);
774                                 break;
775                         case Sink:
776                         case Source:
777                                 draw_plugin_pin (cr, *i);
778                                 break;
779                 }
780         }
781
782         cairo_destroy (cr);
783         return true;
784 }
785
786 void
787 PluginPinDialog::darea_size_request (Gtk::Requisition* req)
788 {
789         req->width = _min_width;
790         req->height = _min_height;
791 }
792
793 void
794 PluginPinDialog::darea_size_allocate (Gtk::Allocation&)
795 {
796         _position_valid = false;
797 }
798
799 bool
800 PluginPinDialog::darea_motion_notify_event (GdkEventMotion* ev)
801 {
802         bool changed = false;
803         _hover.reset ();
804         for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
805                 if (ev->x >= i->x && ev->x <= i->x + i->w
806                                 && ev->y >= i->y && ev->y <= i->y + i->h)
807                 {
808                         if (!i->prelight) changed = true;
809                         i->prelight = true;
810                         _hover = i->e;
811                 } else {
812                         if (i->prelight) changed = true;
813                         i->prelight = false;
814                 }
815         }
816         if (changed) {
817                 darea.queue_draw ();
818         }
819         return true;
820 }
821
822 bool
823 PluginPinDialog::darea_button_press_event (GdkEventButton* ev)
824 {
825         if (ev->type != GDK_BUTTON_PRESS) {
826                 return false;
827         }
828
829         switch (ev->button) {
830                 case 1:
831                         if (!_selection || (_selection && !_hover)) {
832                                 _selection = _hover;
833                                 _actor.reset ();
834                                 darea.queue_draw ();
835                         } else if (_selection && _hover && _selection != _hover) {
836                                 if (_selection->dt != _hover->dt) { _actor.reset (); }
837                                 else if (_selection->ct == Input && _hover->ct == Sink) { _actor = _hover; }
838                                 else if (_selection->ct == Sink && _hover->ct == Input) { _actor = _hover; }
839                                 else if (_selection->ct == Output && _hover->ct == Source) { _actor = _hover; }
840                                 else if (_selection->ct == Source && _hover->ct == Output) { _actor = _hover; }
841                                 if (!_actor) {
842                                 _selection = _hover;
843                                 }
844                                 darea.queue_draw ();
845                         }
846                         break;
847                 case 3:
848                         if (_selection != _hover) {
849                                 _selection = _hover;
850                                 darea.queue_draw ();
851                         }
852                         _actor.reset ();
853                         break;
854                 default:
855                         break;
856         }
857
858         return true;
859 }
860
861 bool
862 PluginPinDialog::darea_button_release_event (GdkEventButton* ev)
863 {
864         if (_hover == _actor && _actor && ev->button == 1) {
865                 assert (_selection);
866                 assert (_selection->dt == _actor->dt);
867                 if      (_selection->ct == Input && _actor->ct == Sink) {
868                         handle_input_action (_actor, _selection);
869                 }
870                 else if (_selection->ct == Sink && _actor->ct == Input) {
871                         handle_input_action (_selection, _actor);
872                 }
873                 else if (_selection->ct == Output && _actor->ct == Source) {
874                         handle_output_action (_actor, _selection);
875                 }
876                 else if (_selection->ct == Source && _actor->ct == Output) {
877                         handle_output_action (_selection, _actor);
878                 }
879                 _selection.reset ();
880         } else if (_hover == _selection && _selection && ev->button == 3) {
881                 handle_disconnect (_selection);
882         }
883         _actor.reset ();
884         darea.queue_draw ();
885         return true;
886 }
887
888 void
889 PluginPinDialog::handle_input_action (const CtrlElem &s, const CtrlElem &i)
890 {
891         const int pc = s->ip;
892         bool valid;
893         ChanMapping in_map (_pi->input_map (pc));
894         uint32_t idx = in_map.get (s->dt, s->id, &valid);
895
896         if (valid && idx == i->id) {
897                 // disconnect
898                 in_map.unset (s->dt, s->id);
899                 _pi->set_input_map (pc, in_map);
900         }
901         else if (!valid) {
902                 // connect
903                 in_map.set (s->dt, s->id, i->id);
904                 _pi->set_input_map (pc, in_map);
905         }
906         else {
907                 // reconnect
908                 in_map.unset (s->dt, s->id);
909                 in_map.set (s->dt, s->id, i->id);
910                 _pi->set_input_map (pc, in_map);
911         }
912 }
913
914 void
915 PluginPinDialog::handle_output_action (const CtrlElem &s, const CtrlElem &o)
916 {
917         const uint32_t pc = s->ip;
918         bool valid;
919         ChanMapping out_map (_pi->output_map (pc));
920         uint32_t idx = out_map.get (s->dt, s->id, &valid);
921
922         if (valid && idx == o->id) {
923                 // disconnect
924                 out_map.unset (s->dt, s->id);
925                 _pi->set_output_map (pc, out_map);
926         }
927         else {
928                 // disconnect source
929                 if (valid) {
930                         out_map.unset (s->dt, s->id);
931                 }
932                 // disconnect other outputs
933                 _ignore_updates = true;
934                 for (uint32_t n = 0; n < _n_plugins; ++n) {
935                         if (n == pc) {
936                                 continue;
937                         }
938                         ChanMapping n_out_map (_pi->output_map (n));
939                         idx = n_out_map.get_src (s->dt, o->id, &valid);
940                         if (valid) {
941                                 n_out_map.unset (s->dt, idx);
942                                 _pi->set_output_map (n, n_out_map);
943                         }
944                 }
945                 _ignore_updates = false;
946                 idx = out_map.get_src (s->dt, o->id, &valid);
947                 if (valid) {
948                         out_map.unset (s->dt, idx);
949                 }
950                 // connect
951                 out_map.set (s->dt, s->id, o->id);
952                 _pi->set_output_map (pc, out_map);
953         }
954 }
955
956 void
957 PluginPinDialog::handle_disconnect (const CtrlElem &e)
958 {
959         _ignore_updates = true;
960         bool changed = false;
961         bool valid;
962
963         switch (e->ct) {
964                 case Input:
965                         for (uint32_t n = 0; n < _n_plugins; ++n) {
966                                 ChanMapping map (_pi->input_map (n));
967                                 for (uint32_t i = 0; i < _sinks.n_total (); ++i) {
968                                         uint32_t idx = map.get (e->dt, i, &valid);
969                                         if (valid && idx == e->id) {
970                                                 map.unset (e->dt, i);
971                                                 changed = true;
972                                         }
973                                 }
974                                 _pi->set_input_map (n, map);
975                         }
976                         break;
977                 case Sink:
978                         {
979                                 ChanMapping map (_pi->input_map (e->ip));
980                                 map.get (e->dt, e->id, &valid);
981                                 if (valid) {
982                                         map.unset (e->dt, e->id);
983                                         _pi->set_input_map (e->ip, map);
984                                         changed = true;
985                                 }
986                         }
987                         break;
988                 case Source:
989                         {
990                                 ChanMapping map (_pi->output_map (e->ip));
991                                 map.get (e->dt, e->id, &valid);
992                                 if (valid) {
993                                         map.unset (e->dt, e->id);
994                                         _pi->set_output_map (e->ip, map);
995                                         changed = true;
996                                 }
997                         }
998                         break;
999                 case Output:
1000                         for (uint32_t n = 0; n < _n_plugins; ++n) {
1001                                 ChanMapping map (_pi->output_map (n));
1002                                 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
1003                                         uint32_t idx = map.get (e->dt, i, &valid);
1004                                         if (valid && idx == e->id) {
1005                                                 map.unset (e->dt, i);
1006                                                 changed = true;
1007                                         }
1008                                 }
1009                                 _pi->set_output_map (n, map);
1010                         }
1011                         break;
1012         }
1013         _ignore_updates = false;
1014         if (changed) {
1015                 plugin_reconfigured ();
1016         }
1017 }
1018
1019 void
1020 PluginPinDialog::toggle_sidechain ()
1021 {
1022         if (_session && _session->actively_recording()) { return; }
1023         _route ()->add_remove_sidechain (_pi, !_pi->has_sidechain ());
1024 }
1025
1026 void
1027 PluginPinDialog::connect_sidechain ()
1028 {
1029         if (!_session) { return; }
1030
1031         if (_sidechain_selector == 0) {
1032                 _sidechain_selector = new IOSelectorWindow (_session, _pi->sidechain_input ());
1033         }
1034
1035         if (_sidechain_selector->is_visible ()) {
1036                 _sidechain_selector->get_toplevel ()->get_window ()->raise ();
1037         } else {
1038                 _sidechain_selector->present ();
1039         }
1040 }
1041
1042 void
1043 PluginPinDialog::reset_configuration ()
1044 {
1045         _route ()->reset_plugin_insert (_pi);
1046 }
1047
1048 void
1049 PluginPinDialog::reset_mapping ()
1050 {
1051         _pi->reset_map ();
1052 }
1053
1054 void
1055 PluginPinDialog::add_remove_plugin_clicked (bool add)
1056 {
1057         if (_session && _session->actively_recording()) { return; }
1058         ChanCount out = _out;
1059         assert (add || _n_plugins > 0);
1060         _route ()->customize_plugin_insert (_pi, _n_plugins + (add ? 1 : -1),  out);
1061 }
1062
1063 void
1064 PluginPinDialog::add_remove_port_clicked (bool add, ARDOUR::DataType dt)
1065 {
1066         if (_session && _session->actively_recording()) { return; }
1067         ChanCount out = _out;
1068         assert (add || out.get (dt) > 0);
1069         out.set (dt, out.get (dt) + (add ? 1 : -1));
1070         _route ()->customize_plugin_insert (_pi, _n_plugins, out);
1071 }
1072
1073 void
1074 PluginPinDialog::add_sidechain_port (DataType dt)
1075 {
1076         if (_session && _session->actively_recording()) { return; }
1077         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1078         if (!io) {
1079                 return;
1080         }
1081         io->add_port ("", this, dt);
1082 }
1083
1084 void
1085 PluginPinDialog::remove_port (boost::weak_ptr<ARDOUR::Port> wp)
1086 {
1087         if (_session && _session->actively_recording()) { return; }
1088         boost::shared_ptr<ARDOUR::Port> p = wp.lock ();
1089         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1090         if (!io || !p) {
1091                 return;
1092         }
1093         io->remove_port (p, this);
1094 }
1095
1096 void
1097 PluginPinDialog::disconnect_port (boost::weak_ptr<ARDOUR::Port> wp)
1098 {
1099         if (_session && _session->actively_recording()) { return; }
1100         boost::shared_ptr<ARDOUR::Port> p = wp.lock ();
1101         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1102         if (!io || !p) {
1103                 return;
1104         }
1105         p->disconnect_all ();
1106 }
1107
1108 void
1109 PluginPinDialog::connect_port (boost::weak_ptr<ARDOUR::Port> wp0, boost::weak_ptr<ARDOUR::Port> wp1)
1110 {
1111         if (_session && _session->actively_recording()) { return; }
1112         boost::shared_ptr<ARDOUR::Port> p0 = wp0.lock ();
1113         boost::shared_ptr<ARDOUR::Port> p1 = wp1.lock ();
1114         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1115         if (!io || !p0 || !p1) {
1116                 return;
1117         }
1118         p0->connect (p1->name ());
1119 }
1120
1121 bool
1122 PluginPinDialog::sc_input_release (GdkEventButton *ev)
1123 {
1124         if (_session && _session->actively_recording()) { return false; }
1125         if (ev->button == 3) {
1126                 connect_sidechain ();
1127         }
1128         return false;
1129 }
1130
1131 struct RouteCompareByName {
1132         bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
1133                 return a->name().compare (b->name()) < 0;
1134         }
1135 };
1136
1137 bool
1138 PluginPinDialog::sc_input_press (GdkEventButton *ev, boost::weak_ptr<ARDOUR::Port> wp)
1139 {
1140         using namespace Menu_Helpers;
1141         if (!_session || _session->actively_recording()) { return false; }
1142         if (!_session->engine().connected()) { return false; }
1143
1144         if (ev->button == 1) {
1145                 MenuList& citems = input_menu.items();
1146                 input_menu.set_name ("ArdourContextMenu");
1147                 citems.clear();
1148
1149                 boost::shared_ptr<Port> p = wp.lock ();
1150                 if (p && p->connected ()) {
1151                         citems.push_back (MenuElem (_("Disconnect"), sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::disconnect_port), wp)));
1152                         citems.push_back (SeparatorElem());
1153                 }
1154
1155                 // TODO add system inputs, too ?!
1156
1157                 boost::shared_ptr<ARDOUR::RouteList> routes = _session->get_routes ();
1158                 RouteList copy = *routes;
1159                 copy.sort (RouteCompareByName ());
1160                 uint32_t added = 0;
1161                 for (ARDOUR::RouteList::const_iterator i = copy.begin(); i != copy.end(); ++i) {
1162                         added += maybe_add_route_to_input_menu (*i, p->type (), wp);
1163                 }
1164
1165                 if (added > 0) {
1166                         citems.push_back (SeparatorElem());
1167                 }
1168                 citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*this, &PluginPinDialog::connect_sidechain)));
1169                 input_menu.popup (1, ev->time);
1170         }
1171         return false;
1172 }
1173
1174 uint32_t
1175 PluginPinDialog::maybe_add_route_to_input_menu (boost::shared_ptr<Route> r, DataType dt, boost::weak_ptr<Port> wp)
1176 {
1177         uint32_t added = 0;
1178         using namespace Menu_Helpers;
1179         if (r->output () == _route()->output()) {
1180                 return added;
1181         }
1182
1183         if (_route ()->feeds_according_to_graph (r)) {
1184                 return added;
1185         }
1186
1187         MenuList& citems = input_menu.items();
1188         const IOVector& iov (r->all_outputs());
1189
1190         for (IOVector::const_iterator o = iov.begin(); o != iov.end(); ++o) {
1191                 boost::shared_ptr<IO> op = o->lock();
1192                 if (!op) {
1193                         continue;
1194                 }
1195                 PortSet& p (op->ports ());
1196                 for (PortSet::iterator i = p.begin (dt); i != p.end (dt); ++i) {
1197                         std::string n = i->name ();
1198                         replace_all (n, "_", " ");
1199                         citems.push_back (MenuElem (n, sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::connect_port), wp, boost::weak_ptr<Port> (*i))));
1200                         ++added;
1201                 }
1202         }
1203         return added;
1204 }
1205
1206 void
1207 PluginPinDialog::port_connected_or_disconnected (boost::weak_ptr<ARDOUR::Port> w0, boost::weak_ptr<ARDOUR::Port> w1)
1208 {
1209         boost::shared_ptr<Port> p0 = w0.lock ();
1210         boost::shared_ptr<Port> p1 = w1.lock ();
1211
1212         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1213         if (!io) { return; }
1214
1215         if (p0 && io->has_port (p0)) {
1216                 plugin_reconfigured ();
1217         }
1218         else if (p1 && io->has_port (p1)) {
1219                 plugin_reconfigured ();
1220         }
1221 }