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