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