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.
21 #include "gtk2ardour-config.h"
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"
36 #include "gtkmm2ext/utils.h"
37 #include "gtkmm2ext/doi.h"
38 #include "gtkmm2ext/application.h"
40 #include "widgets/tooltips.h"
41 #include "widgets/fastmeter.h"
43 #include "ardour/session.h"
44 #include "ardour/plugin.h"
45 #include "ardour/plugin_insert.h"
46 #include "ardour/ladspa_plugin.h"
47 #ifdef WINDOWS_VST_SUPPORT
48 #include "ardour/windows_vst_plugin.h"
49 #include "windows_vst_plugin_ui.h"
52 #include "ardour/lxvst_plugin.h"
53 #include "lxvst_plugin_ui.h"
56 #include "ardour/mac_vst_plugin.h"
57 #include "vst_plugin_ui.h"
60 #include "ardour/lv2_plugin.h"
61 #include "lv2_plugin_ui.h"
64 #include "ardour_window.h"
65 #include "ardour_ui.h"
66 #include "plugin_ui.h"
68 #include "gui_thread.h"
69 #include "public_editor.h"
70 #include "processor_box.h"
72 #include "latency_gui.h"
73 #include "plugin_eq_gui.h"
74 #include "new_plugin_preset_dialog.h"
79 using namespace ARDOUR;
80 using namespace ARDOUR_UI_UTILS;
81 using namespace ArdourWidgets;
83 using namespace Gtkmm2ext;
86 PluginUIWindow::PluginUIWindow (
87 boost::shared_ptr<PluginInsert> insert,
90 : ArdourWindow (string())
92 , _keyboard_focused (false)
93 #ifdef AUDIOUNIT_SUPPORT
94 , pre_deactivate_x (-1)
95 , pre_deactivate_y (-1)
99 bool have_gui = false;
100 Label* label = manage (new Label());
101 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
103 if (editor && insert->plugin()->has_editor()) {
104 switch (insert->type()) {
105 case ARDOUR::Windows_VST:
106 have_gui = create_windows_vst_editor (insert);
110 have_gui = create_lxvst_editor (insert);
114 have_gui = create_mac_vst_editor (insert);
117 case ARDOUR::AudioUnit:
118 have_gui = create_audiounit_editor (insert);
122 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
126 have_gui = create_lv2_editor (insert);
130 #ifndef WINDOWS_VST_SUPPORT
131 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
134 error << _("unknown type of editor-supplying plugin")
137 throw failed_constructor ();
143 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
146 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
148 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
150 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
151 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
154 set_name ("PluginEditor");
155 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
157 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
159 gint h = _pluginui->get_preferred_height ();
160 gint w = _pluginui->get_preferred_width ();
163 if (h > 600) h = 600;
166 set_default_size (w, h);
167 set_resizable (_pluginui->resizable());
170 PluginUIWindow::~PluginUIWindow ()
173 cerr << "PluginWindow deleted for " << this << endl;
179 PluginUIWindow::on_show ()
181 set_role("plugin_ui");
184 _pluginui->update_preset_list ();
185 _pluginui->update_preset ();
189 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
190 if (pre_deactivate_x >= 0) {
191 move (pre_deactivate_x, pre_deactivate_y);
195 if (_pluginui->on_window_show (_title)) {
202 PluginUIWindow::on_hide ()
204 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
205 get_position (pre_deactivate_x, pre_deactivate_y);
211 _pluginui->on_window_hide ();
216 PluginUIWindow::set_title(const std::string& title)
218 Gtk::Window::set_title(title);
223 #ifdef WINDOWS_VST_SUPPORT
224 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
226 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
229 #ifndef WINDOWS_VST_SUPPORT
233 boost::shared_ptr<WindowsVSTPlugin> vp;
235 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
236 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
238 throw failed_constructor ();
240 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
243 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
245 vpu->package (*this);
254 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
256 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
259 #ifndef LXVST_SUPPORT
263 boost::shared_ptr<LXVSTPlugin> lxvp;
265 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
266 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
268 throw failed_constructor ();
270 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
273 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
275 lxvpu->package (*this);
283 #ifdef MACVST_SUPPORT
284 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert> insert)
286 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert>)
289 #ifndef MACVST_SUPPORT
292 boost::shared_ptr<MacVSTPlugin> mvst;
293 if ((mvst = boost::dynamic_pointer_cast<MacVSTPlugin> (insert->plugin())) == 0) {
294 error << string_compose (_("unknown type of editor-supplying plugin (note: no MacVST support in this version of %1)"), PROGRAM_NAME)
296 throw failed_constructor ();
298 VSTPluginUI* vpu = create_mac_vst_gui (insert);
300 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
302 vpu->package (*this);
304 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
312 #ifdef AUDIOUNIT_SUPPORT
313 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
315 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
318 #ifndef AUDIOUNIT_SUPPORT
322 _pluginui = create_au_gui (insert, &box);
323 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
326 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
334 PluginUIWindow::app_activated (bool yn)
336 PluginUIWindow::app_activated (bool)
339 #ifdef AUDIOUNIT_SUPPORT
343 _pluginui->activate ();
344 if (pre_deactivate_x >= 0) {
345 move (pre_deactivate_x, pre_deactivate_y);
351 was_visible = is_visible();
352 get_position (pre_deactivate_x, pre_deactivate_y);
354 _pluginui->deactivate ();
361 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
364 boost::shared_ptr<LV2Plugin> vp;
366 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
367 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
368 throw failed_constructor ();
370 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
373 lpu->package (*this);
374 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
384 PluginUIWindow::keyboard_focused (bool yn)
386 _keyboard_focused = yn;
390 PluginUIWindow::on_key_press_event (GdkEventKey* event)
392 if (_keyboard_focused) {
394 _pluginui->grab_focus();
395 if (_pluginui->non_gtk_gui()) {
396 _pluginui->forward_key_event (event);
398 return relay_key_press (event, this);
403 /* for us to be getting key press events, there really
404 MUST be a _pluginui, but just to be safe, check ...
408 _pluginui->grab_focus();
409 if (_pluginui->non_gtk_gui()) {
410 /* pass main window as the window for the event
411 to be handled in, not this one, because there are
412 no widgets in this window that we want to have
415 return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
417 return relay_key_press (event, this);
425 PluginUIWindow::on_key_release_event (GdkEventKey *event)
427 if (_keyboard_focused) {
429 if (_pluginui->non_gtk_gui()) {
430 _pluginui->forward_key_event (event);
434 gtk_window_propagate_key_event (GTK_WINDOW(gobj()), event);
436 /* don't forward releases */
441 PluginUIWindow::plugin_going_away ()
443 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
446 _pluginui->stop_updating(0);
449 death_connection.disconnect ();
452 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
454 , plugin (insert->plugin())
455 , add_button (_("Add"))
456 , save_button (_("Save"))
457 , delete_button (_("Delete"))
458 , reset_button (_("Reset"))
459 , bypass_button (ArdourButton::led_default_elements)
460 , pin_management_button (_("Pinout"))
461 , description_expander (_("Description"))
462 , plugin_analysis_expander (_("Plugin analysis"))
467 _preset_modified.set_size_request (16, -1);
468 _preset_combo.set_text("(default)");
469 set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
470 set_tooltip (add_button, _("Save a new preset"));
471 set_tooltip (save_button, _("Save the current preset"));
472 set_tooltip (delete_button, _("Delete the current preset"));
473 set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
474 set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
475 set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
478 update_preset_list ();
481 add_button.set_name ("generic button");
482 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
484 save_button.set_name ("generic button");
485 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
487 delete_button.set_name ("generic button");
488 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
490 reset_button.set_name ("generic button");
491 reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
493 pin_management_button.set_name ("generic button");
494 pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
496 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
498 bypass_button.set_name ("plugin bypass button");
499 bypass_button.set_text (_("Bypass"));
500 bypass_button.set_active (!pi->enabled ());
501 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
502 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
504 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
505 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
507 /* these images are not managed, so that we can remove them at will */
509 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
510 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
512 focus_button.add (*focus_out_image);
514 set_tooltip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
515 set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
517 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
518 description_expander.set_expanded(false);
520 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
521 plugin_analysis_expander.set_expanded(false);
523 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
525 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
526 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
527 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
528 plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
530 insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
532 automation_state_changed();
535 PlugUIBase::~PlugUIBase()
542 PlugUIBase::plugin_going_away ()
544 /* drop references to the plugin/insert */
550 PlugUIBase::set_latency_label ()
552 framecnt_t const l = insert->effective_latency ();
553 framecnt_t const sr = insert->session().frame_rate ();
558 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
560 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
563 latency_button.set_text (t);
567 PlugUIBase::latency_button_clicked ()
570 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
571 latency_dialog = new ArdourWindow (_("Edit Latency"));
572 /* use both keep-above and transient for to try cover as many
573 different WM's as possible.
575 latency_dialog->set_keep_above (true);
576 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
578 latency_dialog->set_transient_for (*win);
580 latency_dialog->add (*latency_gui);
581 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
584 latency_dialog->show_all ();
588 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
590 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
591 boost::shared_ptr<Processor> p (weak_p.lock());
594 bypass_button.set_active (!p->enabled ());
599 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
601 if (_no_load_preset) {
604 if (!preset.label.empty()) {
605 insert->load_preset (preset);
607 // blank selected = no preset
608 plugin->clear_preset();
612 #ifdef NO_PLUGIN_STATE
613 static bool seen_saving_message = false;
615 static void show_no_plugin_message()
617 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
620 info << _("To get full access to updates without this limitation\n"
621 "consider becoming a subscriber for a low cost every month.")
623 info << X_("https://community.ardour.org/s/subscribe")
625 ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
630 PlugUIBase::add_plugin_setting ()
632 #ifndef NO_PLUGIN_STATE
633 NewPluginPresetDialog d (plugin, _("New Preset"));
636 case Gtk::RESPONSE_ACCEPT:
637 if (d.name().empty()) {
642 plugin->remove_preset (d.name ());
645 Plugin::PresetRecord const r = plugin->save_preset (d.name());
646 if (!r.uri.empty ()) {
647 plugin->load_preset (r);
652 if (!seen_saving_message) {
653 seen_saving_message = true;
654 show_no_plugin_message();
660 PlugUIBase::save_plugin_setting ()
662 #ifndef NO_PLUGIN_STATE
663 string const name = _preset_combo.get_text ();
664 plugin->remove_preset (name);
665 Plugin::PresetRecord const r = plugin->save_preset (name);
666 if (!r.uri.empty ()) {
667 plugin->load_preset (r);
670 if (!seen_saving_message) {
671 seen_saving_message = true;
672 show_no_plugin_message();
678 PlugUIBase::delete_plugin_setting ()
680 #ifndef NO_PLUGIN_STATE
681 plugin->remove_preset (_preset_combo.get_text ());
683 if (!seen_saving_message) {
684 seen_saving_message = true;
685 show_no_plugin_message();
691 PlugUIBase::automation_state_changed ()
693 reset_button.set_sensitive (insert->can_reset_all_parameters());
697 PlugUIBase::reset_plugin_parameters ()
699 insert->reset_parameters_to_default ();
703 PlugUIBase::manage_pins ()
705 PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
709 proxy->get ()->raise();
714 PlugUIBase::bypass_button_release (GdkEventButton*)
716 bool view_says_bypassed = (bypass_button.active_state() != 0);
718 if (view_says_bypassed != insert->enabled ()) {
719 insert->enable (view_says_bypassed);
726 PlugUIBase::focus_toggled (GdkEventButton*)
728 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
729 Keyboard::the_keyboard().magic_widget_drop_focus();
730 focus_button.remove ();
731 focus_button.add (*focus_out_image);
732 focus_out_image->show ();
733 set_tooltip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
734 KeyboardFocused (false);
736 Keyboard::the_keyboard().magic_widget_grab_focus();
737 focus_button.remove ();
738 focus_button.add (*focus_in_image);
739 focus_in_image->show ();
740 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
741 KeyboardFocused (true);
748 PlugUIBase::toggle_description()
750 if (description_expander.get_expanded() &&
751 !description_expander.get_child()) {
752 const std::string text = plugin->get_docs();
757 Gtk::Label* label = manage(new Gtk::Label(text));
758 label->set_line_wrap(true);
759 label->set_line_wrap_mode(Pango::WRAP_WORD);
760 description_expander.add(*label);
761 description_expander.show_all();
764 if (!description_expander.get_expanded()) {
765 const int child_height = description_expander.get_child ()->get_height ();
767 description_expander.remove();
769 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
773 toplevel->get_size (wr.width, wr.height);
774 wr.height -= child_height;
775 toplevel->resize (wr.width, wr.height);
783 PlugUIBase::toggle_plugin_analysis()
785 if (plugin_analysis_expander.get_expanded() &&
786 !plugin_analysis_expander.get_child()) {
789 eqgui = new PluginEqGui (insert);
792 plugin_analysis_expander.add (*eqgui);
793 plugin_analysis_expander.show_all ();
794 eqgui->start_listening ();
797 if (!plugin_analysis_expander.get_expanded()) {
798 // Hide & remove from expander
799 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
802 eqgui->stop_listening ();
803 plugin_analysis_expander.remove();
805 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
809 toplevel->get_size (wr.width, wr.height);
810 wr.height -= child_height;
811 toplevel->resize (wr.width, wr.height);
817 PlugUIBase::update_preset_list ()
819 using namespace Menu_Helpers;
821 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
825 // Add a menu entry for each preset
826 _preset_combo.clear_items();
827 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
828 _preset_combo.AddMenuElem(
829 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
832 // Add an empty entry for un-setting current preset (see preset_selected)
833 Plugin::PresetRecord no_preset;
834 _preset_combo.AddMenuElem(
835 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
841 PlugUIBase::update_preset ()
843 Plugin::PresetRecord p = plugin->last_preset();
847 _preset_combo.set_text (_("(none)"));
849 _preset_combo.set_text (p.label);
853 save_button.set_sensitive (!p.uri.empty() && p.user);
854 delete_button.set_sensitive (!p.uri.empty() && p.user);
856 update_preset_modified ();
860 PlugUIBase::update_preset_modified ()
863 if (plugin->last_preset().uri.empty()) {
864 _preset_modified.set_text ("");
868 bool const c = plugin->parameter_changed_since_last_preset ();
869 if (_preset_modified.get_text().empty() == c) {
870 _preset_modified.set_text (c ? "*" : "");
875 PlugUIBase::preset_added_or_removed ()
877 /* Update both the list and the currently-displayed preset */
878 update_preset_list ();