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"
62 #include "plugin_ui.h"
64 #include "gui_thread.h"
65 #include "public_editor.h"
67 #include "latency_gui.h"
68 #include "plugin_eq_gui.h"
69 #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 return relay_key_press (event, this);
365 /* for us to be getting key press events, there really
366 MUST be a _pluginui, but just to be safe, check ...
370 _pluginui->grab_focus();
371 if (_pluginui->non_gtk_gui()) {
372 /* pass editor window as the window for the event
373 to be handled in, not this one, because there are
374 no widgets in this window that we want to have
377 return relay_key_press (event, &PublicEditor::instance());
379 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->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 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->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), 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->active());
558 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
560 if (_no_load_preset) {
563 if (!preset.label.empty()) {
564 plugin->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);
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->active()) {
667 if (view_says_bypassed) {
670 insert->deactivate ();
678 PlugUIBase::focus_toggled (GdkEventButton*)
680 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
681 Keyboard::the_keyboard().magic_widget_drop_focus();
682 focus_button.remove ();
683 focus_button.add (*focus_out_image);
684 focus_out_image->show ();
685 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));
686 KeyboardFocused (false);
688 Keyboard::the_keyboard().magic_widget_grab_focus();
689 focus_button.remove ();
690 focus_button.add (*focus_in_image);
691 focus_in_image->show ();
692 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
693 KeyboardFocused (true);
700 PlugUIBase::toggle_description()
702 if (description_expander.get_expanded() &&
703 !description_expander.get_child()) {
704 const std::string text = plugin->get_docs();
709 Gtk::Label* label = manage(new Gtk::Label(text));
710 label->set_line_wrap(true);
711 label->set_line_wrap_mode(Pango::WRAP_WORD);
712 description_expander.add(*label);
713 description_expander.show_all();
716 if (!description_expander.get_expanded()) {
717 description_expander.remove();
723 PlugUIBase::toggle_plugin_analysis()
725 if (plugin_analysis_expander.get_expanded() &&
726 !plugin_analysis_expander.get_child()) {
729 eqgui = new PluginEqGui (insert);
732 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
735 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
738 plugin_analysis_expander.add (*eqgui);
739 plugin_analysis_expander.show_all ();
740 eqgui->start_listening ();
743 if (!plugin_analysis_expander.get_expanded()) {
744 // Hide & remove from expander
747 eqgui->stop_listening ();
748 plugin_analysis_expander.remove();
750 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
753 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
759 PlugUIBase::update_preset_list ()
761 using namespace Menu_Helpers;
763 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
767 // Add a menu entry for each preset
768 _preset_combo.clear_items();
769 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
770 _preset_combo.AddMenuElem(
771 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
774 // Add an empty entry for un-setting current preset (see preset_selected)
775 Plugin::PresetRecord no_preset;
776 _preset_combo.AddMenuElem(
777 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
783 PlugUIBase::update_preset ()
785 Plugin::PresetRecord p = plugin->last_preset();
789 _preset_combo.set_text (_("(none)"));
791 _preset_combo.set_text (p.label);
795 save_button.set_sensitive (!p.uri.empty() && p.user);
796 delete_button.set_sensitive (!p.uri.empty() && p.user);
798 update_preset_modified ();
802 PlugUIBase::update_preset_modified ()
805 if (plugin->last_preset().uri.empty()) {
806 _preset_modified.set_text ("");
810 bool const c = plugin->parameter_changed_since_last_preset ();
811 if (_preset_modified.get_text().empty() == c) {
812 _preset_modified.set_text (c ? "*" : "");
817 PlugUIBase::parameter_changed (uint32_t, float)
819 update_preset_modified ();
823 PlugUIBase::preset_added_or_removed ()
825 /* Update both the list and the currently-displayed preset */
826 update_preset_list ();