add GUI to configure/connect sidechain
[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 "gtkmm2ext/utils.h"
25 #include "gtkmm2ext/rgb_macros.h"
26
27 #include "io_selector.h"
28 #include "plugin_pin_dialog.h"
29 #include "gui_thread.h"
30 #include "ui_config.h"
31
32 #include "i18n.h"
33
34 using namespace ARDOUR;
35 using namespace PBD;
36 using namespace std;
37 using namespace Gtk;
38 using namespace Gtkmm2ext;
39
40 PluginPinDialog::PluginPinDialog (boost::shared_ptr<ARDOUR::PluginInsert> pi)
41         : ArdourWindow (string_compose (_("Pin Configuration: %1"), pi->name ()))
42         , _ind_strict_io (_("Strict I/O"))
43         , _ind_customized (_("Customized"))
44         , _rst_config (_("Configuration"))
45         , _rst_mapping (_("Connections"))
46         , _tgl_sidechain (_("Enable"))
47         , _edt_sidechain (_("Connect"))
48         , _add_plugin (_("+"))
49         , _del_plugin (_("-"))
50         , _add_output_audio (_("+"))
51         , _del_output_audio (_("-"))
52         , _add_output_midi (_("+"))
53         , _del_output_midi (_("-"))
54         , _pi (pi)
55         , _pin_box_size (8)
56         , _min_width (300)
57         , _position_valid (false)
58         , _ignore_updates (false)
59 {
60         assert (pi->owner ()); // Route
61
62         _pi->PluginIoReConfigure.connect (
63                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
64                         );
65
66         _pi->PluginMapChanged.connect (
67                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
68                         );
69
70         _pi->PluginConfigChanged.connect (
71                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
72                         );
73
74         // needs a better way: insensitive indicators
75         _ind_strict_io.set_sensitive (false); // track status
76         _ind_strict_io.set_name ("rude solo");
77         _ind_customized.set_sensitive (false); // plugin status
78         _ind_customized.set_name ("rude solo");
79
80         Label* l;
81         int r = 0;
82         Table* t = manage (new Table (4, 3));
83         t->set_border_width (0);
84         t->set_spacings (4);
85
86         l = manage (new Label (_("Track/Bus:"), ALIGN_END));
87         t->attach (*l, 0, 1, r, r + 1);
88         l = manage (new Label ());
89         l->set_alignment (ALIGN_START);
90         l->set_padding (2, 1);
91         l->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
92         l->set_width_chars (24);
93         l->set_max_width_chars (24);
94         l->set_text (_route ()->name ());
95         t->attach (*l, 1, 3, r, r + 1);
96         ++r;
97
98         l = manage (new Label (_("Plugin:"), ALIGN_END));
99         t->attach (*l, 0, 1, r, r + 1);
100         l = manage (new Label ());
101         l->set_alignment (ALIGN_START);
102         l->set_padding (2, 1);
103         l->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
104         l->set_width_chars (24);
105         l->set_max_width_chars (24);
106         l->set_text (pi->name ());
107         t->attach (*l, 1, 3, r, r + 1);
108         ++r;
109
110         l = manage (new Label (_("Status:"), ALIGN_END));
111         t->attach (*l, 0, 1, r, r + 1);
112         t->attach (_ind_strict_io, 1, 2, r, r + 1, FILL, SHRINK);
113         t->attach (_ind_customized, 2, 3, r, r + 1, FILL, SHRINK);
114         ++r;
115
116         l = manage (new Label (_("Reset:"), ALIGN_END));
117         t->attach (*l, 0, 1, r, r + 1);
118         t->attach (_rst_mapping, 1, 2, r, r + 1, FILL, SHRINK);
119         t->attach (_rst_config, 2, 3, r, r + 1, FILL, SHRINK);
120         ++r;
121
122         l = manage (new Label (_("Sidechain:"), ALIGN_END));
123         t->attach (*l, 0, 1, r, r + 1);
124         t->attach (_tgl_sidechain, 1, 2, r, r + 1, FILL, SHRINK);
125         t->attach (_edt_sidechain, 2, 3, r, r + 1, FILL, SHRINK);
126         ++r;
127
128         l = manage (new Label (_("Instances:"), ALIGN_END));
129         t->attach (*l, 0, 1, r, r + 1);
130         t->attach (_add_plugin, 1, 2, r, r + 1, SHRINK, SHRINK);
131         t->attach (_del_plugin, 2, 3, r, r + 1, SHRINK, SHRINK);
132         ++r;
133
134         l = manage (new Label (_("Audio Out:"), ALIGN_END));
135         t->attach (*l, 0, 1, r, r + 1);
136         t->attach (_add_output_audio, 1, 2, r, r + 1, SHRINK, SHRINK);
137         t->attach (_del_output_audio, 2, 3, r, r + 1, SHRINK, SHRINK);
138         ++r;
139
140         l = manage (new Label (_("Midi Out:"), ALIGN_END));
141         t->attach (*l, 0, 1, r, r + 1);
142         t->attach (_add_output_midi, 1, 2, r, r + 1, SHRINK, SHRINK);
143         t->attach (_del_output_midi, 2, 3, r, r + 1, SHRINK, SHRINK);
144         ++r;
145
146         HBox* hbox = manage (new HBox);
147         hbox->pack_start (darea, true, true);
148         hbox->pack_start (*t, false, true);
149
150         VBox* vbox = manage (new VBox);
151         vbox->pack_start (*hbox, true, true);
152         add (*vbox);
153         vbox->show_all ();
154
155         plugin_reconfigured ();
156
157         darea.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
158         darea.signal_size_request ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_request));
159         darea.signal_size_allocate ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_allocate));
160         darea.signal_expose_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_expose_event));
161         darea.signal_button_press_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_press_event));
162         darea.signal_button_release_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_release_event));
163         darea.signal_motion_notify_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_motion_notify_event));
164
165         _tgl_sidechain.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::toggle_sidechain));
166         _edt_sidechain.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::connect_sidechain));
167
168         _rst_mapping.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::reset_mapping));
169         _rst_config.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::reset_configuration));
170         _add_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_plugin_clicked), true));
171         _del_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_plugin_clicked), false));
172
173         _add_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::AUDIO));
174         _del_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::AUDIO));
175         _add_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::MIDI));
176         _del_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::MIDI));
177 }
178
179 PluginPinDialog::~PluginPinDialog ()
180 {
181 }
182
183 void
184 PluginPinDialog::plugin_reconfigured ()
185 {
186         if (_ignore_updates) {
187                 return;
188         }
189         _n_plugins = _pi->get_count ();
190         _pi->configured_io (_in, _out);
191         _ins = _pi->internal_streams (); // with sidechain
192         _sinks = _pi->natural_input_streams ();
193         _sources = _pi->natural_output_streams ();
194
195         _del_plugin.set_sensitive (_n_plugins > 1);
196         _del_output_audio.set_sensitive (_out.n_audio () > 0 && _out.n_total () > 1);
197         _del_output_midi.set_sensitive (_out.n_midi () > 0 && _out.n_total () > 1);
198         _ind_strict_io.set_active (_pi->strict_io ());
199         _ind_customized.set_active (_pi->custom_cfg ());
200         _tgl_sidechain.set_active (_pi->has_sidechain ());
201         _edt_sidechain.set_sensitive (_pi->has_sidechain ()); // && _session
202
203         // calc minimum width
204         const uint32_t max_ports = std::max (_ins.n_total (), _out.n_total ());
205         const uint32_t max_pins = std::max ((_sinks * _n_plugins).n_total (), (_sources * _n_plugins).n_total ());
206         uint32_t min_width = std::max (25 * max_ports, (uint32_t)(20 + _pin_box_size) * max_pins);
207         min_width = std::max (min_width, 64 * _n_plugins); // bxh2 = 18 ; aspect 16:9 (incl. 10% space)
208         min_width = std::max ((uint32_t)300, min_width);
209         min_width = 10 * ceilf (min_width / 10.f);
210         if (min_width != _min_width) {
211                 _min_width = min_width;
212                 darea.queue_resize ();
213         }
214
215         update_elements ();
216 }
217
218 void
219 PluginPinDialog::update_elements ()
220 {
221         _elements.clear ();
222         _hover.reset ();
223         _actor.reset ();
224         _selection.reset ();
225
226         for (uint32_t i = 0; i < _ins.n_total (); ++i) {
227                 int id = (i < _ins.n_midi ()) ? i : i - _ins.n_midi ();
228                 uint32_t sidechain = i >= _in.n_total () ? -1 : 0;
229                 _elements.push_back (CtrlWidget (Input, (i < _ins.n_midi () ? DataType::MIDI : DataType::AUDIO), id, sidechain));
230         }
231
232         for (uint32_t i = 0; i < _out.n_total (); ++i) {
233                 int id = (i < _out.n_midi ()) ? i : i - _out.n_midi ();
234                 _elements.push_back (CtrlWidget (Output, (i < _out.n_midi () ? DataType::MIDI : DataType::AUDIO), id));
235         }
236
237         for (uint32_t n = 0; n < _n_plugins; ++n) {
238                 for (uint32_t i = 0; i < _sinks.n_total (); ++i) {
239                         _elements.push_back (CtrlWidget (Sink, (i < _sinks.n_midi () ? DataType::MIDI : DataType::AUDIO), i, n));
240                 }
241                 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
242                         _elements.push_back (CtrlWidget (Source, (i < _sources.n_midi () ? DataType::MIDI : DataType::AUDIO), i, n));
243                 }
244         }
245         _position_valid = false;
246         darea.queue_draw ();
247 }
248
249 void
250 PluginPinDialog::update_element_pos ()
251 {
252         /* layout sizes */
253         const double yc   = rint (_height * .5);
254         const double bxh2 = 18;
255         const double bxw  = rint ((_width * .9) / ((_n_plugins) + .2 * (_n_plugins - 1)));
256         const double bxw2 = rint (bxw * .5);
257         const double y_in = 40;
258         const double y_out = _height - 40;
259
260         _pin_box_size = rint (max (6., 8. * UIConfiguration::instance ().get_ui_scale ()));
261         const double dx = ceil (_pin_box_size * .5);
262
263         for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
264                 switch (i->e->ct) {
265                         case Input:
266                                 {
267                                         uint32_t idx = i->e->id;
268                                         if (i->e->dt == DataType::AUDIO) { idx += _ins.n_midi (); }
269                                         i->x = rint ((idx + 1) * _width / (1. + _ins.n_total ())) - 0.5 - dx;
270                                         i->y = y_in - 25;
271                                         i->w = 10;
272                                         i->h = 25;
273                                 }
274                                 break;
275                         case Output:
276                                 {
277                                         uint32_t idx = i->e->id;
278                                         if (i->e->dt == DataType::AUDIO) { idx += _out.n_midi (); }
279                                         i->x = rint ((idx + 1) * _width / (1. + _out.n_total ())) - 0.5 - dx;
280                                         i->y = y_out;
281                                         i->w = 10;
282                                         i->h = 25;
283                                 }
284                                 break;
285                         case Sink:
286                                 {
287                                         const double x0 = rint ((i->e->ip + .5) * _width / (double)(_n_plugins)) - .5 - bxw2;
288                                         i->x = rint (x0 + (i->e->id + 1) * bxw / (1. + _sinks.n_total ())) - .5 - _pin_box_size * .5;
289                                         i->y = yc - bxh2 - _pin_box_size;
290                                         i->w = _pin_box_size + 1;
291                                         i->h = _pin_box_size;
292                                 }
293                                 break;
294                         case Source:
295                                 {
296                                         const double x0 = rint ((i->e->ip + .5) * _width / (double)(_n_plugins)) - .5 - bxw2;
297                                         i->x = rint (x0 + (i->e->id + 1) * bxw / (1. + _sources.n_total ())) - .5 - _pin_box_size * .5;
298                                         i->y = yc + bxh2;
299                                         i->w = _pin_box_size + 1;
300                                         i->h = _pin_box_size;
301                                 }
302                                 break;
303                 }
304         }
305
306 }
307
308 void
309 PluginPinDialog::set_color (cairo_t* cr, bool midi)
310 {
311         // see also gtk2_ardour/processor_box.cc
312         static const uint32_t audio_port_color = 0x4A8A0EFF; // Green
313         static const uint32_t midi_port_color = 0x960909FF; //Red
314
315         if (midi) {
316                 cairo_set_source_rgb (cr,
317                                 UINT_RGBA_R_FLT (midi_port_color),
318                                 UINT_RGBA_G_FLT (midi_port_color),
319                                 UINT_RGBA_B_FLT (midi_port_color));
320         } else {
321                 cairo_set_source_rgb (cr,
322                                 UINT_RGBA_R_FLT (audio_port_color),
323                                 UINT_RGBA_G_FLT (audio_port_color),
324                                 UINT_RGBA_B_FLT (audio_port_color));
325         }
326 }
327
328 void
329 PluginPinDialog::draw_io_pin (cairo_t* cr, const CtrlWidget& w)
330 {
331         const double dir = (w.e->ct == Input) ? 1 : -1;
332         const double dx = ceil (_pin_box_size * .5);
333         const double dy = min (36.0, 6. * dx);
334
335         cairo_move_to (cr, w.x + dx, w.y + ((w.e->ct == Input) ? 25 : 0));
336         cairo_rel_line_to (cr,     -dx, -dx * dir);
337         cairo_rel_line_to (cr,      0., -dy * dir);
338         cairo_rel_line_to (cr, 2. * dx,        0.);
339         cairo_rel_line_to (cr,      0.,  dy * dir);
340         cairo_close_path  (cr);
341
342
343         if (w.e->ip != 0) {
344                 assert (w.e->ct == Input);
345                 // side-chain
346                 cairo_set_source_rgb (cr, .1, .6, .7);
347         } else {
348                 set_color (cr, w.e->dt == DataType::MIDI);
349         }
350         if (w.e == _selection || w.e == _actor) {
351                 cairo_fill_preserve (cr);
352                 cairo_set_source_rgba (cr, 0.9, 0.9, 1.0, 0.6);
353         } else if (w.prelight) {
354                 cairo_fill_preserve (cr);
355                 cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 0.3);
356         }
357         cairo_fill_preserve (cr);
358
359         cairo_set_line_width (cr, 1.0);
360         cairo_set_source_rgb (cr, 0, 0, 0);
361         cairo_stroke (cr);
362 }
363
364 void
365 PluginPinDialog::draw_plugin_pin (cairo_t* cr, const CtrlWidget& w)
366 {
367         cairo_rectangle (cr, w.x, w.y, w.w, w.h);
368         set_color (cr, w.e->dt == DataType::MIDI);
369         if (w.e == _selection || w.e == _actor) {
370                 cairo_fill_preserve (cr);
371                 cairo_set_source_rgba (cr, 0.9, 0.9, 1.0, 0.6);
372         } else if (w.prelight) {
373                 cairo_fill_preserve (cr);
374                 cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 0.3);
375         }
376         cairo_fill (cr);
377 }
378
379 double
380 PluginPinDialog::pin_x_pos (uint32_t i, double x0, double width, uint32_t n_total, uint32_t n_midi, bool midi)
381 {
382         if (!midi) { i += n_midi; }
383         return rint (x0 + (i + 1) * width / (1. + n_total)) - .5;
384 }
385
386 void
387 PluginPinDialog::draw_connection (cairo_t* cr, double x0, double x1, double y0, double y1, bool midi, bool dashed)
388 {
389         const double bz = 2 * _pin_box_size;
390         const double bc = dashed ? 1.25 * _pin_box_size : 0;
391
392         cairo_move_to (cr, x0, y0);
393         cairo_curve_to (cr, x0 - bc, y0 + bz, x1 - bc, y1 - bz, x1, y1);
394         cairo_set_line_width (cr, 3.0);
395         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
396         cairo_set_source_rgb (cr, 1, 0, 0);
397         if (dashed) {
398                 const double dashes[] = { 5, 7 };
399                 cairo_set_dash (cr, dashes, 2, 0);
400         }
401         set_color (cr, midi);
402         cairo_stroke (cr);
403         if (dashed) {
404                 cairo_set_dash (cr, 0, 0, 0);
405         }
406 }
407
408 bool
409 PluginPinDialog::darea_expose_event (GdkEventExpose* ev)
410 {
411         Gtk::Allocation a = darea.get_allocation ();
412         double const width = a.get_width ();
413         double const height = a.get_height ();
414
415         if (!_position_valid) {
416                 _width = width;
417                 _height = height;
418                 update_element_pos ();
419                 _position_valid = true;
420         }
421
422         cairo_t* cr = gdk_cairo_create (darea.get_window ()->gobj ());
423         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
424         cairo_clip (cr);
425
426         Gdk::Color const bg = get_style ()->get_bg (STATE_NORMAL);
427         cairo_set_source_rgb (cr, bg.get_red_p (), bg.get_green_p (), bg.get_blue_p ());
428         cairo_rectangle (cr, 0, 0, width, height);
429         cairo_fill (cr);
430
431         /* layout sizes  -- TODO consolidate w/ update_element_pos() */
432         // i/o pins
433         const double y_in = 40;
434         const double y_out = height - 40;
435
436         // plugin box(es)
437         const double yc   = rint (height * .5);
438         const double bxh2 = 18;
439         const double bxw  = rint ((width * .9) / ((_n_plugins) + .2 * (_n_plugins - 1)));
440         const double bxw2 = rint (bxw * .5);
441
442         const uint32_t pc_in = _ins.n_total ();
443         const uint32_t pc_in_midi = _ins.n_midi ();
444         const uint32_t pc_out = _out.n_total ();
445         const uint32_t pc_out_midi = _out.n_midi ();
446
447         /* draw midi-bypass (behind) */
448         if (_pi->has_midi_bypass ()) {
449                 double x0 = rint (width / (1. + pc_in)) - .5;
450                 double x1 = rint (width / (1. + pc_out)) - .5;
451                 draw_connection (cr, x0, x1, y_in, y_out, true, true);
452         }
453
454         /* plugins & connection wires */
455         for (uint32_t i = 0; i < _n_plugins; ++i) {
456                 double x0 = rint ((i + .5) * width / (double)(_n_plugins)) - .5;
457
458                 /* plugin box */
459                 cairo_set_source_rgb (cr, .3, .3, .3);
460                 rounded_rectangle (cr, x0 - bxw2, yc - bxh2, bxw, 2 * bxh2, 7);
461                 cairo_fill (cr);
462
463                 const ChanMapping::Mappings in_map = _pi->input_map (i).mappings ();
464                 const ChanMapping::Mappings out_map = _pi->output_map (i).mappings ();
465
466                 for (ChanMapping::Mappings::const_iterator t = in_map.begin (); t != in_map.end (); ++t) {
467                         bool is_midi = t->first == DataType::MIDI;
468                         for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
469                                 uint32_t pn = (*c).first; // pin
470                                 uint32_t pb = (*c).second;
471                                 double c_x0 = pin_x_pos (pb, 0, width, pc_in, pc_in_midi, is_midi);
472                                 double c_x1 = pin_x_pos (pn, x0 - bxw2, bxw, _sinks.n_total (), _sinks.n_midi (), is_midi);
473                                 draw_connection (cr, c_x0, c_x1, y_in, yc - bxh2 - _pin_box_size, is_midi);
474                         }
475                 }
476
477                 for (ChanMapping::Mappings::const_iterator t = out_map.begin (); t != out_map.end (); ++t) {
478                         bool is_midi = t->first == DataType::MIDI;
479                         for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
480                                 uint32_t pn = (*c).first; // pin
481                                 uint32_t pb = (*c).second;
482                                 double c_x0 = pin_x_pos (pn, x0 - bxw2, bxw, _sources.n_total (), _sources.n_midi (), is_midi);
483                                 double c_x1 = pin_x_pos (pb, 0, width, pc_out, pc_out_midi, is_midi);
484                                 draw_connection (cr, c_x0, c_x1, yc + bxh2 + _pin_box_size, y_out, is_midi);
485                         }
486                 }
487         }
488
489         /* pins and ports */
490         for (CtrlElemList::const_iterator i = _elements.begin (); i != _elements.end (); ++i) {
491                 switch (i->e->ct) {
492                         case Input:
493                         case Output:
494                                 draw_io_pin (cr, *i);
495                                 break;
496                         case Sink:
497                         case Source:
498                                 draw_plugin_pin (cr, *i);
499                                 break;
500                 }
501         }
502
503         cairo_destroy (cr);
504         return true;
505 }
506
507 void
508 PluginPinDialog::darea_size_request (Gtk::Requisition* req)
509 {
510         req->width = _min_width;
511         req->height = 200;
512 }
513
514 void
515 PluginPinDialog::darea_size_allocate (Gtk::Allocation&)
516 {
517         _position_valid = false;
518 }
519
520 bool
521 PluginPinDialog::darea_motion_notify_event (GdkEventMotion* ev)
522 {
523         bool changed = false;
524         _hover.reset ();
525         for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
526                 if (ev->x >= i->x && ev->x <= i->x + i->w
527                                 && ev->y >= i->y && ev->y <= i->y + i->h)
528                 {
529                         if (!i->prelight) changed = true;
530                         i->prelight = true;
531                         _hover = i->e;
532                 } else {
533                         if (i->prelight) changed = true;
534                         i->prelight = false;
535                 }
536         }
537         if (changed) {
538                 darea.queue_draw ();
539         }
540         return true;
541 }
542
543 bool
544 PluginPinDialog::darea_button_press_event (GdkEventButton* ev)
545 {
546         if (ev->type != GDK_BUTTON_PRESS) {
547                 return false;
548         }
549
550         switch (ev->button) {
551                 case 1:
552                         if (!_selection || (_selection && !_hover)) {
553                                 _selection = _hover;
554                                 _actor.reset ();
555                                 darea.queue_draw ();
556                         } else if (_selection && _hover && _selection != _hover) {
557                                 if (_selection->dt != _hover->dt) { _actor.reset (); }
558                                 else if (_selection->ct == Input && _hover->ct == Sink) { _actor = _hover; }
559                                 else if (_selection->ct == Sink && _hover->ct == Input) { _actor = _hover; }
560                                 else if (_selection->ct == Output && _hover->ct == Source) { _actor = _hover; }
561                                 else if (_selection->ct == Source && _hover->ct == Output) { _actor = _hover; }
562                                 if (!_actor) {
563                                 _selection = _hover;
564                                 }
565                                 darea.queue_draw ();
566                         }
567                 case 3:
568                         if (_hover) {
569                         }
570                         break;
571                 default:
572                         break;
573         }
574
575         return true;
576 }
577
578 bool
579 PluginPinDialog::darea_button_release_event (GdkEventButton* ev)
580 {
581         if (_hover == _actor && _actor) {
582                 assert (_selection);
583                 assert (_selection->dt == _actor->dt);
584                 if      (_selection->ct == Input && _actor->ct == Sink) {
585                         handle_input_action (_actor, _selection);
586                 }
587                 else if (_selection->ct == Sink && _actor->ct == Input) {
588                         handle_input_action (_selection, _actor);
589                 }
590                 else if (_selection->ct == Output && _actor->ct == Source) {
591                         handle_output_action (_actor, _selection);
592                 }
593                 else if (_selection->ct == Source && _actor->ct == Output) {
594                         handle_output_action (_selection, _actor);
595                 }
596                 _selection.reset ();
597         }
598         _actor.reset ();
599         darea.queue_draw ();
600         return true;
601 }
602
603 void
604 PluginPinDialog::handle_input_action (const CtrlElem &s, const CtrlElem &i)
605 {
606         const int pc = s->ip;
607         bool valid;
608         ChanMapping in_map (_pi->input_map (pc));
609         uint32_t idx = in_map.get (s->dt, s->id, &valid);
610
611         if (valid && idx == i->id) {
612                 // disconnect
613                 in_map.unset (s->dt, s->id);
614                 _pi->set_input_map (pc, in_map);
615         }
616         else if (!valid) {
617                 // connect
618                 in_map.set (s->dt, s->id, i->id);
619                 _pi->set_input_map (pc, in_map);
620         }
621         else {
622                 // reconnect
623                 in_map.unset (s->dt, s->id);
624                 in_map.set (s->dt, s->id, i->id);
625                 _pi->set_input_map (pc, in_map);
626         }
627 }
628
629 void
630 PluginPinDialog::handle_output_action (const CtrlElem &s, const CtrlElem &o)
631 {
632         const uint32_t pc = s->ip;
633         bool valid;
634         ChanMapping out_map (_pi->output_map (pc));
635         uint32_t idx = out_map.get (s->dt, s->id, &valid);
636
637         if (valid && idx == o->id) {
638                 // disconnect
639                 out_map.unset (s->dt, s->id);
640                 _pi->set_output_map (pc, out_map);
641         }
642         else {
643                 // disconnect source
644                 if (valid) {
645                         out_map.unset (s->dt, s->id);
646                 }
647                 // disconnect other outputs
648                 _ignore_updates = true;
649                 for (uint32_t n = 0; n < _n_plugins; ++n) {
650                         if (n == pc) {
651                                 continue;
652                         }
653                         ChanMapping n_out_map (_pi->output_map (n));
654                         idx = n_out_map.get_src (s->dt, o->id, &valid);
655                         if (valid) {
656                                 n_out_map.unset (s->dt, idx);
657                                 _pi->set_output_map (n, n_out_map);
658                         }
659                 }
660                 _ignore_updates = false;
661                 idx = out_map.get_src (s->dt, o->id, &valid);
662                 if (valid) {
663                         out_map.unset (s->dt, idx);
664                 }
665                 // connect
666                 out_map.set (s->dt, s->id, o->id);
667                 _pi->set_output_map (pc, out_map);
668         }
669 }
670
671 void
672 PluginPinDialog::toggle_sidechain ()
673 {
674         _route ()->add_remove_sidechain (_pi, !_pi->has_sidechain ());
675 }
676
677 void
678 PluginPinDialog::connect_sidechain ()
679 {
680         if (!_session) { return; }
681         // TODO non-modal would be cooler ... :)
682         SideChainUI sc (*this, _session, _pi->sidechain_input ());
683         sc.run ();
684 }
685
686 void
687 PluginPinDialog::reset_configuration ()
688 {
689         _route ()->reset_plugin_insert (_pi);
690 }
691
692 void
693 PluginPinDialog::reset_mapping ()
694 {
695         _pi->reset_map ();
696 }
697
698 void
699 PluginPinDialog::add_remove_plugin_clicked (bool add)
700 {
701         ChanCount out = _out;
702         assert (add || _n_plugins > 0);
703         _route ()->customize_plugin_insert (_pi, _n_plugins + (add ? 1 : -1),  out);
704 }
705
706 void
707 PluginPinDialog::add_remove_port_clicked (bool add, ARDOUR::DataType dt)
708 {
709         ChanCount out = _out;
710         assert (add || out.get (dt) > 0);
711         out.set (dt, out.get (dt) + (add ? 1 : -1));
712         _route ()->customize_plugin_insert (_pi, _n_plugins, out);
713 }
714
715 SideChainUI::SideChainUI  (Gtk::Window& p, Session* session, boost::shared_ptr<IO> sc)
716         : ArdourDialog (p, string (_("Sidechain ")) + sc->name (), true)
717 {
718         HBox* hbox = manage (new HBox);
719         IOSelector * io = Gtk::manage (new IOSelector (this, session, sc));
720         hbox->pack_start (*io, true, true);
721         get_vbox ()->pack_start (*hbox, true, true);
722         io->show ();
723         hbox->show ();
724 }