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 ()
163 cerr << "PluginWindow deleted for " << this << endl;
169 PluginUIWindow::on_show ()
171 set_role("plugin_ui");
174 _pluginui->update_preset_list ();
175 _pluginui->update_preset ();
179 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
180 if (pre_deactivate_x >= 0) {
181 move (pre_deactivate_x, pre_deactivate_y);
185 if (_pluginui->on_window_show (_title)) {
192 PluginUIWindow::on_hide ()
194 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
195 get_position (pre_deactivate_x, pre_deactivate_y);
201 _pluginui->on_window_hide ();
206 PluginUIWindow::set_title(const std::string& title)
208 Gtk::Window::set_title(title);
213 #ifdef WINDOWS_VST_SUPPORT
214 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
216 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
219 #ifndef WINDOWS_VST_SUPPORT
223 boost::shared_ptr<WindowsVSTPlugin> vp;
225 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
226 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
228 throw failed_constructor ();
230 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
233 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
235 vpu->package (*this);
244 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
246 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
249 #ifndef LXVST_SUPPORT
253 boost::shared_ptr<LXVSTPlugin> lxvp;
255 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
256 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
258 throw failed_constructor ();
260 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
263 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
265 lxvpu->package (*this);
273 #ifdef AUDIOUNIT_SUPPORT
274 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
276 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
279 #ifndef AUDIOUNIT_SUPPORT
283 _pluginui = create_au_gui (insert, &box);
284 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
287 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
295 PluginUIWindow::app_activated (bool yn)
297 PluginUIWindow::app_activated (bool)
300 #ifdef AUDIOUNIT_SUPPORT
304 _pluginui->activate ();
305 if (pre_deactivate_x >= 0) {
306 move (pre_deactivate_x, pre_deactivate_y);
312 was_visible = is_visible();
313 get_position (pre_deactivate_x, pre_deactivate_y);
315 _pluginui->deactivate ();
322 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
325 boost::shared_ptr<LV2Plugin> vp;
327 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
328 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
329 throw failed_constructor ();
331 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
334 lpu->package (*this);
344 PluginUIWindow::keyboard_focused (bool yn)
346 _keyboard_focused = yn;
350 PluginUIWindow::on_key_press_event (GdkEventKey* event)
352 if (_keyboard_focused) {
354 if (_pluginui->non_gtk_gui()) {
355 _pluginui->forward_key_event (event);
357 return relay_key_press (event, this);
362 /* for us to be getting key press events, there really
363 MUST be a _pluginui, but just to be safe, check ...
367 if (_pluginui->non_gtk_gui()) {
368 /* pass editor window as the window for the event
369 to be handled in, not this one, because there are
370 no widgets in this window that we want to have
373 return relay_key_press (event, &PublicEditor::instance());
375 return relay_key_press (event, this);
384 PluginUIWindow::on_key_release_event (GdkEventKey *event)
386 if (_keyboard_focused) {
388 if (_pluginui->non_gtk_gui()) {
389 _pluginui->forward_key_event (event);
400 PluginUIWindow::plugin_going_away ()
402 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
405 _pluginui->stop_updating(0);
408 death_connection.disconnect ();
411 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
413 , plugin (insert->plugin())
414 , add_button (_("Add"))
415 , save_button (_("Save"))
416 , delete_button (_("Delete"))
417 , bypass_button (ArdourButton::led_default_elements)
418 , description_expander (_("Description"))
419 , plugin_analysis_expander (_("Plugin analysis"))
424 _preset_modified.set_size_request (16, -1);
425 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
426 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
427 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
428 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
429 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
430 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
433 update_preset_list ();
436 add_button.set_name ("PluginAddButton");
437 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
439 save_button.set_name ("PluginSaveButton");
440 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
442 delete_button.set_name ("PluginDeleteButton");
443 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
445 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
447 bypass_button.set_name ("plugin bypass button");
448 bypass_button.set_text (_("Bypass"));
449 bypass_button.set_active (!pi->active());
450 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
451 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
453 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
454 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
456 /* these images are not managed, so that we can remove them at will */
458 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
459 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
461 focus_button.add (*focus_out_image);
463 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));
464 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
466 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
467 description_expander.set_expanded(false);
469 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
470 plugin_analysis_expander.set_expanded(false);
472 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
474 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
475 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
476 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
477 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
480 PlugUIBase::~PlugUIBase()
487 PlugUIBase::plugin_going_away ()
489 /* drop references to the plugin/insert */
495 PlugUIBase::set_latency_label ()
497 framecnt_t const l = insert->effective_latency ();
498 framecnt_t const sr = insert->session().frame_rate ();
503 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
505 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
508 latency_label.set_text (t);
512 PlugUIBase::latency_button_clicked ()
515 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
516 latency_dialog = new ArdourWindow (_("Edit Latency"));
517 /* use both keep-above and transient for to try cover as many
518 different WM's as possible.
520 latency_dialog->set_keep_above (true);
521 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
523 latency_dialog->set_transient_for (*win);
525 latency_dialog->add (*latency_gui);
526 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
529 latency_dialog->show_all ();
533 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
535 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
536 boost::shared_ptr<Processor> p (weak_p.lock());
539 bypass_button.set_active (!p->active());
544 PlugUIBase::preset_selected ()
546 if (_no_load_preset) {
550 if (_preset_combo.get_active_text().length() > 0) {
551 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
553 plugin->load_preset (*pr);
555 warning << string_compose(_("Plugin preset %1 not found"),
556 _preset_combo.get_active_text()) << endmsg;
559 // blank selected = no preset
560 plugin->clear_preset();
564 #ifdef NO_PLUGIN_STATE
565 static bool seen_saving_message = false;
569 PlugUIBase::add_plugin_setting ()
571 #ifndef NO_PLUGIN_STATE
572 NewPluginPresetDialog d (plugin);
575 case Gtk::RESPONSE_ACCEPT:
576 if (d.name().empty()) {
581 plugin->remove_preset (d.name ());
584 Plugin::PresetRecord const r = plugin->save_preset (d.name());
585 if (!r.uri.empty ()) {
586 plugin->load_preset (r);
591 if (!seen_saving_message) {
592 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
595 seen_saving_message = true;
601 PlugUIBase::save_plugin_setting ()
603 #ifndef NO_PLUGIN_STATE
604 string const name = _preset_combo.get_active_text ();
605 plugin->remove_preset (name);
606 Plugin::PresetRecord const r = plugin->save_preset (name);
607 if (!r.uri.empty ()) {
608 plugin->load_preset (r);
611 if (!seen_saving_message) {
612 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
615 seen_saving_message = true;
621 PlugUIBase::delete_plugin_setting ()
623 #ifndef NO_PLUGIN_STATE
624 plugin->remove_preset (_preset_combo.get_active_text ());
626 if (!seen_saving_message) {
627 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
630 seen_saving_message = true;
636 PlugUIBase::bypass_button_release (GdkEventButton*)
638 bool view_says_bypassed = (bypass_button.active_state() != 0);
640 if (view_says_bypassed != insert->active()) {
641 if (view_says_bypassed) {
644 insert->deactivate ();
652 PlugUIBase::focus_toggled (GdkEventButton*)
654 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
655 Keyboard::the_keyboard().magic_widget_drop_focus();
656 focus_button.remove ();
657 focus_button.add (*focus_out_image);
658 focus_out_image->show ();
659 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));
660 KeyboardFocused (false);
662 Keyboard::the_keyboard().magic_widget_grab_focus();
663 focus_button.remove ();
664 focus_button.add (*focus_in_image);
665 focus_in_image->show ();
666 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
667 KeyboardFocused (true);
674 PlugUIBase::toggle_description()
676 if (description_expander.get_expanded() &&
677 !description_expander.get_child()) {
678 const std::string text = plugin->get_docs();
683 Gtk::Label* label = manage(new Gtk::Label(text));
684 label->set_line_wrap(true);
685 label->set_line_wrap_mode(Pango::WRAP_WORD);
686 description_expander.add(*label);
687 description_expander.show_all();
690 if (!description_expander.get_expanded()) {
691 description_expander.remove();
697 PlugUIBase::toggle_plugin_analysis()
699 if (plugin_analysis_expander.get_expanded() &&
700 !plugin_analysis_expander.get_child()) {
703 eqgui = new PluginEqGui (insert);
706 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
709 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
712 plugin_analysis_expander.add (*eqgui);
713 plugin_analysis_expander.show_all ();
714 eqgui->start_listening ();
717 if (!plugin_analysis_expander.get_expanded()) {
718 // Hide & remove from expander
721 eqgui->stop_listening ();
722 plugin_analysis_expander.remove();
724 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
727 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
733 PlugUIBase::update_preset_list ()
735 vector<string> preset_labels;
736 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
740 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
741 preset_labels.push_back (i->label);
744 preset_labels.push_back("");
746 set_popdown_strings (_preset_combo, preset_labels);
752 PlugUIBase::update_preset ()
754 Plugin::PresetRecord p = plugin->last_preset();
757 _preset_combo.set_active_text (p.label);
760 save_button.set_sensitive (!p.uri.empty() && p.user);
761 delete_button.set_sensitive (!p.uri.empty() && p.user);
763 update_preset_modified ();
767 PlugUIBase::update_preset_modified ()
770 if (plugin->last_preset().uri.empty()) {
771 _preset_modified.set_text ("");
775 bool const c = plugin->parameter_changed_since_last_preset ();
776 if (_preset_modified.get_text().empty() == c) {
777 _preset_modified.set_text (c ? "*" : "");
782 PlugUIBase::parameter_changed (uint32_t, float)
784 update_preset_modified ();
788 PlugUIBase::preset_added_or_removed ()
790 /* Update both the list and the currently-displayed preset */
791 update_preset_list ();