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