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/lv2_plugin.h"
57 #include "lv2_plugin_ui.h"
60 #include "ardour_window.h"
61 #include "ardour_ui.h"
63 #include "plugin_ui.h"
65 #include "gui_thread.h"
66 #include "public_editor.h"
67 #include "processor_box.h"
69 #include "latency_gui.h"
70 #include "plugin_eq_gui.h"
71 #include "new_plugin_preset_dialog.h"
77 using namespace ARDOUR;
78 using namespace ARDOUR_UI_UTILS;
80 using namespace Gtkmm2ext;
83 PluginUIWindow::PluginUIWindow (
84 boost::shared_ptr<PluginInsert> insert,
87 : ArdourWindow (string())
89 , _keyboard_focused (false)
90 #ifdef AUDIOUNIT_SUPPORT
91 , pre_deactivate_x (-1)
92 , pre_deactivate_y (-1)
96 bool have_gui = false;
97 Label* label = manage (new Label());
98 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
100 if (editor && insert->plugin()->has_editor()) {
101 switch (insert->type()) {
102 case ARDOUR::Windows_VST:
103 have_gui = create_windows_vst_editor (insert);
107 have_gui = create_lxvst_editor (insert);
110 case ARDOUR::AudioUnit:
111 have_gui = create_audiounit_editor (insert);
115 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
119 have_gui = create_lv2_editor (insert);
123 #ifndef WINDOWS_VST_SUPPORT
124 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
127 error << _("unknown type of editor-supplying plugin")
130 throw failed_constructor ();
136 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
139 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
141 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
143 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
144 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
147 set_name ("PluginEditor");
148 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
150 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
152 gint h = _pluginui->get_preferred_height ();
153 gint w = _pluginui->get_preferred_width ();
156 if (h > 600) h = 600;
159 set_default_size (w, h);
160 set_resizable (_pluginui->resizable());
163 PluginUIWindow::~PluginUIWindow ()
166 cerr << "PluginWindow deleted for " << this << endl;
172 PluginUIWindow::on_show ()
174 set_role("plugin_ui");
177 _pluginui->update_preset_list ();
178 _pluginui->update_preset ();
182 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
183 if (pre_deactivate_x >= 0) {
184 move (pre_deactivate_x, pre_deactivate_y);
188 if (_pluginui->on_window_show (_title)) {
195 PluginUIWindow::on_hide ()
197 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
198 get_position (pre_deactivate_x, pre_deactivate_y);
204 _pluginui->on_window_hide ();
209 PluginUIWindow::set_title(const std::string& title)
211 Gtk::Window::set_title(title);
216 #ifdef WINDOWS_VST_SUPPORT
217 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
219 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
222 #ifndef WINDOWS_VST_SUPPORT
226 boost::shared_ptr<WindowsVSTPlugin> vp;
228 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
229 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
231 throw failed_constructor ();
233 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
236 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
238 vpu->package (*this);
247 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
249 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
252 #ifndef LXVST_SUPPORT
256 boost::shared_ptr<LXVSTPlugin> lxvp;
258 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
259 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
261 throw failed_constructor ();
263 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
266 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
268 lxvpu->package (*this);
276 #ifdef AUDIOUNIT_SUPPORT
277 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
279 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
282 #ifndef AUDIOUNIT_SUPPORT
286 _pluginui = create_au_gui (insert, &box);
287 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
290 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
298 PluginUIWindow::app_activated (bool yn)
300 PluginUIWindow::app_activated (bool)
303 #ifdef AUDIOUNIT_SUPPORT
307 _pluginui->activate ();
308 if (pre_deactivate_x >= 0) {
309 move (pre_deactivate_x, pre_deactivate_y);
315 was_visible = is_visible();
316 get_position (pre_deactivate_x, pre_deactivate_y);
318 _pluginui->deactivate ();
325 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
328 boost::shared_ptr<LV2Plugin> vp;
330 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
331 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
332 throw failed_constructor ();
334 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
337 lpu->package (*this);
338 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
348 PluginUIWindow::keyboard_focused (bool yn)
350 _keyboard_focused = yn;
354 PluginUIWindow::on_key_press_event (GdkEventKey* event)
356 if (_keyboard_focused) {
358 _pluginui->grab_focus();
359 if (_pluginui->non_gtk_gui()) {
360 _pluginui->forward_key_event (event);
362 return relay_key_press (event, this);
367 /* for us to be getting key press events, there really
368 MUST be a _pluginui, but just to be safe, check ...
372 _pluginui->grab_focus();
373 if (_pluginui->non_gtk_gui()) {
374 /* pass main window as the window for the event
375 to be handled in, not this one, because there are
376 no widgets in this window that we want to have
379 return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
381 return relay_key_press (event, this);
389 PluginUIWindow::on_key_release_event (GdkEventKey *event)
391 if (_keyboard_focused) {
393 if (_pluginui->non_gtk_gui()) {
394 _pluginui->forward_key_event (event);
405 PluginUIWindow::plugin_going_away ()
407 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
410 _pluginui->stop_updating(0);
413 death_connection.disconnect ();
416 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
418 , plugin (insert->plugin())
419 , add_button (_("Add"))
420 , save_button (_("Save"))
421 , delete_button (_("Delete"))
422 , reset_button (_("Reset"))
423 , bypass_button (ArdourButton::led_default_elements)
424 , pin_management_button (_("Pin Connections...")) // TODO use a shorter label once the string-freeze is over.
425 , description_expander (_("Description"))
426 , plugin_analysis_expander (_("Plugin analysis"))
431 _preset_modified.set_size_request (16, -1);
432 _preset_combo.set_text("(default)");
433 set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
434 set_tooltip (add_button, _("Save a new preset"));
435 set_tooltip (save_button, _("Save the current preset"));
436 set_tooltip (delete_button, _("Delete the current preset"));
437 set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
438 #if 0 // string freeze is over
439 set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
441 set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
444 update_preset_list ();
447 add_button.set_name ("generic button");
448 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
450 save_button.set_name ("generic button");
451 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
453 delete_button.set_name ("generic button");
454 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
456 reset_button.set_name ("generic button");
457 reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
459 pin_management_button.set_name ("generic button");
460 pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
462 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
464 bypass_button.set_name ("plugin bypass button");
465 bypass_button.set_text (_("Bypass"));
466 bypass_button.set_active (!pi->enabled ());
467 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
468 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
470 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
471 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
473 /* these images are not managed, so that we can remove them at will */
475 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
476 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
478 focus_button.add (*focus_out_image);
480 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));
481 set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
483 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
484 description_expander.set_expanded(false);
486 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
487 plugin_analysis_expander.set_expanded(false);
489 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
491 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
492 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
493 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
494 plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
496 insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
498 automation_state_changed();
501 PlugUIBase::~PlugUIBase()
508 PlugUIBase::plugin_going_away ()
510 /* drop references to the plugin/insert */
516 PlugUIBase::set_latency_label ()
518 framecnt_t const l = insert->effective_latency ();
519 framecnt_t const sr = insert->session().frame_rate ();
524 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
526 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
529 latency_button.set_text (t);
533 PlugUIBase::latency_button_clicked ()
536 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
537 latency_dialog = new ArdourWindow (_("Edit Latency"));
538 /* use both keep-above and transient for to try cover as many
539 different WM's as possible.
541 latency_dialog->set_keep_above (true);
542 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
544 latency_dialog->set_transient_for (*win);
546 latency_dialog->add (*latency_gui);
547 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
550 latency_dialog->show_all ();
554 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
556 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
557 boost::shared_ptr<Processor> p (weak_p.lock());
560 bypass_button.set_active (!p->enabled ());
565 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
567 if (_no_load_preset) {
570 if (!preset.label.empty()) {
571 insert->load_preset (preset);
573 // blank selected = no preset
574 plugin->clear_preset();
578 #ifdef NO_PLUGIN_STATE
579 static bool seen_saving_message = false;
581 static void show_no_plugin_message()
583 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
586 info << _("To get full access to updates without this limitation\n"
587 "consider becoming a subscriber for a low cost every month.")
589 info << X_("https://community.ardour.org/s/subscribe")
591 ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
596 PlugUIBase::add_plugin_setting ()
598 #ifndef NO_PLUGIN_STATE
599 NewPluginPresetDialog d (plugin, _("New Preset"));
602 case Gtk::RESPONSE_ACCEPT:
603 if (d.name().empty()) {
608 plugin->remove_preset (d.name ());
611 Plugin::PresetRecord const r = plugin->save_preset (d.name());
612 if (!r.uri.empty ()) {
613 plugin->load_preset (r);
618 if (!seen_saving_message) {
619 seen_saving_message = true;
620 show_no_plugin_message();
626 PlugUIBase::save_plugin_setting ()
628 #ifndef NO_PLUGIN_STATE
629 string const name = _preset_combo.get_text ();
630 plugin->remove_preset (name);
631 Plugin::PresetRecord const r = plugin->save_preset (name);
632 if (!r.uri.empty ()) {
633 plugin->load_preset (r);
636 if (!seen_saving_message) {
637 seen_saving_message = true;
638 show_no_plugin_message();
644 PlugUIBase::delete_plugin_setting ()
646 #ifndef NO_PLUGIN_STATE
647 plugin->remove_preset (_preset_combo.get_text ());
649 if (!seen_saving_message) {
650 seen_saving_message = true;
651 show_no_plugin_message();
657 PlugUIBase::automation_state_changed ()
659 reset_button.set_sensitive (insert->can_reset_all_parameters());
663 PlugUIBase::reset_plugin_parameters ()
665 insert->reset_parameters_to_default ();
669 PlugUIBase::manage_pins ()
671 PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
675 proxy->get ()->raise();
680 PlugUIBase::bypass_button_release (GdkEventButton*)
682 bool view_says_bypassed = (bypass_button.active_state() != 0);
684 if (view_says_bypassed != insert->enabled ()) {
685 insert->enable (view_says_bypassed);
692 PlugUIBase::focus_toggled (GdkEventButton*)
694 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
695 Keyboard::the_keyboard().magic_widget_drop_focus();
696 focus_button.remove ();
697 focus_button.add (*focus_out_image);
698 focus_out_image->show ();
699 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));
700 KeyboardFocused (false);
702 Keyboard::the_keyboard().magic_widget_grab_focus();
703 focus_button.remove ();
704 focus_button.add (*focus_in_image);
705 focus_in_image->show ();
706 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
707 KeyboardFocused (true);
714 PlugUIBase::toggle_description()
716 if (description_expander.get_expanded() &&
717 !description_expander.get_child()) {
718 const std::string text = plugin->get_docs();
723 Gtk::Label* label = manage(new Gtk::Label(text));
724 label->set_line_wrap(true);
725 label->set_line_wrap_mode(Pango::WRAP_WORD);
726 description_expander.add(*label);
727 description_expander.show_all();
730 if (!description_expander.get_expanded()) {
731 const int child_height = description_expander.get_child ()->get_height ();
733 description_expander.remove();
735 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
739 toplevel->get_size (wr.width, wr.height);
740 wr.height -= child_height;
741 toplevel->resize (wr.width, wr.height);
749 PlugUIBase::toggle_plugin_analysis()
751 if (plugin_analysis_expander.get_expanded() &&
752 !plugin_analysis_expander.get_child()) {
755 eqgui = new PluginEqGui (insert);
758 plugin_analysis_expander.add (*eqgui);
759 plugin_analysis_expander.show_all ();
760 eqgui->start_listening ();
763 if (!plugin_analysis_expander.get_expanded()) {
764 // Hide & remove from expander
765 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
768 eqgui->stop_listening ();
769 plugin_analysis_expander.remove();
771 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
775 toplevel->get_size (wr.width, wr.height);
776 wr.height -= child_height;
777 toplevel->resize (wr.width, wr.height);
783 PlugUIBase::update_preset_list ()
785 using namespace Menu_Helpers;
787 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
791 // Add a menu entry for each preset
792 _preset_combo.clear_items();
793 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
794 _preset_combo.AddMenuElem(
795 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
798 // Add an empty entry for un-setting current preset (see preset_selected)
799 Plugin::PresetRecord no_preset;
800 _preset_combo.AddMenuElem(
801 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
807 PlugUIBase::update_preset ()
809 Plugin::PresetRecord p = plugin->last_preset();
813 _preset_combo.set_text (_("(none)"));
815 _preset_combo.set_text (p.label);
819 save_button.set_sensitive (!p.uri.empty() && p.user);
820 delete_button.set_sensitive (!p.uri.empty() && p.user);
822 update_preset_modified ();
826 PlugUIBase::update_preset_modified ()
829 if (plugin->last_preset().uri.empty()) {
830 _preset_modified.set_text ("");
834 bool const c = plugin->parameter_changed_since_last_preset ();
835 if (_preset_modified.get_text().empty() == c) {
836 _preset_modified.set_text (c ? "*" : "");
841 PlugUIBase::preset_added_or_removed ()
843 /* Update both the list and the currently-displayed preset */
844 update_preset_list ();