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;
76 using namespace ARDOUR_UI_UTILS;
78 using namespace Gtkmm2ext;
81 PluginUIWindow::PluginUIWindow (
82 boost::shared_ptr<PluginInsert> insert,
85 : ArdourWindow (string())
87 , _keyboard_focused (false)
88 #ifdef AUDIOUNIT_SUPPORT
89 , pre_deactivate_x (-1)
90 , pre_deactivate_y (-1)
94 bool have_gui = false;
95 Label* label = manage (new Label());
96 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
98 if (editor && insert->plugin()->has_editor()) {
99 switch (insert->type()) {
100 case ARDOUR::Windows_VST:
101 have_gui = create_windows_vst_editor (insert);
105 have_gui = create_lxvst_editor (insert);
108 case ARDOUR::AudioUnit:
109 have_gui = create_audiounit_editor (insert);
113 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
117 have_gui = create_lv2_editor (insert);
121 #ifndef WINDOWS_VST_SUPPORT
122 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
125 error << _("unknown type of editor-supplying plugin")
128 throw failed_constructor ();
134 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
137 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
139 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
141 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
142 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
145 set_name ("PluginEditor");
146 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
148 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
150 gint h = _pluginui->get_preferred_height ();
151 gint w = _pluginui->get_preferred_width ();
154 if (h > 600) h = 600;
157 set_default_size (w, h);
158 set_resizable (_pluginui->resizable());
161 PluginUIWindow::~PluginUIWindow ()
164 cerr << "PluginWindow deleted for " << this << endl;
170 PluginUIWindow::on_show ()
172 set_role("plugin_ui");
175 _pluginui->update_preset_list ();
176 _pluginui->update_preset ();
180 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
181 if (pre_deactivate_x >= 0) {
182 move (pre_deactivate_x, pre_deactivate_y);
186 if (_pluginui->on_window_show (_title)) {
193 PluginUIWindow::on_hide ()
195 #if defined (HAVE_AUDIOUNITS) && defined(GTKOSX)
196 get_position (pre_deactivate_x, pre_deactivate_y);
202 _pluginui->on_window_hide ();
207 PluginUIWindow::set_title(const std::string& title)
209 Gtk::Window::set_title(title);
214 #ifdef WINDOWS_VST_SUPPORT
215 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
217 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
220 #ifndef WINDOWS_VST_SUPPORT
224 boost::shared_ptr<WindowsVSTPlugin> vp;
226 if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
227 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
229 throw failed_constructor ();
231 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
234 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
236 vpu->package (*this);
245 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
247 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
250 #ifndef LXVST_SUPPORT
254 boost::shared_ptr<LXVSTPlugin> lxvp;
256 if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
257 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
259 throw failed_constructor ();
261 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
264 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
266 lxvpu->package (*this);
274 #ifdef AUDIOUNIT_SUPPORT
275 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
277 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
280 #ifndef AUDIOUNIT_SUPPORT
284 _pluginui = create_au_gui (insert, &box);
285 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
288 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
296 PluginUIWindow::app_activated (bool yn)
298 PluginUIWindow::app_activated (bool)
301 #ifdef AUDIOUNIT_SUPPORT
305 _pluginui->activate ();
306 if (pre_deactivate_x >= 0) {
307 move (pre_deactivate_x, pre_deactivate_y);
313 was_visible = is_visible();
314 get_position (pre_deactivate_x, pre_deactivate_y);
316 _pluginui->deactivate ();
323 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
326 boost::shared_ptr<LV2Plugin> vp;
328 if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
329 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
330 throw failed_constructor ();
332 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
335 lpu->package (*this);
345 PluginUIWindow::keyboard_focused (bool yn)
347 _keyboard_focused = yn;
351 PluginUIWindow::on_key_press_event (GdkEventKey* event)
353 if (_keyboard_focused) {
355 if (_pluginui->non_gtk_gui()) {
356 _pluginui->forward_key_event (event);
358 return relay_key_press (event, this);
363 /* for us to be getting key press events, there really
364 MUST be a _pluginui, but just to be safe, check ...
368 if (_pluginui->non_gtk_gui()) {
369 /* pass editor window as the window for the event
370 to be handled in, not this one, because there are
371 no widgets in this window that we want to have
374 return relay_key_press (event, &PublicEditor::instance());
376 return relay_key_press (event, this);
385 PluginUIWindow::on_key_release_event (GdkEventKey *event)
387 if (_keyboard_focused) {
389 if (_pluginui->non_gtk_gui()) {
390 _pluginui->forward_key_event (event);
401 PluginUIWindow::plugin_going_away ()
403 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
406 _pluginui->stop_updating(0);
409 death_connection.disconnect ();
412 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
414 , plugin (insert->plugin())
415 , add_button (_("Add"))
416 , save_button (_("Save"))
417 , delete_button (_("Delete"))
418 , bypass_button (ArdourButton::led_default_elements)
419 , description_expander (_("Description"))
420 , plugin_analysis_expander (_("Plugin analysis"))
425 _preset_modified.set_size_request (16, -1);
426 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
427 ARDOUR_UI::instance()->set_tip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
428 ARDOUR_UI::instance()->set_tip (add_button, _("Save a new preset"));
429 ARDOUR_UI::instance()->set_tip (save_button, _("Save the current preset"));
430 ARDOUR_UI::instance()->set_tip (delete_button, _("Delete the current preset"));
431 ARDOUR_UI::instance()->set_tip (bypass_button, _("Disable signal processing by the plugin"));
434 update_preset_list ();
437 add_button.set_name ("PluginAddButton");
438 add_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
440 save_button.set_name ("PluginSaveButton");
441 save_button.signal_clicked().connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
443 delete_button.set_name ("PluginDeleteButton");
444 delete_button.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
446 insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this, boost::weak_ptr<Processor>(insert)), gui_context());
448 bypass_button.set_name ("plugin bypass button");
449 bypass_button.set_text (_("Bypass"));
450 bypass_button.set_active (!pi->active());
451 bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
452 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
454 focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
455 focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
457 /* these images are not managed, so that we can remove them at will */
459 focus_out_image = new Image (get_icon (X_("computer_keyboard")));
460 focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
462 focus_button.add (*focus_out_image);
464 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));
465 ARDOUR_UI::instance()->set_tip (bypass_button, _("Click to enable/disable this plugin"));
467 description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
468 description_expander.set_expanded(false);
470 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
471 plugin_analysis_expander.set_expanded(false);
473 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
475 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
476 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
477 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
478 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
481 PlugUIBase::~PlugUIBase()
488 PlugUIBase::plugin_going_away ()
490 /* drop references to the plugin/insert */
496 PlugUIBase::set_latency_label ()
498 framecnt_t const l = insert->effective_latency ();
499 framecnt_t const sr = insert->session().frame_rate ();
504 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
506 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
509 latency_label.set_text (t);
513 PlugUIBase::latency_button_clicked ()
516 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
517 latency_dialog = new ArdourWindow (_("Edit Latency"));
518 /* use both keep-above and transient for to try cover as many
519 different WM's as possible.
521 latency_dialog->set_keep_above (true);
522 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
524 latency_dialog->set_transient_for (*win);
526 latency_dialog->add (*latency_gui);
527 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
530 latency_dialog->show_all ();
534 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
536 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
537 boost::shared_ptr<Processor> p (weak_p.lock());
540 bypass_button.set_active (!p->active());
545 PlugUIBase::preset_selected ()
547 if (_no_load_preset) {
551 if (_preset_combo.get_active_text().length() > 0) {
552 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
554 plugin->load_preset (*pr);
556 warning << string_compose(_("Plugin preset %1 not found"),
557 _preset_combo.get_active_text()) << endmsg;
560 // blank selected = no preset
561 plugin->clear_preset();
565 #ifdef NO_PLUGIN_STATE
566 static bool seen_saving_message = false;
570 PlugUIBase::add_plugin_setting ()
572 #ifndef NO_PLUGIN_STATE
573 NewPluginPresetDialog d (plugin);
576 case Gtk::RESPONSE_ACCEPT:
577 if (d.name().empty()) {
582 plugin->remove_preset (d.name ());
585 Plugin::PresetRecord const r = plugin->save_preset (d.name());
586 if (!r.uri.empty ()) {
587 plugin->load_preset (r);
592 if (!seen_saving_message) {
593 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
596 seen_saving_message = true;
602 PlugUIBase::save_plugin_setting ()
604 #ifndef NO_PLUGIN_STATE
605 string const name = _preset_combo.get_active_text ();
606 plugin->remove_preset (name);
607 Plugin::PresetRecord const r = plugin->save_preset (name);
608 if (!r.uri.empty ()) {
609 plugin->load_preset (r);
612 if (!seen_saving_message) {
613 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
616 seen_saving_message = true;
622 PlugUIBase::delete_plugin_setting ()
624 #ifndef NO_PLUGIN_STATE
625 plugin->remove_preset (_preset_combo.get_active_text ());
627 if (!seen_saving_message) {
628 info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a newer version"),
631 seen_saving_message = true;
637 PlugUIBase::bypass_button_release (GdkEventButton*)
639 bool view_says_bypassed = (bypass_button.active_state() != 0);
641 if (view_says_bypassed != insert->active()) {
642 if (view_says_bypassed) {
645 insert->deactivate ();
653 PlugUIBase::focus_toggled (GdkEventButton*)
655 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
656 Keyboard::the_keyboard().magic_widget_drop_focus();
657 focus_button.remove ();
658 focus_button.add (*focus_out_image);
659 focus_out_image->show ();
660 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));
661 KeyboardFocused (false);
663 Keyboard::the_keyboard().magic_widget_grab_focus();
664 focus_button.remove ();
665 focus_button.add (*focus_in_image);
666 focus_in_image->show ();
667 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
668 KeyboardFocused (true);
675 PlugUIBase::toggle_description()
677 if (description_expander.get_expanded() &&
678 !description_expander.get_child()) {
679 const std::string text = plugin->get_docs();
684 Gtk::Label* label = manage(new Gtk::Label(text));
685 label->set_line_wrap(true);
686 label->set_line_wrap_mode(Pango::WRAP_WORD);
687 description_expander.add(*label);
688 description_expander.show_all();
691 if (!description_expander.get_expanded()) {
692 description_expander.remove();
698 PlugUIBase::toggle_plugin_analysis()
700 if (plugin_analysis_expander.get_expanded() &&
701 !plugin_analysis_expander.get_child()) {
704 eqgui = new PluginEqGui (insert);
707 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
710 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
713 plugin_analysis_expander.add (*eqgui);
714 plugin_analysis_expander.show_all ();
715 eqgui->start_listening ();
718 if (!plugin_analysis_expander.get_expanded()) {
719 // Hide & remove from expander
722 eqgui->stop_listening ();
723 plugin_analysis_expander.remove();
725 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
728 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
734 PlugUIBase::update_preset_list ()
736 vector<string> preset_labels;
737 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
741 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
742 preset_labels.push_back (i->label);
745 preset_labels.push_back("");
747 set_popdown_strings (_preset_combo, preset_labels);
753 PlugUIBase::update_preset ()
755 Plugin::PresetRecord p = plugin->last_preset();
758 _preset_combo.set_active_text (p.label);
761 save_button.set_sensitive (!p.uri.empty() && p.user);
762 delete_button.set_sensitive (!p.uri.empty() && p.user);
764 update_preset_modified ();
768 PlugUIBase::update_preset_modified ()
771 if (plugin->last_preset().uri.empty()) {
772 _preset_modified.set_text ("");
776 bool const c = plugin->parameter_changed_since_last_preset ();
777 if (_preset_modified.get_text().empty() == c) {
778 _preset_modified.set_text (c ? "*" : "");
783 PlugUIBase::parameter_changed (uint32_t, float)
785 update_preset_modified ();
789 PlugUIBase::preset_added_or_removed ()
791 /* Update both the list and the currently-displayed preset */
792 update_preset_list ();