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 signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
152 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
154 gint h = _pluginui->get_preferred_height ();
155 gint w = _pluginui->get_preferred_width ();
158 if (h > 600) h = 600;
161 set_default_size (w, h);
162 set_resizable (_pluginui->resizable());
165 PluginUIWindow::~PluginUIWindow ()
171 PluginUIWindow::on_map ()
177 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
179 Keyboard::the_keyboard().enter_window (ev, this);
184 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
186 Keyboard::the_keyboard().leave_window (ev, this);
191 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
193 Window::on_focus_in_event (ev);
194 //Keyboard::the_keyboard().magic_widget_grab_focus ();
199 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
201 Window::on_focus_out_event (ev);
202 //Keyboard::the_keyboard().magic_widget_drop_focus ();
207 PluginUIWindow::on_show ()
209 set_role("plugin_ui");
212 _pluginui->update_preset_list ();
213 _pluginui->update_preset ();
217 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
218 if (pre_deactivate_x >= 0) {
219 move (pre_deactivate_x, pre_deactivate_y);
223 if (_pluginui->on_window_show (_title)) {
230 PluginUIWindow::on_hide ()
232 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
233 get_position (pre_deactivate_x, pre_deactivate_y);
239 _pluginui->on_window_hide ();
244 PluginUIWindow::set_title(const std::string& title)
246 Gtk::Window::set_title(title);
251 #ifdef WINDOWS_VST_SUPPORT
252 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
254 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
257 #ifndef WINDOWS_VST_SUPPORT
261 boost::shared_ptr<WindowsVSTPlugin> vp;
263 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
264 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
266 throw failed_constructor ();
268 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp);
271 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
273 vpu->package (*this);
282 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
284 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
287 #ifndef LXVST_SUPPORT
291 boost::shared_ptr<LXVSTPlugin> lxvp;
293 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
294 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
296 throw failed_constructor ();
298 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
301 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
303 lxvpu->package (*this);
311 #ifdef AUDIOUNIT_SUPPORT
312 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
314 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
317 #ifndef AUDIOUNIT_SUPPORT
321 _pluginui = create_au_gui (insert, &box);
322 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
325 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
333 PluginUIWindow::app_activated (bool yn)
335 PluginUIWindow::app_activated (bool)
338 #ifdef AUDIOUNIT_SUPPORT
342 _pluginui->activate ();
343 if (pre_deactivate_x >= 0) {
344 move (pre_deactivate_x, pre_deactivate_y);
350 was_visible = is_visible();
351 get_position (pre_deactivate_x, pre_deactivate_y);
353 _pluginui->deactivate ();
360 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
363 boost::shared_ptr<LV2Plugin> vp;
365 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
366 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
367 throw failed_constructor ();
369 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
372 lpu->package (*this);
382 PluginUIWindow::keyboard_focused (bool yn)
384 _keyboard_focused = yn;
388 PluginUIWindow::on_key_press_event (GdkEventKey* event)
390 if (_keyboard_focused) {
392 if (_pluginui->non_gtk_gui()) {
393 _pluginui->forward_key_event (event);
395 return relay_key_press (event, this);
400 /* for us to be getting key press events, there really
401 MUST be a _pluginui, but just to be safe, check ...
405 if (_pluginui->non_gtk_gui()) {
406 /* pass editor window as the window for the event
407 to be handled in, not this one, because there are
408 no widgets in this window that we want to have
411 return relay_key_press (event, &PublicEditor::instance());
413 return relay_key_press (event, this);
422 PluginUIWindow::on_key_release_event (GdkEventKey *event)
424 if (_keyboard_focused) {
426 if (_pluginui->non_gtk_gui()) {
427 _pluginui->forward_key_event (event);
438 PluginUIWindow::plugin_going_away ()
440 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
443 _pluginui->stop_updating(0);
446 death_connection.disconnect ();
448 delete_when_idle (this);
451 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
453 , plugin (insert->plugin())
454 , add_button (_("Add"))
455 , save_button (_("Save"))
456 , delete_button (_("Delete"))
457 , bypass_button (ArdourButton::led_default_elements)
458 , description_expander (_("Description"))
459 , plugin_analysis_expander (_("Plugin analysis"))
464 _preset_modified.set_size_request (16, -1);
465 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
466 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
467 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
468 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
469 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
470 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
473 update_preset_list ();
476 add_button.set_name ("PluginAddButton");
477 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
479 save_button.set_name ("PluginSaveButton");
480 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
482 delete_button.set_name ("PluginDeleteButton");
483 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
485 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
487 bypass_button.set_name ("plugin bypass button");
488 bypass_button.set_text (_("Bypass"));
489 bypass_button.set_active (!pi->active());
490 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
491 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
493 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
494 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
496 /* these images are not managed, so that we can remove them at will */
498 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
499 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
501 focus_button.add (*focus_out_image);
503 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));
504 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
506 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
507 description_expander.set_expanded(false);
509 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
510 plugin_analysis_expander.set_expanded(false);
512 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
514 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
515 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
516 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
517 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
520 PlugUIBase::~PlugUIBase()
527 PlugUIBase::plugin_going_away ()
529 /* drop references to the plugin/insert */
535 PlugUIBase::set_latency_label ()
537 framecnt_t const l = insert->effective_latency ();
538 framecnt_t const sr = insert->session().frame_rate ();
543 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
545 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
548 latency_label.set_text (t);
552 PlugUIBase::latency_button_clicked ()
555 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
556 latency_dialog = new ArdourWindow (_("Edit Latency"));
557 /* use both keep-above and transient for to try cover as many
558 different WM's as possible.
560 latency_dialog->set_keep_above (true);
561 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
563 latency_dialog->set_transient_for (*win);
565 latency_dialog->add (*latency_gui);
566 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
569 latency_dialog->show_all ();
573 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
575 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
576 boost::shared_ptr<Processor> p (weak_p.lock());
579 bypass_button.set_active (!p->active());
584 PlugUIBase::preset_selected ()
586 if (_no_load_preset) {
590 if (_preset_combo.get_active_text().length() > 0) {
591 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
593 plugin->load_preset (*pr);
595 warning << string_compose(_("Plugin preset %1 not found"),
596 _preset_combo.get_active_text()) << endmsg;
599 // blank selected = no preset
600 plugin->clear_preset();
605 PlugUIBase::add_plugin_setting ()
607 NewPluginPresetDialog d (plugin);
610 case Gtk::RESPONSE_ACCEPT:
611 if (d.name().empty()) {
616 plugin->remove_preset (d.name ());
619 Plugin::PresetRecord const r = plugin->save_preset (d.name());
620 if (!r.uri.empty ()) {
621 plugin->load_preset (r);
628 PlugUIBase::save_plugin_setting ()
630 string const name = _preset_combo.get_active_text ();
631 plugin->remove_preset (name);
632 Plugin::PresetRecord const r = plugin->save_preset (name);
633 if (!r.uri.empty ()) {
634 plugin->load_preset (r);
639 PlugUIBase::delete_plugin_setting ()
641 plugin->remove_preset (_preset_combo.get_active_text ());
645 PlugUIBase::bypass_button_release (GdkEventButton*)
647 bool view_says_bypassed = (bypass_button.active_state() != 0);
649 if (view_says_bypassed != insert->active()) {
650 if (view_says_bypassed) {
653 insert->deactivate ();
661 PlugUIBase::focus_toggled (GdkEventButton*)
663 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
664 Keyboard::the_keyboard().magic_widget_drop_focus();
665 focus_button.remove ();
666 focus_button.add (*focus_out_image);
667 focus_out_image->show ();
668 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));
669 KeyboardFocused (false);
671 Keyboard::the_keyboard().magic_widget_grab_focus();
672 focus_button.remove ();
673 focus_button.add (*focus_in_image);
674 focus_in_image->show ();
675 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
676 KeyboardFocused (true);
683 PlugUIBase::toggle_description()
685 if (description_expander.get_expanded() &&
686 !description_expander.get_child()) {
687 const std::string text = plugin->get_docs();
692 Gtk::Label* label = manage(new Gtk::Label(text));
693 label->set_line_wrap(true);
694 label->set_line_wrap_mode(Pango::WRAP_WORD);
695 description_expander.add(*label);
696 description_expander.show_all();
699 if (!description_expander.get_expanded()) {
700 description_expander.remove();
706 PlugUIBase::toggle_plugin_analysis()
708 if (plugin_analysis_expander.get_expanded() &&
709 !plugin_analysis_expander.get_child()) {
712 eqgui = new PluginEqGui (insert);
715 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
718 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
721 plugin_analysis_expander.add (*eqgui);
722 plugin_analysis_expander.show_all ();
723 eqgui->start_listening ();
726 if (!plugin_analysis_expander.get_expanded()) {
727 // Hide & remove from expander
730 eqgui->stop_listening ();
731 plugin_analysis_expander.remove();
733 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
736 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
742 PlugUIBase::update_preset_list ()
744 vector<string> preset_labels;
745 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
749 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
750 preset_labels.push_back (i->label);
753 preset_labels.push_back("");
755 set_popdown_strings (_preset_combo, preset_labels);
761 PlugUIBase::update_preset ()
763 Plugin::PresetRecord p = plugin->last_preset();
766 _preset_combo.set_active_text (p.label);
769 save_button.set_sensitive (!p.uri.empty() && p.user);
770 delete_button.set_sensitive (!p.uri.empty() && p.user);
772 update_preset_modified ();
776 PlugUIBase::update_preset_modified ()
779 if (plugin->last_preset().uri.empty()) {
780 _preset_modified.set_text ("");
784 bool const c = plugin->parameter_changed_since_last_preset ();
785 if (_preset_modified.get_text().empty() == c) {
786 _preset_modified.set_text (c ? "*" : "");
791 PlugUIBase::parameter_changed (uint32_t, float)
793 update_preset_modified ();
797 PlugUIBase::preset_added_or_removed ()
799 /* Update both the list and the currently-displayed preset */
800 update_preset_list ();