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 "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"
67 #include "plugin_ui.h"
69 #include "gui_thread.h"
70 #include "public_editor.h"
71 #include "processor_box.h"
73 #include "latency_gui.h"
74 #include "plugin_eq_gui.h"
75 #include "new_plugin_preset_dialog.h"
81 using namespace ARDOUR;
82 using namespace ARDOUR_UI_UTILS;
84 using namespace Gtkmm2ext;
87 PluginUIWindow::PluginUIWindow (
88 boost::shared_ptr<PluginInsert> insert,
91 : ArdourWindow (string())
93 , _keyboard_focused (false)
94 #ifdef AUDIOUNIT_SUPPORT
95 , pre_deactivate_x (-1)
96 , pre_deactivate_y (-1)
100 bool have_gui = false;
101 Label* label = manage (new Label());
102 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
104 if (editor && insert->plugin()->has_editor()) {
105 switch (insert->type()) {
106 case ARDOUR::Windows_VST:
107 have_gui = create_windows_vst_editor (insert);
111 have_gui = create_lxvst_editor (insert);
115 have_gui = create_mac_vst_editor (insert);
118 case ARDOUR::AudioUnit:
119 have_gui = create_audiounit_editor (insert);
123 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
127 have_gui = create_lv2_editor (insert);
131 #ifndef WINDOWS_VST_SUPPORT
132 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
135 error << _("unknown type of editor-supplying plugin")
138 throw failed_constructor ();
144 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
147 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
149 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
151 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
152 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
155 set_name ("PluginEditor");
156 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
158 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
160 gint h = _pluginui->get_preferred_height ();
161 gint w = _pluginui->get_preferred_width ();
164 if (h > 600) h = 600;
167 set_default_size (w, h);
168 set_resizable (_pluginui->resizable());
171 PluginUIWindow::~PluginUIWindow ()
174 cerr << "PluginWindow deleted for " << this << endl;
180 PluginUIWindow::on_show ()
182 set_role("plugin_ui");
185 _pluginui->update_preset_list ();
186 _pluginui->update_preset ();
190 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
191 if (pre_deactivate_x >= 0) {
192 move (pre_deactivate_x, pre_deactivate_y);
196 if (_pluginui->on_window_show (_title)) {
203 PluginUIWindow::on_hide ()
205 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
206 get_position (pre_deactivate_x, pre_deactivate_y);
212 _pluginui->on_window_hide ();
217 PluginUIWindow::set_title(const std::string& title)
219 Gtk::Window::set_title(title);
224 #ifdef WINDOWS_VST_SUPPORT
225 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
227 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
230 #ifndef WINDOWS_VST_SUPPORT
234 boost::shared_ptr<WindowsVSTPlugin> vp;
236 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
237 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
239 throw failed_constructor ();
241 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
244 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
246 vpu->package (*this);
255 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
257 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
260 #ifndef LXVST_SUPPORT
264 boost::shared_ptr<LXVSTPlugin> lxvp;
266 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
267 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
269 throw failed_constructor ();
271 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
274 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
276 lxvpu->package (*this);
284 #ifdef MACVST_SUPPORT
285 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert> insert)
287 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert>)
290 #ifndef MACVST_SUPPORT
293 boost::shared_ptr<MacVSTPlugin> mvst;
294 if ((mvst = boost::dynamic_pointer_cast<MacVSTPlugin> (insert->plugin())) == 0) {
295 error << string_compose (_("unknown type of editor-supplying plugin (note: no MacVST support in this version of %1)"), PROGRAM_NAME)
297 throw failed_constructor ();
299 VSTPluginUI* vpu = create_mac_vst_gui (insert);
301 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
303 vpu->package (*this);
305 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
313 #ifdef AUDIOUNIT_SUPPORT
314 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
316 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
319 #ifndef AUDIOUNIT_SUPPORT
323 _pluginui = create_au_gui (insert, &box);
324 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
327 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
335 PluginUIWindow::app_activated (bool yn)
337 PluginUIWindow::app_activated (bool)
340 #ifdef AUDIOUNIT_SUPPORT
344 _pluginui->activate ();
345 if (pre_deactivate_x >= 0) {
346 move (pre_deactivate_x, pre_deactivate_y);
352 was_visible = is_visible();
353 get_position (pre_deactivate_x, pre_deactivate_y);
355 _pluginui->deactivate ();
362 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
365 boost::shared_ptr<LV2Plugin> vp;
367 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
368 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
369 throw failed_constructor ();
371 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
374 lpu->package (*this);
375 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
385 PluginUIWindow::keyboard_focused (bool yn)
387 _keyboard_focused = yn;
391 PluginUIWindow::on_key_press_event (GdkEventKey* event)
393 if (_keyboard_focused) {
395 _pluginui->grab_focus();
396 if (_pluginui->non_gtk_gui()) {
397 _pluginui->forward_key_event (event);
399 return relay_key_press (event, this);
404 /* for us to be getting key press events, there really
405 MUST be a _pluginui, but just to be safe, check ...
409 _pluginui->grab_focus();
410 if (_pluginui->non_gtk_gui()) {
411 /* pass main window as the window for the event
412 to be handled in, not this one, because there are
413 no widgets in this window that we want to have
416 return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
418 return relay_key_press (event, this);
426 PluginUIWindow::on_key_release_event (GdkEventKey *event)
428 if (_keyboard_focused) {
430 if (_pluginui->non_gtk_gui()) {
431 _pluginui->forward_key_event (event);
442 PluginUIWindow::plugin_going_away ()
444 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
447 _pluginui->stop_updating(0);
450 death_connection.disconnect ();
453 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
455 , plugin (insert->plugin())
456 , add_button (_("Add"))
457 , save_button (_("Save"))
458 , delete_button (_("Delete"))
459 , reset_button (_("Reset"))
460 , bypass_button (ArdourButton::led_default_elements)
461 , pin_management_button (_("Pinout"))
462 , description_expander (_("Description"))
463 , plugin_analysis_expander (_("Plugin analysis"))
468 _preset_modified.set_size_request (16, -1);
469 _preset_combo.set_text("(default)");
470 set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
471 set_tooltip (add_button, _("Save a new preset"));
472 set_tooltip (save_button, _("Save the current preset"));
473 set_tooltip (delete_button, _("Delete the current preset"));
474 set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
475 set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
476 set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
479 update_preset_list ();
482 add_button.set_name ("generic button");
483 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
485 save_button.set_name ("generic button");
486 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
488 delete_button.set_name ("generic button");
489 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
491 reset_button.set_name ("generic button");
492 reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
494 pin_management_button.set_name ("generic button");
495 pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
497 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
499 bypass_button.set_name ("plugin bypass button");
500 bypass_button.set_text (_("Bypass"));
501 bypass_button.set_active (!pi->enabled ());
502 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
503 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
505 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
506 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
508 /* these images are not managed, so that we can remove them at will */
510 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
511 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
513 focus_button.add (*focus_out_image);
515 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));
516 set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
518 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
519 description_expander.set_expanded(false);
521 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
522 plugin_analysis_expander.set_expanded(false);
524 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
526 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
527 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
528 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
529 plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
531 insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
533 automation_state_changed();
536 PlugUIBase::~PlugUIBase()
543 PlugUIBase::plugin_going_away ()
545 /* drop references to the plugin/insert */
551 PlugUIBase::set_latency_label ()
553 framecnt_t const l = insert->effective_latency ();
554 framecnt_t const sr = insert->session().frame_rate ();
559 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
561 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
564 latency_button.set_text (t);
568 PlugUIBase::latency_button_clicked ()
571 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
572 latency_dialog = new ArdourWindow (_("Edit Latency"));
573 /* use both keep-above and transient for to try cover as many
574 different WM's as possible.
576 latency_dialog->set_keep_above (true);
577 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
579 latency_dialog->set_transient_for (*win);
581 latency_dialog->add (*latency_gui);
582 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
585 latency_dialog->show_all ();
589 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
591 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
592 boost::shared_ptr<Processor> p (weak_p.lock());
595 bypass_button.set_active (!p->enabled ());
600 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
602 if (_no_load_preset) {
605 if (!preset.label.empty()) {
606 insert->load_preset (preset);
608 // blank selected = no preset
609 plugin->clear_preset();
613 #ifdef NO_PLUGIN_STATE
614 static bool seen_saving_message = false;
616 static void show_no_plugin_message()
618 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
621 info << _("To get full access to updates without this limitation\n"
622 "consider becoming a subscriber for a low cost every month.")
624 info << X_("https://community.ardour.org/s/subscribe")
626 ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
631 PlugUIBase::add_plugin_setting ()
633 #ifndef NO_PLUGIN_STATE
634 NewPluginPresetDialog d (plugin, _("New Preset"));
637 case Gtk::RESPONSE_ACCEPT:
638 if (d.name().empty()) {
643 plugin->remove_preset (d.name ());
646 Plugin::PresetRecord const r = plugin->save_preset (d.name());
647 if (!r.uri.empty ()) {
648 plugin->load_preset (r);
653 if (!seen_saving_message) {
654 seen_saving_message = true;
655 show_no_plugin_message();
661 PlugUIBase::save_plugin_setting ()
663 #ifndef NO_PLUGIN_STATE
664 string const name = _preset_combo.get_text ();
665 plugin->remove_preset (name);
666 Plugin::PresetRecord const r = plugin->save_preset (name);
667 if (!r.uri.empty ()) {
668 plugin->load_preset (r);
671 if (!seen_saving_message) {
672 seen_saving_message = true;
673 show_no_plugin_message();
679 PlugUIBase::delete_plugin_setting ()
681 #ifndef NO_PLUGIN_STATE
682 plugin->remove_preset (_preset_combo.get_text ());
684 if (!seen_saving_message) {
685 seen_saving_message = true;
686 show_no_plugin_message();
692 PlugUIBase::automation_state_changed ()
694 reset_button.set_sensitive (insert->can_reset_all_parameters());
698 PlugUIBase::reset_plugin_parameters ()
700 insert->reset_parameters_to_default ();
704 PlugUIBase::manage_pins ()
706 PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
710 proxy->get ()->raise();
715 PlugUIBase::bypass_button_release (GdkEventButton*)
717 bool view_says_bypassed = (bypass_button.active_state() != 0);
719 if (view_says_bypassed != insert->enabled ()) {
720 insert->enable (view_says_bypassed);
727 PlugUIBase::focus_toggled (GdkEventButton*)
729 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
730 Keyboard::the_keyboard().magic_widget_drop_focus();
731 focus_button.remove ();
732 focus_button.add (*focus_out_image);
733 focus_out_image->show ();
734 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));
735 KeyboardFocused (false);
737 Keyboard::the_keyboard().magic_widget_grab_focus();
738 focus_button.remove ();
739 focus_button.add (*focus_in_image);
740 focus_in_image->show ();
741 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
742 KeyboardFocused (true);
749 PlugUIBase::toggle_description()
751 if (description_expander.get_expanded() &&
752 !description_expander.get_child()) {
753 const std::string text = plugin->get_docs();
758 Gtk::Label* label = manage(new Gtk::Label(text));
759 label->set_line_wrap(true);
760 label->set_line_wrap_mode(Pango::WRAP_WORD);
761 description_expander.add(*label);
762 description_expander.show_all();
765 if (!description_expander.get_expanded()) {
766 const int child_height = description_expander.get_child ()->get_height ();
768 description_expander.remove();
770 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
774 toplevel->get_size (wr.width, wr.height);
775 wr.height -= child_height;
776 toplevel->resize (wr.width, wr.height);
784 PlugUIBase::toggle_plugin_analysis()
786 if (plugin_analysis_expander.get_expanded() &&
787 !plugin_analysis_expander.get_child()) {
790 eqgui = new PluginEqGui (insert);
793 plugin_analysis_expander.add (*eqgui);
794 plugin_analysis_expander.show_all ();
795 eqgui->start_listening ();
798 if (!plugin_analysis_expander.get_expanded()) {
799 // Hide & remove from expander
800 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
803 eqgui->stop_listening ();
804 plugin_analysis_expander.remove();
806 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
810 toplevel->get_size (wr.width, wr.height);
811 wr.height -= child_height;
812 toplevel->resize (wr.width, wr.height);
818 PlugUIBase::update_preset_list ()
820 using namespace Menu_Helpers;
822 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
826 // Add a menu entry for each preset
827 _preset_combo.clear_items();
828 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
829 _preset_combo.AddMenuElem(
830 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
833 // Add an empty entry for un-setting current preset (see preset_selected)
834 Plugin::PresetRecord no_preset;
835 _preset_combo.AddMenuElem(
836 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
842 PlugUIBase::update_preset ()
844 Plugin::PresetRecord p = plugin->last_preset();
848 _preset_combo.set_text (_("(none)"));
850 _preset_combo.set_text (p.label);
854 save_button.set_sensitive (!p.uri.empty() && p.user);
855 delete_button.set_sensitive (!p.uri.empty() && p.user);
857 update_preset_modified ();
861 PlugUIBase::update_preset_modified ()
864 if (plugin->last_preset().uri.empty()) {
865 _preset_modified.set_text ("");
869 bool const c = plugin->parameter_changed_since_last_preset ();
870 if (_preset_modified.get_text().empty() == c) {
871 _preset_modified.set_text (c ? "*" : "");
876 PlugUIBase::preset_added_or_removed ()
878 /* Update both the list and the currently-displayed preset */
879 update_preset_list ();