Fix double separator on session range context menu.
[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 "midi++/manager.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 LV2_SUPPORT
58 #include "ardour/lv2_plugin.h"
59 #include "lv2_plugin_ui.h"
60 #endif
61
62 #include <lrdf.h>
63
64 #include "ardour_window.h"
65 #include "ardour_ui.h"
66 #include "prompter.h"
67 #include "plugin_ui.h"
68 #include "utils.h"
69 #include "gui_thread.h"
70 #include "public_editor.h"
71 #include "keyboard.h"
72 #include "latency_gui.h"
73 #include "plugin_eq_gui.h"
74 #include "new_plugin_preset_dialog.h"
75
76 #include "i18n.h"
77
78 using namespace std;
79 using namespace ARDOUR;
80 using namespace PBD;
81 using namespace Gtkmm2ext;
82 using namespace Gtk;
83
84 PluginUIWindow::PluginUIWindow (
85         Gtk::Window*                    win,
86         boost::shared_ptr<PluginInsert> insert,
87         bool                            scrollable,
88         bool                            editor)
89         : parent (win)
90         , was_visible (false)
91         , _keyboard_focused (false)
92 {
93         bool have_gui = false;
94
95         Label* label = manage (new Label());
96         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
97
98         std::cout << "SHOW UI " << insert->plugin()->unique_id()
99                   << " editor: " << editor << std::endl;
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 << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
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_position (Gtk::WIN_POS_MOUSE);
148         set_name ("PluginEditor");
149         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
150
151         signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
152         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
153
154         gint h = _pluginui->get_preferred_height ();
155         gint w = _pluginui->get_preferred_width ();
156
157         if (scrollable) {
158                 if (h > 600) h = 600;
159         }
160
161         set_default_size (w, h);
162 }
163
164 PluginUIWindow::~PluginUIWindow ()
165 {
166         delete _pluginui;
167 }
168
169 void
170 PluginUIWindow::set_parent (Gtk::Window* win)
171 {
172         parent = win;
173 }
174
175 void
176 PluginUIWindow::on_map ()
177 {
178         Window::on_map ();
179         set_keep_above (true);
180 }
181
182 bool
183 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
184 {
185         Keyboard::the_keyboard().enter_window (ev, this);
186         return false;
187 }
188
189 bool
190 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
191 {
192         Keyboard::the_keyboard().leave_window (ev, this);
193         return false;
194 }
195
196 bool
197 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
198 {
199         Window::on_focus_in_event (ev);
200         //Keyboard::the_keyboard().magic_widget_grab_focus ();
201         return false;
202 }
203
204 bool
205 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
206 {
207         Window::on_focus_out_event (ev);
208         //Keyboard::the_keyboard().magic_widget_drop_focus ();
209         return false;
210 }
211
212 void
213 PluginUIWindow::on_show ()
214 {
215         set_role("plugin_ui");
216
217         if (_pluginui) {
218                 _pluginui->update_preset_list ();
219                 _pluginui->update_preset ();
220         }
221
222         if (_pluginui) {
223                 if (_pluginui->on_window_show (_title)) {
224                         Window::on_show ();
225                 }
226         }
227
228         if (parent) {
229                 // set_transient_for (*parent);
230         }
231 }
232
233 void
234 PluginUIWindow::on_hide ()
235 {
236         Window::on_hide ();
237
238         if (_pluginui) {
239                 _pluginui->on_window_hide ();
240         }
241 }
242
243 void
244 PluginUIWindow::set_title(const std::string& title)
245 {
246         Gtk::Window::set_title(title);
247         _title = title;
248 }
249
250 bool
251 #ifdef WINDOWS_VST_SUPPORT
252 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
253 #else
254 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
255 #endif
256 {
257 #ifndef WINDOWS_VST_SUPPORT
258         return false;
259 #else
260
261         boost::shared_ptr<WindowsVSTPlugin> vp;
262
263         if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
264                 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
265                               << endmsg;
266                 throw failed_constructor ();
267         } else {
268                 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp);
269
270                 _pluginui = vpu;
271                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
272                 add (*vpu);
273                 vpu->package (*this);
274         }
275
276         return true;
277 #endif
278 }
279
280 bool
281 #ifdef LXVST_SUPPORT
282 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
283 #else
284 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
285 #endif
286 {
287 #ifndef LXVST_SUPPORT
288         return false;
289 #else
290
291         boost::shared_ptr<LXVSTPlugin> lxvp;
292
293         if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
294                 error << _("unknown type of editor-supplying plugin (note: no linuxVST support in this version of ardour)")
295                               << endmsg;
296                 throw failed_constructor ();
297         } else {
298                 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
299
300                 _pluginui = lxvpu;
301                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
302                 add (*lxvpu);
303                 lxvpu->package (*this);
304         }
305
306         return true;
307 #endif
308 }
309
310 bool
311 #ifdef AUDIOUNIT_SUPPORT
312 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
313 #else
314 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
315 #endif
316 {
317 #ifndef AUDIOUNIT_SUPPORT
318         return false;
319 #else
320         VBox* box;
321         _pluginui = create_au_gui (insert, &box);
322         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
323         add (*box);
324
325         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
326
327         return true;
328 #endif
329 }
330
331 void
332 #ifdef GTKOSX
333 PluginUIWindow::app_activated (bool yn)
334 #else
335 PluginUIWindow::app_activated (bool)
336 #endif
337 {
338 #ifdef AUDIOUNIT_SUPPORT
339         if (_pluginui) {
340                 if (yn) {
341                         if (was_visible) {
342                                 _pluginui->activate ();
343                                 present ();
344                                 was_visible = true;
345                         }
346                 } else {
347                         was_visible = is_visible();
348                         hide ();
349                         _pluginui->deactivate ();
350                 }
351         }
352 #endif
353 }
354
355 bool
356 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
357 {
358 #ifdef HAVE_SUIL
359         boost::shared_ptr<LV2Plugin> vp;
360
361         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
362                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
363                 throw failed_constructor ();
364         } else {
365                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
366                 _pluginui = lpu;
367                 add (*lpu);
368                 lpu->package (*this);
369         }
370
371         return true;
372 #else
373         return false;
374 #endif
375 }
376
377 void
378 PluginUIWindow::keyboard_focused (bool yn)
379 {
380         _keyboard_focused = yn;
381 }
382
383 bool
384 PluginUIWindow::on_key_press_event (GdkEventKey* event)
385 {
386         if (_keyboard_focused) {
387                 if (_pluginui) {
388                         if (_pluginui->non_gtk_gui()) {
389                                 _pluginui->forward_key_event (event);
390                         } else {
391                                 return relay_key_press (event, this);
392                         }
393                 }
394                 return true;
395         } else {
396                 /* for us to be getting key press events, there really
397                    MUST be a _pluginui, but just to be safe, check ...
398                 */
399
400                 if (_pluginui) {
401                         if (_pluginui->non_gtk_gui()) {
402                                 /* pass editor window as the window for the event
403                                    to be handled in, not this one, because there are
404                                    no widgets in this window that we want to have
405                                    key focus.
406                                 */
407                                 return relay_key_press (event, &PublicEditor::instance());
408                         } else {
409                                 return relay_key_press (event, this);
410                         }
411                 } else {
412                         return false;
413                 }
414         }
415 }
416
417 bool
418 PluginUIWindow::on_key_release_event (GdkEventKey *event)
419 {
420         if (_keyboard_focused) {
421                 if (_pluginui) {
422                         if (_pluginui->non_gtk_gui()) {
423                                 _pluginui->forward_key_event (event);
424                         }
425                         return true;
426                 }
427                 return false;
428         } else {
429                 return true;
430         }
431 }
432
433 void
434 PluginUIWindow::plugin_going_away ()
435 {
436         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
437
438         if (_pluginui) {
439                 _pluginui->stop_updating(0);
440         }
441
442         death_connection.disconnect ();
443
444         delete_when_idle (this);
445 }
446
447 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
448         : insert (pi)
449         , plugin (insert->plugin())
450         , add_button (_("Add"))
451         , save_button (_("Save"))
452         , delete_button (_("Delete"))
453         , bypass_button (ArdourButton::led_default_elements)
454         , plugin_analysis_expander (_("Plugin analysis"))
455         , latency_gui (0)
456         , latency_dialog (0)
457         , eqgui (0)
458 {
459         _preset_modified.set_size_request (16, -1);
460         _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
461         ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
462         ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
463         ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
464         ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
465         ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
466         _no_load_preset = 0;
467
468         update_preset_list ();
469         update_preset ();
470
471         add_button.set_name ("PluginAddButton");
472         add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
473
474         save_button.set_name ("PluginSaveButton");
475         save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
476
477         delete_button.set_name ("PluginDeleteButton");
478         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
479
480         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
481
482         bypass_button.set_name ("plugin bypass button");
483         bypass_button.set_text (_("Bypass"));
484         bypass_button.set_active (!pi->active());
485         bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
486         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
487
488         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
489         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
490
491         /* these images are not managed, so that we can remove them at will */
492
493         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
494         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
495
496         focus_button.add (*focus_out_image);
497
498         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));
499         ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
500
501         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
502         plugin_analysis_expander.set_expanded(false);
503
504         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
505
506         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
507         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
508         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
509         plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
510 }
511
512 PlugUIBase::~PlugUIBase()
513 {
514         delete eqgui;
515         delete latency_gui;
516 }
517
518 void
519 PlugUIBase::plugin_going_away ()
520 {
521         /* drop references to the plugin/insert */
522         insert.reset ();
523         plugin.reset ();
524         death_connection.disconnect ();
525 }
526
527 void
528 PlugUIBase::set_latency_label ()
529 {
530         framecnt_t const l = insert->effective_latency ();
531         framecnt_t const sr = insert->session().frame_rate ();
532
533         string t;
534
535         if (l < sr / 1000) {
536                 t = string_compose (_("latency (%1 samples)"), l);
537         } else {
538                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
539         }
540
541         latency_label.set_text (t);
542 }
543
544 void
545 PlugUIBase::latency_button_clicked ()
546 {
547         if (!latency_gui) {
548                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
549                 latency_dialog = new ArdourWindow (_("Edit Latency"));
550                 latency_dialog->add (*latency_gui);
551                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
552         }
553
554         latency_dialog->show_all ();
555 }
556
557 void
558 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
559 {
560         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
561         boost::shared_ptr<Processor> p (weak_p.lock());
562
563         if (p) {
564                 bypass_button.set_active (!p->active());
565         }
566 }
567
568 void
569 PlugUIBase::preset_selected ()
570 {
571         if (_no_load_preset) {
572                 return;
573         }
574
575         if (_preset_combo.get_active_text().length() > 0) {
576                 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
577                 if (pr) {
578                         plugin->load_preset (*pr);
579                 } else {
580                         warning << string_compose(_("Plugin preset %1 not found"),
581                                                   _preset_combo.get_active_text()) << endmsg;
582                 }
583         }
584 }
585
586 void
587 PlugUIBase::add_plugin_setting ()
588 {
589         NewPluginPresetDialog d (plugin);
590
591         switch (d.run ()) {
592         case Gtk::RESPONSE_ACCEPT:
593                 if (d.name().empty()) {
594                         break;
595                 }
596
597                 if (d.replace ()) {
598                         plugin->remove_preset (d.name ());
599                 }
600
601                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
602                 if (!r.uri.empty ()) {
603                         plugin->load_preset (r);
604                 }
605                 break;
606         }
607 }
608
609 void
610 PlugUIBase::save_plugin_setting ()
611 {
612         string const name = _preset_combo.get_active_text ();
613         plugin->remove_preset (name);
614         Plugin::PresetRecord const r = plugin->save_preset (name);
615         if (!r.uri.empty ()) {
616                 plugin->load_preset (r);
617         }
618 }
619
620 void
621 PlugUIBase::delete_plugin_setting ()
622 {
623         plugin->remove_preset (_preset_combo.get_active_text ());
624 }
625
626 bool
627 PlugUIBase::bypass_button_release (GdkEventButton*)
628 {
629         bool view_says_bypassed = (bypass_button.active_state() != 0);
630         
631         if (view_says_bypassed != insert->active()) {
632                 if (view_says_bypassed) {
633                         insert->activate ();
634                 } else {
635                         insert->deactivate ();
636                 }
637         }
638
639         return false;
640 }
641
642 bool
643 PlugUIBase::focus_toggled (GdkEventButton*)
644 {
645         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
646                 Keyboard::the_keyboard().magic_widget_drop_focus();
647                 focus_button.remove ();
648                 focus_button.add (*focus_out_image);
649                 focus_out_image->show ();
650                 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));
651                 KeyboardFocused (false);
652         } else {
653                 Keyboard::the_keyboard().magic_widget_grab_focus();
654                 focus_button.remove ();
655                 focus_button.add (*focus_in_image);
656                 focus_in_image->show ();
657                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
658                 KeyboardFocused (true);
659         }
660
661         return true;
662 }
663
664 void
665 PlugUIBase::toggle_plugin_analysis()
666 {
667         if (plugin_analysis_expander.get_expanded() &&
668             !plugin_analysis_expander.get_child()) {
669                 // Create the GUI
670                 if (eqgui == 0) {
671                         eqgui = new PluginEqGui (insert);
672                 }
673
674                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
675
676                 if (toplevel) {
677                         toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
678                 }
679
680                 plugin_analysis_expander.add (*eqgui);
681                 plugin_analysis_expander.show_all ();
682                 eqgui->start_listening ();
683         }
684
685         if (!plugin_analysis_expander.get_expanded()) {
686
687                 // Hide & remove from expander
688
689                 eqgui->hide ();
690                 eqgui->stop_listening ();
691                 plugin_analysis_expander.remove();
692
693                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
694
695                 if (toplevel) {
696                         toplevel->resize (pre_eq_size.width, pre_eq_size.height);
697                 }
698         }
699 }
700
701 void
702 PlugUIBase::update_preset_list ()
703 {
704         vector<string> preset_labels;
705         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
706
707         ++_no_load_preset;
708
709         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
710                 preset_labels.push_back (i->label);
711         }
712
713         set_popdown_strings (_preset_combo, preset_labels);
714
715         --_no_load_preset;
716 }
717
718 void
719 PlugUIBase::update_preset ()
720 {
721         Plugin::PresetRecord p = plugin->last_preset();
722
723         ++_no_load_preset;
724         _preset_combo.set_active_text (p.label);
725         --_no_load_preset;
726
727         save_button.set_sensitive (!p.uri.empty() && p.user);
728         delete_button.set_sensitive (!p.uri.empty() && p.user);
729
730         update_preset_modified ();
731 }
732
733 void
734 PlugUIBase::update_preset_modified ()
735 {
736         if (plugin->last_preset().uri.empty()) {
737                 _preset_modified.set_text ("");
738                 return;
739         }
740
741         bool const c = plugin->parameter_changed_since_last_preset ();
742         if (_preset_modified.get_text().empty() == c) {
743                 _preset_modified.set_text (c ? "*" : "");
744         }
745 }
746
747 void
748 PlugUIBase::parameter_changed (uint32_t, float)
749 {
750         update_preset_modified ();
751 }
752
753 void
754 PlugUIBase::preset_added_or_removed ()
755 {
756         /* Update both the list and the currently-displayed preset */
757         update_preset_list ();
758         update_preset ();
759 }