Fix thinkos in cubasish theme
[ardour.git] / gtk2_ardour / plugin_pin_dialog.cc
1 /*
2  * Copyright (C) 2016-2018 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 #include <boost/algorithm/string.hpp>
20
21 #include <gtkmm/box.h>
22 #include <gtkmm/frame.h>
23 #include <gtkmm/label.h>
24 #include <gtkmm/messagedialog.h>
25 #include <gtkmm/separator.h>
26 #include <gtkmm/table.h>
27
28 #include "pbd/replace_all.h"
29
30 #include "gtkmm2ext/utils.h"
31 #include "gtkmm2ext/rgb_macros.h"
32 #include "gtkmm2ext/menu_elems.h"
33
34 #include "widgets/tooltips.h"
35
36 #include "ardour/amp.h"
37 #include "ardour/audioengine.h"
38 #include "ardour/pannable.h"
39 #include "ardour/plugin.h"
40 #include "ardour/port.h"
41 #include "ardour/profile.h"
42 #include "ardour/send.h"
43 #include "ardour/session.h"
44 #include "ardour/value_as_string.h"
45
46 #include "plugin_pin_dialog.h"
47 #include "gui_thread.h"
48 #include "timers.h"
49 #include "ui_config.h"
50 #include "utils.h"
51
52 #include "pbd/i18n.h"
53
54 using namespace ARDOUR;
55 using namespace PBD;
56 using namespace std;
57 using namespace Gtk;
58 using namespace Gtkmm2ext;
59 using namespace ArdourWidgets;
60
61 PluginPinWidget::PluginPinWidget (boost::shared_ptr<ARDOUR::PluginInsert> pi)
62         : _set_config (_("Manual Config"), ArdourButton::led_default_elements)
63         , _tgl_sidechain (_("Sidechain"), ArdourButton::led_default_elements)
64         , _add_plugin (_("+"))
65         , _del_plugin (_("-"))
66         , _add_input_audio (_("+"))
67         , _del_input_audio (_("-"))
68         , _add_input_midi (_("+"))
69         , _del_input_midi (_("-"))
70         , _add_output_audio (_("+"))
71         , _del_output_audio (_("-"))
72         , _add_output_midi (_("+"))
73         , _del_output_midi (_("-"))
74         , _add_sc_audio (_("Audio"))
75         , _add_sc_midi (_("MIDI"))
76         , _pi (pi)
77         , _pin_box_size (10)
78         , _width (0)
79         , _height (0)
80         , _innerwidth (0)
81         , _margin_x (28)
82         , _margin_y (40)
83         , _min_width (300)
84         , _min_height (200)
85         , _n_inputs (0)
86         , _n_sidechains (0)
87         , _position_valid (false)
88         , _ignore_updates (false)
89         , _sidechain_selector (0)
90         , _dragging (false)
91 {
92         assert (pi->owner ()); // Route
93
94         _pi->PluginIoReConfigure.connect (
95                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinWidget::queue_idle_update, this), gui_context ()
96                         );
97
98         _pi->PluginMapChanged.connect (
99                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinWidget::queue_idle_update, this), gui_context ()
100                         );
101
102         _pi->PluginConfigChanged.connect (
103                         _plugin_connections, invalidator (*this), boost::bind (&PluginPinWidget::queue_idle_update, this), gui_context ()
104                         );
105
106         _pin_box_size = 2 * ceil (max (8., 10. * UIConfiguration::instance ().get_ui_scale ()) * .5);
107         _margin_x = 2 * ceil (max (24., 28. * UIConfiguration::instance ().get_ui_scale ()) * .5);
108         _margin_y = 2 * ceil (max (36., 40. * UIConfiguration::instance ().get_ui_scale ()) * .5);
109
110         _tgl_sidechain.set_name ("pinrouting sidechain");
111         _set_config.set_name ("pinrouting custom");
112
113         Menu_Helpers::MenuList& citems = reset_menu.items ();
114         reset_menu.set_name ("ArdourContextMenu");
115         citems.clear ();
116         citems.push_back (Menu_Helpers::MenuElem (_("Reset"), sigc::mem_fun (*this, &PluginPinWidget::reset_mapping)));
117
118         _pm_size_group  = SizeGroup::create (SIZE_GROUP_BOTH);
119         _add_plugin.set_tweaks (ArdourButton::Square);
120         _del_plugin.set_tweaks (ArdourButton::Square);
121         if (_pi->plugin (0)->get_info()->reconfigurable_io ()) {
122                 _pm_size_group->add_widget (_add_input_audio);
123                 _pm_size_group->add_widget (_del_input_audio);
124                 _pm_size_group->add_widget (_add_input_midi);
125                 _pm_size_group->add_widget (_del_input_midi);
126         } else {
127                 _pm_size_group->add_widget (_add_plugin);
128                 _pm_size_group->add_widget (_del_plugin);
129         }
130         _pm_size_group->add_widget (_add_output_audio);
131         _pm_size_group->add_widget (_del_output_audio);
132         _pm_size_group->add_widget (_add_output_midi);
133         _pm_size_group->add_widget (_del_output_midi);
134
135         Box* box;
136         Frame *f;
137
138         VBox* tl = manage (new VBox ());
139         tl->set_border_width (2);
140         tl->set_spacing (2);
141
142         VBox* tr = manage (new VBox ());
143         tr->set_border_width (2);
144         tr->set_spacing (2);
145
146         /* left side */
147         tl->pack_start (_set_config, false, false);
148
149         if (_pi->plugin (0)->get_info()->reconfigurable_io ()) {
150                 box = manage (new HBox ());
151                 box->set_border_width (2);
152                 box->pack_start (_add_input_audio, true, false);
153                 box->pack_start (_del_input_audio, true, false);
154                 f = manage (new Frame ());
155                 f->set_label (_("Audio Input Pins"));
156                 f->add (*box);
157                 tl->pack_start (*f, false, false);
158
159                 box = manage (new HBox ());
160                 box->set_border_width (2);
161                 box->pack_start (_add_input_midi, true, false);
162                 box->pack_start (_del_input_midi, true, false);
163                 f = manage (new Frame ());
164                 f->set_label (_("MIDI Input Pins"));
165                 f->add (*box);
166                 tl->pack_start (*f, false, false);
167         } else {
168                 box = manage (new HBox ());
169                 box->set_border_width (2);
170                 box->pack_start (_add_plugin, true, false);
171                 box->pack_start (_del_plugin, true, false);
172                 f = manage (new Frame ());
173                 f->set_label (_("Instances"));
174                 f->add (*box);
175                 tl->pack_start (*f, false, false);
176         }
177
178         box = manage (new HBox ());
179         box->set_border_width (2);
180         box->pack_start (_add_output_audio, true, false);
181         box->pack_start (_del_output_audio, true, false);
182         f = manage (new Frame ());
183         f->set_label (_("Audio Out"));
184         f->add (*box);
185         tl->pack_start (*f, false, false);
186
187         box = manage (new HBox ());
188         box->set_border_width (2);
189         box->pack_start (_add_output_midi, true, false);
190         box->pack_start (_del_output_midi, true, false);
191         f = manage (new Frame ());
192         f->set_label (_("MIDI Out"));
193         f->add (*box);
194         tl->pack_start (*f, false, false);
195
196         tl->pack_start (*manage (new Label ("")), true, true); // invisible separator
197         tl->pack_start (*manage (new HSeparator ()), false, false, 4);
198         _out_presets.disable_scrolling ();
199         ArdourWidgets::set_tooltip (_out_presets, _("Output Presets"));
200         tl->pack_start (_out_presets, false, false);
201
202         /* right side */
203         _sidechain_tbl = manage (new Gtk::Table ());
204         _sidechain_tbl->set_spacings (2);
205
206         tr->pack_start (_tgl_sidechain, false, false);
207         tr->pack_start (*_sidechain_tbl, true, true);
208
209         box = manage (new VBox ());
210         box->set_border_width (2);
211         box->set_spacing (2);
212         box->pack_start (_add_sc_audio, false, false);
213         box->pack_start (_add_sc_midi , false, false);
214         f = manage (new Frame ());
215         f->set_label (_("Add Sidechain Input"));
216         f->add (*box);
217
218         tr->pack_start (*f, false, false);
219
220         /* global packing */
221         HBox* hbox = manage (new HBox ());
222         hbox->set_spacing (4);
223         hbox->pack_start (*tl, false, false);
224         hbox->pack_start (darea, true, true);
225         hbox->pack_start (*tr, false, false);
226
227         pack_start (*hbox, true, true);
228         set_border_width (4);
229         show_all ();
230
231         plugin_reconfigured ();
232
233         darea.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
234         darea.signal_size_request ().connect (sigc::mem_fun (*this, &PluginPinWidget::darea_size_request));
235         darea.signal_size_allocate ().connect (sigc::mem_fun (*this, &PluginPinWidget::darea_size_allocate));
236         darea.signal_expose_event ().connect (sigc::mem_fun (*this, &PluginPinWidget::darea_expose_event));
237         darea.signal_button_press_event ().connect (sigc::mem_fun (*this, &PluginPinWidget::darea_button_press_event));
238         darea.signal_button_release_event ().connect (sigc::mem_fun (*this, &PluginPinWidget::darea_button_release_event));
239         darea.signal_motion_notify_event ().connect (sigc::mem_fun (*this, &PluginPinWidget::darea_motion_notify_event));
240
241         _tgl_sidechain.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinWidget::toggle_sidechain));
242
243         _set_config.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinWidget::reset_configuration));
244         _add_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_plugin_clicked), true));
245         _del_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_plugin_clicked), false));
246
247         _add_input_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_inpin_clicked), true, DataType::AUDIO));
248         _del_input_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_inpin_clicked), false, DataType::AUDIO));
249         _add_input_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_inpin_clicked), true, DataType::MIDI));
250         _del_input_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_inpin_clicked), false, DataType::MIDI));
251
252         _add_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_port_clicked), true, DataType::AUDIO));
253         _del_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_port_clicked), false, DataType::AUDIO));
254         _add_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_port_clicked), true, DataType::MIDI));
255         _del_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_remove_port_clicked), false, DataType::MIDI));
256
257         _add_sc_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_sidechain_port), DataType::AUDIO));
258         _add_sc_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_sidechain_port), DataType::MIDI));
259
260         AudioEngine::instance ()->PortConnectedOrDisconnected.connect (
261                         _io_connection, invalidator (*this), boost::bind (&PluginPinWidget::port_connected_or_disconnected, this, _1, _3), gui_context ()
262                         );
263 }
264
265 PluginPinWidget::~PluginPinWidget ()
266 {
267         delete _sidechain_selector;
268 }
269
270 void
271 PluginPinWidget::set_session (ARDOUR::Session *s)
272 {
273         SessionHandlePtr::set_session (s);
274         plugin_reconfigured ();
275 }
276
277 void
278 PluginPinWidget::queue_idle_update ()
279 {
280         /* various actions here are directly executed, in the GUI thread,
281          * with the GUI-thread eventually taking the process and processor lock.
282          * "connect gui_context()" will call back immediately and this
283          * signal-handler will run with the locks held.
284          *
285          * This will lead to a crash with calling nth_send() which takes
286          * a processor read-lock while holding a write lock in the same thread.
287          *
288          * decouple update to GUI idle.
289          *
290          * BUT, do delete existing controls here (in case they're affected by
291          * the change and hit by the Timer before idle comes around)
292          */
293         for (list<Control*>::iterator i = _controls.begin (); i != _controls.end (); ++i) {
294                 _sidechain_tbl->remove ((*i)->box);
295                 delete *i;
296         }
297         _controls.clear ();
298         Glib::signal_idle().connect (sigc::mem_fun(*this, &PluginPinWidget::idle_update));
299 }
300
301 bool
302 PluginPinWidget::idle_update ()
303 {
304         plugin_reconfigured ();
305         return false;
306 }
307
308 void
309 PluginPinWidget::error_message_dialog (std::string const& msg) const
310 {
311         assert (_session);
312         Gtk::MessageDialog d (
313                         _session->actively_recording () ? _("Cannot perform operation while actively recording.") : msg
314                         , false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
315         d.run();
316 }
317
318
319 void
320 PluginPinWidget::plugin_reconfigured ()
321 {
322         ENSURE_GUI_THREAD (*this, &PluginPinWidget::plugin_reconfigured);
323         if (_ignore_updates) {
324                 return;
325         }
326         _n_plugins = _pi->get_count ();
327         _pi->configured_io (_in, _out);
328         _ins = _pi->internal_streams (); // with sidechain
329         _sinks = _pi->natural_input_streams ();
330         _sources = _pi->natural_output_streams ();
331
332
333         _tgl_sidechain.set_active (_pi->has_sidechain ());
334         _add_sc_audio.set_sensitive (_pi->has_sidechain ());
335         _add_sc_midi.set_sensitive (_pi->has_sidechain ());
336
337         if (_pi->custom_cfg ()) {
338                 _set_config.set_active (true);
339                 _add_plugin.set_sensitive (true);
340                 _del_plugin.set_sensitive (_n_plugins > 1);
341                 _add_output_audio.set_sensitive (true);
342                 _add_output_midi.set_sensitive (true);
343                 _del_output_audio.set_sensitive (_out.n_audio () > 0 && _out.n_total () > 1);
344                 _del_output_midi.set_sensitive (_out.n_midi () > 0 && _out.n_total () > 1);
345                 _add_input_audio.set_sensitive (true);
346                 _add_input_midi.set_sensitive (true);
347                 _del_input_audio.set_sensitive (_sinks.n_audio () > 0 && _sinks.n_total () > 1);
348                 _del_input_midi.set_sensitive (_sinks.n_midi () > 0 && _sinks.n_total () > 1);
349                 _out_presets.set_sensitive (false);
350                 _out_presets.set_text (_("Manual"));
351         } else {
352                 _set_config.set_active (false);
353                 _add_plugin.set_sensitive (false);
354                 _del_plugin.set_sensitive (false);
355                 _add_input_audio.set_sensitive (false);
356                 _add_input_midi.set_sensitive (false);
357                 _del_input_audio.set_sensitive (false);
358                 _del_input_midi.set_sensitive (false);
359                 _add_output_audio.set_sensitive (false);
360                 _add_output_midi.set_sensitive (false);
361                 _del_output_audio.set_sensitive (false);
362                 _del_output_midi.set_sensitive (false);
363                 _out_presets.set_sensitive (true);
364                 refill_output_presets ();
365         }
366
367         if (!_pi->has_sidechain () && _sidechain_selector) {
368                 delete _sidechain_selector;
369                 _sidechain_selector = 0;
370         }
371
372         refill_sidechain_table ();
373
374         /* update elements */
375
376         _elements.clear ();
377         _hover.reset ();
378         _actor.reset ();
379         _selection.reset ();
380         _drag_dst.reset ();
381         _dragging = false;
382
383         _n_inputs = _n_sidechains = 0;
384
385         for (uint32_t i = 0; i < _ins.n_total (); ++i) {
386                 DataType dt = i < _ins.n_midi () ? DataType::MIDI : DataType::AUDIO;
387                 uint32_t id = dt == DataType::MIDI ? i : i - _ins.n_midi ();
388                 bool sidechain = id >= _in.get (dt) ? true : false;
389                 if (sidechain) {
390                         ++_n_sidechains;
391                 } else {
392                         ++_n_inputs;
393                 }
394
395                 CtrlWidget cw (CtrlWidget ("", Input, dt, id, 0, sidechain));
396                 _elements.push_back (cw);
397         }
398
399         for (uint32_t i = 0; i < _out.n_total (); ++i) {
400                 int id = (i < _out.n_midi ()) ? i : i - _out.n_midi ();
401                 _elements.push_back (CtrlWidget ("", Output, (i < _out.n_midi () ? DataType::MIDI : DataType::AUDIO), id));
402         }
403
404         _in_map.clear ();
405         _out_map.clear ();
406
407         for (uint32_t n = 0; n < _n_plugins; ++n) {
408                 boost::shared_ptr<Plugin> plugin = _pi->plugin (n);
409                 for (uint32_t i = 0; i < _sinks.n_total (); ++i) {
410                         DataType dt (i < _sinks.n_midi () ? DataType::MIDI : DataType::AUDIO);
411                         int idx = (dt == DataType::MIDI) ? i : i - _sinks.n_midi ();
412                         const Plugin::IOPortDescription& iod (plugin->describe_io_port (dt, true, idx));
413                         CtrlWidget cw (CtrlWidget (iod.name, Sink, dt, idx, n, iod.is_sidechain));
414                         _elements.push_back (cw);
415                 }
416                 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
417                         DataType dt (i < _sources.n_midi () ? DataType::MIDI : DataType::AUDIO);
418                         int idx = (dt == DataType::MIDI) ? i : i - _sources.n_midi ();
419                         const Plugin::IOPortDescription& iod (plugin->describe_io_port (dt, false, idx));
420                         _elements.push_back (CtrlWidget (iod.name, Source, dt, idx, n));
421                 }
422                 _in_map[n] = _pi->input_map (n);
423                 _out_map[n] = _pi->output_map (n);
424         }
425         _has_midi_bypass = _pi->has_midi_bypass ();
426         _thru_map = _pi->thru_map ();
427
428         /* cache maps */
429
430         /* calc minimum size */
431         const uint32_t max_ports = std::max (_ins.n_total (), _out.n_total ());
432         const uint32_t max_pins = std::max ((_sinks * _n_plugins).n_total (), (_sources * _n_plugins).n_total ());
433         uint32_t min_width = std::max (25 * max_ports, (uint32_t)(20 + _pin_box_size) * max_pins);
434         min_width = std::max (min_width, (uint32_t)ceilf (_margin_y * .45 * _n_plugins * 16. / 9.)); // 16 : 9 aspect
435         min_width = std::max ((uint32_t)300, min_width);
436
437         min_width = 50 + 10 * ceilf (min_width / 10.f);
438
439         uint32_t min_height = 3.5 * _margin_y + 2 * (_n_sidechains + 1) * _pin_box_size;
440         min_height = std::max ((uint32_t)200, min_height);
441         min_height = 4 * ceilf (min_height / 4.f);
442
443         if (min_width != _min_width || min_height != _min_height) {
444                 _min_width = min_width;
445                 _min_height = min_height;
446                 darea.queue_resize ();
447         }
448
449         _position_valid = false;
450         darea.queue_draw ();
451 }
452
453 void
454 PluginPinWidget::refill_sidechain_table ()
455 {
456         Table_Helpers::TableList& kids = _sidechain_tbl->children ();
457         for (Table_Helpers::TableList::iterator i = kids.begin (); i != kids.end ();) {
458                 i = kids.erase (i);
459         }
460         _sidechain_tbl->resize (1, 1);
461         for (list<Control*>::iterator i = _controls.begin (); i != _controls.end (); ++i) {
462                 delete *i;
463         }
464         _controls.clear ();
465         if (!_pi->has_sidechain () && _sidechain_selector) {
466                 return;
467         }
468         boost::shared_ptr<IO> io = _pi->sidechain_input ();
469         if (!io) {
470                 return;
471         }
472
473         uint32_t r = 0;
474         PortSet& p (io->ports ());
475         bool can_remove = p.num_ports () > 1;
476         for (PortSet::iterator i = p.begin (DataType::MIDI); i != p.end (DataType::MIDI); ++i) {
477                 r += add_port_to_table (*i, r, can_remove);
478         }
479         for (PortSet::iterator i = p.begin (DataType::AUDIO); i != p.end (DataType::AUDIO); ++i) {
480                 r += add_port_to_table (*i, r, can_remove);
481         }
482         _sidechain_tbl->show_all ();
483 }
484
485 void
486 PluginPinWidget::refill_output_presets ()
487 {
488         using namespace Menu_Helpers;
489         _out_presets.clear_items ();
490
491         bool need_dropdown = _pi->has_output_presets ();
492
493         if (!need_dropdown) {
494                 _out_presets.set_sensitive (false);
495                 _out_presets.set_text (_("Automatic"));
496                 return;
497         }
498
499         _out_presets.AddMenuElem (MenuElem (_("Automatic"), sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::select_output_preset), 0)));
500
501         const uint32_t n_audio = _pi->preset_out ().n_audio ();
502         if (n_audio == 0) {
503                 _out_presets.set_text (_("Automatic"));
504         }
505
506         PluginOutputConfiguration ppc (_pi->plugin (0)->possible_output ());
507         if (ppc.find (0) != ppc.end ()) {
508                 // anyting goes
509                 ppc.clear ();
510                 if (n_audio != 0) {
511                         ppc.insert (n_audio);
512                 }
513                 ppc.insert (1);
514                 ppc.insert (2);
515                 ppc.insert (8);
516                 ppc.insert (16);
517                 ppc.insert (24);
518                 ppc.insert (32);
519         }
520
521         for (PluginOutputConfiguration::const_iterator i = ppc.begin () ; i != ppc.end (); ++i) {
522                 assert (*i > 0);
523                 std::string tmp;
524                 switch (*i) {
525                         case 1:
526                                 tmp = _("Mono");
527                                 break;
528                         case 2:
529                                 tmp = _("Stereo");
530                                 break;
531                         default:
532                                 tmp = string_compose (P_("%1 Channel", "%1 Channels", *i), *i);
533                                 break;
534                 }
535                 _out_presets.AddMenuElem (MenuElem (tmp, sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::select_output_preset), *i)));
536                 if (n_audio == *i) {
537                         _out_presets.set_text (tmp);
538                 }
539         }
540 }
541
542 std::string
543 PluginPinWidget::port_label (const std::string& portname, bool strip)
544 {
545         // compare to MixerStrip::update_io_button()
546         string lpn (PROGRAM_NAME);
547         boost::to_lower (lpn);
548         std::string program_port_prefix = lpn + ":"; // e.g. "ardour:"
549
550         std::string pn = AudioEngine::instance ()->get_pretty_name_by_name (portname);
551         if (!pn.empty ()) {
552                 string::size_type slash = pn.find ("/");
553                 if (slash != string::npos) {
554                         pn = pn.substr (0, slash);
555                 }
556                 return pn;
557         }
558         std::string label (portname);
559         if (label.find ("system:capture_") == 0) {
560                 if (label.empty ()) {
561                         label = portname.substr (15);
562                 }
563         } else if (label.find ("system:midi_capture_") == 0) {
564                 if (label.empty ()) {
565                         // "system:midi_capture_123" -> "123"
566                         label = "M " + portname.substr (20);
567                 }
568         } else if (label.find (program_port_prefix) == 0) {
569                 label = label.substr (program_port_prefix.size ());
570                 if (strip) {
571                         string::size_type slash = label.find ("/");
572                         if (slash != string::npos) {
573                                 label = label.substr (0, slash);
574                         }
575                 }
576         }
577         return label;
578 }
579
580 uint32_t
581 PluginPinWidget::add_port_to_table (boost::shared_ptr<Port> p, uint32_t r, bool can_remove)
582 {
583         std::string lbl;
584         std::string tip = Gtkmm2ext::markup_escape_text (p->name ());
585         std::vector<std::string> cns;
586         bool single_source = true;
587         p->get_connections (cns);
588
589         for (std::vector<std::string>::const_iterator i = cns.begin (); i != cns.end (); ++i) {
590                 if (lbl.empty ()) {
591                         lbl = port_label (*i, true);
592                         continue;
593                 }
594                 if (port_label (*i, true) != lbl) {
595                         lbl = "...";
596                         single_source = false;
597                         break;
598                 }
599         }
600
601         if (cns.size () == 0) {
602                 lbl = "-";
603                 single_source = false;
604         } else if (cns.size () == 1) {
605                 tip += " &lt;- ";
606                 lbl = port_label (cns[0], false);
607         } else {
608                 tip += " &lt;- ";
609         }
610         replace_all (lbl, "_", " ");
611
612         for (std::vector<std::string>::const_iterator i = cns.begin (); i != cns.end (); ++i) {
613                 tip += *i;
614                 tip += " ";
615         }
616
617         ArdourButton *pb = manage (new ArdourButton (lbl));
618         pb->set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE);
619         pb->set_layout_ellipsize_width (108 * PANGO_SCALE);
620         ArdourWidgets::set_tooltip (*pb, tip);
621         _sidechain_tbl->attach (*pb, 0, 1, r, r +1 , EXPAND|FILL, SHRINK);
622
623         pb->signal_button_press_event ().connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::sc_input_press), boost::weak_ptr<Port> (p)), false);
624         pb->signal_button_release_event ().connect (sigc::mem_fun (*this, &PluginPinWidget::sc_input_release), false);
625
626         pb = manage (new ArdourButton ("-"));
627         _sidechain_tbl->attach (*pb, 1, 2, r, r + 1, FILL, SHRINK);
628         if (can_remove) {
629                 pb->signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::remove_port), boost::weak_ptr<Port> (p)));
630         } else {
631                 pb->set_sensitive (false);
632         }
633
634         uint32_t rv = 1;
635
636         if (single_source && _session) {
637                 /* check if it's an Ardour Send feeding.. */
638                 boost::shared_ptr<ARDOUR::RouteList> routes = _session->get_routes ();
639                 for (ARDOUR::RouteList::const_iterator i = routes->begin (); i != routes->end (); ++i) {
640                         uint32_t nth = 0;
641                         boost::shared_ptr<Processor> proc;
642                         /* nth_send () takes a processor read-lock */
643                         while ((proc = (*i)->nth_send (nth))) {
644                                 boost::shared_ptr<IOProcessor> send = boost::dynamic_pointer_cast<IOProcessor> (proc);
645                                 if (!send || !send->output ()) {
646                                         ++nth;
647                                         continue;
648                                 }
649                                 if (!send->output ()->connected_to (p->name ())) {
650                                         ++nth;
651                                         continue;
652                                 }
653                                 /* if processor goes away, we're notified by the port disconnect,
654                                  * there should be no need to explicily connect to proc->DropReferences
655                                  */
656                                 set<Evoral::Parameter> p = proc->what_can_be_automated ();
657                                 for (set<Evoral::Parameter>::iterator i = p.begin (); i != p.end (); ++i) {
658                                         Control* c = new Control (proc->automation_control (*i), _("Send"));
659                                         _controls.push_back (c);
660                                         ++r; ++rv;
661                                         _sidechain_tbl->attach (c->box, 0, 2, r, r + 1, EXPAND|FILL, SHRINK);
662                                 }
663                                 break;
664                         }
665                 }
666         }
667         return rv;
668 }
669
670 void
671 PluginPinWidget::update_element_pos ()
672 {
673         /* layout sizes */
674         _innerwidth = _width - 2. * _margin_x;
675
676         const double yc   = rint (_height * .5);
677         const double bxh2 = rint (_margin_y * .45); // TODO grow?
678         const double bxw  = rint ((_innerwidth * .95) / ((_n_plugins) + .2 * (_n_plugins - 1)));
679         const double bxw2 = rint (bxw * .5);
680         const double y_in = _margin_y;
681         const double y_out = _height - _margin_y;
682
683         _bxw2 = bxw2;
684         _bxh2 = bxh2;
685
686         const double dx = _pin_box_size * .5;
687
688         uint32_t sc_cnt = 0;
689         for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
690                 switch (i->e->ct) {
691                         case Input:
692                                 if (i->e->sc) {
693                                         i->x = _innerwidth + _margin_x - dx;
694                                         i->y = y_in + (sc_cnt + .5) * _pin_box_size;
695                                         i->h = _pin_box_size;
696                                         i->w = 1.5 * _pin_box_size;
697                                         ++ sc_cnt;
698                                 } else {
699                                         uint32_t idx = i->e->id;
700                                         if (i->e->dt == DataType::AUDIO) { idx += _in.n_midi (); }
701                                         i->x = rint ((idx + 1) * _width / (1. + _n_inputs)) - 0.5 - dx;
702                                         i->w = _pin_box_size;
703                                         i->h = 1.5 * _pin_box_size;
704                                         i->y = y_in - i->h;
705                                 }
706                                 break;
707                         case Output:
708                                 {
709                                         uint32_t idx = i->e->id;
710                                         if (i->e->dt == DataType::AUDIO) { idx += _out.n_midi (); }
711                                         i->x = rint ((idx + 1) * _width / (1. + _out.n_total ())) - 0.5 - dx;
712                                         i->y = y_out;
713                                         i->w = _pin_box_size;
714                                         i->h = 1.5 * _pin_box_size;
715                                 }
716                                 break;
717                         case Sink:
718                                 {
719                                         uint32_t idx = i->e->id;
720                                         if (i->e->dt == DataType::AUDIO) { idx += _sinks.n_midi (); }
721                                         const double x0 = rint ((i->e->ip + .5) * _innerwidth / (double)(_n_plugins)) - .5 - bxw2;
722                                         i->x = _margin_x + rint (x0 + (idx + 1) * bxw / (1. + _sinks.n_total ())) - .5 - dx;
723                                         i->y = yc - bxh2 - dx;
724                                         i->w = _pin_box_size;
725                                         i->h = _pin_box_size;
726                                 }
727                                 break;
728                         case Source:
729                                 {
730                                         uint32_t idx = i->e->id;
731                                         if (i->e->dt == DataType::AUDIO) { idx += _sources.n_midi (); }
732                                         const double x0 = rint ((i->e->ip + .5) * _innerwidth / (double)(_n_plugins)) - .5 - bxw2;
733                                         i->x = _margin_x + rint (x0 + (idx + 1) * bxw / (1. + _sources.n_total ())) - .5 - dx;
734                                         i->y = yc + bxh2 - dx;
735                                         i->w = _pin_box_size;
736                                         i->h = _pin_box_size;
737                                 }
738                                 break;
739                 }
740         }
741 }
742
743 void
744 PluginPinWidget::set_color (cairo_t* cr, bool midi)
745 {
746         // see also gtk2_ardour/processor_box.cc
747         static const uint32_t audio_port_color = 0x4A8A0EFF; // Green
748         static const uint32_t midi_port_color = 0x960909FF; //Red
749
750         if (midi) {
751                 cairo_set_source_rgb (cr,
752                                 UINT_RGBA_R_FLT (midi_port_color),
753                                 UINT_RGBA_G_FLT (midi_port_color),
754                                 UINT_RGBA_B_FLT (midi_port_color));
755         } else {
756                 cairo_set_source_rgb (cr,
757                                 UINT_RGBA_R_FLT (audio_port_color),
758                                 UINT_RGBA_G_FLT (audio_port_color),
759                                 UINT_RGBA_B_FLT (audio_port_color));
760         }
761 }
762
763 void
764 PluginPinWidget::draw_io_pin (cairo_t* cr, const CtrlWidget& w)
765 {
766         if (w.e->sc) {
767                 const double dy = w.h * .5;
768                 const double dx = w.w - dy;
769                 cairo_move_to (cr, w.x, w.y + dy);
770                 cairo_rel_line_to (cr,  dy, -dy);
771                 cairo_rel_line_to (cr,  dx,  0);
772                 cairo_rel_line_to (cr,   0,  w.h);
773                 cairo_rel_line_to (cr, -dx,  0);
774         } else {
775                 const double dir = (w.e->ct == Input) ? 1 : -1;
776                 const double dx = w.w * .5;
777                 const double dy = w.h - dx;
778
779                 cairo_move_to (cr, w.x + dx, w.y + ((w.e->ct == Input) ? w.h : 0));
780                 cairo_rel_line_to (cr,     -dx, -dx * dir);
781                 cairo_rel_line_to (cr,      0., -dy * dir);
782                 cairo_rel_line_to (cr, 2. * dx,        0.);
783                 cairo_rel_line_to (cr,      0.,  dy * dir);
784         }
785         cairo_close_path  (cr);
786
787         cairo_set_line_width (cr, 1.0);
788         cairo_set_source_rgb (cr, 0, 0, 0);
789         cairo_stroke_preserve (cr);
790
791         set_color (cr, w.e->dt == DataType::MIDI);
792
793         if (w.e->sc) {
794                 assert (w.e->ct == Input);
795                 cairo_fill_preserve (cr);
796                 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.4);
797         }
798
799         if (w.e == _selection || w.e == _actor) {
800                 cairo_fill_preserve (cr);
801                 cairo_set_source_rgba (cr, 0.9, 0.9, 1.0, 0.6);
802         } else if (w.prelight) {
803                 cairo_fill_preserve (cr);
804                 cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 0.3);
805         }
806         cairo_fill (cr);
807 }
808
809 void
810 PluginPinWidget::draw_plugin_pin (cairo_t* cr, const CtrlWidget& w)
811 {
812         const double dx = w.w * .5;
813         const double dy = w.h * .5;
814
815         cairo_move_to (cr, w.x + dx, w.y);
816         cairo_rel_line_to (cr, -dx,  dy);
817         cairo_rel_line_to (cr,  dx,  dy);
818         cairo_rel_line_to (cr,  dx, -dy);
819         cairo_close_path  (cr);
820
821         cairo_set_line_width (cr, 1.0);
822         cairo_set_source_rgb (cr, 0, 0, 0);
823         cairo_stroke_preserve (cr);
824
825         set_color (cr, w.e->dt == DataType::MIDI);
826
827         if (w.e->sc) {
828                 assert (w.e->ct == Sink);
829                 cairo_fill_preserve (cr);
830                 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.4);
831         }
832
833         if (w.e == _selection || w.e == _actor) {
834                 cairo_fill_preserve (cr);
835                 cairo_set_source_rgba (cr, 0.9, 0.9, 1.0, 0.6);
836         } else if (w.prelight) {
837                 cairo_fill_preserve (cr);
838                 cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 0.3);
839         }
840         cairo_fill (cr);
841
842         if ((w.prelight || w.e == _selection) && !w.name.empty ()) {
843                 int text_width;
844                 int text_height;
845                 Glib::RefPtr<Pango::Layout> layout;
846                 layout = Pango::Layout::create (get_pango_context ());
847                 layout->set_text (w.name);
848                 layout->get_pixel_size (text_width, text_height);
849
850                 rounded_rectangle (cr, w.x + dx - .5 * text_width - 2, w.y - text_height - 2,  text_width + 4, text_height + 2, 7);
851                 cairo_set_source_rgba (cr, 0, 0, 0, .5);
852                 cairo_fill (cr);
853
854                 cairo_move_to (cr, w.x + dx - .5 * text_width, w.y - text_height - 1);
855                 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
856                 pango_cairo_show_layout (cr, layout->gobj ());
857         }
858 }
859
860 double
861 PluginPinWidget::pin_x_pos (uint32_t i, double x0, double width, uint32_t n_total, uint32_t n_midi, bool midi)
862 {
863         if (!midi) { i += n_midi; }
864         return rint (x0 + (i + 1) * width / (1. + n_total)) - .5;
865 }
866
867 const PluginPinWidget::CtrlWidget&
868 PluginPinWidget::get_io_ctrl (CtrlType ct, DataType dt, uint32_t id, uint32_t ip) const
869 {
870         for (CtrlElemList::const_iterator i = _elements.begin (); i != _elements.end (); ++i) {
871                 if (i->e->ct == ct && i->e->dt == dt && i->e->id == id && i->e->ip == ip) {
872                         return *i;
873                 }
874         }
875         assert (0);
876         fatal << string_compose (_("programming error: %1"),
877                         X_("Invalid Plugin I/O Port."))
878                 << endmsg;
879         abort (); /*NOTREACHED*/
880         static CtrlWidget screw_old_compilers ("", Input, DataType::NIL, 0);
881         return screw_old_compilers;
882 }
883
884 void
885 PluginPinWidget::edge_coordinates (const CtrlWidget& w, double &x, double &y)
886 {
887         switch (w.e->ct) {
888                 case Input:
889                         if (w.e->sc) {
890                                 x = w.x;
891                                 y = w.y + w.h * .5;
892                         } else {
893                                 x = w.x + w.w * .5;
894                                 y = w.y + w.h;
895                         }
896                         break;
897                 case Output:
898                         x = w.x + w.w * .5;
899                         y = w.y;
900                         break;
901                 case Sink:
902                         x = w.x + w.w * .5;
903                         y = w.y;
904                         break;
905                 case Source:
906                         x = w.x + w.w * .5;
907                         y = w.y + w.h;
908                         break;
909         }
910 }
911
912 void
913 PluginPinWidget::draw_connection (cairo_t* cr, double x0, double x1, double y0, double y1, bool midi, bool horiz, bool dashed)
914 {
915         const double bz = 2 * _pin_box_size;
916         double bc = (dashed && x0 == x1) ? 1.25 * _pin_box_size : 0;
917         if (x0 > _width * .5) { bc *= -1; }
918
919         cairo_move_to (cr, x0, y0);
920         if (horiz) {
921                 cairo_curve_to (cr, x0 - bz, y0 + bc, x1 - bc, y1 - bz, x1, y1);
922         } else {
923                 cairo_curve_to (cr, x0 - bc, y0 + bz, x1 - bc, y1 - bz, x1, y1);
924         }
925         cairo_set_line_width (cr, 3.0);
926         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
927         cairo_set_source_rgb (cr, 1, 0, 0);
928         if (dashed) {
929                 const double dashes[] = { 5, 7 };
930                 cairo_set_dash (cr, dashes, 2, 0);
931         }
932         set_color (cr, midi);
933         cairo_stroke (cr);
934         if (dashed) {
935                 cairo_set_dash (cr, 0, 0, 0);
936         }
937 }
938
939 void
940 PluginPinWidget::draw_connection (cairo_t* cr, const CtrlWidget& w0, const CtrlWidget& w1, bool dashed)
941 {
942         double x0, x1, y0, y1;
943         edge_coordinates (w0, x0, y0);
944         edge_coordinates (w1, x1, y1);
945         assert (w0.e->dt == w1.e->dt);
946         draw_connection (cr, x0, x1, y0, y1, w0.e->dt == DataType::MIDI, w0.e->sc, dashed);
947 }
948
949
950 bool
951 PluginPinWidget::darea_expose_event (GdkEventExpose* ev)
952 {
953         Gtk::Allocation a = darea.get_allocation ();
954         double const width = a.get_width ();
955         double const height = a.get_height ();
956
957         if (!_position_valid) {
958                 _width = width;
959                 _height = height;
960                 update_element_pos ();
961                 _position_valid = true;
962         }
963
964         cairo_t* cr = gdk_cairo_create (darea.get_window ()->gobj ());
965         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
966         cairo_clip (cr);
967
968         Gdk::Color const bg = get_style ()->get_bg (STATE_NORMAL);
969         cairo_set_source_rgb (cr, bg.get_red_p (), bg.get_green_p (), bg.get_blue_p ());
970         cairo_rectangle (cr, 0, 0, width, height);
971         cairo_fill (cr);
972
973         const double yc = rint (_height * .5);
974
975         /* processor box */
976         rounded_rectangle (cr, _margin_x, _margin_y - _pin_box_size * .5, _innerwidth, _height - 2 * _margin_y + _pin_box_size, 7);
977         cairo_set_line_width (cr, 1.0);
978         cairo_set_source_rgb (cr, .1, .1, .3);
979         cairo_stroke_preserve (cr);
980         cairo_set_source_rgb (cr, .3, .3, .3);
981         cairo_fill (cr);
982
983         /* labels */
984         Glib::RefPtr<Pango::Layout> layout;
985         layout = Pango::Layout::create (get_pango_context ());
986
987         layout->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
988         layout->set_width (_height * PANGO_SCALE);
989
990         int text_width;
991         int text_height;
992
993         layout->set_text (_route ()->name ());
994         layout->get_pixel_size (text_width, text_height);
995         cairo_save (cr);
996         cairo_move_to (cr, .5 * (_margin_x - text_height), .5 * (_height + text_width));
997         cairo_rotate (cr, M_PI * -.5);
998         cairo_set_source_rgba (cr, 1., 1., 1., 1.);
999         pango_cairo_show_layout (cr, layout->gobj ());
1000         cairo_new_path (cr);
1001         cairo_restore (cr);
1002
1003         layout->set_width ((_innerwidth - 2 * _pin_box_size) * PANGO_SCALE);
1004         layout->set_text (_pi->name ());
1005         layout->get_pixel_size (text_width, text_height);
1006         cairo_move_to (cr, _margin_x + _innerwidth - text_width - _pin_box_size * .5, _height - _margin_y - text_height);
1007         cairo_set_source_rgba (cr, 1., 1., 1., 1.);
1008         pango_cairo_show_layout (cr, layout->gobj ());
1009
1010 #ifndef NDEBUG
1011         if (_pi->signal_latency () > 0 || !_pi->inplace()) {
1012                 layout->set_width ((_innerwidth - 2 * _pin_box_size) * PANGO_SCALE);
1013                 if (_pi->signal_latency () > 0 && !_pi->inplace()) {
1014                         layout->set_text (string_compose (_("Latency %1 spl%2 %3"), _pi->signal_latency (), ", ", _("no-inplace")));
1015                 } else if (_pi->signal_latency () > 0) {
1016                         layout->set_text (string_compose (_("Latency %1 spl"), _pi->signal_latency ()));
1017                 } else {
1018                         layout->set_text (_("no-inplace"));
1019                 }
1020                 layout->get_pixel_size (text_width, text_height);
1021                 cairo_move_to (cr, _margin_x + _pin_box_size * .5, _margin_y + 2);
1022                 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
1023                 pango_cairo_show_layout (cr, layout->gobj ());
1024         }
1025 #endif
1026
1027         if (_pi->strict_io () && !Profile->get_mixbus ()) {
1028                 layout->set_text (_("Strict I/O"));
1029                 layout->get_pixel_size (text_width, text_height);
1030                 const double sx0 = _margin_x + .5 * (_innerwidth - text_width);
1031                 const double sy0 = _height - 3 - text_height;
1032
1033                 rounded_rectangle (cr, sx0 - 2, sy0 - 1, text_width + 4, text_height + 2, 7);
1034                 cairo_set_source_rgba (cr, .4, .3, .1, 1.);
1035                 cairo_fill (cr);
1036
1037                 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
1038                 cairo_move_to (cr, sx0, sy0);
1039                 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
1040                 pango_cairo_show_layout (cr, layout->gobj ());
1041         }
1042
1043         /* draw midi-bypass (behind) */
1044         if (_has_midi_bypass) {
1045                 const CtrlWidget& cw0 = get_io_ctrl (Input, DataType::MIDI, 0);
1046                 const CtrlWidget& cw1 = get_io_ctrl (Output, DataType::MIDI, 0);
1047                 draw_connection (cr, cw0, cw1, true);
1048         }
1049
1050         /* thru connections */
1051         const ChanMapping::Mappings thru_map (_thru_map.mappings ());
1052         for (ChanMapping::Mappings::const_iterator t = thru_map.begin (); t != thru_map.end (); ++t) {
1053                 for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
1054                         const CtrlWidget& cw0 = get_io_ctrl (Output, t->first, c->first);
1055                         const CtrlWidget& cw1 = get_io_ctrl (Input, t->first, c->second);
1056                         if (!(_dragging && cw1.e == _selection && cw0.e == _drag_dst)) {
1057                                 draw_connection (cr, cw1, cw0, true);
1058                         }
1059                 }
1060         }
1061
1062         /* plugins & connection wires */
1063         for (uint32_t i = 0; i < _n_plugins; ++i) {
1064                 double x0 = _margin_x + rint ((i + .5) * _innerwidth / (double)(_n_plugins)) - .5;
1065
1066                 /* plugin box */
1067                 cairo_set_source_rgb (cr, .5, .5, .5);
1068                 rounded_rectangle (cr, x0 - _bxw2, yc - _bxh2, 2 * _bxw2, 2 * _bxh2, 7);
1069                 cairo_fill (cr);
1070
1071                 layout->set_width (1.9 * _bxw2 * PANGO_SCALE);
1072                 layout->set_text (string_compose (_("Instance #%1"), i + 1));
1073                 layout->get_pixel_size (text_width, text_height);
1074                 cairo_move_to (cr, x0 - text_width * .5, yc - text_height * .5);
1075                 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
1076                 pango_cairo_show_layout (cr, layout->gobj ());
1077
1078                 const ChanMapping::Mappings in_map = _in_map[i].mappings ();
1079                 const ChanMapping::Mappings out_map = _out_map[i].mappings ();
1080
1081                 for (ChanMapping::Mappings::const_iterator t = in_map.begin (); t != in_map.end (); ++t) {
1082                         for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
1083                                 const CtrlWidget& cw0 = get_io_ctrl (Input, t->first, c->second);
1084                                 const CtrlWidget& cw1 = get_io_ctrl (Sink, t->first, c->first, i);
1085                                 if (!(_dragging && cw0.e == _selection && cw1.e == _drag_dst)) {
1086                                         draw_connection (cr, cw0, cw1);
1087                                 }
1088                         }
1089                 }
1090
1091                 for (ChanMapping::Mappings::const_iterator t = out_map.begin (); t != out_map.end (); ++t) {
1092                         for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
1093                                 const CtrlWidget& cw0 = get_io_ctrl (Source, t->first, c->first, i);
1094                                 const CtrlWidget& cw1 = get_io_ctrl (Output, t->first, c->second);
1095                                 if (!(_dragging && cw0.e == _selection && cw1.e == _drag_dst)) {
1096                                         draw_connection (cr, cw0, cw1);
1097                                 }
1098                         }
1099                 }
1100         }
1101
1102         /* pins and ports */
1103         for (CtrlElemList::const_iterator i = _elements.begin (); i != _elements.end (); ++i) {
1104                 switch (i->e->ct) {
1105                         case Input:
1106                         case Output:
1107                                 draw_io_pin (cr, *i);
1108                                 break;
1109                         case Sink:
1110                         case Source:
1111                                 draw_plugin_pin (cr, *i);
1112                                 break;
1113                 }
1114         }
1115
1116         /* DnD wire */
1117         CtrlWidget *drag_src = NULL;
1118         if (_dragging) {
1119                 for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
1120                         if (i->e  == _selection ) {
1121                                 drag_src = &(*i);
1122                         }
1123                 }
1124         }
1125
1126         if (drag_src) {
1127                 double x0, y0;
1128                 if (_selection->ct == Input || _selection->ct == Source) {
1129                         edge_coordinates (*drag_src, x0, y0);
1130                         draw_connection (cr, x0, _drag_x, y0, _drag_y,
1131                                         _selection->dt == DataType::MIDI, _selection->sc);
1132                 } else {
1133                         edge_coordinates (*drag_src, x0, y0);
1134                         draw_connection (cr, _drag_x, x0, _drag_y, y0,
1135                                         _selection->dt == DataType::MIDI, _selection->sc);
1136                 }
1137         }
1138
1139         cairo_destroy (cr);
1140         return true;
1141 }
1142
1143 void
1144 PluginPinWidget::darea_size_request (Gtk::Requisition* req)
1145 {
1146         req->width = _min_width;
1147         req->height = _min_height;
1148 }
1149
1150 void
1151 PluginPinWidget::darea_size_allocate (Gtk::Allocation&)
1152 {
1153         _position_valid = false;
1154 }
1155
1156 bool
1157 PluginPinWidget::drag_type_matches (const CtrlElem& e)
1158 {
1159         if (!_dragging || !_selection) {
1160                 return true;
1161         }
1162         if (_selection->dt != e->dt) {
1163                 return false;
1164         }
1165         if (_selection->ct == Input  && e->ct == Sink)   { return true; }
1166         if (_selection->ct == Sink   && e->ct == Input)  { return true; }
1167         if (_selection->ct == Output && e->ct == Source) { return true; }
1168         if (_selection->ct == Source && e->ct == Output) { return true; }
1169         if (_selection->ct == Input  && e->ct == Output) { return true; }
1170         if (_selection->ct == Output && e->ct == Input)  { return true; }
1171         return false;
1172 }
1173
1174 void
1175 PluginPinWidget::start_drag (const CtrlElem& e, double x, double y)
1176 {
1177         assert (_selection == e);
1178         _drag_dst.reset ();
1179         if (e->ct == Sink) {
1180                 bool valid;
1181                 const ChanMapping& map (_in_map[e->ip]);
1182                 uint32_t idx = map.get (e->dt, e->id, &valid);
1183                 if (valid) {
1184                         const CtrlWidget& cw = get_io_ctrl (Input, e->dt, idx, 0);
1185                         _drag_dst = e;
1186                         _selection = cw.e;
1187                 }
1188         }
1189         else if (e->ct == Output) {
1190                 for (uint32_t i = 0; i < _n_plugins; ++i) {
1191                         bool valid;
1192                         const ChanMapping& map (_out_map[i]);
1193                         uint32_t idx = map.get_src (e->dt, e->id, &valid);
1194                         if (valid) {
1195                                 const CtrlWidget& cw = get_io_ctrl (Source, e->dt, idx, i);
1196                                 _drag_dst = e;
1197                                 _selection = cw.e;
1198                                 break;
1199                         }
1200                 }
1201                 if (!_drag_dst) {
1202                         bool valid;
1203                         const ChanMapping& map (_thru_map);
1204                         uint32_t idx = map.get (e->dt, e->id, &valid);
1205                         if (valid) {
1206                                 const CtrlWidget& cw = get_io_ctrl (Input, e->dt, idx, 0);
1207                                 _drag_dst = e;
1208                                 _selection = cw.e;
1209                         }
1210                 }
1211         }
1212         _dragging = true;
1213         _drag_x = x;
1214         _drag_y = y;
1215 }
1216
1217 bool
1218 PluginPinWidget::darea_motion_notify_event (GdkEventMotion* ev)
1219 {
1220         bool changed = false;
1221         _hover.reset ();
1222         for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
1223                 if (ev->x >= i->x && ev->x <= i->x + i->w
1224                                 && ev->y >= i->y && ev->y <= i->y + i->h
1225                                 && drag_type_matches (i->e))
1226                 {
1227                         if (!i->prelight) changed = true;
1228                         i->prelight = true;
1229                         _hover = i->e;
1230                 } else {
1231                         if (i->prelight) changed = true;
1232                         i->prelight = false;
1233                 }
1234         }
1235         if (_dragging) {
1236                 _drag_x = ev->x;
1237                 _drag_y = ev->y;
1238         }
1239         if (changed || _dragging) {
1240                 darea.queue_draw ();
1241         }
1242         return true;
1243 }
1244
1245 bool
1246 PluginPinWidget::darea_button_press_event (GdkEventButton* ev)
1247 {
1248         if (ev->type != GDK_BUTTON_PRESS) {
1249                 return false;
1250         }
1251
1252         switch (ev->button) {
1253                 case 1:
1254                         _drag_dst.reset ();
1255                         if (!_selection || (_selection && !_hover)) {
1256                                 _selection = _hover;
1257                                 _actor.reset ();
1258                                 if (_selection) {
1259                                         start_drag (_selection, ev->x, ev->y);
1260                                 } else {
1261                                         darea.queue_draw ();
1262                                 }
1263                         } else if (_selection && _hover && _selection != _hover) {
1264                                 if (_selection->dt != _hover->dt) { _actor.reset (); }
1265                                 else if (_selection->ct == Input  && _hover->ct == Sink)   { _actor = _hover; }
1266                                 else if (_selection->ct == Sink   && _hover->ct == Input)  { _actor = _hover; }
1267                                 else if (_selection->ct == Output && _hover->ct == Source) { _actor = _hover; }
1268                                 else if (_selection->ct == Source && _hover->ct == Output) { _actor = _hover; }
1269                                 else if (_selection->ct == Input  && _hover->ct == Output) { _actor = _hover; }
1270                                 else if (_selection->ct == Output && _hover->ct == Input)  { _actor = _hover; }
1271                                 if (!_actor) {
1272                                         _selection = _hover;
1273                                         start_drag (_selection, ev->x, ev->y);
1274                                 } else {
1275                                         darea.queue_draw ();
1276                                 }
1277                         } else if (_hover) {
1278                                 _selection = _hover;
1279                                 _actor.reset ();
1280                                 start_drag (_selection, ev->x, ev->y);
1281                         }
1282                         break;
1283                 case 3:
1284                         _drag_dst.reset ();
1285                         if (_selection != _hover) {
1286                                 _selection = _hover;
1287                                 darea.queue_draw ();
1288                         }
1289                         _actor.reset ();
1290                         break;
1291                 default:
1292                         break;
1293         }
1294
1295         return true;
1296 }
1297
1298 bool
1299 PluginPinWidget::darea_button_release_event (GdkEventButton* ev)
1300 {
1301         if (_dragging && _selection && _drag_dst && _drag_dst == _hover) {
1302                 // select click. (or re-connect same)
1303                 assert (_selection != _hover);
1304                 _actor.reset ();
1305                 _dragging = false;
1306                 _drag_dst.reset ();
1307                 _selection =_hover;
1308                 darea.queue_draw ();
1309                 return true;
1310         }
1311
1312         if (_dragging && _hover && _hover != _selection) {
1313                 _actor = _hover;
1314         }
1315
1316         if (_hover == _actor && _actor && ev->button == 1) {
1317                 assert (_selection);
1318                 assert (_selection->dt == _actor->dt);
1319                 if (_drag_dst) {
1320                         assert (_dragging && _selection != _drag_dst);
1321                         handle_disconnect (_drag_dst, true);
1322                 }
1323                 if      (_selection->ct == Input && _actor->ct == Sink) {
1324                         handle_input_action (_actor, _selection);
1325                 }
1326                 else if (_selection->ct == Sink && _actor->ct == Input) {
1327                         handle_input_action (_selection, _actor);
1328                 }
1329                 else if (_selection->ct == Output && _actor->ct == Source) {
1330                         handle_output_action (_actor, _selection);
1331                 }
1332                 else if (_selection->ct == Source && _actor->ct == Output) {
1333                         handle_output_action (_selection, _actor);
1334                 }
1335                 else if (_selection->ct == Input && _actor->ct == Output) {
1336                         handle_thru_action (_actor, _selection);
1337                 }
1338                 else if (_selection->ct == Output && _actor->ct == Input) {
1339                         handle_thru_action (_selection, _actor);
1340                 }
1341                 _selection.reset ();
1342         } else if (_hover == _selection && _selection && ev->button == 3) {
1343                 handle_disconnect (_selection);
1344         } else if (!_hover && ev->button == 3) {
1345                 reset_menu.popup (1, ev->time);
1346         }
1347
1348         if (_dragging && _hover != _selection) {
1349                 _selection.reset ();
1350         }
1351         _actor.reset ();
1352         _dragging = false;
1353         _drag_dst.reset ();
1354         darea.queue_draw ();
1355         return true;
1356 }
1357
1358 void
1359 PluginPinWidget::handle_input_action (const CtrlElem &s, const CtrlElem &i)
1360 {
1361         const int pc = s->ip;
1362         bool valid;
1363         ChanMapping in_map (_pi->input_map (pc));
1364         uint32_t idx = in_map.get (s->dt, s->id, &valid);
1365
1366         if (valid && idx == i->id) {
1367                 // disconnect
1368                 if (!_dragging) {
1369                         in_map.unset (s->dt, s->id);
1370                         _pi->set_input_map (pc, in_map);
1371                 } else {
1372                         plugin_reconfigured ();
1373                 }
1374         }
1375         else if (!valid) {
1376                 // connect
1377                 in_map.set (s->dt, s->id, i->id);
1378                 _pi->set_input_map (pc, in_map);
1379         }
1380         else {
1381                 // reconnect
1382                 in_map.unset (s->dt, s->id);
1383                 in_map.set (s->dt, s->id, i->id);
1384                 _pi->set_input_map (pc, in_map);
1385         }
1386 }
1387
1388 void
1389 PluginPinWidget::disconnect_other_outputs (uint32_t skip_pc, DataType dt, uint32_t id)
1390 {
1391         _ignore_updates = true;
1392         for (uint32_t n = 0; n < _n_plugins; ++n) {
1393                 if (n == skip_pc) {
1394                         continue;
1395                 }
1396                 bool valid;
1397                 ChanMapping n_out_map (_pi->output_map (n));
1398                 uint32_t idx = n_out_map.get_src (dt, id, &valid);
1399                 if (valid) {
1400                         n_out_map.unset (dt, idx);
1401                         _pi->set_output_map (n, n_out_map);
1402                 }
1403         }
1404         _ignore_updates = false;
1405 }
1406
1407 void
1408 PluginPinWidget::disconnect_other_thru (DataType dt, uint32_t id)
1409 {
1410         _ignore_updates = true;
1411         bool valid;
1412         ChanMapping n_thru_map (_pi->thru_map ());
1413         n_thru_map.get (dt, id, &valid);
1414         if (valid) {
1415                 n_thru_map.unset (dt, id);
1416                 _pi->set_thru_map (n_thru_map);
1417         }
1418         _ignore_updates = false;
1419 }
1420
1421 void
1422 PluginPinWidget::handle_output_action (const CtrlElem &s, const CtrlElem &o)
1423 {
1424         const uint32_t pc = s->ip;
1425         bool valid;
1426         ChanMapping out_map (_pi->output_map (pc));
1427         uint32_t idx = out_map.get (s->dt, s->id, &valid);
1428
1429         if (valid && idx == o->id) {
1430                 // disconnect
1431                 if (!_dragging) {
1432                         out_map.unset (s->dt, s->id);
1433                         _pi->set_output_map (pc, out_map);
1434                 } else {
1435                         plugin_reconfigured ();
1436                 }
1437         }
1438         else {
1439                 // disconnect source
1440                 disconnect_other_outputs (pc, s->dt, o->id);
1441                 disconnect_other_thru (s->dt, o->id);
1442                 out_map = _pi->output_map (pc); // re-read map
1443                 if (valid) {
1444                         out_map.unset (s->dt, s->id);
1445                 }
1446                 idx = out_map.get_src (s->dt, o->id, &valid);
1447                 if (valid) {
1448                         out_map.unset (s->dt, idx);
1449                 }
1450                 // connect
1451                 out_map.set (s->dt, s->id, o->id);
1452                 _pi->set_output_map (pc, out_map);
1453         }
1454 }
1455
1456 void
1457 PluginPinWidget::handle_thru_action (const CtrlElem &o, const CtrlElem &i)
1458 {
1459         bool valid;
1460         ChanMapping thru_map (_pi->thru_map ());
1461         uint32_t idx = thru_map.get (o->dt, o->id, &valid);
1462
1463         if (valid && idx == i->id) {
1464                 if (!_dragging) {
1465                         thru_map.unset (o->dt, o->id);
1466                 }
1467         } else {
1468                 // disconnect other outputs first
1469                 disconnect_other_outputs (UINT32_MAX, o->dt, o->id);
1470                 disconnect_other_thru (o->dt, o->id);
1471                 thru_map = _pi->thru_map (); // re-read map
1472
1473                 thru_map.set (o->dt, o->id, i->id);
1474         }
1475         _pi->set_thru_map (thru_map);
1476 }
1477
1478 bool
1479 PluginPinWidget::handle_disconnect (const CtrlElem &e, bool no_signal)
1480 {
1481         _ignore_updates = true;
1482         bool changed = false;
1483         bool valid;
1484
1485         switch (e->ct) {
1486                 case Input:
1487                         {
1488                                 ChanMapping n_thru_map (_pi->thru_map ());
1489                                 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
1490                                         uint32_t idx = n_thru_map.get (e->dt, i, &valid);
1491                                         if (valid && idx == e->id) {
1492                                                 n_thru_map.unset (e->dt, i);
1493                                                 changed = true;
1494                                         }
1495                                 }
1496                                 if (changed) {
1497                                         _pi->set_thru_map (n_thru_map);
1498                                 }
1499                         }
1500                         for (uint32_t n = 0; n < _n_plugins; ++n) {
1501                                 ChanMapping map (_pi->input_map (n));
1502                                 for (uint32_t i = 0; i < _sinks.n_total (); ++i) {
1503                                         uint32_t idx = map.get (e->dt, i, &valid);
1504                                         if (valid && idx == e->id) {
1505                                                 map.unset (e->dt, i);
1506                                                 changed = true;
1507                                         }
1508                                 }
1509                                 _pi->set_input_map (n, map);
1510                         }
1511                         break;
1512                 case Sink:
1513                         {
1514                                 ChanMapping map (_pi->input_map (e->ip));
1515                                 map.get (e->dt, e->id, &valid);
1516                                 if (valid) {
1517                                         map.unset (e->dt, e->id);
1518                                         _pi->set_input_map (e->ip, map);
1519                                         changed = true;
1520                                 }
1521                         }
1522                         break;
1523                 case Source:
1524                         {
1525                                 ChanMapping map (_pi->output_map (e->ip));
1526                                 map.get (e->dt, e->id, &valid);
1527                                 if (valid) {
1528                                         map.unset (e->dt, e->id);
1529                                         _pi->set_output_map (e->ip, map);
1530                                         changed = true;
1531                                 }
1532                         }
1533                         break;
1534                 case Output:
1535                         for (uint32_t n = 0; n < _n_plugins; ++n) {
1536                                 ChanMapping map (_pi->output_map (n));
1537                                 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
1538                                         uint32_t idx = map.get (e->dt, i, &valid);
1539                                         if (valid && idx == e->id) {
1540                                                 map.unset (e->dt, i);
1541                                                 changed = true;
1542                                         }
1543                                 }
1544                                 if (changed) {
1545                                         _pi->set_output_map (n, map);
1546                                 }
1547                         }
1548                         {
1549                                 ChanMapping n_thru_map (_pi->thru_map ());
1550                                 n_thru_map.get (e->dt, e->id, &valid);
1551                                 if (valid) {
1552                                         n_thru_map.unset (e->dt, e->id);
1553                                         changed = true;
1554                                         _pi->set_thru_map (n_thru_map);
1555                                 }
1556                         }
1557                         break;
1558         }
1559         _ignore_updates = false;
1560         if (changed && !no_signal) {
1561                 plugin_reconfigured ();
1562         }
1563         return changed;
1564 }
1565
1566 void
1567 PluginPinWidget::toggle_sidechain ()
1568 {
1569         if (!_route ()->add_remove_sidechain (_pi, !_pi->has_sidechain ())) {
1570                 error_message_dialog (_("Failed to toggle sidechain."));
1571         }
1572 }
1573
1574 void
1575 PluginPinWidget::connect_sidechain ()
1576 {
1577         assert (_session);
1578
1579         if (_sidechain_selector == 0) {
1580                 _sidechain_selector = new IOSelectorWindow (_session, _pi->sidechain_input ());
1581         }
1582
1583         if (_sidechain_selector->is_visible ()) {
1584                 _sidechain_selector->get_toplevel ()->get_window ()->raise ();
1585         } else {
1586                 _sidechain_selector->present ();
1587         }
1588 }
1589
1590 void
1591 PluginPinWidget::reset_configuration ()
1592 {
1593         bool rv;
1594         if (_set_config.get_active ()) {
1595                 rv = _route ()->reset_plugin_insert (_pi);
1596         } else {
1597                 rv = _route ()->customize_plugin_insert (_pi, _n_plugins, _out, _sinks);
1598         }
1599         if (!rv) {
1600                 error_message_dialog (_("Failed to reset plugin configuration."));
1601         }
1602 }
1603
1604 void
1605 PluginPinWidget::reset_mapping ()
1606 {
1607         _pi->reset_map ();
1608 }
1609
1610 void
1611 PluginPinWidget::select_output_preset (uint32_t n_audio)
1612 {
1613         ChanCount out (DataType::AUDIO, n_audio);
1614         if (!_route ()->plugin_preset_output (_pi, out)) {
1615                 error_message_dialog (_("Failed to change channel preset."));
1616         }
1617 }
1618
1619 void
1620 PluginPinWidget::add_remove_plugin_clicked (bool add)
1621 {
1622         ChanCount out = _out;
1623         ChanCount sinks = _sinks;
1624         assert (add || _n_plugins > 0);
1625         if (!_route ()->customize_plugin_insert (_pi, _n_plugins + (add ? 1 : -1), out, sinks)) {
1626                 error_message_dialog (_("Failed to change instance count"));
1627         }
1628 }
1629
1630 void
1631 PluginPinWidget::add_remove_port_clicked (bool add, ARDOUR::DataType dt)
1632 {
1633         ChanCount out = _out;
1634         ChanCount sinks = _sinks;
1635         assert (add || out.get (dt) > 0);
1636         out.set (dt, out.get (dt) + (add ? 1 : -1));
1637         if (!_route ()->customize_plugin_insert (_pi, _n_plugins, out, sinks)) {
1638                 error_message_dialog (_("Failed to alter plugin output configuration."));
1639         }
1640 }
1641
1642 void
1643 PluginPinWidget::add_remove_inpin_clicked (bool add, ARDOUR::DataType dt)
1644 {
1645         ChanCount out = _out;
1646         ChanCount sinks = _sinks;
1647         assert (add || sinks.get (dt) > 0);
1648         sinks.set (dt, sinks.get (dt) + (add ? 1 : -1));
1649         if (!_route ()->customize_plugin_insert (_pi, _n_plugins, out, sinks)) {
1650                 error_message_dialog (_("Failed to alter plugin input configuration."));
1651         }
1652 }
1653
1654 void
1655 PluginPinWidget::add_sidechain_port (DataType dt)
1656 {
1657         assert (_session);
1658         if (_session->actively_recording ()) {
1659                 error_message_dialog (/* unused */ "");
1660                 return;
1661         }
1662
1663         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1664         if (!io) {
1665                 return;
1666         }
1667
1668         // this triggers a PluginIoReConfigure with process and processor write lock held
1669         // from /this/ thread.
1670         io->add_port ("", this, dt);
1671 }
1672
1673 void
1674 PluginPinWidget::remove_port (boost::weak_ptr<ARDOUR::Port> wp)
1675 {
1676         assert (_session);
1677         if (_session->actively_recording ()) {
1678                 error_message_dialog (/* unused */ "");
1679                 return;
1680         }
1681         boost::shared_ptr<ARDOUR::Port> p = wp.lock ();
1682         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1683         if (!io || !p) {
1684                 return;
1685         }
1686         io->remove_port (p, this);
1687 }
1688
1689 void
1690 PluginPinWidget::disconnect_port (boost::weak_ptr<ARDOUR::Port> wp)
1691 {
1692         assert (_session);
1693         if (_session->actively_recording ()) {
1694                 error_message_dialog (/* unused */ "");
1695                 return;
1696         }
1697
1698         boost::shared_ptr<ARDOUR::Port> p = wp.lock ();
1699         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1700         if (!io || !p) {
1701                 return;
1702         }
1703         p->disconnect_all ();
1704 }
1705
1706 void
1707 PluginPinWidget::connect_port (boost::weak_ptr<ARDOUR::Port> wp0, boost::weak_ptr<ARDOUR::Port> wp1)
1708 {
1709         assert (_session);
1710         if (_session->actively_recording ()) {
1711                 error_message_dialog (/* unused */ "");
1712                 return;
1713         }
1714
1715         boost::shared_ptr<ARDOUR::Port> p0 = wp0.lock ();
1716         boost::shared_ptr<ARDOUR::Port> p1 = wp1.lock ();
1717         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1718         if (!io || !p0 || !p1) {
1719                 return;
1720         }
1721         _ignore_updates = true;
1722         p0->disconnect_all ();
1723         _ignore_updates = false;
1724         p0->connect (p1->name ());
1725 }
1726
1727 void
1728 PluginPinWidget::add_send_from (boost::weak_ptr<ARDOUR::Port> wp, boost::weak_ptr<ARDOUR::Route> wr)
1729 {
1730         assert (_session);
1731         if (_session->actively_recording ()) {
1732                 error_message_dialog (/* unused */ "");
1733                 return;
1734         }
1735
1736         boost::shared_ptr<Port> p = wp.lock ();
1737         boost::shared_ptr<Route> r = wr.lock ();
1738         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1739         if (!p || !r || !io) {
1740                 return;
1741         }
1742
1743         boost::shared_ptr<Pannable> sendpan (new Pannable (*_session));
1744         boost::shared_ptr<Send> send (new Send (*_session, r->pannable (), r->mute_master ()));
1745         const ChanCount& outs (r->amp ()->input_streams ());
1746         try {
1747                 Glib::Threads::Mutex::Lock lm (AudioEngine::instance ()->process_lock ());
1748                 send->output()->ensure_io (outs, false, this);
1749         } catch (AudioEngine::PortRegistrationFailure& err) {
1750                 error << string_compose (_("Cannot set up new send: %1"), err.what ()) << endmsg;
1751                 return;
1752         }
1753
1754         std::string sendname = send->name ();
1755         string::size_type last_letter = sendname.find_last_not_of ("0123456789");
1756         if (last_letter != string::npos) {
1757                 send->output ()->set_pretty_name (string_compose (_("SC %1 (%2)"),
1758                                 r->name (),
1759                                 sendname.substr (last_letter + 1)));
1760         }
1761
1762         _ignore_updates = true;
1763         p->disconnect_all ();
1764
1765         DataType dt = p->type ();
1766         PortSet& ps (send->output ()->ports ());
1767         for (PortSet::iterator i = ps.begin (dt); i != ps.end (dt); ++i) {
1768                 p->connect (&(**i));
1769         }
1770
1771         send->set_remove_on_disconnect (true);
1772         r->add_processor (send, PreFader);
1773         _ignore_updates = false;
1774         queue_idle_update ();
1775 }
1776
1777 bool
1778 PluginPinWidget::sc_input_release (GdkEventButton *ev)
1779 {
1780         assert (_session);
1781         if (_session->actively_recording ()) {
1782                 error_message_dialog (/* unused */ "");
1783                 return false;
1784         }
1785
1786         if (ev->button == 3) {
1787                 connect_sidechain ();
1788         }
1789         return false;
1790 }
1791
1792 bool
1793 PluginPinWidget::sc_input_press (GdkEventButton *ev, boost::weak_ptr<ARDOUR::Port> wp)
1794 {
1795         using namespace Menu_Helpers;
1796         assert (_session);
1797         if (!ARDOUR_UI_UTILS::engine_is_running ()) {
1798                 return false;
1799         }
1800         if (_session->actively_recording ()) {
1801                 error_message_dialog (/* unused */ "");
1802                 return false;
1803         }
1804
1805         if (ev->button == 1) {
1806                 MenuList& citems = input_menu.items ();
1807                 input_menu.set_name ("ArdourContextMenu");
1808                 citems.clear ();
1809
1810                 boost::shared_ptr<Port> p = wp.lock ();
1811                 if (p && p->connected ()) {
1812                         citems.push_back (MenuElem (_("Disconnect"), sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::disconnect_port), wp)));
1813                         citems.push_back (SeparatorElem ());
1814                 }
1815
1816 #if 0
1817                 // TODO add system inputs, too ?!
1818                 boost::shared_ptr<ARDOUR::BundleList> b = _session->bundles ();
1819                 for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) {
1820                         for (uint32_t j = 0; j < i->nchannels ().n_total (); ++j) {
1821                         }
1822                         //maybe_add_bundle_to_input_menu (*i, current);
1823                 }
1824 #endif
1825
1826                 RouteList copy = _session->get_routelist ();
1827                 copy.sort (Stripable::Sorter(true));
1828                 uint32_t added = 0;
1829                 for (ARDOUR::RouteList::const_iterator i = copy.begin (); i != copy.end (); ++i) {
1830                         added += maybe_add_route_to_input_menu (*i, p->type (), wp);
1831                 }
1832
1833                 if (added > 0) {
1834                         citems.push_back (SeparatorElem ());
1835                 }
1836                 citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*this, &PluginPinWidget::connect_sidechain)));
1837                 input_menu.popup (1, ev->time);
1838         }
1839         return false;
1840 }
1841
1842 uint32_t
1843 PluginPinWidget::maybe_add_route_to_input_menu (boost::shared_ptr<Route> r, DataType dt, boost::weak_ptr<Port> wp)
1844 {
1845         uint32_t added = 0;
1846         using namespace Menu_Helpers;
1847         if (r->output () == _route ()->output ()) {
1848                 return added;
1849         }
1850
1851         if (_route ()->feeds_according_to_graph (r)) {
1852                 return added;
1853         }
1854
1855         MenuList& citems = input_menu.items ();
1856
1857         /*check if there's already a send.. */
1858         bool already_present = false;
1859         uint32_t nth = 0;
1860         boost::shared_ptr<Processor> proc;
1861         /* Note: nth_send () takes a processor read-lock */
1862         while ((proc = r->nth_send (nth))) {
1863                 boost::shared_ptr<IOProcessor> send = boost::dynamic_pointer_cast<IOProcessor> (proc);
1864                 if (!send || !send->output ()) {
1865                         ++nth;
1866                         continue;
1867                 }
1868                 if (send->output ()->connected_to (_pi->sidechain_input ())) {
1869                         // only if (send->remove_on_disconnect ()) ??
1870                         already_present = true;
1871                         break;
1872                 }
1873                 ++nth;
1874         }
1875         /* we're going to create the new send pre-fader, so check the route amp's data type.  */
1876         const ChanCount& rc (r->amp ()->input_streams ());
1877         if (!already_present && rc.get (dt) > 0) {
1878                 citems.push_back (MenuElemNoMnemonic (r->name (), sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_send_from), wp, boost::weak_ptr<Route> (r))));
1879                 ++added;
1880         }
1881         return added;
1882 }
1883
1884 void
1885 PluginPinWidget::port_connected_or_disconnected (boost::weak_ptr<ARDOUR::Port> w0, boost::weak_ptr<ARDOUR::Port> w1)
1886 {
1887         boost::shared_ptr<Port> p0 = w0.lock ();
1888         boost::shared_ptr<Port> p1 = w1.lock ();
1889
1890         boost::shared_ptr<IO> io = _pi->sidechain_input ();
1891         if (!io) { return; }
1892
1893         if (p0 && io->has_port (p0)) {
1894                 queue_idle_update ();
1895         }
1896         else if (p1 && io->has_port (p1)) {
1897                 queue_idle_update ();
1898         }
1899 }
1900
1901 /* lifted from ProcessorEntry::Control */
1902 PluginPinWidget::Control::Control (boost::shared_ptr<AutomationControl> c, string const & n)
1903         : _control (c)
1904         , _adjustment (gain_to_slider_position_with_max (1.0, Config->get_max_gain ()), 0, 1, 0.01, 0.1)
1905         , _slider (&_adjustment, boost::shared_ptr<PBD::Controllable> (), 0, max (13.f, rintf (13.f * UIConfiguration::instance ().get_ui_scale ())))
1906         , _slider_persistant_tooltip (&_slider)
1907         , _ignore_ui_adjustment (false)
1908         , _name (n)
1909 {
1910         _slider.set_controllable (c);
1911         box.set_padding (0, 0, 4, 4);
1912
1913         _slider.set_name ("ProcessorControlSlider");
1914         _slider.set_text (_name);
1915
1916         box.add (_slider);
1917         _slider.show ();
1918
1919         const ARDOUR::ParameterDescriptor& desc = c->desc ();
1920         double const lo        = c->internal_to_interface (desc.lower);
1921         double const up        = c->internal_to_interface (desc.upper);
1922         double const normal    = c->internal_to_interface (desc.normal);
1923         double const smallstep = c->internal_to_interface (desc.lower + desc.smallstep);
1924         double const largestep = c->internal_to_interface (desc.lower + desc.largestep);
1925
1926         _adjustment.set_lower (lo);
1927         _adjustment.set_upper (up);
1928         _adjustment.set_step_increment (smallstep);
1929         _adjustment.set_page_increment (largestep);
1930         _slider.set_default_value (normal);
1931
1932         _adjustment.signal_value_changed ().connect (sigc::mem_fun (*this, &Control::slider_adjusted));
1933         // dup. currently timers are used :(
1934         //c->Changed.connect (_connection, MISSING_INVALIDATOR, boost::bind (&Control::control_changed, this), gui_context ());
1935
1936         // yuck, do we really need to do this?
1937         // according to c404374 this is only needed for send automation
1938         timer_connection = Timers::rapid_connect (sigc::mem_fun (*this, &Control::control_changed));
1939
1940         control_changed ();
1941         set_tooltip ();
1942
1943         /* We're providing our own PersistentTooltip */
1944         set_no_tooltip_whatsoever (_slider);
1945 }
1946
1947 PluginPinWidget::Control::~Control ()
1948 {
1949         timer_connection.disconnect ();
1950 }
1951
1952 void
1953 PluginPinWidget::Control::set_tooltip ()
1954 {
1955         boost::shared_ptr<AutomationControl> c = _control.lock ();
1956         if (!c) {
1957                 return;
1958         }
1959         std::string tt = _name + ": " + ARDOUR::value_as_string (c->desc(), c->get_value ());
1960         string sm = Gtkmm2ext::markup_escape_text (tt);
1961         _slider_persistant_tooltip.set_tip (sm);
1962 }
1963
1964 void
1965 PluginPinWidget::Control::slider_adjusted ()
1966 {
1967         if (_ignore_ui_adjustment) {
1968                 return;
1969         }
1970         boost::shared_ptr<AutomationControl> c = _control.lock ();
1971         if (!c) {
1972                 return;
1973         }
1974         c->set_value ( c->interface_to_internal (_adjustment.get_value ()) , Controllable::NoGroup);
1975         set_tooltip ();
1976 }
1977
1978
1979 void
1980 PluginPinWidget::Control::control_changed ()
1981 {
1982         boost::shared_ptr<AutomationControl> c = _control.lock ();
1983         if (!c) {
1984                 return;
1985         }
1986
1987         _ignore_ui_adjustment = true;
1988
1989         // as long as rapid timers are used, only update the tooltip
1990         // if the value has changed.
1991         const double nval = c->internal_to_interface (c->get_value ());
1992         if (_adjustment.get_value () != nval) {
1993                 _adjustment.set_value (nval);
1994                 set_tooltip ();
1995         }
1996
1997         _ignore_ui_adjustment = false;
1998 }
1999
2000
2001
2002 PluginPinDialog::PluginPinDialog (boost::shared_ptr<ARDOUR::PluginInsert> pi)
2003         : ArdourWindow (string_compose (_("Pin Configuration: %1"), pi->name ()))
2004 {
2005         ppw.push_back (PluginPinWidgetPtr(new PluginPinWidget (pi)));
2006         add (*ppw.back());
2007 }
2008
2009
2010 PluginPinDialog::PluginPinDialog (boost::shared_ptr<ARDOUR::Route> r)
2011         : ArdourWindow (string_compose (_("Pin Configuration: %1"), r->name ()))
2012         , _route (r)
2013         , _height_mapped (false)
2014 {
2015         vbox = manage (new VBox ());
2016         vbox->signal_size_allocate().connect (sigc::mem_fun (*this, &PluginPinDialog::map_height));
2017         scroller = manage (new ScrolledWindow);
2018         scroller->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2019         scroller->set_shadow_type (Gtk::SHADOW_NONE);
2020         scroller->show ();
2021         vbox->show ();
2022         scroller->add (*vbox);
2023         add (*scroller);
2024
2025
2026         _route->foreach_processor (sigc::mem_fun (*this, &PluginPinDialog::add_processor));
2027
2028         _route->processors_changed.connect (
2029                 _route_connections, invalidator (*this), boost::bind (&PluginPinDialog::route_processors_changed, this, _1), gui_context()
2030                 );
2031
2032         _route->DropReferences.connect (
2033                 _route_connections, invalidator (*this), boost::bind (&PluginPinDialog::route_going_away, this), gui_context()
2034                 );
2035 }
2036 void
2037 PluginPinDialog::set_session (ARDOUR::Session *s)
2038 {
2039         SessionHandlePtr::set_session (s);
2040         for (PluginPinWidgetList::iterator i = ppw.begin(); i != ppw.end(); ++i) {
2041                 (*i)->set_session (s);
2042         }
2043 }
2044
2045 void
2046 PluginPinDialog::map_height (Gtk::Allocation&)
2047 {
2048         if (!_height_mapped) {
2049                 scroller->set_size_request (-1, std::min (600, 2 + vbox->get_height()));
2050                 _height_mapped = true;
2051         }
2052 }
2053
2054 void
2055 PluginPinDialog::route_processors_changed (ARDOUR::RouteProcessorChange)
2056 {
2057         ppw.clear ();
2058         _height_mapped = false;
2059         scroller->remove ();
2060         vbox = manage (new VBox ());
2061         vbox->signal_size_allocate().connect (sigc::mem_fun (*this, &PluginPinDialog::map_height));
2062         scroller->add (*vbox);
2063         _route->foreach_processor (sigc::mem_fun (*this, &PluginPinDialog::add_processor));
2064         vbox->show ();
2065 }
2066
2067 void
2068 PluginPinDialog::route_going_away ()
2069 {
2070         ppw.clear ();
2071         _route.reset ();
2072         remove ();
2073 }
2074
2075 void
2076 PluginPinDialog::add_processor (boost::weak_ptr<Processor> p)
2077 {
2078         boost::shared_ptr<Processor> proc = p.lock ();
2079         if (!proc || !proc->display_to_user ()) {
2080                 return;
2081         }
2082         boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (proc);
2083 #ifdef MIXBUS
2084         if (pi && pi->is_channelstrip ()) {
2085                 pi.reset ();
2086         }
2087 #endif
2088         if (pi) {
2089                 ppw.push_back (PluginPinWidgetPtr(new PluginPinWidget (pi)));
2090                 ppw.back()->set_session (_session);
2091                 vbox->pack_start (*ppw.back());
2092         } else {
2093                 HBox* hbox = manage (new HBox ());
2094                 hbox->pack_start (*manage (new HSeparator ()));
2095                 hbox->pack_start (*manage (new Label (proc->display_name ())));
2096                 hbox->pack_start (*manage (new HSeparator ()));
2097                 vbox->pack_start (*hbox, false, false);
2098                 hbox->show_all ();
2099         }
2100 }