globally remove all trailing whitespace from ardour code base.
[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 "prompter.h"
62 #include "plugin_ui.h"
63 #include "utils.h"
64 #include "gui_thread.h"
65 #include "public_editor.h"
66 #include "keyboard.h"
67 #include "latency_gui.h"
68 #include "plugin_eq_gui.h"
69 #include "new_plugin_preset_dialog.h"
70 #include "tooltips.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                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
337         }
338
339         return true;
340 #else
341         return false;
342 #endif
343 }
344
345 void
346 PluginUIWindow::keyboard_focused (bool yn)
347 {
348         _keyboard_focused = yn;
349 }
350
351 bool
352 PluginUIWindow::on_key_press_event (GdkEventKey* event)
353 {
354         if (_keyboard_focused) {
355                 if (_pluginui) {
356                         _pluginui->grab_focus();
357                         if (_pluginui->non_gtk_gui()) {
358                                 _pluginui->forward_key_event (event);
359                         } else {
360                                         return relay_key_press (event, this);
361                         }
362                 }
363                 return true;
364         } else {
365                 /* for us to be getting key press events, there really
366                    MUST be a _pluginui, but just to be safe, check ...
367                 */
368
369                 if (_pluginui) {
370                         _pluginui->grab_focus();
371                         if (_pluginui->non_gtk_gui()) {
372                                 /* pass editor window as the window for the event
373                                    to be handled in, not this one, because there are
374                                    no widgets in this window that we want to have
375                                    key focus.
376                                 */
377                                 return relay_key_press (event, &PublicEditor::instance());
378                         } else {
379                                 return relay_key_press (event, this);
380                         }
381                 } else {
382                         return false;
383                 }
384         }
385 }
386
387 bool
388 PluginUIWindow::on_key_release_event (GdkEventKey *event)
389 {
390         if (_keyboard_focused) {
391                 if (_pluginui) {
392                         if (_pluginui->non_gtk_gui()) {
393                                 _pluginui->forward_key_event (event);
394                         }
395                         return true;
396                 }
397                 return false;
398         } else {
399                 return true;
400         }
401 }
402
403 void
404 PluginUIWindow::plugin_going_away ()
405 {
406         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
407
408         if (_pluginui) {
409                 _pluginui->stop_updating(0);
410         }
411
412         death_connection.disconnect ();
413 }
414
415 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
416         : insert (pi)
417         , plugin (insert->plugin())
418         , add_button (_("Add"))
419         , save_button (_("Save"))
420         , delete_button (_("Delete"))
421         , reset_button (_("Reset"))
422         , bypass_button (ArdourButton::led_default_elements)
423         , description_expander (_("Description"))
424         , plugin_analysis_expander (_("Plugin analysis"))
425         , latency_gui (0)
426         , latency_dialog (0)
427         , eqgui (0)
428 {
429         _preset_modified.set_size_request (16, -1);
430         _preset_combo.set_text("(default)");
431         set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
432         set_tooltip (add_button, _("Save a new preset"));
433         set_tooltip (save_button, _("Save the current preset"));
434         set_tooltip (delete_button, _("Delete the current preset"));
435         set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
436         set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
437         _no_load_preset = 0;
438
439         update_preset_list ();
440         update_preset ();
441
442         add_button.set_name ("generic button");
443         add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
444
445         save_button.set_name ("generic button");
446         save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
447
448         delete_button.set_name ("generic button");
449         delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
450
451         reset_button.set_name ("generic button");
452         reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
453
454
455         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
456
457         bypass_button.set_name ("plugin bypass button");
458         bypass_button.set_text (_("Bypass"));
459         bypass_button.set_active (!pi->active());
460         bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
461         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
462
463         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
464         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
465
466         /* these images are not managed, so that we can remove them at will */
467
468         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
469         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
470
471         focus_button.add (*focus_out_image);
472
473         set_tooltip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
474         set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
475
476         description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
477         description_expander.set_expanded(false);
478
479         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
480         plugin_analysis_expander.set_expanded(false);
481
482         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
483
484         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
485         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
486         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
487         plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
488
489         insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
490
491         automation_state_changed();
492 }
493
494 PlugUIBase::~PlugUIBase()
495 {
496         delete eqgui;
497         delete latency_gui;
498 }
499
500 void
501 PlugUIBase::plugin_going_away ()
502 {
503         /* drop references to the plugin/insert */
504         insert.reset ();
505         plugin.reset ();
506 }
507
508 void
509 PlugUIBase::set_latency_label ()
510 {
511         framecnt_t const l = insert->effective_latency ();
512         framecnt_t const sr = insert->session().frame_rate ();
513
514         string t;
515
516         if (l < sr / 1000) {
517                 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
518         } else {
519                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
520         }
521
522         latency_button.set_text (t);
523 }
524
525 void
526 PlugUIBase::latency_button_clicked ()
527 {
528         if (!latency_gui) {
529                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
530                 latency_dialog = new ArdourWindow (_("Edit Latency"));
531                 /* use both keep-above and transient for to try cover as many
532                    different WM's as possible.
533                 */
534                 latency_dialog->set_keep_above (true);
535                 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
536                 if (win) {
537                         latency_dialog->set_transient_for (*win);
538                 }
539                 latency_dialog->add (*latency_gui);
540                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
541         }
542
543         latency_dialog->show_all ();
544 }
545
546 void
547 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
548 {
549         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
550         boost::shared_ptr<Processor> p (weak_p.lock());
551
552         if (p) {
553                 bypass_button.set_active (!p->active());
554         }
555 }
556
557 void
558 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
559 {
560         if (_no_load_preset) {
561                 return;
562         }
563         if (!preset.label.empty()) {
564                 plugin->load_preset (preset);
565         } else {
566                 // blank selected = no preset
567                 plugin->clear_preset();
568         }
569 }
570
571 #ifdef NO_PLUGIN_STATE
572 static bool seen_saving_message = false;
573
574 static void show_no_plugin_message()
575 {
576         info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
577                         PROGRAM_NAME)
578              << endmsg;
579         info << _("To get full access to updates without this limitation\n"
580                   "consider becoming a subscriber for a low cost every month.")
581              << endmsg;
582         info << X_("https://community.ardour.org/s/subscribe")
583              << endmsg;
584         ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
585 }
586 #endif
587
588 void
589 PlugUIBase::add_plugin_setting ()
590 {
591 #ifndef NO_PLUGIN_STATE
592         NewPluginPresetDialog d (plugin);
593
594         switch (d.run ()) {
595         case Gtk::RESPONSE_ACCEPT:
596                 if (d.name().empty()) {
597                         break;
598                 }
599
600                 if (d.replace ()) {
601                         plugin->remove_preset (d.name ());
602                 }
603
604                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
605                 if (!r.uri.empty ()) {
606                         plugin->load_preset (r);
607                 }
608                 break;
609         }
610 #else
611         if (!seen_saving_message) {
612                 seen_saving_message = true;
613                 show_no_plugin_message();
614         }
615 #endif
616 }
617
618 void
619 PlugUIBase::save_plugin_setting ()
620 {
621 #ifndef NO_PLUGIN_STATE
622         string const name = _preset_combo.get_text ();
623         plugin->remove_preset (name);
624         Plugin::PresetRecord const r = plugin->save_preset (name);
625         if (!r.uri.empty ()) {
626                 plugin->load_preset (r);
627         }
628 #else
629         if (!seen_saving_message) {
630                 seen_saving_message = true;
631                 show_no_plugin_message();
632         }
633 #endif
634 }
635
636 void
637 PlugUIBase::delete_plugin_setting ()
638 {
639 #ifndef NO_PLUGIN_STATE
640         plugin->remove_preset (_preset_combo.get_text ());
641 #else
642         if (!seen_saving_message) {
643                 seen_saving_message = true;
644                 show_no_plugin_message();
645         }
646 #endif
647 }
648
649 void
650 PlugUIBase::automation_state_changed ()
651 {
652         reset_button.set_sensitive (insert->can_reset_all_parameters());
653 }
654
655 void
656 PlugUIBase::reset_plugin_parameters ()
657 {
658         insert->reset_parameters_to_default ();
659 }
660
661 bool
662 PlugUIBase::bypass_button_release (GdkEventButton*)
663 {
664         bool view_says_bypassed = (bypass_button.active_state() != 0);
665         
666         if (view_says_bypassed != insert->active()) {
667                 if (view_says_bypassed) {
668                         insert->activate ();
669                 } else {
670                         insert->deactivate ();
671                 }
672         }
673
674         return false;
675 }
676
677 bool
678 PlugUIBase::focus_toggled (GdkEventButton*)
679 {
680         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
681                 Keyboard::the_keyboard().magic_widget_drop_focus();
682                 focus_button.remove ();
683                 focus_button.add (*focus_out_image);
684                 focus_out_image->show ();
685                 set_tooltip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
686                 KeyboardFocused (false);
687         } else {
688                 Keyboard::the_keyboard().magic_widget_grab_focus();
689                 focus_button.remove ();
690                 focus_button.add (*focus_in_image);
691                 focus_in_image->show ();
692                 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
693                 KeyboardFocused (true);
694         }
695
696         return true;
697 }
698
699 void
700 PlugUIBase::toggle_description()
701 {
702         if (description_expander.get_expanded() &&
703             !description_expander.get_child()) {
704                 const std::string text = plugin->get_docs();
705                 if (text.empty()) {
706                         return;
707                 }
708
709                 Gtk::Label* label = manage(new Gtk::Label(text));
710                 label->set_line_wrap(true);
711                 label->set_line_wrap_mode(Pango::WRAP_WORD);
712                 description_expander.add(*label);
713                 description_expander.show_all();
714         }
715         
716         if (!description_expander.get_expanded()) {
717                 description_expander.remove();
718         }
719 }
720
721
722 void
723 PlugUIBase::toggle_plugin_analysis()
724 {
725         if (plugin_analysis_expander.get_expanded() &&
726             !plugin_analysis_expander.get_child()) {
727                 // Create the GUI
728                 if (eqgui == 0) {
729                         eqgui = new PluginEqGui (insert);
730                 }
731
732                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
733
734                 if (toplevel) {
735                         toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
736                 }
737
738                 plugin_analysis_expander.add (*eqgui);
739                 plugin_analysis_expander.show_all ();
740                 eqgui->start_listening ();
741         }
742
743         if (!plugin_analysis_expander.get_expanded()) {
744                 // Hide & remove from expander
745
746                 eqgui->hide ();
747                 eqgui->stop_listening ();
748                 plugin_analysis_expander.remove();
749
750                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
751
752                 if (toplevel) {
753                         toplevel->resize (pre_eq_size.width, pre_eq_size.height);
754                 }
755         }
756 }
757
758 void
759 PlugUIBase::update_preset_list ()
760 {
761         using namespace Menu_Helpers;
762
763         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
764
765         ++_no_load_preset;
766
767         // Add a menu entry for each preset
768         _preset_combo.clear_items();
769         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
770                 _preset_combo.AddMenuElem(
771                         MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
772         }
773
774         // Add an empty entry for un-setting current preset (see preset_selected)
775         Plugin::PresetRecord no_preset;
776         _preset_combo.AddMenuElem(
777                 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
778
779         --_no_load_preset;
780 }
781
782 void
783 PlugUIBase::update_preset ()
784 {
785         Plugin::PresetRecord p = plugin->last_preset();
786
787         ++_no_load_preset;
788         if (p.uri.empty()) {
789                 _preset_combo.set_text (_("(none)"));
790         } else {
791                 _preset_combo.set_text (p.label);
792         }
793         --_no_load_preset;
794
795         save_button.set_sensitive (!p.uri.empty() && p.user);
796         delete_button.set_sensitive (!p.uri.empty() && p.user);
797
798         update_preset_modified ();
799 }
800
801 void
802 PlugUIBase::update_preset_modified ()
803 {
804
805         if (plugin->last_preset().uri.empty()) {
806                 _preset_modified.set_text ("");
807                 return;
808         }
809
810         bool const c = plugin->parameter_changed_since_last_preset ();
811         if (_preset_modified.get_text().empty() == c) {
812                 _preset_modified.set_text (c ? "*" : "");
813         }
814 }
815
816 void
817 PlugUIBase::parameter_changed (uint32_t, float)
818 {
819         update_preset_modified ();
820 }
821
822 void
823 PlugUIBase::preset_added_or_removed ()
824 {
825         /* Update both the list and the currently-displayed preset */
826         update_preset_list ();
827         update_preset ();
828 }
829