7fe878398eb1e56674cf2789a9c0b393739b897d
[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_default_size (w, h);
177         set_resizable (_pluginui->resizable());
178 }
179
180 PluginUIWindow::~PluginUIWindow ()
181 {
182 #ifndef NDEBUG
183         cerr << "PluginWindow deleted for " << this << endl;
184 #endif
185         delete _pluginui;
186 }
187
188 void
189 PluginUIWindow::on_show ()
190 {
191         set_role("plugin_ui");
192
193         if (_pluginui) {
194                 _pluginui->update_preset_list ();
195                 _pluginui->update_preset ();
196         }
197
198         if (_pluginui) {
199 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
200                 if (pre_deactivate_x >= 0) {
201                         move (pre_deactivate_x, pre_deactivate_y);
202                 }
203 #endif
204
205                 if (_pluginui->on_window_show (_title)) {
206                         Window::on_show ();
207                 }
208         }
209 }
210
211 void
212 PluginUIWindow::on_hide ()
213 {
214 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
215         get_position (pre_deactivate_x, pre_deactivate_y);
216 #endif
217
218         Window::on_hide ();
219
220         if (_pluginui) {
221                 _pluginui->on_window_hide ();
222         }
223 }
224
225 void
226 PluginUIWindow::set_title(const std::string& title)
227 {
228         Gtk::Window::set_title(title);
229         _title = title;
230 }
231
232 bool
233 #ifdef WINDOWS_VST_SUPPORT
234 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
235 #else
236 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
237 #endif
238 {
239 #ifndef WINDOWS_VST_SUPPORT
240         return false;
241 #else
242
243         boost::shared_ptr<WindowsVSTPlugin> vp;
244
245         if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
246                 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
247                       << endmsg;
248                 throw failed_constructor ();
249         } else {
250                 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
251
252                 _pluginui = vpu;
253                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
254                 add (*vpu);
255                 vpu->package (*this);
256         }
257
258         return true;
259 #endif
260 }
261
262 bool
263 #ifdef LXVST_SUPPORT
264 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
265 #else
266 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
267 #endif
268 {
269 #ifndef LXVST_SUPPORT
270         return false;
271 #else
272
273         boost::shared_ptr<LXVSTPlugin> lxvp;
274
275         if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
276                 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
277                       << endmsg;
278                 throw failed_constructor ();
279         } else {
280                 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
281
282                 _pluginui = lxvpu;
283                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
284                 add (*lxvpu);
285                 lxvpu->package (*this);
286         }
287
288         return true;
289 #endif
290 }
291
292 bool
293 #ifdef MACVST_SUPPORT
294 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert> insert)
295 #else
296 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert>)
297 #endif
298 {
299 #ifndef MACVST_SUPPORT
300         return false;
301 #else
302         boost::shared_ptr<MacVSTPlugin> mvst;
303         if ((mvst = boost::dynamic_pointer_cast<MacVSTPlugin> (insert->plugin())) == 0) {
304                 error << string_compose (_("unknown type of editor-supplying plugin (note: no MacVST support in this version of %1)"), PROGRAM_NAME)
305                       << endmsg;
306                 throw failed_constructor ();
307         }
308         VSTPluginUI* vpu = create_mac_vst_gui (insert);
309         _pluginui = vpu;
310         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
311         add (*vpu);
312         vpu->package (*this);
313
314         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
315
316         return true;
317 #endif
318 }
319
320
321 bool
322 #ifdef AUDIOUNIT_SUPPORT
323 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
324 #else
325 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
326 #endif
327 {
328 #ifndef AUDIOUNIT_SUPPORT
329         return false;
330 #else
331         VBox* box;
332         _pluginui = create_au_gui (insert, &box);
333         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
334         add (*box);
335
336         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
337
338         return true;
339 #endif
340 }
341
342 void
343 #ifdef __APPLE__
344 PluginUIWindow::app_activated (bool yn)
345 #else
346 PluginUIWindow::app_activated (bool)
347 #endif
348 {
349 #ifdef AUDIOUNIT_SUPPORT
350         if (_pluginui) {
351                 if (yn) {
352                         if (was_visible) {
353                                 _pluginui->activate ();
354                                 if (pre_deactivate_x >= 0) {
355                                         move (pre_deactivate_x, pre_deactivate_y);
356                                 }
357                                 present ();
358                                 was_visible = true;
359                         }
360                 } else {
361                         was_visible = is_visible();
362                         get_position (pre_deactivate_x, pre_deactivate_y);
363                         hide ();
364                         _pluginui->deactivate ();
365                 }
366         }
367 #endif
368 }
369
370 bool
371 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
372 {
373 #ifdef HAVE_SUIL
374         boost::shared_ptr<LV2Plugin> vp;
375
376         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
377                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
378                 throw failed_constructor ();
379         } else {
380                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
381                 _pluginui = lpu;
382                 add (*lpu);
383                 lpu->package (*this);
384                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
385         }
386
387         return true;
388 #else
389         return false;
390 #endif
391 }
392
393 void
394 PluginUIWindow::keyboard_focused (bool yn)
395 {
396         _keyboard_focused = yn;
397 }
398
399 bool
400 PluginUIWindow::on_key_press_event (GdkEventKey* event)
401 {
402         if (_keyboard_focused) {
403                 if (_pluginui) {
404                         _pluginui->grab_focus();
405                         if (_pluginui->non_gtk_gui()) {
406                                 _pluginui->forward_key_event (event);
407                         } else {
408                                         return relay_key_press (event, this);
409                         }
410                 }
411                 return true;
412         }
413         /* for us to be getting key press events, there really
414            MUST be a _pluginui, but just to be safe, check ...
415         */
416
417         if (_pluginui) {
418                 _pluginui->grab_focus();
419                 if (_pluginui->non_gtk_gui()) {
420                         /* pass main window as the window for the event
421                            to be handled in, not this one, because there are
422                            no widgets in this window that we want to have
423                            key focus.
424                         */
425                         return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
426                 } else {
427                         return relay_key_press (event, this);
428                 }
429         }
430
431         return false;
432 }
433
434 bool
435 PluginUIWindow::on_key_release_event (GdkEventKey *event)
436 {
437         if (_keyboard_focused) {
438                 if (_pluginui) {
439                         if (_pluginui->non_gtk_gui()) {
440                                 _pluginui->forward_key_event (event);
441                         }
442                 }
443         } else {
444                 gtk_window_propagate_key_event (GTK_WINDOW(gobj()), event);
445         }
446         /* don't forward releases */
447         return true;
448 }
449
450 void
451 PluginUIWindow::plugin_going_away ()
452 {
453         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
454
455         if (_pluginui) {
456                 _pluginui->stop_updating(0);
457         }
458
459         death_connection.disconnect ();
460 }
461
462 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
463         : insert (pi)
464         , plugin (insert->plugin())
465         , add_button (_("Add"))
466         , save_button (_("Save"))
467         , delete_button (_("Delete"))
468         , reset_button (_("Reset"))
469         , bypass_button (ArdourButton::led_default_elements)
470         , pin_management_button (_("Pinout"))
471         , description_expander (_("Description"))
472         , plugin_analysis_expander (_("Plugin analysis"))
473         , cpuload_expander (_("CPU Profile"))
474         , latency_gui (0)
475         , latency_dialog (0)
476         , eqgui (0)
477         , stats_gui (0)
478         , preset_gui (0)
479 {
480         _preset_modified.set_size_request (16, -1);
481         _preset_combo.set_text("(default)");
482         set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
483         set_tooltip (add_button, _("Save a new preset"));
484         set_tooltip (save_button, _("Save the current preset"));
485         set_tooltip (delete_button, _("Delete the current preset"));
486         set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
487         set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
488         set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
489         _no_load_preset = 0;
490
491         update_preset_list ();
492         update_preset ();
493
494         add_button.set_name ("generic button");
495         add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
496
497         save_button.set_name ("generic button");
498         save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
499
500         delete_button.set_name ("generic button");
501         delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
502
503         reset_button.set_name ("generic button");
504         reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
505
506         pin_management_button.set_name ("generic button");
507         pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
508
509         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
510
511         bypass_button.set_name ("plugin bypass button");
512         bypass_button.set_text (_("Bypass"));
513         bypass_button.set_active (!pi->enabled ());
514         bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
515         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
516
517         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
518         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
519
520         /* these images are not managed, so that we can remove them at will */
521
522         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
523         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
524
525         focus_button.add (*focus_out_image);
526
527         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));
528         set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
529
530         description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
531         description_expander.set_expanded(false);
532
533         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
534         plugin_analysis_expander.set_expanded(false);
535
536         cpuload_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_cpuload_display));
537         cpuload_expander.set_expanded(false);
538
539         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
540
541         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
542         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
543         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
544         plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
545
546         insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
547
548         insert->LatencyChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::set_latency_label, this), gui_context());
549
550         automation_state_changed();
551 }
552
553 PlugUIBase::~PlugUIBase()
554 {
555         delete eqgui;
556         delete stats_gui;
557         delete preset_gui;
558         delete latency_gui;
559         delete latency_dialog;
560 }
561
562 void
563 PlugUIBase::plugin_going_away ()
564 {
565         /* drop references to the plugin/insert */
566         insert.reset ();
567         plugin.reset ();
568 }
569
570 void
571 PlugUIBase::set_latency_label ()
572 {
573         samplecnt_t const l = insert->effective_latency ();
574         samplecnt_t const sr = insert->session().sample_rate ();
575
576         string t;
577
578         if (l < sr / 1000) {
579                 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
580         } else {
581                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
582         }
583
584         latency_button.set_text (t);
585 }
586
587 void
588 PlugUIBase::latency_button_clicked ()
589 {
590         if (!latency_gui) {
591                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().sample_rate(), insert->session().get_block_size());
592                 latency_dialog = new ArdourWindow (_("Edit Latency"));
593                 /* use both keep-above and transient for to try cover as many
594                    different WM's as possible.
595                 */
596                 latency_dialog->set_keep_above (true);
597                 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
598                 if (win) {
599                         latency_dialog->set_transient_for (*win);
600                 }
601                 latency_dialog->add (*latency_gui);
602         }
603
604         latency_gui->refresh ();
605         latency_dialog->show_all ();
606 }
607
608 void
609 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
610 {
611         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
612         boost::shared_ptr<Processor> p (weak_p.lock());
613
614         if (p) {
615                 bypass_button.set_active (!p->enabled ());
616         }
617 }
618
619 void
620 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
621 {
622         if (_no_load_preset) {
623                 return;
624         }
625         if (!preset.label.empty()) {
626                 insert->load_preset (preset);
627         } else {
628                 // blank selected = no preset
629                 plugin->clear_preset();
630         }
631 }
632
633 #ifdef NO_PLUGIN_STATE
634 static bool seen_saving_message = false;
635
636 static void show_no_plugin_message()
637 {
638         info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
639                         PROGRAM_NAME)
640              << endmsg;
641         info << _("To get full access to updates without this limitation\n"
642                   "consider becoming a subscriber for a low cost every month.")
643              << endmsg;
644         info << X_("https://community.ardour.org/s/subscribe")
645              << endmsg;
646         ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
647 }
648 #endif
649
650 void
651 PlugUIBase::add_plugin_setting ()
652 {
653 #ifndef NO_PLUGIN_STATE
654         NewPluginPresetDialog d (plugin, _("New Preset"));
655
656         switch (d.run ()) {
657         case Gtk::RESPONSE_ACCEPT:
658                 if (d.name().empty()) {
659                         break;
660                 }
661
662                 if (d.replace ()) {
663                         plugin->remove_preset (d.name ());
664                 }
665
666                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
667                 if (!r.uri.empty ()) {
668                         plugin->load_preset (r);
669                 }
670                 break;
671         }
672 #else
673         if (!seen_saving_message) {
674                 seen_saving_message = true;
675                 show_no_plugin_message();
676         }
677 #endif
678 }
679
680 void
681 PlugUIBase::save_plugin_setting ()
682 {
683 #ifndef NO_PLUGIN_STATE
684         string const name = _preset_combo.get_text ();
685         plugin->remove_preset (name);
686         Plugin::PresetRecord const r = plugin->save_preset (name);
687         if (!r.uri.empty ()) {
688                 plugin->load_preset (r);
689         }
690 #else
691         if (!seen_saving_message) {
692                 seen_saving_message = true;
693                 show_no_plugin_message();
694         }
695 #endif
696 }
697
698 void
699 PlugUIBase::delete_plugin_setting ()
700 {
701 #ifndef NO_PLUGIN_STATE
702         plugin->remove_preset (_preset_combo.get_text ());
703 #else
704         if (!seen_saving_message) {
705                 seen_saving_message = true;
706                 show_no_plugin_message();
707         }
708 #endif
709 }
710
711 void
712 PlugUIBase::automation_state_changed ()
713 {
714         reset_button.set_sensitive (insert->can_reset_all_parameters());
715 }
716
717 void
718 PlugUIBase::reset_plugin_parameters ()
719 {
720         insert->reset_parameters_to_default ();
721 }
722
723 void
724 PlugUIBase::manage_pins ()
725 {
726         PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
727         if (proxy) {
728                 proxy->get (true);
729                 proxy->present ();
730                 proxy->get ()->raise();
731         }
732 }
733
734 bool
735 PlugUIBase::bypass_button_release (GdkEventButton*)
736 {
737         bool view_says_bypassed = (bypass_button.active_state() != 0);
738
739         if (view_says_bypassed != insert->enabled ()) {
740                 insert->enable (view_says_bypassed);
741         }
742
743         return false;
744 }
745
746 bool
747 PlugUIBase::focus_toggled (GdkEventButton*)
748 {
749         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
750                 Keyboard::the_keyboard().magic_widget_drop_focus();
751                 focus_button.remove ();
752                 focus_button.add (*focus_out_image);
753                 focus_out_image->show ();
754                 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));
755                 KeyboardFocused (false);
756         } else {
757                 Keyboard::the_keyboard().magic_widget_grab_focus();
758                 focus_button.remove ();
759                 focus_button.add (*focus_in_image);
760                 focus_in_image->show ();
761                 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
762                 KeyboardFocused (true);
763         }
764
765         return true;
766 }
767
768 void
769 PlugUIBase::toggle_description()
770 {
771         if (description_expander.get_expanded() &&
772             !description_expander.get_child()) {
773                 const std::string text = plugin->get_docs();
774                 if (text.empty()) {
775                         return;
776                 }
777
778                 Gtk::Label* label = manage(new Gtk::Label(text));
779                 label->set_line_wrap(true);
780                 label->set_line_wrap_mode(Pango::WRAP_WORD);
781                 description_expander.add(*label);
782                 description_expander.show_all();
783         }
784
785         if (!description_expander.get_expanded()) {
786                 const int child_height = description_expander.get_child ()->get_height ();
787
788                 description_expander.remove();
789
790                 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
791
792                 if (toplevel) {
793                         Gtk::Requisition wr;
794                         toplevel->get_size (wr.width, wr.height);
795                         wr.height -= child_height;
796                         toplevel->resize (wr.width, wr.height);
797                 }
798         }
799 }
800
801 void
802 PlugUIBase::toggle_plugin_analysis()
803 {
804         if (plugin_analysis_expander.get_expanded() &&
805             !plugin_analysis_expander.get_child()) {
806                 // Create the GUI
807                 if (eqgui == 0) {
808                         eqgui = new PluginEqGui (insert);
809                 }
810
811                 plugin_analysis_expander.add (*eqgui);
812                 plugin_analysis_expander.show_all ();
813                 eqgui->start_listening ();
814         }
815
816         if (!plugin_analysis_expander.get_expanded()) {
817                 // Hide & remove from expander
818                 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
819
820                 eqgui->hide ();
821                 eqgui->stop_listening ();
822                 plugin_analysis_expander.remove();
823
824                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
825
826                 if (toplevel) {
827                         Gtk::Requisition wr;
828                         toplevel->get_size (wr.width, wr.height);
829                         wr.height -= child_height;
830                         toplevel->resize (wr.width, wr.height);
831                 }
832         }
833 }
834
835 void
836 PlugUIBase::toggle_cpuload_display()
837 {
838         if (cpuload_expander.get_expanded() && !cpuload_expander.get_child()) {
839                 if (stats_gui == 0) {
840                         stats_gui = new PluginLoadStatsGui (insert);
841                 }
842                 cpuload_expander.add (*stats_gui);
843                 cpuload_expander.show_all();
844                 stats_gui->start_updating ();
845         }
846
847         if (!cpuload_expander.get_expanded()) {
848                 const int child_height = cpuload_expander.get_child ()->get_height ();
849
850                 stats_gui->hide ();
851                 stats_gui->stop_updating ();
852                 cpuload_expander.remove();
853
854                 Gtk::Window *toplevel = (Gtk::Window*) cpuload_expander.get_ancestor (GTK_TYPE_WINDOW);
855
856                 if (toplevel) {
857                         Gtk::Requisition wr;
858                         toplevel->get_size (wr.width, wr.height);
859                         wr.height -= child_height;
860                         toplevel->resize (wr.width, wr.height);
861                 }
862         }
863
864 }
865
866 void
867 PlugUIBase::update_preset_list ()
868 {
869         using namespace Menu_Helpers;
870
871         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
872
873         ++_no_load_preset;
874
875         // Add a menu entry for each preset
876         _preset_combo.clear_items();
877         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
878                 _preset_combo.AddMenuElem(
879                         MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
880         }
881
882         // Add an empty entry for un-setting current preset (see preset_selected)
883         Plugin::PresetRecord no_preset;
884         _preset_combo.AddMenuElem(
885                 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
886
887         --_no_load_preset;
888 }
889
890 void
891 PlugUIBase::update_preset ()
892 {
893         Plugin::PresetRecord p = plugin->last_preset();
894
895         ++_no_load_preset;
896         if (p.uri.empty()) {
897                 _preset_combo.set_text (_("(none)"));
898         } else {
899                 _preset_combo.set_text (p.label);
900         }
901         --_no_load_preset;
902
903         delete_button.set_sensitive (!p.uri.empty() && p.user);
904         update_preset_modified ();
905 }
906
907 void
908 PlugUIBase::update_preset_modified ()
909 {
910         Plugin::PresetRecord p = plugin->last_preset();
911
912         if (p.uri.empty()) {
913                 save_button.set_sensitive (false);
914                 _preset_modified.set_text ("");
915                 return;
916         }
917
918         bool const c = plugin->parameter_changed_since_last_preset ();
919         if (_preset_modified.get_text().empty() == c) {
920                 _preset_modified.set_text (c ? "*" : "");
921         }
922         save_button.set_sensitive (c && p.user);
923 }
924
925 void
926 PlugUIBase::preset_added_or_removed ()
927 {
928         /* Update both the list and the currently-displayed preset */
929         update_preset_list ();
930         update_preset ();
931 }
932