make context menu-driven stuff on ruler bars happen in the right place (e.g. new...
[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         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 }
145
146 void
147 PluginUIWindow::set_parent (Gtk::Window* win)
148 {
149         parent = win;
150 }
151
152 void
153 PluginUIWindow::on_map ()
154 {
155         Window::on_map ();
156         set_keep_above (true);
157 }
158
159 bool
160 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
161 {
162         Keyboard::the_keyboard().enter_window (ev, this);
163         return false;
164 }
165
166 bool
167 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
168 {
169         Keyboard::the_keyboard().leave_window (ev, this);
170         return false;
171 }
172
173 bool
174 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
175 {
176         Window::on_focus_in_event (ev);
177         //Keyboard::the_keyboard().magic_widget_grab_focus ();
178         return false;
179 }
180
181 bool
182 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
183 {
184         Window::on_focus_out_event (ev);
185         //Keyboard::the_keyboard().magic_widget_drop_focus ();
186         return false;
187 }
188
189 void
190 PluginUIWindow::on_show ()
191 {
192         if (_pluginui) {
193                 _pluginui->update_presets ();
194         }
195
196         Window::on_show ();
197
198         if (parent) {
199                 // set_transient_for (*parent);
200         }
201 }
202
203 void
204 PluginUIWindow::on_hide ()
205 {
206         Window::on_hide ();
207 }
208
209 bool
210 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert> insert)
211 {
212 #ifndef VST_SUPPORT
213         return false;
214 #else
215
216         boost::shared_ptr<VSTPlugin> vp;
217
218         if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) == 0) {
219                 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
220                               << endmsg;
221                 throw failed_constructor ();
222         } else {
223                 VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
224         
225                 _pluginui = vpu;
226                 add (*vpu);
227                 vpu->package (*this);
228         }
229
230         non_gtk_gui = true;
231         return true;
232 #endif
233 }
234
235 bool
236 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
237 {
238 #if !defined(HAVE_AUDIOUNITS) || !defined(GTKOSX)
239         return false;
240 #else
241         VBox* box;
242         _pluginui = create_au_gui (insert, &box);
243         add (*box);
244         non_gtk_gui = true;
245
246         extern sigc::signal<void,bool> ApplicationActivationChanged;
247         ApplicationActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
248
249         return true;
250 #endif
251 }
252
253 void
254 PluginUIWindow::app_activated (bool yn)
255 {
256 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
257         cerr << "APP activated ? " << yn << endl;
258         if (_pluginui) {
259                 if (yn) {
260                         if (was_visible) {
261                                 _pluginui->activate ();
262                                 present ();
263                                 was_visible = true;
264                         }
265                 } else {
266                         was_visible = is_visible();
267                         hide ();
268                         _pluginui->deactivate ();
269                 }
270         } 
271 #endif
272 }
273
274 bool
275 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
276 {
277 #ifndef HAVE_LV2
278         return false;
279 #else
280
281         boost::shared_ptr<LV2Plugin> vp;
282         
283         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
284                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
285                 throw failed_constructor ();
286         } else {
287                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
288                 _pluginui = lpu;
289                 add (*lpu);
290                 lpu->package (*this);
291         }
292
293         non_gtk_gui = false;
294         return true;
295 #endif
296 }
297
298 bool
299 PluginUIWindow::on_key_press_event (GdkEventKey* event)
300 {
301         if (!key_press_focus_accelerator_handler (*this, event)) {
302                 return PublicEditor::instance().on_key_press_event(event);
303         } else {
304                 return true;
305         }
306 }
307
308 bool
309 PluginUIWindow::on_key_release_event (GdkEventKey* event)
310 {
311         return true;
312 }
313
314 void
315 PluginUIWindow::plugin_going_away ()
316 {
317         ENSURE_GUI_THREAD(mem_fun(*this, &PluginUIWindow::plugin_going_away));
318         
319         if (_pluginui) {
320                 _pluginui->stop_updating(0);
321         }
322         delete_when_idle (this);
323 }
324
325 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
326         : insert (pi),
327           plugin (insert->plugin()),
328           save_button(_("Save")),
329           bypass_button (_("Bypass"))
330 {
331         //preset_combo.set_use_arrows_always(true);
332         preset_combo.set_size_request (100, -1);
333         update_presets ();
334
335         preset_combo.signal_changed().connect(mem_fun(*this, &PlugUIBase::setting_selected));
336         no_load_preset = false;
337
338         save_button.set_name ("PluginSaveButton");
339         save_button.signal_clicked().connect(mem_fun(*this, &PlugUIBase::save_plugin_setting));
340
341         insert->active_changed.connect (mem_fun(*this, &PlugUIBase::redirect_active_changed));
342         bypass_button.set_active (!pi->active());
343
344         bypass_button.set_name ("PluginBypassButton");
345         bypass_button.signal_toggled().connect (mem_fun(*this, &PlugUIBase::bypass_toggled));
346         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
347
348         focus_button.signal_button_release_event().connect (mem_fun(*this, &PlugUIBase::focus_toggled));
349         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
350
351         /* these images are not managed, so that we can remove them at will */
352
353         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
354         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
355         
356         focus_button.add (*focus_out_image);
357
358         ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to focus all keyboard events on this plugin window"), "");
359         ARDOUR_UI::instance()->set_tip (&bypass_button, _("Click to enable/disable this plugin"), "");
360 }
361
362 void
363 PlugUIBase::redirect_active_changed (Redirect* r, void* src)
364 {
365         ENSURE_GUI_THREAD(bind (mem_fun(*this, &PlugUIBase::redirect_active_changed), r, src));
366         bypass_button.set_active (!r->active());
367 }
368
369 void
370 PlugUIBase::setting_selected()
371 {
372         if (no_load_preset) {
373                 return;
374         }
375         
376         if (preset_combo.get_active_text().length() > 0) {
377                 if (!plugin->load_preset(preset_combo.get_active_text())) {
378                         warning << string_compose(_("Plugin preset %1 not found"), preset_combo.get_active_text()) << endmsg;
379                 }
380         }
381 }
382
383 void
384 PlugUIBase::save_plugin_setting ()
385 {
386         ArdourPrompter prompter (true);
387         prompter.set_prompt(_("Name of New Preset:"));
388         prompter.add_button (Gtk::Stock::ADD, Gtk::RESPONSE_ACCEPT);
389         prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
390         prompter.set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
391
392         prompter.show_all();
393
394         switch (prompter.run ()) {
395         case Gtk::RESPONSE_ACCEPT:
396
397                 string name;
398
399                 prompter.get_result(name);
400
401                 if (name.length()) {
402                         if (plugin->save_preset(name)) {
403
404                                 /* a rather inefficient way to add the newly saved preset
405                                    to the list.
406                                 */
407
408                                 set_popdown_strings (preset_combo, plugin->get_presets());
409                                 no_load_preset = true;
410                                 preset_combo.set_active_text (name);
411                                 no_load_preset = false;
412                         }
413                 }
414                 break;
415         }
416 }
417
418 void
419 PlugUIBase::bypass_toggled ()
420 {
421         bool x;
422
423         if ((x = bypass_button.get_active()) == insert->active()) {
424                 insert->set_active (!x, this);
425         }
426 }
427
428 bool
429 PlugUIBase::focus_toggled (GdkEventButton* ev)
430 {
431         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
432                 Keyboard::the_keyboard().magic_widget_drop_focus();
433                 focus_button.remove ();
434                 focus_button.add (*focus_out_image);
435                 focus_out_image->show ();
436                 ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to focus all keyboard events on this plugin window"), "");
437         } else {
438                 Keyboard::the_keyboard().magic_widget_grab_focus();
439                 focus_button.remove ();
440                 focus_button.add (*focus_in_image);
441                 focus_in_image->show ();
442                 ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to remove keyboard focus from this plugin window"), "");
443         }
444
445         return true;
446 }
447
448 void
449 PlugUIBase::update_presets ()
450 {
451         vector<string> presets = plugin->get_presets();
452         set_popdown_strings (preset_combo, plugin->get_presets());
453
454         string current_preset = plugin->current_preset();
455
456         if (!current_preset.empty()) {
457                 for (vector<string>::iterator p = presets.begin(); p != presets.end(); ++p) {
458                         if (*p == current_preset) {
459                                 preset_combo.set_active_text (current_preset);
460                         }
461                 }
462         }
463 }