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"
68 #include "latency_gui.h"
69 #include "plugin_eq_gui.h"
70 #include "new_plugin_preset_dialog.h"
75 using namespace ARDOUR;
76 using namespace ARDOUR_UI_UTILS;
78 using namespace Gtkmm2ext;
81 PluginUIWindow::PluginUIWindow (
82 boost::shared_ptr<PluginInsert> insert,
85 : ArdourWindow (string())
87 , _keyboard_focused (false)
88 #ifdef AUDIOUNIT_SUPPORT
89 , pre_deactivate_x (-1)
90 , pre_deactivate_y (-1)
94 bool have_gui = false;
95 Label* label = manage (new Label());
96 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
98 if (editor && insert->plugin()->has_editor()) {
99 switch (insert->type()) {
100 case ARDOUR::Windows_VST:
101 have_gui = create_windows_vst_editor (insert);
105 have_gui = create_lxvst_editor (insert);
108 case ARDOUR::AudioUnit:
109 have_gui = create_audiounit_editor (insert);
113 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
117 have_gui = create_lv2_editor (insert);
121 #ifndef WINDOWS_VST_SUPPORT
122 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
125 error << _("unknown type of editor-supplying plugin")
128 throw failed_constructor ();
134 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
137 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
139 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
141 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
142 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
145 set_name ("PluginEditor");
146 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
148 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
150 gint h = _pluginui->get_preferred_height ();
151 gint w = _pluginui->get_preferred_width ();
154 if (h > 600) h = 600;
157 set_default_size (w, h);
158 set_resizable (_pluginui->resizable());
161 PluginUIWindow::~PluginUIWindow ()
164 cerr << "PluginWindow deleted for " << this << endl;
170 PluginUIWindow::on_show ()
172 set_role("plugin_ui");
175 _pluginui->update_preset_list ();
176 _pluginui->update_preset ();
180 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
181 if (pre_deactivate_x >= 0) {
182 move (pre_deactivate_x, pre_deactivate_y);
186 if (_pluginui->on_window_show (_title)) {
193 PluginUIWindow::on_hide ()
195 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
196 get_position (pre_deactivate_x, pre_deactivate_y);
202 _pluginui->on_window_hide ();
207 PluginUIWindow::set_title(const std::string& title)
209 Gtk::Window::set_title(title);
214 #ifdef WINDOWS_VST_SUPPORT
215 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
217 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
220 #ifndef WINDOWS_VST_SUPPORT
224 boost::shared_ptr<WindowsVSTPlugin> vp;
226 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
227 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
229 throw failed_constructor ();
231 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
234 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
236 vpu->package (*this);
245 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
247 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
250 #ifndef LXVST_SUPPORT
254 boost::shared_ptr<LXVSTPlugin> lxvp;
256 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
257 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
259 throw failed_constructor ();
261 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
264 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
266 lxvpu->package (*this);
274 #ifdef AUDIOUNIT_SUPPORT
275 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
277 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
280 #ifndef AUDIOUNIT_SUPPORT
284 _pluginui = create_au_gui (insert, &box);
285 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
288 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
296 PluginUIWindow::app_activated (bool yn)
298 PluginUIWindow::app_activated (bool)
301 #ifdef AUDIOUNIT_SUPPORT
305 _pluginui->activate ();
306 if (pre_deactivate_x >= 0) {
307 move (pre_deactivate_x, pre_deactivate_y);
313 was_visible = is_visible();
314 get_position (pre_deactivate_x, pre_deactivate_y);
316 _pluginui->deactivate ();
323 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
326 boost::shared_ptr<LV2Plugin> vp;
328 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
329 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
330 throw failed_constructor ();
332 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
335 lpu->package (*this);
336 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
346 PluginUIWindow::keyboard_focused (bool yn)
348 _keyboard_focused = yn;
352 PluginUIWindow::on_key_press_event (GdkEventKey* event)
354 if (_keyboard_focused) {
356 _pluginui->grab_focus();
357 if (_pluginui->non_gtk_gui()) {
358 _pluginui->forward_key_event (event);
360 if (!Window::on_key_press_event (event)) {
361 return relay_key_press (event, this);
368 /* for us to be getting key press events, there really
369 MUST be a _pluginui, but just to be safe, check ...
373 _pluginui->grab_focus();
374 if (_pluginui->non_gtk_gui()) {
375 /* pass editor window as the window for the event
376 to be handled in, not this one, because there are
377 no widgets in this window that we want to have
380 return relay_key_press (event, &PublicEditor::instance());
382 if (!Window::on_key_press_event (event)) {
383 return relay_key_press (event, this);
394 PluginUIWindow::on_key_release_event (GdkEventKey *event)
396 if (_keyboard_focused) {
398 if (_pluginui->non_gtk_gui()) {
399 _pluginui->forward_key_event (event);
410 PluginUIWindow::plugin_going_away ()
412 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
415 _pluginui->stop_updating(0);
418 death_connection.disconnect ();
421 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
423 , plugin (insert->plugin())
424 , add_button (_("Add"))
425 , save_button (_("Save"))
426 , delete_button (_("Delete"))
427 , bypass_button (ArdourButton::led_default_elements)
428 , description_expander (_("Description"))
429 , plugin_analysis_expander (_("Plugin analysis"))
434 _preset_modified.set_size_request (16, -1);
435 _preset_combo.set_text("(default)");
436 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
437 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
438 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
439 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
440 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
443 update_preset_list ();
446 add_button.set_name ("generic button");
447 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
449 save_button.set_name ("generic button");
450 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
452 delete_button.set_name ("generic button");
453 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
455 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
457 bypass_button.set_name ("plugin bypass button");
458 bypass_button.set_text (_("Bypass"));
459 bypass_button.set_active (!pi->active());
460 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
461 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
463 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
464 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
466 /* these images are not managed, so that we can remove them at will */
468 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
469 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
471 focus_button.add (*focus_out_image);
473 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
474 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
476 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
477 description_expander.set_expanded(false);
479 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
480 plugin_analysis_expander.set_expanded(false);
482 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
484 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
485 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
486 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
487 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
490 PlugUIBase::~PlugUIBase()
497 PlugUIBase::plugin_going_away ()
499 /* drop references to the plugin/insert */
505 PlugUIBase::set_latency_label ()
507 framecnt_t const l = insert->effective_latency ();
508 framecnt_t const sr = insert->session().frame_rate ();
513 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
515 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
518 latency_button.set_text (t);
522 PlugUIBase::latency_button_clicked ()
525 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
526 latency_dialog = new ArdourWindow (_("Edit Latency"));
527 /* use both keep-above and transient for to try cover as many
528 different WM's as possible.
530 latency_dialog->set_keep_above (true);
531 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
533 latency_dialog->set_transient_for (*win);
535 latency_dialog->add (*latency_gui);
536 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
539 latency_dialog->show_all ();
543 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
545 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
546 boost::shared_ptr<Processor> p (weak_p.lock());
549 bypass_button.set_active (!p->active());
554 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
556 if (_no_load_preset) {
559 if (!preset.label.empty()) {
560 plugin->load_preset (preset);
562 // blank selected = no preset
563 plugin->clear_preset();
567 #ifdef NO_PLUGIN_STATE
568 static bool seen_saving_message = false;
570 static void show_no_plugin_message()
572 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
575 info << _("To get full access to updates without this limitation\n"
576 "consider becoming a subscriber for a low cost every month.")
578 info << X_("https://community.ardour.org/s/subscribe")
580 ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
585 PlugUIBase::add_plugin_setting ()
587 #ifndef NO_PLUGIN_STATE
588 NewPluginPresetDialog d (plugin);
591 case Gtk::RESPONSE_ACCEPT:
592 if (d.name().empty()) {
597 plugin->remove_preset (d.name ());
600 Plugin::PresetRecord const r = plugin->save_preset (d.name());
601 if (!r.uri.empty ()) {
602 plugin->load_preset (r);
607 if (!seen_saving_message) {
608 seen_saving_message = true;
609 show_no_plugin_message();
615 PlugUIBase::save_plugin_setting ()
617 #ifndef NO_PLUGIN_STATE
618 string const name = _preset_combo.get_text ();
619 plugin->remove_preset (name);
620 Plugin::PresetRecord const r = plugin->save_preset (name);
621 if (!r.uri.empty ()) {
622 plugin->load_preset (r);
625 if (!seen_saving_message) {
626 seen_saving_message = true;
627 show_no_plugin_message();
633 PlugUIBase::delete_plugin_setting ()
635 #ifndef NO_PLUGIN_STATE
636 plugin->remove_preset (_preset_combo.get_text ());
638 if (!seen_saving_message) {
639 seen_saving_message = true;
640 show_no_plugin_message();
646 PlugUIBase::bypass_button_release (GdkEventButton*)
648 bool view_says_bypassed = (bypass_button.active_state() != 0);
650 if (view_says_bypassed != insert->active()) {
651 if (view_says_bypassed) {
654 insert->deactivate ();
662 PlugUIBase::focus_toggled (GdkEventButton*)
664 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
665 Keyboard::the_keyboard().magic_widget_drop_focus();
666 focus_button.remove ();
667 focus_button.add (*focus_out_image);
668 focus_out_image->show ();
669 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
670 KeyboardFocused (false);
672 Keyboard::the_keyboard().magic_widget_grab_focus();
673 focus_button.remove ();
674 focus_button.add (*focus_in_image);
675 focus_in_image->show ();
676 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
677 KeyboardFocused (true);
684 PlugUIBase::toggle_description()
686 if (description_expander.get_expanded() &&
687 !description_expander.get_child()) {
688 const std::string text = plugin->get_docs();
693 Gtk::Label* label = manage(new Gtk::Label(text));
694 label->set_line_wrap(true);
695 label->set_line_wrap_mode(Pango::WRAP_WORD);
696 description_expander.add(*label);
697 description_expander.show_all();
700 if (!description_expander.get_expanded()) {
701 description_expander.remove();
707 PlugUIBase::toggle_plugin_analysis()
709 if (plugin_analysis_expander.get_expanded() &&
710 !plugin_analysis_expander.get_child()) {
713 eqgui = new PluginEqGui (insert);
716 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
719 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
722 plugin_analysis_expander.add (*eqgui);
723 plugin_analysis_expander.show_all ();
724 eqgui->start_listening ();
727 if (!plugin_analysis_expander.get_expanded()) {
728 // Hide & remove from expander
731 eqgui->stop_listening ();
732 plugin_analysis_expander.remove();
734 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
737 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
743 PlugUIBase::update_preset_list ()
745 using namespace Menu_Helpers;
747 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
751 // Add a menu entry for each preset
752 _preset_combo.clear_items();
753 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
754 _preset_combo.AddMenuElem(
755 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
758 // Add an empty entry for un-setting current preset (see preset_selected)
759 Plugin::PresetRecord no_preset;
760 _preset_combo.AddMenuElem(
761 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
767 PlugUIBase::update_preset ()
769 Plugin::PresetRecord p = plugin->last_preset();
773 _preset_combo.set_text ("(none)");
775 _preset_combo.set_text (p.label);
779 save_button.set_sensitive (!p.uri.empty() && p.user);
780 delete_button.set_sensitive (!p.uri.empty() && p.user);
782 update_preset_modified ();
786 PlugUIBase::update_preset_modified ()
789 if (plugin->last_preset().uri.empty()) {
790 _preset_modified.set_text ("");
794 bool const c = plugin->parameter_changed_since_last_preset ();
795 if (_preset_modified.get_text().empty() == c) {
796 _preset_modified.set_text (c ? "*" : "");
801 PlugUIBase::parameter_changed (uint32_t, float)
803 update_preset_modified ();
807 PlugUIBase::preset_added_or_removed ()
809 /* Update both the list and the currently-displayed preset */
810 update_preset_list ();