From 98703d8c9944016b90f673cf00521125ada5a4c8 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 12 Sep 2015 16:03:03 +0000 Subject: Add Qt5 version. git-svn-id: http://svn.drobilla.net/lad/trunk/jalv@5726 a436a847-0d15-0410-975c-d299462d15a1 --- NEWS | 3 +- src/jalv_qt.cpp | 733 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/jalv_qt4.cpp | 706 ----------------------------------------------------- wscript | 45 +++- 4 files changed, 770 insertions(+), 717 deletions(-) create mode 100644 src/jalv_qt.cpp delete mode 100644 src/jalv_qt4.cpp diff --git a/NEWS b/NEWS index ea12df5..76481e1 100644 --- a/NEWS +++ b/NEWS @@ -9,8 +9,9 @@ jalv (1.4.7) unstable; * Exit GUI versions on interrupt * Fix semaphore correctness issues * Use moc-qt4 if present for systems with multiple Qt versions + * Add Qt5 version - -- David Robillard Tue, 07 Apr 2015 17:30:32 -0400 + -- David Robillard Sat, 12 Sep 2015 12:02:48 -0400 jalv (1.4.6) stable; diff --git a/src/jalv_qt.cpp b/src/jalv_qt.cpp new file mode 100644 index 0000000..88a024d --- /dev/null +++ b/src/jalv_qt.cpp @@ -0,0 +1,733 @@ +/* + Copyright 2007-2013 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include +#include + +#include "jalv_internal.h" + +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h" + +#include + +#if QT_VERSION >= 0x050000 +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#else +# include +#endif + +#define CONTROL_WIDTH 150 +#define DIAL_STEPS 10000 + +static QApplication* app = NULL; + +class FlowLayout : public QLayout +{ +public: + FlowLayout(QWidget* parent, + int margin = -1, + int hSpacing = -1, + int vSpacing = -1); + + FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + + ~FlowLayout(); + + void addItem(QLayoutItem* item); + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const; + bool hasHeightForWidth() const; + int heightForWidth(int) const; + int count() const; + QLayoutItem* itemAt(int index) const; + QSize minimumSize() const; + void setGeometry(const QRect &rect); + QSize sizeHint() const; + QLayoutItem* takeAt(int index); + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QList itemList; + int m_hSpace; + int m_vSpace; +}; + +FlowLayout::FlowLayout(QWidget* parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::~FlowLayout() +{ + QLayoutItem* item; + while ((item = takeAt(0))) { + delete item; + } +} + +void +FlowLayout::addItem(QLayoutItem* item) +{ + itemList.append(item); +} + +int +FlowLayout::horizontalSpacing() const +{ + if (m_hSpace >= 0) { + return m_hSpace; + } else { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int +FlowLayout::verticalSpacing() const +{ + if (m_vSpace >= 0) { + return m_vSpace; + } else { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} + +int +FlowLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem* +FlowLayout::itemAt(int index) const +{ + return itemList.value(index); +} + +QLayoutItem* +FlowLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) { + return itemList.takeAt(index); + } else { + return 0; + } +} + +Qt::Orientations +FlowLayout::expandingDirections() const +{ + return 0; +} + +bool +FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int +FlowLayout::heightForWidth(int width) const +{ + return doLayout(QRect(0, 0, width, 0), true); +} + +void +FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize +FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize +FlowLayout::minimumSize() const +{ + QSize size; + QLayoutItem* item; + foreach (item, itemList) { + size = size.expandedTo(item->minimumSize()); + } + + return size + QSize(2 * margin(), 2 * margin()); +} + +int +FlowLayout::doLayout(const QRect &rect, bool testOnly) const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + + QLayoutItem* item; + foreach (item, itemList) { + QWidget* wid = item->widget(); + + int spaceX = horizontalSpacing(); + if (spaceX == -1) { + spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, + QSizePolicy::PushButton, + Qt::Horizontal); + } + int spaceY = verticalSpacing(); + if (spaceY == -1) { + spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, + QSizePolicy::PushButton, + Qt::Vertical); + } + + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) { + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + } + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; +} + +int +FlowLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + QObject* parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget* pw = static_cast(parent); + return pw->style()->pixelMetric(pm, 0, pw); + } else { + return static_cast(parent)->spacing(); + } +} + +class PresetAction : public QAction +{ + Q_OBJECT + +public: + PresetAction(QObject* parent, Jalv* jalv, LilvNode* preset) + : QAction(parent) + , _jalv(jalv) + , _preset(preset) + { + connect(this, SIGNAL(triggered()), + this, SLOT(presetChosen())); + } + + Q_SLOT void presetChosen() { + jalv_apply_preset(_jalv, _preset); + } + +private: + Jalv* _jalv; + LilvNode* _preset; +}; + +typedef struct { + Jalv* jalv; + struct Port* port; +} PortContainer; + +class Control : public QGroupBox +{ + Q_OBJECT + +public: + Control(PortContainer portContainer, QWidget* parent = 0); + + Q_SLOT void dialChanged(int value); + + void setValue(float value); + + QDial* dial; + +private: + void setRange(float min, float max); + QString getValueLabel(float value); + float getValue(); + + const LilvPlugin* plugin; + struct Port* port; + + QLabel* label; + QString name; + int steps; + float max; + float min; + bool isInteger; + bool isEnum; + bool isLogarithmic; + + std::vector scalePoints; + std::map scaleMap; +}; + +#if QT_VERSION >= 0x050000 +# include "jalv_qt5_meta.hpp" +#else +# include "jalv_qt4_meta.hpp" +#endif + +extern "C" { + +int +jalv_init(int* argc, char*** argv, JalvOptions* opts) +{ + app = new QApplication(*argc, *argv, true); + app->setStyleSheet("QGroupBox::title { subcontrol-position: top center }"); + + return 0; +} + +const char* +jalv_native_ui_type(Jalv* jalv) +{ +#if QT_VERSION >= 0x050000 + return "http://lv2plug.in/ns/extensions/ui#Qt5UI"; +#else + return "http://lv2plug.in/ns/extensions/ui#Qt4UI"; +#endif +} + +int +jalv_ui_resize(Jalv* jalv, int width, int height) +{ + if (jalv->ui_instance && width > 0 && height > 0) { + QWidget* widget = (QWidget*)suil_instance_get_widget(jalv->ui_instance); + if (widget) { + widget->resize(width, height); + } + } + return 0; +} + +void +jalv_ui_port_event(Jalv* jalv, + uint32_t port_index, + uint32_t buffer_size, + uint32_t protocol, + const void* buffer) +{ + Control* control = (Control*)jalv->ports[port_index].widget; + if (control) { + control->setValue(*(const float*)buffer); + } +} + +class Timer : public QTimer +{ +public: + explicit Timer(Jalv* jalv) : _jalv(jalv) {} + + void timerEvent(QTimerEvent* e) { + jalv_update(_jalv); + } + +private: + Jalv* _jalv; +}; + +static int +add_preset_to_menu(Jalv* jalv, + const LilvNode* node, + const LilvNode* title, + void* data) +{ + QMenu* menu = (QMenu*)data; + const char* label = lilv_node_as_string(title); + + QAction* action = new PresetAction(menu, jalv, lilv_node_duplicate(node)); + action->setText(label); + menu->addAction(action); + return 0; +} + +Control::Control(PortContainer portContainer, QWidget* parent) + : QGroupBox(parent) + , dial(new QDial()) + , plugin(portContainer.jalv->plugin) + , port(portContainer.port) + , label(new QLabel()) +{ + const LilvPort* lilvPort = port->lilv_port; + LilvWorld* world = portContainer.jalv->world; + + LilvNode* lv2_integer = lilv_new_uri(world, LV2_CORE__integer); + LilvNode* lv2_toggled = lilv_new_uri(world, LV2_CORE__toggled); + LilvNode* lv2_enumeration = lilv_new_uri(world, LV2_CORE__enumeration); + LilvNode* logarithmic = lilv_new_uri(world, LV2_PORT_PROPS__logarithmic); + LilvNode* rangeSteps = lilv_new_uri(world, LV2_PORT_PROPS__rangeSteps); + LilvNode* rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment"); + + LilvNode* nmin; + LilvNode* nmax; + LilvNode* ndef; + lilv_port_get_range(plugin, lilvPort, &ndef, &nmin, &nmax); + + if (lilv_port_has_property(plugin, lilvPort, rangeSteps)) { + steps = lilv_node_as_int(rangeSteps); + } else { + steps = DIAL_STEPS; + } + + // Fill scalePoints Map + LilvScalePoints* sp = lilv_port_get_scale_points(plugin, lilvPort); + if (sp) { + LILV_FOREACH(scale_points, s, sp) { + const LilvScalePoint* p = lilv_scale_points_get(sp, s); + const LilvNode* val = lilv_scale_point_get_value(p); + if (!lilv_node_is_float(val)) { + continue; + } + + const float f = lilv_node_as_float(val); + scalePoints.push_back(f); + scaleMap[f] = lilv_node_as_string(lilv_scale_point_get_label(p)); + } + + lilv_scale_points_free(sp); + } + + // Check port properties + isLogarithmic = lilv_port_has_property(plugin, lilvPort, logarithmic); + isInteger = lilv_port_has_property(plugin, lilvPort, lv2_integer); + isEnum = lilv_port_has_property(plugin, lilvPort, lv2_enumeration); + + if (lilv_port_has_property(plugin, lilvPort, lv2_toggled)) { + isInteger = true; + + if (!scaleMap[0]) { + scaleMap[0] = "Off"; + } + if (!scaleMap[1]) { + scaleMap[1] = "On" ; + } + } + + // Find and set min, max and default values for port + float defaultValue = ndef ? lilv_node_as_float(ndef) : port->control; + setRange(lilv_node_as_float(nmin), lilv_node_as_float(nmax)); + setValue(defaultValue); + + // Fill layout + QVBoxLayout* layout = new QVBoxLayout(); + layout->addWidget(label, 0, Qt::AlignHCenter); + layout->addWidget(dial, 0, Qt::AlignHCenter); + setLayout(layout); + + setMinimumWidth(CONTROL_WIDTH); + setMaximumWidth(CONTROL_WIDTH); + + LilvNode* nname = lilv_port_get_name(plugin, lilvPort); + name = QString("%1").arg(lilv_node_as_string(nname)); + + // Handle long names + if (fontMetrics().width(name) > CONTROL_WIDTH) { + setTitle(fontMetrics().elidedText(name, Qt::ElideRight, CONTROL_WIDTH)); + } else { + setTitle(name); + } + + // Set tooltip if comment is available + LilvNode* comment = lilv_port_get(plugin, lilvPort, rdfs_comment); + if (comment) { + QString* tooltip = new QString(); + tooltip->append(lilv_node_as_string(comment)); + setToolTip(*tooltip); + } + + setFlat(true); + + connect(dial, SIGNAL(valueChanged(int)), this, SLOT(dialChanged(int))); + + lilv_node_free(nmin); + lilv_node_free(nmax); + lilv_node_free(ndef); + lilv_node_free(nname); + lilv_node_free(lv2_integer); + lilv_node_free(lv2_toggled); + lilv_node_free(lv2_enumeration); + lilv_node_free(logarithmic); + lilv_node_free(rangeSteps); + lilv_node_free(comment); +} + +void +Control::setValue(float value) +{ + float step; + + if (isInteger) { + step = value; + } else if (isEnum) { + step = (std::find(scalePoints.begin(), scalePoints.end(), value) + - scalePoints.begin()); + } else if (isLogarithmic) { + step = steps * log(value / min) / log(max / min); + } else { + step = value * steps; + } + + dial->setValue(step); + label->setText(getValueLabel(value)); +} + +QString +Control::getValueLabel(float value) +{ + if (scaleMap[value]) { + if (fontMetrics().width(scaleMap[value]) > CONTROL_WIDTH) { + label->setToolTip(scaleMap[value]); + return fontMetrics().elidedText(QString(scaleMap[value]), + Qt::ElideRight, + CONTROL_WIDTH); + } + return scaleMap[value]; + } + + return QString("%1").arg(value); +} + +void +Control::setRange(float minRange, float maxRange) +{ + min = minRange; + max = maxRange; + + if (isLogarithmic) { + minRange = 1; + maxRange = steps; + } else if (isEnum) { + minRange = 0; + maxRange = scalePoints.size() - 1; + } else if (!isInteger) { + minRange *= steps; + maxRange *= steps; + } + + dial->setRange(minRange, maxRange); +} + +float +Control::getValue() +{ + if (isEnum) { + return scalePoints[dial->value()]; + } else if (isInteger) { + return dial->value(); + } else if (isLogarithmic) { + return min * pow(max / min, (float)dial->value() / steps); + } else { + return (float)dial->value() / steps; + } +} + +void +Control::dialChanged(int dialValue) +{ + float value = getValue(); + + label->setText(getValueLabel(value)); + port->control = value; +} + +static bool +portGroupLessThan(const PortContainer &p1, const PortContainer &p2) +{ + Jalv* jalv = p1.jalv; + const LilvPort* port1 = p1.port->lilv_port; + const LilvPort* port2 = p2.port->lilv_port; + + LilvNode* group1 = lilv_port_get( + jalv->plugin, port1, jalv->nodes.pg_group); + LilvNode* group2 = lilv_port_get( + jalv->plugin, port2, jalv->nodes.pg_group); + + const int cmp = (group1 && group2) + ? strcmp(lilv_node_as_string(group1), lilv_node_as_string(group2)) + : ((intptr_t)group1 - (intptr_t)group2); + + lilv_node_free(group2); + lilv_node_free(group1); + + return cmp < 0; +} + +static QWidget* +build_control_widget(Jalv* jalv) +{ + const LilvPlugin* plugin = jalv->plugin; + LilvWorld* world = jalv->world; + + LilvNode* pprop_notOnGUI = lilv_new_uri(world, LV2_PORT_PROPS__notOnGUI); + + QList portContainers; + for (unsigned i = 0; i < jalv->num_ports; ++i) { + if (!jalv->opts.show_hidden && + lilv_port_has_property(plugin, jalv->ports[i].lilv_port, pprop_notOnGUI)) { + continue; + } + + if (jalv->ports[i].type == TYPE_CONTROL) { + PortContainer portContainer; + portContainer.jalv = jalv; + portContainer.port = &jalv->ports[i]; + portContainers.append(portContainer); + } + } + + qSort(portContainers.begin(), portContainers.end(), portGroupLessThan); + + QWidget* grid = new QWidget(); + FlowLayout* flowLayout = new FlowLayout(); + QLayout* layout = flowLayout; + + LilvNode* lastGroup = NULL; + QHBoxLayout* groupLayout; + for (int i = 0; i < portContainers.count(); ++i) { + PortContainer portContainer = portContainers[i]; + Port* port = portContainer.port; + + Control* control = new Control(portContainer); + LilvNode* group = lilv_port_get( + plugin, port->lilv_port, jalv->nodes.pg_group); + if (group) { + if (!lilv_node_equals(group, lastGroup)) { + /* Group has changed */ + LilvNode* groupName = lilv_world_get( + world, group, jalv->nodes.lv2_name, NULL); + QGroupBox* groupBox = new QGroupBox(lilv_node_as_string(groupName)); + + groupLayout = new QHBoxLayout(); + groupBox->setLayout(groupLayout); + layout->addWidget(groupBox); + } + + groupLayout->addWidget(control); + } else { + layout->addWidget(control); + } + lastGroup = group; + + uint32_t index = lilv_port_get_index(plugin, port->lilv_port); + jalv->ports[index].widget = control; + } + + grid->setLayout(layout); + + lilv_node_free(pprop_notOnGUI); + + return grid; +} + +int +jalv_open_ui(Jalv* jalv) +{ + QMainWindow* win = new QMainWindow(); + QMenu* file_menu = win->menuBar()->addMenu("&File"); + QMenu* presets_menu = win->menuBar()->addMenu("&Presets"); + QAction* quit_action = new QAction("&Quit", win); + + QObject::connect(quit_action, SIGNAL(triggered()), win, SLOT(close())); + quit_action->setShortcuts(QKeySequence::Quit); + quit_action->setStatusTip("Quit Jalv"); + file_menu->addAction(quit_action); + jalv->has_ui = true; + + jalv_load_presets(jalv, add_preset_to_menu, presets_menu); + + if (jalv->ui && !jalv->opts.generic_ui) { + jalv_ui_instantiate(jalv, jalv_native_ui_type(jalv), win); + } + + QWidget* widget; + if (jalv->ui_instance) { + widget = (QWidget*)suil_instance_get_widget(jalv->ui_instance); + } else { + QWidget* controlWidget = build_control_widget(jalv); + + widget = new QScrollArea(); + ((QScrollArea*)widget)->setWidget(controlWidget); + ((QScrollArea*)widget)->setWidgetResizable(true); + widget->setMinimumWidth(800); + widget->setMinimumHeight(600); + } + + LilvNode* name = lilv_plugin_get_name(jalv->plugin); + win->setWindowTitle(lilv_node_as_string(name)); + lilv_node_free(name); + + win->setCentralWidget(widget); + app->connect(app, SIGNAL(lastWindowClosed()), app, SLOT(quit())); + + win->show(); + + Timer* timer = new Timer(jalv); + timer->start(1000 / jalv->ui_update_hz); + + int ret = app->exec(); + zix_sem_post(jalv->done); + return ret; +} + +int +jalv_close_ui(Jalv* jalv) +{ + app->quit(); + return 0; +} + +} // extern "C" diff --git a/src/jalv_qt4.cpp b/src/jalv_qt4.cpp deleted file mode 100644 index 38dc3cf..0000000 --- a/src/jalv_qt4.cpp +++ /dev/null @@ -1,706 +0,0 @@ -/* - Copyright 2007-2013 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include -#include - -#include "jalv_internal.h" - -#include "lv2/lv2plug.in/ns/ext/patch/patch.h" -#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h" - -#include - -#define CONTROL_WIDTH 150 -#define DIAL_STEPS 10000 - -static QApplication* app = NULL; - -class FlowLayout : public QLayout -{ -public: - FlowLayout(QWidget* parent, - int margin = -1, - int hSpacing = -1, - int vSpacing = -1); - - FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); - - ~FlowLayout(); - - void addItem(QLayoutItem* item); - int horizontalSpacing() const; - int verticalSpacing() const; - Qt::Orientations expandingDirections() const; - bool hasHeightForWidth() const; - int heightForWidth(int) const; - int count() const; - QLayoutItem* itemAt(int index) const; - QSize minimumSize() const; - void setGeometry(const QRect &rect); - QSize sizeHint() const; - QLayoutItem* takeAt(int index); - -private: - int doLayout(const QRect &rect, bool testOnly) const; - int smartSpacing(QStyle::PixelMetric pm) const; - - QList itemList; - int m_hSpace; - int m_vSpace; -}; - -FlowLayout::FlowLayout(QWidget* parent, int margin, int hSpacing, int vSpacing) - : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) -{ - setContentsMargins(margin, margin, margin, margin); -} - -FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) - : m_hSpace(hSpacing), m_vSpace(vSpacing) -{ - setContentsMargins(margin, margin, margin, margin); -} - -FlowLayout::~FlowLayout() -{ - QLayoutItem* item; - while ((item = takeAt(0))) { - delete item; - } -} - -void -FlowLayout::addItem(QLayoutItem* item) -{ - itemList.append(item); -} - -int -FlowLayout::horizontalSpacing() const -{ - if (m_hSpace >= 0) { - return m_hSpace; - } else { - return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); - } -} - -int -FlowLayout::verticalSpacing() const -{ - if (m_vSpace >= 0) { - return m_vSpace; - } else { - return smartSpacing(QStyle::PM_LayoutVerticalSpacing); - } -} - -int -FlowLayout::count() const -{ - return itemList.size(); -} - -QLayoutItem* -FlowLayout::itemAt(int index) const -{ - return itemList.value(index); -} - -QLayoutItem* -FlowLayout::takeAt(int index) -{ - if (index >= 0 && index < itemList.size()) { - return itemList.takeAt(index); - } else { - return 0; - } -} - -Qt::Orientations -FlowLayout::expandingDirections() const -{ - return 0; -} - -bool -FlowLayout::hasHeightForWidth() const -{ - return true; -} - -int -FlowLayout::heightForWidth(int width) const -{ - return doLayout(QRect(0, 0, width, 0), true); -} - -void -FlowLayout::setGeometry(const QRect &rect) -{ - QLayout::setGeometry(rect); - doLayout(rect, false); -} - -QSize -FlowLayout::sizeHint() const -{ - return minimumSize(); -} - -QSize -FlowLayout::minimumSize() const -{ - QSize size; - QLayoutItem* item; - foreach (item, itemList) { - size = size.expandedTo(item->minimumSize()); - } - - return size + QSize(2 * margin(), 2 * margin()); -} - -int -FlowLayout::doLayout(const QRect &rect, bool testOnly) const -{ - int left, top, right, bottom; - getContentsMargins(&left, &top, &right, &bottom); - - QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); - int x = effectiveRect.x(); - int y = effectiveRect.y(); - int lineHeight = 0; - - QLayoutItem* item; - foreach (item, itemList) { - QWidget* wid = item->widget(); - - int spaceX = horizontalSpacing(); - if (spaceX == -1) { - spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, - QSizePolicy::PushButton, - Qt::Horizontal); - } - int spaceY = verticalSpacing(); - if (spaceY == -1) { - spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, - QSizePolicy::PushButton, - Qt::Vertical); - } - - int nextX = x + item->sizeHint().width() + spaceX; - if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { - x = effectiveRect.x(); - y = y + lineHeight + spaceY; - nextX = x + item->sizeHint().width() + spaceX; - lineHeight = 0; - } - - if (!testOnly) { - item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); - } - - x = nextX; - lineHeight = qMax(lineHeight, item->sizeHint().height()); - } - return y + lineHeight - rect.y() + bottom; -} - -int -FlowLayout::smartSpacing(QStyle::PixelMetric pm) const -{ - QObject* parent = this->parent(); - if (!parent) { - return -1; - } else if (parent->isWidgetType()) { - QWidget* pw = static_cast(parent); - return pw->style()->pixelMetric(pm, 0, pw); - } else { - return static_cast(parent)->spacing(); - } -} - -class PresetAction : public QAction -{ - Q_OBJECT - -public: - PresetAction(QObject* parent, Jalv* jalv, LilvNode* preset) - : QAction(parent) - , _jalv(jalv) - , _preset(preset) - { - connect(this, SIGNAL(triggered()), - this, SLOT(presetChosen())); - } - - Q_SLOT void presetChosen() { - jalv_apply_preset(_jalv, _preset); - } - -private: - Jalv* _jalv; - LilvNode* _preset; -}; - -typedef struct { - Jalv* jalv; - struct Port* port; -} PortContainer; - -class Control : public QGroupBox -{ - Q_OBJECT - -public: - Control(PortContainer portContainer, QWidget* parent = 0); - - Q_SLOT void dialChanged(int value); - - void setValue(float value); - - QDial* dial; - -private: - void setRange(float min, float max); - QString getValueLabel(float value); - float getValue(); - - const LilvPlugin* plugin; - struct Port* port; - - QLabel* label; - QString name; - int steps; - float max; - float min; - bool isInteger; - bool isEnum; - bool isLogarithmic; - - std::vector scalePoints; - std::map scaleMap; -}; - -#include "jalv_qt4_meta.hpp" - -extern "C" { - -int -jalv_init(int* argc, char*** argv, JalvOptions* opts) -{ - app = new QApplication(*argc, *argv, true); - app->setStyleSheet("QGroupBox::title { subcontrol-position: top center }"); - - return 0; -} - -const char* -jalv_native_ui_type(Jalv* jalv) -{ - return "http://lv2plug.in/ns/extensions/ui#Qt4UI"; -} - -int -jalv_ui_resize(Jalv* jalv, int width, int height) -{ - if (jalv->ui_instance && width > 0 && height > 0) { - QWidget* widget = (QWidget*)suil_instance_get_widget(jalv->ui_instance); - if (widget) { - widget->resize(width, height); - } - } - return 0; -} - -void -jalv_ui_port_event(Jalv* jalv, - uint32_t port_index, - uint32_t buffer_size, - uint32_t protocol, - const void* buffer) -{ - Control* control = (Control*)jalv->ports[port_index].widget; - if (control) { - control->setValue(*(const float*)buffer); - } -} - -class Timer : public QTimer -{ -public: - explicit Timer(Jalv* jalv) : _jalv(jalv) {} - - void timerEvent(QTimerEvent* e) { - jalv_update(_jalv); - } - -private: - Jalv* _jalv; -}; - -static int -add_preset_to_menu(Jalv* jalv, - const LilvNode* node, - const LilvNode* title, - void* data) -{ - QMenu* menu = (QMenu*)data; - const char* label = lilv_node_as_string(title); - - QAction* action = new PresetAction(menu, jalv, lilv_node_duplicate(node)); - action->setText(label); - menu->addAction(action); - return 0; -} - -Control::Control(PortContainer portContainer, QWidget* parent) - : QGroupBox(parent) - , dial(new QDial()) - , plugin(portContainer.jalv->plugin) - , port(portContainer.port) - , label(new QLabel()) -{ - const LilvPort* lilvPort = port->lilv_port; - LilvWorld* world = portContainer.jalv->world; - - LilvNode* lv2_integer = lilv_new_uri(world, LV2_CORE__integer); - LilvNode* lv2_toggled = lilv_new_uri(world, LV2_CORE__toggled); - LilvNode* lv2_enumeration = lilv_new_uri(world, LV2_CORE__enumeration); - LilvNode* logarithmic = lilv_new_uri(world, LV2_PORT_PROPS__logarithmic); - LilvNode* rangeSteps = lilv_new_uri(world, LV2_PORT_PROPS__rangeSteps); - LilvNode* rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment"); - - LilvNode* nmin; - LilvNode* nmax; - LilvNode* ndef; - lilv_port_get_range(plugin, lilvPort, &ndef, &nmin, &nmax); - - if (lilv_port_has_property(plugin, lilvPort, rangeSteps)) { - steps = lilv_node_as_int(rangeSteps); - } else { - steps = DIAL_STEPS; - } - - // Fill scalePoints Map - LilvScalePoints* sp = lilv_port_get_scale_points(plugin, lilvPort); - if (sp) { - LILV_FOREACH(scale_points, s, sp) { - const LilvScalePoint* p = lilv_scale_points_get(sp, s); - const LilvNode* val = lilv_scale_point_get_value(p); - if (!lilv_node_is_float(val)) { - continue; - } - - const float f = lilv_node_as_float(val); - scalePoints.push_back(f); - scaleMap[f] = lilv_node_as_string(lilv_scale_point_get_label(p)); - } - - lilv_scale_points_free(sp); - } - - // Check port properties - isLogarithmic = lilv_port_has_property(plugin, lilvPort, logarithmic); - isInteger = lilv_port_has_property(plugin, lilvPort, lv2_integer); - isEnum = lilv_port_has_property(plugin, lilvPort, lv2_enumeration); - - if (lilv_port_has_property(plugin, lilvPort, lv2_toggled)) { - isInteger = true; - - if (!scaleMap[0]) { - scaleMap[0] = "Off"; - } - if (!scaleMap[1]) { - scaleMap[1] = "On" ; - } - } - - // Find and set min, max and default values for port - float defaultValue = ndef ? lilv_node_as_float(ndef) : port->control; - setRange(lilv_node_as_float(nmin), lilv_node_as_float(nmax)); - setValue(defaultValue); - - // Fill layout - QVBoxLayout* layout = new QVBoxLayout(); - layout->addWidget(label, 0, Qt::AlignHCenter); - layout->addWidget(dial, 0, Qt::AlignHCenter); - setLayout(layout); - - setMinimumWidth(CONTROL_WIDTH); - setMaximumWidth(CONTROL_WIDTH); - - LilvNode* nname = lilv_port_get_name(plugin, lilvPort); - name = QString("%1").arg(lilv_node_as_string(nname)); - - // Handle long names - if (fontMetrics().width(name) > CONTROL_WIDTH) { - setTitle(fontMetrics().elidedText(name, Qt::ElideRight, CONTROL_WIDTH)); - } else { - setTitle(name); - } - - // Set tooltip if comment is available - LilvNode* comment = lilv_port_get(plugin, lilvPort, rdfs_comment); - if (comment) { - QString* tooltip = new QString(); - tooltip->append(lilv_node_as_string(comment)); - setToolTip(*tooltip); - } - - setFlat(true); - - connect(dial, SIGNAL(valueChanged(int)), this, SLOT(dialChanged(int))); - - lilv_node_free(nmin); - lilv_node_free(nmax); - lilv_node_free(ndef); - lilv_node_free(nname); - lilv_node_free(lv2_integer); - lilv_node_free(lv2_toggled); - lilv_node_free(lv2_enumeration); - lilv_node_free(logarithmic); - lilv_node_free(rangeSteps); - lilv_node_free(comment); -} - -void -Control::setValue(float value) -{ - float step; - - if (isInteger) { - step = value; - } else if (isEnum) { - step = (std::find(scalePoints.begin(), scalePoints.end(), value) - - scalePoints.begin()); - } else if (isLogarithmic) { - step = steps * log(value / min) / log(max / min); - } else { - step = value * steps; - } - - dial->setValue(step); - label->setText(getValueLabel(value)); -} - -QString -Control::getValueLabel(float value) -{ - if (scaleMap[value]) { - if (fontMetrics().width(scaleMap[value]) > CONTROL_WIDTH) { - label->setToolTip(scaleMap[value]); - return fontMetrics().elidedText(QString(scaleMap[value]), - Qt::ElideRight, - CONTROL_WIDTH); - } - return scaleMap[value]; - } - - return QString("%1").arg(value); -} - -void -Control::setRange(float minRange, float maxRange) -{ - min = minRange; - max = maxRange; - - if (isLogarithmic) { - minRange = 1; - maxRange = steps; - } else if (isEnum) { - minRange = 0; - maxRange = scalePoints.size() - 1; - } else if (!isInteger) { - minRange *= steps; - maxRange *= steps; - } - - dial->setRange(minRange, maxRange); -} - -float -Control::getValue() -{ - if (isEnum) { - return scalePoints[dial->value()]; - } else if (isInteger) { - return dial->value(); - } else if (isLogarithmic) { - return min * pow(max / min, (float)dial->value() / steps); - } else { - return (float)dial->value() / steps; - } -} - -void -Control::dialChanged(int dialValue) -{ - float value = getValue(); - - label->setText(getValueLabel(value)); - port->control = value; -} - -static bool -portGroupLessThan(const PortContainer &p1, const PortContainer &p2) -{ - Jalv* jalv = p1.jalv; - const LilvPort* port1 = p1.port->lilv_port; - const LilvPort* port2 = p2.port->lilv_port; - - LilvNode* group1 = lilv_port_get( - jalv->plugin, port1, jalv->nodes.pg_group); - LilvNode* group2 = lilv_port_get( - jalv->plugin, port2, jalv->nodes.pg_group); - - const int cmp = (group1 && group2) - ? strcmp(lilv_node_as_string(group1), lilv_node_as_string(group2)) - : ((intptr_t)group1 - (intptr_t)group2); - - lilv_node_free(group2); - lilv_node_free(group1); - - return cmp < 0; -} - -static QWidget* -build_control_widget(Jalv* jalv) -{ - const LilvPlugin* plugin = jalv->plugin; - LilvWorld* world = jalv->world; - - LilvNode* pprop_notOnGUI = lilv_new_uri(world, LV2_PORT_PROPS__notOnGUI); - - QList portContainers; - for (unsigned i = 0; i < jalv->num_ports; ++i) { - if (!jalv->opts.show_hidden && - lilv_port_has_property(plugin, jalv->ports[i].lilv_port, pprop_notOnGUI)) { - continue; - } - - if (jalv->ports[i].type == TYPE_CONTROL) { - PortContainer portContainer; - portContainer.jalv = jalv; - portContainer.port = &jalv->ports[i]; - portContainers.append(portContainer); - } - } - - qSort(portContainers.begin(), portContainers.end(), portGroupLessThan); - - QWidget* grid = new QWidget(); - FlowLayout* flowLayout = new FlowLayout(); - QLayout* layout = flowLayout; - - LilvNode* lastGroup = NULL; - QHBoxLayout* groupLayout; - for (int i = 0; i < portContainers.count(); ++i) { - PortContainer portContainer = portContainers[i]; - Port* port = portContainer.port; - - Control* control = new Control(portContainer); - LilvNode* group = lilv_port_get( - plugin, port->lilv_port, jalv->nodes.pg_group); - if (group) { - if (!lilv_node_equals(group, lastGroup)) { - /* Group has changed */ - LilvNode* groupName = lilv_world_get( - world, group, jalv->nodes.lv2_name, NULL); - QGroupBox* groupBox = new QGroupBox(lilv_node_as_string(groupName)); - - groupLayout = new QHBoxLayout(); - groupBox->setLayout(groupLayout); - layout->addWidget(groupBox); - } - - groupLayout->addWidget(control); - } else { - layout->addWidget(control); - } - lastGroup = group; - - uint32_t index = lilv_port_get_index(plugin, port->lilv_port); - jalv->ports[index].widget = control; - } - - grid->setLayout(layout); - - lilv_node_free(pprop_notOnGUI); - - return grid; -} - -int -jalv_open_ui(Jalv* jalv) -{ - QMainWindow* win = new QMainWindow(); - QMenu* file_menu = win->menuBar()->addMenu("&File"); - QMenu* presets_menu = win->menuBar()->addMenu("&Presets"); - QAction* quit_action = new QAction("&Quit", win); - - QObject::connect(quit_action, SIGNAL(triggered()), win, SLOT(close())); - quit_action->setShortcuts(QKeySequence::Quit); - quit_action->setStatusTip("Quit Jalv"); - file_menu->addAction(quit_action); - jalv->has_ui = TRUE; - - jalv_load_presets(jalv, add_preset_to_menu, presets_menu); - - if (jalv->ui && !jalv->opts.generic_ui) { - jalv_ui_instantiate(jalv, jalv_native_ui_type(jalv), win); - } - - QWidget* widget; - if (jalv->ui_instance) { - widget = (QWidget*)suil_instance_get_widget(jalv->ui_instance); - } else { - QWidget* controlWidget = build_control_widget(jalv); - - widget = new QScrollArea(); - ((QScrollArea*)widget)->setWidget(controlWidget); - ((QScrollArea*)widget)->setWidgetResizable(true); - widget->setMinimumWidth(800); - widget->setMinimumHeight(600); - } - - LilvNode* name = lilv_plugin_get_name(jalv->plugin); - win->setWindowTitle(lilv_node_as_string(name)); - lilv_node_free(name); - - win->setCentralWidget(widget); - app->connect(app, SIGNAL(lastWindowClosed()), app, SLOT(quit())); - - win->show(); - - Timer* timer = new Timer(jalv); - timer->start(1000 / jalv->ui_update_hz); - - int ret = app->exec(); - zix_sem_post(jalv->done); - return ret; -} - -int -jalv_close_ui(Jalv* jalv) -{ - app->quit(); - return 0; -} - -} // extern "C" diff --git a/wscript b/wscript index 0fbe94a..5b715c0 100644 --- a/wscript +++ b/wscript @@ -24,6 +24,10 @@ def options(opt): opt.add_option('--no-qt', action='store_true', default=False, dest='no_qt', help="Do not build Qt GUI") + opt.add_option('--no-qt4', action='store_true', dest='no_qt4', + help='Do not build support for Qt4') + opt.add_option('--no-qt5', action='store_true', dest='no_qt5', + help='Do not build support for Qt5') def configure(conf): conf.line_just = 52 @@ -53,11 +57,17 @@ def configure(conf): autowaf.check_pkg(conf, 'gtkmm-2.4', uselib_store='GTKMM2', atleast_version='2.20.0', mandatory=False) if not Options.options.no_qt: - autowaf.check_pkg(conf, 'QtGui', uselib_store='QT4', - atleast_version='4.0.0', mandatory=False) - if conf.env.HAVE_QT4: - if not conf.find_program('moc-qt4', var='MOC', mandatory=False): - conf.find_program('moc') + if not Options.options.no_qt4: + autowaf.check_pkg(conf, 'QtGui', uselib_store='QT4', + atleast_version='4.0.0', mandatory=False) + if conf.env.HAVE_QT4: + if not conf.find_program('moc-qt4', var='MOC4', mandatory=False): + conf.find_program('moc', var='MOC4') + + if not Options.options.no_qt5: + autowaf.check_pkg(conf, 'Qt5Widgets', uselib_store='QT5', + atleast_version='5.1.0', mandatory=False) + conf.find_program('moc', var='MOC5') conf.check(function_name='jack_port_type_get_buffer_size', header_name='jack/jack.h', @@ -84,6 +94,7 @@ def configure(conf): autowaf.display_msg(conf, "Gtk 3.0 support", bool(conf.env.HAVE_GTK3)) autowaf.display_msg(conf, "Gtkmm 2.0 support", bool(conf.env.HAVE_GTKMM2)) autowaf.display_msg(conf, "Qt 4.0 support", bool(conf.env.HAVE_QT4)) + autowaf.display_msg(conf, "Qt 5.0 support", bool(conf.env.HAVE_QT5)) print('') def build(bld): @@ -130,19 +141,33 @@ def build(bld): install_path = '${BINDIR}') autowaf.use_lib(bld, obj, libs + ' GTKMM2') - # Qt version + # Qt4 version if bld.env.HAVE_QT4: - obj = bld(rule = '${MOC} ${SRC} > ${TGT}', - source = 'src/jalv_qt4.cpp', + obj = bld(rule = '${MOC4} ${SRC} > ${TGT}', + source = 'src/jalv_qt.cpp', target = 'jalv_qt4_meta.hpp') obj = bld(features = 'c cxx cxxprogram', - source = source + ' src/jalv_qt4.cpp', - target = 'jalv.qt', + source = source + ' src/jalv_qt.cpp', + target = 'jalv.qt4', includes = ['.', 'src'], lib = ['pthread'], install_path = '${BINDIR}') autowaf.use_lib(bld, obj, libs + ' QT4') + # Qt5 version + if bld.env.HAVE_QT5: + obj = bld(rule = '${MOC5} ${SRC} > ${TGT}', + source = 'src/jalv_qt.cpp', + target = 'jalv_qt5_meta.hpp') + obj = bld(features = 'c cxx cxxprogram', + source = source + ' src/jalv_qt.cpp', + target = 'jalv.qt5', + includes = ['.', 'src'], + lib = ['pthread'], + install_path = '${BINDIR}', + cxxflags = ['-fPIC']) + autowaf.use_lib(bld, obj, libs + ' QT5') + # Man pages bld.install_files('${MANDIR}/man1', bld.path.ant_glob('doc/*.1')) -- cgit v1.2.1