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