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