Merge branch 'ripple-mode-cc' into cairocanvas
[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 ARDOUR_UI_UTILS;
77 using namespace PBD;
78 using namespace Gtkmm2ext;
79 using namespace Gtk;
80
81 PluginUIWindow::PluginUIWindow (
82         boost::shared_ptr<PluginInsert> insert,
83         bool                            scrollable,
84         bool                            editor)
85         : ArdourWindow (string())
86         , was_visible (false)
87         , _keyboard_focused (false)
88 #ifdef AUDIOUNIT_SUPPORT
89         , pre_deactivate_x (-1)
90         , pre_deactivate_y (-1)
91 #endif
92
93 {
94         bool have_gui = false;
95         Label* label = manage (new Label());
96         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
97
98         if (editor && insert->plugin()->has_editor()) {
99                 switch (insert->type()) {
100                 case ARDOUR::Windows_VST:
101                         have_gui = create_windows_vst_editor (insert);
102                         break;
103                         
104                 case ARDOUR::LXVST:
105                         have_gui = create_lxvst_editor (insert);
106                         break;
107
108                 case ARDOUR::AudioUnit:
109                         have_gui = create_audiounit_editor (insert);
110                         break;
111
112                 case ARDOUR::LADSPA:
113                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
114                         break;
115
116                 case ARDOUR::LV2:
117                         have_gui = create_lv2_editor (insert);
118                         break;
119
120                 default:
121 #ifndef WINDOWS_VST_SUPPORT
122                         error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
123                               << endmsg;
124 #else
125                         error << _("unknown type of editor-supplying plugin")
126                               << endmsg;
127 #endif
128                         throw failed_constructor ();
129                 }
130
131         }
132
133         if (!have_gui) {
134                 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
135
136                 _pluginui = pu;
137                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
138                 add (*pu);
139                 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
140
141                 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
142                 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
143         }
144
145         set_name ("PluginEditor");
146         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
147
148         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
149
150         gint h = _pluginui->get_preferred_height ();
151         gint w = _pluginui->get_preferred_width ();
152
153         if (scrollable) {
154                 if (h > 600) h = 600;
155         }
156
157         set_default_size (w, h);
158         set_resizable (_pluginui->resizable());
159 }
160
161 PluginUIWindow::~PluginUIWindow ()
162 {
163 #ifndef NDEBUG
164         cerr << "PluginWindow deleted for " << this << endl;
165 #endif
166         delete _pluginui;
167 }
168
169 void
170 PluginUIWindow::on_show ()
171 {
172         set_role("plugin_ui");
173
174         if (_pluginui) {
175                 _pluginui->update_preset_list ();
176                 _pluginui->update_preset ();
177         }
178
179         if (_pluginui) {
180 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
181                 if (pre_deactivate_x >= 0) {                                                                             
182                         move (pre_deactivate_x, pre_deactivate_y);
183                 }                                                      
184 #endif
185
186                 if (_pluginui->on_window_show (_title)) {
187                         Window::on_show ();
188                 }
189         }
190 }
191
192 void
193 PluginUIWindow::on_hide ()
194 {
195 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
196         get_position (pre_deactivate_x, pre_deactivate_y);                                                               
197 #endif
198
199         Window::on_hide ();
200
201         if (_pluginui) {
202                 _pluginui->on_window_hide ();
203         }
204 }
205
206 void
207 PluginUIWindow::set_title(const std::string& title)
208 {
209         Gtk::Window::set_title(title);
210         _title = title;
211 }
212
213 bool
214 #ifdef WINDOWS_VST_SUPPORT
215 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
216 #else
217 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
218 #endif
219 {
220 #ifndef WINDOWS_VST_SUPPORT
221         return false;
222 #else
223
224         boost::shared_ptr<WindowsVSTPlugin> vp;
225
226         if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
227                 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
228                       << endmsg;
229                 throw failed_constructor ();
230         } else {
231                 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
232
233                 _pluginui = vpu;
234                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
235                 add (*vpu);
236                 vpu->package (*this);
237         }
238
239         return true;
240 #endif
241 }
242
243 bool
244 #ifdef LXVST_SUPPORT
245 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
246 #else
247 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
248 #endif
249 {
250 #ifndef LXVST_SUPPORT
251         return false;
252 #else
253
254         boost::shared_ptr<LXVSTPlugin> lxvp;
255
256         if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
257                 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
258                       << endmsg;
259                 throw failed_constructor ();
260         } else {
261                 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
262
263                 _pluginui = lxvpu;
264                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
265                 add (*lxvpu);
266                 lxvpu->package (*this);
267         }
268
269         return true;
270 #endif
271 }
272
273 bool
274 #ifdef AUDIOUNIT_SUPPORT
275 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
276 #else
277 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
278 #endif
279 {
280 #ifndef AUDIOUNIT_SUPPORT
281         return false;
282 #else
283         VBox* box;
284         _pluginui = create_au_gui (insert, &box);
285         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
286         add (*box);
287
288         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
289
290         return true;
291 #endif
292 }
293
294 void
295 #ifdef GTKOSX
296 PluginUIWindow::app_activated (bool yn)
297 #else
298 PluginUIWindow::app_activated (bool)
299 #endif
300 {
301 #ifdef AUDIOUNIT_SUPPORT
302         if (_pluginui) {
303                 if (yn) {
304                         if (was_visible) {
305                                 _pluginui->activate ();
306                                 if (pre_deactivate_x >= 0) {
307                                         move (pre_deactivate_x, pre_deactivate_y);
308                                 }
309                                 present ();
310                                 was_visible = true;
311                         }
312                 } else {
313                         was_visible = is_visible();
314                         get_position (pre_deactivate_x, pre_deactivate_y);
315                         hide ();
316                         _pluginui->deactivate ();
317                 }
318         }
319 #endif
320 }
321
322 bool
323 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
324 {
325 #ifdef HAVE_SUIL
326         boost::shared_ptr<LV2Plugin> vp;
327
328         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
329                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
330                 throw failed_constructor ();
331         } else {
332                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
333                 _pluginui = lpu;
334                 add (*lpu);
335                 lpu->package (*this);
336         }
337
338         return true;
339 #else
340         return false;
341 #endif
342 }
343
344 void
345 PluginUIWindow::keyboard_focused (bool yn)
346 {
347         _keyboard_focused = yn;
348 }
349
350 bool
351 PluginUIWindow::on_key_press_event (GdkEventKey* event)
352 {
353         if (_keyboard_focused) {
354                 if (_pluginui) {
355                         if (_pluginui->non_gtk_gui()) {
356                                 _pluginui->forward_key_event (event);
357                         } else {
358                                 return relay_key_press (event, this);
359                         }
360                 }
361                 return true;
362         } else {
363                 /* for us to be getting key press events, there really
364                    MUST be a _pluginui, but just to be safe, check ...
365                 */
366
367                 if (_pluginui) {
368                         if (_pluginui->non_gtk_gui()) {
369                                 /* pass editor window as the window for the event
370                                    to be handled in, not this one, because there are
371                                    no widgets in this window that we want to have
372                                    key focus.
373                                 */
374                                 return relay_key_press (event, &PublicEditor::instance());
375                         } else {
376                                 return relay_key_press (event, this);
377                         }
378                 } else {
379                         return false;
380                 }
381         }
382 }
383
384 bool
385 PluginUIWindow::on_key_release_event (GdkEventKey *event)
386 {
387         if (_keyboard_focused) {
388                 if (_pluginui) {
389                         if (_pluginui->non_gtk_gui()) {
390                                 _pluginui->forward_key_event (event);
391                         }
392                         return true;
393                 }
394                 return false;
395         } else {
396                 return true;
397         }
398 }
399
400 void
401 PluginUIWindow::plugin_going_away ()
402 {
403         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
404
405         if (_pluginui) {
406                 _pluginui->stop_updating(0);
407         }
408
409         death_connection.disconnect ();
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