1 /*
2   Copyright 2007-2016 David Robillard <d@drobilla.net>
3 
4   Permission to use, copy, modify, and/or distribute this software for any
5   purpose with or without fee is hereby granted, provided that the above
6   copyright notice and this permission notice appear in all copies.
7 
8   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 
17 #include <ctype.h> // workaround for error: use of undeclared identifier 'isascii'
18 
19 #include "jalv_internal.h"
20 
21 #include "lilv/lilv.h"
22 #include "suil/suil.h"
23 #include "zix/sem.h"
24 
25 #include <QtGlobal>
26 
27 #if QT_VERSION >= 0x050000
28 #    include <QAction>
29 #    include <QApplication>
30 #    include <QDial>
31 #    include <QFontMetrics>
32 #    include <QGroupBox>
33 #    include <QGuiApplication>
34 #    include <QHBoxLayout>
35 #    include <QKeySequence>
36 #    include <QLabel>
37 #    include <QLayout>
38 #    include <QLayoutItem>
39 #    include <QList>
40 #    include <QMainWindow>
41 #    include <QMenu>
42 #    include <QMenuBar>
43 #    include <QObject>
44 #    include <QPoint>
45 #    include <QRect>
46 #    include <QScreen>
47 #    include <QScrollArea>
48 #    include <QSize>
49 #    include <QSizePolicy>
50 #    include <QString>
51 #    include <QStyle>
52 #    include <QTimer>
53 #    include <QVBoxLayout>
54 #    include <QWidget>
55 #    include <QtCore>
56 #else
57 #    include <QtGui>
58 #endif
59 
60 #include <algorithm>
61 #include <cmath>
62 #include <cstdint>
63 #include <cstring>
64 #include <map>
65 #include <vector>
66 
67 #define CONTROL_WIDTH 150
68 #define DIAL_STEPS    10000
69 
70 static QApplication* app = nullptr;
71 
72 class FlowLayout : public QLayout
73 {
74 public:
75 	explicit FlowLayout(QWidget* parent,
76 	                    int      margin,
77 	                    int      hSpacing,
78 	                    int      vSpacing);
79 
80 	explicit FlowLayout(int margin, int hSpacing, int vSpacing);
81 
82 	FlowLayout(const FlowLayout&) = delete;
83 	FlowLayout& operator=(const FlowLayout&) = delete;
84 
85 	FlowLayout(FlowLayout&&) = delete;
86 	FlowLayout&& operator=(FlowLayout&&) = delete;
87 
88 	~FlowLayout() override;
89 
90 	void             addItem(QLayoutItem* item) override;
91 	int              horizontalSpacing() const;
92 	int              verticalSpacing() const;
93 	Qt::Orientations expandingDirections() const override;
94 	bool             hasHeightForWidth() const override;
95 	int              heightForWidth(int) const override;
96 	int              count() const override;
97 	QLayoutItem*     itemAt(int index) const override;
98 	QSize            minimumSize() const override;
99 	void             setGeometry(const QRect &rect) override;
100 	QSize            sizeHint() const override;
101 	QLayoutItem*     takeAt(int index) override;
102 
103 private:
104 	int doLayout(const QRect &rect, bool testOnly) const;
105 	int smartSpacing(QStyle::PixelMetric pm) const;
106 
107 	QList<QLayoutItem*> itemList;
108 	int m_hSpace;
109 	int m_vSpace;
110 };
111 
FlowLayout(QWidget * parent,int margin,int hSpacing,int vSpacing)112 FlowLayout::FlowLayout(QWidget* parent, int margin, int hSpacing, int vSpacing)
113 	: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
114 {
115 	setContentsMargins(margin, margin, margin, margin);
116 }
117 
FlowLayout(int margin,int hSpacing,int vSpacing)118 FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
119 	: m_hSpace(hSpacing), m_vSpace(vSpacing)
120 {
121 	setContentsMargins(margin, margin, margin, margin);
122 }
123 
~FlowLayout()124 FlowLayout::~FlowLayout()
125 {
126 	QLayoutItem* item = nullptr;
127 	while ((item = takeAt(0))) {
128 		delete item;
129 	}
130 }
131 
132 void
addItem(QLayoutItem * item)133 FlowLayout::addItem(QLayoutItem* item)
134 {
135 	itemList.append(item);
136 }
137 
138 int
horizontalSpacing() const139 FlowLayout::horizontalSpacing() const
140 {
141 	if (m_hSpace >= 0) {
142 		return m_hSpace;
143 	} else {
144 		return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
145 	}
146 }
147 
148 int
verticalSpacing() const149 FlowLayout::verticalSpacing() const
150 {
151 	if (m_vSpace >= 0) {
152 		return m_vSpace;
153 	} else {
154 		return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
155 	}
156 }
157 
158 int
count() const159 FlowLayout::count() const
160 {
161 	return itemList.size();
162 }
163 
164 QLayoutItem*
itemAt(int index) const165 FlowLayout::itemAt(int index) const
166 {
167 	return itemList.value(index);
168 }
169 
170 QLayoutItem*
takeAt(int index)171 FlowLayout::takeAt(int index)
172 {
173 	if (index >= 0 && index < itemList.size()) {
174 		return itemList.takeAt(index);
175 	} else {
176 		return nullptr;
177 	}
178 }
179 
180 Qt::Orientations
expandingDirections() const181 FlowLayout::expandingDirections() const
182 {
183 	return Qt::Orientations();
184 }
185 
186 bool
hasHeightForWidth() const187 FlowLayout::hasHeightForWidth() const
188 {
189 	return true;
190 }
191 
192 int
heightForWidth(int width) const193 FlowLayout::heightForWidth(int width) const
194 {
195 	return doLayout(QRect(0, 0, width, 0), true);
196 }
197 
198 void
setGeometry(const QRect & rect)199 FlowLayout::setGeometry(const QRect &rect)
200 {
201 	QLayout::setGeometry(rect);
202 	doLayout(rect, false);
203 }
204 
205 QSize
sizeHint() const206 FlowLayout::sizeHint() const
207 {
208 	return minimumSize();
209 }
210 
211 QSize
minimumSize() const212 FlowLayout::minimumSize() const
213 {
214 	QSize        size = {};
215 	QLayoutItem* item = nullptr;
216 	foreach (item, itemList) {
217 		size = size.expandedTo(item->minimumSize());
218 	}
219 
220 	return size + QSize(2 * margin(), 2 * margin());
221 }
222 
223 int
doLayout(const QRect & rect,bool testOnly) const224 FlowLayout::doLayout(const QRect &rect, bool testOnly) const
225 {
226 	int left   = 0;
227 	int top    = 0;
228 	int right  = 0;
229 	int bottom = 0;
230 	getContentsMargins(&left, &top, &right, &bottom);
231 
232 	QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
233 	int   x             = effectiveRect.x();
234 	int   y             = effectiveRect.y();
235 	int   lineHeight    = 0;
236 
237 	QLayoutItem* item = nullptr;
238 	foreach (item, itemList) {
239 		QWidget* wid = item->widget();
240 
241 		int spaceX = horizontalSpacing();
242 		if (spaceX == -1) {
243 			spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton,
244 			                                     QSizePolicy::PushButton,
245 			                                     Qt::Horizontal,
246 			                                     nullptr,
247 			                                     nullptr);
248 		}
249 		int spaceY = verticalSpacing();
250 		if (spaceY == -1) {
251 			spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton,
252 			                                     QSizePolicy::PushButton,
253 			                                     Qt::Vertical,
254 			                                     nullptr,
255 			                                     nullptr);
256 		}
257 
258 		int nextX = x + item->sizeHint().width() + spaceX;
259 		if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
260 			x          = effectiveRect.x();
261 			y          = y + lineHeight + spaceY;
262 			nextX      = x + item->sizeHint().width() + spaceX;
263 			lineHeight = 0;
264 		}
265 
266 		if (!testOnly) {
267 			item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
268 		}
269 
270 		x          = nextX;
271 		lineHeight = qMax(lineHeight, item->sizeHint().height());
272 	}
273 	return y + lineHeight - rect.y() + bottom;
274 }
275 
276 int
smartSpacing(QStyle::PixelMetric pm) const277 FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
278 {
279 	QObject* parent = this->parent();
280 	if (!parent) {
281 		return -1;
282 	} else if (parent->isWidgetType()) {
283 		QWidget* pw = static_cast<QWidget*>(parent);
284 		return pw->style()->pixelMetric(pm, nullptr, pw);
285 	} else {
286 		return static_cast<QLayout*>(parent)->spacing();
287 	}
288 }
289 
290 class PresetAction : public QAction
291 {
292 	Q_OBJECT // NOLINT
293 
294 public:
PresetAction(QObject * parent,Jalv * jalv,LilvNode * preset)295 	PresetAction(QObject* parent, Jalv* jalv, LilvNode* preset)
296 		: QAction(parent)
297 		, _jalv(jalv)
298 		, _preset(preset)
299 	{
300 		connect(this, SIGNAL(triggered()),
301 		        this, SLOT(presetChosen()));
302 	}
303 
presetChosen()304 	Q_SLOT void presetChosen() {
305 		jalv_apply_preset(_jalv, _preset);
306 	}
307 
308 private:
309 	Jalv*     _jalv;
310 	LilvNode* _preset;
311 };
312 
313 typedef struct {
314 	Jalv*        jalv;
315 	struct Port* port;
316 } PortContainer;
317 
318 class Control : public QGroupBox
319 {
320 	Q_OBJECT // NOLINT
321 
322 public:
323 	explicit Control(PortContainer portContainer, QWidget* parent);
324 
325 	Q_SLOT void dialChanged(int value);
326 
327 	void setValue(float value);
328 
329 private:
330 	void    setRange(float min, float max);
331 	QString getValueLabel(float value);
332 	float   getValue();
333 	int     stringWidth(const QString& str);
334 
335 	QDial*            dial;
336 	const LilvPlugin* plugin;
337 	struct Port*      port;
338 
339 	QLabel* label;
340 	QString name;
341 	int     steps;
342 	float   max;
343 	float   min;
344 	bool    isInteger;
345 	bool    isEnum;
346 	bool    isLogarithmic;
347 
348 	std::vector<float>           scalePoints;
349 	std::map<float, const char*> scaleMap;
350 };
351 
352 #if QT_VERSION >= 0x050000
353 #    include "jalv_qt5_meta.hpp" // IWYU pragma: keep
354 #else
355 #    include "jalv_qt4_meta.hpp" // IWYU pragma: keep
356 #endif
357 
358 extern "C" {
359 
360 int
jalv_init(int * argc,char *** argv,JalvOptions *)361 jalv_init(int* argc, char*** argv, JalvOptions*)
362 {
363 	app = new QApplication(*argc, *argv, true);
364 	app->setStyleSheet("QGroupBox::title { subcontrol-position: top center }");
365 
366 	return 0;
367 }
368 
369 const char*
jalv_native_ui_type(void)370 jalv_native_ui_type(void)
371 {
372 #if QT_VERSION >= 0x050000
373 	return "http://lv2plug.in/ns/extensions/ui#Qt5UI";
374 #else
375 	return "http://lv2plug.in/ns/extensions/ui#Qt4UI";
376 #endif
377 }
378 
379 void
jalv_ui_port_event(Jalv * jalv,uint32_t port_index,uint32_t buffer_size,uint32_t protocol,const void * buffer)380 jalv_ui_port_event(Jalv*       jalv,
381                    uint32_t    port_index,
382                    uint32_t    buffer_size,
383                    uint32_t    protocol,
384                    const void* buffer)
385 {
386 	if (jalv->ui_instance) {
387 		suil_instance_port_event(jalv->ui_instance, port_index,
388 		                         buffer_size, protocol, buffer);
389 	} else {
390 		Control* control =
391 		    static_cast<Control*>(jalv->ports[port_index].widget);
392 		if (control) {
393 			control->setValue(*static_cast<const float*>(buffer));
394 		}
395 	}
396 }
397 
398 class Timer : public QTimer
399 {
400 public:
Timer(Jalv * jalv)401 	explicit Timer(Jalv* jalv) : _jalv(jalv) {}
402 
timerEvent(QTimerEvent *)403 	void timerEvent(QTimerEvent*) override {
404 		jalv_update(_jalv);
405 	}
406 
407 private:
408 	Jalv* _jalv;
409 };
410 
411 static int
add_preset_to_menu(Jalv * jalv,const LilvNode * node,const LilvNode * title,void * data)412 add_preset_to_menu(Jalv*           jalv,
413                    const LilvNode* node,
414                    const LilvNode* title,
415                    void*           data)
416 {
417 	QMenu*      menu  = static_cast<QMenu*>(data);
418 	const char* label = lilv_node_as_string(title);
419 
420 	QAction* action = new PresetAction(menu, jalv, lilv_node_duplicate(node));
421 	action->setText(label);
422 	menu->addAction(action);
423 	return 0;
424 }
425 
Control(PortContainer portContainer,QWidget * parent)426 Control::Control(PortContainer portContainer, QWidget* parent)
427     : QGroupBox(parent)
428     , dial(new QDial())
429     , plugin(portContainer.jalv->plugin)
430     , port(portContainer.port)
431     , label(new QLabel())
432     , max(1.0f)
433     , min(0.0f)
434     , isInteger(false)
435     , isEnum(false)
436     , isLogarithmic(false)
437 {
438 	JalvNodes*      nodes    = &portContainer.jalv->nodes;
439 	const LilvPort* lilvPort = port->lilv_port;
440 
441 	LilvNode* nmin = nullptr;
442 	LilvNode* nmax = nullptr;
443 	LilvNode* ndef = nullptr;
444 	lilv_port_get_range(plugin, lilvPort, &ndef, &nmin, &nmax);
445 
446 	LilvNode* stepsNode = lilv_port_get(plugin, lilvPort, nodes->pprops_rangeSteps);
447 	if (lilv_node_is_int(stepsNode)) {
448 		steps = std::max(lilv_node_as_int(stepsNode), 2);
449 	} else {
450 		steps = DIAL_STEPS;
451 	}
452 	lilv_node_free(stepsNode);
453 
454 	// Fill scalePoints Map
455 	LilvScalePoints* sp = lilv_port_get_scale_points(plugin, lilvPort);
456 	if (sp) {
457 		LILV_FOREACH(scale_points, s, sp) {
458 			const LilvScalePoint* p   = lilv_scale_points_get(sp, s);
459 			const LilvNode*       val = lilv_scale_point_get_value(p);
460 			if (!lilv_node_is_float(val) && !lilv_node_is_int(val)) {
461 				continue;
462 			}
463 
464 			const float f = lilv_node_as_float(val);
465 			scalePoints.push_back(f);
466 			scaleMap[f] = lilv_node_as_string(lilv_scale_point_get_label(p));
467 		}
468 
469 		lilv_scale_points_free(sp);
470 	}
471 
472 	// Check port properties
473 	isLogarithmic = lilv_port_has_property(plugin, lilvPort, nodes->pprops_logarithmic);
474 	isInteger     = lilv_port_has_property(plugin, lilvPort, nodes->lv2_integer);
475 	isEnum        = lilv_port_has_property(plugin, lilvPort, nodes->lv2_enumeration);
476 
477 	if (lilv_port_has_property(plugin, lilvPort, nodes->lv2_toggled)) {
478 		isInteger = true;
479 
480 		if (!scaleMap[0]) {
481 			scaleMap[0] = "Off";
482 		}
483 		if (!scaleMap[1]) {
484 			scaleMap[1] = "On" ;
485 		}
486 	}
487 
488 	// Find and set min, max and default values for port
489 	float defaultValue = ndef ? lilv_node_as_float(ndef) : port->control;
490 	setRange(lilv_node_as_float(nmin), lilv_node_as_float(nmax));
491 	setValue(defaultValue);
492 
493 	// Fill layout
494 	QVBoxLayout* layout = new QVBoxLayout();
495 	layout->addWidget(label, 0, Qt::AlignHCenter);
496 	layout->addWidget(dial, 0, Qt::AlignHCenter);
497 	setLayout(layout);
498 
499 	setMinimumWidth(CONTROL_WIDTH);
500 	setMaximumWidth(CONTROL_WIDTH);
501 
502 	LilvNode* nname = lilv_port_get_name(plugin, lilvPort);
503 	name = QString("%1").arg(lilv_node_as_string(nname));
504 
505 	// Handle long names
506 	if (stringWidth(name) > CONTROL_WIDTH) {
507 		setTitle(fontMetrics().elidedText(name, Qt::ElideRight, CONTROL_WIDTH));
508 	} else {
509 		setTitle(name);
510 	}
511 
512 	// Set tooltip if comment is available
513 	LilvNode* comment = lilv_port_get(plugin, lilvPort, nodes->rdfs_comment);
514 	if (comment) {
515 		QString* tooltip = new QString();
516 		tooltip->append(lilv_node_as_string(comment));
517 		setToolTip(*tooltip);
518 	}
519 
520 	setFlat(true);
521 
522 	connect(dial, SIGNAL(valueChanged(int)), this, SLOT(dialChanged(int)));
523 
524 	lilv_node_free(nmin);
525 	lilv_node_free(nmax);
526 	lilv_node_free(ndef);
527 	lilv_node_free(nname);
528 	lilv_node_free(comment);
529 }
530 
531 void
setValue(float value)532 Control::setValue(float value)
533 {
534 	float step = 0.0f;
535 
536 	if (isInteger) {
537 		step = value;
538 	} else if (isEnum) {
539 		step = (std::find(scalePoints.begin(), scalePoints.end(), value)
540 		        - scalePoints.begin());
541 	} else if (isLogarithmic) {
542 		step = steps * logf(value / min) / logf(max / min);
543 	} else {
544 		step = value * steps;
545 	}
546 
547 	dial->setValue(step);
548 	label->setText(getValueLabel(value));
549 }
550 
551 QString
getValueLabel(float value)552 Control::getValueLabel(float value)
553 {
554 	if (scaleMap[value]) {
555 		if (stringWidth(scaleMap[value]) > CONTROL_WIDTH) {
556 			label->setToolTip(scaleMap[value]);
557 			return fontMetrics().elidedText(QString(scaleMap[value]),
558 			                                Qt::ElideRight,
559 			                                CONTROL_WIDTH);
560 		}
561 		return scaleMap[value];
562 	}
563 
564 	return QString("%1").arg(value);
565 }
566 
567 void
setRange(float minRange,float maxRange)568 Control::setRange(float minRange, float maxRange)
569 {
570 	min = minRange;
571 	max = maxRange;
572 
573 	if (isLogarithmic) {
574 		minRange = 1;
575 		maxRange = steps;
576 	} else if (isEnum) {
577 		minRange = 0;
578 		maxRange = scalePoints.size() - 1;
579 	} else if (!isInteger) {
580 		minRange *= steps;
581 		maxRange *= steps;
582 	}
583 
584 	dial->setRange(minRange, maxRange);
585 }
586 
587 float
getValue()588 Control::getValue()
589 {
590 	if (isEnum) {
591 		return scalePoints[dial->value()];
592 	} else if (isInteger) {
593 		return dial->value();
594 	} else if (isLogarithmic) {
595 		return min * powf(max / min, (float)dial->value() / (steps - 1));
596 	} else {
597 		return (float)dial->value() / steps;
598 	}
599 }
600 
601 int
stringWidth(const QString & str)602 Control::stringWidth(const QString& str)
603 {
604 #if QT_VERSION >= 0x050B00
605 	return fontMetrics().horizontalAdvance(str);
606 #else
607 	return fontMetrics().width(str);
608 #endif
609 }
610 
611 void
dialChanged(int)612 Control::dialChanged(int)
613 {
614 	float value = getValue();
615 
616 	label->setText(getValueLabel(value));
617 	port->control = value;
618 }
619 
620 static bool
portGroupLessThan(const PortContainer & p1,const PortContainer & p2)621 portGroupLessThan(const PortContainer &p1, const PortContainer &p2)
622 {
623 	Jalv*           jalv  = p1.jalv;
624 	const LilvPort* port1 = p1.port->lilv_port;
625 	const LilvPort* port2 = p2.port->lilv_port;
626 
627 	LilvNode* group1 = lilv_port_get(
628 		jalv->plugin, port1, jalv->nodes.pg_group);
629 	LilvNode* group2 = lilv_port_get(
630 		jalv->plugin, port2, jalv->nodes.pg_group);
631 
632 	const int cmp = (group1 && group2)
633 		? strcmp(lilv_node_as_string(group1), lilv_node_as_string(group2))
634 		: (intptr_t(group1) - intptr_t(group2));
635 
636 	lilv_node_free(group2);
637 	lilv_node_free(group1);
638 
639 	return cmp < 0;
640 }
641 
642 static QWidget*
build_control_widget(Jalv * jalv)643 build_control_widget(Jalv* jalv)
644 {
645 	const LilvPlugin* plugin = jalv->plugin;
646 	LilvWorld*        world  = jalv->world;
647 
648 	QList<PortContainer> portContainers;
649 	for (unsigned i = 0; i < jalv->num_ports; ++i) {
650 		if (!jalv->opts.show_hidden &&
651 		    lilv_port_has_property(plugin, jalv->ports[i].lilv_port,
652 		                           jalv->nodes.pprops_notOnGUI)) {
653 			continue;
654 		}
655 
656 		if (jalv->ports[i].type == TYPE_CONTROL) {
657 			PortContainer portContainer;
658 			portContainer.jalv = jalv;
659 			portContainer.port = &jalv->ports[i];
660 			portContainers.append(portContainer);
661 		}
662 	}
663 
664 	std::sort(portContainers.begin(), portContainers.end(), portGroupLessThan);
665 
666 	QWidget*    grid       = new QWidget();
667 	FlowLayout* flowLayout = new FlowLayout(-1, -1, -1);
668 	QLayout*    layout     = flowLayout;
669 
670 	LilvNode*    lastGroup   = nullptr;
671 	QHBoxLayout* groupLayout = nullptr;
672 	for (int i = 0; i < portContainers.count(); ++i) {
673 		PortContainer portContainer = portContainers[i];
674 		Port*         port          = portContainer.port;
675 
676 		Control*  control = new Control(portContainer, nullptr);
677 		LilvNode* group   = lilv_port_get(
678 			plugin, port->lilv_port, jalv->nodes.pg_group);
679 		if (group) {
680 			if (!lilv_node_equals(group, lastGroup)) {
681 				/* Group has changed */
682 				LilvNode* groupName = lilv_world_get(
683 					world, group, jalv->nodes.lv2_name, nullptr);
684 				if (!groupName) {
685 					groupName = lilv_world_get(
686 					        world, group, jalv->nodes.rdfs_label, nullptr);
687 				}
688 
689 				QGroupBox* groupBox = new QGroupBox(lilv_node_as_string(groupName));
690 
691 				groupLayout = new QHBoxLayout();
692 				groupBox->setLayout(groupLayout);
693 				layout->addWidget(groupBox);
694 			}
695 
696 			groupLayout->addWidget(control);
697 		} else {
698 			layout->addWidget(control);
699 		}
700 		lilv_node_free(lastGroup);
701 		lastGroup = group;
702 
703 		uint32_t index = lilv_port_get_index(plugin, port->lilv_port);
704 		jalv->ports[index].widget = control;
705 	}
706 	lilv_node_free(lastGroup);
707 
708 	grid->setLayout(layout);
709 
710 	return grid;
711 }
712 
713 bool
jalv_discover_ui(Jalv *)714 jalv_discover_ui(Jalv*)
715 {
716 	return true;
717 }
718 
719 float
jalv_ui_refresh_rate(Jalv *)720 jalv_ui_refresh_rate(Jalv*)
721 {
722 #if QT_VERSION >= 0x050000
723 	return (float)QGuiApplication::primaryScreen()->refreshRate();
724 #else
725 	return 30.0f;
726 #endif
727 }
728 
729 int
jalv_open_ui(Jalv * jalv)730 jalv_open_ui(Jalv* jalv)
731 {
732 	QMainWindow* win          = new QMainWindow();
733 	QMenu*       file_menu    = win->menuBar()->addMenu("&File");
734 	QMenu*       presets_menu = win->menuBar()->addMenu("&Presets");
735 	QAction*     quit_action  = new QAction("&Quit", win);
736 
737 	QObject::connect(quit_action, SIGNAL(triggered()), win, SLOT(close()));
738 	quit_action->setShortcuts(QKeySequence::Quit);
739 	quit_action->setStatusTip("Quit Jalv");
740 	file_menu->addAction(quit_action);
741 
742 	jalv_load_presets(jalv, add_preset_to_menu, presets_menu);
743 
744 	if (jalv->ui && !jalv->opts.generic_ui) {
745 		jalv_ui_instantiate(jalv, jalv_native_ui_type(), win);
746 	}
747 
748 	QWidget* widget = nullptr;
749 	if (jalv->ui_instance) {
750 		widget =
751 		    static_cast<QWidget*>(suil_instance_get_widget(jalv->ui_instance));
752 	} else {
753 		QWidget* controlWidget = build_control_widget(jalv);
754 
755 		widget = new QScrollArea();
756 		static_cast<QScrollArea*>(widget)->setWidget(controlWidget);
757 		static_cast<QScrollArea*>(widget)->setWidgetResizable(true);
758 		widget->setMinimumWidth(800);
759 		widget->setMinimumHeight(600);
760 	}
761 
762 	LilvNode* name = lilv_plugin_get_name(jalv->plugin);
763 	win->setWindowTitle(lilv_node_as_string(name));
764 	lilv_node_free(name);
765 
766 	win->setCentralWidget(widget);
767 	app->connect(app, SIGNAL(lastWindowClosed()), app, SLOT(quit()));
768 
769 	jalv_init_ui(jalv);
770 
771 	win->show();
772 	if (jalv->ui_instance && !jalv_ui_is_resizable(jalv)) {
773 		widget->setMinimumSize(widget->width(), widget->height());
774 		widget->setMaximumSize(widget->width(), widget->height());
775 		win->adjustSize();
776 		win->setFixedSize(win->width(), win->height());
777 	} else {
778 		win->resize(widget->width(),
779 		            widget->height() + win->menuBar()->height());
780 	}
781 
782 	Timer* timer = new Timer(jalv);
783 	timer->start(1000 / jalv->ui_update_hz);
784 
785 	int ret = app->exec();
786 	zix_sem_post(&jalv->done);
787 	return ret;
788 }
789 
790 int
jalv_close_ui(Jalv *)791 jalv_close_ui(Jalv*)
792 {
793 	app->quit();
794 	return 0;
795 }
796 
797 }  // extern "C"
798