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"
76 using namespace ARDOUR;
77 using namespace ARDOUR_UI_UTILS;
79 using namespace Gtkmm2ext;
82 PluginUIWindow::PluginUIWindow (
83 boost::shared_ptr<PluginInsert> insert,
86 : ArdourWindow (string())
88 , _keyboard_focused (false)
89 #ifdef AUDIOUNIT_SUPPORT
90 , pre_deactivate_x (-1)
91 , pre_deactivate_y (-1)
95 bool have_gui = false;
96 Label* label = manage (new Label());
97 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
99 if (editor && insert->plugin()->has_editor()) {
100 switch (insert->type()) {
101 case ARDOUR::Windows_VST:
102 have_gui = create_windows_vst_editor (insert);
106 have_gui = create_lxvst_editor (insert);
109 case ARDOUR::AudioUnit:
110 have_gui = create_audiounit_editor (insert);
114 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
118 have_gui = create_lv2_editor (insert);
122 #ifndef WINDOWS_VST_SUPPORT
123 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
126 error << _("unknown type of editor-supplying plugin")
129 throw failed_constructor ();
135 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
138 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
140 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
142 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
143 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
146 set_name ("PluginEditor");
147 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
149 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
151 gint h = _pluginui->get_preferred_height ();
152 gint w = _pluginui->get_preferred_width ();
155 if (h > 600) h = 600;
158 set_default_size (w, h);
159 set_resizable (_pluginui->resizable());
162 PluginUIWindow::~PluginUIWindow ()
165 cerr << "PluginWindow deleted for " << this << endl;
171 PluginUIWindow::on_show ()
173 set_role("plugin_ui");
176 _pluginui->update_preset_list ();
177 _pluginui->update_preset ();
181 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
182 if (pre_deactivate_x >= 0) {
183 move (pre_deactivate_x, pre_deactivate_y);
187 if (_pluginui->on_window_show (_title)) {
194 PluginUIWindow::on_hide ()
196 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
197 get_position (pre_deactivate_x, pre_deactivate_y);
203 _pluginui->on_window_hide ();
208 PluginUIWindow::set_title(const std::string& title)
210 Gtk::Window::set_title(title);
215 #ifdef WINDOWS_VST_SUPPORT
216 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
218 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
221 #ifndef WINDOWS_VST_SUPPORT
225 boost::shared_ptr<WindowsVSTPlugin> vp;
227 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
228 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
230 throw failed_constructor ();
232 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
235 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
237 vpu->package (*this);
246 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
248 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
251 #ifndef LXVST_SUPPORT
255 boost::shared_ptr<LXVSTPlugin> lxvp;
257 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
258 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
260 throw failed_constructor ();
262 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
265 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
267 lxvpu->package (*this);
275 #ifdef AUDIOUNIT_SUPPORT
276 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
278 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
281 #ifndef AUDIOUNIT_SUPPORT
285 _pluginui = create_au_gui (insert, &box);
286 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
289 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
297 PluginUIWindow::app_activated (bool yn)
299 PluginUIWindow::app_activated (bool)
302 #ifdef AUDIOUNIT_SUPPORT
306 _pluginui->activate ();
307 if (pre_deactivate_x >= 0) {
308 move (pre_deactivate_x, pre_deactivate_y);
314 was_visible = is_visible();
315 get_position (pre_deactivate_x, pre_deactivate_y);
317 _pluginui->deactivate ();
324 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
327 boost::shared_ptr<LV2Plugin> vp;
329 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
330 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
331 throw failed_constructor ();
333 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
336 lpu->package (*this);
337 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
347 PluginUIWindow::keyboard_focused (bool yn)
349 _keyboard_focused = yn;
353 PluginUIWindow::on_key_press_event (GdkEventKey* event)
355 if (_keyboard_focused) {
357 _pluginui->grab_focus();
358 if (_pluginui->non_gtk_gui()) {
359 _pluginui->forward_key_event (event);
361 return relay_key_press (event, this);
366 /* for us to be getting key press events, there really
367 MUST be a _pluginui, but just to be safe, check ...
371 _pluginui->grab_focus();
372 if (_pluginui->non_gtk_gui()) {
373 /* pass main window as the window for the event
374 to be handled in, not this one, because there are
375 no widgets in this window that we want to have
378 return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
380 return relay_key_press (event, this);
388 PluginUIWindow::on_key_release_event (GdkEventKey *event)
390 if (_keyboard_focused) {
392 if (_pluginui->non_gtk_gui()) {
393 _pluginui->forward_key_event (event);
404 PluginUIWindow::plugin_going_away ()
406 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
409 _pluginui->stop_updating(0);
412 death_connection.disconnect ();
415 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
417 , plugin (insert->plugin())
418 , add_button (_("Add"))
419 , save_button (_("Save"))
420 , delete_button (_("Delete"))
421 , reset_button (_("Reset"))
422 , bypass_button (ArdourButton::led_default_elements)
423 , description_expander (_("Description"))
424 , plugin_analysis_expander (_("Plugin analysis"))
429 _preset_modified.set_size_request (16, -1);
430 _preset_combo.set_text("(default)");
431 set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
432 set_tooltip (add_button, _("Save a new preset"));
433 set_tooltip (save_button, _("Save the current preset"));
434 set_tooltip (delete_button, _("Delete the current preset"));
435 set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
436 set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
439 update_preset_list ();
442 add_button.set_name ("generic button");
443 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
445 save_button.set_name ("generic button");
446 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
448 delete_button.set_name ("generic button");
449 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
451 reset_button.set_name ("generic button");
452 reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
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->enabled ());
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 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));
474 set_tooltip (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->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
489 insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
491 automation_state_changed();
494 PlugUIBase::~PlugUIBase()
501 PlugUIBase::plugin_going_away ()
503 /* drop references to the plugin/insert */
509 PlugUIBase::set_latency_label ()
511 framecnt_t const l = insert->effective_latency ();
512 framecnt_t const sr = insert->session().frame_rate ();
517 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
519 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
522 latency_button.set_text (t);
526 PlugUIBase::latency_button_clicked ()
529 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
530 latency_dialog = new ArdourWindow (_("Edit Latency"));
531 /* use both keep-above and transient for to try cover as many
532 different WM's as possible.
534 latency_dialog->set_keep_above (true);
535 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
537 latency_dialog->set_transient_for (*win);
539 latency_dialog->add (*latency_gui);
540 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
543 latency_dialog->show_all ();
547 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
549 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
550 boost::shared_ptr<Processor> p (weak_p.lock());
553 bypass_button.set_active (!p->enabled ());
558 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
560 if (_no_load_preset) {
563 if (!preset.label.empty()) {
564 insert->load_preset (preset);
566 // blank selected = no preset
567 plugin->clear_preset();
571 #ifdef NO_PLUGIN_STATE
572 static bool seen_saving_message = false;
574 static void show_no_plugin_message()
576 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
579 info << _("To get full access to updates without this limitation\n"
580 "consider becoming a subscriber for a low cost every month.")
582 info << X_("https://community.ardour.org/s/subscribe")
584 ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
589 PlugUIBase::add_plugin_setting ()
591 #ifndef NO_PLUGIN_STATE
592 NewPluginPresetDialog d (plugin, _("New Preset"));
595 case Gtk::RESPONSE_ACCEPT:
596 if (d.name().empty()) {
601 plugin->remove_preset (d.name ());
604 Plugin::PresetRecord const r = plugin->save_preset (d.name());
605 if (!r.uri.empty ()) {
606 plugin->load_preset (r);
611 if (!seen_saving_message) {
612 seen_saving_message = true;
613 show_no_plugin_message();
619 PlugUIBase::save_plugin_setting ()
621 #ifndef NO_PLUGIN_STATE
622 string const name = _preset_combo.get_text ();
623 plugin->remove_preset (name);
624 Plugin::PresetRecord const r = plugin->save_preset (name);
625 if (!r.uri.empty ()) {
626 plugin->load_preset (r);
629 if (!seen_saving_message) {
630 seen_saving_message = true;
631 show_no_plugin_message();
637 PlugUIBase::delete_plugin_setting ()
639 #ifndef NO_PLUGIN_STATE
640 plugin->remove_preset (_preset_combo.get_text ());
642 if (!seen_saving_message) {
643 seen_saving_message = true;
644 show_no_plugin_message();
650 PlugUIBase::automation_state_changed ()
652 reset_button.set_sensitive (insert->can_reset_all_parameters());
656 PlugUIBase::reset_plugin_parameters ()
658 insert->reset_parameters_to_default ();
662 PlugUIBase::bypass_button_release (GdkEventButton*)
664 bool view_says_bypassed = (bypass_button.active_state() != 0);
666 if (view_says_bypassed != insert->enabled ()) {
667 insert->enable (view_says_bypassed);
674 PlugUIBase::focus_toggled (GdkEventButton*)
676 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
677 Keyboard::the_keyboard().magic_widget_drop_focus();
678 focus_button.remove ();
679 focus_button.add (*focus_out_image);
680 focus_out_image->show ();
681 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));
682 KeyboardFocused (false);
684 Keyboard::the_keyboard().magic_widget_grab_focus();
685 focus_button.remove ();
686 focus_button.add (*focus_in_image);
687 focus_in_image->show ();
688 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
689 KeyboardFocused (true);
696 PlugUIBase::toggle_description()
698 if (description_expander.get_expanded() &&
699 !description_expander.get_child()) {
700 const std::string text = plugin->get_docs();
705 Gtk::Label* label = manage(new Gtk::Label(text));
706 label->set_line_wrap(true);
707 label->set_line_wrap_mode(Pango::WRAP_WORD);
708 description_expander.add(*label);
709 description_expander.show_all();
712 if (!description_expander.get_expanded()) {
713 description_expander.remove();
719 PlugUIBase::toggle_plugin_analysis()
721 if (plugin_analysis_expander.get_expanded() &&
722 !plugin_analysis_expander.get_child()) {
725 eqgui = new PluginEqGui (insert);
728 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
731 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
734 plugin_analysis_expander.add (*eqgui);
735 plugin_analysis_expander.show_all ();
736 eqgui->start_listening ();
739 if (!plugin_analysis_expander.get_expanded()) {
740 // Hide & remove from expander
743 eqgui->stop_listening ();
744 plugin_analysis_expander.remove();
746 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
749 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
755 PlugUIBase::update_preset_list ()
757 using namespace Menu_Helpers;
759 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
763 // Add a menu entry for each preset
764 _preset_combo.clear_items();
765 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
766 _preset_combo.AddMenuElem(
767 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
770 // Add an empty entry for un-setting current preset (see preset_selected)
771 Plugin::PresetRecord no_preset;
772 _preset_combo.AddMenuElem(
773 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
779 PlugUIBase::update_preset ()
781 Plugin::PresetRecord p = plugin->last_preset();
785 _preset_combo.set_text (_("(none)"));
787 _preset_combo.set_text (p.label);
791 save_button.set_sensitive (!p.uri.empty() && p.user);
792 delete_button.set_sensitive (!p.uri.empty() && p.user);
794 update_preset_modified ();
798 PlugUIBase::update_preset_modified ()
801 if (plugin->last_preset().uri.empty()) {
802 _preset_modified.set_text ("");
806 bool const c = plugin->parameter_changed_since_last_preset ();
807 if (_preset_modified.get_text().empty() == c) {
808 _preset_modified.set_text (c ? "*" : "");
813 PlugUIBase::preset_added_or_removed ()
815 /* Update both the list and the currently-displayed preset */
816 update_preset_list ();