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 ()
184 set_keep_above (true);
189 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
191 Keyboard::the_keyboard().enter_window (ev, this);
196 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
198 Keyboard::the_keyboard().leave_window (ev, this);
203 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
205 Window::on_focus_in_event (ev);
206 //Keyboard::the_keyboard().magic_widget_grab_focus ();
211 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
213 Window::on_focus_out_event (ev);
214 //Keyboard::the_keyboard().magic_widget_drop_focus ();
219 PluginUIWindow::on_show ()
221 set_role("plugin_ui");
224 _pluginui->update_preset_list ();
225 _pluginui->update_preset ();
229 if (_pluginui->on_window_show (_title)) {
235 // set_transient_for (*parent);
240 PluginUIWindow::on_hide ()
245 _pluginui->on_window_hide ();
250 PluginUIWindow::set_title(const std::string& title)
252 Gtk::Window::set_title(title);
257 #ifdef WINDOWS_VST_SUPPORT
258 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
260 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
263 #ifndef WINDOWS_VST_SUPPORT
267 boost::shared_ptr<WindowsVSTPlugin> vp;
269 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
270 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
272 throw failed_constructor ();
274 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp);
277 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
279 vpu->package (*this);
288 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
290 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
293 #ifndef LXVST_SUPPORT
297 boost::shared_ptr<LXVSTPlugin> lxvp;
299 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
300 error << _("unknown type of editor-supplying plugin (note: no linuxVST support in this version of ardour)")
302 throw failed_constructor ();
304 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
307 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
309 lxvpu->package (*this);
317 #ifdef AUDIOUNIT_SUPPORT
318 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
320 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
323 #ifndef AUDIOUNIT_SUPPORT
327 _pluginui = create_au_gui (insert, &box);
328 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
331 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
339 PluginUIWindow::app_activated (bool yn)
341 PluginUIWindow::app_activated (bool)
344 #ifdef AUDIOUNIT_SUPPORT
348 _pluginui->activate ();
349 if (pre_deactivate_x >= 0) {
350 move (pre_deactivate_x, pre_deactivate_y);
356 was_visible = is_visible();
357 get_position (pre_deactivate_x, pre_deactivate_y);
359 _pluginui->deactivate ();
366 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
369 boost::shared_ptr<LV2Plugin> vp;
371 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
372 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
373 throw failed_constructor ();
375 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
378 lpu->package (*this);
388 PluginUIWindow::keyboard_focused (bool yn)
390 _keyboard_focused = yn;
394 PluginUIWindow::on_key_press_event (GdkEventKey* event)
396 if (_keyboard_focused) {
398 if (_pluginui->non_gtk_gui()) {
399 _pluginui->forward_key_event (event);
401 return relay_key_press (event, this);
406 /* for us to be getting key press events, there really
407 MUST be a _pluginui, but just to be safe, check ...
411 if (_pluginui->non_gtk_gui()) {
412 /* pass editor window as the window for the event
413 to be handled in, not this one, because there are
414 no widgets in this window that we want to have
417 return relay_key_press (event, &PublicEditor::instance());
419 return relay_key_press (event, this);
428 PluginUIWindow::on_key_release_event (GdkEventKey *event)
430 if (_keyboard_focused) {
432 if (_pluginui->non_gtk_gui()) {
433 _pluginui->forward_key_event (event);
444 PluginUIWindow::plugin_going_away ()
446 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
449 _pluginui->stop_updating(0);
452 death_connection.disconnect ();
454 delete_when_idle (this);
457 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
459 , plugin (insert->plugin())
460 , add_button (_("Add"))
461 , save_button (_("Save"))
462 , delete_button (_("Delete"))
463 , bypass_button (ArdourButton::led_default_elements)
464 , description_expander (_("Description"))
465 , plugin_analysis_expander (_("Plugin analysis"))
470 _preset_modified.set_size_request (16, -1);
471 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
472 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
473 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
474 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
475 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
476 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
479 update_preset_list ();
482 add_button.set_name ("PluginAddButton");
483 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
485 save_button.set_name ("PluginSaveButton");
486 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
488 delete_button.set_name ("PluginDeleteButton");
489 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
491 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
493 bypass_button.set_name ("plugin bypass button");
494 bypass_button.set_text (_("Bypass"));
495 bypass_button.set_active (!pi->active());
496 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
497 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
499 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
500 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
502 /* these images are not managed, so that we can remove them at will */
504 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
505 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
507 focus_button.add (*focus_out_image);
509 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));
510 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
512 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
513 description_expander.set_expanded(false);
515 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
516 plugin_analysis_expander.set_expanded(false);
518 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
520 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
521 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
522 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
523 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
526 PlugUIBase::~PlugUIBase()
533 PlugUIBase::plugin_going_away ()
535 /* drop references to the plugin/insert */
541 PlugUIBase::set_latency_label ()
543 framecnt_t const l = insert->effective_latency ();
544 framecnt_t const sr = insert->session().frame_rate ();
549 t = string_compose (_("latency (%1 samples)"), l);
551 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
554 latency_label.set_text (t);
558 PlugUIBase::latency_button_clicked ()
561 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
562 latency_dialog = new ArdourWindow (_("Edit Latency"));
563 latency_dialog->set_position (WIN_POS_MOUSE);
564 /* use both keep-above and transient for to try cover as many
565 different WM's as possible.
567 latency_dialog->set_keep_above (true);
568 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
570 latency_dialog->set_transient_for (*win);
572 latency_dialog->add (*latency_gui);
573 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
576 latency_dialog->show_all ();
580 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
582 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
583 boost::shared_ptr<Processor> p (weak_p.lock());
586 bypass_button.set_active (!p->active());
591 PlugUIBase::preset_selected ()
593 if (_no_load_preset) {
597 if (_preset_combo.get_active_text().length() > 0) {
598 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
600 plugin->load_preset (*pr);
602 warning << string_compose(_("Plugin preset %1 not found"),
603 _preset_combo.get_active_text()) << endmsg;
606 // blank selected = no preset
607 plugin->clear_preset();
612 PlugUIBase::add_plugin_setting ()
614 NewPluginPresetDialog d (plugin);
617 case Gtk::RESPONSE_ACCEPT:
618 if (d.name().empty()) {
623 plugin->remove_preset (d.name ());
626 Plugin::PresetRecord const r = plugin->save_preset (d.name());
627 if (!r.uri.empty ()) {
628 plugin->load_preset (r);
635 PlugUIBase::save_plugin_setting ()
637 string const name = _preset_combo.get_active_text ();
638 plugin->remove_preset (name);
639 Plugin::PresetRecord const r = plugin->save_preset (name);
640 if (!r.uri.empty ()) {
641 plugin->load_preset (r);
646 PlugUIBase::delete_plugin_setting ()
648 plugin->remove_preset (_preset_combo.get_active_text ());
652 PlugUIBase::bypass_button_release (GdkEventButton*)
654 bool view_says_bypassed = (bypass_button.active_state() != 0);
656 if (view_says_bypassed != insert->active()) {
657 if (view_says_bypassed) {
660 insert->deactivate ();
668 PlugUIBase::focus_toggled (GdkEventButton*)
670 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
671 Keyboard::the_keyboard().magic_widget_drop_focus();
672 focus_button.remove ();
673 focus_button.add (*focus_out_image);
674 focus_out_image->show ();
675 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));
676 KeyboardFocused (false);
678 Keyboard::the_keyboard().magic_widget_grab_focus();
679 focus_button.remove ();
680 focus_button.add (*focus_in_image);
681 focus_in_image->show ();
682 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
683 KeyboardFocused (true);
690 PlugUIBase::toggle_description()
692 if (description_expander.get_expanded() &&
693 !description_expander.get_child()) {
694 const std::string text = plugin->get_docs();
699 Gtk::Label* label = manage(new Gtk::Label(text));
700 label->set_line_wrap(true);
701 label->set_line_wrap_mode(Pango::WRAP_WORD);
702 description_expander.add(*label);
703 description_expander.show_all();
706 if (!description_expander.get_expanded()) {
707 description_expander.remove();
713 PlugUIBase::toggle_plugin_analysis()
715 if (plugin_analysis_expander.get_expanded() &&
716 !plugin_analysis_expander.get_child()) {
719 eqgui = new PluginEqGui (insert);
722 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
725 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
728 plugin_analysis_expander.add (*eqgui);
729 plugin_analysis_expander.show_all ();
730 eqgui->start_listening ();
733 if (!plugin_analysis_expander.get_expanded()) {
734 // Hide & remove from expander
737 eqgui->stop_listening ();
738 plugin_analysis_expander.remove();
740 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
743 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
749 PlugUIBase::update_preset_list ()
751 vector<string> preset_labels;
752 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
756 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
757 preset_labels.push_back (i->label);
760 preset_labels.push_back("");
762 set_popdown_strings (_preset_combo, preset_labels);
768 PlugUIBase::update_preset ()
770 Plugin::PresetRecord p = plugin->last_preset();
773 _preset_combo.set_active_text (p.label);
776 save_button.set_sensitive (!p.uri.empty() && p.user);
777 delete_button.set_sensitive (!p.uri.empty() && p.user);
779 update_preset_modified ();
783 PlugUIBase::update_preset_modified ()
786 if (plugin->last_preset().uri.empty()) {
787 _preset_modified.set_text ("");
791 bool const c = plugin->parameter_changed_since_last_preset ();
792 if (_preset_modified.get_text().empty() == c) {
793 _preset_modified.set_text (c ? "*" : "");
798 PlugUIBase::parameter_changed (uint32_t, float)
800 update_preset_modified ();
804 PlugUIBase::preset_added_or_removed ()
806 /* Update both the list and the currently-displayed preset */
807 update_preset_list ();