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