Skip to content

Instantly share code, notes, and snippets.

@badosu
Created March 25, 2015 00:20
Show Gist options
  • Save badosu/e9e3ed9f3e9378a28289 to your computer and use it in GitHub Desktop.
Save badosu/e9e3ed9f3e9378a28289 to your computer and use it in GitHub Desktop.
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