LV2 external UI patch from nedko (#2777)
[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 #include <climits>
21 #include <cerrno>
22 #include <cmath>
23 #include <string>
24
25 #include <pbd/stl_delete.h>
26 #include <pbd/xml++.h>
27 #include <pbd/failed_constructor.h>
28
29 #include <gtkmm/widget.h>
30 #include <gtkmm2ext/click_box.h>
31 #include <gtkmm2ext/fastmeter.h>
32 #include <gtkmm2ext/barcontroller.h>
33 #include <gtkmm2ext/utils.h>
34 #include <gtkmm2ext/doi.h>
35 #include <gtkmm2ext/slider_controller.h>
36
37 #include <midi++/manager.h>
38
39 #include <ardour/plugin.h>
40 #include <ardour/insert.h>
41 #include <ardour/ladspa_plugin.h>
42 #ifdef VST_SUPPORT
43 #include <ardour/vst_plugin.h>
44 #endif
45 #ifdef HAVE_LV2
46 #include <ardour/lv2_plugin.h>
47 #include "lv2_plugin_ui.h"
48 #endif
49
50 #include <lrdf.h>
51
52 #include "ardour_ui.h"
53 #include "prompter.h"
54 #include "plugin_ui.h"
55 #include "utils.h"
56 #include "gui_thread.h"
57 #include "public_editor.h"
58 #include "keyboard.h"
59
60 #include "i18n.h"
61
62 using namespace std;
63 using namespace ARDOUR;
64 using namespace PBD;
65 using namespace Gtkmm2ext;
66 using namespace Gtk;
67 using namespace sigc;
68
69 PluginUIWindow::PluginUIWindow (Gtk::Window* win, boost::shared_ptr<PluginInsert> insert, bool scrollable)
70         : parent (win)
71 {
72         bool have_gui = false;
73         non_gtk_gui = false;
74         was_visible = false;
75
76         if (insert->plugin()->has_editor()) {
77                 switch (insert->type()) {
78                 case ARDOUR::VST:
79                         have_gui = create_vst_editor (insert);
80                         break;
81
82                 case ARDOUR::AudioUnit:
83                         have_gui = create_audiounit_editor (insert);
84                         break;
85                         
86                 case ARDOUR::LADSPA:
87                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
88                         break;
89
90                 case ARDOUR::LV2:
91                         have_gui = create_lv2_editor (insert);
92                         break;
93
94                 default:
95 #ifndef VST_SUPPORT
96                         error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
97                               << endmsg;
98 #else
99                         error << _("unknown type of editor-supplying plugin")
100                               << endmsg;
101 #endif
102                         throw failed_constructor ();
103                 }
104
105         } 
106
107         if (!have_gui) {
108
109                 GenericPluginUI*  pu  = new GenericPluginUI (insert, scrollable);
110                 
111                 _pluginui = pu;
112                 add (*pu);
113                 
114                 set_wmclass (X_("ardour_plugin_editor"), "Ardour");
115
116                 signal_map_event().connect (mem_fun (*pu, &GenericPluginUI::start_updating));
117                 signal_unmap_event().connect (mem_fun (*pu, &GenericPluginUI::stop_updating));
118         }
119
120         // set_position (Gtk::WIN_POS_MOUSE);
121         set_name ("PluginEditor");
122         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
123
124         signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
125         death_connection = insert->GoingAway.connect (mem_fun(*this, &PluginUIWindow::plugin_going_away));
126
127         gint h = _pluginui->get_preferred_height ();
128         gint w = _pluginui->get_preferred_width ();
129
130         if (scrollable) {
131                 if (h > 600) h = 600;
132                 if (w > 600) w = 600;
133
134                 if (w < 0) {
135                         w = 450;
136                 }
137         }
138
139         set_default_size (w, h); 
140 }
141
142 PluginUIWindow::~PluginUIWindow ()
143 {
144         delete _pluginui;
145 }
146
147 void
148 PluginUIWindow::set_parent (Gtk::Window* win)
149 {
150         parent = win;
151 }
152
153 void
154 PluginUIWindow::on_map ()
155 {
156         Window::on_map ();
157         set_keep_above (true);
158 }
159
160 bool
161 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
162 {
163         Keyboard::the_keyboard().enter_window (ev, this);
164         return false;
165 }
166
167 bool
168 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
169 {
170         Keyboard::the_keyboard().leave_window (ev, this);
171         return false;
172 }
173
174 bool
175 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
176 {
177         Window::on_focus_in_event (ev);
178         //Keyboard::the_keyboard().magic_widget_grab_focus ();
179         return false;
180 }
181
182 bool
183 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
184 {
185         Window::on_focus_out_event (ev);
186         //Keyboard::the_keyboard().magic_widget_drop_focus ();
187         return false;
188 }
189
190 void
191 PluginUIWindow::on_show ()
192 {
193         set_role("plugin_ui");
194
195         if (_pluginui) {
196                 _pluginui->update_presets ();
197         }
198
199         if (_pluginui) {
200                 if (_pluginui->on_window_show (_title)) {
201                         Window::on_show ();
202                 }
203         }
204
205         if (parent) {
206                 // set_transient_for (*parent);
207         }
208 }
209
210 void
211 PluginUIWindow::on_hide ()
212 {
213         Window::on_hide ();
214
215         if (_pluginui) {
216                 _pluginui->on_window_hide ();
217         }
218 }
219
220 void
221 PluginUIWindow::set_title(const Glib::ustring& title)
222 {
223         //cout << "PluginUIWindow::set_title(\"" << title << "\"" << endl;
224         Gtk::Window::set_title(title);
225         _title = title;
226 }
227
228 bool
229 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert> insert)
230 {
231 #ifndef VST_SUPPORT
232         return false;
233 #else
234
235         boost::shared_ptr<VSTPlugin> vp;
236
237         if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) == 0) {
238                 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
239                               << endmsg;
240                 throw failed_constructor ();
241         } else {
242                 VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
243         
244                 _pluginui = vpu;
245                 add (*vpu);
246                 vpu->package (*this);
247         }
248
249         non_gtk_gui = true;
250         return true;
251 #endif
252 }
253
254 bool
255 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
256 {
257 #if !defined(HAVE_AUDIOUNITS) || !defined(GTKOSX)
258         return false;
259 #else
260         VBox* box;
261         _pluginui = create_au_gui (insert, &box);
262         add (*box);
263         non_gtk_gui = true;
264
265         extern sigc::signal<void,bool> ApplicationActivationChanged;
266         ApplicationActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
267
268         return true;
269 #endif
270 }
271
272 void
273 PluginUIWindow::app_activated (bool yn)
274 {
275 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
276         cerr << "APP activated ? " << yn << endl;
277         if (_pluginui) {
278                 if (yn) {
279                         if (was_visible) {
280                                 _pluginui->activate ();
281                                 present ();
282                                 was_visible = true;
283                         }
284                 } else {
285                         was_visible = is_visible();
286                         hide ();
287                         _pluginui->deactivate ();
288                 }
289         } 
290 #endif
291 }
292
293 bool
294 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
295 {
296 #ifndef HAVE_LV2
297         return false;
298 #else
299
300         boost::shared_ptr<LV2Plugin> vp;
301         
302         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
303                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
304                 throw failed_constructor ();
305         } else {
306                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
307                 _pluginui = lpu;
308                 add (*lpu);
309                 lpu->package (*this);
310         }
311
312         non_gtk_gui = false;
313         return true;
314 #endif
315 }
316
317 bool
318 PluginUIWindow::on_key_press_event (GdkEventKey* event)
319 {
320         return relay_key_press (event, this);
321 }
322
323 bool
324 PluginUIWindow::on_key_release_event (GdkEventKey* event)
325 {
326         return true;
327 }
328
329 void
330 PluginUIWindow::plugin_going_away ()
331 {
332         ENSURE_GUI_THREAD(mem_fun(*this, &PluginUIWindow::plugin_going_away));
333
334         if (_pluginui) {
335                 _pluginui->stop_updating(0);
336         }
337         
338         death_connection.disconnect ();
339         delete_when_idle (this);
340 }
341
342 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
343         : insert (pi),
344           plugin (insert->plugin()),
345           save_button(_("Save")),
346           bypass_button (_("Bypass"))
347 {
348         //preset_combo.set_use_arrows_always(true);
349         preset_combo.set_size_request (100, -1);
350         update_presets ();
351
352         preset_combo.signal_changed().connect(mem_fun(*this, &PlugUIBase::setting_selected));
353         no_load_preset = false;
354
355         save_button.set_name ("PluginSaveButton");
356         save_button.signal_clicked().connect(mem_fun(*this, &PlugUIBase::save_plugin_setting));
357
358         insert->active_changed.connect (mem_fun(*this, &PlugUIBase::redirect_active_changed));
359         bypass_button.set_active (!pi->active());
360
361         bypass_button.set_name ("PluginBypassButton");
362         bypass_button.signal_toggled().connect (mem_fun(*this, &PlugUIBase::bypass_toggled));
363         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
364
365         focus_button.signal_button_release_event().connect (mem_fun(*this, &PlugUIBase::focus_toggled));
366         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
367
368         /* these images are not managed, so that we can remove them at will */
369
370         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
371         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
372         
373         focus_button.add (*focus_out_image);
374
375         ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to allow the plugin to receive keyboard events that Ardour would normally use as a shortcut"), "");
376         ARDOUR_UI::instance()->set_tip (&bypass_button, _("Click to enable/disable this plugin"), "");
377
378         insert->GoingAway.connect (mem_fun (*this, &PlugUIBase::plugin_going_away));
379 }
380
381 PlugUIBase::~PlugUIBase ()
382 {
383 }
384
385 void
386 PlugUIBase::plugin_going_away ()
387 {
388         /* drop references to the plugin/insert */
389         insert.reset ();
390         plugin.reset ();
391 }
392
393 void
394 PlugUIBase::redirect_active_changed (Redirect* r, void* src)
395 {
396         ENSURE_GUI_THREAD(bind (mem_fun(*this, &PlugUIBase::redirect_active_changed), r, src));
397         bypass_button.set_active (!r->active());
398 }
399
400 void
401 PlugUIBase::setting_selected()
402 {
403         if (no_load_preset) {
404                 return;
405         }
406         
407         if (preset_combo.get_active_text().length() > 0) {
408                 if (!plugin->load_preset(preset_combo.get_active_text())) {
409                         warning << string_compose(_("Plugin preset %1 not found"), preset_combo.get_active_text()) << endmsg;
410                 }
411         }
412 }
413
414 void
415 PlugUIBase::save_plugin_setting ()
416 {
417         ArdourPrompter prompter (true);
418         prompter.set_prompt(_("Name of New Preset:"));
419         prompter.add_button (Gtk::Stock::ADD, Gtk::RESPONSE_ACCEPT);
420         prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
421         prompter.set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
422
423         prompter.show_all();
424         prompter.present ();
425
426         switch (prompter.run ()) {
427         case Gtk::RESPONSE_ACCEPT:
428
429                 string name;
430
431                 prompter.get_result(name);
432
433                 if (name.length()) {
434                         if (plugin->save_preset(name)) {
435
436                                 /* a rather inefficient way to add the newly saved preset
437                                    to the list.
438                                 */
439
440                                 set_popdown_strings (preset_combo, plugin->get_presets());
441                                 no_load_preset = true;
442                                 preset_combo.set_active_text (name);
443                                 no_load_preset = false;
444                         }
445                 }
446                 break;
447         }
448 }
449
450 void
451 PlugUIBase::bypass_toggled ()
452 {
453         bool x;
454
455         if ((x = bypass_button.get_active()) == insert->active()) {
456                 insert->set_active (!x, this);
457         }
458 }
459
460 bool
461 PlugUIBase::focus_toggled (GdkEventButton* ev)
462 {
463         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
464                 Keyboard::the_keyboard().magic_widget_drop_focus();
465                 focus_button.remove ();
466                 focus_button.add (*focus_out_image);
467                 focus_out_image->show ();
468                 ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to allow the plugin to receive keyboard events that Ardour would normally use as a shortcut"), "");
469         } else {
470                 Keyboard::the_keyboard().magic_widget_grab_focus();
471                 focus_button.remove ();
472                 focus_button.add (*focus_in_image);
473                 focus_in_image->show ();
474                 ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to allow normal use of Ardour keyboard shortcuts"), "");
475         }
476
477         return true;
478 }
479
480 void
481 PlugUIBase::update_presets ()
482 {
483         vector<string> presets = plugin->get_presets();
484         set_popdown_strings (preset_combo, plugin->get_presets());
485
486         string current_preset = plugin->current_preset();
487
488         if (!current_preset.empty()) {
489                 for (vector<string>::iterator p = presets.begin(); p != presets.end(); ++p) {
490                         if (*p == current_preset) {
491                                 preset_combo.set_active_text (current_preset);
492                         }
493                 }
494         }
495 }