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 (_("Pinout"))
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 set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
439 set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
442 update_preset_list ();
445 add_button.set_name ("generic button");
446 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
448 save_button.set_name ("generic button");
449 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
451 delete_button.set_name ("generic button");
452 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
454 reset_button.set_name ("generic button");
455 reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
457 pin_management_button.set_name ("generic button");
458 pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
460 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
462 bypass_button.set_name ("plugin bypass button");
463 bypass_button.set_text (_("Bypass"));
464 bypass_button.set_active (!pi->enabled ());
465 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
466 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
468 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
469 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
471 /* these images are not managed, so that we can remove them at will */
473 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
474 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
476 focus_button.add (*focus_out_image);
478 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));
479 set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
481 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
482 description_expander.set_expanded(false);
484 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
485 plugin_analysis_expander.set_expanded(false);
487 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
489 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
490 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
491 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
492 plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
494 insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
496 automation_state_changed();
499 PlugUIBase::~PlugUIBase()
506 PlugUIBase::plugin_going_away ()
508 /* drop references to the plugin/insert */
514 PlugUIBase::set_latency_label ()
516 framecnt_t const l = insert->effective_latency ();
517 framecnt_t const sr = insert->session().frame_rate ();
522 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
524 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
527 latency_button.set_text (t);
531 PlugUIBase::latency_button_clicked ()
534 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
535 latency_dialog = new ArdourWindow (_("Edit Latency"));
536 /* use both keep-above and transient for to try cover as many
537 different WM's as possible.
539 latency_dialog->set_keep_above (true);
540 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
542 latency_dialog->set_transient_for (*win);
544 latency_dialog->add (*latency_gui);
545 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
548 latency_dialog->show_all ();
552 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
554 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
555 boost::shared_ptr<Processor> p (weak_p.lock());
558 bypass_button.set_active (!p->enabled ());
563 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
565 if (_no_load_preset) {
568 if (!preset.label.empty()) {
569 insert->load_preset (preset);
571 // blank selected = no preset
572 plugin->clear_preset();
576 #ifdef NO_PLUGIN_STATE
577 static bool seen_saving_message = false;
579 static void show_no_plugin_message()
581 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
584 info << _("To get full access to updates without this limitation\n"
585 "consider becoming a subscriber for a low cost every month.")
587 info << X_("https://community.ardour.org/s/subscribe")
589 ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
594 PlugUIBase::add_plugin_setting ()
596 #ifndef NO_PLUGIN_STATE
597 NewPluginPresetDialog d (plugin, _("New Preset"));
600 case Gtk::RESPONSE_ACCEPT:
601 if (d.name().empty()) {
606 plugin->remove_preset (d.name ());
609 Plugin::PresetRecord const r = plugin->save_preset (d.name());
610 if (!r.uri.empty ()) {
611 plugin->load_preset (r);
616 if (!seen_saving_message) {
617 seen_saving_message = true;
618 show_no_plugin_message();
624 PlugUIBase::save_plugin_setting ()
626 #ifndef NO_PLUGIN_STATE
627 string const name = _preset_combo.get_text ();
628 plugin->remove_preset (name);
629 Plugin::PresetRecord const r = plugin->save_preset (name);
630 if (!r.uri.empty ()) {
631 plugin->load_preset (r);
634 if (!seen_saving_message) {
635 seen_saving_message = true;
636 show_no_plugin_message();
642 PlugUIBase::delete_plugin_setting ()
644 #ifndef NO_PLUGIN_STATE
645 plugin->remove_preset (_preset_combo.get_text ());
647 if (!seen_saving_message) {
648 seen_saving_message = true;
649 show_no_plugin_message();
655 PlugUIBase::automation_state_changed ()
657 reset_button.set_sensitive (insert->can_reset_all_parameters());
661 PlugUIBase::reset_plugin_parameters ()
663 insert->reset_parameters_to_default ();
667 PlugUIBase::manage_pins ()
669 PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
673 proxy->get ()->raise();
678 PlugUIBase::bypass_button_release (GdkEventButton*)
680 bool view_says_bypassed = (bypass_button.active_state() != 0);
682 if (view_says_bypassed != insert->enabled ()) {
683 insert->enable (view_says_bypassed);
690 PlugUIBase::focus_toggled (GdkEventButton*)
692 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
693 Keyboard::the_keyboard().magic_widget_drop_focus();
694 focus_button.remove ();
695 focus_button.add (*focus_out_image);
696 focus_out_image->show ();
697 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));
698 KeyboardFocused (false);
700 Keyboard::the_keyboard().magic_widget_grab_focus();
701 focus_button.remove ();
702 focus_button.add (*focus_in_image);
703 focus_in_image->show ();
704 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
705 KeyboardFocused (true);
712 PlugUIBase::toggle_description()
714 if (description_expander.get_expanded() &&
715 !description_expander.get_child()) {
716 const std::string text = plugin->get_docs();
721 Gtk::Label* label = manage(new Gtk::Label(text));
722 label->set_line_wrap(true);
723 label->set_line_wrap_mode(Pango::WRAP_WORD);
724 description_expander.add(*label);
725 description_expander.show_all();
728 if (!description_expander.get_expanded()) {
729 const int child_height = description_expander.get_child ()->get_height ();
731 description_expander.remove();
733 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
737 toplevel->get_size (wr.width, wr.height);
738 wr.height -= child_height;
739 toplevel->resize (wr.width, wr.height);
747 PlugUIBase::toggle_plugin_analysis()
749 if (plugin_analysis_expander.get_expanded() &&
750 !plugin_analysis_expander.get_child()) {
753 eqgui = new PluginEqGui (insert);
756 plugin_analysis_expander.add (*eqgui);
757 plugin_analysis_expander.show_all ();
758 eqgui->start_listening ();
761 if (!plugin_analysis_expander.get_expanded()) {
762 // Hide & remove from expander
763 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
766 eqgui->stop_listening ();
767 plugin_analysis_expander.remove();
769 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_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);
781 PlugUIBase::update_preset_list ()
783 using namespace Menu_Helpers;
785 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
789 // Add a menu entry for each preset
790 _preset_combo.clear_items();
791 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
792 _preset_combo.AddMenuElem(
793 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
796 // Add an empty entry for un-setting current preset (see preset_selected)
797 Plugin::PresetRecord no_preset;
798 _preset_combo.AddMenuElem(
799 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
805 PlugUIBase::update_preset ()
807 Plugin::PresetRecord p = plugin->last_preset();
811 _preset_combo.set_text (_("(none)"));
813 _preset_combo.set_text (p.label);
817 save_button.set_sensitive (!p.uri.empty() && p.user);
818 delete_button.set_sensitive (!p.uri.empty() && p.user);
820 update_preset_modified ();
824 PlugUIBase::update_preset_modified ()
827 if (plugin->last_preset().uri.empty()) {
828 _preset_modified.set_text ("");
832 bool const c = plugin->parameter_changed_since_last_preset ();
833 if (_preset_modified.get_text().empty() == c) {
834 _preset_modified.set_text (c ? "*" : "");
839 PlugUIBase::preset_added_or_removed ()
841 /* Update both the list and the currently-displayed preset */
842 update_preset_list ();