1 /*
2  * Copyright (C) 2005-2006 Nick Mainsbridge <mainsbridge@gmail.com>
3  * Copyright (C) 2005-2017 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
5  * Copyright (C) 2006-2009 Sampo Savolainen <v2@iki.fi>
6  * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
7  * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
8  * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
9  * Copyright (C) 2013-2018 John Emmas <john@creativepost.co.uk>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License along
22  * with this program; if not, write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25 
26 #ifdef WAF_BUILD
27 #include "gtk2ardour-config.h"
28 #endif
29 
30 #include <climits>
31 #include <cerrno>
32 #include <cmath>
33 #include <string>
34 
35 #include "pbd/stl_delete.h"
36 #include "pbd/xml++.h"
37 #include "pbd/failed_constructor.h"
38 
39 #include "gtkmm/widget.h"
40 #include "gtkmm/box.h"
41 
42 #include "gtkmm2ext/utils.h"
43 #include "gtkmm2ext/doi.h"
44 #include "gtkmm2ext/application.h"
45 
46 #include "widgets/tooltips.h"
47 #include "widgets/fastmeter.h"
48 
49 #include "ardour/session.h"
50 #include "ardour/plugin.h"
51 #include "ardour/plugin_insert.h"
52 #include "ardour/ladspa_plugin.h"
53 #include "ardour/lv2_plugin.h"
54 #include "lv2_plugin_ui.h"
55 #ifdef WINDOWS_VST_SUPPORT
56 #include "ardour/windows_vst_plugin.h"
57 #include "windows_vst_plugin_ui.h"
58 #endif
59 #ifdef LXVST_SUPPORT
60 #include "ardour/lxvst_plugin.h"
61 #include "lxvst_plugin_ui.h"
62 #endif
63 #ifdef MACVST_SUPPORT
64 #include "ardour/mac_vst_plugin.h"
65 #include "vst_plugin_ui.h"
66 #endif
67 #ifdef VST3_SUPPORT
68 #include "ardour/vst3_plugin.h"
69 # ifdef PLATFORM_WINDOWS
70 #  include "vst3_hwnd_plugin_ui.h"
71 # elif defined (__APPLE__)
72 #  include "vst3_plugin_ui.h"
73 extern VST3PluginUI* create_mac_vst3_gui (boost::shared_ptr<ARDOUR::PluginInsert>, Gtk::VBox**);
74 # else
75 #  include "vst3_x11_plugin_ui.h"
76 # endif
77 #endif
78 
79 #include "ardour_window.h"
80 #include "ardour_ui.h"
81 #include "plugin_ui.h"
82 #include "utils.h"
83 #include "gui_thread.h"
84 #include "public_editor.h"
85 #include "processor_box.h"
86 #include "keyboard.h"
87 #include "latency_gui.h"
88 #include "plugin_dspload_ui.h"
89 #include "plugin_eq_gui.h"
90 #include "plugin_presets_ui.h"
91 #include "timers.h"
92 #include "new_plugin_preset_dialog.h"
93 
94 #include "pbd/i18n.h"
95 
96 using namespace std;
97 using namespace ARDOUR;
98 using namespace ARDOUR_UI_UTILS;
99 using namespace ArdourWidgets;
100 using namespace PBD;
101 using namespace Gtkmm2ext;
102 using namespace Gtk;
103 
104 
PluginUIWindow(boost::shared_ptr<PluginInsert> insert,bool scrollable,bool editor)105 PluginUIWindow::PluginUIWindow (
106 	boost::shared_ptr<PluginInsert> insert,
107 	bool                            scrollable,
108 	bool                            editor)
109 	: ArdourWindow (string())
110 	, was_visible (false)
111 	, _keyboard_focused (false)
112 #ifdef AUDIOUNIT_SUPPORT
113 	, pre_deactivate_x (-1)
114 	, pre_deactivate_y (-1)
115 #endif
116 
117 {
118 	bool have_gui = false;
119 	Label* label = manage (new Label());
120 	label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
121 
122 	if (editor && insert->plugin()->has_editor()) {
123 		switch (insert->type()) {
124 		case ARDOUR::Windows_VST:
125 			have_gui = create_windows_vst_editor (insert);
126 			break;
127 
128 		case ARDOUR::LXVST:
129 			have_gui = create_lxvst_editor (insert);
130 			break;
131 
132 		case ARDOUR::MacVST:
133 			have_gui = create_mac_vst_editor (insert);
134 			break;
135 
136 		case ARDOUR::AudioUnit:
137 			have_gui = create_audiounit_editor (insert);
138 			break;
139 
140 		case ARDOUR::LADSPA:
141 			error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
142 			break;
143 
144 		case ARDOUR::LV2:
145 			have_gui = create_lv2_editor (insert);
146 			break;
147 
148 		case ARDOUR::VST3:
149 			have_gui = create_vst3_editor (insert);
150 			break;
151 
152 		default:
153 #ifndef WINDOWS_VST_SUPPORT
154 			error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
155 			      << endmsg;
156 #else
157 			error << _("unknown type of editor-supplying plugin")
158 			      << endmsg;
159 #endif
160 			throw failed_constructor ();
161 		}
162 
163 	}
164 
165 	if (!have_gui) {
166 		GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
167 
168 		_pluginui = pu;
169 		_pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
170 		add (*pu);
171 		set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
172 
173 		signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
174 		signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
175 	}
176 
177 	set_name ("PluginEditor");
178 	add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
179 
180 	insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
181 
182 	gint h = _pluginui->get_preferred_height ();
183 	gint w = _pluginui->get_preferred_width ();
184 
185 	if (scrollable) {
186 		if (h > 600) h = 600;
187 	}
188 
189 	set_border_width (0);
190 	set_default_size (w, h);
191 	set_resizable (_pluginui->resizable());
192 	unset_transient_for ();
193 }
194 
~PluginUIWindow()195 PluginUIWindow::~PluginUIWindow ()
196 {
197 #ifndef NDEBUG
198 	cerr << "PluginWindow deleted for " << this << endl;
199 #endif
200 	delete _pluginui;
201 }
202 
203 void
on_show()204 PluginUIWindow::on_show ()
205 {
206 	set_role("plugin_ui");
207 
208 	if (_pluginui) {
209 		_pluginui->update_preset_list ();
210 		_pluginui->update_preset ();
211 	}
212 
213 	if (_pluginui) {
214 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
215 		if (pre_deactivate_x >= 0) {
216 			move (pre_deactivate_x, pre_deactivate_y);
217 		}
218 #endif
219 
220 		if (_pluginui->on_window_show (_title)) {
221 			Window::on_show ();
222 		}
223 	}
224 }
225 
226 void
on_hide()227 PluginUIWindow::on_hide ()
228 {
229 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
230 	get_position (pre_deactivate_x, pre_deactivate_y);
231 #endif
232 
233 	Window::on_hide ();
234 
235 	if (_pluginui) {
236 		_pluginui->on_window_hide ();
237 	}
238 }
239 
240 void
set_title(const std::string & title)241 PluginUIWindow::set_title(const std::string& title)
242 {
243 	Gtk::Window::set_title(title);
244 	_title = title;
245 }
246 
247 bool
248 #ifdef WINDOWS_VST_SUPPORT
create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)249 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
250 #else
251 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
252 #endif
253 {
254 #ifndef WINDOWS_VST_SUPPORT
255 	return false;
256 #else
257 
258 	boost::shared_ptr<WindowsVSTPlugin> vp;
259 
260 	if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
261 		error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
262 		      << endmsg;
263 		throw failed_constructor ();
264 	} else {
265 		WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
266 
267 		_pluginui = vpu;
268 		_pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
269 		add (*vpu);
270 		vpu->package (*this);
271 	}
272 
273 	return true;
274 #endif
275 }
276 
277 bool
278 #ifdef LXVST_SUPPORT
create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)279 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
280 #else
281 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
282 #endif
283 {
284 #ifndef LXVST_SUPPORT
285 	return false;
286 #else
287 
288 	boost::shared_ptr<LXVSTPlugin> lxvp;
289 
290 	if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
291 		error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
292 		      << endmsg;
293 		throw failed_constructor ();
294 	} else {
295 		LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
296 
297 		_pluginui = lxvpu;
298 		_pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
299 		add (*lxvpu);
300 		lxvpu->package (*this);
301 	}
302 
303 	return true;
304 #endif
305 }
306 
307 bool
308 #ifdef MACVST_SUPPORT
create_mac_vst_editor(boost::shared_ptr<PluginInsert> insert)309 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert> insert)
310 #else
311 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert>)
312 #endif
313 {
314 #ifndef MACVST_SUPPORT
315 	return false;
316 #else
317 	boost::shared_ptr<MacVSTPlugin> mvst;
318 	if ((mvst = boost::dynamic_pointer_cast<MacVSTPlugin> (insert->plugin())) == 0) {
319 		error << string_compose (_("unknown type of editor-supplying plugin (note: no MacVST support in this version of %1)"), PROGRAM_NAME)
320 		      << endmsg;
321 		throw failed_constructor ();
322 	}
323 	VSTPluginUI* vpu = create_mac_vst_gui (insert);
324 	_pluginui = vpu;
325 	_pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
326 	add (*vpu);
327 	vpu->package (*this);
328 
329 	Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
330 
331 	return true;
332 #endif
333 }
334 
335 bool
336 #ifdef VST3_SUPPORT
create_vst3_editor(boost::shared_ptr<PluginInsert> insert)337 PluginUIWindow::create_vst3_editor (boost::shared_ptr<PluginInsert> insert)
338 #else
339 PluginUIWindow::create_vst3_editor (boost::shared_ptr<PluginInsert>)
340 #endif
341 {
342 #ifndef VST3_SUPPORT
343 	return false;
344 #else
345 	boost::shared_ptr<VST3Plugin> vst3;
346 	if ((vst3 = boost::dynamic_pointer_cast<VST3Plugin> (insert->plugin())) == 0) {
347 		error << _("create_vst3_editor called on non-VST3 plugin") << endmsg;
348 		throw failed_constructor ();
349 	} else {
350 #ifdef PLATFORM_WINDOWS
351 		VST3HWNDPluginUI* pui = new VST3HWNDPluginUI (insert, vst3);
352 		add (*pui);
353 #elif defined (__APPLE__)
354 		VBox* box;
355 		VST3PluginUI* pui = create_mac_vst3_gui (insert, &box);
356 		add (*box);
357 		Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
358 #else
359 		VST3X11PluginUI* pui = new VST3X11PluginUI (insert, vst3);
360 		add (*pui);
361 #endif
362 		_pluginui = pui;
363 		pui->package (*this);
364 		_pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
365 	}
366 	return true;
367 #endif
368 }
369 
370 
371 bool
372 #ifdef AUDIOUNIT_SUPPORT
create_audiounit_editor(boost::shared_ptr<PluginInsert> insert)373 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
374 #else
375 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
376 #endif
377 {
378 #ifndef AUDIOUNIT_SUPPORT
379 	return false;
380 #else
381 	VBox* box;
382 	_pluginui = create_au_gui (insert, &box);
383 	_pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
384 	add (*box);
385 
386 	Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
387 
388 	return true;
389 #endif
390 }
391 
392 void
393 #ifdef __APPLE__
app_activated(bool yn)394 PluginUIWindow::app_activated (bool yn)
395 #else
396 PluginUIWindow::app_activated (bool)
397 #endif
398 {
399 #ifdef AUDIOUNIT_SUPPORT
400 	if (_pluginui) {
401 		if (yn) {
402 			if (was_visible) {
403 				_pluginui->activate ();
404 				if (pre_deactivate_x >= 0) {
405 					move (pre_deactivate_x, pre_deactivate_y);
406 				}
407 				present ();
408 				was_visible = true;
409 			}
410 		} else {
411 			was_visible = is_visible();
412 			get_position (pre_deactivate_x, pre_deactivate_y);
413 			hide ();
414 			_pluginui->deactivate ();
415 		}
416 	}
417 #endif
418 }
419 
420 bool
create_lv2_editor(boost::shared_ptr<PluginInsert> insert)421 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
422 {
423 #ifdef HAVE_SUIL
424 	boost::shared_ptr<LV2Plugin> vp;
425 
426 	if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
427 		error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
428 		throw failed_constructor ();
429 	} else {
430 		LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
431 		_pluginui = lpu;
432 		add (*lpu);
433 		lpu->package (*this);
434 		_pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
435 	}
436 
437 	return true;
438 #else
439 	return false;
440 #endif
441 }
442 
443 void
keyboard_focused(bool yn)444 PluginUIWindow::keyboard_focused (bool yn)
445 {
446 	_keyboard_focused = yn;
447 }
448 
449 bool
on_key_press_event(GdkEventKey * event)450 PluginUIWindow::on_key_press_event (GdkEventKey* event)
451 {
452 	if (_keyboard_focused) {
453 		if (_pluginui) {
454 			_pluginui->grab_focus();
455 			if (_pluginui->non_gtk_gui()) {
456 				_pluginui->forward_key_event (event);
457 			} else {
458 					return relay_key_press (event, this);
459 			}
460 		}
461 		return true;
462 	}
463 	/* for us to be getting key press events, there really
464 	   MUST be a _pluginui, but just to be safe, check ...
465 	*/
466 
467 	if (_pluginui) {
468 		_pluginui->grab_focus();
469 		if (_pluginui->non_gtk_gui()) {
470 			/* pass main window as the window for the event
471 			   to be handled in, not this one, because there are
472 			   no widgets in this window that we want to have
473 			   key focus.
474 			*/
475 			return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
476 		} else {
477 			return relay_key_press (event, this);
478 		}
479 	}
480 
481 	return false;
482 }
483 
484 bool
on_key_release_event(GdkEventKey * event)485 PluginUIWindow::on_key_release_event (GdkEventKey *event)
486 {
487 	if (_keyboard_focused) {
488 		if (_pluginui) {
489 			if (_pluginui->non_gtk_gui()) {
490 				_pluginui->forward_key_event (event);
491 				return true;
492 			}
493 		}
494 	} else {
495 		gtk_window_propagate_key_event (GTK_WINDOW(gobj()), event);
496 	}
497 	return relay_key_press (event, this);
498 }
499 
500 void
plugin_going_away()501 PluginUIWindow::plugin_going_away ()
502 {
503 	ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
504 
505 	if (_pluginui) {
506 		_pluginui->stop_updating(0);
507 	}
508 
509 	death_connection.disconnect ();
510 }
511 
PlugUIBase(boost::shared_ptr<PluginInsert> pi)512 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
513 	: insert (pi)
514 	, plugin (insert->plugin())
515 	, _add_button (_("Add"))
516 	, _save_button (_("Save"))
517 	, _delete_button (_("Delete"))
518 	, _preset_browser_button (_("Preset Browser"))
519 	, _reset_button (_("Reset"))
520 	, _bypass_button (ArdourButton::led_default_elements)
521 	, _pin_management_button (_("Pinout"))
522 	, description_expander (_("Description"))
523 	, plugin_analysis_expander (_("Plugin analysis"))
524 	, cpuload_expander (_("CPU Profile"))
525 	, latency_gui (0)
526 	, latency_dialog (0)
527 	, eqgui (0)
528 	, stats_gui (0)
529 	, preset_gui (0)
530 	, preset_dialog (0)
531 {
532 	_preset_modified.set_size_request (16, -1);
533 	_preset_combo.set_text("(default)");
534 	set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
535 	set_tooltip (_add_button, _("Save a new preset"));
536 	set_tooltip (_save_button, _("Save the current preset"));
537 	set_tooltip (_delete_button, _("Delete the current preset"));
538 	set_tooltip (_preset_browser_button, _("Show Preset Browser Dialog"));
539 	set_tooltip (_reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
540 	set_tooltip (_pin_management_button, _("Show Plugin Pin Management Dialog"));
541 	set_tooltip (_bypass_button, _("Disable signal processing by the plugin"));
542 	set_tooltip (_latency_button, _("Edit Plugin Delay/Latency Compensation"));
543 	_no_load_preset = 0;
544 
545 	update_preset_list ();
546 	update_preset ();
547 
548 	_latency_button.set_icon (ArdourIcon::LatencyClock);
549 	_latency_button.add_elements (ArdourButton::Text);
550 	_latency_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::latency_button_clicked));
551 	set_latency_label ();
552 
553 	_add_button.set_name ("generic button");
554 	_add_button.set_icon (ArdourIcon::PsetAdd);
555 	_add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
556 
557 	_save_button.set_name ("generic button");
558 	_save_button.set_icon (ArdourIcon::PsetSave);
559 	_save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
560 
561 	_delete_button.set_name ("generic button");
562 	_delete_button.set_icon (ArdourIcon::PsetDelete);
563 	_delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
564 
565 	_preset_browser_button.set_name ("generic button");
566 	_preset_browser_button.set_icon (ArdourIcon::PsetBrowse);
567 	_preset_browser_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::browse_presets));
568 
569 	_reset_button.set_name ("generic button");
570 	_reset_button.set_icon (ArdourIcon::PluginReset);
571 	_reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
572 
573 	_pin_management_button.set_name ("generic button");
574 	_pin_management_button.set_icon (ArdourIcon::PluginPinout);
575 	_pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
576 
577 
578 	insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
579 
580 	_bypass_button.set_name ("plugin bypass button");
581 	_bypass_button.set_text (_("Bypass"));
582 	_bypass_button.set_icon (ArdourIcon::PluginBypass);
583 	_bypass_button.set_active (!pi->enabled ());
584 	_bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
585 
586 	_focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
587 	_focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
588 
589 	/* these images are not managed, so that we can remove them at will */
590 
591 	_focus_out_image = new Image (get_icon (X_("computer_keyboard")));
592 	_focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
593 
594 	_focus_button.add (*_focus_out_image);
595 
596 	set_tooltip (_focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
597 	set_tooltip (_bypass_button, _("Click to enable/disable this plugin"));
598 
599 	description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
600 	description_expander.set_expanded(false);
601 
602 	plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
603 	plugin_analysis_expander.set_expanded(false);
604 
605 	cpuload_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_cpuload_display));
606 	cpuload_expander.set_expanded(false);
607 
608 	insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
609 
610 	plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
611 	plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
612 	plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
613 	plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
614 
615 	insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
616 
617 	insert->LatencyChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::set_latency_label, this), gui_context());
618 
619 	automation_state_changed();
620 }
621 
~PlugUIBase()622 PlugUIBase::~PlugUIBase()
623 {
624 	delete eqgui;
625 	delete stats_gui;
626 	delete preset_gui;
627 	delete latency_gui;
628 	delete latency_dialog;
629 	delete preset_dialog;
630 
631 	delete _focus_out_image;
632 	delete _focus_in_image;
633 }
634 
635 void
plugin_going_away()636 PlugUIBase::plugin_going_away ()
637 {
638 	drop_connections ();
639 	/* drop references to the plugin/insert */
640 	insert.reset ();
641 	plugin.reset ();
642 }
643 
644 void
add_common_widgets(Gtk::HBox * b,bool with_focus)645 PlugUIBase::add_common_widgets (Gtk::HBox* b, bool with_focus)
646 {
647 	if (with_focus) {
648 		b->pack_end (_focus_button, false, false);
649 	}
650 
651 	b->pack_end (_bypass_button, false, false, with_focus ? 4 : 0);
652 
653 	if (insert->controls().size() > 0) {
654 		b->pack_end (_reset_button, false, false, 4);
655 	}
656 	if (has_descriptive_presets ()) {
657 		b->pack_end (_preset_browser_button, false, false);
658 	}
659 	b->pack_end (_delete_button, false, false);
660 	b->pack_end (_save_button, false, false);
661 	b->pack_end (_add_button, false, false);
662 	b->pack_end (_preset_combo, false, false);
663 	b->pack_end (_preset_modified, false, false);
664 	b->pack_end (_pin_management_button, false, false);
665 
666 	b->pack_start (_latency_button, false, false, 4);
667 }
668 
669 void
set_latency_label()670 PlugUIBase::set_latency_label ()
671 {
672 	samplecnt_t const l = insert->effective_latency ();
673 	float const sr = insert->session().sample_rate ();
674 
675 	_latency_button.set_text (samples_as_time_string (l, sr, true));
676 }
677 
678 void
latency_button_clicked()679 PlugUIBase::latency_button_clicked ()
680 {
681 	if (!latency_gui) {
682 		latency_gui = new LatencyGUI (*(insert.get()), insert->session().sample_rate(), insert->session().get_block_size());
683 		latency_dialog = new ArdourWindow (_("Edit Latency"));
684 		/* use both keep-above and transient for to try cover as many
685 		   different WM's as possible.
686 		*/
687 		latency_dialog->set_keep_above (true);
688 		Window* win = dynamic_cast<Window*> (_bypass_button.get_toplevel ());
689 		if (win) {
690 			latency_dialog->set_transient_for (*win);
691 		}
692 		latency_dialog->add (*latency_gui);
693 	}
694 
695 	latency_gui->refresh ();
696 	latency_dialog->show_all ();
697 }
698 
699 void
processor_active_changed(boost::weak_ptr<Processor> weak_p)700 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
701 {
702 	ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
703 	boost::shared_ptr<Processor> p (weak_p.lock());
704 
705 	if (p) {
706 		_bypass_button.set_active (!p->enabled ());
707 	}
708 }
709 
710 void
preset_selected(Plugin::PresetRecord preset)711 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
712 {
713 	if (_no_load_preset) {
714 		return;
715 	}
716 	if (!preset.label.empty()) {
717 		insert->load_preset (preset);
718 	} else {
719 		// blank selected = no preset
720 		plugin->clear_preset();
721 	}
722 }
723 
724 void
add_plugin_setting()725 PlugUIBase::add_plugin_setting ()
726 {
727 	NewPluginPresetDialog d (plugin, _("New Preset"));
728 
729 	switch (d.run ()) {
730 	case Gtk::RESPONSE_ACCEPT:
731 		if (d.name().empty()) {
732 			break;
733 		}
734 
735 		Plugin::PresetRecord const r = plugin->save_preset (d.name());
736 		if (!r.uri.empty ()) {
737 			plugin->Plugin::load_preset (r);
738 		}
739 		break;
740 	}
741 }
742 
743 void
save_plugin_setting()744 PlugUIBase::save_plugin_setting ()
745 {
746 	string const name = _preset_combo.get_text ();
747 	Plugin::PresetRecord const r = plugin->save_preset (name);
748 	if (!r.uri.empty ()) {
749 		plugin->Plugin::load_preset (r);
750 	}
751 }
752 
753 void
delete_plugin_setting()754 PlugUIBase::delete_plugin_setting ()
755 {
756 	plugin->remove_preset (_preset_combo.get_text ());
757 }
758 
759 void
automation_state_changed()760 PlugUIBase::automation_state_changed ()
761 {
762 	_reset_button.set_sensitive (insert->can_reset_all_parameters());
763 }
764 
765 void
reset_plugin_parameters()766 PlugUIBase::reset_plugin_parameters ()
767 {
768 	insert->reset_parameters_to_default ();
769 }
770 
771 bool
has_descriptive_presets() const772 PlugUIBase::has_descriptive_presets () const
773 {
774 	std::vector<Plugin::PresetRecord> presets = insert->plugin()->get_presets();
775 	for (std::vector<Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
776 		if (i->valid && !i->description.empty()) {
777 			return true;
778 		}
779 	}
780 	return false;
781 }
782 
783 void
browse_presets()784 PlugUIBase::browse_presets ()
785 {
786 	if (!preset_dialog) {
787 		if (preset_gui) {
788 			/* Do not allow custom window, if preset_gui is used.
789 			 * e.g. generic-plugin UI.
790 			 */
791 			return;
792 		}
793 		preset_dialog = new ArdourWindow (_("Select Preset"));
794 		preset_dialog->set_keep_above (true);
795 		Window* win = dynamic_cast<Window*> (_preset_browser_button.get_toplevel ());
796 		if (win) {
797 			preset_dialog->set_transient_for (*win);
798 		}
799 		preset_gui = new PluginPresetsUI (insert);
800 		preset_dialog->add (*preset_gui);
801 	}
802 	preset_dialog->show_all ();
803 }
804 
805 void
manage_pins()806 PlugUIBase::manage_pins ()
807 {
808 	PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
809 	if (proxy) {
810 		proxy->get (true);
811 		proxy->present ();
812 		proxy->get ()->raise();
813 	}
814 }
815 
816 bool
bypass_button_release(GdkEventButton *)817 PlugUIBase::bypass_button_release (GdkEventButton*)
818 {
819 	bool view_says_bypassed = (_bypass_button.active_state() != 0);
820 
821 	if (view_says_bypassed != insert->enabled ()) {
822 		insert->enable (view_says_bypassed);
823 	}
824 
825 	return false;
826 }
827 
828 bool
focus_toggled(GdkEventButton *)829 PlugUIBase::focus_toggled (GdkEventButton*)
830 {
831 	if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
832 		Keyboard::the_keyboard().magic_widget_drop_focus();
833 		_focus_button.remove ();
834 		_focus_button.add (*_focus_out_image);
835 		_focus_out_image->show ();
836 		set_tooltip (_focus_button, string_compose (_("Click to allow the plugin to receive keyboard events that %1 would normally use as a shortcut"), PROGRAM_NAME));
837 		KeyboardFocused (false);
838 	} else {
839 		Keyboard::the_keyboard().magic_widget_grab_focus();
840 		_focus_button.remove ();
841 		_focus_button.add (*_focus_in_image);
842 		_focus_in_image->show ();
843 		set_tooltip (_focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
844 		KeyboardFocused (true);
845 	}
846 
847 	return true;
848 }
849 
850 void
toggle_description()851 PlugUIBase::toggle_description()
852 {
853 	if (description_expander.get_expanded() &&
854 	    !description_expander.get_child()) {
855 		const std::string text = plugin->get_docs();
856 		if (text.empty()) {
857 			return;
858 		}
859 
860 		Gtk::Label* label = manage(new Gtk::Label(text));
861 		label->set_line_wrap(true);
862 		label->set_line_wrap_mode(Pango::WRAP_WORD);
863 		description_expander.add(*label);
864 		description_expander.show_all();
865 	}
866 
867 	if (!description_expander.get_expanded()) {
868 		const int child_height = description_expander.get_child ()->get_height ();
869 
870 		description_expander.remove();
871 
872 		Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
873 
874 		if (toplevel) {
875 			Gtk::Requisition wr;
876 			toplevel->get_size (wr.width, wr.height);
877 			wr.height -= child_height;
878 			toplevel->resize (wr.width, wr.height);
879 		}
880 	}
881 }
882 
883 void
toggle_plugin_analysis()884 PlugUIBase::toggle_plugin_analysis()
885 {
886 	if (plugin_analysis_expander.get_expanded() &&
887 	    !plugin_analysis_expander.get_child()) {
888 		// Create the GUI
889 		if (eqgui == 0) {
890 			eqgui = new PluginEqGui (insert);
891 		}
892 
893 		plugin_analysis_expander.add (*eqgui);
894 		plugin_analysis_expander.show_all ();
895 	}
896 
897 	if (!plugin_analysis_expander.get_expanded()) {
898 		// Hide & remove from expander
899 		const int child_height = plugin_analysis_expander.get_child ()->get_height ();
900 
901 		eqgui->hide ();
902 		plugin_analysis_expander.remove();
903 
904 		Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
905 
906 		if (toplevel) {
907 			Gtk::Requisition wr;
908 			toplevel->get_size (wr.width, wr.height);
909 			wr.height -= child_height;
910 			toplevel->resize (wr.width, wr.height);
911 		}
912 	}
913 }
914 
915 void
toggle_cpuload_display()916 PlugUIBase::toggle_cpuload_display()
917 {
918 	if (cpuload_expander.get_expanded() && !cpuload_expander.get_child()) {
919 		if (stats_gui == 0) {
920 			stats_gui = new PluginLoadStatsGui (insert);
921 		}
922 		cpuload_expander.add (*stats_gui);
923 		cpuload_expander.show_all();
924 		stats_gui->start_updating ();
925 	}
926 
927 	if (!cpuload_expander.get_expanded()) {
928 		const int child_height = cpuload_expander.get_child ()->get_height ();
929 
930 		stats_gui->hide ();
931 		stats_gui->stop_updating ();
932 		cpuload_expander.remove();
933 
934 		Gtk::Window *toplevel = (Gtk::Window*) cpuload_expander.get_ancestor (GTK_TYPE_WINDOW);
935 
936 		if (toplevel) {
937 			Gtk::Requisition wr;
938 			toplevel->get_size (wr.width, wr.height);
939 			wr.height -= child_height;
940 			toplevel->resize (wr.width, wr.height);
941 		}
942 	}
943 
944 }
945 
946 void
update_preset_list()947 PlugUIBase::update_preset_list ()
948 {
949 	using namespace Menu_Helpers;
950 
951 	vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
952 
953 	++_no_load_preset;
954 
955 	// Add a menu entry for each preset
956 	_preset_combo.clear_items();
957 	for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
958 		_preset_combo.AddMenuElem(
959 			MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
960 	}
961 
962 	// Add an empty entry for un-setting current preset (see preset_selected)
963 	Plugin::PresetRecord no_preset;
964 	_preset_combo.AddMenuElem(
965 		MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
966 
967 	--_no_load_preset;
968 }
969 
970 void
update_preset()971 PlugUIBase::update_preset ()
972 {
973 	Plugin::PresetRecord p = plugin->last_preset();
974 
975 	++_no_load_preset;
976 	if (p.uri.empty()) {
977 		_preset_combo.set_text (_("(none)"));
978 	} else {
979 		_preset_combo.set_text (p.label);
980 	}
981 	--_no_load_preset;
982 
983 	_delete_button.set_sensitive (!p.uri.empty() && p.user);
984 	update_preset_modified ();
985 }
986 
987 void
update_preset_modified()988 PlugUIBase::update_preset_modified ()
989 {
990 	Plugin::PresetRecord p = plugin->last_preset();
991 
992 	if (p.uri.empty()) {
993 		_save_button.set_sensitive (false);
994 		_preset_modified.set_text ("");
995 		return;
996 	}
997 
998 	bool const c = plugin->parameter_changed_since_last_preset ();
999 	if (_preset_modified.get_text().empty() == c) {
1000 		_preset_modified.set_text (c ? "*" : "");
1001 	}
1002 	_save_button.set_sensitive (c && p.user);
1003 }
1004 
1005 void
preset_added_or_removed()1006 PlugUIBase::preset_added_or_removed ()
1007 {
1008 	/* Update both the list and the currently-displayed preset */
1009 	update_preset_list ();
1010 	update_preset ();
1011 }
1012 
1013