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 (
86 boost::shared_ptr<PluginInsert> insert,
91 , _keyboard_focused (false)
92 #ifdef AUDIOUNIT_SUPPORT
93 , pre_deactivate_x (-1)
94 , pre_deactivate_y (-1)
98 bool have_gui = false;
100 Label* label = manage (new Label());
101 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
103 if (editor && insert->plugin()->has_editor()) {
104 switch (insert->type()) {
105 case ARDOUR::Windows_VST:
106 have_gui = create_windows_vst_editor (insert);
110 have_gui = create_lxvst_editor (insert);
113 case ARDOUR::AudioUnit:
114 have_gui = create_audiounit_editor (insert);
118 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
122 have_gui = create_lv2_editor (insert);
126 #ifndef WINDOWS_VST_SUPPORT
127 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
130 error << _("unknown type of editor-supplying plugin")
133 throw failed_constructor ();
139 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
142 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
144 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
146 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
147 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
150 // set_position (Gtk::WIN_POS_MOUSE);
151 set_name ("PluginEditor");
152 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
154 signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
155 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
157 gint h = _pluginui->get_preferred_height ();
158 gint w = _pluginui->get_preferred_width ();
161 if (h > 600) h = 600;
164 set_default_size (w, h);
165 set_resizable (_pluginui->resizable());
168 PluginUIWindow::~PluginUIWindow ()
174 PluginUIWindow::set_parent (Gtk::Window* win)
180 PluginUIWindow::on_map ()
183 set_keep_above (true);
187 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
189 Keyboard::the_keyboard().enter_window (ev, this);
194 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
196 Keyboard::the_keyboard().leave_window (ev, this);
201 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
203 Window::on_focus_in_event (ev);
204 //Keyboard::the_keyboard().magic_widget_grab_focus ();
209 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
211 Window::on_focus_out_event (ev);
212 //Keyboard::the_keyboard().magic_widget_drop_focus ();
217 PluginUIWindow::on_show ()
219 set_role("plugin_ui");
222 _pluginui->update_preset_list ();
223 _pluginui->update_preset ();
227 if (_pluginui->on_window_show (_title)) {
233 // set_transient_for (*parent);
238 PluginUIWindow::on_hide ()
243 _pluginui->on_window_hide ();
248 PluginUIWindow::set_title(const std::string& title)
250 Gtk::Window::set_title(title);
255 #ifdef WINDOWS_VST_SUPPORT
256 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
258 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
261 #ifndef WINDOWS_VST_SUPPORT
265 boost::shared_ptr<WindowsVSTPlugin> vp;
267 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
268 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
270 throw failed_constructor ();
272 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp);
275 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
277 vpu->package (*this);
286 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
288 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
291 #ifndef LXVST_SUPPORT
295 boost::shared_ptr<LXVSTPlugin> lxvp;
297 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
298 error << _("unknown type of editor-supplying plugin (note: no linuxVST support in this version of ardour)")
300 throw failed_constructor ();
302 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
305 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
307 lxvpu->package (*this);
315 #ifdef AUDIOUNIT_SUPPORT
316 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
318 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
321 #ifndef AUDIOUNIT_SUPPORT
325 _pluginui = create_au_gui (insert, &box);
326 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
329 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
337 PluginUIWindow::app_activated (bool yn)
339 PluginUIWindow::app_activated (bool)
342 #ifdef AUDIOUNIT_SUPPORT
346 _pluginui->activate ();
347 if (pre_deactivate_x >= 0) {
348 move (pre_deactivate_x, pre_deactivate_y);
354 was_visible = is_visible();
355 get_position (pre_deactivate_x, pre_deactivate_y);
357 _pluginui->deactivate ();
364 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
367 boost::shared_ptr<LV2Plugin> vp;
369 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
370 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
371 throw failed_constructor ();
373 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
376 lpu->package (*this);
386 PluginUIWindow::keyboard_focused (bool yn)
388 _keyboard_focused = yn;
392 PluginUIWindow::on_key_press_event (GdkEventKey* event)
394 if (_keyboard_focused) {
396 if (_pluginui->non_gtk_gui()) {
397 _pluginui->forward_key_event (event);
399 return relay_key_press (event, this);
404 /* for us to be getting key press events, there really
405 MUST be a _pluginui, but just to be safe, check ...
409 if (_pluginui->non_gtk_gui()) {
410 /* pass editor window as the window for the event
411 to be handled in, not this one, because there are
412 no widgets in this window that we want to have
415 return relay_key_press (event, &PublicEditor::instance());
417 return relay_key_press (event, this);
426 PluginUIWindow::on_key_release_event (GdkEventKey *event)
428 if (_keyboard_focused) {
430 if (_pluginui->non_gtk_gui()) {
431 _pluginui->forward_key_event (event);
442 PluginUIWindow::plugin_going_away ()
444 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
447 _pluginui->stop_updating(0);
450 death_connection.disconnect ();
452 delete_when_idle (this);
455 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
457 , plugin (insert->plugin())
458 , add_button (_("Add"))
459 , save_button (_("Save"))
460 , delete_button (_("Delete"))
461 , bypass_button (ArdourButton::led_default_elements)
462 , description_expander (_("Description"))
463 , plugin_analysis_expander (_("Plugin analysis"))
468 _preset_modified.set_size_request (16, -1);
469 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
470 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
471 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
472 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
473 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
474 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
477 update_preset_list ();
480 add_button.set_name ("PluginAddButton");
481 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
483 save_button.set_name ("PluginSaveButton");
484 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
486 delete_button.set_name ("PluginDeleteButton");
487 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
489 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
491 bypass_button.set_name ("plugin bypass button");
492 bypass_button.set_text (_("Bypass"));
493 bypass_button.set_active (!pi->active());
494 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
495 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
497 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
498 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
500 /* these images are not managed, so that we can remove them at will */
502 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
503 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
505 focus_button.add (*focus_out_image);
507 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));
508 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
510 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
511 description_expander.set_expanded(false);
513 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
514 plugin_analysis_expander.set_expanded(false);
516 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
518 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
519 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
520 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
521 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
524 PlugUIBase::~PlugUIBase()
531 PlugUIBase::plugin_going_away ()
533 /* drop references to the plugin/insert */
539 PlugUIBase::set_latency_label ()
541 framecnt_t const l = insert->effective_latency ();
542 framecnt_t const sr = insert->session().frame_rate ();
547 t = string_compose (_("latency (%1 samples)"), l);
549 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
552 latency_label.set_text (t);
556 PlugUIBase::latency_button_clicked ()
559 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
560 latency_dialog = new ArdourWindow (_("Edit Latency"));
561 latency_dialog->set_position (WIN_POS_MOUSE);
562 /* use both keep-above and transient for to try cover as many
563 different WM's as possible.
565 latency_dialog->set_keep_above (true);
566 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
568 latency_dialog->set_transient_for (*win);
570 latency_dialog->add (*latency_gui);
571 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
574 latency_dialog->show_all ();
578 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
580 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
581 boost::shared_ptr<Processor> p (weak_p.lock());
584 bypass_button.set_active (!p->active());
589 PlugUIBase::preset_selected ()
591 if (_no_load_preset) {
595 if (_preset_combo.get_active_text().length() > 0) {
596 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
598 plugin->load_preset (*pr);
600 warning << string_compose(_("Plugin preset %1 not found"),
601 _preset_combo.get_active_text()) << endmsg;
604 // blank selected = no preset
605 plugin->clear_preset();
610 PlugUIBase::add_plugin_setting ()
612 NewPluginPresetDialog d (plugin);
615 case Gtk::RESPONSE_ACCEPT:
616 if (d.name().empty()) {
621 plugin->remove_preset (d.name ());
624 Plugin::PresetRecord const r = plugin->save_preset (d.name());
625 if (!r.uri.empty ()) {
626 plugin->load_preset (r);
633 PlugUIBase::save_plugin_setting ()
635 string const name = _preset_combo.get_active_text ();
636 plugin->remove_preset (name);
637 Plugin::PresetRecord const r = plugin->save_preset (name);
638 if (!r.uri.empty ()) {
639 plugin->load_preset (r);
644 PlugUIBase::delete_plugin_setting ()
646 plugin->remove_preset (_preset_combo.get_active_text ());
650 PlugUIBase::bypass_button_release (GdkEventButton*)
652 bool view_says_bypassed = (bypass_button.active_state() != 0);
654 if (view_says_bypassed != insert->active()) {
655 if (view_says_bypassed) {
658 insert->deactivate ();
666 PlugUIBase::focus_toggled (GdkEventButton*)
668 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
669 Keyboard::the_keyboard().magic_widget_drop_focus();
670 focus_button.remove ();
671 focus_button.add (*focus_out_image);
672 focus_out_image->show ();
673 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));
674 KeyboardFocused (false);
676 Keyboard::the_keyboard().magic_widget_grab_focus();
677 focus_button.remove ();
678 focus_button.add (*focus_in_image);
679 focus_in_image->show ();
680 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
681 KeyboardFocused (true);
688 PlugUIBase::toggle_description()
690 if (description_expander.get_expanded() &&
691 !description_expander.get_child()) {
692 const std::string text = plugin->get_docs();
697 Gtk::Label* label = manage(new Gtk::Label(text));
698 label->set_line_wrap(true);
699 label->set_line_wrap_mode(Pango::WRAP_WORD);
700 description_expander.add(*label);
701 description_expander.show_all();
704 if (!description_expander.get_expanded()) {
705 description_expander.remove();
711 PlugUIBase::toggle_plugin_analysis()
713 if (plugin_analysis_expander.get_expanded() &&
714 !plugin_analysis_expander.get_child()) {
717 eqgui = new PluginEqGui (insert);
720 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
723 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
726 plugin_analysis_expander.add (*eqgui);
727 plugin_analysis_expander.show_all ();
728 eqgui->start_listening ();
731 if (!plugin_analysis_expander.get_expanded()) {
732 // Hide & remove from expander
735 eqgui->stop_listening ();
736 plugin_analysis_expander.remove();
738 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
741 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
747 PlugUIBase::update_preset_list ()
749 vector<string> preset_labels;
750 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
754 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
755 preset_labels.push_back (i->label);
758 preset_labels.push_back("");
760 set_popdown_strings (_preset_combo, preset_labels);
766 PlugUIBase::update_preset ()
768 Plugin::PresetRecord p = plugin->last_preset();
771 _preset_combo.set_active_text (p.label);
774 save_button.set_sensitive (!p.uri.empty() && p.user);
775 delete_button.set_sensitive (!p.uri.empty() && p.user);
777 update_preset_modified ();
781 PlugUIBase::update_preset_modified ()
784 if (plugin->last_preset().uri.empty()) {
785 _preset_modified.set_text ("");
789 bool const c = plugin->parameter_changed_since_last_preset ();
790 if (_preset_modified.get_text().empty() == c) {
791 _preset_modified.set_text (c ? "*" : "");
796 PlugUIBase::parameter_changed (uint32_t, float)
798 update_preset_modified ();
802 PlugUIBase::preset_added_or_removed ()
804 /* Update both the list and the currently-displayed preset */
805 update_preset_list ();