update drobilla's fascistic dir-locals.el to force emacs users into whitespace submis...
[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_preset_list ();
221                 _pluginui->update_preset ();
222         }
223
224         if (_pluginui) {
225                 if (_pluginui->on_window_show (_title)) {
226                         Window::on_show ();
227                 }
228         }
229
230         if (parent) {
231                 // set_transient_for (*parent);
232         }
233 }
234
235 void
236 PluginUIWindow::on_hide ()
237 {
238         Window::on_hide ();
239
240         if (_pluginui) {
241                 _pluginui->on_window_hide ();
242         }
243 }
244
245 void
246 PluginUIWindow::set_title(const std::string& title)
247 {
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                 /* for us to be getting key press events, there really
371                    MUST be a _pluginui, but just to be safe, check ...
372                 */
373
374                 if (_pluginui) {
375                         if (_pluginui->non_gtk_gui()) {
376                                 /* pass editor window as the window for the event
377                                    to be handled in, not this one, because there are
378                                    no widgets in this window that we want to have
379                                    key focus.
380                                 */
381                                 return relay_key_press (event, &PublicEditor::instance());
382                         } else {
383                                 return relay_key_press (event, this);
384                         }
385                 } else {
386                         return false;
387                 }
388         }
389 }
390
391 bool
392 PluginUIWindow::on_key_release_event (GdkEventKey *event)
393 {
394         if (_keyboard_focused) {
395                 if (_pluginui) {
396                         if (_pluginui->non_gtk_gui()) {
397                                 _pluginui->forward_key_event (event);
398                         } 
399                         return true;
400                 }
401                 return false;
402         } else {
403                 return true;
404         }
405 }
406
407 void
408 PluginUIWindow::plugin_going_away ()
409 {
410         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
411
412         if (_pluginui) {
413                 _pluginui->stop_updating(0);
414         }
415
416         death_connection.disconnect ();
417
418         delete_when_idle (this);
419 }
420
421 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
422         : insert (pi),
423           plugin (insert->plugin()),
424           add_button (_("Add")),
425           save_button (_("Save")),
426           delete_button (_("Delete")),
427           bypass_button (_("Bypass")),
428           latency_gui (0),
429           plugin_analysis_expander (_("Plugin analysis"))
430 {
431         _preset_combo.set_size_request (100, -1);
432         _preset_modified.set_size_request (16, -1);
433         _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
434         _no_load_preset = 0;
435
436         _preset_box.pack_start (_preset_combo);
437         _preset_box.pack_start (_preset_modified);
438
439         update_preset_list ();
440         update_preset ();
441         
442         add_button.set_name ("PluginAddButton");
443         add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
444
445         save_button.set_name ("PluginSaveButton");
446         save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
447
448         delete_button.set_name ("PluginDeleteButton");
449         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
450
451         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
452
453         bypass_button.set_active (!pi->active());
454
455         bypass_button.set_name ("PluginBypassButton");
456         bypass_button.signal_toggled().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_toggled));
457         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
458
459         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
460         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
461
462         /* these images are not managed, so that we can remove them at will */
463
464         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
465         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
466
467         focus_button.add (*focus_out_image);
468
469         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));
470         ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
471
472         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
473         plugin_analysis_expander.set_expanded(false);
474         
475         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
476
477         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
478         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());   
479         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
480         plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
481 }
482
483 PlugUIBase::~PlugUIBase()
484 {
485         delete latency_gui;
486 }
487
488 void
489 PlugUIBase::plugin_going_away ()
490 {
491         /* drop references to the plugin/insert */
492         insert.reset ();
493         plugin.reset ();
494         death_connection.disconnect ();
495 }
496
497 void
498 PlugUIBase::set_latency_label ()
499 {
500         framecnt_t const l = insert->effective_latency ();
501         framecnt_t const sr = insert->session().frame_rate ();
502
503         string t;
504
505         if (l < sr / 1000) {
506                 t = string_compose (_("latency (%1 samples)"), l);
507         } else {
508                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
509         }
510
511         latency_label.set_text (t);
512 }
513
514 void
515 PlugUIBase::latency_button_clicked ()
516 {
517         if (!latency_gui) {
518                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
519                 latency_dialog = new ArdourDialog (_("Edit Latency"), false, false);
520                 latency_dialog->get_vbox()->pack_start (*latency_gui);
521                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
522         }
523
524         latency_dialog->show_all ();
525 }
526
527 void
528 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
529 {
530         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p)
531         boost::shared_ptr<Processor> p (weak_p);
532         if (p) {
533                 bypass_button.set_active (!p->active());
534         }
535 }
536
537 void
538 PlugUIBase::preset_selected ()
539 {
540         if (_no_load_preset) {
541                 return;
542         }
543
544         if (_preset_combo.get_active_text().length() > 0) {
545                 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
546                 if (pr) {
547                         plugin->load_preset (*pr);
548                 } else {
549                         warning << string_compose(_("Plugin preset %1 not found"),
550                                                   _preset_combo.get_active_text()) << endmsg;
551                 }
552         }
553 }
554
555 void
556 PlugUIBase::add_plugin_setting ()
557 {
558         NewPluginPresetDialog d (plugin);
559
560         switch (d.run ()) {
561         case Gtk::RESPONSE_ACCEPT:
562                 if (d.name().empty()) {
563                         break;
564                 }
565
566                 if (d.replace ()) {
567                         plugin->remove_preset (d.name ());
568                 }
569
570                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
571                 if (!r.uri.empty ()) {
572                         plugin->load_preset (r);
573                 }
574                 break;
575         }
576 }
577
578 void
579 PlugUIBase::save_plugin_setting ()
580 {
581         string const name = _preset_combo.get_active_text ();
582         plugin->remove_preset (name);
583         Plugin::PresetRecord const r = plugin->save_preset (name);
584         if (!r.uri.empty ()) {
585                 plugin->load_preset (r);
586         }
587 }
588
589 void
590 PlugUIBase::delete_plugin_setting ()
591 {
592         plugin->remove_preset (_preset_combo.get_active_text ());
593 }
594
595 void
596 PlugUIBase::bypass_toggled ()
597 {
598         bool x;
599
600         if ((x = bypass_button.get_active()) == insert->active()) {
601                 if (x) {
602                         insert->deactivate ();
603                 } else {
604                         insert->activate ();
605                 }
606         }
607 }
608
609 bool
610 PlugUIBase::focus_toggled (GdkEventButton*)
611 {
612         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
613                 Keyboard::the_keyboard().magic_widget_drop_focus();
614                 focus_button.remove ();
615                 focus_button.add (*focus_out_image);
616                 focus_out_image->show ();
617                 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));
618                 KeyboardFocused (false);
619         } else {
620                 Keyboard::the_keyboard().magic_widget_grab_focus();
621                 focus_button.remove ();
622                 focus_button.add (*focus_in_image);
623                 focus_in_image->show ();
624                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
625                 KeyboardFocused (true);
626         }
627
628         return true;
629 }
630
631 void
632 PlugUIBase::toggle_plugin_analysis()
633 {
634         if (plugin_analysis_expander.get_expanded() &&
635             !plugin_analysis_expander.get_child()) {
636                 // Create the GUI
637                 PluginEqGui *foo = new PluginEqGui(insert);
638                 plugin_analysis_expander.add( *foo );
639                 plugin_analysis_expander.show_all();
640         }
641
642         Gtk::Widget *gui;
643
644         if (!plugin_analysis_expander.get_expanded() &&
645             (gui = plugin_analysis_expander.get_child())) {
646                 // Hide & remove
647                 gui->hide();
648                 //plugin_analysis_expander.remove(*gui);
649                 plugin_analysis_expander.remove();
650
651                 delete gui;
652
653                 Gtk::Widget *toplevel = plugin_analysis_expander.get_toplevel();
654                 if (!toplevel) {
655                         std::cerr << "No toplevel widget?!?!" << std::endl;
656                         return;
657                 }
658
659                 Gtk::Container *cont = dynamic_cast<Gtk::Container *>(toplevel);
660                 if (!cont) {
661                         std::cerr << "Toplevel widget is not a container?!?" << std::endl;
662                         return;
663                 }
664
665                 Gtk::Allocation alloc(0, 0, 50, 50); // Just make it small
666                 toplevel->size_allocate(alloc);
667         }
668 }
669
670 void
671 PlugUIBase::update_preset_list ()
672 {
673         vector<string> preset_labels;
674         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
675
676         ++_no_load_preset;
677
678         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
679                 preset_labels.push_back (i->label);
680         }
681
682         set_popdown_strings (_preset_combo, preset_labels);
683         
684         --_no_load_preset;
685 }
686
687 void
688 PlugUIBase::update_preset ()
689 {
690         Plugin::PresetRecord p = plugin->last_preset();
691
692         ++_no_load_preset;
693         _preset_combo.set_active_text (p.label);
694         --_no_load_preset;
695
696         save_button.set_sensitive (!p.uri.empty() && p.user);
697         delete_button.set_sensitive (!p.uri.empty() && p.user);
698
699         update_preset_modified ();
700 }
701
702 void
703 PlugUIBase::update_preset_modified ()
704 {
705         if (plugin->last_preset().uri.empty()) {
706                 _preset_modified.set_text ("");
707                 return;
708         }
709         
710         bool const c = plugin->parameter_changed_since_last_preset ();
711         if (_preset_modified.get_text().empty() == c) {
712                 _preset_modified.set_text (c ? "*" : "");
713         }
714 }
715
716 void
717 PlugUIBase::parameter_changed (uint32_t, float)
718 {
719         update_preset_modified ();
720 }
721
722 void
723 PlugUIBase::preset_added_or_removed ()
724 {
725         /* Update both the list and the currently-displayed preset */
726         update_preset_list ();
727         update_preset ();
728 }