Tweak plugin DSP stats UI
[ardour.git] / gtk2_ardour / plugin_ui.cc
1 /*
2     Copyright (C) 2000 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <climits>
25 #include <cerrno>
26 #include <cmath>
27 #include <string>
28
29 #include "pbd/stl_delete.h"
30 #include "pbd/xml++.h"
31 #include "pbd/failed_constructor.h"
32
33 #include "gtkmm/widget.h"
34 #include "gtkmm/box.h"
35 #include "gtkmm/separator.h"
36
37 #include "gtkmm2ext/utils.h"
38 #include "gtkmm2ext/doi.h"
39 #include "gtkmm2ext/application.h"
40
41 #include "widgets/tooltips.h"
42 #include "widgets/fastmeter.h"
43
44 #include "ardour/session.h"
45 #include "ardour/plugin.h"
46 #include "ardour/plugin_insert.h"
47 #include "ardour/ladspa_plugin.h"
48 #ifdef WINDOWS_VST_SUPPORT
49 #include "ardour/windows_vst_plugin.h"
50 #include "windows_vst_plugin_ui.h"
51 #endif
52 #ifdef LXVST_SUPPORT
53 #include "ardour/lxvst_plugin.h"
54 #include "lxvst_plugin_ui.h"
55 #endif
56 #ifdef MACVST_SUPPORT
57 #include "ardour/mac_vst_plugin.h"
58 #include "vst_plugin_ui.h"
59 #endif
60 #ifdef LV2_SUPPORT
61 #include "ardour/lv2_plugin.h"
62 #include "lv2_plugin_ui.h"
63 #endif
64
65 #include "ardour_window.h"
66 #include "ardour_ui.h"
67 #include "plugin_ui.h"
68 #include "utils.h"
69 #include "gui_thread.h"
70 #include "public_editor.h"
71 #include "processor_box.h"
72 #include "keyboard.h"
73 #include "latency_gui.h"
74 #include "plugin_eq_gui.h"
75 #include "timers.h"
76 #include "new_plugin_preset_dialog.h"
77
78 #include "pbd/i18n.h"
79
80 using namespace std;
81 using namespace ARDOUR;
82 using namespace ARDOUR_UI_UTILS;
83 using namespace ArdourWidgets;
84 using namespace PBD;
85 using namespace Gtkmm2ext;
86 using namespace Gtk;
87
88
89 class PluginLoadStatsGui : public Gtk::Table
90 {
91 public:
92         PluginLoadStatsGui (boost::shared_ptr<ARDOUR::PluginInsert>);
93
94         void start_updating () {
95                 update_cpu_label ();
96                 update_cpu_label_connection = Timers::second_connect (sigc::mem_fun(*this, &PluginLoadStatsGui::update_cpu_label));
97         }
98
99         void stop_updating () {
100                 _valid = false;
101                 update_cpu_label_connection.disconnect ();
102         }
103
104 private:
105         void update_cpu_label ();
106         bool draw_bar (GdkEventExpose*);
107         void clear_stats () {
108                 _insert->clear_stats ();
109         }
110
111         boost::shared_ptr<ARDOUR::PluginInsert> _insert;
112         sigc::connection update_cpu_label_connection;
113
114         Gtk::Label _lbl_min;
115         Gtk::Label _lbl_max;
116         Gtk::Label _lbl_avg;
117         Gtk::Label _lbl_dev;
118
119         ArdourWidgets::ArdourButton _reset_button;
120         Gtk::DrawingArea _darea;
121
122         uint64_t _min, _max;
123         double   _avg, _dev;
124         bool     _valid;
125 };
126
127 PluginLoadStatsGui::PluginLoadStatsGui (boost::shared_ptr<ARDOUR::PluginInsert> insert)
128         : _insert (insert)
129         , _lbl_min ("", ALIGN_RIGHT, ALIGN_CENTER)
130         , _lbl_max ("", ALIGN_RIGHT, ALIGN_CENTER)
131         , _lbl_avg ("", ALIGN_RIGHT, ALIGN_CENTER)
132         , _lbl_dev ("", ALIGN_RIGHT, ALIGN_CENTER)
133         , _reset_button (_("Reset"))
134         , _valid (false)
135 {
136         _reset_button.set_name ("generic button");
137         _reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PluginLoadStatsGui::clear_stats));
138         _darea.signal_expose_event ().connect (sigc::mem_fun (*this, &PluginLoadStatsGui::draw_bar));
139         set_size_request_to_display_given_text (_lbl_dev, string_compose (_("%1 [ms]"), 999.123), 0, 0);
140
141         attach (*manage (new Gtk::Label (_("Min:"), ALIGN_RIGHT, ALIGN_CENTER)),
142                         0, 1, 0, 1, Gtk::FILL, Gtk::SHRINK, 4, 0);
143         attach (*manage (new Gtk::Label (_("Max:"), ALIGN_RIGHT, ALIGN_CENTER)),
144                         0, 1, 1, 2, Gtk::FILL, Gtk::SHRINK, 4, 0);
145         attach (*manage (new Gtk::Label (_("Avg:"), ALIGN_RIGHT, ALIGN_CENTER)),
146                         0, 1, 2, 3, Gtk::FILL, Gtk::SHRINK, 4, 0);
147         attach (*manage (new Gtk::Label (_("Dev:"), ALIGN_RIGHT, ALIGN_CENTER)),
148                         0, 1, 3, 4, Gtk::FILL, Gtk::SHRINK, 4, 0);
149
150         attach (_lbl_min, 1, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
151         attach (_lbl_max, 1, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
152         attach (_lbl_avg, 1, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
153         attach (_lbl_dev, 1, 2, 3, 4, Gtk::FILL, Gtk::SHRINK);
154
155         attach (*manage (new Gtk::VSeparator ()),
156                         2, 3, 0, 4, Gtk::FILL, Gtk::FILL, 4, 0);
157
158         attach (_darea, 3, 4, 0, 4, Gtk::FILL|Gtk::EXPAND, Gtk::FILL, 4, 4);
159
160         attach (_reset_button, 4, 5, 2, 4, Gtk::FILL, Gtk::SHRINK);
161 }
162
163 void
164 PluginLoadStatsGui::update_cpu_label()
165 {
166         if (_insert->get_stats (_min, _max, _avg, _dev)) {
167                 _valid = true;
168                 _lbl_min.set_text (string_compose (_("%1 [ms]"), rint (_min / 10.) / 100.));
169                 _lbl_max.set_text (string_compose (_("%1 [ms]"), rint (_max / 10.) / 100.));
170                 _lbl_avg.set_text (string_compose (_("%1 [ms]"), rint (_avg) / 1000.));
171                 _lbl_dev.set_text (string_compose (_("%1 [ms]"), rint (_dev) / 1000.));
172         } else {
173                 _valid = false;
174                 _lbl_min.set_text ("-");
175                 _lbl_max.set_text ("-");
176                 _lbl_avg.set_text ("-");
177                 _lbl_dev.set_text ("-");
178         }
179         _darea.queue_draw ();
180 }
181
182 bool
183 PluginLoadStatsGui::draw_bar (GdkEventExpose* ev)
184 {
185         Gtk::Allocation a = _darea.get_allocation ();
186         const int width = a.get_width ();
187         const int height = a.get_height ();
188         cairo_t* cr = gdk_cairo_create (_darea.get_window ()->gobj ());
189         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
190         cairo_clip (cr);
191
192         Gdk::Color const bg = get_style ()->get_bg (STATE_NORMAL);
193         Gdk::Color const fg = get_style ()->get_fg (STATE_NORMAL);
194
195         cairo_set_source_rgb (cr, bg.get_red_p (), bg.get_green_p (), bg.get_blue_p ());
196         cairo_rectangle (cr, 0, 0, width, height);
197         cairo_fill (cr);
198
199         int border = (height / 7) | 1;
200
201         int x0 = 2;
202         int y0 = border;
203         int x1 = width - border;
204         int y1 = (height - 3 * border) & ~1;
205
206         const int w = x1 - x0;
207         const int h = y1 - y0;
208         const double cycle_ms = 1000. * _insert->session().get_block_size() / (double)_insert->session().nominal_sample_rate();
209
210         const double base_mult = std::max (1.0, cycle_ms / 2.0);
211         const double log_base = log1p (base_mult);
212
213 #if 0 // Linear
214 # define DEFLECT(T) ( (T) * w * 8. / (9. * cycle_ms) )
215 #else
216 # define DEFLECT(T) ( log1p((T) * base_mult / cycle_ms) * w * 8. / (9 * log_base) )
217 #endif
218
219         cairo_save (cr);
220         rounded_rectangle (cr, x0, y0, w, h, 7);
221
222         cairo_set_source_rgba (cr, .0, .0, .0, 1);
223         cairo_set_line_width (cr, 1);
224         cairo_stroke_preserve (cr);
225
226         if (_valid) {
227                 cairo_pattern_t *pat = cairo_pattern_create_linear (x0, 0.0, w, 0.0);
228                 cairo_pattern_add_color_stop_rgba (pat, 0,         0,  1, 0, .7);
229                 cairo_pattern_add_color_stop_rgba (pat, 6.  / 9.,  0,  1, 0, .7);
230                 cairo_pattern_add_color_stop_rgba (pat, 6.5 / 9., .8, .8, 0, .7);
231                 cairo_pattern_add_color_stop_rgba (pat, 7.5 / 9., .8, .8, 0, .7);
232                 cairo_pattern_add_color_stop_rgba (pat, 8.  / 9.,  1,  0, 0, .7);
233                 cairo_set_source (cr, pat);
234                 cairo_pattern_destroy (pat);
235                 cairo_fill_preserve (cr);
236         } else {
237                 cairo_set_source_rgba (cr, .4, .3, .1, .5);
238                 cairo_fill_preserve (cr);
239         }
240
241
242         cairo_clip (cr);
243
244         if (_valid) {
245                 double xmin = DEFLECT(_min / 1000.);
246                 double xmax = DEFLECT(_max / 1000.);
247
248                 rounded_rectangle (cr, x0 + xmin, y0, xmax - xmin, h, 7);
249                 cairo_set_source_rgba (cr, .8, .8, .9, .5);
250                 cairo_fill (cr);
251         }
252
253         cairo_restore (cr);
254
255         Glib::RefPtr<Pango::Layout> layout;
256         layout = Pango::Layout::create (get_pango_context ());
257
258         cairo_set_line_width (cr, 1);
259
260         for (int i = 1; i < 9; ++i) {
261                 int text_width, text_height;
262 #if 0 // Linear
263                 double v = cycle_ms * i / 8.;
264 #else
265                 double v = (exp (i * log_base / 8) - 1) * cycle_ms / base_mult;
266 #endif
267                 double decimal = v > 10 ? 10 : 100;
268                 layout->set_text (string_compose ("%1", rint (decimal * v) / decimal));
269                 layout->get_pixel_size (text_width, text_height);
270
271                 const int dx = w * i / 9.; // == DEFLECT (v)
272
273                 cairo_move_to (cr, x0 + dx - .5, y0);
274                 cairo_line_to (cr, x0 + dx - .5, y1);
275                 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
276                 cairo_stroke (cr);
277
278                 cairo_move_to (cr, x0 + dx - .5 * text_width, y1 + 1);
279                 cairo_set_source_rgb (cr, fg.get_red_p (), fg.get_green_p (), fg.get_blue_p ());
280                 pango_cairo_show_layout (cr, layout->gobj ());
281         }
282
283         {
284                 int text_width, text_height;
285                 layout->set_text ("0");
286                 cairo_move_to (cr, x0 + 1, y1 + 1);
287                 cairo_set_source_rgb (cr, fg.get_red_p (), fg.get_green_p (), fg.get_blue_p ());
288                 pango_cairo_show_layout (cr, layout->gobj ());
289
290                 layout->set_text (_("[ms]"));
291                 layout->get_pixel_size (text_width, text_height);
292                 cairo_move_to (cr, x0 + w - text_width - 1, y1 + 1);
293                 pango_cairo_show_layout (cr, layout->gobj ());
294         }
295
296         if (_valid) {
297                 cairo_save (cr);
298                 rounded_rectangle (cr, x0, y0, w, h, 7);
299                 cairo_clip (cr);
300
301                 double xavg = DEFLECT(_avg / 1000.);
302                 double xd0 = DEFLECT((_avg - _dev) / 1000.);
303                 double xd1 = DEFLECT((_avg + _dev) / 1000.);
304
305                 cairo_set_line_width (cr, 3);
306                 cairo_set_source_rgba (cr, .0, .1, .0, 1.);
307                 cairo_move_to (cr, x0 + xavg - 1.5, y0);
308                 cairo_line_to (cr, x0 + xavg - 1.5, y1);
309                 cairo_stroke (cr);
310
311                 if (xd1 - xd0 > 2) {
312                         const double ym = .5 + floor (y0 + h / 2);
313                         const int h4 = h / 4;
314
315                         cairo_set_line_width (cr, 1);
316                         cairo_set_source_rgba (cr, 0, 0, 0, 1.);
317                         cairo_move_to (cr, floor (x0 + xd0), ym);
318                         cairo_line_to (cr, ceil (x0 + xd1),  ym);
319                         cairo_stroke (cr);
320
321                         cairo_move_to (cr, floor (x0 + xd0) - .5, ym - h4);
322                         cairo_line_to (cr, floor (x0 + xd0) - .5, ym + h4);
323                         cairo_stroke (cr);
324                         cairo_move_to (cr, ceil (x0 + xd1) - .5, ym - h4);
325                         cairo_line_to (cr, ceil (x0 + xd1) - .5, ym + h4);
326                         cairo_stroke (cr);
327                 }
328                 cairo_restore (cr);
329         }
330 #undef DEFLECT
331
332         cairo_destroy (cr);
333         return true;
334 }
335
336
337 /* --- */
338
339
340 PluginUIWindow::PluginUIWindow (
341         boost::shared_ptr<PluginInsert> insert,
342         bool                            scrollable,
343         bool                            editor)
344         : ArdourWindow (string())
345         , was_visible (false)
346         , _keyboard_focused (false)
347 #ifdef AUDIOUNIT_SUPPORT
348         , pre_deactivate_x (-1)
349         , pre_deactivate_y (-1)
350 #endif
351
352 {
353         bool have_gui = false;
354         Label* label = manage (new Label());
355         label->set_markup ("<b>THIS IS THE PLUGIN UI</b>");
356
357         if (editor && insert->plugin()->has_editor()) {
358                 switch (insert->type()) {
359                 case ARDOUR::Windows_VST:
360                         have_gui = create_windows_vst_editor (insert);
361                         break;
362
363                 case ARDOUR::LXVST:
364                         have_gui = create_lxvst_editor (insert);
365                         break;
366
367                 case ARDOUR::MacVST:
368                         have_gui = create_mac_vst_editor (insert);
369                         break;
370
371                 case ARDOUR::AudioUnit:
372                         have_gui = create_audiounit_editor (insert);
373                         break;
374
375                 case ARDOUR::LADSPA:
376                         error << _("Eh? LADSPA plugins don't have editors!") << endmsg;
377                         break;
378
379                 case ARDOUR::LV2:
380                         have_gui = create_lv2_editor (insert);
381                         break;
382
383                 default:
384 #ifndef WINDOWS_VST_SUPPORT
385                         error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
386                               << endmsg;
387 #else
388                         error << _("unknown type of editor-supplying plugin")
389                               << endmsg;
390 #endif
391                         throw failed_constructor ();
392                 }
393
394         }
395
396         if (!have_gui) {
397                 GenericPluginUI* pu = new GenericPluginUI (insert, scrollable);
398
399                 _pluginui = pu;
400                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
401                 add (*pu);
402                 set_wmclass (X_("ardour_plugin_editor"), PROGRAM_NAME);
403
404                 signal_map_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::start_updating));
405                 signal_unmap_event().connect (sigc::mem_fun (*pu, &GenericPluginUI::stop_updating));
406         }
407
408         set_name ("PluginEditor");
409         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
410
411         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PluginUIWindow::plugin_going_away, this), gui_context());
412
413         gint h = _pluginui->get_preferred_height ();
414         gint w = _pluginui->get_preferred_width ();
415
416         if (scrollable) {
417                 if (h > 600) h = 600;
418         }
419
420         set_default_size (w, h);
421         set_resizable (_pluginui->resizable());
422 }
423
424 PluginUIWindow::~PluginUIWindow ()
425 {
426 #ifndef NDEBUG
427         cerr << "PluginWindow deleted for " << this << endl;
428 #endif
429         delete _pluginui;
430 }
431
432 void
433 PluginUIWindow::on_show ()
434 {
435         set_role("plugin_ui");
436
437         if (_pluginui) {
438                 _pluginui->update_preset_list ();
439                 _pluginui->update_preset ();
440         }
441
442         if (_pluginui) {
443 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
444                 if (pre_deactivate_x >= 0) {
445                         move (pre_deactivate_x, pre_deactivate_y);
446                 }
447 #endif
448
449                 if (_pluginui->on_window_show (_title)) {
450                         Window::on_show ();
451                 }
452         }
453 }
454
455 void
456 PluginUIWindow::on_hide ()
457 {
458 #if defined (HAVE_AUDIOUNITS) && defined(__APPLE__)
459         get_position (pre_deactivate_x, pre_deactivate_y);
460 #endif
461
462         Window::on_hide ();
463
464         if (_pluginui) {
465                 _pluginui->on_window_hide ();
466         }
467 }
468
469 void
470 PluginUIWindow::set_title(const std::string& title)
471 {
472         Gtk::Window::set_title(title);
473         _title = title;
474 }
475
476 bool
477 #ifdef WINDOWS_VST_SUPPORT
478 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert> insert)
479 #else
480 PluginUIWindow::create_windows_vst_editor(boost::shared_ptr<PluginInsert>)
481 #endif
482 {
483 #ifndef WINDOWS_VST_SUPPORT
484         return false;
485 #else
486
487         boost::shared_ptr<WindowsVSTPlugin> vp;
488
489         if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (insert->plugin())) == 0) {
490                 error << string_compose (_("unknown type of editor-supplying plugin (note: no VST support in this version of %1)"), PROGRAM_NAME)
491                       << endmsg;
492                 throw failed_constructor ();
493         } else {
494                 WindowsVSTPluginUI* vpu = new WindowsVSTPluginUI (insert, vp, GTK_WIDGET(this->gobj()));
495
496                 _pluginui = vpu;
497                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
498                 add (*vpu);
499                 vpu->package (*this);
500         }
501
502         return true;
503 #endif
504 }
505
506 bool
507 #ifdef LXVST_SUPPORT
508 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert> insert)
509 #else
510 PluginUIWindow::create_lxvst_editor(boost::shared_ptr<PluginInsert>)
511 #endif
512 {
513 #ifndef LXVST_SUPPORT
514         return false;
515 #else
516
517         boost::shared_ptr<LXVSTPlugin> lxvp;
518
519         if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (insert->plugin())) == 0) {
520                 error << string_compose (_("unknown type of editor-supplying plugin (note: no linuxVST support in this version of %1)"), PROGRAM_NAME)
521                       << endmsg;
522                 throw failed_constructor ();
523         } else {
524                 LXVSTPluginUI* lxvpu = new LXVSTPluginUI (insert, lxvp);
525
526                 _pluginui = lxvpu;
527                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
528                 add (*lxvpu);
529                 lxvpu->package (*this);
530         }
531
532         return true;
533 #endif
534 }
535
536 bool
537 #ifdef MACVST_SUPPORT
538 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert> insert)
539 #else
540 PluginUIWindow::create_mac_vst_editor (boost::shared_ptr<PluginInsert>)
541 #endif
542 {
543 #ifndef MACVST_SUPPORT
544         return false;
545 #else
546         boost::shared_ptr<MacVSTPlugin> mvst;
547         if ((mvst = boost::dynamic_pointer_cast<MacVSTPlugin> (insert->plugin())) == 0) {
548                 error << string_compose (_("unknown type of editor-supplying plugin (note: no MacVST support in this version of %1)"), PROGRAM_NAME)
549                       << endmsg;
550                 throw failed_constructor ();
551         }
552         VSTPluginUI* vpu = create_mac_vst_gui (insert);
553         _pluginui = vpu;
554         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
555         add (*vpu);
556         vpu->package (*this);
557
558         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
559
560         return true;
561 #endif
562 }
563
564
565 bool
566 #ifdef AUDIOUNIT_SUPPORT
567 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert> insert)
568 #else
569 PluginUIWindow::create_audiounit_editor (boost::shared_ptr<PluginInsert>)
570 #endif
571 {
572 #ifndef AUDIOUNIT_SUPPORT
573         return false;
574 #else
575         VBox* box;
576         _pluginui = create_au_gui (insert, &box);
577         _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
578         add (*box);
579
580         Application::instance()->ActivationChanged.connect (mem_fun (*this, &PluginUIWindow::app_activated));
581
582         return true;
583 #endif
584 }
585
586 void
587 #ifdef __APPLE__
588 PluginUIWindow::app_activated (bool yn)
589 #else
590 PluginUIWindow::app_activated (bool)
591 #endif
592 {
593 #ifdef AUDIOUNIT_SUPPORT
594         if (_pluginui) {
595                 if (yn) {
596                         if (was_visible) {
597                                 _pluginui->activate ();
598                                 if (pre_deactivate_x >= 0) {
599                                         move (pre_deactivate_x, pre_deactivate_y);
600                                 }
601                                 present ();
602                                 was_visible = true;
603                         }
604                 } else {
605                         was_visible = is_visible();
606                         get_position (pre_deactivate_x, pre_deactivate_y);
607                         hide ();
608                         _pluginui->deactivate ();
609                 }
610         }
611 #endif
612 }
613
614 bool
615 PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert)
616 {
617 #ifdef HAVE_SUIL
618         boost::shared_ptr<LV2Plugin> vp;
619
620         if ((vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin())) == 0) {
621                 error << _("create_lv2_editor called on non-LV2 plugin") << endmsg;
622                 throw failed_constructor ();
623         } else {
624                 LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
625                 _pluginui = lpu;
626                 add (*lpu);
627                 lpu->package (*this);
628                 _pluginui->KeyboardFocused.connect (sigc::mem_fun (*this, &PluginUIWindow::keyboard_focused));
629         }
630
631         return true;
632 #else
633         return false;
634 #endif
635 }
636
637 void
638 PluginUIWindow::keyboard_focused (bool yn)
639 {
640         _keyboard_focused = yn;
641 }
642
643 bool
644 PluginUIWindow::on_key_press_event (GdkEventKey* event)
645 {
646         if (_keyboard_focused) {
647                 if (_pluginui) {
648                         _pluginui->grab_focus();
649                         if (_pluginui->non_gtk_gui()) {
650                                 _pluginui->forward_key_event (event);
651                         } else {
652                                         return relay_key_press (event, this);
653                         }
654                 }
655                 return true;
656         }
657         /* for us to be getting key press events, there really
658            MUST be a _pluginui, but just to be safe, check ...
659         */
660
661         if (_pluginui) {
662                 _pluginui->grab_focus();
663                 if (_pluginui->non_gtk_gui()) {
664                         /* pass main window as the window for the event
665                            to be handled in, not this one, because there are
666                            no widgets in this window that we want to have
667                            key focus.
668                         */
669                         return relay_key_press (event, &ARDOUR_UI::instance()->main_window());
670                 } else {
671                         return relay_key_press (event, this);
672                 }
673         }
674
675         return false;
676 }
677
678 bool
679 PluginUIWindow::on_key_release_event (GdkEventKey *event)
680 {
681         if (_keyboard_focused) {
682                 if (_pluginui) {
683                         if (_pluginui->non_gtk_gui()) {
684                                 _pluginui->forward_key_event (event);
685                         }
686                 }
687         } else {
688                 gtk_window_propagate_key_event (GTK_WINDOW(gobj()), event);
689         }
690         /* don't forward releases */
691         return true;
692 }
693
694 void
695 PluginUIWindow::plugin_going_away ()
696 {
697         ENSURE_GUI_THREAD (*this, &PluginUIWindow::plugin_going_away)
698
699         if (_pluginui) {
700                 _pluginui->stop_updating(0);
701         }
702
703         death_connection.disconnect ();
704 }
705
706 PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
707         : insert (pi)
708         , plugin (insert->plugin())
709         , add_button (_("Add"))
710         , save_button (_("Save"))
711         , delete_button (_("Delete"))
712         , reset_button (_("Reset"))
713         , bypass_button (ArdourButton::led_default_elements)
714         , pin_management_button (_("Pinout"))
715         , description_expander (_("Description"))
716         , plugin_analysis_expander (_("Plugin analysis"))
717         , cpuload_expander (_("CPU Profile"))
718         , latency_gui (0)
719         , latency_dialog (0)
720         , eqgui (0)
721         , stats_gui (0)
722 {
723         _preset_modified.set_size_request (16, -1);
724         _preset_combo.set_text("(default)");
725         set_tooltip (_preset_combo, _("Presets (if any) for this plugin\n(Both factory and user-created)"));
726         set_tooltip (add_button, _("Save a new preset"));
727         set_tooltip (save_button, _("Save the current preset"));
728         set_tooltip (delete_button, _("Delete the current preset"));
729         set_tooltip (reset_button, _("Reset parameters to default (if no parameters are in automation play mode)"));
730         set_tooltip (pin_management_button, _("Show Plugin Pin Management Dialog"));
731         set_tooltip (bypass_button, _("Disable signal processing by the plugin"));
732         _no_load_preset = 0;
733
734         update_preset_list ();
735         update_preset ();
736
737         add_button.set_name ("generic button");
738         add_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::add_plugin_setting));
739
740         save_button.set_name ("generic button");
741         save_button.signal_clicked.connect(sigc::mem_fun(*this, &PlugUIBase::save_plugin_setting));
742
743         delete_button.set_name ("generic button");
744         delete_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::delete_plugin_setting));
745
746         reset_button.set_name ("generic button");
747         reset_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::reset_plugin_parameters));
748
749         pin_management_button.set_name ("generic button");
750         pin_management_button.signal_clicked.connect (sigc::mem_fun (*this, &PlugUIBase::manage_pins));
751
752         insert->ActiveChanged.connect (active_connection, invalidator (*this), boost::bind (&PlugUIBase::processor_active_changed, this,  boost::weak_ptr<Processor>(insert)), gui_context());
753
754         bypass_button.set_name ("plugin bypass button");
755         bypass_button.set_text (_("Bypass"));
756         bypass_button.set_active (!pi->enabled ());
757         bypass_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::bypass_button_release), false);
758         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
759
760         focus_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PlugUIBase::focus_toggled));
761         focus_button.add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
762
763         /* these images are not managed, so that we can remove them at will */
764
765         focus_out_image = new Image (get_icon (X_("computer_keyboard")));
766         focus_in_image = new Image (get_icon (X_("computer_keyboard_active")));
767
768         focus_button.add (*focus_out_image);
769
770         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));
771         set_tooltip (bypass_button, _("Click to enable/disable this plugin"));
772
773         description_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_description));
774         description_expander.set_expanded(false);
775
776         plugin_analysis_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_plugin_analysis));
777         plugin_analysis_expander.set_expanded(false);
778
779         cpuload_expander.property_expanded().signal_changed().connect( sigc::mem_fun(*this, &PlugUIBase::toggle_cpuload_display));
780         cpuload_expander.set_expanded(false);
781
782         insert->DropReferences.connect (death_connection, invalidator (*this), boost::bind (&PlugUIBase::plugin_going_away, this), gui_context());
783
784         plugin->PresetAdded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
785         plugin->PresetRemoved.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::preset_added_or_removed, this), gui_context ());
786         plugin->PresetLoaded.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset, this), gui_context ());
787         plugin->PresetDirty.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::update_preset_modified, this), gui_context ());
788
789         insert->AutomationStateChanged.connect (*this, invalidator (*this), boost::bind (&PlugUIBase::automation_state_changed, this), gui_context());
790
791         automation_state_changed();
792 }
793
794 PlugUIBase::~PlugUIBase()
795 {
796         delete eqgui;
797         delete stats_gui;
798         delete latency_gui;
799 }
800
801 void
802 PlugUIBase::plugin_going_away ()
803 {
804         /* drop references to the plugin/insert */
805         insert.reset ();
806         plugin.reset ();
807 }
808
809 void
810 PlugUIBase::set_latency_label ()
811 {
812         samplecnt_t const l = insert->effective_latency ();
813         samplecnt_t const sr = insert->session().sample_rate ();
814
815         string t;
816
817         if (l < sr / 1000) {
818                 t = string_compose (P_("latency (%1 sample)", "latency (%1 samples)", l), l);
819         } else {
820                 t = string_compose (_("latency (%1 ms)"), (float) l / ((float) sr / 1000.0f));
821         }
822
823         latency_button.set_text (t);
824 }
825
826 void
827 PlugUIBase::latency_button_clicked ()
828 {
829         if (!latency_gui) {
830                 latency_gui = new LatencyGUI (*(insert.get()), insert->session().sample_rate(), insert->session().get_block_size());
831                 latency_dialog = new ArdourWindow (_("Edit Latency"));
832                 /* use both keep-above and transient for to try cover as many
833                    different WM's as possible.
834                 */
835                 latency_dialog->set_keep_above (true);
836                 Window* win = dynamic_cast<Window*> (bypass_button.get_toplevel ());
837                 if (win) {
838                         latency_dialog->set_transient_for (*win);
839                 }
840                 latency_dialog->add (*latency_gui);
841                 latency_dialog->signal_hide().connect (sigc::mem_fun (*this, &PlugUIBase::set_latency_label));
842         }
843
844         latency_dialog->show_all ();
845 }
846
847 void
848 PlugUIBase::processor_active_changed (boost::weak_ptr<Processor> weak_p)
849 {
850         ENSURE_GUI_THREAD (*this, &PlugUIBase::processor_active_changed, weak_p);
851         boost::shared_ptr<Processor> p (weak_p.lock());
852
853         if (p) {
854                 bypass_button.set_active (!p->enabled ());
855         }
856 }
857
858 void
859 PlugUIBase::preset_selected (Plugin::PresetRecord preset)
860 {
861         if (_no_load_preset) {
862                 return;
863         }
864         if (!preset.label.empty()) {
865                 insert->load_preset (preset);
866         } else {
867                 // blank selected = no preset
868                 plugin->clear_preset();
869         }
870 }
871
872 #ifdef NO_PLUGIN_STATE
873 static bool seen_saving_message = false;
874
875 static void show_no_plugin_message()
876 {
877         info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
878                         PROGRAM_NAME)
879              << endmsg;
880         info << _("To get full access to updates without this limitation\n"
881                   "consider becoming a subscriber for a low cost every month.")
882              << endmsg;
883         info << X_("https://community.ardour.org/s/subscribe")
884              << endmsg;
885         ARDOUR_UI::instance()->popup_error(_("Plugin presets are not supported in this build, see the Log window for more information."));
886 }
887 #endif
888
889 void
890 PlugUIBase::add_plugin_setting ()
891 {
892 #ifndef NO_PLUGIN_STATE
893         NewPluginPresetDialog d (plugin, _("New Preset"));
894
895         switch (d.run ()) {
896         case Gtk::RESPONSE_ACCEPT:
897                 if (d.name().empty()) {
898                         break;
899                 }
900
901                 if (d.replace ()) {
902                         plugin->remove_preset (d.name ());
903                 }
904
905                 Plugin::PresetRecord const r = plugin->save_preset (d.name());
906                 if (!r.uri.empty ()) {
907                         plugin->load_preset (r);
908                 }
909                 break;
910         }
911 #else
912         if (!seen_saving_message) {
913                 seen_saving_message = true;
914                 show_no_plugin_message();
915         }
916 #endif
917 }
918
919 void
920 PlugUIBase::save_plugin_setting ()
921 {
922 #ifndef NO_PLUGIN_STATE
923         string const name = _preset_combo.get_text ();
924         plugin->remove_preset (name);
925         Plugin::PresetRecord const r = plugin->save_preset (name);
926         if (!r.uri.empty ()) {
927                 plugin->load_preset (r);
928         }
929 #else
930         if (!seen_saving_message) {
931                 seen_saving_message = true;
932                 show_no_plugin_message();
933         }
934 #endif
935 }
936
937 void
938 PlugUIBase::delete_plugin_setting ()
939 {
940 #ifndef NO_PLUGIN_STATE
941         plugin->remove_preset (_preset_combo.get_text ());
942 #else
943         if (!seen_saving_message) {
944                 seen_saving_message = true;
945                 show_no_plugin_message();
946         }
947 #endif
948 }
949
950 void
951 PlugUIBase::automation_state_changed ()
952 {
953         reset_button.set_sensitive (insert->can_reset_all_parameters());
954 }
955
956 void
957 PlugUIBase::reset_plugin_parameters ()
958 {
959         insert->reset_parameters_to_default ();
960 }
961
962 void
963 PlugUIBase::manage_pins ()
964 {
965         PluginPinWindowProxy* proxy = insert->pinmgr_proxy ();
966         if (proxy) {
967                 proxy->get (true);
968                 proxy->present ();
969                 proxy->get ()->raise();
970         }
971 }
972
973 bool
974 PlugUIBase::bypass_button_release (GdkEventButton*)
975 {
976         bool view_says_bypassed = (bypass_button.active_state() != 0);
977
978         if (view_says_bypassed != insert->enabled ()) {
979                 insert->enable (view_says_bypassed);
980         }
981
982         return false;
983 }
984
985 bool
986 PlugUIBase::focus_toggled (GdkEventButton*)
987 {
988         if (Keyboard::the_keyboard().some_magic_widget_has_focus()) {
989                 Keyboard::the_keyboard().magic_widget_drop_focus();
990                 focus_button.remove ();
991                 focus_button.add (*focus_out_image);
992                 focus_out_image->show ();
993                 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));
994                 KeyboardFocused (false);
995         } else {
996                 Keyboard::the_keyboard().magic_widget_grab_focus();
997                 focus_button.remove ();
998                 focus_button.add (*focus_in_image);
999                 focus_in_image->show ();
1000                 set_tooltip (focus_button, string_compose (_("Click to allow normal use of %1 keyboard shortcuts"), PROGRAM_NAME));
1001                 KeyboardFocused (true);
1002         }
1003
1004         return true;
1005 }
1006
1007 void
1008 PlugUIBase::toggle_description()
1009 {
1010         if (description_expander.get_expanded() &&
1011             !description_expander.get_child()) {
1012                 const std::string text = plugin->get_docs();
1013                 if (text.empty()) {
1014                         return;
1015                 }
1016
1017                 Gtk::Label* label = manage(new Gtk::Label(text));
1018                 label->set_line_wrap(true);
1019                 label->set_line_wrap_mode(Pango::WRAP_WORD);
1020                 description_expander.add(*label);
1021                 description_expander.show_all();
1022         }
1023
1024         if (!description_expander.get_expanded()) {
1025                 const int child_height = description_expander.get_child ()->get_height ();
1026
1027                 description_expander.remove();
1028
1029                 Gtk::Window *toplevel = (Gtk::Window*) description_expander.get_ancestor (GTK_TYPE_WINDOW);
1030
1031                 if (toplevel) {
1032                         Gtk::Requisition wr;
1033                         toplevel->get_size (wr.width, wr.height);
1034                         wr.height -= child_height;
1035                         toplevel->resize (wr.width, wr.height);
1036                 }
1037         }
1038 }
1039
1040 void
1041 PlugUIBase::toggle_plugin_analysis()
1042 {
1043         if (plugin_analysis_expander.get_expanded() &&
1044             !plugin_analysis_expander.get_child()) {
1045                 // Create the GUI
1046                 if (eqgui == 0) {
1047                         eqgui = new PluginEqGui (insert);
1048                 }
1049
1050                 plugin_analysis_expander.add (*eqgui);
1051                 plugin_analysis_expander.show_all ();
1052                 eqgui->start_listening ();
1053         }
1054
1055         if (!plugin_analysis_expander.get_expanded()) {
1056                 // Hide & remove from expander
1057                 const int child_height = plugin_analysis_expander.get_child ()->get_height ();
1058
1059                 eqgui->hide ();
1060                 eqgui->stop_listening ();
1061                 plugin_analysis_expander.remove();
1062
1063                 Gtk::Window *toplevel = (Gtk::Window*) plugin_analysis_expander.get_ancestor (GTK_TYPE_WINDOW);
1064
1065                 if (toplevel) {
1066                         Gtk::Requisition wr;
1067                         toplevel->get_size (wr.width, wr.height);
1068                         wr.height -= child_height;
1069                         toplevel->resize (wr.width, wr.height);
1070                 }
1071         }
1072 }
1073
1074 void
1075 PlugUIBase::toggle_cpuload_display()
1076 {
1077         if (cpuload_expander.get_expanded() && !cpuload_expander.get_child()) {
1078                 if (stats_gui == 0) {
1079                         stats_gui = new PluginLoadStatsGui (insert);
1080                 }
1081                 cpuload_expander.add (*stats_gui);
1082                 cpuload_expander.show_all();
1083                 stats_gui->start_updating ();
1084         }
1085
1086         if (!cpuload_expander.get_expanded()) {
1087                 const int child_height = cpuload_expander.get_child ()->get_height ();
1088
1089                 stats_gui->hide ();
1090                 stats_gui->stop_updating ();
1091                 cpuload_expander.remove();
1092
1093                 Gtk::Window *toplevel = (Gtk::Window*) cpuload_expander.get_ancestor (GTK_TYPE_WINDOW);
1094
1095                 if (toplevel) {
1096                         Gtk::Requisition wr;
1097                         toplevel->get_size (wr.width, wr.height);
1098                         wr.height -= child_height;
1099                         toplevel->resize (wr.width, wr.height);
1100                 }
1101         }
1102
1103 }
1104
1105 void
1106 PlugUIBase::update_preset_list ()
1107 {
1108         using namespace Menu_Helpers;
1109
1110         vector<ARDOUR::Plugin::PresetRecord> presets = plugin->get_presets();
1111
1112         ++_no_load_preset;
1113
1114         // Add a menu entry for each preset
1115         _preset_combo.clear_items();
1116         for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
1117                 _preset_combo.AddMenuElem(
1118                         MenuElem(i->label, sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), *i)));
1119         }
1120
1121         // Add an empty entry for un-setting current preset (see preset_selected)
1122         Plugin::PresetRecord no_preset;
1123         _preset_combo.AddMenuElem(
1124                 MenuElem("", sigc::bind(sigc::mem_fun(*this, &PlugUIBase::preset_selected), no_preset)));
1125
1126         --_no_load_preset;
1127 }
1128
1129 void
1130 PlugUIBase::update_preset ()
1131 {
1132         Plugin::PresetRecord p = plugin->last_preset();
1133
1134         ++_no_load_preset;
1135         if (p.uri.empty()) {
1136                 _preset_combo.set_text (_("(none)"));
1137         } else {
1138                 _preset_combo.set_text (p.label);
1139         }
1140         --_no_load_preset;
1141
1142         save_button.set_sensitive (!p.uri.empty() && p.user);
1143         delete_button.set_sensitive (!p.uri.empty() && p.user);
1144
1145         update_preset_modified ();
1146 }
1147
1148 void
1149 PlugUIBase::update_preset_modified ()
1150 {
1151
1152         if (plugin->last_preset().uri.empty()) {
1153                 _preset_modified.set_text ("");
1154                 return;
1155         }
1156
1157         bool const c = plugin->parameter_changed_since_last_preset ();
1158         if (_preset_modified.get_text().empty() == c) {
1159                 _preset_modified.set_text (c ? "*" : "");
1160         }
1161 }
1162
1163 void
1164 PlugUIBase::preset_added_or_removed ()
1165 {
1166         /* Update both the list and the currently-displayed preset */
1167         update_preset_list ();
1168         update_preset ();
1169 }
1170