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