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