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