2 Copyright (C) 2000 Paul Davis
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.
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.
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.
21 #include "gtk2ardour-config.h"
29 #include "pbd/stl_delete.h"
30 #include "pbd/xml++.h"
31 #include "pbd/failed_constructor.h"
33 #include "gtkmm/widget.h"
34 #include "gtkmm/box.h"
36 #include "gtkmm2ext/utils.h"
37 #include "gtkmm2ext/doi.h"
38 #include "gtkmm2ext/application.h"
40 #include "widgets/tooltips.h"
41 #include "widgets/fastmeter.h"
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"
52 #include "ardour/lxvst_plugin.h"
53 #include "lxvst_plugin_ui.h"
56 #include "ardour/mac_vst_plugin.h"
57 #include "vst_plugin_ui.h"
60 #include "ardour/lv2_plugin.h"
61 #include "lv2_plugin_ui.h"
64 #include "ardour_window.h"
65 #include "ardour_ui.h"
66 #include "plugin_ui.h"
68 #include "gui_thread.h"
69 #include "public_editor.h"
70 #include "processor_box.h"
72 #include "latency_gui.h"
73 #include "plugin_dspload_ui.h"
74 #include "plugin_eq_gui.h"
76 #include "new_plugin_preset_dialog.h"
81 using namespace ARDOUR;
82 using namespace ARDOUR_UI_UTILS;
83 using namespace ArdourWidgets;
85 using namespace Gtkmm2ext;
89 PluginUIWindow::PluginUIWindow (
90 boost::shared_ptr<PluginInsert> insert,
93 : ArdourWindow (string())
95 , _keyboard_focused (false)
96 #ifdef AUDIOUNIT_SUPPORT
97 , pre_deactivate_x (-1)
98 , pre_deactivate_y (-1)
102 bool have_gui = false;
103 Label* label = manage (new Label());
104 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
106 if (editor && insert->plugin()->has_editor()) {
107 switch (insert->type()) {
108 case ARDOUR::Windows_VST:
109 have_gui = create_windows_vst_editor (insert);
113 have_gui = create_lxvst_editor (insert);
117 have_gui = create_mac_vst_editor (insert);
120 case ARDOUR::AudioUnit:
121 have_gui = create_audiounit_editor (insert);
125 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
129 have_gui = create_lv2_editor (insert);
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)
137 error << _("unknown type of editor-supplying plugin")
140 throw failed_constructor ();
146 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
149 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
151 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
153 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
154 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
157 set_name ("PluginEditor");
158 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
160 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
162 gint h = _pluginui->get_preferred_height ();
163 gint w = _pluginui->get_preferred_width ();
166 if (h > 600) h = 600;
169 set_default_size (w, h);
170 set_resizable (_pluginui->resizable());
173 PluginUIWindow::~PluginUIWindow ()
176 cerr << "PluginWindow deleted for " << this << endl;
182 PluginUIWindow::on_show ()
184 set_role("plugin_ui");
187 _pluginui->update_preset_list ();
188 _pluginui->update_preset ();
192 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
193 if (pre_deactivate_x >= 0) {
194 move (pre_deactivate_x, pre_deactivate_y);
198 if (_pluginui->on_window_show (_title)) {
205 PluginUIWindow::on_hide ()
207 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
208 get_position (pre_deactivate_x, pre_deactivate_y);
214 _pluginui->on_window_hide ();
219 PluginUIWindow::set_title(const std::string& title)
221 Gtk::Window::set_title(title);
226 #ifdef WINDOWS_VST_SUPPORT
227 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
229 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
232 #ifndef WINDOWS_VST_SUPPORT
236 boost::shared_ptr<WindowsVSTPlugin> vp;
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)
241 throw failed_constructor ();
243 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
246 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
248 vpu->package (*this);
257 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
259 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
262 #ifndef LXVST_SUPPORT
266 boost::shared_ptr<LXVSTPlugin> lxvp;
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)
271 throw failed_constructor ();
273 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
276 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
278 lxvpu->package (*this);
286 #ifdef MACVST_SUPPORT
287 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert> insert)
289 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert>)
292 #ifndef MACVST_SUPPORT
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)
299 throw failed_constructor ();
301 VSTPluginUI* vpu = create_mac_vst_gui (insert);
303 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
305 vpu->package (*this);
307 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
315 #ifdef AUDIOUNIT_SUPPORT
316 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
318 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
321 #ifndef AUDIOUNIT_SUPPORT
325 _pluginui = create_au_gui (insert, &box);
326 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
329 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
337 PluginUIWindow::app_activated (bool yn)
339 PluginUIWindow::app_activated (bool)
342 #ifdef AUDIOUNIT_SUPPORT
346 _pluginui->activate ();
347 if (pre_deactivate_x >= 0) {
348 move (pre_deactivate_x, pre_deactivate_y);
354 was_visible = is_visible();
355 get_position (pre_deactivate_x, pre_deactivate_y);
357 _pluginui->deactivate ();
364 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
367 boost::shared_ptr<LV2Plugin> vp;
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 ();
373 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
376 lpu->package (*this);
377 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
387 PluginUIWindow::keyboard_focused (bool yn)
389 _keyboard_focused = yn;
393 PluginUIWindow::on_key_press_event (GdkEventKey* event)
395 if (_keyboard_focused) {
397 _pluginui->grab_focus();
398 if (_pluginui->non_gtk_gui()) {
399 _pluginui->forward_key_event (event);
401 return relay_key_press (event, this);
406 /* for us to be getting key press events, there really
407 MUST be a _pluginui, but just to be safe, check ...
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
418 return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
420 return relay_key_press (event, this);
428 PluginUIWindow::on_key_release_event (GdkEventKey *event)
430 if (_keyboard_focused) {
432 if (_pluginui->non_gtk_gui()) {
433 _pluginui->forward_key_event (event);
437 gtk_window_propagate_key_event (GTK_WINDOW(gobj()), event);
439 /* don't forward releases */
444 PluginUIWindow::plugin_going_away ()
446 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
449 _pluginui->stop_updating(0);
452 death_connection.disconnect ();
455 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> 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 , cpuload_expander (_("CPU Profile"))
472 _preset_modified.set_size_request (16, -1);
473 _preset_combo.set_text("(default)");
474 set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
475 set_tooltip (add_button, _("Save a new preset"));
476 set_tooltip (save_button, _("Save the current preset"));
477 set_tooltip (delete_button, _("Delete the current preset"));
478 set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
479 set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
480 set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
483 update_preset_list ();
486 add_button.set_name ("generic button");
487 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
489 save_button.set_name ("generic button");
490 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
492 delete_button.set_name ("generic button");
493 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
495 reset_button.set_name ("generic button");
496 reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
498 pin_management_button.set_name ("generic button");
499 pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
501 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
503 bypass_button.set_name ("plugin bypass button");
504 bypass_button.set_text (_("Bypass"));
505 bypass_button.set_active (!pi->enabled ());
506 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
507 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
509 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
510 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
512 /* these images are not managed, so that we can remove them at will */
514 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
515 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
517 focus_button.add (*focus_out_image);
519 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));
520 set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
522 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
523 description_expander.set_expanded(false);
525 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
526 plugin_analysis_expander.set_expanded(false);
528 cpuload_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_cpuload_display));
529 cpuload_expander.set_expanded(false);
531 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
533 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
534 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
535 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
536 plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
538 insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
540 automation_state_changed();
543 PlugUIBase::~PlugUIBase()
551 PlugUIBase::plugin_going_away ()
553 /* drop references to the plugin/insert */
559 PlugUIBase::set_latency_label ()
561 samplecnt_t const l = insert->effective_latency ();
562 samplecnt_t const sr = insert->session().sample_rate ();
567 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
569 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
572 latency_button.set_text (t);
576 PlugUIBase::latency_button_clicked ()
579 latency_gui = new LatencyGUI (*(insert.get()), insert->session().sample_rate(), insert->session().get_block_size());
580 latency_dialog = new ArdourWindow (_("Edit Latency"));
581 /* use both keep-above and transient for to try cover as many
582 different WM's as possible.
584 latency_dialog->set_keep_above (true);
585 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
587 latency_dialog->set_transient_for (*win);
589 latency_dialog->add (*latency_gui);
590 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
593 latency_dialog->show_all ();
597 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
599 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
600 boost::shared_ptr<Processor> p (weak_p.lock());
603 bypass_button.set_active (!p->enabled ());
608 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
610 if (_no_load_preset) {
613 if (!preset.label.empty()) {
614 insert->load_preset (preset);
616 // blank selected = no preset
617 plugin->clear_preset();
621 #ifdef NO_PLUGIN_STATE
622 static bool seen_saving_message = false;
624 static void show_no_plugin_message()
626 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
629 info << _("To get full access to updates without this limitation\n"
630 "consider becoming a subscriber for a low cost every month.")
632 info << X_("https://community.ardour.org/s/subscribe")
634 ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
639 PlugUIBase::add_plugin_setting ()
641 #ifndef NO_PLUGIN_STATE
642 NewPluginPresetDialog d (plugin, _("New Preset"));
645 case Gtk::RESPONSE_ACCEPT:
646 if (d.name().empty()) {
651 plugin->remove_preset (d.name ());
654 Plugin::PresetRecord const r = plugin->save_preset (d.name());
655 if (!r.uri.empty ()) {
656 plugin->load_preset (r);
661 if (!seen_saving_message) {
662 seen_saving_message = true;
663 show_no_plugin_message();
669 PlugUIBase::save_plugin_setting ()
671 #ifndef NO_PLUGIN_STATE
672 string const name = _preset_combo.get_text ();
673 plugin->remove_preset (name);
674 Plugin::PresetRecord const r = plugin->save_preset (name);
675 if (!r.uri.empty ()) {
676 plugin->load_preset (r);
679 if (!seen_saving_message) {
680 seen_saving_message = true;
681 show_no_plugin_message();
687 PlugUIBase::delete_plugin_setting ()
689 #ifndef NO_PLUGIN_STATE
690 plugin->remove_preset (_preset_combo.get_text ());
692 if (!seen_saving_message) {
693 seen_saving_message = true;
694 show_no_plugin_message();
700 PlugUIBase::automation_state_changed ()
702 reset_button.set_sensitive (insert->can_reset_all_parameters());
706 PlugUIBase::reset_plugin_parameters ()
708 insert->reset_parameters_to_default ();
712 PlugUIBase::manage_pins ()
714 PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
718 proxy->get ()->raise();
723 PlugUIBase::bypass_button_release (GdkEventButton*)
725 bool view_says_bypassed = (bypass_button.active_state() != 0);
727 if (view_says_bypassed != insert->enabled ()) {
728 insert->enable (view_says_bypassed);
735 PlugUIBase::focus_toggled (GdkEventButton*)
737 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
738 Keyboard::the_keyboard().magic_widget_drop_focus();
739 focus_button.remove ();
740 focus_button.add (*focus_out_image);
741 focus_out_image->show ();
742 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));
743 KeyboardFocused (false);
745 Keyboard::the_keyboard().magic_widget_grab_focus();
746 focus_button.remove ();
747 focus_button.add (*focus_in_image);
748 focus_in_image->show ();
749 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
750 KeyboardFocused (true);
757 PlugUIBase::toggle_description()
759 if (description_expander.get_expanded() &&
760 !description_expander.get_child()) {
761 const std::string text = plugin->get_docs();
766 Gtk::Label* label = manage(new Gtk::Label(text));
767 label->set_line_wrap(true);
768 label->set_line_wrap_mode(Pango::WRAP_WORD);
769 description_expander.add(*label);
770 description_expander.show_all();
773 if (!description_expander.get_expanded()) {
774 const int child_height = description_expander.get_child ()->get_height ();
776 description_expander.remove();
778 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
782 toplevel->get_size (wr.width, wr.height);
783 wr.height -= child_height;
784 toplevel->resize (wr.width, wr.height);
790 PlugUIBase::toggle_plugin_analysis()
792 if (plugin_analysis_expander.get_expanded() &&
793 !plugin_analysis_expander.get_child()) {
796 eqgui = new PluginEqGui (insert);
799 plugin_analysis_expander.add (*eqgui);
800 plugin_analysis_expander.show_all ();
801 eqgui->start_listening ();
804 if (!plugin_analysis_expander.get_expanded()) {
805 // Hide & remove from expander
806 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
809 eqgui->stop_listening ();
810 plugin_analysis_expander.remove();
812 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
816 toplevel->get_size (wr.width, wr.height);
817 wr.height -= child_height;
818 toplevel->resize (wr.width, wr.height);
824 PlugUIBase::toggle_cpuload_display()
826 if (cpuload_expander.get_expanded() && !cpuload_expander.get_child()) {
827 if (stats_gui == 0) {
828 stats_gui = new PluginLoadStatsGui (insert);
830 cpuload_expander.add (*stats_gui);
831 cpuload_expander.show_all();
832 stats_gui->start_updating ();
835 if (!cpuload_expander.get_expanded()) {
836 const int child_height = cpuload_expander.get_child ()->get_height ();
839 stats_gui->stop_updating ();
840 cpuload_expander.remove();
842 Gtk::Window *toplevel = (Gtk::Window*) cpuload_expander.get_ancestor (GTK_TYPE_WINDOW);
846 toplevel->get_size (wr.width, wr.height);
847 wr.height -= child_height;
848 toplevel->resize (wr.width, wr.height);
855 PlugUIBase::update_preset_list ()
857 using namespace Menu_Helpers;
859 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
863 // Add a menu entry for each preset
864 _preset_combo.clear_items();
865 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
866 _preset_combo.AddMenuElem(
867 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
870 // Add an empty entry for un-setting current preset (see preset_selected)
871 Plugin::PresetRecord no_preset;
872 _preset_combo.AddMenuElem(
873 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
879 PlugUIBase::update_preset ()
881 Plugin::PresetRecord p = plugin->last_preset();
885 _preset_combo.set_text (_("(none)"));
887 _preset_combo.set_text (p.label);
891 delete_button.set_sensitive (!p.uri.empty() && p.user);
892 update_preset_modified ();
896 PlugUIBase::update_preset_modified ()
898 Plugin::PresetRecord p = plugin->last_preset();
901 save_button.set_sensitive (false);
902 _preset_modified.set_text ("");
906 bool const c = plugin->parameter_changed_since_last_preset ();
907 if (_preset_modified.get_text().empty() == c) {
908 _preset_modified.set_text (c ? "*" : "");
910 save_button.set_sensitive (c && p.user);
914 PlugUIBase::preset_added_or_removed ()
916 /* Update both the list and the currently-displayed preset */
917 update_preset_list ();