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