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