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"
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 "widgets/tooltips.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 WINDOWS_VST_SUPPORT
50 #include "ardour/windows_vst_plugin.h"
51 #include "windows_vst_plugin_ui.h"
54 #include "ardour/lxvst_plugin.h"
55 #include "lxvst_plugin_ui.h"
58 #include "ardour/mac_vst_plugin.h"
59 #include "vst_plugin_ui.h"
62 #include "ardour/lv2_plugin.h"
63 #include "lv2_plugin_ui.h"
66 #include "ardour_window.h"
67 #include "ardour_ui.h"
69 #include "plugin_ui.h"
71 #include "gui_thread.h"
72 #include "public_editor.h"
73 #include "processor_box.h"
75 #include "latency_gui.h"
76 #include "plugin_eq_gui.h"
77 #include "new_plugin_preset_dialog.h"
82 using namespace ARDOUR;
83 using namespace ARDOUR_UI_UTILS;
84 using namespace ArdourWidgets;
86 using namespace Gtkmm2ext;
89 PluginUIWindow::PluginUIWindow (
90 boost::shared_ptr<PluginInsert> insert,
93 : ArdourWindow (string())
95 , _keyboard_focused (false)
96 #ifdef AUDIOUNIT_SUPPORT
97 , pre_deactivate_x (-1)
98 , pre_deactivate_y (-1)
102 bool have_gui = false;
103 Label* label = manage (new Label());
104 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
106 if (editor && insert->plugin()->has_editor()) {
107 switch (insert->type()) {
108 case ARDOUR::Windows_VST:
109 have_gui = create_windows_vst_editor (insert);
113 have_gui = create_lxvst_editor (insert);
117 have_gui = create_mac_vst_editor (insert);
120 case ARDOUR::AudioUnit:
121 have_gui = create_audiounit_editor (insert);
125 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
129 have_gui = create_lv2_editor (insert);
133 #ifndef WINDOWS_VST_SUPPORT
134 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
137 error << _("unknown type of editor-supplying plugin")
140 throw failed_constructor ();
146 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
149 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
151 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
153 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
154 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
157 set_name ("PluginEditor");
158 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
160 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
162 gint h = _pluginui->get_preferred_height ();
163 gint w = _pluginui->get_preferred_width ();
166 if (h > 600) h = 600;
169 set_default_size (w, h);
170 set_resizable (_pluginui->resizable());
173 PluginUIWindow::~PluginUIWindow ()
176 cerr << "PluginWindow deleted for " << this << endl;
182 PluginUIWindow::on_show ()
184 set_role("plugin_ui");
187 _pluginui->update_preset_list ();
188 _pluginui->update_preset ();
192 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
193 if (pre_deactivate_x >= 0) {
194 move (pre_deactivate_x, pre_deactivate_y);
198 if (_pluginui->on_window_show (_title)) {
205 PluginUIWindow::on_hide ()
207 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
208 get_position (pre_deactivate_x, pre_deactivate_y);
214 _pluginui->on_window_hide ();
219 PluginUIWindow::set_title(const std::string& title)
221 Gtk::Window::set_title(title);
226 #ifdef WINDOWS_VST_SUPPORT
227 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
229 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
232 #ifndef WINDOWS_VST_SUPPORT
236 boost::shared_ptr<WindowsVSTPlugin> vp;
238 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
239 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
241 throw failed_constructor ();
243 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
246 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
248 vpu->package (*this);
257 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
259 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
262 #ifndef LXVST_SUPPORT
266 boost::shared_ptr<LXVSTPlugin> lxvp;
268 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
269 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
271 throw failed_constructor ();
273 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
276 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
278 lxvpu->package (*this);
286 #ifdef MACVST_SUPPORT
287 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert> insert)
289 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert>)
292 #ifndef MACVST_SUPPORT
295 boost::shared_ptr<MacVSTPlugin> mvst;
296 if ((mvst = boost::dynamic_pointer_cast<MacVSTPlugin> (insert->plugin())) == 0) {
297 error << string_compose (_("unknown type of editor-supplying plugin (note: no MacVST support in this version of %1)"), PROGRAM_NAME)
299 throw failed_constructor ();
301 VSTPluginUI* vpu = create_mac_vst_gui (insert);
303 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
305 vpu->package (*this);
307 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
315 #ifdef AUDIOUNIT_SUPPORT
316 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
318 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
321 #ifndef AUDIOUNIT_SUPPORT
325 _pluginui = create_au_gui (insert, &box);
326 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
329 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
337 PluginUIWindow::app_activated (bool yn)
339 PluginUIWindow::app_activated (bool)
342 #ifdef AUDIOUNIT_SUPPORT
346 _pluginui->activate ();
347 if (pre_deactivate_x >= 0) {
348 move (pre_deactivate_x, pre_deactivate_y);
354 was_visible = is_visible();
355 get_position (pre_deactivate_x, pre_deactivate_y);
357 _pluginui->deactivate ();
364 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
367 boost::shared_ptr<LV2Plugin> vp;
369 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
370 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
371 throw failed_constructor ();
373 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
376 lpu->package (*this);
377 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
387 PluginUIWindow::keyboard_focused (bool yn)
389 _keyboard_focused = yn;
393 PluginUIWindow::on_key_press_event (GdkEventKey* event)
395 if (_keyboard_focused) {
397 _pluginui->grab_focus();
398 if (_pluginui->non_gtk_gui()) {
399 _pluginui->forward_key_event (event);
401 return relay_key_press (event, this);
406 /* for us to be getting key press events, there really
407 MUST be a _pluginui, but just to be safe, check ...
411 _pluginui->grab_focus();
412 if (_pluginui->non_gtk_gui()) {
413 /* pass main window as the window for the event
414 to be handled in, not this one, because there are
415 no widgets in this window that we want to have
418 return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
420 return relay_key_press (event, this);
428 PluginUIWindow::on_key_release_event (GdkEventKey *event)
430 if (_keyboard_focused) {
432 if (_pluginui->non_gtk_gui()) {
433 _pluginui->forward_key_event (event);
444 PluginUIWindow::plugin_going_away ()
446 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
449 _pluginui->stop_updating(0);
452 death_connection.disconnect ();
455 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
457 , plugin (insert->plugin())
458 , add_button (_("Add"))
459 , save_button (_("Save"))
460 , delete_button (_("Delete"))
461 , reset_button (_("Reset"))
462 , bypass_button (ArdourButton::led_default_elements)
463 , pin_management_button (_("Pinout"))
464 , description_expander (_("Description"))
465 , plugin_analysis_expander (_("Plugin analysis"))
470 _preset_modified.set_size_request (16, -1);
471 _preset_combo.set_text("(default)");
472 set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
473 set_tooltip (add_button, _("Save a new preset"));
474 set_tooltip (save_button, _("Save the current preset"));
475 set_tooltip (delete_button, _("Delete the current preset"));
476 set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
477 set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
478 set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
481 update_preset_list ();
484 add_button.set_name ("generic button");
485 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
487 save_button.set_name ("generic button");
488 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
490 delete_button.set_name ("generic button");
491 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
493 reset_button.set_name ("generic button");
494 reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
496 pin_management_button.set_name ("generic button");
497 pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
499 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
501 bypass_button.set_name ("plugin bypass button");
502 bypass_button.set_text (_("Bypass"));
503 bypass_button.set_active (!pi->enabled ());
504 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
505 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
507 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
508 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
510 /* these images are not managed, so that we can remove them at will */
512 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
513 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
515 focus_button.add (*focus_out_image);
517 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));
518 set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
520 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
521 description_expander.set_expanded(false);
523 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
524 plugin_analysis_expander.set_expanded(false);
526 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
528 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
529 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
530 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
531 plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
533 insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
535 automation_state_changed();
538 PlugUIBase::~PlugUIBase()
545 PlugUIBase::plugin_going_away ()
547 /* drop references to the plugin/insert */
553 PlugUIBase::set_latency_label ()
555 framecnt_t const l = insert->effective_latency ();
556 framecnt_t const sr = insert->session().frame_rate ();
561 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
563 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
566 latency_button.set_text (t);
570 PlugUIBase::latency_button_clicked ()
573 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
574 latency_dialog = new ArdourWindow (_("Edit Latency"));
575 /* use both keep-above and transient for to try cover as many
576 different WM's as possible.
578 latency_dialog->set_keep_above (true);
579 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
581 latency_dialog->set_transient_for (*win);
583 latency_dialog->add (*latency_gui);
584 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
587 latency_dialog->show_all ();
591 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
593 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
594 boost::shared_ptr<Processor> p (weak_p.lock());
597 bypass_button.set_active (!p->enabled ());
602 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
604 if (_no_load_preset) {
607 if (!preset.label.empty()) {
608 insert->load_preset (preset);
610 // blank selected = no preset
611 plugin->clear_preset();
615 #ifdef NO_PLUGIN_STATE
616 static bool seen_saving_message = false;
618 static void show_no_plugin_message()
620 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
623 info << _("To get full access to updates without this limitation\n"
624 "consider becoming a subscriber for a low cost every month.")
626 info << X_("https://community.ardour.org/s/subscribe")
628 ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
633 PlugUIBase::add_plugin_setting ()
635 #ifndef NO_PLUGIN_STATE
636 NewPluginPresetDialog d (plugin, _("New Preset"));
639 case Gtk::RESPONSE_ACCEPT:
640 if (d.name().empty()) {
645 plugin->remove_preset (d.name ());
648 Plugin::PresetRecord const r = plugin->save_preset (d.name());
649 if (!r.uri.empty ()) {
650 plugin->load_preset (r);
655 if (!seen_saving_message) {
656 seen_saving_message = true;
657 show_no_plugin_message();
663 PlugUIBase::save_plugin_setting ()
665 #ifndef NO_PLUGIN_STATE
666 string const name = _preset_combo.get_text ();
667 plugin->remove_preset (name);
668 Plugin::PresetRecord const r = plugin->save_preset (name);
669 if (!r.uri.empty ()) {
670 plugin->load_preset (r);
673 if (!seen_saving_message) {
674 seen_saving_message = true;
675 show_no_plugin_message();
681 PlugUIBase::delete_plugin_setting ()
683 #ifndef NO_PLUGIN_STATE
684 plugin->remove_preset (_preset_combo.get_text ());
686 if (!seen_saving_message) {
687 seen_saving_message = true;
688 show_no_plugin_message();
694 PlugUIBase::automation_state_changed ()
696 reset_button.set_sensitive (insert->can_reset_all_parameters());
700 PlugUIBase::reset_plugin_parameters ()
702 insert->reset_parameters_to_default ();
706 PlugUIBase::manage_pins ()
708 PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
712 proxy->get ()->raise();
717 PlugUIBase::bypass_button_release (GdkEventButton*)
719 bool view_says_bypassed = (bypass_button.active_state() != 0);
721 if (view_says_bypassed != insert->enabled ()) {
722 insert->enable (view_says_bypassed);
729 PlugUIBase::focus_toggled (GdkEventButton*)
731 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
732 Keyboard::the_keyboard().magic_widget_drop_focus();
733 focus_button.remove ();
734 focus_button.add (*focus_out_image);
735 focus_out_image->show ();
736 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));
737 KeyboardFocused (false);
739 Keyboard::the_keyboard().magic_widget_grab_focus();
740 focus_button.remove ();
741 focus_button.add (*focus_in_image);
742 focus_in_image->show ();
743 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
744 KeyboardFocused (true);
751 PlugUIBase::toggle_description()
753 if (description_expander.get_expanded() &&
754 !description_expander.get_child()) {
755 const std::string text = plugin->get_docs();
760 Gtk::Label* label = manage(new Gtk::Label(text));
761 label->set_line_wrap(true);
762 label->set_line_wrap_mode(Pango::WRAP_WORD);
763 description_expander.add(*label);
764 description_expander.show_all();
767 if (!description_expander.get_expanded()) {
768 const int child_height = description_expander.get_child ()->get_height ();
770 description_expander.remove();
772 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
776 toplevel->get_size (wr.width, wr.height);
777 wr.height -= child_height;
778 toplevel->resize (wr.width, wr.height);
786 PlugUIBase::toggle_plugin_analysis()
788 if (plugin_analysis_expander.get_expanded() &&
789 !plugin_analysis_expander.get_child()) {
792 eqgui = new PluginEqGui (insert);
795 plugin_analysis_expander.add (*eqgui);
796 plugin_analysis_expander.show_all ();
797 eqgui->start_listening ();
800 if (!plugin_analysis_expander.get_expanded()) {
801 // Hide & remove from expander
802 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
805 eqgui->stop_listening ();
806 plugin_analysis_expander.remove();
808 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
812 toplevel->get_size (wr.width, wr.height);
813 wr.height -= child_height;
814 toplevel->resize (wr.width, wr.height);
820 PlugUIBase::update_preset_list ()
822 using namespace Menu_Helpers;
824 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
828 // Add a menu entry for each preset
829 _preset_combo.clear_items();
830 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
831 _preset_combo.AddMenuElem(
832 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
835 // Add an empty entry for un-setting current preset (see preset_selected)
836 Plugin::PresetRecord no_preset;
837 _preset_combo.AddMenuElem(
838 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
844 PlugUIBase::update_preset ()
846 Plugin::PresetRecord p = plugin->last_preset();
850 _preset_combo.set_text (_("(none)"));
852 _preset_combo.set_text (p.label);
856 save_button.set_sensitive (!p.uri.empty() && p.user);
857 delete_button.set_sensitive (!p.uri.empty() && p.user);
859 update_preset_modified ();
863 PlugUIBase::update_preset_modified ()
866 if (plugin->last_preset().uri.empty()) {
867 _preset_modified.set_text ("");
871 bool const c = plugin->parameter_changed_since_last_preset ();
872 if (_preset_modified.get_text().empty() == c) {
873 _preset_modified.set_text (c ? "*" : "");
878 PlugUIBase::preset_added_or_removed ()
880 /* Update both the list and the currently-displayed preset */
881 update_preset_list ();