Merge branch 'master' into windows
[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 #include <gtkmm2ext/click_box.h>
36 #include <gtkmm2ext/fastmeter.h>
37 #include <gtkmm2ext/barcontroller.h>
38 #include <gtkmm2ext/utils.h>
39 #include <gtkmm2ext/doi.h>
40 #include <gtkmm2ext/slider_controller.h>
41 #include <gtkmm2ext/application.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 LV2_SUPPORT
56 #include "ardour/lv2_plugin.h"
57 #include "lv2_plugin_ui.h"
58 #endif
59
60 #include "ardour_window.h"
61 #include "ardour_ui.h"
62 #include "prompter.h"
63 #include "plugin_ui.h"
64 #include "utils.h"
65 #include "gui_thread.h"
66 #include "public_editor.h"
67 #include "keyboard.h"
68 #include "latency_gui.h"
69 #include "plugin_eq_gui.h"
70 #include "new_plugin_preset_dialog.h"
71
72 #include "i18n.h"
73
74 using namespace std;
75 using namespace ARDOUR;
76 using namespace PBD;
77 using namespace Gtkmm2ext;
78 using namespace Gtk;
79
80 PluginUIWindow::PluginUIWindow (
81         boost::shared_ptr<PluginInsert> insert,
82         bool                            scrollable,
83         bool                            editor)
84         : ArdourWindow (string())
85         , was_visible (false)
86         , _keyboard_focused (false)
87 #ifdef AUDIOUNIT_SUPPORT
88         , pre_deactivate_x (-1)
89         , pre_deactivate_y (-1)
90 #endif
91
92 {
93         bool have_gui = false;
94         Label* label = manage (new Label());
95         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
96
97         if (editor && insert->plugin()->has_editor()) {
98                 switch (insert->type()) {
99                 case ARDOUR::Windows_VST:
100                         have_gui = create_windows_vst_editor (insert);
101                         break;
102                         
103                 case ARDOUR::LXVST:
104                         have_gui = create_lxvst_editor (insert);
105                         break;
106
107                 case ARDOUR::AudioUnit:
108                         have_gui = create_audiounit_editor (insert);
109                         break;
110
111                 case ARDOUR::LADSPA:
112                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
113                         break;
114
115                 case ARDOUR::LV2:
116                         have_gui = create_lv2_editor (insert);
117                         break;
118
119                 default:
120 #ifndef WINDOWS_VST_SUPPORT
121                         error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
122                               << endmsg;
123 #else
124                         error << _("unknown type of editor-supplying plugin")
125                               << endmsg;
126 #endif
127                         throw failed_constructor ();
128                 }
129
130         }
131
132         if (!have_gui) {
133                 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
134
135                 _pluginui = pu;
136                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
137                 add (*pu);
138                 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
139
140                 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
141                 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
142         }
143
144         set_name ("PluginEditor");
145         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
146
147         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
148
149         gint h = _pluginui->get_preferred_height ();
150         gint w = _pluginui->get_preferred_width ();
151
152         if (scrollable) {
153                 if (h > 600) h = 600;
154         }
155
156         set_default_size (w, h);
157         set_resizable (_pluginui->resizable());
158 }
159
160 PluginUIWindow::~PluginUIWindow ()
161 {
162         cerr << "PluginWindow deleted for " << this << endl;
163         delete _pluginui;
164 }
165
166 void
167 PluginUIWindow::on_show ()
168 {
169         set_role("plugin_ui");
170
171         if (_pluginui) {
172                 _pluginui->update_preset_list ();
173                 _pluginui->update_preset ();
174         }
175
176         if (_pluginui) {
177 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
178                 if (pre_deactivate_x >= 0) {                                                                             
179                         move (pre_deactivate_x, pre_deactivate_y);
180                 }                                                      
181 #endif
182
183                 if (_pluginui->on_window_show (_title)) {
184                         Window::on_show ();
185                 }
186         }
187 }
188
189 void
190 PluginUIWindow::on_hide ()
191 {
192 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
193         get_position (pre_deactivate_x, pre_deactivate_y);                                                               
194 #endif
195
196         Window::on_hide ();
197
198         if (_pluginui) {
199                 _pluginui->on_window_hide ();
200         }
201 }
202
203 void
204 PluginUIWindow::set_title(const std::string& title)
205 {
206         Gtk::Window::set_title(title);
207         _title = title;
208 }
209
210 bool
211 #ifdef WINDOWS_VST_SUPPORT
212 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
213 #else
214 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
215 #endif
216 {
217 #ifndef WINDOWS_VST_SUPPORT
218         return false;
219 #else
220
221         boost::shared_ptr<WindowsVSTPlugin> vp;
222
223         if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
224                 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
225                       << endmsg;
226                 throw failed_constructor ();
227         } else {
228                 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp);
229
230                 _pluginui = vpu;
231                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
232                 add (*vpu);
233                 vpu->package (*this);
234         }
235
236         return true;
237 #endif
238 }
239
240 bool
241 #ifdef LXVST_SUPPORT
242 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
243 #else
244 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
245 #endif
246 {
247 #ifndef LXVST_SUPPORT
248         return false;
249 #else
250
251         boost::shared_ptr<LXVSTPlugin> lxvp;
252
253         if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
254                 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
255                       << endmsg;
256                 throw failed_constructor ();
257         } else {
258                 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
259
260                 _pluginui = lxvpu;
261                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
262                 add (*lxvpu);
263                 lxvpu->package (*this);
264         }
265
266         return true;
267 #endif
268 }
269
270 bool
271 #ifdef AUDIOUNIT_SUPPORT
272 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
273 #else
274 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
275 #endif
276 {
277 #ifndef AUDIOUNIT_SUPPORT
278         return false;
279 #else
280         VBox* box;
281         _pluginui = create_au_gui (insert, &box);
282         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
283         add (*box);
284
285         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
286
287         return true;
288 #endif
289 }
290
291 void
292 #ifdef GTKOSX
293 PluginUIWindow::app_activated (bool yn)
294 #else
295 PluginUIWindow::app_activated (bool)
296 #endif
297 {
298 #ifdef AUDIOUNIT_SUPPORT
299         if (_pluginui) {
300                 if (yn) {
301                         if (was_visible) {
302                                 _pluginui->activate ();
303                                 if (pre_deactivate_x >= 0) {
304                                         move (pre_deactivate_x, pre_deactivate_y);
305                                 }
306                                 present ();
307                                 was_visible = true;
308                         }
309                 } else {
310                         was_visible = is_visible();
311                         get_position (pre_deactivate_x, pre_deactivate_y);
312                         hide ();
313                         _pluginui->deactivate ();
314                 }
315         }
316 #endif
317 }
318
319 bool
320 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
321 {
322 #ifdef HAVE_SUIL
323         boost::shared_ptr<LV2Plugin> vp;
324
325         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
326                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
327                 throw failed_constructor ();
328         } else {
329                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
330                 _pluginui = lpu;
331                 add (*lpu);
332                 lpu->package (*this);
333         }
334
335         return true;
336 #else
337         return false;
338 #endif
339 }
340
341 void
342 PluginUIWindow::keyboard_focused (bool yn)
343 {
344         _keyboard_focused = yn;
345 }
346
347 bool
348 PluginUIWindow::on_key_press_event (GdkEventKey* event)
349 {
350         if (_keyboard_focused) {
351                 if (_pluginui) {
352                         if (_pluginui->non_gtk_gui()) {
353                                 _pluginui->forward_key_event (event);
354                         } else {
355                                 return relay_key_press (event, this);
356                         }
357                 }
358                 return true;
359         } else {
360                 /* for us to be getting key press events, there really
361                    MUST be a _pluginui, but just to be safe, check ...
362                 */
363
364                 if (_pluginui) {
365                         if (_pluginui->non_gtk_gui()) {
366                                 /* pass editor window as the window for the event
367                                    to be handled in, not this one, because there are
368                                    no widgets in this window that we want to have
369                                    key focus.
370                                 */
371                                 return relay_key_press (event, &PublicEditor::instance());
372                         } else {
373                                 return relay_key_press (event, this);
374                         }
375                 } else {
376                         return false;
377                 }
378         }
379 }
380
381 bool
382 PluginUIWindow::on_key_release_event (GdkEventKey *event)
383 {
384         if (_keyboard_focused) {
385                 if (_pluginui) {
386                         if (_pluginui->non_gtk_gui()) {
387                                 _pluginui->forward_key_event (event);
388                         }
389                         return true;
390                 }
391                 return false;
392         } else {
393                 return true;
394         }
395 }
396
397 void
398 PluginUIWindow::plugin_going_away ()
399 {
400         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
401
402         if (_pluginui) {
403                 _pluginui->stop_updating(0);
404         }
405
406         death_connection.disconnect ();
407 }
408
409 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
410         : insert (pi)
411         , plugin (insert->plugin())
412         , add_button (_("Add"))
413         , save_button (_("Save"))
414         , delete_button (_("Delete"))
415         , bypass_button (ArdourButton::led_default_elements)
416         , description_expander (_("Description"))
417         , plugin_analysis_expander (_("Plugin analysis"))
418         , latency_gui (0)
419         , latency_dialog (0)
420         , eqgui (0)
421 {
422         _preset_modified.set_size_request (16, -1);
423         _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
424         ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
425         ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
426         ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
427         ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
428         ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
429         _no_load_preset = 0;
430
431         update_preset_list ();
432         update_preset ();
433
434         add_button.set_name ("PluginAddButton");
435         add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
436
437         save_button.set_name ("PluginSaveButton");
438         save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
439
440         delete_button.set_name ("PluginDeleteButton");
441         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
442
443         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
444
445         bypass_button.set_name ("plugin bypass button");
446         bypass_button.set_text (_("Bypass"));
447         bypass_button.set_active (!pi->active());
448         bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
449         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
450
451         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
452         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
453
454         /* these images are not managed, so that we can remove them at will */
455
456         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
457         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
458
459         focus_button.add (*focus_out_image);
460
461         ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
462         ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
463
464         description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
465         description_expander.set_expanded(false);
466
467         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
468         plugin_analysis_expander.set_expanded(false);
469
470         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
471
472         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
473         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
474         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
475         plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
476 }
477
478 PlugUIBase::~PlugUIBase()
479 {
480         delete eqgui;
481         delete latency_gui;
482 }
483
484 void
485 PlugUIBase::plugin_going_away ()
486 {
487         /* drop references to the plugin/insert */
488         insert.reset ();
489         plugin.reset ();
490 }
491
492 void
493 PlugUIBase::set_latency_label ()
494 {
495         framecnt_t const l = insert->effective_latency ();
496         framecnt_t const sr = insert->session().frame_rate ();
497
498         string t;
499
500         if (l < sr / 1000) {
501                 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
502         } else {
503                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
504         }
505
506         latency_label.set_text (t);
507 }
508
509 void
510 PlugUIBase::latency_button_clicked ()
511 {
512         if (!latency_gui) {
513                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
514                 latency_dialog = new ArdourWindow (_("Edit Latency"));
515                 /* use both keep-above and transient for to try cover as many
516                    different WM's as possible.
517                 */
518                 latency_dialog->set_keep_above (true);
519                 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
520                 if (win) {
521                         latency_dialog->set_transient_for (*win);
522                 }
523                 latency_dialog->add (*latency_gui);
524                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
525         }
526
527         latency_dialog->show_all ();
528 }
529
530 void
531 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
532 {
533         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
534         boost::shared_ptr<Processor> p (weak_p.lock());
535
536         if (p) {
537                 bypass_button.set_active (!p->active());
538         }
539 }
540
541 void
542 PlugUIBase::preset_selected ()
543 {
544         if (_no_load_preset) {
545                 return;
546         }
547
548         if (_preset_combo.get_active_text().length() > 0) {
549                 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
550                 if (pr) {
551                         plugin->load_preset (*pr);
552                 } else {
553                         warning << string_compose(_("Plugin preset %1 not found"),
554                                                   _preset_combo.get_active_text()) << endmsg;
555                 }
556         } else {
557                 // blank selected = no preset
558                 plugin->clear_preset();
559         }
560 }
561
562 #ifdef NO_PLUGIN_STATE
563 static bool seen_saving_message = false;
564 #endif
565
566 void
567 PlugUIBase::add_plugin_setting ()
568 {
569 #ifndef NO_PLUGIN_STATE
570         NewPluginPresetDialog d (plugin);
571
572         switch (d.run ()) {
573         case Gtk::RESPONSE_ACCEPT:
574                 if (d.name().empty()) {
575                         break;
576                 }
577
578                 if (d.replace ()) {
579                         plugin->remove_preset (d.name ());
580                 }
581
582                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
583                 if (!r.uri.empty ()) {
584                         plugin->load_preset (r);
585                 }
586                 break;
587         }
588 #else 
589         if (!seen_saving_message) {
590                 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
591                                         PROGRAM_NAME)
592                      << endmsg;
593                 seen_saving_message = true;
594         }
595 #endif
596 }
597
598 void
599 PlugUIBase::save_plugin_setting ()
600 {
601 #ifndef NO_PLUGIN_STATE
602         string const name = _preset_combo.get_active_text ();
603         plugin->remove_preset (name);
604         Plugin::PresetRecord const r = plugin->save_preset (name);
605         if (!r.uri.empty ()) {
606                 plugin->load_preset (r);
607         }
608 #else 
609         if (!seen_saving_message) {
610                 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
611                                         PROGRAM_NAME)
612                      << endmsg;
613                 seen_saving_message = true;
614         }
615 #endif
616 }
617
618 void
619 PlugUIBase::delete_plugin_setting ()
620 {
621 #ifndef NO_PLUGIN_STATE
622         plugin->remove_preset (_preset_combo.get_active_text ());
623 #else
624         if (!seen_saving_message) {
625                 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
626                                         PROGRAM_NAME)
627                      << endmsg;
628                 seen_saving_message = true;
629         }
630 #endif
631 }
632
633 bool
634 PlugUIBase::bypass_button_release (GdkEventButton*)
635 {
636         bool view_says_bypassed = (bypass_button.active_state() != 0);
637         
638         if (view_says_bypassed != insert->active()) {
639                 if (view_says_bypassed) {
640                         insert->activate ();
641                 } else {
642                         insert->deactivate ();
643                 }
644         }
645
646         return false;
647 }
648
649 bool
650 PlugUIBase::focus_toggled (GdkEventButton*)
651 {
652         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
653                 Keyboard::the_keyboard().magic_widget_drop_focus();
654                 focus_button.remove ();
655                 focus_button.add (*focus_out_image);
656                 focus_out_image->show ();
657                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
658                 KeyboardFocused (false);
659         } else {
660                 Keyboard::the_keyboard().magic_widget_grab_focus();
661                 focus_button.remove ();
662                 focus_button.add (*focus_in_image);
663                 focus_in_image->show ();
664                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
665                 KeyboardFocused (true);
666         }
667
668         return true;
669 }
670
671 void
672 PlugUIBase::toggle_description()
673 {
674         if (description_expander.get_expanded() &&
675             !description_expander.get_child()) {
676                 const std::string text = plugin->get_docs();
677                 if (text.empty()) {
678                         return;
679                 }
680
681                 Gtk::Label* label = manage(new Gtk::Label(text));
682                 label->set_line_wrap(true);
683                 label->set_line_wrap_mode(Pango::WRAP_WORD);
684                 description_expander.add(*label);
685                 description_expander.show_all();
686         }
687         
688         if (!description_expander.get_expanded()) {
689                 description_expander.remove();
690         }
691 }
692
693
694 void
695 PlugUIBase::toggle_plugin_analysis()
696 {
697         if (plugin_analysis_expander.get_expanded() &&
698             !plugin_analysis_expander.get_child()) {
699                 // Create the GUI
700                 if (eqgui == 0) {
701                         eqgui = new PluginEqGui (insert);
702                 }
703
704                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
705
706                 if (toplevel) {
707                         toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
708                 }
709
710                 plugin_analysis_expander.add (*eqgui);
711                 plugin_analysis_expander.show_all ();
712                 eqgui->start_listening ();
713         }
714
715         if (!plugin_analysis_expander.get_expanded()) {
716                 // Hide & remove from expander
717
718                 eqgui->hide ();
719                 eqgui->stop_listening ();
720                 plugin_analysis_expander.remove();
721
722                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
723
724                 if (toplevel) {
725                         toplevel->resize (pre_eq_size.width, pre_eq_size.height);
726                 }
727         }
728 }
729
730 void
731 PlugUIBase::update_preset_list ()
732 {
733         vector<string> preset_labels;
734         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
735
736         ++_no_load_preset;
737
738         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
739                 preset_labels.push_back (i->label);
740         }
741
742         preset_labels.push_back("");
743
744         set_popdown_strings (_preset_combo, preset_labels);
745
746         --_no_load_preset;
747 }
748
749 void
750 PlugUIBase::update_preset ()
751 {
752         Plugin::PresetRecord p = plugin->last_preset();
753
754         ++_no_load_preset;
755         _preset_combo.set_active_text (p.label);
756         --_no_load_preset;
757
758         save_button.set_sensitive (!p.uri.empty() && p.user);
759         delete_button.set_sensitive (!p.uri.empty() && p.user);
760
761         update_preset_modified ();
762 }
763
764 void
765 PlugUIBase::update_preset_modified ()
766 {
767
768         if (plugin->last_preset().uri.empty()) {
769                 _preset_modified.set_text ("");
770                 return;
771         }
772
773         bool const c = plugin->parameter_changed_since_last_preset ();
774         if (_preset_modified.get_text().empty() == c) {
775                 _preset_modified.set_text (c ? "*" : "");
776         }
777 }
778
779 void
780 PlugUIBase::parameter_changed (uint32_t, float)
781 {
782         update_preset_modified ();
783 }
784
785 void
786 PlugUIBase::preset_added_or_removed ()
787 {
788         /* Update both the list and the currently-displayed preset */
789         update_preset_list ();
790         update_preset ();
791 }
792