Zoom session when the mouse pointer is moved up and down during a playhead drag.
[ardour2.git] / gtk2_ardour / plugin_ui.cc
blob1e54344a83bdeb64e03f9890aa0fede1c283dd08
1 /*
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.
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
24 #include <climits>
25 #include <cerrno>
26 #include <cmath>
27 #include <string>
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 VST_SUPPORT
50 #include "ardour/vst_plugin.h"
51 #include "vst_pluginui.h"
52 #endif
53 #ifdef LV2_SUPPORT
54 #include "ardour/lv2_plugin.h"
55 #include "lv2_plugin_ui.h"
56 #endif
58 #include <lrdf.h>
60 #include "ardour_dialog.h"
61 #include "ardour_ui.h"
62 #include "prompter.h"
63 #include "plugin_ui.h"
64 #include "utils.h"
65 #include "gui_thread.h"
66 #include "public_editor.h"
67 #include "keyboard.h"
68 #include "latency_gui.h"
69 #include "plugin_eq_gui.h"
70 #include "new_plugin_preset_dialog.h"
72 #include "i18n.h"
74 using namespace std;
75 using namespace ARDOUR;
76 using namespace PBD;
77 using namespace Gtkmm2ext;
78 using namespace Gtk;
80 PluginUIWindow::PluginUIWindow (
81 Gtk::Window* win,
82 boost::shared_ptr<PluginInsert> insert,
83 bool scrollable,
84 bool editor)
85 : parent (win)
86 , was_visible (false)
87 , _keyboard_focused (false)
89 bool have_gui = false;
91 Label* label = manage (new Label());
92 label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
94 std::cout << "SHOW UI " << insert->plugin()->unique_id()
95 << " editor: " << editor << std::endl;
96 if (editor && insert->plugin()->has_editor()) {
97 switch (insert->type()) {
98 case ARDOUR::VST:
99 have_gui = create_vst_editor (insert);
100 break;
102 case ARDOUR::AudioUnit:
103 have_gui = create_audiounit_editor (insert);
104 break;
106 case ARDOUR::LADSPA:
107 error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
108 break;
110 case ARDOUR::LV2:
111 have_gui = create_lv2_editor (insert);
112 break;
114 default:
115 #ifndef VST_SUPPORT
116 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
117 << endmsg;
118 #else
119 error << _("unknown type of editor-supplying plugin")
120 << endmsg;
121 #endif
122 throw failed_constructor ();
127 if (!have_gui) {
128 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
130 _pluginui = pu;
131 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
132 add (*pu);
133 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
135 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
136 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
139 // set_position (Gtk::WIN_POS_MOUSE);
140 set_name ("PluginEditor");
141 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
143 signal_delete_event().connect (sigc::bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window*> (this)), false);
144 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
146 gint h = _pluginui->get_preferred_height ();
147 gint w = _pluginui->get_preferred_width ();
149 if (scrollable) {
150 if (h > 600) h = 600;
151 if (w > 600) w = 600;
153 if (w < 0) {
154 w = 450;
158 set_default_size (w, h);
161 PluginUIWindow::~PluginUIWindow ()
163 delete _pluginui;
166 void
167 PluginUIWindow::set_parent (Gtk::Window* win)
169 parent = win;
172 void
173 PluginUIWindow::on_map ()
175 Window::on_map ();
176 set_keep_above (true);
179 bool
180 PluginUIWindow::on_enter_notify_event (GdkEventCrossing *ev)
182 Keyboard::the_keyboard().enter_window (ev, this);
183 return false;
186 bool
187 PluginUIWindow::on_leave_notify_event (GdkEventCrossing *ev)
189 Keyboard::the_keyboard().leave_window (ev, this);
190 return false;
193 bool
194 PluginUIWindow::on_focus_in_event (GdkEventFocus *ev)
196 Window::on_focus_in_event (ev);
197 //Keyboard::the_keyboard().magic_widget_grab_focus ();
198 return false;
201 bool
202 PluginUIWindow::on_focus_out_event (GdkEventFocus *ev)
204 Window::on_focus_out_event (ev);
205 //Keyboard::the_keyboard().magic_widget_drop_focus ();
206 return false;
209 void
210 PluginUIWindow::on_show ()
212 set_role("plugin_ui");
214 if (_pluginui) {
215 _pluginui->update_preset_list ();
216 _pluginui->update_preset ();
219 if (_pluginui) {
220 if (_pluginui->on_window_show (_title)) {
221 Window::on_show ();
225 if (parent) {
226 // set_transient_for (*parent);
230 void
231 PluginUIWindow::on_hide ()
233 Window::on_hide ();
235 if (_pluginui) {
236 _pluginui->on_window_hide ();
240 void
241 PluginUIWindow::set_title(const std::string& title)
243 Gtk::Window::set_title(title);
244 _title = title;
247 bool
248 #ifdef VST_SUPPORT
249 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert> insert)
250 #else
251 PluginUIWindow::create_vst_editor(boost::shared_ptr<PluginInsert>)
252 #endif
254 #ifndef VST_SUPPORT
255 return false;
256 #else
258 boost::shared_ptr<VSTPlugin> vp;
260 if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) == 0) {
261 error << _("unknown type of editor-supplying plugin (note: no VST support in this version of ardour)")
262 << endmsg;
263 throw failed_constructor ();
264 } else {
265 VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
267 _pluginui = vpu;
268 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
269 add (*vpu);
270 vpu->package (*this);
273 return true;
274 #endif
277 bool
278 #ifdef GTKOSX
279 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
280 #else
281 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
282 #endif
284 #ifndef GTKOSX
285 return false;
286 #else
287 VBox* box;
288 _pluginui = create_au_gui (insert, &box);
289 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
290 add (*box);
292 Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
294 return true;
295 #endif
298 void
299 #ifdef GTKOSX
300 PluginUIWindow::app_activated (bool yn)
301 #else
302 PluginUIWindow::app_activated (bool)
303 #endif
305 #ifdef GTKOSX
306 if (_pluginui) {
307 if (yn) {
308 if (was_visible) {
309 _pluginui->activate ();
310 present ();
311 was_visible = true;
313 } else {
314 was_visible = is_visible();
315 hide ();
316 _pluginui->deactivate ();
319 #endif
322 bool
323 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
325 #if defined(HAVE_SLV2) || defined(HAVE_SUIL)
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 ();
331 } else {
332 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
333 _pluginui = lpu;
334 add (*lpu);
335 lpu->package (*this);
338 return true;
339 #else
340 return false;
341 #endif
344 void
345 PluginUIWindow::keyboard_focused (bool yn)
347 _keyboard_focused = yn;
350 bool
351 PluginUIWindow::on_key_press_event (GdkEventKey* event)
353 if (_keyboard_focused) {
354 if (_pluginui) {
355 if (_pluginui->non_gtk_gui()) {
356 _pluginui->forward_key_event (event);
357 } else {
358 return relay_key_press (event, this);
361 return true;
362 } else {
363 /* for us to be getting key press events, there really
364 MUST be a _pluginui, but just to be safe, check ...
367 if (_pluginui) {
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
372 key focus.
374 return relay_key_press (event, &PublicEditor::instance());
375 } else {
376 return relay_key_press (event, this);
378 } else {
379 return false;
384 bool
385 PluginUIWindow::on_key_release_event (GdkEventKey *event)
387 if (_keyboard_focused) {
388 if (_pluginui) {
389 if (_pluginui->non_gtk_gui()) {
390 _pluginui->forward_key_event (event);
392 return true;
394 return false;
395 } else {
396 return true;
400 void
401 PluginUIWindow::plugin_going_away ()
403 ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
405 if (_pluginui) {
406 _pluginui->stop_updating(0);
409 death_connection.disconnect ();
411 delete_when_idle (this);
414 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
415 : insert (pi)
416 , plugin (insert->plugin())
417 , add_button (_("Add"))
418 , save_button (_("Save"))
419 , delete_button (_("Delete"))
420 , bypass_button (_("Bypass"))
421 , latency_gui (0)
422 , latency_dialog (0)
423 , plugin_analysis_expander (_("Plugin analysis"))
424 , eqgui (0)
426 _preset_combo.set_size_request (100, -1);
427 _preset_modified.set_size_request (16, -1);
428 _preset_combo.signal_changed().connect(sigc::mem_fun(*this, &PlugUIBase::preset_selected));
429 _no_load_preset = 0;
431 _preset_box.pack_start (_preset_combo);
432 _preset_box.pack_start (_preset_modified);
434 update_preset_list ();
435 update_preset ();
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_active (!pi->active());
450 bypass_button.set_name ("PluginBypassButton");
451 bypass_button.signal_toggled().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_toggled));
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 plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
468 plugin_analysis_expander.set_expanded(false);
470 insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
472 plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
473 plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
474 plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
475 plugin->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::parameter_changed, this, _1, _2), gui_context ());
478 PlugUIBase::~PlugUIBase()
480 delete eqgui;
481 delete latency_gui;
484 void
485 PlugUIBase::plugin_going_away ()
487 /* drop references to the plugin/insert */
488 insert.reset ();
489 plugin.reset ();
490 death_connection.disconnect ();
493 void
494 PlugUIBase::set_latency_label ()
496 framecnt_t const l = insert->effective_latency ();
497 framecnt_t const sr = insert->session().frame_rate ();
499 string t;
501 if (l < sr / 1000) {
502 t = string_compose (_("latency (%1 samples)"), l);
503 } else {
504 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
507 latency_label.set_text (t);
510 void
511 PlugUIBase::latency_button_clicked ()
513 if (!latency_gui) {
514 latency_gui = new LatencyGUI (*(insert.get()), insert->session().frame_rate(), insert->session().get_block_size());
515 latency_dialog = new ArdourDialog (_("Edit Latency"), false, false);
516 latency_dialog->get_vbox()->pack_start (*latency_gui);
517 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
520 latency_dialog->show_all ();
523 void
524 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
526 ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p)
527 boost::shared_ptr<Processor> p (weak_p);
528 if (p) {
529 bypass_button.set_active (!p->active());
533 void
534 PlugUIBase::preset_selected ()
536 if (_no_load_preset) {
537 return;
540 if (_preset_combo.get_active_text().length() > 0) {
541 const Plugin::PresetRecord* pr = plugin->preset_by_label (_preset_combo.get_active_text());
542 if (pr) {
543 plugin->load_preset (*pr);
544 } else {
545 warning << string_compose(_("Plugin preset %1 not found"),
546 _preset_combo.get_active_text()) << endmsg;
551 void
552 PlugUIBase::add_plugin_setting ()
554 NewPluginPresetDialog d (plugin);
556 switch (d.run ()) {
557 case Gtk::RESPONSE_ACCEPT:
558 if (d.name().empty()) {
559 break;
562 if (d.replace ()) {
563 plugin->remove_preset (d.name ());
566 Plugin::PresetRecord const r = plugin->save_preset (d.name());
567 if (!r.uri.empty ()) {
568 plugin->load_preset (r);
570 break;
574 void
575 PlugUIBase::save_plugin_setting ()
577 string const name = _preset_combo.get_active_text ();
578 plugin->remove_preset (name);
579 Plugin::PresetRecord const r = plugin->save_preset (name);
580 if (!r.uri.empty ()) {
581 plugin->load_preset (r);
585 void
586 PlugUIBase::delete_plugin_setting ()
588 plugin->remove_preset (_preset_combo.get_active_text ());
591 void
592 PlugUIBase::bypass_toggled ()
594 bool x;
596 if ((x = bypass_button.get_active()) == insert->active()) {
597 if (x) {
598 insert->deactivate ();
599 } else {
600 insert->activate ();
605 bool
606 PlugUIBase::focus_toggled (GdkEventButton*)
608 if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
609 Keyboard::the_keyboard().magic_widget_drop_focus();
610 focus_button.remove ();
611 focus_button.add (*focus_out_image);
612 focus_out_image->show ();
613 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));
614 KeyboardFocused (false);
615 } else {
616 Keyboard::the_keyboard().magic_widget_grab_focus();
617 focus_button.remove ();
618 focus_button.add (*focus_in_image);
619 focus_in_image->show ();
620 ARDOUR_UI::instance()->set_tip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
621 KeyboardFocused (true);
624 return true;
627 void
628 PlugUIBase::toggle_plugin_analysis()
630 if (plugin_analysis_expander.get_expanded() &&
631 !plugin_analysis_expander.get_child()) {
632 // Create the GUI
633 if (eqgui == 0) {
634 eqgui = new PluginEqGui (insert);
637 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
639 if (toplevel) {
640 toplevel->get_size (pre_eq_size.width, pre_eq_size.height);
643 plugin_analysis_expander.add (*eqgui);
644 plugin_analysis_expander.show_all ();
645 eqgui->start_listening ();
648 if (!plugin_analysis_expander.get_expanded()) {
650 // Hide & remove from expander
652 eqgui->hide ();
653 eqgui->stop_listening ();
654 plugin_analysis_expander.remove();
656 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
658 if (toplevel) {
659 toplevel->resize (pre_eq_size.width, pre_eq_size.height);
664 void
665 PlugUIBase::update_preset_list ()
667 vector<string> preset_labels;
668 vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
670 ++_no_load_preset;
672 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
673 preset_labels.push_back (i->label);
676 set_popdown_strings (_preset_combo, preset_labels);
678 --_no_load_preset;
681 void
682 PlugUIBase::update_preset ()
684 Plugin::PresetRecord p = plugin->last_preset();
686 ++_no_load_preset;
687 _preset_combo.set_active_text (p.label);
688 --_no_load_preset;
690 save_button.set_sensitive (!p.uri.empty() && p.user);
691 delete_button.set_sensitive (!p.uri.empty() && p.user);
693 update_preset_modified ();
696 void
697 PlugUIBase::update_preset_modified ()
699 if (plugin->last_preset().uri.empty()) {
700 _preset_modified.set_text ("");
701 return;
704 bool const c = plugin->parameter_changed_since_last_preset ();
705 if (_preset_modified.get_text().empty() == c) {
706 _preset_modified.set_text (c ? "*" : "");
710 void
711 PlugUIBase::parameter_changed (uint32_t, float)
713 update_preset_modified ();
716 void
717 PlugUIBase::preset_added_or_removed ()
719 /* Update both the list and the currently-displayed preset */
720 update_preset_list ();
721 update_preset ();