remove Glib::ustring from gtk2_ardour
[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
42 #include "midi++/manager.h"
43
44 #include "ardour/session.h"
45 #include "ardour/plugin.h"
46 #include "ardour/plugin_insert.h"
47 #include "ardour/ladspa_plugin.h"
48 #ifdef VST_SUPPORT
49 #include "ardour/vst_plugin.h"
50 #endif
51 #ifdef HAVE_SLV2
52 #include "ardour/lv2_plugin.h"
53 #include "lv2_plugin_ui.h"
54 #endif
55
56 #include <lrdf.h>
57
58 #include "ardour_dialog.h"
59 #include "ardour_ui.h"
60 #include "prompter.h"
61 #include "plugin_ui.h"
62 #include "utils.h"
63 #include "gui_thread.h"
64 #include "public_editor.h"
65 #include "keyboard.h"
66 #include "latency_gui.h"
67 #include "plugin_eq_gui.h"
68
69 #include "i18n.h"
70
71 using namespace std;
72 using namespace ARDOUR;
73 using namespace PBD;
74 using namespace Gtkmm2ext;
75 using namespace Gtk;
76
77 PluginUIWindow::PluginUIWindow (Gtk::Window* win, boost::shared_ptr<PluginInsert> insert, bool scrollable)
78         : parent (win)
79         , was_visible (false)
80         , _keyboard_focused (false)
81 {
82         bool have_gui = false;
83
84         Label* label = manage (new Label());
85         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
86
87         if (insert->plugin()->has_editor()) {
88                 switch (insert->type()) {
89                 case ARDOUR::VST:
90                         have_gui = create_vst_editor (insert);
91                         break;
92
93                 case ARDOUR::AudioUnit:
94                         have_gui = create_audiounit_editor (insert);
95                         break;
96
97                 case ARDOUR::LADSPA:
98                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
99                         break;
100
101                 case ARDOUR::LV2:
102                         have_gui = create_lv2_editor (insert);
103                         break;
104
105                 default:
106 #ifndef VST_SUPPORT
107                         error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
108                               << endmsg;
109 #else
110                         error << _("unknown type of editor-supplying plugin")
111                               << endmsg;
112 #endif
113                         throw failed_constructor ();
114                 }
115
116         }
117
118         if (!have_gui) {
119
120                 GenericPluginUI*  pu  = new GenericPluginUI (insert, scrollable);
121
122                 _pluginui = pu;
123                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
124                 add (*pu);
125
126                 /*
127                 Gtk::HBox *hbox = new Gtk::HBox();
128                 hbox->pack_start( *pu);
129                 // TODO: this should be nicer
130                 hbox->pack_start( eqgui_bin );
131
132                 add (*manage(hbox));
133                 */
134
135                 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
136
137                 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
138                 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
139         }
140
141         // set_position (Gtk::WIN_POS_MOUSE);
142         set_name ("PluginEditor");
143         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
144
145         signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
146         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
147
148         gint h = _pluginui->get_preferred_height ();
149         gint w = _pluginui->get_preferred_width ();
150
151         if (scrollable) {
152                 if (h > 600) h = 600;
153                 if (w > 600) w = 600;
154
155                 if (w < 0) {
156                         w = 450;
157                 }
158         }
159
160         set_default_size (w, h);
161 }
162
163 PluginUIWindow::~PluginUIWindow ()
164 {
165         delete _pluginui;
166 }
167
168 void
169 PluginUIWindow::set_parent (Gtk::Window* win)
170 {
171         parent = win;
172 }
173
174 void
175 PluginUIWindow::on_map ()
176 {
177         Window::on_map ();
178         set_keep_above (true);
179 }
180
181 bool
182 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
183 {
184         Keyboard::the_keyboard().enter_window (ev, this);
185         return false;
186 }
187
188 bool
189 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
190 {
191         Keyboard::the_keyboard().leave_window (ev, this);
192         return false;
193 }
194
195 bool
196 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
197 {
198         Window::on_focus_in_event (ev);
199         //Keyboard::the_keyboard().magic_widget_grab_focus ();
200         return false;
201 }
202
203 bool
204 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
205 {
206         Window::on_focus_out_event (ev);
207         //Keyboard::the_keyboard().magic_widget_drop_focus ();
208         return false;
209 }
210
211 void
212 PluginUIWindow::on_show ()
213 {
214         set_role("plugin_ui");
215
216         if (_pluginui) {
217                 _pluginui->update_presets ();
218         }
219
220         if (_pluginui) {
221                 if (_pluginui->on_window_show (_title)) {
222                         Window::on_show ();
223                 }
224         }
225
226         if (parent) {
227                 // set_transient_for (*parent);
228         }
229 }
230
231 void
232 PluginUIWindow::on_hide ()
233 {
234         Window::on_hide ();
235
236         if (_pluginui) {
237                 _pluginui->on_window_hide ();
238         }
239 }
240
241 void
242 PluginUIWindow::set_title(const std::string& title)
243 {
244         //cout << "PluginUIWindow::set_title(\"" << title << "\"" << endl;
245         Gtk::Window::set_title(title);
246         _title = title;
247 }
248
249 bool
250 #ifdef VST_SUPPORT
251 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert> insert)
252 #else
253 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert>)
254 #endif
255 {
256 #ifndef VST_SUPPORT
257         return false;
258 #else
259
260         boost::shared_ptr<VSTPlugin> vp;
261
262         if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) == 0) {
263                 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
264                               << endmsg;
265                 throw failed_constructor ();
266         } else {
267                 VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
268
269                 _pluginui = vpu;
270                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
271                 add (*vpu);
272                 vpu->package (*this);
273         }
274
275         return true;
276 #endif
277 }
278
279 bool
280 #if defined (HAVE_AUDIOUNITS) && defined (GTKOSX)
281 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
282 #else
283 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
284 #endif
285 {
286 #if !defined(HAVE_AUDIOUNITS) || !defined(GTKOSX)
287         return false;
288 #else
289         VBox* box;
290         _pluginui = create_au_gui (insert, &box);
291         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
292         add (*box);
293
294         extern sigc::signal<void,bool> ApplicationActivationChanged;
295         ApplicationActivationChanged.connect (sigc::mem_fun (*this, &PluginUIWindow::app_activated));
296
297         return true;
298 #endif
299 }
300
301 void
302 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
303 PluginUIWindow::app_activated (bool yn)
304 #else
305 PluginUIWindow::app_activated (bool)
306 #endif
307 {
308 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
309         cerr << "APP activated ? " << yn << endl;
310         if (_pluginui) {
311                 if (yn) {
312                         if (was_visible) {
313                                 _pluginui->activate ();
314                                 present ();
315                                 was_visible = true;
316                         }
317                 } else {
318                         was_visible = is_visible();
319                         hide ();
320                         _pluginui->deactivate ();
321                 }
322         }
323 #endif
324 }
325
326 bool
327 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
328 {
329 #ifndef HAVE_SLV2
330         return false;
331 #else
332
333         boost::shared_ptr<LV2Plugin> vp;
334
335         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
336                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
337                 throw failed_constructor ();
338         } else {
339                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
340                 _pluginui = lpu;
341                 add (*lpu);
342                 lpu->package (*this);
343         }
344
345         return true;
346 #endif
347 }
348
349 void
350 PluginUIWindow::keyboard_focused (bool yn)
351 {
352         _keyboard_focused = yn;
353 }
354
355 bool
356 PluginUIWindow::on_key_press_event (GdkEventKey* event)
357 {
358         if (_keyboard_focused) {
359                 if (_pluginui) {
360                         if (_pluginui->non_gtk_gui()) {
361                                 _pluginui->forward_key_event (event); 
362                         } else {
363                                 return relay_key_press (event, this);
364                         }
365                 }
366                 return true;
367         } else {
368                 if (_pluginui->non_gtk_gui()) {
369                         /* pass editor window as the window for the event
370                            to be handled in, not this one, because there are
371                            no widgets in this window that we want to have
372                            key focus.
373                         */
374                         return relay_key_press (event, &PublicEditor::instance());
375                 } else {
376                         return relay_key_press (event, this);
377                 }
378         }
379 }
380
381 bool
382 PluginUIWindow::on_key_release_event (GdkEventKey *event)
383 {
384         if (_keyboard_focused) {
385                 if (_pluginui) {
386                         if (_pluginui->non_gtk_gui()) {
387                                 _pluginui->forward_key_event (event);
388                         } 
389                         return true;
390                 }
391                 return false;
392         } else {
393                 return true;
394         }
395 }
396
397 void
398 PluginUIWindow::plugin_going_away ()
399 {
400         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
401
402         if (_pluginui) {
403                 _pluginui->stop_updating(0);
404         }
405
406         death_connection.disconnect ();
407
408         delete_when_idle (this);
409 }
410
411 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
412         : insert (pi),
413           plugin (insert->plugin()),
414           save_button(_("Add")),
415           bypass_button (_("Bypass")),
416           latency_gui (0),
417           plugin_analysis_expander (_("Plugin analysis"))
418 {
419         //preset_combo.set_use_arrows_always(true);
420         update_presets();
421         preset_combo.set_size_request (100, -1);
422         preset_combo.set_active_text ("");
423         preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::setting_selected));
424         no_load_preset = false;
425
426         save_button.set_name ("PluginSaveButton");
427         save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
428
429         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
430
431         bypass_button.set_active (!pi->active());
432
433         bypass_button.set_name ("PluginBypassButton");
434         bypass_button.signal_toggled().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_toggled));
435         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
436
437         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
438         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
439
440         /* these images are not managed, so that we can remove them at will */
441
442         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
443         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
444
445         focus_button.add (*focus_out_image);
446
447         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));
448         ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
449
450         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
451         plugin_analysis_expander.set_expanded(false);
452         
453         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
454 }
455
456 PlugUIBase::~PlugUIBase()
457 {
458         delete latency_gui;
459 }
460
461 void
462 PlugUIBase::plugin_going_away ()
463 {
464         /* drop references to the plugin/insert */
465         insert.reset ();
466         plugin.reset ();
467         death_connection.disconnect ();
468 }
469
470 void
471 PlugUIBase::set_latency_label ()
472 {
473         char buf[64];
474         nframes_t l = insert->effective_latency ();
475         nframes_t sr = insert->session().frame_rate();
476
477         if (l < sr / 1000) {
478                 snprintf (buf, sizeof (buf), "latency (%d samples)", l);
479         } else {
480                 snprintf (buf, sizeof (buf), "latency (%.2f msecs)", (float) l / ((float) sr / 1000.0f));
481         }
482
483         latency_label.set_text (buf);
484 }
485
486 void
487 PlugUIBase::latency_button_clicked ()
488 {
489         if (!latency_gui) {
490                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
491                 latency_dialog = new ArdourDialog ("Edit Latency", false, false);
492                 latency_dialog->get_vbox()->pack_start (*latency_gui);
493                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
494         }
495
496         latency_dialog->show_all ();
497 }
498
499 void
500 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
501 {
502         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p)
503         boost::shared_ptr<Processor> p (weak_p);
504         if (p) {
505                 bypass_button.set_active (!p->active());
506         }
507 }
508
509 void
510 PlugUIBase::setting_selected()
511 {
512         if (no_load_preset) {
513                 return;
514         }
515
516         if (preset_combo.get_active_text().length() > 0) {
517                 const Plugin::PresetRecord* pr = plugin->preset_by_label(preset_combo.get_active_text());
518                 if (pr) {
519                         plugin->load_preset(pr->uri);
520                 } else {
521                         warning << string_compose(_("Plugin preset %1 not found"),
522                                         preset_combo.get_active_text()) << endmsg;
523                 }
524         }
525 }
526
527 void
528 PlugUIBase::save_plugin_setting ()
529 {
530         ArdourPrompter prompter (true);
531         prompter.set_prompt(_("Name of New Preset:"));
532         prompter.add_button (Gtk::Stock::ADD, Gtk::RESPONSE_ACCEPT);
533         prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
534         prompter.set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
535
536         prompter.show_all();
537         prompter.present ();
538
539         switch (prompter.run ()) {
540         case Gtk::RESPONSE_ACCEPT:
541                 string name;
542                 prompter.get_result(name);
543                 if (name.length()) {
544                         if (plugin->save_preset(name)) {
545                                 update_presets();
546                                 no_load_preset = true;
547                                 preset_combo.set_active_text (name);
548                                 no_load_preset = false;
549                         }
550                 }
551                 break;
552         }
553 }
554
555 void
556 PlugUIBase::bypass_toggled ()
557 {
558         bool x;
559
560         if ((x = bypass_button.get_active()) == insert->active()) {
561                 if (x) {
562                         insert->deactivate ();
563                 } else {
564                         insert->activate ();
565                 }
566         }
567 }
568
569 bool
570 PlugUIBase::focus_toggled (GdkEventButton*)
571 {
572         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
573                 Keyboard::the_keyboard().magic_widget_drop_focus();
574                 focus_button.remove ();
575                 focus_button.add (*focus_out_image);
576                 focus_out_image->show ();
577                 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));
578                 KeyboardFocused (false);
579         } else {
580                 Keyboard::the_keyboard().magic_widget_grab_focus();
581                 focus_button.remove ();
582                 focus_button.add (*focus_in_image);
583                 focus_in_image->show ();
584                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
585                 KeyboardFocused (true);
586         }
587
588         return true;
589 }
590
591 void
592 PlugUIBase::toggle_plugin_analysis()
593 {
594         if (plugin_analysis_expander.get_expanded() &&
595             !plugin_analysis_expander.get_child()) {
596                 // Create the GUI
597                 PluginEqGui *foo = new PluginEqGui(insert);
598                 plugin_analysis_expander.add( *foo );
599                 plugin_analysis_expander.show_all();
600         }
601
602         Gtk::Widget *gui;
603
604         if (!plugin_analysis_expander.get_expanded() &&
605             (gui = plugin_analysis_expander.get_child())) {
606                 // Hide & remove
607                 gui->hide();
608                 //plugin_analysis_expander.remove(*gui);
609                 plugin_analysis_expander.remove();
610
611                 delete gui;
612
613                 Gtk::Widget *toplevel = plugin_analysis_expander.get_toplevel();
614                 if (!toplevel) {
615                         std::cerr << "No toplevel widget?!?!" << std::endl;
616                         return;
617                 }
618
619                 Gtk::Container *cont = dynamic_cast<Gtk::Container *>(toplevel);
620                 if (!cont) {
621                         std::cerr << "Toplevel widget is not a container?!?" << std::endl;
622                         return;
623                 }
624
625                 Gtk::Allocation alloc(0, 0, 50, 50); // Just make it small
626                 toplevel->size_allocate(alloc);
627         }
628 }
629
630 void
631 PlugUIBase::update_presets ()
632 {
633         vector<string> preset_labels;
634         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
635
636         no_load_preset = true;
637
638         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
639                 preset_labels.push_back(i->label);
640         }
641
642         set_popdown_strings (preset_combo, preset_labels);
643         
644         no_load_preset = false;
645 }