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 ()
166 PluginUIWindow::on_show ()
168 set_role("plugin_ui");
171 _pluginui->update_preset_list ();
172 _pluginui->update_preset ();
176 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
177 if (pre_deactivate_x >= 0) {
178 move (pre_deactivate_x, pre_deactivate_y);
182 if (_pluginui->on_window_show (_title)) {
189 PluginUIWindow::on_hide ()
191 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
192 get_position (pre_deactivate_x, pre_deactivate_y);
198 _pluginui->on_window_hide ();
203 PluginUIWindow::set_title(const std::string& title)
205 Gtk::Window::set_title(title);
210 #ifdef WINDOWS_VST_SUPPORT
211 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
213 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
216 #ifndef WINDOWS_VST_SUPPORT
220 boost::shared_ptr<WindowsVSTPlugin> vp;
222 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
223 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
225 throw failed_constructor ();
227 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp);
230 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
232 vpu->package (*this);
241 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
243 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
246 #ifndef LXVST_SUPPORT
250 boost::shared_ptr<LXVSTPlugin> lxvp;
252 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
253 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
255 throw failed_constructor ();
257 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
260 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
262 lxvpu->package (*this);
270 #ifdef AUDIOUNIT_SUPPORT
271 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
273 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
276 #ifndef AUDIOUNIT_SUPPORT
280 _pluginui = create_au_gui (insert, &box);
281 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
284 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
292 PluginUIWindow::app_activated (bool yn)
294 PluginUIWindow::app_activated (bool)
297 #ifdef AUDIOUNIT_SUPPORT
301 _pluginui->activate ();
302 if (pre_deactivate_x >= 0) {
303 move (pre_deactivate_x, pre_deactivate_y);
309 was_visible = is_visible();
310 get_position (pre_deactivate_x, pre_deactivate_y);
312 _pluginui->deactivate ();
319 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
322 boost::shared_ptr<LV2Plugin> vp;
324 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
325 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
326 throw failed_constructor ();
328 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
331 lpu->package (*this);
341 PluginUIWindow::keyboard_focused (bool yn)
343 _keyboard_focused = yn;
347 PluginUIWindow::on_key_press_event (GdkEventKey* event)
349 if (_keyboard_focused) {
351 if (_pluginui->non_gtk_gui()) {
352 _pluginui->forward_key_event (event);
354 return relay_key_press (event, this);
359 /* for us to be getting key press events, there really
360 MUST be a _pluginui, but just to be safe, check ...
364 if (_pluginui->non_gtk_gui()) {
365 /* pass editor window as the window for the event
366 to be handled in, not this one, because there are
367 no widgets in this window that we want to have
370 return relay_key_press (event, &PublicEditor::instance());
372 return relay_key_press (event, this);
381 PluginUIWindow::on_key_release_event (GdkEventKey *event)
383 if (_keyboard_focused) {
385 if (_pluginui->non_gtk_gui()) {
386 _pluginui->forward_key_event (event);
397 PluginUIWindow::plugin_going_away ()
399 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
402 _pluginui->stop_updating(0);
405 death_connection.disconnect ();
407 delete_when_idle (this);
410 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
412 , plugin (insert->plugin())
413 , add_button (_("Add"))
414 , save_button (_("Save"))
415 , delete_button (_("Delete"))
416 , bypass_button (ArdourButton::led_default_elements)
417 , description_expander (_("Description"))
418 , plugin_analysis_expander (_("Plugin analysis"))
423 _preset_modified.set_size_request (16, -1);
424 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
425 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
426 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
427 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
428 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
429 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
432 update_preset_list ();
435 add_button.set_name ("PluginAddButton");
436 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
438 save_button.set_name ("PluginSaveButton");
439 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
441 delete_button.set_name ("PluginDeleteButton");
442 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
444 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
446 bypass_button.set_name ("plugin bypass button");
447 bypass_button.set_text (_("Bypass"));
448 bypass_button.set_active (!pi->active());
449 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
450 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
452 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
453 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
455 /* these images are not managed, so that we can remove them at will */
457 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
458 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
460 focus_button.add (*focus_out_image);
462 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));
463 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
465 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
466 description_expander.set_expanded(false);
468 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
469 plugin_analysis_expander.set_expanded(false);
471 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
473 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
474 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
475 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
476 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
479 PlugUIBase::~PlugUIBase()
486 PlugUIBase::plugin_going_away ()
488 /* drop references to the plugin/insert */
494 PlugUIBase::set_latency_label ()
496 framecnt_t const l = insert->effective_latency ();
497 framecnt_t const sr = insert->session().frame_rate ();
502 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
504 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
507 latency_label.set_text (t);
511 PlugUIBase::latency_button_clicked ()
514 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
515 latency_dialog = new ArdourWindow (_("Edit Latency"));
516 /* use both keep-above and transient for to try cover as many
517 different WM's as possible.
519 latency_dialog->set_keep_above (true);
520 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
522 latency_dialog->set_transient_for (*win);
524 latency_dialog->add (*latency_gui);
525 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
528 latency_dialog->show_all ();
532 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
534 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
535 boost::shared_ptr<Processor> p (weak_p.lock());
538 bypass_button.set_active (!p->active());
543 PlugUIBase::preset_selected ()
545 if (_no_load_preset) {
549 if (_preset_combo.get_active_text().length() > 0) {
550 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
552 plugin->load_preset (*pr);
554 warning << string_compose(_("Plugin preset %1 not found"),
555 _preset_combo.get_active_text()) << endmsg;
558 // blank selected = no preset
559 plugin->clear_preset();
563 #ifdef NO_PLUGIN_STATE
564 static bool seen_saving_message = false;
568 PlugUIBase::add_plugin_setting ()
570 #ifndef NO_PLUGIN_STATE
571 NewPluginPresetDialog d (plugin);
574 case Gtk::RESPONSE_ACCEPT:
575 if (d.name().empty()) {
580 plugin->remove_preset (d.name ());
583 Plugin::PresetRecord const r = plugin->save_preset (d.name());
584 if (!r.uri.empty ()) {
585 plugin->load_preset (r);
590 if (!seen_saving_message) {
591 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
594 seen_saving_message = true;
600 PlugUIBase::save_plugin_setting ()
602 #ifndef NO_PLUGIN_STATE
603 string const name = _preset_combo.get_active_text ();
604 plugin->remove_preset (name);
605 Plugin::PresetRecord const r = plugin->save_preset (name);
606 if (!r.uri.empty ()) {
607 plugin->load_preset (r);
610 if (!seen_saving_message) {
611 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
614 seen_saving_message = true;
620 PlugUIBase::delete_plugin_setting ()
622 #ifndef NO_PLUGIN_STATE
623 plugin->remove_preset (_preset_combo.get_active_text ());
625 if (!seen_saving_message) {
626 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
629 seen_saving_message = true;
635 PlugUIBase::bypass_button_release (GdkEventButton*)
637 bool view_says_bypassed = (bypass_button.active_state() != 0);
639 if (view_says_bypassed != insert->active()) {
640 if (view_says_bypassed) {
643 insert->deactivate ();
651 PlugUIBase::focus_toggled (GdkEventButton*)
653 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
654 Keyboard::the_keyboard().magic_widget_drop_focus();
655 focus_button.remove ();
656 focus_button.add (*focus_out_image);
657 focus_out_image->show ();
658 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));
659 KeyboardFocused (false);
661 Keyboard::the_keyboard().magic_widget_grab_focus();
662 focus_button.remove ();
663 focus_button.add (*focus_in_image);
664 focus_in_image->show ();
665 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
666 KeyboardFocused (true);
673 PlugUIBase::toggle_description()
675 if (description_expander.get_expanded() &&
676 !description_expander.get_child()) {
677 const std::string text = plugin->get_docs();
682 Gtk::Label* label = manage(new Gtk::Label(text));
683 label->set_line_wrap(true);
684 label->set_line_wrap_mode(Pango::WRAP_WORD);
685 description_expander.add(*label);
686 description_expander.show_all();
689 if (!description_expander.get_expanded()) {
690 description_expander.remove();
696 PlugUIBase::toggle_plugin_analysis()
698 if (plugin_analysis_expander.get_expanded() &&
699 !plugin_analysis_expander.get_child()) {
702 eqgui = new PluginEqGui (insert);
705 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
708 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
711 plugin_analysis_expander.add (*eqgui);
712 plugin_analysis_expander.show_all ();
713 eqgui->start_listening ();
716 if (!plugin_analysis_expander.get_expanded()) {
717 // Hide & remove from expander
720 eqgui->stop_listening ();
721 plugin_analysis_expander.remove();
723 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
726 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
732 PlugUIBase::update_preset_list ()
734 vector<string> preset_labels;
735 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
739 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
740 preset_labels.push_back (i->label);
743 preset_labels.push_back("");
745 set_popdown_strings (_preset_combo, preset_labels);
751 PlugUIBase::update_preset ()
753 Plugin::PresetRecord p = plugin->last_preset();
756 _preset_combo.set_active_text (p.label);
759 save_button.set_sensitive (!p.uri.empty() && p.user);
760 delete_button.set_sensitive (!p.uri.empty() && p.user);
762 update_preset_modified ();
766 PlugUIBase::update_preset_modified ()
769 if (plugin->last_preset().uri.empty()) {
770 _preset_modified.set_text ("");
774 bool const c = plugin->parameter_changed_since_last_preset ();
775 if (_preset_modified.get_text().empty() == c) {
776 _preset_modified.set_text (c ? "*" : "");
781 PlugUIBase::parameter_changed (uint32_t, float)
783 update_preset_modified ();
787 PlugUIBase::preset_added_or_removed ()
789 /* Update both the list and the currently-displayed preset */
790 update_preset_list ();