Clean up plugin preset handling a bit.
[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 VST_SUPPORT
50 #include "ardour/vst_plugin.h"
51 #include "vst_pluginui.h"
52 #endif
53 #ifdef HAVE_SLV2
54 #include "ardour/lv2_plugin.h"
55 #include "lv2_plugin_ui.h"
56 #endif
57
58 #include <lrdf.h>
59
60 #include "ardour_dialog.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 PBD;
77 using namespace Gtkmm2ext;
78 using namespace Gtk;
79
80 PluginUIWindow::PluginUIWindow (Gtk::Window* win, boost::shared_ptr<PluginInsert> insert, bool scrollable)
81         : parent (win)
82         , was_visible (false)
83         , _keyboard_focused (false)
84 {
85         bool have_gui = false;
86
87         Label* label = manage (new Label());
88         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
89
90         if (insert->plugin()->has_editor()) {
91                 switch (insert->type()) {
92                 case ARDOUR::VST:
93                         have_gui = create_vst_editor (insert);
94                         break;
95
96                 case ARDOUR::AudioUnit:
97                         have_gui = create_audiounit_editor (insert);
98                         break;
99
100                 case ARDOUR::LADSPA:
101                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
102                         break;
103
104                 case ARDOUR::LV2:
105                         have_gui = create_lv2_editor (insert);
106                         break;
107
108                 default:
109 #ifndef VST_SUPPORT
110                         error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
111                               << endmsg;
112 #else
113                         error << _("unknown type of editor-supplying plugin")
114                               << endmsg;
115 #endif
116                         throw failed_constructor ();
117                 }
118
119         }
120
121         if (!have_gui) {
122
123                 GenericPluginUI*  pu  = new GenericPluginUI (insert, scrollable);
124
125                 _pluginui = pu;
126                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
127                 add (*pu);
128
129                 /*
130                 Gtk::HBox *hbox = new Gtk::HBox();
131                 hbox->pack_start( *pu);
132                 // TODO: this should be nicer
133                 hbox->pack_start( eqgui_bin );
134
135                 add (*manage(hbox));
136                 */
137
138                 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
139
140                 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
141                 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
142         }
143
144         // set_position (Gtk::WIN_POS_MOUSE);
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         signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
149         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
150
151         gint h = _pluginui->get_preferred_height ();
152         gint w = _pluginui->get_preferred_width ();
153
154         if (scrollable) {
155                 if (h > 600) h = 600;
156                 if (w > 600) w = 600;
157
158                 if (w < 0) {
159                         w = 450;
160                 }
161         }
162
163         set_default_size (w, h);
164 }
165
166 PluginUIWindow::~PluginUIWindow ()
167 {
168         delete _pluginui;
169 }
170
171 void
172 PluginUIWindow::set_parent (Gtk::Window* win)
173 {
174         parent = win;
175 }
176
177 void
178 PluginUIWindow::on_map ()
179 {
180         Window::on_map ();
181         set_keep_above (true);
182 }
183
184 bool
185 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
186 {
187         Keyboard::the_keyboard().enter_window (ev, this);
188         return false;
189 }
190
191 bool
192 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
193 {
194         Keyboard::the_keyboard().leave_window (ev, this);
195         return false;
196 }
197
198 bool
199 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
200 {
201         Window::on_focus_in_event (ev);
202         //Keyboard::the_keyboard().magic_widget_grab_focus ();
203         return false;
204 }
205
206 bool
207 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
208 {
209         Window::on_focus_out_event (ev);
210         //Keyboard::the_keyboard().magic_widget_drop_focus ();
211         return false;
212 }
213
214 void
215 PluginUIWindow::on_show ()
216 {
217         set_role("plugin_ui");
218
219         if (_pluginui) {
220                 _pluginui->update_preset_list ();
221                 _pluginui->update_preset ();
222         }
223
224         if (_pluginui) {
225                 if (_pluginui->on_window_show (_title)) {
226                         Window::on_show ();
227                 }
228         }
229
230         if (parent) {
231                 // set_transient_for (*parent);
232         }
233 }
234
235 void
236 PluginUIWindow::on_hide ()
237 {
238         Window::on_hide ();
239
240         if (_pluginui) {
241                 _pluginui->on_window_hide ();
242         }
243 }
244
245 void
246 PluginUIWindow::set_title(const std::string& title)
247 {
248         Gtk::Window::set_title(title);
249         _title = title;
250 }
251
252 bool
253 #ifdef VST_SUPPORT
254 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert> insert)
255 #else
256 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert>)
257 #endif
258 {
259 #ifndef VST_SUPPORT
260         return false;
261 #else
262
263         boost::shared_ptr<VSTPlugin> vp;
264
265         if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) == 0) {
266                 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
267                               << endmsg;
268                 throw failed_constructor ();
269         } else {
270                 VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
271
272                 _pluginui = vpu;
273                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
274                 add (*vpu);
275                 vpu->package (*this);
276         }
277
278         return true;
279 #endif
280 }
281
282 bool
283 #ifdef GTKOSX
284 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
285 #else
286 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
287 #endif
288 {
289 #ifndef GTKOSX
290         return false;
291 #else
292         VBox* box;
293         _pluginui = create_au_gui (insert, &box);
294         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
295         add (*box);
296
297         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
298
299         return true;
300 #endif
301 }
302
303 void
304 #ifdef GTKOSX
305 PluginUIWindow::app_activated (bool yn)
306 #else
307 PluginUIWindow::app_activated (bool)
308 #endif
309 {
310 #ifdef GTKOSX
311         cerr << "APP activated ? " << yn << endl;
312         if (_pluginui) {
313                 if (yn) {
314                         if (was_visible) {
315                                 _pluginui->activate ();
316                                 present ();
317                                 was_visible = true;
318                         }
319                 } else {
320                         was_visible = is_visible();
321                         hide ();
322                         _pluginui->deactivate ();
323                 }
324         }
325 #endif
326 }
327
328 bool
329 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
330 {
331 #ifndef HAVE_SLV2
332         return false;
333 #else
334
335         boost::shared_ptr<LV2Plugin> vp;
336
337         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
338                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
339                 throw failed_constructor ();
340         } else {
341                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
342                 _pluginui = lpu;
343                 add (*lpu);
344                 lpu->package (*this);
345         }
346
347         return true;
348 #endif
349 }
350
351 void
352 PluginUIWindow::keyboard_focused (bool yn)
353 {
354         _keyboard_focused = yn;
355 }
356
357 bool
358 PluginUIWindow::on_key_press_event (GdkEventKey* event)
359 {
360         if (_keyboard_focused) {
361                 if (_pluginui) {
362                         if (_pluginui->non_gtk_gui()) {
363                                 _pluginui->forward_key_event (event); 
364                         } else {
365                                 return relay_key_press (event, this);
366                         }
367                 }
368                 return true;
369         } else {
370                 /* for us to be getting key press events, there really
371                    MUST be a _pluginui, but just to be safe, check ...
372                 */
373
374                 if (_pluginui) {
375                         if (_pluginui->non_gtk_gui()) {
376                                 /* pass editor window as the window for the event
377                                    to be handled in, not this one, because there are
378                                    no widgets in this window that we want to have
379                                    key focus.
380                                 */
381                                 return relay_key_press (event, &PublicEditor::instance());
382                         } else {
383                                 return relay_key_press (event, this);
384                         }
385                 } else {
386                         return false;
387                 }
388         }
389 }
390
391 bool
392 PluginUIWindow::on_key_release_event (GdkEventKey *event)
393 {
394         if (_keyboard_focused) {
395                 if (_pluginui) {
396                         if (_pluginui->non_gtk_gui()) {
397                                 _pluginui->forward_key_event (event);
398                         } 
399                         return true;
400                 }
401                 return false;
402         } else {
403                 return true;
404         }
405 }
406
407 void
408 PluginUIWindow::plugin_going_away ()
409 {
410         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
411
412         if (_pluginui) {
413                 _pluginui->stop_updating(0);
414         }
415
416         death_connection.disconnect ();
417
418         delete_when_idle (this);
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 (_("Bypass")),
428           latency_gui (0),
429           plugin_analysis_expander (_("Plugin analysis"))
430 {
431         _preset_combo.set_size_request (100, -1);
432         _preset_modified.set_size_request (16, -1);
433         _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
434         _no_load_preset = 0;
435
436         _preset_box.pack_start (_preset_combo);
437         _preset_box.pack_start (_preset_modified);
438
439         update_preset_list ();
440         update_preset ();
441         
442         add_button.set_name ("PluginAddButton");
443         add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
444
445         save_button.set_name ("PluginSaveButton");
446         save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
447
448         delete_button.set_name ("PluginDeleteButton");
449         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
450
451         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
452
453         bypass_button.set_active (!pi->active());
454
455         bypass_button.set_name ("PluginBypassButton");
456         bypass_button.signal_toggled().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_toggled));
457         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
458
459         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
460         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
461
462         /* these images are not managed, so that we can remove them at will */
463
464         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
465         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
466
467         focus_button.add (*focus_out_image);
468
469         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));
470         ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
471
472         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
473         plugin_analysis_expander.set_expanded(false);
474         
475         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
476
477         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
478         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());   
479         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
480         plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
481 }
482
483 PlugUIBase::~PlugUIBase()
484 {
485         delete latency_gui;
486 }
487
488 void
489 PlugUIBase::plugin_going_away ()
490 {
491         /* drop references to the plugin/insert */
492         insert.reset ();
493         plugin.reset ();
494         death_connection.disconnect ();
495 }
496
497 void
498 PlugUIBase::set_latency_label ()
499 {
500         char buf[64];
501         framecnt_t const l = insert->effective_latency ();
502         framecnt_t const sr = insert->session().frame_rate ();
503
504         if (l < sr / 1000) {
505                 snprintf (buf, sizeof (buf), "latency (%" PRId64 " samples)", l);
506         } else {
507                 snprintf (buf, sizeof (buf), "latency (%.2f msecs)", (float) l / ((float) sr / 1000.0f));
508         }
509
510         latency_label.set_text (buf);
511 }
512
513 void
514 PlugUIBase::latency_button_clicked ()
515 {
516         if (!latency_gui) {
517                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
518                 latency_dialog = new ArdourDialog ("Edit Latency", false, false);
519                 latency_dialog->get_vbox()->pack_start (*latency_gui);
520                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
521         }
522
523         latency_dialog->show_all ();
524 }
525
526 void
527 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
528 {
529         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p)
530         boost::shared_ptr<Processor> p (weak_p);
531         if (p) {
532                 bypass_button.set_active (!p->active());
533         }
534 }
535
536 void
537 PlugUIBase::preset_selected ()
538 {
539         if (_no_load_preset) {
540                 return;
541         }
542
543         if (_preset_combo.get_active_text().length() > 0) {
544                 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
545                 if (pr) {
546                         plugin->load_preset (*pr);
547                 } else {
548                         warning << string_compose(_("Plugin preset %1 not found"),
549                                                   _preset_combo.get_active_text()) << endmsg;
550                 }
551         }
552 }
553
554 void
555 PlugUIBase::add_plugin_setting ()
556 {
557         NewPluginPresetDialog d (plugin);
558
559         switch (d.run ()) {
560         case Gtk::RESPONSE_ACCEPT:
561                 if (d.name().empty()) {
562                         break;
563                 }
564
565                 if (d.replace ()) {
566                         plugin->remove_preset (d.name ());
567                 }
568
569                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
570                 if (!r.uri.empty ()) {
571                         plugin->load_preset (r);
572                 }
573                 break;
574         }
575 }
576
577 void
578 PlugUIBase::save_plugin_setting ()
579 {
580         string const name = _preset_combo.get_active_text ();
581         plugin->remove_preset (name);
582         Plugin::PresetRecord const r = plugin->save_preset (name);
583         if (!r.uri.empty ()) {
584                 plugin->load_preset (r);
585         }
586 }
587
588 void
589 PlugUIBase::delete_plugin_setting ()
590 {
591         plugin->remove_preset (_preset_combo.get_active_text ());
592 }
593
594 void
595 PlugUIBase::bypass_toggled ()
596 {
597         bool x;
598
599         if ((x = bypass_button.get_active()) == insert->active()) {
600                 if (x) {
601                         insert->deactivate ();
602                 } else {
603                         insert->activate ();
604                 }
605         }
606 }
607
608 bool
609 PlugUIBase::focus_toggled (GdkEventButton*)
610 {
611         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
612                 Keyboard::the_keyboard().magic_widget_drop_focus();
613                 focus_button.remove ();
614                 focus_button.add (*focus_out_image);
615                 focus_out_image->show ();
616                 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));
617                 KeyboardFocused (false);
618         } else {
619                 Keyboard::the_keyboard().magic_widget_grab_focus();
620                 focus_button.remove ();
621                 focus_button.add (*focus_in_image);
622                 focus_in_image->show ();
623                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
624                 KeyboardFocused (true);
625         }
626
627         return true;
628 }
629
630 void
631 PlugUIBase::toggle_plugin_analysis()
632 {
633         if (plugin_analysis_expander.get_expanded() &&
634             !plugin_analysis_expander.get_child()) {
635                 // Create the GUI
636                 PluginEqGui *foo = new PluginEqGui(insert);
637                 plugin_analysis_expander.add( *foo );
638                 plugin_analysis_expander.show_all();
639         }
640
641         Gtk::Widget *gui;
642
643         if (!plugin_analysis_expander.get_expanded() &&
644             (gui = plugin_analysis_expander.get_child())) {
645                 // Hide & remove
646                 gui->hide();
647                 //plugin_analysis_expander.remove(*gui);
648                 plugin_analysis_expander.remove();
649
650                 delete gui;
651
652                 Gtk::Widget *toplevel = plugin_analysis_expander.get_toplevel();
653                 if (!toplevel) {
654                         std::cerr << "No toplevel widget?!?!" << std::endl;
655                         return;
656                 }
657
658                 Gtk::Container *cont = dynamic_cast<Gtk::Container *>(toplevel);
659                 if (!cont) {
660                         std::cerr << "Toplevel widget is not a container?!?" << std::endl;
661                         return;
662                 }
663
664                 Gtk::Allocation alloc(0, 0, 50, 50); // Just make it small
665                 toplevel->size_allocate(alloc);
666         }
667 }
668
669 void
670 PlugUIBase::update_preset_list ()
671 {
672         vector<string> preset_labels;
673         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
674
675         ++_no_load_preset;
676
677         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
678                 preset_labels.push_back (i->label);
679         }
680
681         set_popdown_strings (_preset_combo, preset_labels);
682         
683         --_no_load_preset;
684 }
685
686 void
687 PlugUIBase::update_preset ()
688 {
689         Plugin::PresetRecord p = plugin->last_preset();
690
691         ++_no_load_preset;
692         _preset_combo.set_active_text (p.label);
693         --_no_load_preset;
694
695         save_button.set_sensitive (!p.uri.empty() && p.user);
696         delete_button.set_sensitive (!p.uri.empty() && p.user);
697
698         update_preset_modified ();
699 }
700
701 void
702 PlugUIBase::update_preset_modified ()
703 {
704         if (plugin->last_preset().uri.empty()) {
705                 _preset_modified.set_text ("");
706                 return;
707         }
708         
709         bool const c = plugin->parameter_changed_since_last_preset ();
710         if (_preset_modified.get_text().empty() == c) {
711                 _preset_modified.set_text (c ? "*" : "");
712         }
713 }
714
715 void
716 PlugUIBase::parameter_changed (uint32_t, float)
717 {
718         update_preset_modified ();
719 }
720
721 void
722 PlugUIBase::preset_added_or_removed ()
723 {
724         /* Update both the list and the currently-displayed preset */
725         update_preset_list ();
726         update_preset ();
727 }