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