Add vamp-pyin authors to user-visible doc
[ardour.git] / gtk2_ardour / plugin_ui.cc
1 /*
2  * Copyright (C) 2005-2006 Nick Mainsbridge <mainsbridge@gmail.com>
3  * Copyright (C) 2005-2017 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
5  * Copyright (C) 2006-2009 Sampo Savolainen <v2@iki.fi>
6  * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
7  * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
8  * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
9  * Copyright (C) 2013-2018 John Emmas <john@creativepost.co.uk>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License along
22  * with this program; if not, write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25
26 #ifdef WAF_BUILD
27 #include "gtk2ardour-config.h"
28 #endif
29
30 #include <climits>
31 #include <cerrno>
32 #include <cmath>
33 #include <string>
34
35 #include "pbd/stl_delete.h"
36 #include "pbd/xml++.h"
37 #include "pbd/failed_constructor.h"
38
39 #include "gtkmm/widget.h"
40 #include "gtkmm/box.h"
41
42 #include "gtkmm2ext/utils.h"
43 #include "gtkmm2ext/doi.h"
44 #include "gtkmm2ext/application.h"
45
46 #include "widgets/tooltips.h"
47 #include "widgets/fastmeter.h"
48
49 #include "ardour/session.h"
50 #include "ardour/plugin.h"
51 #include "ardour/plugin_insert.h"
52 #include "ardour/ladspa_plugin.h"
53 #ifdef WINDOWS_VST_SUPPORT
54 #include "ardour/windows_vst_plugin.h"
55 #include "windows_vst_plugin_ui.h"
56 #endif
57 #ifdef LXVST_SUPPORT
58 #include "ardour/lxvst_plugin.h"
59 #include "lxvst_plugin_ui.h"
60 #endif
61 #ifdef MACVST_SUPPORT
62 #include "ardour/mac_vst_plugin.h"
63 #include "vst_plugin_ui.h"
64 #endif
65 #ifdef LV2_SUPPORT
66 #include "ardour/lv2_plugin.h"
67 #include "lv2_plugin_ui.h"
68 #endif
69
70 #include "ardour_window.h"
71 #include "ardour_ui.h"
72 #include "plugin_ui.h"
73 #include "utils.h"
74 #include "gui_thread.h"
75 #include "public_editor.h"
76 #include "processor_box.h"
77 #include "keyboard.h"
78 #include "latency_gui.h"
79 #include "plugin_dspload_ui.h"
80 #include "plugin_eq_gui.h"
81 #include "plugin_presets_ui.h"
82 #include "timers.h"
83 #include "new_plugin_preset_dialog.h"
84
85 #include "pbd/i18n.h"
86
87 using namespace std;
88 using namespace ARDOUR;
89 using namespace ARDOUR_UI_UTILS;
90 using namespace ArdourWidgets;
91 using namespace PBD;
92 using namespace Gtkmm2ext;
93 using namespace Gtk;
94
95
96 PluginUIWindow::PluginUIWindow (
97         boost::shared_ptr<PluginInsert> insert,
98         bool                            scrollable,
99         bool                            editor)
100         : ArdourWindow (string())
101         , was_visible (false)
102         , _keyboard_focused (false)
103 #ifdef AUDIOUNIT_SUPPORT
104         , pre_deactivate_x (-1)
105         , pre_deactivate_y (-1)
106 #endif
107
108 {
109         bool have_gui = false;
110         Label* label = manage (new Label());
111         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
112
113         if (editor && insert->plugin()->has_editor()) {
114                 switch (insert->type()) {
115                 case ARDOUR::Windows_VST:
116                         have_gui = create_windows_vst_editor (insert);
117                         break;
118
119                 case ARDOUR::LXVST:
120                         have_gui = create_lxvst_editor (insert);
121                         break;
122
123                 case ARDOUR::MacVST:
124                         have_gui = create_mac_vst_editor (insert);
125                         break;
126
127                 case ARDOUR::AudioUnit:
128                         have_gui = create_audiounit_editor (insert);
129                         break;
130
131                 case ARDOUR::LADSPA:
132                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
133                         break;
134
135                 case ARDOUR::LV2:
136                         have_gui = create_lv2_editor (insert);
137                         break;
138
139                 default:
140 #ifndef WINDOWS_VST_SUPPORT
141                         error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
142                               << endmsg;
143 #else
144                         error << _("unknown type of editor-supplying plugin")
145                               << endmsg;
146 #endif
147                         throw failed_constructor ();
148                 }
149
150         }
151
152         if (!have_gui) {
153                 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
154
155                 _pluginui = pu;
156                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
157                 add (*pu);
158                 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
159
160                 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
161                 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
162         }
163
164         set_name ("PluginEditor");
165         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
166
167         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
168
169         gint h = _pluginui->get_preferred_height ();
170         gint w = _pluginui->get_preferred_width ();
171
172         if (scrollable) {
173                 if (h > 600) h = 600;
174         }
175
176         set_border_width (0);
177         set_default_size (w, h);
178         set_resizable (_pluginui->resizable());
179 }
180
181 PluginUIWindow::~PluginUIWindow ()
182 {
183 #ifndef NDEBUG
184         cerr << "PluginWindow deleted for " << this << endl;
185 #endif
186         delete _pluginui;
187 }
188
189 void
190 PluginUIWindow::on_show ()
191 {
192         set_role("plugin_ui");
193
194         if (_pluginui) {
195                 _pluginui->update_preset_list ();
196                 _pluginui->update_preset ();
197         }
198
199         if (_pluginui) {
200 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
201                 if (pre_deactivate_x >= 0) {
202                         move (pre_deactivate_x, pre_deactivate_y);
203                 }
204 #endif
205
206                 if (_pluginui->on_window_show (_title)) {
207                         Window::on_show ();
208                 }
209         }
210 }
211
212 void
213 PluginUIWindow::on_hide ()
214 {
215 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
216         get_position (pre_deactivate_x, pre_deactivate_y);
217 #endif
218
219         Window::on_hide ();
220
221         if (_pluginui) {
222                 _pluginui->on_window_hide ();
223         }
224 }
225
226 void
227 PluginUIWindow::set_title(const std::string& title)
228 {
229         Gtk::Window::set_title(title);
230         _title = title;
231 }
232
233 bool
234 #ifdef WINDOWS_VST_SUPPORT
235 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
236 #else
237 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
238 #endif
239 {
240 #ifndef WINDOWS_VST_SUPPORT
241         return false;
242 #else
243
244         boost::shared_ptr<WindowsVSTPlugin> vp;
245
246         if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
247                 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
248                       << endmsg;
249                 throw failed_constructor ();
250         } else {
251                 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
252
253                 _pluginui = vpu;
254                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
255                 add (*vpu);
256                 vpu->package (*this);
257         }
258
259         return true;
260 #endif
261 }
262
263 bool
264 #ifdef LXVST_SUPPORT
265 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
266 #else
267 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
268 #endif
269 {
270 #ifndef LXVST_SUPPORT
271         return false;
272 #else
273
274         boost::shared_ptr<LXVSTPlugin> lxvp;
275
276         if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
277                 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
278                       << endmsg;
279                 throw failed_constructor ();
280         } else {
281                 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
282
283                 _pluginui = lxvpu;
284                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
285                 add (*lxvpu);
286                 lxvpu->package (*this);
287         }
288
289         return true;
290 #endif
291 }
292
293 bool
294 #ifdef MACVST_SUPPORT
295 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert> insert)
296 #else
297 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert>)
298 #endif
299 {
300 #ifndef MACVST_SUPPORT
301         return false;
302 #else
303         boost::shared_ptr<MacVSTPlugin> mvst;
304         if ((mvst = boost::dynamic_pointer_cast<MacVSTPlugin> (insert->plugin())) == 0) {
305                 error << string_compose (_("unknown type of editor-supplying plugin (note: no MacVST support in this version of %1)"), PROGRAM_NAME)
306                       << endmsg;
307                 throw failed_constructor ();
308         }
309         VSTPluginUI* vpu = create_mac_vst_gui (insert);
310         _pluginui = vpu;
311         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
312         add (*vpu);
313         vpu->package (*this);
314
315         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
316
317         return true;
318 #endif
319 }
320
321
322 bool
323 #ifdef AUDIOUNIT_SUPPORT
324 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
325 #else
326 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
327 #endif
328 {
329 #ifndef AUDIOUNIT_SUPPORT
330         return false;
331 #else
332         VBox* box;
333         _pluginui = create_au_gui (insert, &box);
334         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
335         add (*box);
336
337         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
338
339         return true;
340 #endif
341 }
342
343 void
344 #ifdef __APPLE__
345 PluginUIWindow::app_activated (bool yn)
346 #else
347 PluginUIWindow::app_activated (bool)
348 #endif
349 {
350 #ifdef AUDIOUNIT_SUPPORT
351         if (_pluginui) {
352                 if (yn) {
353                         if (was_visible) {
354                                 _pluginui->activate ();
355                                 if (pre_deactivate_x >= 0) {
356                                         move (pre_deactivate_x, pre_deactivate_y);
357                                 }
358                                 present ();
359                                 was_visible = true;
360                         }
361                 } else {
362                         was_visible = is_visible();
363                         get_position (pre_deactivate_x, pre_deactivate_y);
364                         hide ();
365                         _pluginui->deactivate ();
366                 }
367         }
368 #endif
369 }
370
371 bool
372 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
373 {
374 #ifdef HAVE_SUIL
375         boost::shared_ptr<LV2Plugin> vp;
376
377         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
378                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
379                 throw failed_constructor ();
380         } else {
381                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
382                 _pluginui = lpu;
383                 add (*lpu);
384                 lpu->package (*this);
385                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
386         }
387
388         return true;
389 #else
390         return false;
391 #endif
392 }
393
394 void
395 PluginUIWindow::keyboard_focused (bool yn)
396 {
397         _keyboard_focused = yn;
398 }
399
400 bool
401 PluginUIWindow::on_key_press_event (GdkEventKey* event)
402 {
403         if (_keyboard_focused) {
404                 if (_pluginui) {
405                         _pluginui->grab_focus();
406                         if (_pluginui->non_gtk_gui()) {
407                                 _pluginui->forward_key_event (event);
408                         } else {
409                                         return relay_key_press (event, this);
410                         }
411                 }
412                 return true;
413         }
414         /* for us to be getting key press events, there really
415            MUST be a _pluginui, but just to be safe, check ...
416         */
417
418         if (_pluginui) {
419                 _pluginui->grab_focus();
420                 if (_pluginui->non_gtk_gui()) {
421                         /* pass main window as the window for the event
422                            to be handled in, not this one, because there are
423                            no widgets in this window that we want to have
424                            key focus.
425                         */
426                         return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
427                 } else {
428                         return relay_key_press (event, this);
429                 }
430         }
431
432         return false;
433 }
434
435 bool
436 PluginUIWindow::on_key_release_event (GdkEventKey *event)
437 {
438         if (_keyboard_focused) {
439                 if (_pluginui) {
440                         if (_pluginui->non_gtk_gui()) {
441                                 _pluginui->forward_key_event (event);
442                         }
443                 }
444         } else {
445                 gtk_window_propagate_key_event (GTK_WINDOW(gobj()), event);
446         }
447         /* don't forward releases */
448         return true;
449 }
450
451 void
452 PluginUIWindow::plugin_going_away ()
453 {
454         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
455
456         if (_pluginui) {
457                 _pluginui->stop_updating(0);
458         }
459
460         death_connection.disconnect ();
461 }
462
463 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
464         : insert (pi)
465         , plugin (insert->plugin())
466         , add_button (_("Add"))
467         , save_button (_("Save"))
468         , delete_button (_("Delete"))
469         , reset_button (_("Reset"))
470         , bypass_button (ArdourButton::led_default_elements)
471         , pin_management_button (_("Pinout"))
472         , description_expander (_("Description"))
473         , plugin_analysis_expander (_("Plugin analysis"))
474         , cpuload_expander (_("CPU Profile"))
475         , latency_gui (0)
476         , latency_dialog (0)
477         , eqgui (0)
478         , stats_gui (0)
479         , preset_gui (0)
480 {
481         _preset_modified.set_size_request (16, -1);
482         _preset_combo.set_text("(default)");
483         set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
484         set_tooltip (add_button, _("Save a new preset"));
485         set_tooltip (save_button, _("Save the current preset"));
486         set_tooltip (delete_button, _("Delete the current preset"));
487         set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
488         set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
489         set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
490         set_tooltip (latency_button, _("Edit Plugin Delay/Latency Compensation"));
491         _no_load_preset = 0;
492
493         update_preset_list ();
494         update_preset ();
495
496         latency_button.set_icon (ArdourIcon::LatencyClock);
497         latency_button.add_elements (ArdourButton::Text);
498
499         add_button.set_name ("generic button");
500         add_button.set_icon (ArdourIcon::PsetAdd);
501         add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
502
503         save_button.set_name ("generic button");
504         save_button.set_icon (ArdourIcon::PsetSave);
505         save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
506
507         delete_button.set_name ("generic button");
508         delete_button.set_icon (ArdourIcon::PsetDelete);
509         delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
510
511         reset_button.set_name ("generic button");
512         reset_button.set_icon (ArdourIcon::PluginReset);
513         reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
514
515         pin_management_button.set_name ("generic button");
516         pin_management_button.set_icon (ArdourIcon::PluginPinout);
517         pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
518
519         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
520
521         bypass_button.set_name ("plugin bypass button");
522         bypass_button.set_text (_("Bypass"));
523         bypass_button.set_icon (ArdourIcon::PluginBypass);
524         bypass_button.set_active (!pi->enabled ());
525         bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
526         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
527
528         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
529         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
530
531         /* these images are not managed, so that we can remove them at will */
532
533         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
534         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
535
536         focus_button.add (*focus_out_image);
537
538         set_tooltip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
539         set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
540
541         description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
542         description_expander.set_expanded(false);
543
544         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
545         plugin_analysis_expander.set_expanded(false);
546
547         cpuload_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_cpuload_display));
548         cpuload_expander.set_expanded(false);
549
550         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
551
552         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
553         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
554         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
555         plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
556
557         insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
558
559         insert->LatencyChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::set_latency_label, this), gui_context());
560
561         automation_state_changed();
562 }
563
564 PlugUIBase::~PlugUIBase()
565 {
566         delete eqgui;
567         delete stats_gui;
568         delete preset_gui;
569         delete latency_gui;
570         delete latency_dialog;
571 }
572
573 void
574 PlugUIBase::plugin_going_away ()
575 {
576         /* drop references to the plugin/insert */
577         insert.reset ();
578         plugin.reset ();
579 }
580
581 void
582 PlugUIBase::set_latency_label ()
583 {
584         samplecnt_t const l = insert->effective_latency ();
585         samplecnt_t const sr = insert->session().sample_rate ();
586
587         string t;
588
589         if (l < sr / 1000) {
590                 t = string_compose (P_("%1 sample", "%1 samples", l), l);
591         } else {
592                 t = string_compose (_("%1 ms"), (float) l / ((float) sr / 1000.0f));
593         }
594
595         latency_button.set_text (t);
596 }
597
598 void
599 PlugUIBase::latency_button_clicked ()
600 {
601         if (!latency_gui) {
602                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().sample_rate(), insert->session().get_block_size());
603                 latency_dialog = new ArdourWindow (_("Edit Latency"));
604                 /* use both keep-above and transient for to try cover as many
605                    different WM's as possible.
606                 */
607                 latency_dialog->set_keep_above (true);
608                 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
609                 if (win) {
610                         latency_dialog->set_transient_for (*win);
611                 }
612                 latency_dialog->add (*latency_gui);
613         }
614
615         latency_gui->refresh ();
616         latency_dialog->show_all ();
617 }
618
619 void
620 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
621 {
622         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
623         boost::shared_ptr<Processor> p (weak_p.lock());
624
625         if (p) {
626                 bypass_button.set_active (!p->enabled ());
627         }
628 }
629
630 void
631 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
632 {
633         if (_no_load_preset) {
634                 return;
635         }
636         if (!preset.label.empty()) {
637                 insert->load_preset (preset);
638         } else {
639                 // blank selected = no preset
640                 plugin->clear_preset();
641         }
642 }
643
644 #ifdef NO_PLUGIN_STATE
645 static bool seen_saving_message = false;
646
647 static void show_no_plugin_message()
648 {
649         info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
650                         PROGRAM_NAME)
651              << endmsg;
652         info << _("To get full access to updates without this limitation\n"
653                   "consider becoming a subscriber for a low cost every month.")
654              << endmsg;
655         info << X_("https://community.ardour.org/s/subscribe")
656              << endmsg;
657         ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
658 }
659 #endif
660
661 void
662 PlugUIBase::add_plugin_setting ()
663 {
664 #ifndef NO_PLUGIN_STATE
665         NewPluginPresetDialog d (plugin, _("New Preset"));
666
667         switch (d.run ()) {
668         case Gtk::RESPONSE_ACCEPT:
669                 if (d.name().empty()) {
670                         break;
671                 }
672
673                 if (d.replace ()) {
674                         plugin->remove_preset (d.name ());
675                 }
676
677                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
678                 if (!r.uri.empty ()) {
679                         plugin->load_preset (r);
680                 }
681                 break;
682         }
683 #else
684         if (!seen_saving_message) {
685                 seen_saving_message = true;
686                 show_no_plugin_message();
687         }
688 #endif
689 }
690
691 void
692 PlugUIBase::save_plugin_setting ()
693 {
694 #ifndef NO_PLUGIN_STATE
695         string const name = _preset_combo.get_text ();
696         plugin->remove_preset (name);
697         Plugin::PresetRecord const r = plugin->save_preset (name);
698         if (!r.uri.empty ()) {
699                 plugin->load_preset (r);
700         }
701 #else
702         if (!seen_saving_message) {
703                 seen_saving_message = true;
704                 show_no_plugin_message();
705         }
706 #endif
707 }
708
709 void
710 PlugUIBase::delete_plugin_setting ()
711 {
712 #ifndef NO_PLUGIN_STATE
713         plugin->remove_preset (_preset_combo.get_text ());
714 #else
715         if (!seen_saving_message) {
716                 seen_saving_message = true;
717                 show_no_plugin_message();
718         }
719 #endif
720 }
721
722 void
723 PlugUIBase::automation_state_changed ()
724 {
725         reset_button.set_sensitive (insert->can_reset_all_parameters());
726 }
727
728 void
729 PlugUIBase::reset_plugin_parameters ()
730 {
731         insert->reset_parameters_to_default ();
732 }
733
734 void
735 PlugUIBase::manage_pins ()
736 {
737         PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
738         if (proxy) {
739                 proxy->get (true);
740                 proxy->present ();
741                 proxy->get ()->raise();
742         }
743 }
744
745 bool
746 PlugUIBase::bypass_button_release (GdkEventButton*)
747 {
748         bool view_says_bypassed = (bypass_button.active_state() != 0);
749
750         if (view_says_bypassed != insert->enabled ()) {
751                 insert->enable (view_says_bypassed);
752         }
753
754         return false;
755 }
756
757 bool
758 PlugUIBase::focus_toggled (GdkEventButton*)
759 {
760         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
761                 Keyboard::the_keyboard().magic_widget_drop_focus();
762                 focus_button.remove ();
763                 focus_button.add (*focus_out_image);
764                 focus_out_image->show ();
765                 set_tooltip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
766                 KeyboardFocused (false);
767         } else {
768                 Keyboard::the_keyboard().magic_widget_grab_focus();
769                 focus_button.remove ();
770                 focus_button.add (*focus_in_image);
771                 focus_in_image->show ();
772                 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
773                 KeyboardFocused (true);
774         }
775
776         return true;
777 }
778
779 void
780 PlugUIBase::toggle_description()
781 {
782         if (description_expander.get_expanded() &&
783             !description_expander.get_child()) {
784                 const std::string text = plugin->get_docs();
785                 if (text.empty()) {
786                         return;
787                 }
788
789                 Gtk::Label* label = manage(new Gtk::Label(text));
790                 label->set_line_wrap(true);
791                 label->set_line_wrap_mode(Pango::WRAP_WORD);
792                 description_expander.add(*label);
793                 description_expander.show_all();
794         }
795
796         if (!description_expander.get_expanded()) {
797                 const int child_height = description_expander.get_child ()->get_height ();
798
799                 description_expander.remove();
800
801                 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
802
803                 if (toplevel) {
804                         Gtk::Requisition wr;
805                         toplevel->get_size (wr.width, wr.height);
806                         wr.height -= child_height;
807                         toplevel->resize (wr.width, wr.height);
808                 }
809         }
810 }
811
812 void
813 PlugUIBase::toggle_plugin_analysis()
814 {
815         if (plugin_analysis_expander.get_expanded() &&
816             !plugin_analysis_expander.get_child()) {
817                 // Create the GUI
818                 if (eqgui == 0) {
819                         eqgui = new PluginEqGui (insert);
820                 }
821
822                 plugin_analysis_expander.add (*eqgui);
823                 plugin_analysis_expander.show_all ();
824                 eqgui->start_listening ();
825         }
826
827         if (!plugin_analysis_expander.get_expanded()) {
828                 // Hide & remove from expander
829                 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
830
831                 eqgui->hide ();
832                 eqgui->stop_listening ();
833                 plugin_analysis_expander.remove();
834
835                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
836
837                 if (toplevel) {
838                         Gtk::Requisition wr;
839                         toplevel->get_size (wr.width, wr.height);
840                         wr.height -= child_height;
841                         toplevel->resize (wr.width, wr.height);
842                 }
843         }
844 }
845
846 void
847 PlugUIBase::toggle_cpuload_display()
848 {
849         if (cpuload_expander.get_expanded() && !cpuload_expander.get_child()) {
850                 if (stats_gui == 0) {
851                         stats_gui = new PluginLoadStatsGui (insert);
852                 }
853                 cpuload_expander.add (*stats_gui);
854                 cpuload_expander.show_all();
855                 stats_gui->start_updating ();
856         }
857
858         if (!cpuload_expander.get_expanded()) {
859                 const int child_height = cpuload_expander.get_child ()->get_height ();
860
861                 stats_gui->hide ();
862                 stats_gui->stop_updating ();
863                 cpuload_expander.remove();
864
865                 Gtk::Window *toplevel = (Gtk::Window*) cpuload_expander.get_ancestor (GTK_TYPE_WINDOW);
866
867                 if (toplevel) {
868                         Gtk::Requisition wr;
869                         toplevel->get_size (wr.width, wr.height);
870                         wr.height -= child_height;
871                         toplevel->resize (wr.width, wr.height);
872                 }
873         }
874
875 }
876
877 void
878 PlugUIBase::update_preset_list ()
879 {
880         using namespace Menu_Helpers;
881
882         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
883
884         ++_no_load_preset;
885
886         // Add a menu entry for each preset
887         _preset_combo.clear_items();
888         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
889                 _preset_combo.AddMenuElem(
890                         MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
891         }
892
893         // Add an empty entry for un-setting current preset (see preset_selected)
894         Plugin::PresetRecord no_preset;
895         _preset_combo.AddMenuElem(
896                 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
897
898         --_no_load_preset;
899 }
900
901 void
902 PlugUIBase::update_preset ()
903 {
904         Plugin::PresetRecord p = plugin->last_preset();
905
906         ++_no_load_preset;
907         if (p.uri.empty()) {
908                 _preset_combo.set_text (_("(none)"));
909         } else {
910                 _preset_combo.set_text (p.label);
911         }
912         --_no_load_preset;
913
914         delete_button.set_sensitive (!p.uri.empty() && p.user);
915         update_preset_modified ();
916 }
917
918 void
919 PlugUIBase::update_preset_modified ()
920 {
921         Plugin::PresetRecord p = plugin->last_preset();
922
923         if (p.uri.empty()) {
924                 save_button.set_sensitive (false);
925                 _preset_modified.set_text ("");
926                 return;
927         }
928
929         bool const c = plugin->parameter_changed_since_last_preset ();
930         if (_preset_modified.get_text().empty() == c) {
931                 _preset_modified.set_text (c ? "*" : "");
932         }
933         save_button.set_sensitive (c && p.user);
934 }
935
936 void
937 PlugUIBase::preset_added_or_removed ()
938 {
939         /* Update both the list and the currently-displayed preset */
940         update_preset_list ();
941         update_preset ();
942 }
943