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"
62 #include "ardour_window.h"
63 #include "ardour_ui.h"
65 #include "plugin_ui.h"
67 #include "gui_thread.h"
68 #include "public_editor.h"
70 #include "latency_gui.h"
71 #include "plugin_eq_gui.h"
72 #include "new_plugin_preset_dialog.h"
77 using namespace ARDOUR;
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(GTKOSX)
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(GTKOSX)
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);
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);
346 PluginUIWindow::keyboard_focused (bool yn)
348 _keyboard_focused = yn;
352 PluginUIWindow::on_key_press_event (GdkEventKey* event)
354 if (_keyboard_focused) {
356 if (_pluginui->non_gtk_gui()) {
357 _pluginui->forward_key_event (event);
359 return relay_key_press (event, this);
364 /* for us to be getting key press events, there really
365 MUST be a _pluginui, but just to be safe, check ...
369 if (_pluginui->non_gtk_gui()) {
370 /* pass editor window as the window for the event
371 to be handled in, not this one, because there are
372 no widgets in this window that we want to have
375 return relay_key_press (event, &PublicEditor::instance());
377 return relay_key_press (event, this);
386 PluginUIWindow::on_key_release_event (GdkEventKey *event)
388 if (_keyboard_focused) {
390 if (_pluginui->non_gtk_gui()) {
391 _pluginui->forward_key_event (event);
402 PluginUIWindow::plugin_going_away ()
404 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
407 _pluginui->stop_updating(0);
410 death_connection.disconnect ();
413 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
415 , plugin (insert->plugin())
416 , add_button (_("Add"))
417 , save_button (_("Save"))
418 , delete_button (_("Delete"))
419 , bypass_button (ArdourButton::led_default_elements)
420 , description_expander (_("Description"))
421 , plugin_analysis_expander (_("Plugin analysis"))
426 _preset_modified.set_size_request (16, -1);
427 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
428 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
429 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
430 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
431 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
432 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
435 update_preset_list ();
438 add_button.set_name ("PluginAddButton");
439 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
441 save_button.set_name ("PluginSaveButton");
442 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
444 delete_button.set_name ("PluginDeleteButton");
445 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
447 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
449 bypass_button.set_name ("plugin bypass button");
450 bypass_button.set_text (_("Bypass"));
451 bypass_button.set_active (!pi->active());
452 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
453 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
455 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
456 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
458 /* these images are not managed, so that we can remove them at will */
460 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
461 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
463 focus_button.add (*focus_out_image);
465 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));
466 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
468 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
469 description_expander.set_expanded(false);
471 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
472 plugin_analysis_expander.set_expanded(false);
474 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
476 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
477 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
478 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
479 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
482 PlugUIBase::~PlugUIBase()
489 PlugUIBase::plugin_going_away ()
491 /* drop references to the plugin/insert */
497 PlugUIBase::set_latency_label ()
499 framecnt_t const l = insert->effective_latency ();
500 framecnt_t const sr = insert->session().frame_rate ();
505 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
507 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
510 latency_label.set_text (t);
514 PlugUIBase::latency_button_clicked ()
517 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
518 latency_dialog = new ArdourWindow (_("Edit Latency"));
519 /* use both keep-above and transient for to try cover as many
520 different WM's as possible.
522 latency_dialog->set_keep_above (true);
523 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
525 latency_dialog->set_transient_for (*win);
527 latency_dialog->add (*latency_gui);
528 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
531 latency_dialog->show_all ();
535 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
537 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
538 boost::shared_ptr<Processor> p (weak_p.lock());
541 bypass_button.set_active (!p->active());
546 PlugUIBase::preset_selected ()
548 if (_no_load_preset) {
552 if (_preset_combo.get_active_text().length() > 0) {
553 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
555 plugin->load_preset (*pr);
557 warning << string_compose(_("Plugin preset %1 not found"),
558 _preset_combo.get_active_text()) << endmsg;
561 // blank selected = no preset
562 plugin->clear_preset();
566 #ifdef NO_PLUGIN_STATE
567 static bool seen_saving_message = false;
571 PlugUIBase::add_plugin_setting ()
573 #ifndef NO_PLUGIN_STATE
574 NewPluginPresetDialog d (plugin);
577 case Gtk::RESPONSE_ACCEPT:
578 if (d.name().empty()) {
583 plugin->remove_preset (d.name ());
586 Plugin::PresetRecord const r = plugin->save_preset (d.name());
587 if (!r.uri.empty ()) {
588 plugin->load_preset (r);
593 if (!seen_saving_message) {
594 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
597 seen_saving_message = true;
603 PlugUIBase::save_plugin_setting ()
605 #ifndef NO_PLUGIN_STATE
606 string const name = _preset_combo.get_active_text ();
607 plugin->remove_preset (name);
608 Plugin::PresetRecord const r = plugin->save_preset (name);
609 if (!r.uri.empty ()) {
610 plugin->load_preset (r);
613 if (!seen_saving_message) {
614 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
617 seen_saving_message = true;
623 PlugUIBase::delete_plugin_setting ()
625 #ifndef NO_PLUGIN_STATE
626 plugin->remove_preset (_preset_combo.get_active_text ());
628 if (!seen_saving_message) {
629 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
632 seen_saving_message = true;
638 PlugUIBase::bypass_button_release (GdkEventButton*)
640 bool view_says_bypassed = (bypass_button.active_state() != 0);
642 if (view_says_bypassed != insert->active()) {
643 if (view_says_bypassed) {
646 insert->deactivate ();
654 PlugUIBase::focus_toggled (GdkEventButton*)
656 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
657 Keyboard::the_keyboard().magic_widget_drop_focus();
658 focus_button.remove ();
659 focus_button.add (*focus_out_image);
660 focus_out_image->show ();
661 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));
662 KeyboardFocused (false);
664 Keyboard::the_keyboard().magic_widget_grab_focus();
665 focus_button.remove ();
666 focus_button.add (*focus_in_image);
667 focus_in_image->show ();
668 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
669 KeyboardFocused (true);
676 PlugUIBase::toggle_description()
678 if (description_expander.get_expanded() &&
679 !description_expander.get_child()) {
680 const std::string text = plugin->get_docs();
685 Gtk::Label* label = manage(new Gtk::Label(text));
686 label->set_line_wrap(true);
687 label->set_line_wrap_mode(Pango::WRAP_WORD);
688 description_expander.add(*label);
689 description_expander.show_all();
692 if (!description_expander.get_expanded()) {
693 description_expander.remove();
699 PlugUIBase::toggle_plugin_analysis()
701 if (plugin_analysis_expander.get_expanded() &&
702 !plugin_analysis_expander.get_child()) {
705 eqgui = new PluginEqGui (insert);
708 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
711 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
714 plugin_analysis_expander.add (*eqgui);
715 plugin_analysis_expander.show_all ();
716 eqgui->start_listening ();
719 if (!plugin_analysis_expander.get_expanded()) {
720 // Hide & remove from expander
723 eqgui->stop_listening ();
724 plugin_analysis_expander.remove();
726 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
729 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
735 PlugUIBase::update_preset_list ()
737 vector<string> preset_labels;
738 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
742 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
743 preset_labels.push_back (i->label);
746 preset_labels.push_back("");
748 set_popdown_strings (_preset_combo, preset_labels);
754 PlugUIBase::update_preset ()
756 Plugin::PresetRecord p = plugin->last_preset();
759 _preset_combo.set_active_text (p.label);
762 save_button.set_sensitive (!p.uri.empty() && p.user);
763 delete_button.set_sensitive (!p.uri.empty() && p.user);
765 update_preset_modified ();
769 PlugUIBase::update_preset_modified ()
772 if (plugin->last_preset().uri.empty()) {
773 _preset_modified.set_text ("");
777 bool const c = plugin->parameter_changed_since_last_preset ();
778 if (_preset_modified.get_text().empty() == c) {
779 _preset_modified.set_text (c ? "*" : "");
784 PlugUIBase::parameter_changed (uint32_t, float)
786 update_preset_modified ();
790 PlugUIBase::preset_added_or_removed ()
792 /* Update both the list and the currently-displayed preset */
793 update_preset_list ();