allow sending OSC from inline display UIs
[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 (_("Pinout"))
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         set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
439         set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
440         _no_load_preset = 0;
441
442         update_preset_list ();
443         update_preset ();
444
445         add_button.set_name ("generic button");
446         add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
447
448         save_button.set_name ("generic button");
449         save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
450
451         delete_button.set_name ("generic button");
452         delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
453
454         reset_button.set_name ("generic button");
455         reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
456
457         pin_management_button.set_name ("generic button");
458         pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
459
460         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
461
462         bypass_button.set_name ("plugin bypass button");
463         bypass_button.set_text (_("Bypass"));
464         bypass_button.set_active (!pi->enabled ());
465         bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
466         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
467
468         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
469         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
470
471         /* these images are not managed, so that we can remove them at will */
472
473         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
474         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
475
476         focus_button.add (*focus_out_image);
477
478         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));
479         set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
480
481         description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
482         description_expander.set_expanded(false);
483
484         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
485         plugin_analysis_expander.set_expanded(false);
486
487         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
488
489         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
490         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
491         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
492         plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
493
494         insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
495
496         automation_state_changed();
497 }
498
499 PlugUIBase::~PlugUIBase()
500 {
501         delete eqgui;
502         delete latency_gui;
503 }
504
505 void
506 PlugUIBase::plugin_going_away ()
507 {
508         /* drop references to the plugin/insert */
509         insert.reset ();
510         plugin.reset ();
511 }
512
513 void
514 PlugUIBase::set_latency_label ()
515 {
516         framecnt_t const l = insert->effective_latency ();
517         framecnt_t const sr = insert->session().frame_rate ();
518
519         string t;
520
521         if (l < sr / 1000) {
522                 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
523         } else {
524                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
525         }
526
527         latency_button.set_text (t);
528 }
529
530 void
531 PlugUIBase::latency_button_clicked ()
532 {
533         if (!latency_gui) {
534                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
535                 latency_dialog = new ArdourWindow (_("Edit Latency"));
536                 /* use both keep-above and transient for to try cover as many
537                    different WM's as possible.
538                 */
539                 latency_dialog->set_keep_above (true);
540                 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
541                 if (win) {
542                         latency_dialog->set_transient_for (*win);
543                 }
544                 latency_dialog->add (*latency_gui);
545                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
546         }
547
548         latency_dialog->show_all ();
549 }
550
551 void
552 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
553 {
554         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
555         boost::shared_ptr<Processor> p (weak_p.lock());
556
557         if (p) {
558                 bypass_button.set_active (!p->enabled ());
559         }
560 }
561
562 void
563 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
564 {
565         if (_no_load_preset) {
566                 return;
567         }
568         if (!preset.label.empty()) {
569                 insert->load_preset (preset);
570         } else {
571                 // blank selected = no preset
572                 plugin->clear_preset();
573         }
574 }
575
576 #ifdef NO_PLUGIN_STATE
577 static bool seen_saving_message = false;
578
579 static void show_no_plugin_message()
580 {
581         info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
582                         PROGRAM_NAME)
583              << endmsg;
584         info << _("To get full access to updates without this limitation\n"
585                   "consider becoming a subscriber for a low cost every month.")
586              << endmsg;
587         info << X_("https://community.ardour.org/s/subscribe")
588              << endmsg;
589         ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
590 }
591 #endif
592
593 void
594 PlugUIBase::add_plugin_setting ()
595 {
596 #ifndef NO_PLUGIN_STATE
597         NewPluginPresetDialog d (plugin, _("New Preset"));
598
599         switch (d.run ()) {
600         case Gtk::RESPONSE_ACCEPT:
601                 if (d.name().empty()) {
602                         break;
603                 }
604
605                 if (d.replace ()) {
606                         plugin->remove_preset (d.name ());
607                 }
608
609                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
610                 if (!r.uri.empty ()) {
611                         plugin->load_preset (r);
612                 }
613                 break;
614         }
615 #else
616         if (!seen_saving_message) {
617                 seen_saving_message = true;
618                 show_no_plugin_message();
619         }
620 #endif
621 }
622
623 void
624 PlugUIBase::save_plugin_setting ()
625 {
626 #ifndef NO_PLUGIN_STATE
627         string const name = _preset_combo.get_text ();
628         plugin->remove_preset (name);
629         Plugin::PresetRecord const r = plugin->save_preset (name);
630         if (!r.uri.empty ()) {
631                 plugin->load_preset (r);
632         }
633 #else
634         if (!seen_saving_message) {
635                 seen_saving_message = true;
636                 show_no_plugin_message();
637         }
638 #endif
639 }
640
641 void
642 PlugUIBase::delete_plugin_setting ()
643 {
644 #ifndef NO_PLUGIN_STATE
645         plugin->remove_preset (_preset_combo.get_text ());
646 #else
647         if (!seen_saving_message) {
648                 seen_saving_message = true;
649                 show_no_plugin_message();
650         }
651 #endif
652 }
653
654 void
655 PlugUIBase::automation_state_changed ()
656 {
657         reset_button.set_sensitive (insert->can_reset_all_parameters());
658 }
659
660 void
661 PlugUIBase::reset_plugin_parameters ()
662 {
663         insert->reset_parameters_to_default ();
664 }
665
666 void
667 PlugUIBase::manage_pins ()
668 {
669         PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
670         if (proxy) {
671                 proxy->get (true);
672                 proxy->present ();
673                 proxy->get ()->raise();
674         }
675 }
676
677 bool
678 PlugUIBase::bypass_button_release (GdkEventButton*)
679 {
680         bool view_says_bypassed = (bypass_button.active_state() != 0);
681
682         if (view_says_bypassed != insert->enabled ()) {
683                 insert->enable (view_says_bypassed);
684         }
685
686         return false;
687 }
688
689 bool
690 PlugUIBase::focus_toggled (GdkEventButton*)
691 {
692         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
693                 Keyboard::the_keyboard().magic_widget_drop_focus();
694                 focus_button.remove ();
695                 focus_button.add (*focus_out_image);
696                 focus_out_image->show ();
697                 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));
698                 KeyboardFocused (false);
699         } else {
700                 Keyboard::the_keyboard().magic_widget_grab_focus();
701                 focus_button.remove ();
702                 focus_button.add (*focus_in_image);
703                 focus_in_image->show ();
704                 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
705                 KeyboardFocused (true);
706         }
707
708         return true;
709 }
710
711 void
712 PlugUIBase::toggle_description()
713 {
714         if (description_expander.get_expanded() &&
715             !description_expander.get_child()) {
716                 const std::string text = plugin->get_docs();
717                 if (text.empty()) {
718                         return;
719                 }
720
721                 Gtk::Label* label = manage(new Gtk::Label(text));
722                 label->set_line_wrap(true);
723                 label->set_line_wrap_mode(Pango::WRAP_WORD);
724                 description_expander.add(*label);
725                 description_expander.show_all();
726         }
727
728         if (!description_expander.get_expanded()) {
729                 const int child_height = description_expander.get_child ()->get_height ();
730
731                 description_expander.remove();
732
733                 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
734
735                 if (toplevel) {
736                         Gtk::Requisition wr;
737                         toplevel->get_size (wr.width, wr.height);
738                         wr.height -= child_height;
739                         toplevel->resize (wr.width, wr.height);
740                 }
741
742         }
743 }
744
745
746 void
747 PlugUIBase::toggle_plugin_analysis()
748 {
749         if (plugin_analysis_expander.get_expanded() &&
750             !plugin_analysis_expander.get_child()) {
751                 // Create the GUI
752                 if (eqgui == 0) {
753                         eqgui = new PluginEqGui (insert);
754                 }
755
756                 plugin_analysis_expander.add (*eqgui);
757                 plugin_analysis_expander.show_all ();
758                 eqgui->start_listening ();
759         }
760
761         if (!plugin_analysis_expander.get_expanded()) {
762                 // Hide & remove from expander
763                 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
764
765                 eqgui->hide ();
766                 eqgui->stop_listening ();
767                 plugin_analysis_expander.remove();
768
769                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
770
771                 if (toplevel) {
772                         Gtk::Requisition wr;
773                         toplevel->get_size (wr.width, wr.height);
774                         wr.height -= child_height;
775                         toplevel->resize (wr.width, wr.height);
776                 }
777         }
778 }
779
780 void
781 PlugUIBase::update_preset_list ()
782 {
783         using namespace Menu_Helpers;
784
785         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
786
787         ++_no_load_preset;
788
789         // Add a menu entry for each preset
790         _preset_combo.clear_items();
791         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
792                 _preset_combo.AddMenuElem(
793                         MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
794         }
795
796         // Add an empty entry for un-setting current preset (see preset_selected)
797         Plugin::PresetRecord no_preset;
798         _preset_combo.AddMenuElem(
799                 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
800
801         --_no_load_preset;
802 }
803
804 void
805 PlugUIBase::update_preset ()
806 {
807         Plugin::PresetRecord p = plugin->last_preset();
808
809         ++_no_load_preset;
810         if (p.uri.empty()) {
811                 _preset_combo.set_text (_("(none)"));
812         } else {
813                 _preset_combo.set_text (p.label);
814         }
815         --_no_load_preset;
816
817         save_button.set_sensitive (!p.uri.empty() && p.user);
818         delete_button.set_sensitive (!p.uri.empty() && p.user);
819
820         update_preset_modified ();
821 }
822
823 void
824 PlugUIBase::update_preset_modified ()
825 {
826
827         if (plugin->last_preset().uri.empty()) {
828                 _preset_modified.set_text ("");
829                 return;
830         }
831
832         bool const c = plugin->parameter_changed_since_last_preset ();
833         if (_preset_modified.get_text().empty() == c) {
834                 _preset_modified.set_text (c ? "*" : "");
835         }
836 }
837
838 void
839 PlugUIBase::preset_added_or_removed ()
840 {
841         /* Update both the list and the currently-displayed preset */
842         update_preset_list ();
843         update_preset ();
844 }
845