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