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 "midi++/manager.h"
45 #include "ardour/session.h"
46 #include "ardour/plugin.h"
47 #include "ardour/plugin_insert.h"
48 #include "ardour/ladspa_plugin.h"
49 #ifdef WINDOWS_VST_SUPPORT
50 #include "ardour/windows_vst_plugin.h"
51 #include "windows_vst_plugin_ui.h"
54 #include "ardour/lxvst_plugin.h"
55 #include "lxvst_plugin_ui.h"
58 #include "ardour/lv2_plugin.h"
59 #include "lv2_plugin_ui.h"
64 #include "ardour_window.h"
65 #include "ardour_ui.h"
67 #include "plugin_ui.h"
69 #include "gui_thread.h"
70 #include "public_editor.h"
72 #include "latency_gui.h"
73 #include "plugin_eq_gui.h"
74 #include "new_plugin_preset_dialog.h"
79 using namespace ARDOUR;
81 using namespace Gtkmm2ext;
84 PluginUIWindow::PluginUIWindow (
85 boost::shared_ptr<PluginInsert> insert,
88 : ArdourWindow (string())
90 , _keyboard_focused (false)
91 #ifdef AUDIOUNIT_SUPPORT
92 , pre_deactivate_x (-1)
93 , pre_deactivate_y (-1)
97 bool have_gui = false;
98 Label* label = manage (new Label());
99 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
101 if (editor && insert->plugin()->has_editor()) {
102 switch (insert->type()) {
103 case ARDOUR::Windows_VST:
104 have_gui = create_windows_vst_editor (insert);
108 have_gui = create_lxvst_editor (insert);
111 case ARDOUR::AudioUnit:
112 have_gui = create_audiounit_editor (insert);
116 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
120 have_gui = create_lv2_editor (insert);
124 #ifndef WINDOWS_VST_SUPPORT
125 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
128 error << _("unknown type of editor-supplying plugin")
131 throw failed_constructor ();
137 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
140 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
142 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
144 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
145 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
148 set_name ("PluginEditor");
149 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
151 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
153 gint h = _pluginui->get_preferred_height ();
154 gint w = _pluginui->get_preferred_width ();
157 if (h > 600) h = 600;
160 set_default_size (w, h);
161 set_resizable (_pluginui->resizable());
164 PluginUIWindow::~PluginUIWindow ()
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);
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);
345 PluginUIWindow::keyboard_focused (bool yn)
347 _keyboard_focused = yn;
351 PluginUIWindow::on_key_press_event (GdkEventKey* event)
353 if (_keyboard_focused) {
355 if (_pluginui->non_gtk_gui()) {
356 _pluginui->forward_key_event (event);
358 return relay_key_press (event, this);
363 /* for us to be getting key press events, there really
364 MUST be a _pluginui, but just to be safe, check ...
368 if (_pluginui->non_gtk_gui()) {
369 /* pass editor window as the window for the event
370 to be handled in, not this one, because there are
371 no widgets in this window that we want to have
374 return relay_key_press (event, &PublicEditor::instance());
376 return relay_key_press (event, this);
385 PluginUIWindow::on_key_release_event (GdkEventKey *event)
387 if (_keyboard_focused) {
389 if (_pluginui->non_gtk_gui()) {
390 _pluginui->forward_key_event (event);
401 PluginUIWindow::plugin_going_away ()
403 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
406 _pluginui->stop_updating(0);
409 death_connection.disconnect ();
411 delete_when_idle (this);
414 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
416 , plugin (insert->plugin())
417 , add_button (_("Add"))
418 , save_button (_("Save"))
419 , delete_button (_("Delete"))
420 , bypass_button (ArdourButton::led_default_elements)
421 , description_expander (_("Description"))
422 , plugin_analysis_expander (_("Plugin analysis"))
427 _preset_modified.set_size_request (16, -1);
428 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
429 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
430 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
431 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
432 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
433 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
436 update_preset_list ();
439 add_button.set_name ("PluginAddButton");
440 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
442 save_button.set_name ("PluginSaveButton");
443 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
445 delete_button.set_name ("PluginDeleteButton");
446 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
448 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
450 bypass_button.set_name ("plugin bypass button");
451 bypass_button.set_text (_("Bypass"));
452 bypass_button.set_active (!pi->active());
453 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
454 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
456 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
457 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
459 /* these images are not managed, so that we can remove them at will */
461 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
462 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
464 focus_button.add (*focus_out_image);
466 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));
467 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
469 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
470 description_expander.set_expanded(false);
472 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
473 plugin_analysis_expander.set_expanded(false);
475 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
477 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
478 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
479 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
480 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
483 PlugUIBase::~PlugUIBase()
490 PlugUIBase::plugin_going_away ()
492 /* drop references to the plugin/insert */
498 PlugUIBase::set_latency_label ()
500 framecnt_t const l = insert->effective_latency ();
501 framecnt_t const sr = insert->session().frame_rate ();
506 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
508 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
511 latency_label.set_text (t);
515 PlugUIBase::latency_button_clicked ()
518 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
519 latency_dialog = new ArdourWindow (_("Edit Latency"));
520 /* use both keep-above and transient for to try cover as many
521 different WM's as possible.
523 latency_dialog->set_keep_above (true);
524 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
526 latency_dialog->set_transient_for (*win);
528 latency_dialog->add (*latency_gui);
529 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
532 latency_dialog->show_all ();
536 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
538 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
539 boost::shared_ptr<Processor> p (weak_p.lock());
542 bypass_button.set_active (!p->active());
547 PlugUIBase::preset_selected ()
549 if (_no_load_preset) {
553 if (_preset_combo.get_active_text().length() > 0) {
554 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
556 plugin->load_preset (*pr);
558 warning << string_compose(_("Plugin preset %1 not found"),
559 _preset_combo.get_active_text()) << endmsg;
562 // blank selected = no preset
563 plugin->clear_preset();
567 #ifdef NO_PLUGIN_STATE
568 static bool seen_saving_message = false;
572 PlugUIBase::add_plugin_setting ()
574 #ifndef NO_PLUGIN_STATE
575 NewPluginPresetDialog d (plugin);
578 case Gtk::RESPONSE_ACCEPT:
579 if (d.name().empty()) {
584 plugin->remove_preset (d.name ());
587 Plugin::PresetRecord const r = plugin->save_preset (d.name());
588 if (!r.uri.empty ()) {
589 plugin->load_preset (r);
594 if (!seen_saving_message) {
595 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
598 seen_saving_message = true;
604 PlugUIBase::save_plugin_setting ()
606 #ifndef NO_PLUGIN_STATE
607 string const name = _preset_combo.get_active_text ();
608 plugin->remove_preset (name);
609 Plugin::PresetRecord const r = plugin->save_preset (name);
610 if (!r.uri.empty ()) {
611 plugin->load_preset (r);
614 if (!seen_saving_message) {
615 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
618 seen_saving_message = true;
624 PlugUIBase::delete_plugin_setting ()
626 #ifndef NO_PLUGIN_STATE
627 plugin->remove_preset (_preset_combo.get_active_text ());
629 if (!seen_saving_message) {
630 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
633 seen_saving_message = true;
639 PlugUIBase::bypass_button_release (GdkEventButton*)
641 bool view_says_bypassed = (bypass_button.active_state() != 0);
643 if (view_says_bypassed != insert->active()) {
644 if (view_says_bypassed) {
647 insert->deactivate ();
655 PlugUIBase::focus_toggled (GdkEventButton*)
657 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
658 Keyboard::the_keyboard().magic_widget_drop_focus();
659 focus_button.remove ();
660 focus_button.add (*focus_out_image);
661 focus_out_image->show ();
662 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));
663 KeyboardFocused (false);
665 Keyboard::the_keyboard().magic_widget_grab_focus();
666 focus_button.remove ();
667 focus_button.add (*focus_in_image);
668 focus_in_image->show ();
669 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
670 KeyboardFocused (true);
677 PlugUIBase::toggle_description()
679 if (description_expander.get_expanded() &&
680 !description_expander.get_child()) {
681 const std::string text = plugin->get_docs();
686 Gtk::Label* label = manage(new Gtk::Label(text));
687 label->set_line_wrap(true);
688 label->set_line_wrap_mode(Pango::WRAP_WORD);
689 description_expander.add(*label);
690 description_expander.show_all();
693 if (!description_expander.get_expanded()) {
694 description_expander.remove();
700 PlugUIBase::toggle_plugin_analysis()
702 if (plugin_analysis_expander.get_expanded() &&
703 !plugin_analysis_expander.get_child()) {
706 eqgui = new PluginEqGui (insert);
709 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
712 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
715 plugin_analysis_expander.add (*eqgui);
716 plugin_analysis_expander.show_all ();
717 eqgui->start_listening ();
720 if (!plugin_analysis_expander.get_expanded()) {
721 // Hide & remove from expander
724 eqgui->stop_listening ();
725 plugin_analysis_expander.remove();
727 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
730 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
736 PlugUIBase::update_preset_list ()
738 vector<string> preset_labels;
739 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
743 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
744 preset_labels.push_back (i->label);
747 preset_labels.push_back("");
749 set_popdown_strings (_preset_combo, preset_labels);
755 PlugUIBase::update_preset ()
757 Plugin::PresetRecord p = plugin->last_preset();
760 _preset_combo.set_active_text (p.label);
763 save_button.set_sensitive (!p.uri.empty() && p.user);
764 delete_button.set_sensitive (!p.uri.empty() && p.user);
766 update_preset_modified ();
770 PlugUIBase::update_preset_modified ()
773 if (plugin->last_preset().uri.empty()) {
774 _preset_modified.set_text ("");
778 bool const c = plugin->parameter_changed_since_last_preset ();
779 if (_preset_modified.get_text().empty() == c) {
780 _preset_modified.set_text (c ? "*" : "");
785 PlugUIBase::parameter_changed (uint32_t, float)
787 update_preset_modified ();
791 PlugUIBase::preset_added_or_removed ()
793 /* Update both the list and the currently-displayed preset */
794 update_preset_list ();