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