Delete trailing whitespace
[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 LV2_SUPPORT
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 (
81         Gtk::Window*                    win,
82         boost::shared_ptr<PluginInsert> insert,
83         bool                            scrollable,
84         bool                            editor)
85         : parent (win)
86         , was_visible (false)
87         , _keyboard_focused (false)
88 {
89         bool have_gui = false;
90
91         Label* label = manage (new Label());
92         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
93
94         std::cout << "SHOW UI " << insert->plugin()->unique_id()
95                   << " editor: " << editor << std::endl;
96         if (editor && insert->plugin()->has_editor()) {
97                 switch (insert->type()) {
98                 case ARDOUR::VST:
99                         have_gui = create_vst_editor (insert);
100                         break;
101
102                 case ARDOUR::AudioUnit:
103                         have_gui = create_audiounit_editor (insert);
104                         break;
105
106                 case ARDOUR::LADSPA:
107                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
108                         break;
109
110                 case ARDOUR::LV2:
111                         have_gui = create_lv2_editor (insert);
112                         break;
113
114                 default:
115 #ifndef VST_SUPPORT
116                         error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
117                               << endmsg;
118 #else
119                         error << _("unknown type of editor-supplying plugin")
120                               << endmsg;
121 #endif
122                         throw failed_constructor ();
123                 }
124
125         }
126
127         if (!have_gui) {
128                 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
129
130                 _pluginui = pu;
131                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
132                 add (*pu);
133                 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
134
135                 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
136                 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
137         }
138
139         // set_position (Gtk::WIN_POS_MOUSE);
140         set_name ("PluginEditor");
141         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
142
143         signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
144         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
145
146         gint h = _pluginui->get_preferred_height ();
147         gint w = _pluginui->get_preferred_width ();
148
149         if (scrollable) {
150                 if (h > 600) h = 600;
151                 if (w > 600) w = 600;
152
153                 if (w < 0) {
154                         w = 450;
155                 }
156         }
157
158         set_default_size (w, h);
159 }
160
161 PluginUIWindow::~PluginUIWindow ()
162 {
163         delete _pluginui;
164 }
165
166 void
167 PluginUIWindow::set_parent (Gtk::Window* win)
168 {
169         parent = win;
170 }
171
172 void
173 PluginUIWindow::on_map ()
174 {
175         Window::on_map ();
176         set_keep_above (true);
177 }
178
179 bool
180 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
181 {
182         Keyboard::the_keyboard().enter_window (ev, this);
183         return false;
184 }
185
186 bool
187 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
188 {
189         Keyboard::the_keyboard().leave_window (ev, this);
190         return false;
191 }
192
193 bool
194 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
195 {
196         Window::on_focus_in_event (ev);
197         //Keyboard::the_keyboard().magic_widget_grab_focus ();
198         return false;
199 }
200
201 bool
202 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
203 {
204         Window::on_focus_out_event (ev);
205         //Keyboard::the_keyboard().magic_widget_drop_focus ();
206         return false;
207 }
208
209 void
210 PluginUIWindow::on_show ()
211 {
212         set_role("plugin_ui");
213
214         if (_pluginui) {
215                 _pluginui->update_preset_list ();
216                 _pluginui->update_preset ();
217         }
218
219         if (_pluginui) {
220                 if (_pluginui->on_window_show (_title)) {
221                         Window::on_show ();
222                 }
223         }
224
225         if (parent) {
226                 // set_transient_for (*parent);
227         }
228 }
229
230 void
231 PluginUIWindow::on_hide ()
232 {
233         Window::on_hide ();
234
235         if (_pluginui) {
236                 _pluginui->on_window_hide ();
237         }
238 }
239
240 void
241 PluginUIWindow::set_title(const std::string& title)
242 {
243         Gtk::Window::set_title(title);
244         _title = title;
245 }
246
247 bool
248 #ifdef VST_SUPPORT
249 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert> insert)
250 #else
251 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert>)
252 #endif
253 {
254 #ifndef VST_SUPPORT
255         return false;
256 #else
257
258         boost::shared_ptr<VSTPlugin> vp;
259
260         if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) == 0) {
261                 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
262                               << endmsg;
263                 throw failed_constructor ();
264         } else {
265                 VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
266
267                 _pluginui = vpu;
268                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
269                 add (*vpu);
270                 vpu->package (*this);
271         }
272
273         return true;
274 #endif
275 }
276
277 bool
278 #ifdef GTKOSX
279 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
280 #else
281 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
282 #endif
283 {
284 #ifndef GTKOSX
285         return false;
286 #else
287         VBox* box;
288         _pluginui = create_au_gui (insert, &box);
289         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
290         add (*box);
291
292         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
293
294         return true;
295 #endif
296 }
297
298 void
299 #ifdef GTKOSX
300 PluginUIWindow::app_activated (bool yn)
301 #else
302 PluginUIWindow::app_activated (bool)
303 #endif
304 {
305 #ifdef GTKOSX
306         if (_pluginui) {
307                 if (yn) {
308                         if (was_visible) {
309                                 _pluginui->activate ();
310                                 present ();
311                                 was_visible = true;
312                         }
313                 } else {
314                         was_visible = is_visible();
315                         hide ();
316                         _pluginui->deactivate ();
317                 }
318         }
319 #endif
320 }
321
322 bool
323 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
324 {
325 #if defined(HAVE_SLV2) || defined(HAVE_SUIL)
326         boost::shared_ptr<LV2Plugin> vp;
327
328         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
329                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
330                 throw failed_constructor ();
331         } else {
332                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
333                 _pluginui = lpu;
334                 add (*lpu);
335                 lpu->package (*this);
336         }
337
338         return true;
339 #else
340         return false;
341 #endif
342 }
343
344 void
345 PluginUIWindow::keyboard_focused (bool yn)
346 {
347         _keyboard_focused = yn;
348 }
349
350 bool
351 PluginUIWindow::on_key_press_event (GdkEventKey* event)
352 {
353         if (_keyboard_focused) {
354                 if (_pluginui) {
355                         if (_pluginui->non_gtk_gui()) {
356                                 _pluginui->forward_key_event (event);
357                         } else {
358                                 return relay_key_press (event, this);
359                         }
360                 }
361                 return true;
362         } else {
363                 /* for us to be getting key press events, there really
364                    MUST be a _pluginui, but just to be safe, check ...
365                 */
366
367                 if (_pluginui) {
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                 } else {
379                         return false;
380                 }
381         }
382 }
383
384 bool
385 PluginUIWindow::on_key_release_event (GdkEventKey *event)
386 {
387         if (_keyboard_focused) {
388                 if (_pluginui) {
389                         if (_pluginui->non_gtk_gui()) {
390                                 _pluginui->forward_key_event (event);
391                         }
392                         return true;
393                 }
394                 return false;
395         } else {
396                 return true;
397         }
398 }
399
400 void
401 PluginUIWindow::plugin_going_away ()
402 {
403         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
404
405         if (_pluginui) {
406                 _pluginui->stop_updating(0);
407         }
408
409         death_connection.disconnect ();
410
411         delete_when_idle (this);
412 }
413
414 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
415         : insert (pi)
416         , plugin (insert->plugin())
417         , add_button (_("Add"))
418         , save_button (_("Save"))
419         , delete_button (_("Delete"))
420         , bypass_button (_("Bypass"))
421         , latency_gui (0)
422         , latency_dialog (0)
423         , plugin_analysis_expander (_("Plugin analysis"))
424         , eqgui (0)
425 {
426         _preset_combo.set_size_request (100, -1);
427         _preset_modified.set_size_request (16, -1);
428         _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
429         _no_load_preset = 0;
430
431         _preset_box.pack_start (_preset_combo);
432         _preset_box.pack_start (_preset_modified);
433
434         update_preset_list ();
435         update_preset ();
436
437         add_button.set_name ("PluginAddButton");
438         add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
439
440         save_button.set_name ("PluginSaveButton");
441         save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
442
443         delete_button.set_name ("PluginDeleteButton");
444         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
445
446         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
447
448         bypass_button.set_active (!pi->active());
449
450         bypass_button.set_name ("PluginBypassButton");
451         bypass_button.signal_toggled().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_toggled));
452         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
453
454         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
455         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
456
457         /* these images are not managed, so that we can remove them at will */
458
459         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
460         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
461
462         focus_button.add (*focus_out_image);
463
464         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));
465         ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
466
467         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
468         plugin_analysis_expander.set_expanded(false);
469
470         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
471
472         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
473         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
474         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
475         plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
476 }
477
478 PlugUIBase::~PlugUIBase()
479 {
480         delete eqgui;
481         delete latency_gui;
482 }
483
484 void
485 PlugUIBase::plugin_going_away ()
486 {
487         /* drop references to the plugin/insert */
488         insert.reset ();
489         plugin.reset ();
490         death_connection.disconnect ();
491 }
492
493 void
494 PlugUIBase::set_latency_label ()
495 {
496         framecnt_t const l = insert->effective_latency ();
497         framecnt_t const sr = insert->session().frame_rate ();
498
499         string t;
500
501         if (l < sr / 1000) {
502                 t = string_compose (_("latency (%1 samples)"), l);
503         } else {
504                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
505         }
506
507         latency_label.set_text (t);
508 }
509
510 void
511 PlugUIBase::latency_button_clicked ()
512 {
513         if (!latency_gui) {
514                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
515                 latency_dialog = new ArdourDialog (_("Edit Latency"), false, false);
516                 latency_dialog->get_vbox()->pack_start (*latency_gui);
517                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
518         }
519
520         latency_dialog->show_all ();
521 }
522
523 void
524 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
525 {
526         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p)
527         boost::shared_ptr<Processor> p (weak_p);
528         if (p) {
529                 bypass_button.set_active (!p->active());
530         }
531 }
532
533 void
534 PlugUIBase::preset_selected ()
535 {
536         if (_no_load_preset) {
537                 return;
538         }
539
540         if (_preset_combo.get_active_text().length() > 0) {
541                 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
542                 if (pr) {
543                         plugin->load_preset (*pr);
544                 } else {
545                         warning << string_compose(_("Plugin preset %1 not found"),
546                                                   _preset_combo.get_active_text()) << endmsg;
547                 }
548         }
549 }
550
551 void
552 PlugUIBase::add_plugin_setting ()
553 {
554         NewPluginPresetDialog d (plugin);
555
556         switch (d.run ()) {
557         case Gtk::RESPONSE_ACCEPT:
558                 if (d.name().empty()) {
559                         break;
560                 }
561
562                 if (d.replace ()) {
563                         plugin->remove_preset (d.name ());
564                 }
565
566                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
567                 if (!r.uri.empty ()) {
568                         plugin->load_preset (r);
569                 }
570                 break;
571         }
572 }
573
574 void
575 PlugUIBase::save_plugin_setting ()
576 {
577         string const name = _preset_combo.get_active_text ();
578         plugin->remove_preset (name);
579         Plugin::PresetRecord const r = plugin->save_preset (name);
580         if (!r.uri.empty ()) {
581                 plugin->load_preset (r);
582         }
583 }
584
585 void
586 PlugUIBase::delete_plugin_setting ()
587 {
588         plugin->remove_preset (_preset_combo.get_active_text ());
589 }
590
591 void
592 PlugUIBase::bypass_toggled ()
593 {
594         bool x;
595
596         if ((x = bypass_button.get_active()) == insert->active()) {
597                 if (x) {
598                         insert->deactivate ();
599                 } else {
600                         insert->activate ();
601                 }
602         }
603 }
604
605 bool
606 PlugUIBase::focus_toggled (GdkEventButton*)
607 {
608         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
609                 Keyboard::the_keyboard().magic_widget_drop_focus();
610                 focus_button.remove ();
611                 focus_button.add (*focus_out_image);
612                 focus_out_image->show ();
613                 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));
614                 KeyboardFocused (false);
615         } else {
616                 Keyboard::the_keyboard().magic_widget_grab_focus();
617                 focus_button.remove ();
618                 focus_button.add (*focus_in_image);
619                 focus_in_image->show ();
620                 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
621                 KeyboardFocused (true);
622         }
623
624         return true;
625 }
626
627 void
628 PlugUIBase::toggle_plugin_analysis()
629 {
630         if (plugin_analysis_expander.get_expanded() &&
631             !plugin_analysis_expander.get_child()) {
632                 // Create the GUI
633                 if (eqgui == 0) {
634                         eqgui = new PluginEqGui (insert);
635                 }
636
637                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
638
639                 if (toplevel) {
640                         toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
641                 }
642
643                 plugin_analysis_expander.add (*eqgui);
644                 plugin_analysis_expander.show_all ();
645                 eqgui->start_listening ();
646         }
647
648         if (!plugin_analysis_expander.get_expanded()) {
649
650                 // Hide & remove from expander
651
652                 eqgui->hide ();
653                 eqgui->stop_listening ();
654                 plugin_analysis_expander.remove();
655
656                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
657
658                 if (toplevel) {
659                         toplevel->resize (pre_eq_size.width, pre_eq_size.height);
660                 }
661         }
662 }
663
664 void
665 PlugUIBase::update_preset_list ()
666 {
667         vector<string> preset_labels;
668         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
669
670         ++_no_load_preset;
671
672         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
673                 preset_labels.push_back (i->label);
674         }
675
676         set_popdown_strings (_preset_combo, preset_labels);
677
678         --_no_load_preset;
679 }
680
681 void
682 PlugUIBase::update_preset ()
683 {
684         Plugin::PresetRecord p = plugin->last_preset();
685
686         ++_no_load_preset;
687         _preset_combo.set_active_text (p.label);
688         --_no_load_preset;
689
690         save_button.set_sensitive (!p.uri.empty() && p.user);
691         delete_button.set_sensitive (!p.uri.empty() && p.user);
692
693         update_preset_modified ();
694 }
695
696 void
697 PlugUIBase::update_preset_modified ()
698 {
699         if (plugin->last_preset().uri.empty()) {
700                 _preset_modified.set_text ("");
701                 return;
702         }
703
704         bool const c = plugin->parameter_changed_since_last_preset ();
705         if (_preset_modified.get_text().empty() == c) {
706                 _preset_modified.set_text (c ? "*" : "");
707         }
708 }
709
710 void
711 PlugUIBase::parameter_changed (uint32_t, float)
712 {
713         update_preset_modified ();
714 }
715
716 void
717 PlugUIBase::preset_added_or_removed ()
718 {
719         /* Update both the list and the currently-displayed preset */
720         update_preset_list ();
721         update_preset ();
722 }