Fix corruption of follow playhead state on quit (#4048).
[ardour2.git] / gtk2_ardour / plugin_ui.cc
blob76764f8a5c77e8ccb47dd6798e6c01099cc6f34f
1 /*
2 Copyright (C) 2000 Paul Davis
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.
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.
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.
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
24 #include <climits>
25 #include <cerrno>
26 #include <cmath>
27 #include <string>
29 #include "pbd/stl_delete.h"
30 #include "pbd/xml++.h"
31 #include "pbd/failed_constructor.h"
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>
43 #include "midi++/manager.h"
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
58 #include <lrdf.h>
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"
72 #include "i18n.h"
74 using namespace std;
75 using namespace ARDOUR;
76 using namespace PBD;
77 using namespace Gtkmm2ext;
78 using namespace Gtk;
80 PluginUIWindow::PluginUIWindow (Gtk::Window* win, boost::shared_ptr<PluginInsert> insert, bool scrollable)
81 : parent (win)
82 , was_visible (false)
83 , _keyboard_focused (false)
85 bool have_gui = false;
87 Label* label = manage (new Label());
88 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
90 if (insert->plugin()->has_editor()) {
91 switch (insert->type()) {
92 case ARDOUR::VST:
93 have_gui = create_vst_editor (insert);
94 break;
96 case ARDOUR::AudioUnit:
97 have_gui = create_audiounit_editor (insert);
98 break;
100 case ARDOUR::LADSPA:
101 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
102 break;
104 case ARDOUR::LV2:
105 have_gui = create_lv2_editor (insert);
106 break;
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 ();
121 if (!have_gui) {
123 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
125 _pluginui = pu;
126 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
127 add (*pu);
128 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
130 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
131 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
134 // set_position (Gtk::WIN_POS_MOUSE);
135 set_name ("PluginEditor");
136 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
138 signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
139 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
141 gint h = _pluginui->get_preferred_height ();
142 gint w = _pluginui->get_preferred_width ();
144 if (scrollable) {
145 if (h > 600) h = 600;
146 if (w > 600) w = 600;
148 if (w < 0) {
149 w = 450;
153 set_default_size (w, h);
156 PluginUIWindow::~PluginUIWindow ()
158 delete _pluginui;
161 void
162 PluginUIWindow::set_parent (Gtk::Window* win)
164 parent = win;
167 void
168 PluginUIWindow::on_map ()
170 Window::on_map ();
171 set_keep_above (true);
174 bool
175 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
177 Keyboard::the_keyboard().enter_window (ev, this);
178 return false;
181 bool
182 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
184 Keyboard::the_keyboard().leave_window (ev, this);
185 return false;
188 bool
189 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
191 Window::on_focus_in_event (ev);
192 //Keyboard::the_keyboard().magic_widget_grab_focus ();
193 return false;
196 bool
197 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
199 Window::on_focus_out_event (ev);
200 //Keyboard::the_keyboard().magic_widget_drop_focus ();
201 return false;
204 void
205 PluginUIWindow::on_show ()
207 set_role("plugin_ui");
209 if (_pluginui) {
210 _pluginui->update_preset_list ();
211 _pluginui->update_preset ();
214 if (_pluginui) {
215 if (_pluginui->on_window_show (_title)) {
216 Window::on_show ();
220 if (parent) {
221 // set_transient_for (*parent);
225 void
226 PluginUIWindow::on_hide ()
228 Window::on_hide ();
230 if (_pluginui) {
231 _pluginui->on_window_hide ();
235 void
236 PluginUIWindow::set_title(const std::string& title)
238 Gtk::Window::set_title(title);
239 _title = title;
242 bool
243 #ifdef VST_SUPPORT
244 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert> insert)
245 #else
246 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert>)
247 #endif
249 #ifndef VST_SUPPORT
250 return false;
251 #else
253 boost::shared_ptr<VSTPlugin> vp;
255 if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) == 0) {
256 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
257 << endmsg;
258 throw failed_constructor ();
259 } else {
260 VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
262 _pluginui = vpu;
263 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
264 add (*vpu);
265 vpu->package (*this);
268 return true;
269 #endif
272 bool
273 #ifdef GTKOSX
274 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
275 #else
276 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
277 #endif
279 #ifndef GTKOSX
280 return false;
281 #else
282 VBox* box;
283 _pluginui = create_au_gui (insert, &box);
284 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
285 add (*box);
287 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
289 return true;
290 #endif
293 void
294 #ifdef GTKOSX
295 PluginUIWindow::app_activated (bool yn)
296 #else
297 PluginUIWindow::app_activated (bool)
298 #endif
300 #ifdef GTKOSX
301 if (_pluginui) {
302 if (yn) {
303 if (was_visible) {
304 _pluginui->activate ();
305 present ();
306 was_visible = true;
308 } else {
309 was_visible = is_visible();
310 hide ();
311 _pluginui->deactivate ();
314 #endif
317 bool
318 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
320 #if defined(HAVE_SLV2) || defined(HAVE_SUIL)
321 boost::shared_ptr<LV2Plugin> vp;
323 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
324 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
325 throw failed_constructor ();
326 } else {
327 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
328 _pluginui = lpu;
329 add (*lpu);
330 lpu->package (*this);
333 return true;
334 #else
335 return false;
336 #endif
339 void
340 PluginUIWindow::keyboard_focused (bool yn)
342 _keyboard_focused = yn;
345 bool
346 PluginUIWindow::on_key_press_event (GdkEventKey* event)
348 if (_keyboard_focused) {
349 if (_pluginui) {
350 if (_pluginui->non_gtk_gui()) {
351 _pluginui->forward_key_event (event);
352 } else {
353 return relay_key_press (event, this);
356 return true;
357 } else {
358 /* for us to be getting key press events, there really
359 MUST be a _pluginui, but just to be safe, check ...
362 if (_pluginui) {
363 if (_pluginui->non_gtk_gui()) {
364 /* pass editor window as the window for the event
365 to be handled in, not this one, because there are
366 no widgets in this window that we want to have
367 key focus.
369 return relay_key_press (event, &PublicEditor::instance());
370 } else {
371 return relay_key_press (event, this);
373 } else {
374 return false;
379 bool
380 PluginUIWindow::on_key_release_event (GdkEventKey *event)
382 if (_keyboard_focused) {
383 if (_pluginui) {
384 if (_pluginui->non_gtk_gui()) {
385 _pluginui->forward_key_event (event);
387 return true;
389 return false;
390 } else {
391 return true;
395 void
396 PluginUIWindow::plugin_going_away ()
398 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
400 if (_pluginui) {
401 _pluginui->stop_updating(0);
404 death_connection.disconnect ();
406 delete_when_idle (this);
409 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
410 : insert (pi)
411 , plugin (insert->plugin())
412 , add_button (_("Add"))
413 , save_button (_("Save"))
414 , delete_button (_("Delete"))
415 , bypass_button (_("Bypass"))
416 , latency_gui (0)
417 , latency_dialog (0)
418 , plugin_analysis_expander (_("Plugin analysis"))
419 , eqgui (0)
421 _preset_combo.set_size_request (100, -1);
422 _preset_modified.set_size_request (16, -1);
423 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
424 _no_load_preset = 0;
426 _preset_box.pack_start (_preset_combo);
427 _preset_box.pack_start (_preset_modified);
429 update_preset_list ();
430 update_preset ();
432 add_button.set_name ("PluginAddButton");
433 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
435 save_button.set_name ("PluginSaveButton");
436 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
438 delete_button.set_name ("PluginDeleteButton");
439 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
441 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
443 bypass_button.set_active (!pi->active());
445 bypass_button.set_name ("PluginBypassButton");
446 bypass_button.signal_toggled().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_toggled));
447 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
449 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
450 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
452 /* these images are not managed, so that we can remove them at will */
454 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
455 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
457 focus_button.add (*focus_out_image);
459 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));
460 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
462 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
463 plugin_analysis_expander.set_expanded(false);
465 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
467 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
468 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
469 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
470 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
473 PlugUIBase::~PlugUIBase()
475 delete eqgui;
476 delete latency_gui;
479 void
480 PlugUIBase::plugin_going_away ()
482 /* drop references to the plugin/insert */
483 insert.reset ();
484 plugin.reset ();
485 death_connection.disconnect ();
488 void
489 PlugUIBase::set_latency_label ()
491 framecnt_t const l = insert->effective_latency ();
492 framecnt_t const sr = insert->session().frame_rate ();
494 string t;
496 if (l < sr / 1000) {
497 t = string_compose (_("latency (%1 samples)"), l);
498 } else {
499 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
502 latency_label.set_text (t);
505 void
506 PlugUIBase::latency_button_clicked ()
508 if (!latency_gui) {
509 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
510 latency_dialog = new ArdourDialog (_("Edit Latency"), false, false);
511 latency_dialog->get_vbox()->pack_start (*latency_gui);
512 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
515 latency_dialog->show_all ();
518 void
519 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
521 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p)
522 boost::shared_ptr<Processor> p (weak_p);
523 if (p) {
524 bypass_button.set_active (!p->active());
528 void
529 PlugUIBase::preset_selected ()
531 if (_no_load_preset) {
532 return;
535 if (_preset_combo.get_active_text().length() > 0) {
536 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
537 if (pr) {
538 plugin->load_preset (*pr);
539 } else {
540 warning << string_compose(_("Plugin preset %1 not found"),
541 _preset_combo.get_active_text()) << endmsg;
546 void
547 PlugUIBase::add_plugin_setting ()
549 NewPluginPresetDialog d (plugin);
551 switch (d.run ()) {
552 case Gtk::RESPONSE_ACCEPT:
553 if (d.name().empty()) {
554 break;
557 if (d.replace ()) {
558 plugin->remove_preset (d.name ());
561 Plugin::PresetRecord const r = plugin->save_preset (d.name());
562 if (!r.uri.empty ()) {
563 plugin->load_preset (r);
565 break;
569 void
570 PlugUIBase::save_plugin_setting ()
572 string const name = _preset_combo.get_active_text ();
573 plugin->remove_preset (name);
574 Plugin::PresetRecord const r = plugin->save_preset (name);
575 if (!r.uri.empty ()) {
576 plugin->load_preset (r);
580 void
581 PlugUIBase::delete_plugin_setting ()
583 plugin->remove_preset (_preset_combo.get_active_text ());
586 void
587 PlugUIBase::bypass_toggled ()
589 bool x;
591 if ((x = bypass_button.get_active()) == insert->active()) {
592 if (x) {
593 insert->deactivate ();
594 } else {
595 insert->activate ();
600 bool
601 PlugUIBase::focus_toggled (GdkEventButton*)
603 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
604 Keyboard::the_keyboard().magic_widget_drop_focus();
605 focus_button.remove ();
606 focus_button.add (*focus_out_image);
607 focus_out_image->show ();
608 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));
609 KeyboardFocused (false);
610 } else {
611 Keyboard::the_keyboard().magic_widget_grab_focus();
612 focus_button.remove ();
613 focus_button.add (*focus_in_image);
614 focus_in_image->show ();
615 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
616 KeyboardFocused (true);
619 return true;
622 void
623 PlugUIBase::toggle_plugin_analysis()
625 if (plugin_analysis_expander.get_expanded() &&
626 !plugin_analysis_expander.get_child()) {
627 // Create the GUI
628 if (eqgui == 0) {
629 eqgui = new PluginEqGui (insert);
632 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
634 if (toplevel) {
635 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
638 plugin_analysis_expander.add (*eqgui);
639 plugin_analysis_expander.show_all ();
640 eqgui->start_listening ();
643 if (!plugin_analysis_expander.get_expanded()) {
645 // Hide & remove from expander
647 eqgui->hide ();
648 eqgui->stop_listening ();
649 plugin_analysis_expander.remove();
651 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
653 if (toplevel) {
654 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
659 void
660 PlugUIBase::update_preset_list ()
662 vector<string> preset_labels;
663 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
665 ++_no_load_preset;
667 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
668 preset_labels.push_back (i->label);
671 set_popdown_strings (_preset_combo, preset_labels);
673 --_no_load_preset;
676 void
677 PlugUIBase::update_preset ()
679 Plugin::PresetRecord p = plugin->last_preset();
681 ++_no_load_preset;
682 _preset_combo.set_active_text (p.label);
683 --_no_load_preset;
685 save_button.set_sensitive (!p.uri.empty() && p.user);
686 delete_button.set_sensitive (!p.uri.empty() && p.user);
688 update_preset_modified ();
691 void
692 PlugUIBase::update_preset_modified ()
694 if (plugin->last_preset().uri.empty()) {
695 _preset_modified.set_text ("");
696 return;
699 bool const c = plugin->parameter_changed_since_last_preset ();
700 if (_preset_modified.get_text().empty() == c) {
701 _preset_modified.set_text (c ? "*" : "");
705 void
706 PlugUIBase::parameter_changed (uint32_t, float)
708 update_preset_modified ();
711 void
712 PlugUIBase::preset_added_or_removed ()
714 /* Update both the list and the currently-displayed preset */
715 update_preset_list ();
716 update_preset ();