--- /dev/null
+#-------------------------------------------------
+#
+# Project created by QtCreator 2013-03-28T12:34:18
+#
+#-------------------------------------------------
+
+QT += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+TARGET = FFBChecker
+TEMPLATE = app
+
+
+SOURCES += main.cpp\
+ mainwindow.cpp \
+ deviceprober.cpp \
+ ffbdevice.cpp \
+ ffbeffect.cpp \
+ ffbeffectparameters.cpp \
+ ffbconstanteffectparameters.cpp \
+ effectsettings.cpp \
+ constanteffectsettings.cpp \
+ envelopesettings.cpp \
+ periodiceffectsettings.cpp \
+ ffbconstanteffect.cpp \
+ ffbeffectfactory.cpp \
+ ffbenvelopeparameters.cpp \
+ ffbperiodiceffectparameters.cpp \
+ ffbperiodiceffect.cpp \
+ ffbnulleffect.cpp
+
+HEADERS += mainwindow.h \
+ deviceprober.h \
+ ffbdevice.h \
+ helpers.h \
+ ffbeffect.h \
+ globals.h \
+ ffbeffectparameters.h \
+ ffbconstanteffectparameters.h \
+ effectsettings.h \
+ constanteffectsettings.h \
+ envelopesettings.h \
+ periodiceffectsettings.h \
+ ffbconstanteffect.h \
+ ffbeffectfactory.h \
+ ffbenvelopeparameters.h \
+ ffbperiodiceffectparameters.h \
+ ffbperiodiceffect.h \
+ ffbnulleffect.h
+
+FORMS += mainwindow.ui \
+ constanteffectsettings.ui \
+ envelopesettings.ui \
+ periodiceffectsettings.ui
+
+QMAKE_CXXFLAGS += -std=c++11 -Wall
--- /dev/null
+#include "constanteffectsettings.h"
+#include "ui_constanteffectsettings.h"
+
+ConstantEffectSettings::ConstantEffectSettings(QWidget* parent) :
+ EffectSettings(parent),
+ ui(new Ui::ConstantEffectSettings)
+{
+ ui->setupUi(this);
+}
+
+const EnvelopeSettings* ConstantEffectSettings::envelopeSettings() const
+{
+ return ui->qwid_envelope;
+}
+
+QString ConstantEffectSettings::level() const
+{
+ return ui->qle_level->text();
+}
+
+bool ConstantEffectSettings::fillFromParameters(const std::shared_ptr<FFBEffectParameters> params)
+{
+ try {
+ const std::shared_ptr<FFBConstantEffectParameters> cfParams = std::dynamic_pointer_cast<FFBConstantEffectParameters>(params);
+
+ ui->qle_level->setText(QString::number(cfParams->level));
+ return ui->qwid_envelope->fillFromParameters(params);
+ } catch (std::bad_cast& ex) {
+ qCritical(ex.what());
+ return false;
+ }
+ return false;
+}
+
+
+ConstantEffectSettings::~ConstantEffectSettings()
+{
+ delete ui;
+}
--- /dev/null
+#ifndef CONSTANTEFFECTSETTINGS_H
+#define CONSTANTEFFECTSETTINGS_H
+
+#include "effectsettings.h"
+#include "envelopesettings.h"
+#include "ffbconstanteffectparameters.h"
+
+namespace Ui {
+ class ConstantEffectSettings;
+}
+
+class ConstantEffectSettings : public EffectSettings
+{
+ Q_OBJECT
+
+public:
+ explicit ConstantEffectSettings(QWidget* parent = 0);
+ ~ConstantEffectSettings();
+ const EnvelopeSettings* envelopeSettings() const;
+ bool fillFromParameters(const std::shared_ptr<FFBEffectParameters> params);
+ QString level() const;
+
+private:
+ Ui::ConstantEffectSettings* ui;
+
+};
+
+#endif // CONSTANTEFFECTSETTINGS_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConstantEffectSettings</class>
+ <widget class="QWidget" name="ConstantEffectSettings">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMinimumSize</enum>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="ql_level">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Level:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="qle_level">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="EnvelopeSettings" name="qwid_envelope" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>EnvelopeSettings</class>
+ <extends>QWidget</extends>
+ <header>envelopesettings.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+#include "deviceprober.h"
+#include "ffbdevice.h"
+#include <QtCore/QDebug>
+#include <QtWidgets/QMessageBox>
+#include <linux/input.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+const QDir DeviceProber::s_deviceNodesByID("/dev/input/by-id");
+const QString DeviceProber::res_ffbdeviceErrCap("FFB Device error");
+
+DeviceProber::DeviceProber(QObject* parent) :
+ QObject(parent)
+{
+}
+
+QStringList DeviceProber::listDevicesByID()
+{
+ QStringList devices = DeviceProber::s_deviceNodesByID.entryList(QDir::NoDotAndDotDot);
+
+ foreach (const QString s, devices)
+ qDebug() << s;
+
+ return devices;
+}
+
+std::shared_ptr<FFBDevice> DeviceProber::openDeviceByID(const QString& id)
+{
+ QString path = DeviceProber::s_deviceNodesByID.absoluteFilePath(id);
+ /* Check if the device is already opened */
+ for (std::shared_ptr<FFBDevice> dev : m_openedDevices) {
+ if (QString::compare(id, dev->id()) == 0) {
+ qDebug() << "Device" << id << "already opened";
+ return dev;
+ }
+ }
+
+ int fd = open(path.toLocal8Bit(), O_RDWR);
+ if (!fd) {
+ QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Cannot open device.");
+ return nullptr;
+ }
+
+ int maxEffectCount;
+ int ret = ioctl(fd, EVIOCGEFFECTS, &maxEffectCount);
+ if (ret < 0) {
+ QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Cannot query maximum effects count.\nDevice probably does not support Force Feedback (errno " + QString::number(ret) + ")");
+ close(fd);
+ return nullptr;
+ }
+ if (maxEffectCount < 1) {
+ QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Maximum effect count for this device is zero.");
+ close(fd);
+ return nullptr;
+ }
+
+ std::shared_ptr<FFBDevice> device(new FFBDevice(fd, id, maxEffectCount));
+ if (!device->queryDeviceCapabilities()) {
+ QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Unable to query device capabilities.");
+ return nullptr;
+ }
+
+ m_openedDevices.push_back(device);
+ return device;
+}
--- /dev/null
+#ifndef DEVICEPROBER_H
+#define DEVICEPROBER_H
+
+#include "ffbdevice.h"
+#include <memory>
+#include <QtCore/QDir>
+#include <QtCore/QObject>
+
+class DeviceProber : public QObject
+{
+ Q_OBJECT
+public:
+ explicit DeviceProber(QObject* parent = 0);
+ QStringList listDevicesByID();
+ std::shared_ptr<FFBDevice> openDeviceByID(const QString& id);
+
+private:
+ std::list<std::shared_ptr<FFBDevice>> m_openedDevices;
+
+ static const QDir s_deviceNodesByID;
+ static const QString res_ffbdeviceErrCap;
+
+signals:
+
+public slots:
+
+};
+
+#endif // DEVICEPROBER_H
--- /dev/null
+#include "effectsettings.h"
+
+EffectSettings::EffectSettings(QWidget* parent) :
+ QWidget(parent)
+{
+}
--- /dev/null
+#ifndef EFFECTSETTINGS_H
+#define EFFECTSETTINGS_H
+
+#include "ffbeffectparameters.h"
+#include <QtWidgets/QWidget>
+
+class EffectSettings : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit EffectSettings(QWidget* parent = 0);
+ virtual bool fillFromParameters(const std::shared_ptr<FFBEffectParameters> params) = 0;
+
+signals:
+
+public slots:
+
+};
+
+#endif // EFFECTSETTINGS_H
--- /dev/null
+#include "envelopesettings.h"
+#include "ui_envelopesettings.h"
+#include <QDebug>
+
+EnvelopeSettings::EnvelopeSettings(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::EnvelopeSettings)
+{
+ ui->setupUi(this);
+}
+
+QString EnvelopeSettings::attackLength() const
+{
+ return ui->qle_attackLength->text();
+}
+
+QString EnvelopeSettings::attackLevel() const
+{
+ return ui->qle_attackLevel->text();
+}
+
+QString EnvelopeSettings::fadeLength() const
+{
+ return ui->qle_fadeLength->text();
+}
+
+QString EnvelopeSettings::fadeLevel() const
+{
+ return ui->qle_fadeLevel->text();
+}
+
+bool EnvelopeSettings::fillFromParameters(const std::shared_ptr<FFBEffectParameters> params)
+{
+ try {
+ const std::shared_ptr<FFBEnvelopeParameters> envParams = std::dynamic_pointer_cast<FFBEnvelopeParameters>(params);
+
+ ui->qle_attackLength->setText(QString::number(envParams->attackLength));
+ ui->qle_attackLevel->setText(QString::number(envParams->attackLevel));
+ ui->qle_fadeLength->setText(QString::number(envParams->fadeLength));
+ ui->qle_fadeLevel->setText(QString::number(envParams->fadeLevel));
+ return true;
+ } catch (std::bad_cast& ex) {
+ qCritical(ex.what());
+ return false;
+ }
+ return false;
+}
+
+EnvelopeSettings::~EnvelopeSettings()
+{
+ delete ui;
+}
--- /dev/null
+#ifndef ENVELOPESETTINGS_H
+#define ENVELOPESETTINGS_H
+
+#include "ffbeffectparameters.h"
+#include "ffbenvelopeparameters.h"
+#include <QtWidgets/QWidget>
+
+namespace Ui {
+ class EnvelopeSettings;
+}
+
+class EnvelopeSettings : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit EnvelopeSettings(QWidget *parent = 0);
+ ~EnvelopeSettings();
+ bool fillFromParameters(const std::shared_ptr<FFBEffectParameters> params);
+
+ QString attackLength() const;
+ QString attackLevel() const;
+ QString fadeLength() const;
+ QString fadeLevel() const;
+
+private:
+ Ui::EnvelopeSettings* ui;
+};
+
+#endif // ENVELOPESETTINGS_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EnvelopeSettings</class>
+ <widget class="QWidget" name="EnvelopeSettings">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>129</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="ql_attackLevel">
+ <property name="text">
+ <string>Attack level:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="qle_attackLevel">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="ql_attackLength">
+ <property name="text">
+ <string>Attack length:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="qle_attackLength">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="ql_fadeLevel">
+ <property name="text">
+ <string>Fade level:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="ql_fadeLength">
+ <property name="text">
+ <string>Fade length:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="qle_fadeLevel">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="qle_fadeLength">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+#include "ffbconstanteffect.h"
+
+FFBConstantEffect::FFBConstantEffect() :
+ FFBEffect(FFBEffectTypes::CONSTANT)
+{
+}
+
+struct ff_effect* FFBConstantEffect::createFFStruct()
+{
+ /* Set up generic effect parameters */
+ struct ff_effect* eff = FFBEffect::createFFStruct(m_params);
+
+ eff->type = FF_CONSTANT;
+
+ eff->u.constant.envelope.attack_length = m_params->attackLength;
+ eff->u.constant.envelope.attack_level = m_params->attackLevel;
+ eff->u.constant.envelope.fade_length = m_params->fadeLength;
+ eff->u.constant.envelope.fade_level= m_params->fadeLevel;
+
+ eff->u.constant.level = m_params->level;
+
+ return eff;
+}
+
+bool FFBConstantEffect::setParameters(const std::shared_ptr<FFBEffectParameters> params)
+{
+ try {
+ return setParameters(std::dynamic_pointer_cast<FFBConstantEffectParameters>(params));
+ } catch (const std::bad_cast&) {
+ return false;
+ }
+ return false;
+}
+
+bool FFBConstantEffect::setParameters(const std::shared_ptr<FFBConstantEffectParameters> params)
+{
+ if (!checkGenericParameters(params))
+ return false;
+
+ if (!checkBoundsInclusive(params->attackLength, 0, 0xFFFF))
+ return false;
+ if (!checkBoundsInclusive(params->attackLevel, 0, 0xFFFF))
+ return false;
+ if (!checkBoundsInclusive(params->fadeLevel, 0, 0xFFFF))
+ return false;
+ if (!checkBoundsInclusive(params->fadeLevel, 0, 0xFFFF))
+ return false;
+
+ if (!checkBoundsInclusive(params->level, -0x7FFF, 0x7FFF)) {
+ reportError("Level out of bounds.");
+ return false;
+ }
+
+ m_params = params;
+ return true;
+}
--- /dev/null
+#ifndef FFBCONSTANTEFFECT_H
+#define FFBCONSTANTEFFECT_H
+
+#include "ffbeffect.h"
+#include "ffbconstanteffectparameters.h"
+
+class FFBConstantEffect : public FFBEffect
+{
+public:
+ explicit FFBConstantEffect();
+ struct ff_effect* createFFStruct();
+ inline const std::shared_ptr<FFBEffectParameters> parameters() const { return m_params; }
+ bool setParameters(const std::shared_ptr<FFBEffectParameters> params);
+ bool setParameters(const std::shared_ptr<FFBConstantEffectParameters> params);
+
+private:
+ std::shared_ptr<FFBConstantEffectParameters> m_params;
+};
+
+#endif // FFBCONSTANTEFFECT_H
--- /dev/null
+#include "ffbconstanteffectparameters.h"
+
+FFBConstantEffectParameters::FFBConstantEffectParameters() :
+ FFBEffectParameters(), FFBEnvelopeParameters(),
+ level(0)
+{}
+
+bool FFBConstantEffectParameters::levelFromString(const QString& level)
+{
+ bool ok;
+ this->level = level.toInt(&ok);
+
+ return ok;
+}
+
+FFBConstantEffectParameters::~FFBConstantEffectParameters()
+{
+}
--- /dev/null
+#ifndef FFBCONSTANTEFFECTPARAMETERS_H
+#define FFBCONSTANTEFFECTPARAMETERS_H
+
+#include "ffbeffectparameters.h"
+#include "ffbenvelopeparameters.h"
+
+class FFBConstantEffectParameters : public FFBEffectParameters, public FFBEnvelopeParameters
+{
+public:
+ FFBConstantEffectParameters();
+ ~FFBConstantEffectParameters();
+
+ bool levelFromString(const QString& level);
+
+ int level;
+};
+
+#endif // FFBCONSTANTEFFECTPARAMETERS_H
--- /dev/null
+#include "ffbdevice.h"
+#include "ffbeffectfactory.h"
+#include <QDebug>
+
+const quint8 FFBDevice::BITS_PER_LONG = sizeof(unsigned long) * 8;
+
+FFBDevice::FFBDevice(const int fd, const QString& id, const int maxEffectCount, QObject* parent) :
+ QObject(parent),
+ c_fd(fd),
+ c_id(id),
+ c_maxEffectCount(maxEffectCount)
+{
+ for (int i = 0; i < maxEffectCount; i++)
+ m_effects.push_back(FFBEffectFactory::createEffect(FFBEffectTypes::NONE));
+}
+
+QStringList FFBDevice::availableEffectsList() const
+{
+ QStringList list;
+
+ for (const FFBEffectTypes e : m_availableEffects)
+ list << effectName(e);
+
+ return list;
+}
+
+QStringList FFBDevice::availableWaveformsList() const
+{
+ QStringList list;
+
+ for (const PeriodicWaveforms w : m_availablePeriodicWaveforms)
+ list << waveformName(w);
+
+ return list;
+}
+QString FFBDevice::effectName(const FFBEffectTypes effect) const
+{
+ switch (effect) {
+ case FFBEffectTypes::CONSTANT:
+ return "Constant force";
+ case FFBEffectTypes::PERIODIC:
+ return "Periodic force";
+ case FFBEffectTypes::RAMP:
+ return "Ramp";
+ case FFBEffectTypes::SPRING:
+ return "Spring";
+ case FFBEffectTypes::FRICTION:
+ return "Friction";
+ case FFBEffectTypes::DAMPER:
+ return "Damper";
+ case FFBEffectTypes::RUMBLE:
+ return "Rumble";
+ case FFBEffectTypes::INERTIA:
+ return "Inertia";
+ default:
+ return "Unknown effect";
+ }
+}
+
+const std::shared_ptr<FFBEffectParameters> FFBDevice::effectParameters(const int idx)
+{
+ if (idx >= c_maxEffectCount)
+ return nullptr;
+
+ return m_effects[idx]->parameters();
+}
+
+FFBEffect::FFBEffectStatus FFBDevice::effectStatusByIdx(const int idx) const
+{
+ if (m_effects[idx] == nullptr)
+ return FFBEffect::FFBEffectStatus::NOT_LOADED;
+
+ return m_effects[idx]->status();
+}
+
+FFBEffectTypes FFBDevice::effectTypeByEffectIdx(const int idx) const
+{
+ return m_effects[idx]->type();
+}
+
+unsigned int FFBDevice::effectTypeToIdx(FFBEffectTypes type)
+{
+ for (unsigned int i = 0; i < m_availableEffects.size(); i++) {
+ if (m_availableEffects[i] == type)
+ return i;
+ }
+ qWarning() << "Effect type no found in the list!";
+ return 0;
+}
+
+bool FFBDevice::isEffectUpdateable(const std::shared_ptr<FFBEffect> effect, const std::shared_ptr<FFBEffectParameters> params, const FFBEffectTypes type)
+{
+ if (effect->type() != type)
+ return false;
+ if (type == FFBEffectTypes::PERIODIC) {
+ const std::shared_ptr<FFBPeriodicEffectParameters> pParams;
+ try {
+ PeriodicWaveforms waveform = std::dynamic_pointer_cast<FFBPeriodicEffectParameters>(effect->parameters())->waveform;
+ if (waveform != std::dynamic_pointer_cast<FFBPeriodicEffectParameters>(params)->waveform)
+ return false;
+ } catch (std::bad_cast& ex) {
+ qDebug() << ex.what();
+ return false;
+ }
+ }
+ return true;
+}
+
+QString FFBDevice::waveformName(const PeriodicWaveforms waveform) const
+{
+ switch (waveform) {
+ case PeriodicWaveforms::SQUARE:
+ return "Square";
+ case PeriodicWaveforms::TRIANGLE:
+ return "Triangle";
+ case PeriodicWaveforms::SINE:
+ return "Sine";
+ case PeriodicWaveforms::SAW_UP:
+ return "Saw up";
+ case PeriodicWaveforms::SAW_DOWN:
+ return "Saw down";
+ default:
+ return "Unknown waveform";
+ }
+}
+
+bool FFBDevice::hasEffect(FFBEffectTypes id) const
+{
+ for (const FFBEffectTypes e : m_availableEffects)
+ if (e == id) return true;
+
+ return false;
+}
+
+bool FFBDevice::hasPeriodicWaveform(PeriodicWaveforms id) const
+{
+ for (const PeriodicWaveforms w : m_availablePeriodicWaveforms)
+ if (w == id) return true;
+
+ return false;
+}
+
+bool FFBDevice::queryDeviceCapabilities()
+{
+ unsigned long caps[4];
+ int ret = ioctl(c_fd, EVIOCGBIT(EV_FF, sizeof(caps)), caps);
+ if (ret < 0)
+ return false;
+
+ /* Query FFB effects this device can do */
+ if (testBit(FF_CONSTANT, caps))
+ m_availableEffects.push_back(FFBEffectTypes::CONSTANT);
+ if (testBit(FF_PERIODIC, caps))
+ m_availableEffects.push_back(FFBEffectTypes::PERIODIC);
+ if (testBit(FF_RAMP, caps))
+ m_availableEffects.push_back(FFBEffectTypes::RAMP);
+ if (testBit(FF_SPRING, caps))
+ m_availableEffects.push_back(FFBEffectTypes::SPRING);
+ if (testBit(FF_FRICTION, caps))
+ m_availableEffects.push_back(FFBEffectTypes::FRICTION);
+ if (testBit(FF_DAMPER, caps))
+ m_availableEffects.push_back(FFBEffectTypes::DAMPER);
+ if (testBit(FF_INERTIA, caps))
+ m_availableEffects.push_back(FFBEffectTypes::INERTIA);
+
+ /* Query waveforms for PERIODIC if the device supports it */
+ if (hasEffect(FFBEffectTypes::PERIODIC)) {
+ if (testBit(FF_SQUARE, caps))
+ m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SQUARE);
+ if (testBit(FF_TRIANGLE, caps))
+ m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::TRIANGLE);
+ if (testBit(FF_SINE, caps))
+ m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SINE);
+ if (testBit(FF_SAW_UP, caps))
+ m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SAW_UP);
+ if (testBit(FF_SAW_DOWN, caps))
+ m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SAW_DOWN);
+ }
+
+ return true;
+}
+
+bool FFBDevice::removeEffect(const int idx)
+{
+ if (m_effects[idx]->status() == FFBEffect::FFBEffectStatus::NOT_LOADED)
+ return true;
+
+ if (!stopEffect(idx))
+ return false;
+
+ int internalIdx = m_effects[idx]->internalIdx();
+ int ret = ioctl(c_fd, EVIOCRMFF, internalIdx);
+ if (ret < 0)
+ return false;
+
+ m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::NOT_LOADED);
+ return true;
+}
+
+bool FFBDevice::startEffect(const int idx, FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> params)
+{
+ if (idx < 0 || idx > c_maxEffectCount) {
+ qDebug() << "Effect index out of bounds";
+ return false;
+ }
+
+ /* There is no effect in the selected slot */
+ if (m_effects[idx]->type() == FFBEffectTypes::NONE) {
+ std::shared_ptr<FFBEffect> effect = FFBEffectFactory::createEffect(type);
+ if (effect == nullptr) {
+ qWarning() << "Unable to create FFBEffect";
+ return false;
+ }
+ m_effects[idx] = effect;
+ qDebug() << "Creating new effect";
+ } else {
+ if (!isEffectUpdateable(m_effects[idx], params, type)) {
+ removeEffect(idx);
+ m_effects[idx] = FFBEffectFactory::createEffect(type);
+ if (m_effects[idx] == nullptr) {
+ qDebug() << "Effect was not recreated.";
+ return false;
+ }
+ qDebug() << "Recreating effect" << idx;
+ } else
+ qDebug() << "Updating effect" << idx;
+ }
+
+ if (!m_effects[idx]->setParameters(params)) {
+ qDebug() << "Unable to set effect parameters, some values are probably invalid.";
+ return false;
+ }
+
+ struct ff_effect* kernelEff = nullptr;
+ kernelEff = m_effects[idx]->createFFStruct();
+ if (kernelEff == nullptr) {
+ qDebug() << "struct ff_effect not created";
+ return false;
+ }
+ int ret = uploadEffect(kernelEff);
+ if (ret < 0) {
+ qDebug() << "Effect not uploaded";
+ delete kernelEff;
+ return false;
+ }
+
+ m_effects[idx]->setInternalIdx(kernelEff->id);
+ m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::STOPPED);
+
+ /* Start playback */
+ struct input_event evt;
+ evt.type = EV_FF;
+ evt.code = kernelEff->id;
+ evt.value = 1;
+
+ ret = write(c_fd, &evt, sizeof(struct input_event));
+ if (ret != sizeof(struct input_event)) {
+ qDebug() << "Effect not started";
+ delete kernelEff;
+ return false;
+ }
+
+ m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::PLAYING);
+
+ delete kernelEff;
+ return true;
+}
+
+bool FFBDevice::stopEffect(const int idx)
+{
+ if (m_effects[idx] == nullptr)
+ return true;
+
+ if (m_effects[idx]->status() != FFBEffect::FFBEffectStatus::PLAYING)
+ return true;
+
+ int internalIdx = m_effects[idx]->internalIdx();
+
+ struct input_event evt;
+ evt.type = EV_FF;
+ evt.code = internalIdx;
+ evt.value = 0;
+
+ int ret = write(c_fd, &evt, sizeof(struct input_event));
+ if (ret != sizeof(struct input_event))
+ return false;
+
+ m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::STOPPED);
+ return true;
+}
+
+int FFBDevice::uploadEffect(struct ff_effect* effect)
+{
+ int ret = ioctl(c_fd, EVIOCSFF, effect);
+ if (ret < 0) {
+ qDebug() << "Error while uploading effect";
+ return ret;
+ }
+
+ return effect->id;
+}
--- /dev/null
+#ifndef FFBDEVICE_H
+#define FFBDEVICE_H
+
+#include "ffbeffect.h"
+#include <memory>
+#include <vector>
+#include <QtCore/QObject>
+#include <QtCore/QStringList>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/input.h>
+#include <sys/stat.h>
+
+class FFBDevice : public QObject
+{
+ Q_OBJECT
+public:
+
+ explicit FFBDevice(const int fd, const QString& id, const int maxEffectCount, QObject* parent = 0);
+ QStringList availableEffectsList() const;
+ QStringList availableWaveformsList() const;
+ QString effectName(const FFBEffectTypes effect) const;
+ const std::shared_ptr<FFBEffectParameters> effectParameters(const int idx);
+ FFBEffect::FFBEffectStatus effectStatusByIdx(const int idx) const;
+ inline FFBEffectTypes effectTypeFromSelectionIdx(const int idx) const { return m_availableEffects[idx]; }
+ unsigned int effectTypeToIdx(FFBEffectTypes type);
+ FFBEffectTypes effectTypeByEffectIdx(const int idx) const;
+ bool hasEffect(FFBEffectTypes id) const;
+ bool hasPeriodicWaveform(PeriodicWaveforms id) const;
+ inline const QString& id() const { return c_id; }
+ inline int maxEffectCount() const { return c_maxEffectCount; }
+ bool queryDeviceCapabilities();
+ bool removeEffect(const int idx);
+ bool startEffect(const int idx, FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> params);
+ bool stopEffect(const int idx);
+ QString waveformName(const PeriodicWaveforms waveform) const;
+ inline PeriodicWaveforms waveformByIdx(const int idx) const { return m_availablePeriodicWaveforms[idx]; }
+
+private:
+ bool isEffectUpdateable(const std::shared_ptr<FFBEffect> effect, const std::shared_ptr<FFBEffectParameters> params, const FFBEffectTypes type);
+ int uploadEffect(struct ff_effect* effect);
+ std::vector<FFBEffectTypes> m_availableEffects;
+ std::vector<PeriodicWaveforms> m_availablePeriodicWaveforms;
+ std::vector<std::shared_ptr<FFBEffect>> m_effects;
+
+ const int c_fd;
+ const QString c_id;
+ const int c_maxEffectCount;
+
+ static inline unsigned long longIdx(unsigned long bit) { return bit / BITS_PER_LONG; }
+ static inline unsigned long offset(unsigned long bit) { return bit % BITS_PER_LONG; }
+ static inline bool testBit(unsigned long bit, unsigned long* array) { return (array[longIdx(bit)] >> offset(bit)) & 1; }
+
+ static const quint8 BITS_PER_LONG;
+signals:
+
+public slots:
+
+};
+
+#endif // FFBDEVICE_H
--- /dev/null
+#include "ffbeffect.h"
+#include <QtWidgets/QMessageBox>
+
+FFBEffect::FFBEffect(FFBEffectTypes type)
+{
+ m_internalIdx = -1;
+ m_status = FFBEffectStatus::NOT_LOADED;
+ m_type = type;
+}
+
+struct ff_effect* FFBEffect::createFFStruct(const std::shared_ptr<FFBEffectParameters> params)
+{
+ struct ff_effect* eff = new struct ff_effect;
+ memset(eff, 0, sizeof(struct ff_effect));
+
+ eff->id = m_internalIdx;
+ eff->direction = params->direction;
+ eff->replay.delay = params->replayDelay;
+ eff->replay.length = params->replayLength;
+
+ return eff;
+}
+
+void FFBEffect::reportError(const QString& errorMsg) const
+{
+ QMessageBox::warning(nullptr, "FFB effect error", errorMsg);
+}
+
+bool FFBEffect::checkGenericParameters(const std::shared_ptr<FFBEffectParameters> params)
+{
+ if (!checkBoundsInclusive(params->direction, 0, 0xFFFF)) {
+ reportError("Direction out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->replayDelay, 0, 0xFFFF)) {
+ reportError("Replay delay out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->replayLength, 0, 0xFFFF)) {
+ reportError("Replay length out of bounds.");
+ return false;
+ }
+
+ return true;
+}
+
+bool FFBEffect::operator==(const FFBEffect& other) const
+{
+ return this->type() == other.type();
+}
+
+bool FFBEffect::operator!=(const FFBEffect& other) const
+{
+ return !(*this == other);
+}
--- /dev/null
+#ifndef FFBEFFECT_H
+#define FFBEFFECT_H
+
+#include "ffbeffectparameters.h"
+#include "globals.h"
+#include <memory>
+#include <linux/input.h>
+
+class FFBEffect {
+public:
+ enum class FFBEffectStatus { PLAYING, STOPPED, NOT_LOADED };
+
+ explicit FFBEffect(FFBEffectTypes type);
+ virtual struct ff_effect* createFFStruct() = 0;
+ inline int internalIdx() const { return m_internalIdx; }
+ virtual const std::shared_ptr<FFBEffectParameters> parameters() const = 0;
+ void reportError(const QString& errorMsg) const;
+ inline void setInternalIdx(int idx) { m_internalIdx = idx; }
+ virtual bool setParameters(const std::shared_ptr<FFBEffectParameters> params) = 0;
+ inline void setStatus(FFBEffectStatus status) { m_status = status; }
+ inline FFBEffectStatus status() const { return m_status; }
+ inline FFBEffectTypes type() const { return m_type; }
+
+ virtual bool operator==(const FFBEffect&) const;
+ virtual bool operator!=(const FFBEffect&) const;
+
+protected:
+ struct ff_effect* createFFStruct(const std::shared_ptr<FFBEffectParameters> params);
+ bool checkGenericParameters(const std::shared_ptr<FFBEffectParameters> params);
+
+private:
+ int m_internalIdx;
+ FFBEffectStatus m_status;
+ FFBEffectTypes m_type;
+
+signals:
+
+public slots:
+
+};
+
+#endif // FFBEFFECT_H
--- /dev/null
+#include "ffbeffectfactory.h"
+
+FFBEffectFactory::FFBEffectFactory()
+{
+}
+
+std::shared_ptr<FFBEffect> FFBEffectFactory::createEffect(FFBEffectTypes type)
+{
+ switch (type) {
+ case FFBEffectTypes::NONE:
+ return std::shared_ptr<FFBEffect>(new FFBNullEffect());
+ case FFBEffectTypes::CONSTANT:
+ return std::shared_ptr<FFBEffect>(new FFBConstantEffect());
+ case FFBEffectTypes::PERIODIC:
+ return std::shared_ptr<FFBEffect>(new FFBPeriodicEffect());
+ default:
+ return nullptr;
+ }
+}
--- /dev/null
+#ifndef FFBEFFECTFACTORY_H
+#define FFBEFFECTFACTORY_H
+
+#include "globals.h"
+#include "ffbconstanteffect.h"
+#include "ffbnulleffect.h"
+#include "ffbperiodiceffect.h"
+
+class FFBEffectFactory
+{
+public:
+ static std::shared_ptr<FFBEffect> createEffect(FFBEffectTypes type);
+
+private:
+ FFBEffectFactory();
+
+};
+
+#endif // FFBEFFECTFACTORY_H
--- /dev/null
+#include "ffbeffectparameters.h"
+
+FFBEffectParameters::FFBEffectParameters() :
+ direction(0),
+ replayDelay(0),
+ replayLength(0)
+{
+}
+
+bool FFBEffectParameters::directionFromString(const QString& direction)
+{
+ bool ok;
+ this->direction = direction.toInt(&ok);
+
+ return ok;
+}
+
+bool FFBEffectParameters::replayDelayFromString(const QString& replayDelay)
+{
+ bool ok;
+ this->replayDelay = replayDelay.toInt(&ok);
+
+ return ok;
+}
+
+bool FFBEffectParameters::replayLengthFromString(const QString& replayLength)
+{
+ bool ok;
+ this->replayLength = replayLength.toInt(&ok);
+
+ return ok;
+}
+
+FFBEffectParameters::~FFBEffectParameters()
+{
+}
--- /dev/null
+#ifndef FFBEFFECTPARAMETERS_H
+#define FFBEFFECTPARAMETERS_H
+
+#include "globals.h"
+#include <memory>
+#include <QtCore/QObject>
+
+class FFBEffectParameters
+{
+public:
+ FFBEffectParameters();
+ virtual ~FFBEffectParameters();
+
+ bool directionFromString(const QString& direction);
+ bool replayDelayFromString(const QString& replayDelay);
+ bool replayLengthFromString(const QString& replayLength);
+
+ int direction;
+ int replayDelay;
+ int replayLength;
+};
+
+#endif // FFBEFFECTPARAMETERS_H
--- /dev/null
+#include "ffbenvelopeparameters.h"
+
+FFBEnvelopeParameters::FFBEnvelopeParameters()
+{}
+
+bool FFBEnvelopeParameters::attackLengthFromString(const QString& attackLength)
+{
+ bool ok;
+ this->attackLength = attackLength.toInt(&ok);
+ return ok;
+}
+
+bool FFBEnvelopeParameters::attackLevelFromString(const QString& attackLevel)
+{
+ bool ok;
+ this->attackLevel = attackLevel.toInt(&ok);
+ return ok;
+}
+
+bool FFBEnvelopeParameters::fadeLengthFromString(const QString& fadeLength)
+{
+ bool ok;
+ this->fadeLength = fadeLength.toInt(&ok);
+ return ok;
+}
+
+
+bool FFBEnvelopeParameters::fadeLevelFromString(const QString& fadeLevel)
+{
+ bool ok;
+ this->fadeLevel = fadeLevel.toInt(&ok);
+ return ok;
+}
--- /dev/null
+#ifndef FFBENVELOPEPARAMETERS_H
+#define FFBENVELOPEPARAMETERS_H
+
+#include <QtCore/QString>
+
+class FFBEnvelopeParameters
+{
+public:
+ FFBEnvelopeParameters();
+ bool attackLengthFromString(const QString& attackLength);
+ bool attackLevelFromString(const QString& attackLevel);
+ bool fadeLengthFromString(const QString& fadeLength);
+ bool fadeLevelFromString(const QString& fadeLevel);
+
+ int attackLength;
+ int attackLevel;
+ int fadeLength;
+ int fadeLevel;
+};
+
+#endif // FFBENVELOPEPARAMETERS_H
--- /dev/null
+#include "ffbnulleffect.h"
+
+FFBNullEffect::FFBNullEffect() :
+ FFBEffect(FFBEffectTypes::NONE)
+{}
--- /dev/null
+#ifndef FFBNULLEFFECT_H
+#define FFBNULLEFFECT_H
+
+#include "ffbeffect.h"
+
+class FFBNullEffect : public FFBEffect
+{
+public:
+ explicit FFBNullEffect();
+ inline struct ff_effect* createFFStruct() { return nullptr; }
+ inline const std::shared_ptr<FFBEffectParameters> parameters() const { return nullptr; }
+ inline bool setParameters(const std::shared_ptr<FFBEffectParameters> params) { return false; }
+};
+
+#endif // FFBNULLEFFECT_H
--- /dev/null
+#include "ffbperiodiceffect.h"
+#include <QDebug>
+
+FFBPeriodicEffect::FFBPeriodicEffect() :
+ FFBEffect(FFBEffectTypes::PERIODIC)
+{}
+
+struct ff_effect* FFBPeriodicEffect::createFFStruct()
+{
+ struct ff_effect* eff = FFBEffect::createFFStruct(m_params);
+ if (eff == nullptr)
+ return nullptr;
+
+ eff->type = FF_PERIODIC;
+
+ eff->u.periodic.envelope.attack_length = m_params->attackLength;
+ eff->u.periodic.envelope.attack_level = m_params->attackLevel;
+ eff->u.periodic.envelope.fade_length = m_params->fadeLength;
+ eff->u.periodic.envelope.fade_level = m_params->fadeLevel;
+
+ eff->u.periodic.magnitude = m_params->magnitude;
+ eff->u.periodic.offset = m_params->offset;
+ eff->u.periodic.period = m_params->period;
+ eff->u.periodic.phase = m_params->phase;
+
+ switch (m_params->waveform) {
+ case PeriodicWaveforms::SINE:
+ eff->u.periodic.waveform= FF_SINE;
+ break;
+ case PeriodicWaveforms::SQUARE:
+ eff->u.periodic.waveform = FF_SQUARE;
+ break;
+ case PeriodicWaveforms::SAW_DOWN:
+ eff->u.periodic.waveform = FF_SAW_DOWN;
+ break;
+ case PeriodicWaveforms::SAW_UP:
+ eff->u.periodic.waveform = FF_SAW_UP;
+ break;
+ case PeriodicWaveforms::TRIANGLE:
+ eff->u.periodic.waveform = FF_TRIANGLE;
+ break;
+ case PeriodicWaveforms::NONE:
+ delete eff;
+ return nullptr;
+ }
+
+ return eff;
+}
+
+bool FFBPeriodicEffect::setParameters(const std::shared_ptr<FFBEffectParameters> params)
+{
+ try {
+ return setParameters(std::dynamic_pointer_cast<FFBPeriodicEffectParameters>(params));
+ } catch (const std::bad_cast& ex) {
+ reportError("Invalid effect parameters object " + QString(ex.what()));
+ return false;
+ }
+}
+
+bool FFBPeriodicEffect::setParameters(const std::shared_ptr<FFBPeriodicEffectParameters> params)
+{
+ if (!checkGenericParameters(params))
+ return false;
+
+ if (!checkBoundsInclusive(params->attackLength, 0, 0xFFFF)) {
+ reportError("Attack length out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->attackLevel, 0, 0xFFFF)) {
+ reportError("Attack level out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->fadeLength, 0, 0xFFFF)) {
+ reportError("Fade length out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->fadeLevel, 0, 0xFFFF)) {
+ reportError("Fade level out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->magnitude, -0x7FFF, 0x7FFF)) {
+ reportError("Magnitude out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->offset, -0x7FFF, 0x7FFF)) {
+ reportError("Offset out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->period, 0, 0xFFFF)) {
+ reportError("Period out of bounds.");
+ return false;
+ }
+
+ if (!checkBoundsInclusive(params->phase, 0, 0xFFFF)) {
+ reportError("Phase out of bounds.");
+ return false;
+ }
+
+ if (params->waveform == PeriodicWaveforms::NONE) {
+ reportError("Invalid waveform type.");
+ return false;
+ }
+
+ m_params = params;
+ return true;
+}
+
+bool FFBPeriodicEffect::operator==(const FFBEffect& other) const
+{
+ if (this->type() != other.type())
+ return false;
+ else
+ return this->m_params->waveform == dynamic_cast<const FFBPeriodicEffect&>(other).m_params->waveform;
+}
+
+bool FFBPeriodicEffect::operator!=(const FFBEffect& other) const
+{
+ return !(*this == other);
+}
+
+FFBPeriodicEffect::~FFBPeriodicEffect()
+{
+}
--- /dev/null
+#ifndef FFBPERIODICEFFECT_H
+#define FFBPERIODICEFFECT_H
+
+#include "ffbeffect.h"
+#include "ffbperiodiceffectparameters.h"
+
+class FFBPeriodicEffect : public FFBEffect
+{
+public:
+ FFBPeriodicEffect();
+ ~FFBPeriodicEffect();
+ struct ff_effect* createFFStruct();
+ inline const std::shared_ptr<FFBEffectParameters> parameters() const { return m_params; }
+ bool setParameters(const std::shared_ptr<FFBEffectParameters> params);
+ bool setParameters(const std::shared_ptr<FFBPeriodicEffectParameters> params);
+ bool operator==(const FFBEffect& other) const;
+ bool operator!=(const FFBEffect& other) const;
+
+private:
+ std::shared_ptr<FFBPeriodicEffectParameters> m_params;
+};
+
+#endif // FFBPERIODICEFFECT_H
--- /dev/null
+#include "ffbperiodiceffectparameters.h"
+
+FFBPeriodicEffectParameters::FFBPeriodicEffectParameters() :
+ FFBEffectParameters(), FFBEnvelopeParameters(),
+ magnitude(0),
+ offset(0),
+ period(0),
+ phase(0),
+ waveform(PeriodicWaveforms::NONE)
+{
+}
+
+bool FFBPeriodicEffectParameters::magnitudeFromString(const QString& magnitude)
+{
+ bool ok;
+ this->magnitude = magnitude.toInt(&ok);
+ return ok;
+}
+
+bool FFBPeriodicEffectParameters::offsetFromString(const QString& offset)
+{
+ bool ok;
+ this->offset = offset.toInt(&ok);
+ return ok;
+}
+
+bool FFBPeriodicEffectParameters::periodFromString(const QString& period)
+{
+ bool ok;
+ this->period = period.toInt(&ok);
+ return ok;
+}
+
+bool FFBPeriodicEffectParameters::phaseFromString(const QString& phase)
+{
+ bool ok;
+ this->phase = phase.toInt(&ok);
+ return ok;
+}
+
+void FFBPeriodicEffectParameters::waveformFromIdx(PeriodicWaveforms waveform)
+{
+ this->waveform = waveform;
+}
+
--- /dev/null
+#ifndef FFBPERIODICEFFECTPARAMETERS_H
+#define FFBPERIODICEFFECTPARAMETERS_H
+
+#include "ffbeffectparameters.h"
+#include "ffbenvelopeparameters.h"
+
+class FFBPeriodicEffectParameters : public FFBEffectParameters, public FFBEnvelopeParameters
+{
+public:
+ FFBPeriodicEffectParameters();
+
+ bool magnitudeFromString(const QString& magnitude);
+ bool offsetFromString(const QString& offset);
+ bool periodFromString(const QString& period);
+ bool phaseFromString(const QString& phase);
+ void waveformFromIdx(PeriodicWaveforms waveform);
+
+ int magnitude;
+ int offset;
+ int period;
+ int phase;
+ PeriodicWaveforms waveform;
+};
+
+#endif // FFBPERIODICEFFECTPARAMETERS_H
--- /dev/null
+#ifndef GLOBALS_H
+#define GLOBALS_H
+
+enum class FFBEffectTypes { NONE, CONSTANT, PERIODIC, RAMP, SPRING, FRICTION, DAMPER, RUMBLE, INERTIA };
+enum class PeriodicWaveforms { NONE, SQUARE, TRIANGLE, SINE, SAW_UP, SAW_DOWN };
+
+template<typename T> inline bool checkBoundsInclusive(const T& val, const T& min, const T& max)
+{
+ if (val >= min && val <= max)
+ return true;
+ else
+ return false;
+}
+
+template<typename T> inline bool checkBoundsExclusive(const T& val, const T& min, const T& max)
+{
+ if (val > min && val < max)
+ return true;
+ else
+ return false;
+}
+
+#endif // GLOBALS_H
--- /dev/null
+#ifndef HELPERS_H
+#define HELPERS_H
+
+static inline int bitsToLongs(unsigned long bits)
+{
+ return (bits + 8 * sizeof(unsigned long) - 1) / (8 *sizeof(unsigned long));
+}
+
+#endif // HELPERS_H
--- /dev/null
+#include "deviceprober.h"
+#include "mainwindow.h"
+#include <QtWidgets/QApplication>
+
+int main(int argc, char** argv)
+{
+ QApplication myApp(argc, argv);
+ std::shared_ptr<DeviceProber> prober(new DeviceProber);
+ MainWindow* mWin = new MainWindow(prober);
+
+ mWin->show();
+ return myApp.exec();
+}
--- /dev/null
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include <QtWidgets/QMessageBox>
+#include <QDebug>
+
+const QString MainWindow::res_effectNotLoaded("Not loaded");
+const QString MainWindow::res_effectPlaying("Playing");
+const QString MainWindow::res_effectStopped("Stopped");
+const QString MainWindow::res_inputFormatErrCap("Invalid input format.");
+
+MainWindow::MainWindow(std::shared_ptr<DeviceProber> prober, QWidget* parent) :
+ QMainWindow(parent),
+ m_prober(prober),
+ ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ /* HACK: Remove all default widgets from the stack */
+ for (int i = 0; i < ui->qstw_effectSpecifics->count(); i++)
+ ui->qstw_effectSpecifics->removeWidget(ui->qstw_effectSpecifics->widget(i));
+
+ m_constantEffSet = new ConstantEffectSettings();
+ m_periodicEffSet = new PeriodicEffectSettings();
+ ui->qstw_effectSpecifics->addWidget(m_constantEffSet);
+ ui->qstw_effectSpecifics->addWidget(m_periodicEffSet);
+
+ fillDeviceList();
+ connect(ui->cbox_devices, SIGNAL(activated(const QString&)), this, SLOT(onDeviceSelected(const QString&)));
+ connect(ui->cbox_effectSlots, SIGNAL(activated(const int)), this, SLOT(onEffectSlotSelected(const int)));
+ connect(ui->cbox_effectTypes, SIGNAL(activated(const int)), this, SLOT(onEffectTypeSelected(const int)));
+ connect(ui->qpb_refreshDevices, SIGNAL(clicked()), this, SLOT(onRefreshDevicesClicked()));
+ connect(ui->qpb_start, SIGNAL(clicked()), this, SLOT(onStartEffectClicked()));
+ connect(ui->qpb_stop, SIGNAL(clicked()), this, SLOT(onStopEffectClicked()));
+}
+
+EffectSettings* MainWindow::effectSettingsByType(FFBEffectTypes type)
+{
+ switch (type) {
+ case FFBEffectTypes::CONSTANT:
+ return m_constantEffSet;
+ case FFBEffectTypes::PERIODIC:
+ return m_periodicEffSet;
+ default:
+ abort();
+ }
+}
+
+void MainWindow::fillDeviceList()
+{
+ ui->cbox_devices->clear();
+ ui->cbox_devices->addItems(m_prober->listDevicesByID());
+}
+void MainWindow::fillEffectSlotsList(const int idx)
+{
+ ui->cbox_effectSlots->clear();
+ for (int i = 1; i <= idx; i++)
+ ui->cbox_effectSlots->addItem(QString::number(i));
+}
+
+void MainWindow::fillEffectTypesList(const QStringList& list)
+{
+ ui->cbox_effectTypes->clear();
+ ui->cbox_effectTypes->addItems(list);
+}
+
+void MainWindow::onDeviceSelected(const QString& id)
+{
+ ui->cbox_effectSlots->clear();
+ m_activeDevice = m_prober->openDeviceByID(id);
+
+ if (m_activeDevice == nullptr)
+ return;
+
+ fillEffectSlotsList(m_activeDevice->maxEffectCount());
+ fillEffectTypesList(m_activeDevice->availableEffectsList());
+ m_periodicEffSet->fillAvailableWaveformsList(m_activeDevice->availableWaveformsList());
+}
+
+void MainWindow::onEffectSlotSelected(const int idx)
+{
+ if (idx < 0)
+ return;
+ if (m_activeDevice == nullptr)
+ return;
+
+ FFBEffectTypes type = m_activeDevice->effectTypeByEffectIdx(idx);
+ qDebug() << static_cast<int>(type);
+ if (type == FFBEffectTypes::NONE) {
+ qDebug() << "Empty effect";
+ ui->cbox_effectTypes->setCurrentIndex(0);
+ ui->qstw_effectSpecifics->setCurrentWidget(effectSettingsByType(m_activeDevice->effectTypeFromSelectionIdx(0)));
+ setEffectStatusText(FFBEffect::FFBEffectStatus::NOT_LOADED);
+ return;
+ }
+
+ const std::shared_ptr<FFBEffectParameters> params = m_activeDevice->effectParameters(idx);
+ /* Set global parameters */
+ ui->qle_direction->setText(QString::number(params->direction));
+ ui->qle_replayDelay->setText(QString::number(params->replayDelay));
+ ui->qle_replayLength->setText(QString::number(params->replayLength));
+
+ EffectSettings* efs = effectSettingsByType(type);
+ if (!efs->fillFromParameters(params))
+ QMessageBox::warning(this, "UI error", "Unable to read effect parameters.");
+ ui->cbox_effectTypes->setCurrentIndex(m_activeDevice->effectTypeToIdx(type));
+ ui->qstw_effectSpecifics->setCurrentWidget(efs);
+ setEffectStatusText(m_activeDevice->effectStatusByIdx(idx));
+}
+
+void MainWindow::onEffectTypeSelected(const int idx)
+{
+ if (idx < 0)
+ return;
+ if (m_activeDevice == nullptr)
+ return;
+
+ ui->qstw_effectSpecifics->setCurrentWidget(effectSettingsByType(m_activeDevice->effectTypeFromSelectionIdx(idx)));
+}
+
+void MainWindow::onRefreshDevicesClicked()
+{
+ fillDeviceList();
+}
+
+void MainWindow::onStartEffectClicked()
+{
+ if (m_activeDevice == nullptr)
+ return;
+
+ FFBEffectTypes type = m_activeDevice->effectTypeFromSelectionIdx(ui->cbox_effectTypes->currentIndex());
+ std::shared_ptr<FFBEffectParameters> params;
+ int effectSlot = ui->cbox_effectSlots->currentIndex();
+ if (!readEffectParameters(params, type)) {
+ qDebug() << "Cannot read effect params.";
+ return;
+ }
+ bool ret = m_activeDevice->startEffect(effectSlot, type, params);
+ if (ret)
+ setEffectStatusText(m_activeDevice->effectStatusByIdx(effectSlot));
+}
+
+void MainWindow::onStopEffectClicked()
+{
+ if (m_activeDevice == nullptr)
+ return;
+
+ int effectSlot = ui->cbox_effectSlots->currentIndex();
+ m_activeDevice->stopEffect(effectSlot);
+ setEffectStatusText(m_activeDevice->effectStatusByIdx(effectSlot));
+}
+
+bool MainWindow::readEnvelopeParameters(std::shared_ptr<FFBEnvelopeParameters> params, const EnvelopeSettings* es)
+{
+ if (!params->attackLengthFromString(es->attackLength())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Attack length\"");
+ return false;
+ }
+ if (!params->attackLevelFromString(es->attackLevel())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Attack level\"");
+ return false;
+ }
+ if (!params->fadeLengthFromString(es->fadeLength())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Fade length\"");
+ return false;
+ }
+ if (!params->fadeLevelFromString(es->fadeLevel())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Fade level\"");
+ return false;
+ }
+
+ return true;
+}
+
+bool MainWindow::readEffectParameters(std::shared_ptr<FFBEffectParameters>& params, FFBEffectTypes type)
+{
+ switch (type) {
+ case FFBEffectTypes::CONSTANT:
+ {
+ std::shared_ptr<FFBConstantEffectParameters> iparams(new FFBConstantEffectParameters);
+ if (!readGeneralEffectParameters(iparams)) {
+ return false;
+ }
+ if (!readEnvelopeParameters(iparams, m_constantEffSet->envelopeSettings())) {
+ return false;
+ }
+ if (!iparams->levelFromString(m_constantEffSet->level())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"level\"");
+ return false;
+ }
+
+ params = iparams;
+ break;
+ }
+ case FFBEffectTypes::PERIODIC:
+ {
+ std::shared_ptr<FFBPeriodicEffectParameters> iparams(new FFBPeriodicEffectParameters);
+ if (!readGeneralEffectParameters(iparams)) {
+ return false;
+ }
+ if (!readEnvelopeParameters(iparams, m_periodicEffSet->envelopeSettings())) {
+ return false;
+ }
+ if (!iparams->magnitudeFromString(m_periodicEffSet->magnitude())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Magnitude\"");
+ return false ;
+ }
+ if (!iparams->offsetFromString(m_periodicEffSet->offset())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Offset\"");
+ return false;
+ }
+ if (!iparams->periodFromString(m_periodicEffSet->period())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Period\"");
+ return false;
+ }
+ if (!iparams->phaseFromString(m_periodicEffSet->phase())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Phase\"");
+ return false;
+ }
+
+ PeriodicWaveforms waveform = m_activeDevice->waveformByIdx(m_periodicEffSet->waveformIdx());
+ iparams->waveformFromIdx(waveform);
+
+ params = iparams;
+ break;
+ }
+ default:
+ qDebug() << "Unhandled type of effect";
+ return false;
+ }
+
+ return true;
+}
+
+bool MainWindow::readGeneralEffectParameters(std::shared_ptr<FFBEffectParameters> params)
+{
+ if (!params->directionFromString(ui->qle_direction->text())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"direction\"");
+ return false;
+ }
+ if (!params->replayDelayFromString(ui->qle_replayDelay->text())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Replay delay\"");
+ return false;
+ }
+ if (!params->replayLengthFromString(ui->qle_replayLength->text())) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Replay length\"");
+ return false;
+ }
+
+ return true;
+}
+
+void MainWindow::setEffectStatusText(const FFBEffect::FFBEffectStatus status)
+{
+ switch (status) {
+ case FFBEffect::FFBEffectStatus::NOT_LOADED:
+ ui->ql_effectStatus->setText(res_effectNotLoaded);
+ break;
+ case FFBEffect::FFBEffectStatus::PLAYING:
+ ui->ql_effectStatus->setText(res_effectPlaying);
+ break;
+ case FFBEffect::FFBEffectStatus::STOPPED:
+ ui->ql_effectStatus->setText(res_effectStopped);
+ break;
+ }
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
--- /dev/null
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include "constanteffectsettings.h"
+#include "deviceprober.h"
+#include "ffbconstanteffectparameters.h"
+#include "ffbdevice.h"
+#include "ffbperiodiceffectparameters.h"
+#include "periodiceffectsettings.h"
+#include <memory>
+#include <QMainWindow>
+
+namespace Ui {
+ class MainWindow;
+}
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(std::shared_ptr<DeviceProber> prober, QWidget* parent = 0);
+ ~MainWindow();
+
+private:
+ EffectSettings* effectSettingsByType(FFBEffectTypes type);
+ void fillDeviceList();
+ void fillEffectSlotsList(const int idx);
+ void fillEffectTypesList(const QStringList& list);
+ bool readEnvelopeParameters(std::shared_ptr<FFBEnvelopeParameters> params, const EnvelopeSettings* es);
+ bool readEffectParameters(std::shared_ptr<FFBEffectParameters>& params, FFBEffectTypes type);
+ bool readGeneralEffectParameters(std::shared_ptr<FFBEffectParameters> params);
+ void setEffectStatusText(const FFBEffect::FFBEffectStatus status);
+
+ std::shared_ptr<FFBDevice> m_activeDevice;
+ ConstantEffectSettings* m_constantEffSet;
+ PeriodicEffectSettings* m_periodicEffSet;
+ std::shared_ptr<DeviceProber> m_prober;
+ Ui::MainWindow* ui;
+
+ static const QString res_effectNotLoaded;
+ static const QString res_effectPlaying;
+ static const QString res_effectStopped;
+ static const QString res_inputFormatErrCap;
+
+private slots:
+ void onDeviceSelected(const QString& id);
+ void onEffectSlotSelected(const int idx);
+ void onEffectTypeSelected(const int idx);
+ void onRefreshDevicesClicked();
+ void onStartEffectClicked();
+ void onStopEffectClicked();
+};
+
+#endif // MAINWINDOW_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>444</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QFormLayout" name="fl_devices">
+ <item row="0" column="0">
+ <widget class="QLabel" name="ql_devices">
+ <property name="text">
+ <string>Devices:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cbox_devices"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="ql_effectSlots">
+ <property name="text">
+ <string>Effect slots:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="cbox_effectSlots"/>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QPushButton" name="qpb_refreshDevices">
+ <property name="text">
+ <string>Refresh</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel" name="ql_effectParameters">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Effect parameters</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="ql_effectStatus">
+ <property name="font">
+ <font>
+ <pointsize>12</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Not loaded</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="fieldGrowthPolicy">
+ <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="ql_direction">
+ <property name="text">
+ <string>Direction:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="qle_direction"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="ql_replayLength">
+ <property name="text">
+ <string>Replay length:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="qle_replayLength"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="ql_replayDelay">
+ <property name="text">
+ <string>Replay delay:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="qle_replayDelay"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="ql_effectTypes">
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cbox_effectTypes"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="qstw_effectSpecifics">
+ <widget class="QWidget" name="page"/>
+ <widget class="QWidget" name="page_2"/>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="qpb_start">
+ <property name="text">
+ <string>Start</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="qpb_stop">
+ <property name="text">
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="qpb_remove">
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+#include "periodiceffectsettings.h"
+#include "ui_periodiceffectsettings.h"
+
+PeriodicEffectSettings::PeriodicEffectSettings(QWidget* parent) :
+ EffectSettings(parent),
+ ui(new Ui::PeriodicEffectSettings)
+{
+ ui->setupUi(this);
+}
+
+const EnvelopeSettings* PeriodicEffectSettings::envelopeSettings() const
+{
+ return ui->qwid_envelope;
+}
+
+void PeriodicEffectSettings::fillAvailableWaveformsList(const QStringList& list)
+{
+ ui->cbox_waveform->addItems(list);
+}
+
+bool PeriodicEffectSettings::fillFromParameters(const std::shared_ptr<FFBEffectParameters> params)
+{
+ try {
+ const std::shared_ptr<FFBPeriodicEffectParameters> pParams = std::dynamic_pointer_cast<FFBPeriodicEffectParameters>(params);
+
+ ui->qle_magnitude->setText(QString::number(pParams->magnitude));
+ ui->qle_offset->setText(QString::number(pParams->offset));
+ ui->qle_period->setText(QString::number(pParams->period));
+ ui->qle_phase->setText(QString::number(pParams->phase));
+ return ui->qwid_envelope->fillFromParameters(pParams);
+ } catch (std::bad_cast& ex) {
+ qCritical(ex.what());
+ return false;
+ }
+ return false;
+}
+
+QString PeriodicEffectSettings::magnitude() const
+{
+ return ui->qle_magnitude->text();
+}
+
+QString PeriodicEffectSettings::offset() const
+{
+ return ui->qle_offset->text();
+}
+
+QString PeriodicEffectSettings::period() const
+{
+ return ui->qle_period->text();
+}
+
+QString PeriodicEffectSettings::phase() const
+{
+ return ui->qle_phase->text();
+}
+
+int PeriodicEffectSettings::waveformIdx() const
+{
+ return ui->cbox_waveform->currentIndex();
+}
+
+PeriodicEffectSettings::~PeriodicEffectSettings()
+{
+ delete ui;
+}
--- /dev/null
+#ifndef PERIODICEFFECTSETTINGS_H
+#define PERIODICEFFECTSETTINGS_H
+
+#include "effectsettings.h"
+#include "envelopesettings.h"
+#include "ffbperiodiceffectparameters.h"
+
+namespace Ui {
+ class PeriodicEffectSettings;
+}
+
+class PeriodicEffectSettings : public EffectSettings
+{
+ Q_OBJECT
+
+public:
+ explicit PeriodicEffectSettings(QWidget* parent = 0);
+ ~PeriodicEffectSettings();
+ const EnvelopeSettings* envelopeSettings() const;
+ void fillAvailableWaveformsList(const QStringList& list);
+ bool fillFromParameters(const std::shared_ptr<FFBEffectParameters> params);
+ QString magnitude() const;
+ QString offset() const;
+ QString period() const;
+ QString phase() const;
+ int waveformIdx() const;
+
+private:
+ Ui::PeriodicEffectSettings* ui;
+};
+
+#endif // PERIODICEFFECTSETTINGS_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PeriodicEffectSettings</class>
+ <widget class="QWidget" name="PeriodicEffectSettings">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="ql_waveform">
+ <property name="text">
+ <string>Waveform:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cbox_waveform"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="ql_period">
+ <property name="text">
+ <string>Period:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="qle_period">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="ql_magnitude">
+ <property name="text">
+ <string>Magnitude:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="qle_magnitude">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="ql_phase">
+ <property name="text">
+ <string>Phase:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="qle_phase">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="ql_offset">
+ <property name="text">
+ <string>Offset:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="qle_offset">
+ <property name="text">
+ <string>0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="EnvelopeSettings" name="qwid_envelope" native="true"/>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>EnvelopeSettings</class>
+ <extends>QWidget</extends>
+ <header>envelopesettings.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>