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