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 << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
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 defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
230 if (pre_deactivate_x >= 0) {
231 move (pre_deactivate_x, pre_deactivate_y);
235 if (_pluginui->on_window_show (_title)) {
241 // set_transient_for (*parent);
246 PluginUIWindow::on_hide ()
248 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
249 get_position (pre_deactivate_x, pre_deactivate_y);
255 _pluginui->on_window_hide ();
260 PluginUIWindow::set_title(const std::string& title)
262 Gtk::Window::set_title(title);
267 #ifdef WINDOWS_VST_SUPPORT
268 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
270 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
273 #ifndef WINDOWS_VST_SUPPORT
277 boost::shared_ptr<WindowsVSTPlugin> vp;
279 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
280 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
282 throw failed_constructor ();
284 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp);
287 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
289 vpu->package (*this);
298 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
300 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
303 #ifndef LXVST_SUPPORT
307 boost::shared_ptr<LXVSTPlugin> lxvp;
309 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
310 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
312 throw failed_constructor ();
314 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
317 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
319 lxvpu->package (*this);
327 #ifdef AUDIOUNIT_SUPPORT
328 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
330 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
333 #ifndef AUDIOUNIT_SUPPORT
337 _pluginui = create_au_gui (insert, &box);
338 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
341 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
349 PluginUIWindow::app_activated (bool yn)
351 PluginUIWindow::app_activated (bool)
354 #ifdef AUDIOUNIT_SUPPORT
358 _pluginui->activate ();
359 if (pre_deactivate_x >= 0) {
360 move (pre_deactivate_x, pre_deactivate_y);
366 was_visible = is_visible();
367 get_position (pre_deactivate_x, pre_deactivate_y);
369 _pluginui->deactivate ();
376 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
379 boost::shared_ptr<LV2Plugin> vp;
381 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
382 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
383 throw failed_constructor ();
385 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
388 lpu->package (*this);
398 PluginUIWindow::keyboard_focused (bool yn)
400 _keyboard_focused = yn;
404 PluginUIWindow::on_key_press_event (GdkEventKey* event)
406 if (_keyboard_focused) {
408 if (_pluginui->non_gtk_gui()) {
409 _pluginui->forward_key_event (event);
411 return relay_key_press (event, this);
416 /* for us to be getting key press events, there really
417 MUST be a _pluginui, but just to be safe, check ...
421 if (_pluginui->non_gtk_gui()) {
422 /* pass editor window as the window for the event
423 to be handled in, not this one, because there are
424 no widgets in this window that we want to have
427 return relay_key_press (event, &PublicEditor::instance());
429 return relay_key_press (event, this);
438 PluginUIWindow::on_key_release_event (GdkEventKey *event)
440 if (_keyboard_focused) {
442 if (_pluginui->non_gtk_gui()) {
443 _pluginui->forward_key_event (event);
454 PluginUIWindow::plugin_going_away ()
456 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
459 _pluginui->stop_updating(0);
462 death_connection.disconnect ();
464 delete_when_idle (this);
467 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
469 , plugin (insert->plugin())
470 , add_button (_("Add"))
471 , save_button (_("Save"))
472 , delete_button (_("Delete"))
473 , bypass_button (ArdourButton::led_default_elements)
474 , description_expander (_("Description"))
475 , plugin_analysis_expander (_("Plugin analysis"))
480 _preset_modified.set_size_request (16, -1);
481 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
482 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
483 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
484 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
485 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
486 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
489 update_preset_list ();
492 add_button.set_name ("PluginAddButton");
493 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
495 save_button.set_name ("PluginSaveButton");
496 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
498 delete_button.set_name ("PluginDeleteButton");
499 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
501 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
503 bypass_button.set_name ("plugin bypass button");
504 bypass_button.set_text (_("Bypass"));
505 bypass_button.set_active (!pi->active());
506 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release));
507 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
509 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
510 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
512 /* these images are not managed, so that we can remove them at will */
514 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
515 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
517 focus_button.add (*focus_out_image);
519 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));
520 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
522 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
523 description_expander.set_expanded(false);
525 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
526 plugin_analysis_expander.set_expanded(false);
528 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
530 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
531 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
532 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
533 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
536 PlugUIBase::~PlugUIBase()
543 PlugUIBase::plugin_going_away ()
545 /* drop references to the plugin/insert */
551 PlugUIBase::set_latency_label ()
553 framecnt_t const l = insert->effective_latency ();
554 framecnt_t const sr = insert->session().frame_rate ();
559 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
561 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
564 latency_label.set_text (t);
568 PlugUIBase::latency_button_clicked ()
571 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
572 latency_dialog = new ArdourWindow (_("Edit Latency"));
573 latency_dialog->set_position (WIN_POS_MOUSE);
574 /* use both keep-above and transient for to try cover as many
575 different WM's as possible.
577 latency_dialog->set_keep_above (true);
578 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
580 latency_dialog->set_transient_for (*win);
582 latency_dialog->add (*latency_gui);
583 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
586 latency_dialog->show_all ();
590 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
592 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
593 boost::shared_ptr<Processor> p (weak_p.lock());
596 bypass_button.set_active (!p->active());
601 PlugUIBase::preset_selected ()
603 if (_no_load_preset) {
607 if (_preset_combo.get_active_text().length() > 0) {
608 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
610 plugin->load_preset (*pr);
612 warning << string_compose(_("Plugin preset %1 not found"),
613 _preset_combo.get_active_text()) << endmsg;
616 // blank selected = no preset
617 plugin->clear_preset();
622 PlugUIBase::add_plugin_setting ()
624 NewPluginPresetDialog d (plugin);
627 case Gtk::RESPONSE_ACCEPT:
628 if (d.name().empty()) {
633 plugin->remove_preset (d.name ());
636 Plugin::PresetRecord const r = plugin->save_preset (d.name());
637 if (!r.uri.empty ()) {
638 plugin->load_preset (r);
645 PlugUIBase::save_plugin_setting ()
647 string const name = _preset_combo.get_active_text ();
648 plugin->remove_preset (name);
649 Plugin::PresetRecord const r = plugin->save_preset (name);
650 if (!r.uri.empty ()) {
651 plugin->load_preset (r);
656 PlugUIBase::delete_plugin_setting ()
658 plugin->remove_preset (_preset_combo.get_active_text ());
662 PlugUIBase::bypass_button_release (GdkEventButton*)
664 bool view_says_bypassed = (bypass_button.active_state() != 0);
666 if (view_says_bypassed != insert->active()) {
667 if (view_says_bypassed) {
670 insert->deactivate ();
678 PlugUIBase::focus_toggled (GdkEventButton*)
680 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
681 Keyboard::the_keyboard().magic_widget_drop_focus();
682 focus_button.remove ();
683 focus_button.add (*focus_out_image);
684 focus_out_image->show ();
685 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));
686 KeyboardFocused (false);
688 Keyboard::the_keyboard().magic_widget_grab_focus();
689 focus_button.remove ();
690 focus_button.add (*focus_in_image);
691 focus_in_image->show ();
692 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
693 KeyboardFocused (true);
700 PlugUIBase::toggle_description()
702 if (description_expander.get_expanded() &&
703 !description_expander.get_child()) {
704 const std::string text = plugin->get_docs();
709 Gtk::Label* label = manage(new Gtk::Label(text));
710 label->set_line_wrap(true);
711 label->set_line_wrap_mode(Pango::WRAP_WORD);
712 description_expander.add(*label);
713 description_expander.show_all();
716 if (!description_expander.get_expanded()) {
717 description_expander.remove();
723 PlugUIBase::toggle_plugin_analysis()
725 if (plugin_analysis_expander.get_expanded() &&
726 !plugin_analysis_expander.get_child()) {
729 eqgui = new PluginEqGui (insert);
732 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
735 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
738 plugin_analysis_expander.add (*eqgui);
739 plugin_analysis_expander.show_all ();
740 eqgui->start_listening ();
743 if (!plugin_analysis_expander.get_expanded()) {
744 // Hide & remove from expander
747 eqgui->stop_listening ();
748 plugin_analysis_expander.remove();
750 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
753 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
759 PlugUIBase::update_preset_list ()
761 vector<string> preset_labels;
762 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
766 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
767 preset_labels.push_back (i->label);
770 preset_labels.push_back("");
772 set_popdown_strings (_preset_combo, preset_labels);
778 PlugUIBase::update_preset ()
780 Plugin::PresetRecord p = plugin->last_preset();
783 _preset_combo.set_active_text (p.label);
786 save_button.set_sensitive (!p.uri.empty() && p.user);
787 delete_button.set_sensitive (!p.uri.empty() && p.user);
789 update_preset_modified ();
793 PlugUIBase::update_preset_modified ()
796 if (plugin->last_preset().uri.empty()) {
797 _preset_modified.set_text ("");
801 bool const c = plugin->parameter_changed_since_last_preset ();
802 if (_preset_modified.get_text().empty() == c) {
803 _preset_modified.set_text (c ? "*" : "");
808 PlugUIBase::parameter_changed (uint32_t, float)
810 update_preset_modified ();
814 PlugUIBase::preset_added_or_removed ()
816 /* Update both the list and the currently-displayed preset */
817 update_preset_list ();