Created
March 25, 2015 00:20
-
-
Save badosu/e9e3ed9f3e9378a28289 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Index: src/jalv_qt4.cpp | |
=================================================================== | |
--- src/jalv_qt4.cpp (revision 5618) | |
+++ src/jalv_qt4.cpp (working copy) | |
@@ -14,25 +14,180 @@ | |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
*/ | |
+#include <stdio.h> | |
+#include <math.h> | |
+ | |
#include "jalv_internal.h" | |
-#include <QApplication> | |
-#include <QMainWindow> | |
-#include <QMenuBar> | |
-#include <QPushButton> | |
-#include <QTimer> | |
+#include "lv2/lv2plug.in/ns/ext/patch/patch.h" | |
+#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h" | |
+#include <QtGui> | |
+ | |
+#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<QLayoutItem *> 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 { | |
+ int height = doLayout(QRect(0, 0, width, 0), true); | |
+ return height; | |
+} | |
+ | |
+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()); | |
+ | |
+ size += QSize(2*margin(), 2*margin()); | |
+ return size; | |
+} | |
+ | |
+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<QWidget *>(parent); | |
+ return pw->style()->pixelMetric(pm, 0, pw); | |
+ } else { | |
+ return static_cast<QLayout *>(parent)->spacing(); | |
+ } | |
+} | |
+ | |
class PresetAction : public QAction { | |
- | |
Q_OBJECT | |
public: | |
PresetAction(QObject* parent, Jalv* jalv, LilvNode* preset) | |
- : QAction(parent) | |
- , _jalv(jalv) | |
- , _preset(preset) | |
+ : QAction(parent), _jalv(jalv), _preset(preset) | |
{ | |
connect(this, SIGNAL(triggered()), | |
this, SLOT(presetChosen())); | |
@@ -47,6 +202,41 @@ | |
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: | |
+ const LilvPlugin* plugin; | |
+ struct Port* port; | |
+ | |
+ QString name; | |
+ int steps; | |
+ float max, min; | |
+ bool isInteger; | |
+ bool isEnum; | |
+ bool isLogarithmic; | |
+ std::vector<float> scalePoints; | |
+ std::map<float, const char*> scaleMap; | |
+ QLabel* label; | |
+ | |
+ void setRange(float min, float max); | |
+ QString getValueLabel(float value); | |
+ float getValue(); | |
+}; | |
+ | |
#include "jalv_qt4_meta.hpp" | |
extern "C" { | |
@@ -55,6 +245,10 @@ | |
jalv_init(int* argc, char*** argv, JalvOptions* opts) | |
{ | |
app = new QApplication(*argc, *argv, true); | |
+ | |
+ char* css = "QGroupBox::title { subcontrol-position: top center }"; | |
+ app->setStyleSheet(css); | |
+ | |
return 0; | |
} | |
@@ -83,6 +277,11 @@ | |
uint32_t protocol, | |
const void* buffer) | |
{ | |
+ Control* control = (Control*)jalv->ports[port_index].widget; | |
+ | |
+ if (!control) { return; } | |
+ | |
+ control->setValue(*(const float*)buffer); | |
} | |
class Timer : public QTimer { | |
@@ -112,6 +311,281 @@ | |
return 0; | |
} | |
+Control::Control(PortContainer portContainer, QWidget* parent) | |
+ : QGroupBox(parent) | |
+{ | |
+ plugin = portContainer.jalv->plugin; | |
+ port = portContainer.port; | |
+ dial = new QDial(); | |
+ 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); | |
+ float value = lilv_node_as_float(lilv_scale_point_get_value(p)); | |
+ | |
+ scalePoints.push_back(value); | |
+ | |
+ scaleMap[value] = 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 text */ | |
+ QString* tooltip = new QString(); | |
+ | |
+ tooltip->append(name); | |
+ | |
+ LilvNode* comment = lilv_port_get(plugin, lilvPort, rdfs_comment); | |
+ if (comment) { | |
+ tooltip->append(QString("\nComment: %1").arg(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; | |
+} | |
+ | |
+bool | |
+portGroupLesserThan(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<PortContainer> 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(), portGroupLesserThan); | |
+ | |
+ QWidget* grid = new QWidget(); | |
+ FlowLayout* flowLayout = new FlowLayout(); | |
+ QLayout* layout = flowLayout; | |
+ | |
+ LilvNode* lastGroup = NULL; | |
+ QHBoxLayout* groupLayout; | |
+ for (unsigned 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) | |
{ | |
@@ -128,19 +602,33 @@ | |
jalv_load_presets(jalv, add_preset_to_menu, presets_menu); | |
- if (jalv->ui) { | |
+ // Uncomment this line to test the generic UI | |
+ jalv->opts.generic_ui = 1; | |
+ if (jalv->ui && !jalv->opts.generic_ui) { | |
jalv_ui_instantiate(jalv, jalv_native_ui_type(jalv), win); | |
} | |
+ QWidget* widget; | |
if (jalv->ui_instance) { | |
- QWidget* widget = (QWidget*)suil_instance_get_widget(jalv->ui_instance); | |
- win->setCentralWidget(widget); | |
- } else { | |
- QPushButton* button = new QPushButton("Close"); | |
- win->setCentralWidget(button); | |
- QObject::connect(button, SIGNAL(clicked()), app, SLOT(quit())); | |
+ 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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment