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 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 , bypass_button (ArdourButton::led_default_elements)
422 , description_expander (_("Description"))
423 , plugin_analysis_expander (_("Plugin analysis"))
428 _preset_modified.set_size_request (16, -1);
429 _preset_combo.set_text("(default)");
430 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
431 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
432 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
433 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
434 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
437 update_preset_list ();
440 add_button.set_name ("generic button");
441 add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
443 save_button.set_name ("generic button");
444 save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
446 delete_button.set_name ("generic button");
447 delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
449 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
451 bypass_button.set_name ("plugin bypass button");
452 bypass_button.set_text (_("Bypass"));
453 bypass_button.set_active (!pi->active());
454 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
455 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
457 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
458 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
460 /* these images are not managed, so that we can remove them at will */
462 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
463 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
465 focus_button.add (*focus_out_image);
467 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));
468 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
470 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
471 description_expander.set_expanded(false);
473 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
474 plugin_analysis_expander.set_expanded(false);
476 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
478 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
479 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
480 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
481 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
484 PlugUIBase::~PlugUIBase()
491 PlugUIBase::plugin_going_away ()
493 /* drop references to the plugin/insert */
499 PlugUIBase::set_latency_label ()
501 framecnt_t const l = insert->effective_latency ();
502 framecnt_t const sr = insert->session().frame_rate ();
507 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
509 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
512 latency_button.set_text (t);
516 PlugUIBase::latency_button_clicked ()
519 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
520 latency_dialog = new ArdourWindow (_("Edit Latency"));
521 /* use both keep-above and transient for to try cover as many
522 different WM's as possible.
524 latency_dialog->set_keep_above (true);
525 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
527 latency_dialog->set_transient_for (*win);
529 latency_dialog->add (*latency_gui);
530 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
533 latency_dialog->show_all ();
537 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
539 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
540 boost::shared_ptr<Processor> p (weak_p.lock());
543 bypass_button.set_active (!p->active());
548 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
550 if (_no_load_preset) {
553 if (!preset.label.empty()) {
554 plugin->load_preset (preset);
556 // blank selected = no preset
557 plugin->clear_preset();
561 #ifdef NO_PLUGIN_STATE
562 static bool seen_saving_message = false;
566 PlugUIBase::add_plugin_setting ()
568 #ifndef NO_PLUGIN_STATE
569 NewPluginPresetDialog d (plugin);
572 case Gtk::RESPONSE_ACCEPT:
573 if (d.name().empty()) {
578 plugin->remove_preset (d.name ());
581 Plugin::PresetRecord const r = plugin->save_preset (d.name());
582 if (!r.uri.empty ()) {
583 plugin->load_preset (r);
588 if (!seen_saving_message) {
589 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
592 seen_saving_message = true;
598 PlugUIBase::save_plugin_setting ()
600 #ifndef NO_PLUGIN_STATE
601 string const name = _preset_combo.get_text ();
602 plugin->remove_preset (name);
603 Plugin::PresetRecord const r = plugin->save_preset (name);
604 if (!r.uri.empty ()) {
605 plugin->load_preset (r);
608 if (!seen_saving_message) {
609 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
612 seen_saving_message = true;
618 PlugUIBase::delete_plugin_setting ()
620 #ifndef NO_PLUGIN_STATE
621 plugin->remove_preset (_preset_combo.get_text ());
623 if (!seen_saving_message) {
624 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
627 seen_saving_message = true;
633 PlugUIBase::bypass_button_release (GdkEventButton*)
635 bool view_says_bypassed = (bypass_button.active_state() != 0);
637 if (view_says_bypassed != insert->active()) {
638 if (view_says_bypassed) {
641 insert->deactivate ();
649 PlugUIBase::focus_toggled (GdkEventButton*)
651 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
652 Keyboard::the_keyboard().magic_widget_drop_focus();
653 focus_button.remove ();
654 focus_button.add (*focus_out_image);
655 focus_out_image->show ();
656 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));
657 KeyboardFocused (false);
659 Keyboard::the_keyboard().magic_widget_grab_focus();
660 focus_button.remove ();
661 focus_button.add (*focus_in_image);
662 focus_in_image->show ();
663 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
664 KeyboardFocused (true);
671 PlugUIBase::toggle_description()
673 if (description_expander.get_expanded() &&
674 !description_expander.get_child()) {
675 const std::string text = plugin->get_docs();
680 Gtk::Label* label = manage(new Gtk::Label(text));
681 label->set_line_wrap(true);
682 label->set_line_wrap_mode(Pango::WRAP_WORD);
683 description_expander.add(*label);
684 description_expander.show_all();
687 if (!description_expander.get_expanded()) {
688 description_expander.remove();
694 PlugUIBase::toggle_plugin_analysis()
696 if (plugin_analysis_expander.get_expanded() &&
697 !plugin_analysis_expander.get_child()) {
700 eqgui = new PluginEqGui (insert);
703 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
706 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
709 plugin_analysis_expander.add (*eqgui);
710 plugin_analysis_expander.show_all ();
711 eqgui->start_listening ();
714 if (!plugin_analysis_expander.get_expanded()) {
715 // Hide & remove from expander
718 eqgui->stop_listening ();
719 plugin_analysis_expander.remove();
721 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
724 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
730 PlugUIBase::update_preset_list ()
732 using namespace Menu_Helpers;
734 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
738 // Add a menu entry for each preset
739 _preset_combo.clear_items();
740 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
741 _preset_combo.AddMenuElem(
742 MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
745 // Add an empty entry for un-setting current preset (see preset_selected)
746 Plugin::PresetRecord no_preset;
747 _preset_combo.AddMenuElem(
748 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
754 PlugUIBase::update_preset ()
756 Plugin::PresetRecord p = plugin->last_preset();
760 _preset_combo.set_text ("(none)");
762 _preset_combo.set_text (p.label);
766 save_button.set_sensitive (!p.uri.empty() && p.user);
767 delete_button.set_sensitive (!p.uri.empty() && p.user);
769 update_preset_modified ();
773 PlugUIBase::update_preset_modified ()
776 if (plugin->last_preset().uri.empty()) {
777 _preset_modified.set_text ("");
781 bool const c = plugin->parameter_changed_since_last_preset ();
782 if (_preset_modified.get_text().empty() == c) {
783 _preset_modified.set_text (c ? "*" : "");
788 PlugUIBase::parameter_changed (uint32_t, float)
790 update_preset_modified ();
794 PlugUIBase::preset_added_or_removed ()
796 /* Update both the list and the currently-displayed preset */
797 update_preset_list ();