enough with umpteen "i18n.h" files. Consolidate on pbd/i18n.h
[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 #include "tooltips.h"
72
73 #include "pbd/i18n.h"
74
75 using namespace std;
76 using namespace ARDOUR;
77 using namespace ARDOUR_UI_UTILS;
78 using namespace PBD;
79 using namespace Gtkmm2ext;
80 using namespace Gtk;
81
82 PluginUIWindow::PluginUIWindow (
83         boost::shared_ptr<PluginInsert> insert,
84         bool                            scrollable,
85         bool                            editor)
86         : ArdourWindow (string())
87         , was_visible (false)
88         , _keyboard_focused (false)
89 #ifdef AUDIOUNIT_SUPPORT
90         , pre_deactivate_x (-1)
91         , pre_deactivate_y (-1)
92 #endif
93
94 {
95         bool have_gui = false;
96         Label* label = manage (new Label());
97         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
98
99         if (editor && insert->plugin()->has_editor()) {
100                 switch (insert->type()) {
101                 case ARDOUR::Windows_VST:
102                         have_gui = create_windows_vst_editor (insert);
103                         break;
104
105                 case ARDOUR::LXVST:
106                         have_gui = create_lxvst_editor (insert);
107                         break;
108
109                 case ARDOUR::AudioUnit:
110                         have_gui = create_audiounit_editor (insert);
111                         break;
112
113                 case ARDOUR::LADSPA:
114                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
115                         break;
116
117                 case ARDOUR::LV2:
118                         have_gui = create_lv2_editor (insert);
119                         break;
120
121                 default:
122 #ifndef WINDOWS_VST_SUPPORT
123                         error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
124                               << endmsg;
125 #else
126                         error << _("unknown type of editor-supplying plugin")
127                               << endmsg;
128 #endif
129                         throw failed_constructor ();
130                 }
131
132         }
133
134         if (!have_gui) {
135                 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
136
137                 _pluginui = pu;
138                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
139                 add (*pu);
140                 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
141
142                 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
143                 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
144         }
145
146         set_name ("PluginEditor");
147         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
148
149         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
150
151         gint h = _pluginui->get_preferred_height ();
152         gint w = _pluginui->get_preferred_width ();
153
154         if (scrollable) {
155                 if (h > 600) h = 600;
156         }
157
158         set_default_size (w, h);
159         set_resizable (_pluginui->resizable());
160 }
161
162 PluginUIWindow::~PluginUIWindow ()
163 {
164 #ifndef NDEBUG
165         cerr << "PluginWindow deleted for " << this << endl;
166 #endif
167         delete _pluginui;
168 }
169
170 void
171 PluginUIWindow::on_show ()
172 {
173         set_role("plugin_ui");
174
175         if (_pluginui) {
176                 _pluginui->update_preset_list ();
177                 _pluginui->update_preset ();
178         }
179
180         if (_pluginui) {
181 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
182                 if (pre_deactivate_x >= 0) {
183                         move (pre_deactivate_x, pre_deactivate_y);
184                 }
185 #endif
186
187                 if (_pluginui->on_window_show (_title)) {
188                         Window::on_show ();
189                 }
190         }
191 }
192
193 void
194 PluginUIWindow::on_hide ()
195 {
196 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
197         get_position (pre_deactivate_x, pre_deactivate_y);
198 #endif
199
200         Window::on_hide ();
201
202         if (_pluginui) {
203                 _pluginui->on_window_hide ();
204         }
205 }
206
207 void
208 PluginUIWindow::set_title(const std::string& title)
209 {
210         Gtk::Window::set_title(title);
211         _title = title;
212 }
213
214 bool
215 #ifdef WINDOWS_VST_SUPPORT
216 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
217 #else
218 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
219 #endif
220 {
221 #ifndef WINDOWS_VST_SUPPORT
222         return false;
223 #else
224
225         boost::shared_ptr<WindowsVSTPlugin> vp;
226
227         if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
228                 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
229                       << endmsg;
230                 throw failed_constructor ();
231         } else {
232                 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
233
234                 _pluginui = vpu;
235                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
236                 add (*vpu);
237                 vpu->package (*this);
238         }
239
240         return true;
241 #endif
242 }
243
244 bool
245 #ifdef LXVST_SUPPORT
246 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
247 #else
248 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
249 #endif
250 {
251 #ifndef LXVST_SUPPORT
252         return false;
253 #else
254
255         boost::shared_ptr<LXVSTPlugin> lxvp;
256
257         if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
258                 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
259                       << endmsg;
260                 throw failed_constructor ();
261         } else {
262                 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
263
264                 _pluginui = lxvpu;
265                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
266                 add (*lxvpu);
267                 lxvpu->package (*this);
268         }
269
270         return true;
271 #endif
272 }
273
274 bool
275 #ifdef AUDIOUNIT_SUPPORT
276 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
277 #else
278 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
279 #endif
280 {
281 #ifndef AUDIOUNIT_SUPPORT
282         return false;
283 #else
284         VBox* box;
285         _pluginui = create_au_gui (insert, &box);
286         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
287         add (*box);
288
289         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
290
291         return true;
292 #endif
293 }
294
295 void
296 #ifdef __APPLE__
297 PluginUIWindow::app_activated (bool yn)
298 #else
299 PluginUIWindow::app_activated (bool)
300 #endif
301 {
302 #ifdef AUDIOUNIT_SUPPORT
303         if (_pluginui) {
304                 if (yn) {
305                         if (was_visible) {
306                                 _pluginui->activate ();
307                                 if (pre_deactivate_x >= 0) {
308                                         move (pre_deactivate_x, pre_deactivate_y);
309                                 }
310                                 present ();
311                                 was_visible = true;
312                         }
313                 } else {
314                         was_visible = is_visible();
315                         get_position (pre_deactivate_x, pre_deactivate_y);
316                         hide ();
317                         _pluginui->deactivate ();
318                 }
319         }
320 #endif
321 }
322
323 bool
324 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
325 {
326 #ifdef HAVE_SUIL
327         boost::shared_ptr<LV2Plugin> vp;
328
329         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
330                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
331                 throw failed_constructor ();
332         } else {
333                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
334                 _pluginui = lpu;
335                 add (*lpu);
336                 lpu->package (*this);
337                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
338         }
339
340         return true;
341 #else
342         return false;
343 #endif
344 }
345
346 void
347 PluginUIWindow::keyboard_focused (bool yn)
348 {
349         _keyboard_focused = yn;
350 }
351
352 bool
353 PluginUIWindow::on_key_press_event (GdkEventKey* event)
354 {
355         if (_keyboard_focused) {
356                 if (_pluginui) {
357                         _pluginui->grab_focus();
358                         if (_pluginui->non_gtk_gui()) {
359                                 _pluginui->forward_key_event (event);
360                         } else {
361                                         return relay_key_press (event, this);
362                         }
363                 }
364                 return true;
365         }
366         /* for us to be getting key press events, there really
367            MUST be a _pluginui, but just to be safe, check ...
368         */
369
370         if (_pluginui) {
371                 _pluginui->grab_focus();
372                 if (_pluginui->non_gtk_gui()) {
373                         /* pass main window as the window for the event
374                            to be handled in, not this one, because there are
375                            no widgets in this window that we want to have
376                            key focus.
377                         */
378                         return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
379                 } else {
380                         return relay_key_press (event, this);
381                 }
382         }
383
384         return false;
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->enabled ());
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->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), 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->enabled ());
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                 insert->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, _("New Preset"));
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->enabled ()) {
667                 insert->enable (view_says_bypassed);
668         }
669
670         return false;
671 }
672
673 bool
674 PlugUIBase::focus_toggled (GdkEventButton*)
675 {
676         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
677                 Keyboard::the_keyboard().magic_widget_drop_focus();
678                 focus_button.remove ();
679                 focus_button.add (*focus_out_image);
680                 focus_out_image->show ();
681                 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));
682                 KeyboardFocused (false);
683         } else {
684                 Keyboard::the_keyboard().magic_widget_grab_focus();
685                 focus_button.remove ();
686                 focus_button.add (*focus_in_image);
687                 focus_in_image->show ();
688                 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
689                 KeyboardFocused (true);
690         }
691
692         return true;
693 }
694
695 void
696 PlugUIBase::toggle_description()
697 {
698         if (description_expander.get_expanded() &&
699             !description_expander.get_child()) {
700                 const std::string text = plugin->get_docs();
701                 if (text.empty()) {
702                         return;
703                 }
704
705                 Gtk::Label* label = manage(new Gtk::Label(text));
706                 label->set_line_wrap(true);
707                 label->set_line_wrap_mode(Pango::WRAP_WORD);
708                 description_expander.add(*label);
709                 description_expander.show_all();
710         }
711
712         if (!description_expander.get_expanded()) {
713                 description_expander.remove();
714         }
715 }
716
717
718 void
719 PlugUIBase::toggle_plugin_analysis()
720 {
721         if (plugin_analysis_expander.get_expanded() &&
722             !plugin_analysis_expander.get_child()) {
723                 // Create the GUI
724                 if (eqgui == 0) {
725                         eqgui = new PluginEqGui (insert);
726                 }
727
728                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
729
730                 if (toplevel) {
731                         toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
732                 }
733
734                 plugin_analysis_expander.add (*eqgui);
735                 plugin_analysis_expander.show_all ();
736                 eqgui->start_listening ();
737         }
738
739         if (!plugin_analysis_expander.get_expanded()) {
740                 // Hide & remove from expander
741
742                 eqgui->hide ();
743                 eqgui->stop_listening ();
744                 plugin_analysis_expander.remove();
745
746                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
747
748                 if (toplevel) {
749                         toplevel->resize (pre_eq_size.width, pre_eq_size.height);
750                 }
751         }
752 }
753
754 void
755 PlugUIBase::update_preset_list ()
756 {
757         using namespace Menu_Helpers;
758
759         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
760
761         ++_no_load_preset;
762
763         // Add a menu entry for each preset
764         _preset_combo.clear_items();
765         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
766                 _preset_combo.AddMenuElem(
767                         MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
768         }
769
770         // Add an empty entry for un-setting current preset (see preset_selected)
771         Plugin::PresetRecord no_preset;
772         _preset_combo.AddMenuElem(
773                 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
774
775         --_no_load_preset;
776 }
777
778 void
779 PlugUIBase::update_preset ()
780 {
781         Plugin::PresetRecord p = plugin->last_preset();
782
783         ++_no_load_preset;
784         if (p.uri.empty()) {
785                 _preset_combo.set_text (_("(none)"));
786         } else {
787                 _preset_combo.set_text (p.label);
788         }
789         --_no_load_preset;
790
791         save_button.set_sensitive (!p.uri.empty() && p.user);
792         delete_button.set_sensitive (!p.uri.empty() && p.user);
793
794         update_preset_modified ();
795 }
796
797 void
798 PlugUIBase::update_preset_modified ()
799 {
800
801         if (plugin->last_preset().uri.empty()) {
802                 _preset_modified.set_text ("");
803                 return;
804         }
805
806         bool const c = plugin->parameter_changed_since_last_preset ();
807         if (_preset_modified.get_text().empty() == c) {
808                 _preset_modified.set_text (c ? "*" : "");
809         }
810 }
811
812 void
813 PlugUIBase::preset_added_or_removed ()
814 {
815         /* Update both the list and the currently-displayed preset */
816         update_preset_list ();
817         update_preset ();
818 }
819