76764f8a5c77e8ccb47dd6798e6c01099cc6f34f
[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 #if defined(HAVE_SLV2) || defined(HAVE_SUIL)
321         boost::shared_ptr<LV2Plugin> vp;
322
323         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
324                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
325                 throw failed_constructor ();
326         } else {
327                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
328                 _pluginui = lpu;
329                 add (*lpu);
330                 lpu->package (*this);
331         }
332
333         return true;
334 #else
335         return false;
336 #endif
337 }
338
339 void
340 PluginUIWindow::keyboard_focused (bool yn)
341 {
342         _keyboard_focused = yn;
343 }
344
345 bool
346 PluginUIWindow::on_key_press_event (GdkEventKey* event)
347 {
348         if (_keyboard_focused) {
349                 if (_pluginui) {
350                         if (_pluginui->non_gtk_gui()) {
351                                 _pluginui->forward_key_event (event); 
352                         } else {
353                                 return relay_key_press (event, this);
354                         }
355                 }
356                 return true;
357         } else {
358                 /* for us to be getting key press events, there really
359                    MUST be a _pluginui, but just to be safe, check ...
360                 */
361
362                 if (_pluginui) {
363                         if (_pluginui->non_gtk_gui()) {
364                                 /* pass editor window as the window for the event
365                                    to be handled in, not this one, because there are
366                                    no widgets in this window that we want to have
367                                    key focus.
368                                 */
369                                 return relay_key_press (event, &PublicEditor::instance());
370                         } else {
371                                 return relay_key_press (event, this);
372                         }
373                 } else {
374                         return false;
375                 }
376         }
377 }
378
379 bool
380 PluginUIWindow::on_key_release_event (GdkEventKey *event)
381 {
382         if (_keyboard_focused) {
383                 if (_pluginui) {
384                         if (_pluginui->non_gtk_gui()) {
385                                 _pluginui->forward_key_event (event);
386                         } 
387                         return true;
388                 }
389                 return false;
390         } else {
391                 return true;
392         }
393 }
394
395 void
396 PluginUIWindow::plugin_going_away ()
397 {
398         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
399
400         if (_pluginui) {
401                 _pluginui->stop_updating(0);
402         }
403
404         death_connection.disconnect ();
405
406         delete_when_idle (this);
407 }
408
409 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
410         : insert (pi)
411         , plugin (insert->plugin())
412         , add_button (_("Add"))
413         , save_button (_("Save"))
414         , delete_button (_("Delete"))
415         , bypass_button (_("Bypass"))
416         , latency_gui (0)
417         , latency_dialog (0)
418         , plugin_analysis_expander (_("Plugin analysis"))
419         , eqgui (0)
420 {
421         _preset_combo.set_size_request (100, -1);
422         _preset_modified.set_size_request (16, -1);
423         _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
424         _no_load_preset = 0;
425
426         _preset_box.pack_start (_preset_combo);
427         _preset_box.pack_start (_preset_modified);
428
429         update_preset_list ();
430         update_preset ();
431         
432         add_button.set_name ("PluginAddButton");
433         add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
434
435         save_button.set_name ("PluginSaveButton");
436         save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
437
438         delete_button.set_name ("PluginDeleteButton");
439         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
440
441         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
442
443         bypass_button.set_active (!pi->active());
444
445         bypass_button.set_name ("PluginBypassButton");
446         bypass_button.signal_toggled().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_toggled));
447         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
448
449         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
450         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
451
452         /* these images are not managed, so that we can remove them at will */
453
454         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
455         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
456
457         focus_button.add (*focus_out_image);
458
459         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));
460         ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
461
462         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
463         plugin_analysis_expander.set_expanded(false);
464         
465         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
466
467         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
468         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());   
469         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
470         plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
471 }
472
473 PlugUIBase::~PlugUIBase()
474 {
475         delete eqgui;
476         delete latency_gui;
477 }
478
479 void
480 PlugUIBase::plugin_going_away ()
481 {
482         /* drop references to the plugin/insert */
483         insert.reset ();
484         plugin.reset ();
485         death_connection.disconnect ();
486 }
487
488 void
489 PlugUIBase::set_latency_label ()
490 {
491         framecnt_t const l = insert->effective_latency ();
492         framecnt_t const sr = insert->session().frame_rate ();
493
494         string t;
495
496         if (l < sr / 1000) {
497                 t = string_compose (_("latency (%1 samples)"), l);
498         } else {
499                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
500         }
501
502         latency_label.set_text (t);
503 }
504
505 void
506 PlugUIBase::latency_button_clicked ()
507 {
508         if (!latency_gui) {
509                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
510                 latency_dialog = new ArdourDialog (_("Edit Latency"), false, false);
511                 latency_dialog->get_vbox()->pack_start (*latency_gui);
512                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
513         }
514
515         latency_dialog->show_all ();
516 }
517
518 void
519 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
520 {
521         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p)
522         boost::shared_ptr<Processor> p (weak_p);
523         if (p) {
524                 bypass_button.set_active (!p->active());
525         }
526 }
527
528 void
529 PlugUIBase::preset_selected ()
530 {
531         if (_no_load_preset) {
532                 return;
533         }
534
535         if (_preset_combo.get_active_text().length() > 0) {
536                 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
537                 if (pr) {
538                         plugin->load_preset (*pr);
539                 } else {
540                         warning << string_compose(_("Plugin preset %1 not found"),
541                                                   _preset_combo.get_active_text()) << endmsg;
542                 }
543         }
544 }
545
546 void
547 PlugUIBase::add_plugin_setting ()
548 {
549         NewPluginPresetDialog d (plugin);
550
551         switch (d.run ()) {
552         case Gtk::RESPONSE_ACCEPT:
553                 if (d.name().empty()) {
554                         break;
555                 }
556
557                 if (d.replace ()) {
558                         plugin->remove_preset (d.name ());
559                 }
560
561                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
562                 if (!r.uri.empty ()) {
563                         plugin->load_preset (r);
564                 }
565                 break;
566         }
567 }
568
569 void
570 PlugUIBase::save_plugin_setting ()
571 {
572         string const name = _preset_combo.get_active_text ();
573         plugin->remove_preset (name);
574         Plugin::PresetRecord const r = plugin->save_preset (name);
575         if (!r.uri.empty ()) {
576                 plugin->load_preset (r);
577         }
578 }
579
580 void
581 PlugUIBase::delete_plugin_setting ()
582 {
583         plugin->remove_preset (_preset_combo.get_active_text ());
584 }
585
586 void
587 PlugUIBase::bypass_toggled ()
588 {
589         bool x;
590
591         if ((x = bypass_button.get_active()) == insert->active()) {
592                 if (x) {
593                         insert->deactivate ();
594                 } else {
595                         insert->activate ();
596                 }
597         }
598 }
599
600 bool
601 PlugUIBase::focus_toggled (GdkEventButton*)
602 {
603         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
604                 Keyboard::the_keyboard().magic_widget_drop_focus();
605                 focus_button.remove ();
606                 focus_button.add (*focus_out_image);
607                 focus_out_image->show ();
608                 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));
609                 KeyboardFocused (false);
610         } else {
611                 Keyboard::the_keyboard().magic_widget_grab_focus();
612                 focus_button.remove ();
613                 focus_button.add (*focus_in_image);
614                 focus_in_image->show ();
615                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
616                 KeyboardFocused (true);
617         }
618
619         return true;
620 }
621
622 void
623 PlugUIBase::toggle_plugin_analysis()
624 {
625         if (plugin_analysis_expander.get_expanded() &&
626             !plugin_analysis_expander.get_child()) {
627                 // Create the GUI
628                 if (eqgui == 0) {
629                         eqgui = new PluginEqGui (insert);
630                 }
631
632                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
633
634                 if (toplevel) {
635                         toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
636                 }
637
638                 plugin_analysis_expander.add (*eqgui);
639                 plugin_analysis_expander.show_all ();
640                 eqgui->start_listening ();
641         }
642
643         if (!plugin_analysis_expander.get_expanded()) {
644
645                 // Hide & remove from expander
646
647                 eqgui->hide ();
648                 eqgui->stop_listening ();
649                 plugin_analysis_expander.remove();
650
651                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
652
653                 if (toplevel) {
654                         toplevel->resize (pre_eq_size.width, pre_eq_size.height);
655                 }
656         }
657 }
658
659 void
660 PlugUIBase::update_preset_list ()
661 {
662         vector<string> preset_labels;
663         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
664
665         ++_no_load_preset;
666
667         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
668                 preset_labels.push_back (i->label);
669         }
670
671         set_popdown_strings (_preset_combo, preset_labels);
672         
673         --_no_load_preset;
674 }
675
676 void
677 PlugUIBase::update_preset ()
678 {
679         Plugin::PresetRecord p = plugin->last_preset();
680
681         ++_no_load_preset;
682         _preset_combo.set_active_text (p.label);
683         --_no_load_preset;
684
685         save_button.set_sensitive (!p.uri.empty() && p.user);
686         delete_button.set_sensitive (!p.uri.empty() && p.user);
687
688         update_preset_modified ();
689 }
690
691 void
692 PlugUIBase::update_preset_modified ()
693 {
694         if (plugin->last_preset().uri.empty()) {
695                 _preset_modified.set_text ("");
696                 return;
697         }
698         
699         bool const c = plugin->parameter_changed_since_last_preset ();
700         if (_preset_modified.get_text().empty() == c) {
701                 _preset_modified.set_text (c ? "*" : "");
702         }
703 }
704
705 void
706 PlugUIBase::parameter_changed (uint32_t, float)
707 {
708         update_preset_modified ();
709 }
710
711 void
712 PlugUIBase::preset_added_or_removed ()
713 {
714         /* Update both the list and the currently-displayed preset */
715         update_preset_list ();
716         update_preset ();
717 }