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