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;
77 using namespace Gtkmm2ext;
80 PluginUIWindow::PluginUIWindow (
81 boost::shared_ptr<PluginInsert> insert,
84 : ArdourWindow (string())
86 , _keyboard_focused (false)
87 #ifdef AUDIOUNIT_SUPPORT
88 , pre_deactivate_x (-1)
89 , pre_deactivate_y (-1)
93 bool have_gui = false;
94 Label* label = manage (new Label());
95 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
97 if (editor && insert->plugin()->has_editor()) {
98 switch (insert->type()) {
99 case ARDOUR::Windows_VST:
100 have_gui = create_windows_vst_editor (insert);
104 have_gui = create_lxvst_editor (insert);
107 case ARDOUR::AudioUnit:
108 have_gui = create_audiounit_editor (insert);
112 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
116 have_gui = create_lv2_editor (insert);
120 #ifndef WINDOWS_VST_SUPPORT
121 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
124 error << _("unknown type of editor-supplying plugin")
127 throw failed_constructor ();
133 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
136 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
138 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
140 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
141 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
144 set_name ("PluginEditor");
145 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
147 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
149 gint h = _pluginui->get_preferred_height ();
150 gint w = _pluginui->get_preferred_width ();
153 if (h > 600) h = 600;
156 set_default_size (w, h);
157 set_resizable (_pluginui->resizable());
160 PluginUIWindow::~PluginUIWindow ()
162 cerr << "PluginWindow deleted for " << this << endl;
167 PluginUIWindow::on_show ()
169 set_role("plugin_ui");
172 _pluginui->update_preset_list ();
173 _pluginui->update_preset ();
177 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
178 if (pre_deactivate_x >= 0) {
179 move (pre_deactivate_x, pre_deactivate_y);
183 if (_pluginui->on_window_show (_title)) {
190 PluginUIWindow::on_hide ()
192 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
193 get_position (pre_deactivate_x, pre_deactivate_y);
199 _pluginui->on_window_hide ();
204 PluginUIWindow::set_title(const std::string& title)
206 Gtk::Window::set_title(title);
211 #ifdef WINDOWS_VST_SUPPORT
212 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
214 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
217 #ifndef WINDOWS_VST_SUPPORT
221 boost::shared_ptr<WindowsVSTPlugin> vp;
223 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
224 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
226 throw failed_constructor ();
228 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp);
231 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
233 vpu->package (*this);
242 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
244 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
247 #ifndef LXVST_SUPPORT
251 boost::shared_ptr<LXVSTPlugin> lxvp;
253 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
254 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
256 throw failed_constructor ();
258 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
261 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
263 lxvpu->package (*this);
271 #ifdef AUDIOUNIT_SUPPORT
272 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
274 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
277 #ifndef AUDIOUNIT_SUPPORT
281 _pluginui = create_au_gui (insert, &box);
282 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
285 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
293 PluginUIWindow::app_activated (bool yn)
295 PluginUIWindow::app_activated (bool)
298 #ifdef AUDIOUNIT_SUPPORT
302 _pluginui->activate ();
303 if (pre_deactivate_x >= 0) {
304 move (pre_deactivate_x, pre_deactivate_y);
310 was_visible = is_visible();
311 get_position (pre_deactivate_x, pre_deactivate_y);
313 _pluginui->deactivate ();
320 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
323 boost::shared_ptr<LV2Plugin> vp;
325 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
326 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
327 throw failed_constructor ();
329 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
332 lpu->package (*this);
342 PluginUIWindow::keyboard_focused (bool yn)
344 _keyboard_focused = yn;
348 PluginUIWindow::on_key_press_event (GdkEventKey* event)
350 if (_keyboard_focused) {
352 if (_pluginui->non_gtk_gui()) {
353 _pluginui->forward_key_event (event);
355 return relay_key_press (event, this);
360 /* for us to be getting key press events, there really
361 MUST be a _pluginui, but just to be safe, check ...
365 if (_pluginui->non_gtk_gui()) {
366 /* pass editor window as the window for the event
367 to be handled in, not this one, because there are
368 no widgets in this window that we want to have
371 return relay_key_press (event, &PublicEditor::instance());
373 return relay_key_press (event, this);
382 PluginUIWindow::on_key_release_event (GdkEventKey *event)
384 if (_keyboard_focused) {
386 if (_pluginui->non_gtk_gui()) {
387 _pluginui->forward_key_event (event);
398 PluginUIWindow::plugin_going_away ()
400 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
403 _pluginui->stop_updating(0);
406 death_connection.disconnect ();
409 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
411 , plugin (insert->plugin())
412 , add_button (_("Add"))
413 , save_button (_("Save"))
414 , delete_button (_("Delete"))
415 , bypass_button (ArdourButton::led_default_elements)
416 , description_expander (_("Description"))
417 , plugin_analysis_expander (_("Plugin analysis"))
422 _preset_modified.set_size_request (16, -1);
423 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
424 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
425 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
426 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
427 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
428 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
431 update_preset_list ();
434 add_button.set_name ("PluginAddButton");
435 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
437 save_button.set_name ("PluginSaveButton");
438 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
440 delete_button.set_name ("PluginDeleteButton");
441 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
443 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
445 bypass_button.set_name ("plugin bypass button");
446 bypass_button.set_text (_("Bypass"));
447 bypass_button.set_active (!pi->active());
448 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
449 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
451 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
452 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
454 /* these images are not managed, so that we can remove them at will */
456 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
457 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
459 focus_button.add (*focus_out_image);
461 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));
462 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
464 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
465 description_expander.set_expanded(false);
467 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
468 plugin_analysis_expander.set_expanded(false);
470 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
472 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
473 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
474 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
475 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
478 PlugUIBase::~PlugUIBase()
485 PlugUIBase::plugin_going_away ()
487 /* drop references to the plugin/insert */
493 PlugUIBase::set_latency_label ()
495 framecnt_t const l = insert->effective_latency ();
496 framecnt_t const sr = insert->session().frame_rate ();
501 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
503 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
506 latency_label.set_text (t);
510 PlugUIBase::latency_button_clicked ()
513 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
514 latency_dialog = new ArdourWindow (_("Edit Latency"));
515 /* use both keep-above and transient for to try cover as many
516 different WM's as possible.
518 latency_dialog->set_keep_above (true);
519 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
521 latency_dialog->set_transient_for (*win);
523 latency_dialog->add (*latency_gui);
524 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
527 latency_dialog->show_all ();
531 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
533 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
534 boost::shared_ptr<Processor> p (weak_p.lock());
537 bypass_button.set_active (!p->active());
542 PlugUIBase::preset_selected ()
544 if (_no_load_preset) {
548 if (_preset_combo.get_active_text().length() > 0) {
549 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
551 plugin->load_preset (*pr);
553 warning << string_compose(_("Plugin preset %1 not found"),
554 _preset_combo.get_active_text()) << endmsg;
557 // blank selected = no preset
558 plugin->clear_preset();
562 #ifdef NO_PLUGIN_STATE
563 static bool seen_saving_message = false;
567 PlugUIBase::add_plugin_setting ()
569 #ifndef NO_PLUGIN_STATE
570 NewPluginPresetDialog d (plugin);
573 case Gtk::RESPONSE_ACCEPT:
574 if (d.name().empty()) {
579 plugin->remove_preset (d.name ());
582 Plugin::PresetRecord const r = plugin->save_preset (d.name());
583 if (!r.uri.empty ()) {
584 plugin->load_preset (r);
589 if (!seen_saving_message) {
590 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
593 seen_saving_message = true;
599 PlugUIBase::save_plugin_setting ()
601 #ifndef NO_PLUGIN_STATE
602 string const name = _preset_combo.get_active_text ();
603 plugin->remove_preset (name);
604 Plugin::PresetRecord const r = plugin->save_preset (name);
605 if (!r.uri.empty ()) {
606 plugin->load_preset (r);
609 if (!seen_saving_message) {
610 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
613 seen_saving_message = true;
619 PlugUIBase::delete_plugin_setting ()
621 #ifndef NO_PLUGIN_STATE
622 plugin->remove_preset (_preset_combo.get_active_text ());
624 if (!seen_saving_message) {
625 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
628 seen_saving_message = true;
634 PlugUIBase::bypass_button_release (GdkEventButton*)
636 bool view_says_bypassed = (bypass_button.active_state() != 0);
638 if (view_says_bypassed != insert->active()) {
639 if (view_says_bypassed) {
642 insert->deactivate ();
650 PlugUIBase::focus_toggled (GdkEventButton*)
652 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
653 Keyboard::the_keyboard().magic_widget_drop_focus();
654 focus_button.remove ();
655 focus_button.add (*focus_out_image);
656 focus_out_image->show ();
657 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));
658 KeyboardFocused (false);
660 Keyboard::the_keyboard().magic_widget_grab_focus();
661 focus_button.remove ();
662 focus_button.add (*focus_in_image);
663 focus_in_image->show ();
664 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
665 KeyboardFocused (true);
672 PlugUIBase::toggle_description()
674 if (description_expander.get_expanded() &&
675 !description_expander.get_child()) {
676 const std::string text = plugin->get_docs();
681 Gtk::Label* label = manage(new Gtk::Label(text));
682 label->set_line_wrap(true);
683 label->set_line_wrap_mode(Pango::WRAP_WORD);
684 description_expander.add(*label);
685 description_expander.show_all();
688 if (!description_expander.get_expanded()) {
689 description_expander.remove();
695 PlugUIBase::toggle_plugin_analysis()
697 if (plugin_analysis_expander.get_expanded() &&
698 !plugin_analysis_expander.get_child()) {
701 eqgui = new PluginEqGui (insert);
704 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
707 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
710 plugin_analysis_expander.add (*eqgui);
711 plugin_analysis_expander.show_all ();
712 eqgui->start_listening ();
715 if (!plugin_analysis_expander.get_expanded()) {
716 // Hide & remove from expander
719 eqgui->stop_listening ();
720 plugin_analysis_expander.remove();
722 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
725 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
731 PlugUIBase::update_preset_list ()
733 vector<string> preset_labels;
734 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
738 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
739 preset_labels.push_back (i->label);
742 preset_labels.push_back("");
744 set_popdown_strings (_preset_combo, preset_labels);
750 PlugUIBase::update_preset ()
752 Plugin::PresetRecord p = plugin->last_preset();
755 _preset_combo.set_active_text (p.label);
758 save_button.set_sensitive (!p.uri.empty() && p.user);
759 delete_button.set_sensitive (!p.uri.empty() && p.user);
761 update_preset_modified ();
765 PlugUIBase::update_preset_modified ()
768 if (plugin->last_preset().uri.empty()) {
769 _preset_modified.set_text ("");
773 bool const c = plugin->parameter_changed_since_last_preset ();
774 if (_preset_modified.get_text().empty() == c) {
775 _preset_modified.set_text (c ? "*" : "");
780 PlugUIBase::parameter_changed (uint32_t, float)
782 update_preset_modified ();
786 PlugUIBase::preset_added_or_removed ()
788 /* Update both the list and the currently-displayed preset */
789 update_preset_list ();