d18f81b2c43e1b0fc008405e43a0d84b730f94cd
[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 #include "vst_pluginui.h"
52 #endif
53 #ifdef HAVE_SLV2
54 #include "ardour/lv2_plugin.h"
55 #include "lv2_plugin_ui.h"
56 #endif
57
58 #include <lrdf.h>
59
60 #include "ardour_dialog.h"
61 #include "ardour_ui.h"
62 #include "prompter.h"
63 #include "plugin_ui.h"
64 #include "utils.h"
65 #include "gui_thread.h"
66 #include "public_editor.h"
67 #include "keyboard.h"
68 #include "latency_gui.h"
69 #include "plugin_eq_gui.h"
70 #include "new_plugin_preset_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         Gtk::Window::set_title(title);
248         _title = title;
249 }
250
251 bool
252 #ifdef VST_SUPPORT
253 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert> insert)
254 #else
255 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert>)
256 #endif
257 {
258 #ifndef VST_SUPPORT
259         return false;
260 #else
261
262         boost::shared_ptr<VSTPlugin> vp;
263
264         if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) == 0) {
265                 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
266                               << endmsg;
267                 throw failed_constructor ();
268         } else {
269                 VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
270
271                 _pluginui = vpu;
272                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
273                 add (*vpu);
274                 vpu->package (*this);
275         }
276
277         return true;
278 #endif
279 }
280
281 bool
282 #ifdef GTKOSX
283 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
284 #else
285 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
286 #endif
287 {
288 #ifndef GTKOSX
289         return false;
290 #else
291         VBox* box;
292         _pluginui = create_au_gui (insert, &box);
293         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
294         add (*box);
295
296         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
297
298         return true;
299 #endif
300 }
301
302 void
303 #ifdef GTKOSX
304 PluginUIWindow::app_activated (bool yn)
305 #else
306 PluginUIWindow::app_activated (bool)
307 #endif
308 {
309 #ifdef GTKOSX
310         cerr << "APP activated ? " << yn << endl;
311         if (_pluginui) {
312                 if (yn) {
313                         if (was_visible) {
314                                 _pluginui->activate ();
315                                 present ();
316                                 was_visible = true;
317                         }
318                 } else {
319                         was_visible = is_visible();
320                         hide ();
321                         _pluginui->deactivate ();
322                 }
323         }
324 #endif
325 }
326
327 bool
328 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
329 {
330 #ifndef HAVE_SLV2
331         return false;
332 #else
333
334         boost::shared_ptr<LV2Plugin> vp;
335
336         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
337                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
338                 throw failed_constructor ();
339         } else {
340                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
341                 _pluginui = lpu;
342                 add (*lpu);
343                 lpu->package (*this);
344         }
345
346         return true;
347 #endif
348 }
349
350 void
351 PluginUIWindow::keyboard_focused (bool yn)
352 {
353         _keyboard_focused = yn;
354 }
355
356 bool
357 PluginUIWindow::on_key_press_event (GdkEventKey* event)
358 {
359         if (_keyboard_focused) {
360                 if (_pluginui) {
361                         if (_pluginui->non_gtk_gui()) {
362                                 _pluginui->forward_key_event (event); 
363                         } else {
364                                 return relay_key_press (event, this);
365                         }
366                 }
367                 return true;
368         } else {
369                 /* for us to be getting key press events, there really
370                    MUST be a _pluginui, but just to be safe, check ...
371                 */
372
373                 if (_pluginui) {
374                         if (_pluginui->non_gtk_gui()) {
375                                 /* pass editor window as the window for the event
376                                    to be handled in, not this one, because there are
377                                    no widgets in this window that we want to have
378                                    key focus.
379                                 */
380                                 return relay_key_press (event, &PublicEditor::instance());
381                         } else {
382                                 return relay_key_press (event, this);
383                         }
384                 } else {
385                         return false;
386                 }
387         }
388 }
389
390 bool
391 PluginUIWindow::on_key_release_event (GdkEventKey *event)
392 {
393         if (_keyboard_focused) {
394                 if (_pluginui) {
395                         if (_pluginui->non_gtk_gui()) {
396                                 _pluginui->forward_key_event (event);
397                         } 
398                         return true;
399                 }
400                 return false;
401         } else {
402                 return true;
403         }
404 }
405
406 void
407 PluginUIWindow::plugin_going_away ()
408 {
409         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
410
411         if (_pluginui) {
412                 _pluginui->stop_updating(0);
413         }
414
415         death_connection.disconnect ();
416
417         delete_when_idle (this);
418 }
419
420 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
421         : insert (pi),
422           plugin (insert->plugin()),
423           add_button (_("Add")),
424           save_button (_("Save")),
425           delete_button (_("Delete")),
426           bypass_button (_("Bypass")),
427           latency_gui (0),
428           plugin_analysis_expander (_("Plugin analysis"))
429 {
430         //preset_combo.set_use_arrows_always(true);
431         update_presets ();
432         update_sensitivity ();
433         
434         preset_combo.set_size_request (100, -1);
435         preset_combo.set_active_text ("");
436         preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::setting_selected));
437         no_load_preset = false;
438
439         add_button.set_name ("PluginAddButton");
440         add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
441
442         save_button.set_name ("PluginSaveButton");
443         save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
444
445         delete_button.set_name ("PluginDeleteButton");
446         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
447
448         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
449
450         bypass_button.set_active (!pi->active());
451
452         bypass_button.set_name ("PluginBypassButton");
453         bypass_button.signal_toggled().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_toggled));
454         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
455
456         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
457         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
458
459         /* these images are not managed, so that we can remove them at will */
460
461         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
462         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
463
464         focus_button.add (*focus_out_image);
465
466         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));
467         ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
468
469         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
470         plugin_analysis_expander.set_expanded(false);
471         
472         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
473
474         plugin->PresetAdded.connect (preset_added_connection, invalidator (*this), boost::bind (&PlugUIBase::update_presets, this), gui_context ());
475         plugin->PresetRemoved.connect (preset_removed_connection, invalidator (*this), boost::bind (&PlugUIBase::update_presets, this), gui_context ());
476 }
477
478 PlugUIBase::~PlugUIBase()
479 {
480         delete latency_gui;
481 }
482
483 void
484 PlugUIBase::plugin_going_away ()
485 {
486         /* drop references to the plugin/insert */
487         insert.reset ();
488         plugin.reset ();
489         death_connection.disconnect ();
490 }
491
492 void
493 PlugUIBase::set_latency_label ()
494 {
495         char buf[64];
496         framecnt_t const l = insert->effective_latency ();
497         framecnt_t const sr = insert->session().frame_rate ();
498
499         if (l < sr / 1000) {
500                 snprintf (buf, sizeof (buf), "latency (%" PRId64 " samples)", l);
501         } else {
502                 snprintf (buf, sizeof (buf), "latency (%.2f msecs)", (float) l / ((float) sr / 1000.0f));
503         }
504
505         latency_label.set_text (buf);
506 }
507
508 void
509 PlugUIBase::latency_button_clicked ()
510 {
511         if (!latency_gui) {
512                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
513                 latency_dialog = new ArdourDialog ("Edit Latency", false, false);
514                 latency_dialog->get_vbox()->pack_start (*latency_gui);
515                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
516         }
517
518         latency_dialog->show_all ();
519 }
520
521 void
522 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
523 {
524         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p)
525         boost::shared_ptr<Processor> p (weak_p);
526         if (p) {
527                 bypass_button.set_active (!p->active());
528         }
529 }
530
531 void
532 PlugUIBase::setting_selected ()
533 {
534         if (no_load_preset) {
535                 return;
536         }
537
538         if (preset_combo.get_active_text().length() > 0) {
539                 const Plugin::PresetRecord* pr = plugin->preset_by_label(preset_combo.get_active_text());
540                 if (pr) {
541                         plugin->load_preset (pr->uri);
542                         update_sensitivity ();
543                 } else {
544                         warning << string_compose(_("Plugin preset %1 not found"),
545                                         preset_combo.get_active_text()) << endmsg;
546                 }
547         }
548 }
549
550 void
551 PlugUIBase::add_plugin_setting ()
552 {
553         NewPluginPresetDialog d (plugin);
554
555         switch (d.run ()) {
556         case Gtk::RESPONSE_ACCEPT:
557                 if (d.name().empty()) {
558                         break;
559                 }
560
561                 if (d.replace ()) {
562                         plugin->remove_preset (d.name ());
563                 }
564
565                 if (plugin->save_preset (d.name())) {
566                         preset_combo.set_active_text (d.name());
567                 }
568                 break;
569         }
570 }
571
572 void
573 PlugUIBase::save_plugin_setting ()
574 {
575         string const name = preset_combo.get_active_text ();
576         plugin->remove_preset (name);
577         plugin->save_preset (name);
578         preset_combo.set_active_text (name);
579 }
580
581 void
582 PlugUIBase::delete_plugin_setting ()
583 {
584         plugin->remove_preset (preset_combo.get_active_text ());
585
586         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
587         if (presets.empty ()) {
588                 preset_combo.set_active_text ("");
589         } else {
590                 preset_combo.set_active_text (presets.front().label);
591         }
592 }
593
594 void
595 PlugUIBase::bypass_toggled ()
596 {
597         bool x;
598
599         if ((x = bypass_button.get_active()) == insert->active()) {
600                 if (x) {
601                         insert->deactivate ();
602                 } else {
603                         insert->activate ();
604                 }
605         }
606 }
607
608 bool
609 PlugUIBase::focus_toggled (GdkEventButton*)
610 {
611         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
612                 Keyboard::the_keyboard().magic_widget_drop_focus();
613                 focus_button.remove ();
614                 focus_button.add (*focus_out_image);
615                 focus_out_image->show ();
616                 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));
617                 KeyboardFocused (false);
618         } else {
619                 Keyboard::the_keyboard().magic_widget_grab_focus();
620                 focus_button.remove ();
621                 focus_button.add (*focus_in_image);
622                 focus_in_image->show ();
623                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
624                 KeyboardFocused (true);
625         }
626
627         return true;
628 }
629
630 void
631 PlugUIBase::toggle_plugin_analysis()
632 {
633         if (plugin_analysis_expander.get_expanded() &&
634             !plugin_analysis_expander.get_child()) {
635                 // Create the GUI
636                 PluginEqGui *foo = new PluginEqGui(insert);
637                 plugin_analysis_expander.add( *foo );
638                 plugin_analysis_expander.show_all();
639         }
640
641         Gtk::Widget *gui;
642
643         if (!plugin_analysis_expander.get_expanded() &&
644             (gui = plugin_analysis_expander.get_child())) {
645                 // Hide & remove
646                 gui->hide();
647                 //plugin_analysis_expander.remove(*gui);
648                 plugin_analysis_expander.remove();
649
650                 delete gui;
651
652                 Gtk::Widget *toplevel = plugin_analysis_expander.get_toplevel();
653                 if (!toplevel) {
654                         std::cerr << "No toplevel widget?!?!" << std::endl;
655                         return;
656                 }
657
658                 Gtk::Container *cont = dynamic_cast<Gtk::Container *>(toplevel);
659                 if (!cont) {
660                         std::cerr << "Toplevel widget is not a container?!?" << std::endl;
661                         return;
662                 }
663
664                 Gtk::Allocation alloc(0, 0, 50, 50); // Just make it small
665                 toplevel->size_allocate(alloc);
666         }
667 }
668
669 void
670 PlugUIBase::update_presets ()
671 {
672         vector<string> preset_labels;
673         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
674
675         no_load_preset = true;
676
677         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
678                 preset_labels.push_back(i->label);
679         }
680
681         set_popdown_strings (preset_combo, preset_labels);
682         
683         no_load_preset = false;
684
685         update_sensitivity ();
686 }
687
688 void
689 PlugUIBase::update_sensitivity ()
690 {
691         bool const have_user_preset =
692                 !preset_combo.get_model()->children().empty() && preset_combo.get_active_row_number() >= plugin->first_user_preset_index();
693         
694         save_button.set_sensitive (have_user_preset);
695         delete_button.set_sensitive (have_user_preset);
696 }