fix up key focus handling for plugin windows
[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(_("Add")),
329           bypass_button (_("Bypass"))
330 {
331         //preset_combo.set_use_arrows_always(true);
332         set_popdown_strings (preset_combo, plugin->get_presets());
333         preset_combo.set_size_request (100, -1);
334         preset_combo.set_active_text ("");
335         preset_combo.signal_changed().connect(mem_fun(*this, &PlugUIBase::setting_selected));
336
337         save_button.set_name ("PluginSaveButton");
338         save_button.signal_clicked().connect(mem_fun(*this, &PlugUIBase::save_plugin_setting));
339
340         insert->active_changed.connect (mem_fun(*this, &PlugUIBase::redirect_active_changed));
341         bypass_button.set_active (!pi->active());
342
343         bypass_button.set_name ("PluginBypassButton");
344         bypass_button.signal_toggled().connect (mem_fun(*this, &PlugUIBase::bypass_toggled));
345
346         focus_button.signal_button_release_event().connect (mem_fun(*this, &PlugUIBase::focus_toggled));
347         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
348
349         /* these images are not managed, so that we can remove them at will */
350
351         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
352         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
353         
354         focus_button.add (*focus_out_image);
355         ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to focus all keyboard events on this plugin window"), "");
356 }
357
358 void
359 PlugUIBase::redirect_active_changed (Redirect* r, void* src)
360 {
361         ENSURE_GUI_THREAD(bind (mem_fun(*this, &PlugUIBase::redirect_active_changed), r, src));
362         bypass_button.set_active (!r->active());
363 }
364
365 void
366 PlugUIBase::setting_selected()
367 {
368         if (preset_combo.get_active_text().length() > 0) {
369                 if (!plugin->load_preset(preset_combo.get_active_text())) {
370                         warning << string_compose(_("Plugin preset %1 not found"), preset_combo.get_active_text()) << endmsg;
371                 }
372         }
373 }
374
375 void
376 PlugUIBase::save_plugin_setting ()
377 {
378         ArdourPrompter prompter (true);
379         prompter.set_prompt(_("Name of New Preset:"));
380         prompter.add_button (Gtk::Stock::ADD, Gtk::RESPONSE_ACCEPT);
381         prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
382
383         prompter.show_all();
384
385         switch (prompter.run ()) {
386         case Gtk::RESPONSE_ACCEPT:
387
388                 string name;
389
390                 prompter.get_result(name);
391
392                 if (name.length()) {
393                         if(plugin->save_preset(name)){
394                                 set_popdown_strings (preset_combo, plugin->get_presets());
395                                 preset_combo.set_active_text (name);
396                         }
397                 }
398                 break;
399         }
400 }
401
402 void
403 PlugUIBase::bypass_toggled ()
404 {
405         bool x;
406
407         if ((x = bypass_button.get_active()) == insert->active()) {
408                 insert->set_active (!x, this);
409                 if (insert->active()) {
410                         bypass_button.set_label (_("Bypass"));
411                 } else {
412                         bypass_button.set_label (_("Active"));
413                 }
414         }
415 }
416
417 bool
418 PlugUIBase::focus_toggled (GdkEventButton* ev)
419 {
420         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
421                 Keyboard::the_keyboard().magic_widget_drop_focus();
422                 focus_button.remove ();
423                 focus_button.add (*focus_out_image);
424                 focus_out_image->show ();
425                 ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to focus all keyboard events on this plugin window"), "");
426         } else {
427                 Keyboard::the_keyboard().magic_widget_grab_focus();
428                 focus_button.remove ();
429                 focus_button.add (*focus_in_image);
430                 focus_in_image->show ();
431                 ARDOUR_UI::instance()->set_tip (&focus_button, _("Click to remove keyboard focus from this plugin window"), "");
432         }
433
434         return true;
435 }
436
437 void
438 PlugUIBase::update_presets ()
439 {
440         set_popdown_strings (preset_combo, plugin->get_presets());
441 }