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