]> Devoid-pointer.net GitWeb - FFBChecker.git/commitdiff
Add all files
authorMichal Malý <madcatxster@gmail.com>
Fri, 26 Jul 2013 07:28:13 +0000 (09:28 +0200)
committerMichal Malý <madcatxster@gmail.com>
Fri, 26 Jul 2013 07:28:13 +0000 (09:28 +0200)
40 files changed:
FFBChecker.pro [new file with mode: 0644]
constanteffectsettings.cpp [new file with mode: 0644]
constanteffectsettings.h [new file with mode: 0644]
constanteffectsettings.ui [new file with mode: 0644]
deviceprober.cpp [new file with mode: 0644]
deviceprober.h [new file with mode: 0644]
effectsettings.cpp [new file with mode: 0644]
effectsettings.h [new file with mode: 0644]
envelopesettings.cpp [new file with mode: 0644]
envelopesettings.h [new file with mode: 0644]
envelopesettings.ui [new file with mode: 0644]
ffbconstanteffect.cpp [new file with mode: 0644]
ffbconstanteffect.h [new file with mode: 0644]
ffbconstanteffectparameters.cpp [new file with mode: 0644]
ffbconstanteffectparameters.h [new file with mode: 0644]
ffbdevice.cpp [new file with mode: 0644]
ffbdevice.h [new file with mode: 0644]
ffbeffect.cpp [new file with mode: 0644]
ffbeffect.h [new file with mode: 0644]
ffbeffectfactory.cpp [new file with mode: 0644]
ffbeffectfactory.h [new file with mode: 0644]
ffbeffectparameters.cpp [new file with mode: 0644]
ffbeffectparameters.h [new file with mode: 0644]
ffbenvelopeparameters.cpp [new file with mode: 0644]
ffbenvelopeparameters.h [new file with mode: 0644]
ffbnulleffect.cpp [new file with mode: 0644]
ffbnulleffect.h [new file with mode: 0644]
ffbperiodiceffect.cpp [new file with mode: 0644]
ffbperiodiceffect.h [new file with mode: 0644]
ffbperiodiceffectparameters.cpp [new file with mode: 0644]
ffbperiodiceffectparameters.h [new file with mode: 0644]
globals.h [new file with mode: 0644]
helpers.h [new file with mode: 0644]
main.cpp [new file with mode: 0644]
mainwindow.cpp [new file with mode: 0644]
mainwindow.h [new file with mode: 0644]
mainwindow.ui [new file with mode: 0644]
periodiceffectsettings.cpp [new file with mode: 0644]
periodiceffectsettings.h [new file with mode: 0644]
periodiceffectsettings.ui [new file with mode: 0644]

diff --git a/FFBChecker.pro b/FFBChecker.pro
new file mode 100644 (file)
index 0000000..e533784
--- /dev/null
@@ -0,0 +1,57 @@
+#-------------------------------------------------
+#
+# 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
diff --git a/constanteffectsettings.cpp b/constanteffectsettings.cpp
new file mode 100644 (file)
index 0000000..555f87d
--- /dev/null
@@ -0,0 +1,39 @@
+#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;
+}
diff --git a/constanteffectsettings.h b/constanteffectsettings.h
new file mode 100644 (file)
index 0000000..a6a4670
--- /dev/null
@@ -0,0 +1,28 @@
+#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
diff --git a/constanteffectsettings.ui b/constanteffectsettings.ui
new file mode 100644 (file)
index 0000000..c105f96
--- /dev/null
@@ -0,0 +1,99 @@
+<?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>
diff --git a/deviceprober.cpp b/deviceprober.cpp
new file mode 100644 (file)
index 0000000..3ead1ee
--- /dev/null
@@ -0,0 +1,66 @@
+#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;
+}
diff --git a/deviceprober.h b/deviceprober.h
new file mode 100644 (file)
index 0000000..e39ab75
--- /dev/null
@@ -0,0 +1,29 @@
+#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
diff --git a/effectsettings.cpp b/effectsettings.cpp
new file mode 100644 (file)
index 0000000..cbe03b1
--- /dev/null
@@ -0,0 +1,6 @@
+#include "effectsettings.h"
+
+EffectSettings::EffectSettings(QWidget* parent) :
+  QWidget(parent)
+{
+}
diff --git a/effectsettings.h b/effectsettings.h
new file mode 100644 (file)
index 0000000..14f42ff
--- /dev/null
@@ -0,0 +1,20 @@
+#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
diff --git a/envelopesettings.cpp b/envelopesettings.cpp
new file mode 100644 (file)
index 0000000..c70fae8
--- /dev/null
@@ -0,0 +1,52 @@
+#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;
+}
diff --git a/envelopesettings.h b/envelopesettings.h
new file mode 100644 (file)
index 0000000..d8723fb
--- /dev/null
@@ -0,0 +1,30 @@
+#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
diff --git a/envelopesettings.ui b/envelopesettings.ui
new file mode 100644 (file)
index 0000000..499d92a
--- /dev/null
@@ -0,0 +1,101 @@
+<?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>
diff --git a/ffbconstanteffect.cpp b/ffbconstanteffect.cpp
new file mode 100644 (file)
index 0000000..e713009
--- /dev/null
@@ -0,0 +1,56 @@
+#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;
+}
diff --git a/ffbconstanteffect.h b/ffbconstanteffect.h
new file mode 100644 (file)
index 0000000..05a907e
--- /dev/null
@@ -0,0 +1,20 @@
+#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
diff --git a/ffbconstanteffectparameters.cpp b/ffbconstanteffectparameters.cpp
new file mode 100644 (file)
index 0000000..43f7b13
--- /dev/null
@@ -0,0 +1,18 @@
+#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()
+{
+}
diff --git a/ffbconstanteffectparameters.h b/ffbconstanteffectparameters.h
new file mode 100644 (file)
index 0000000..2f61009
--- /dev/null
@@ -0,0 +1,18 @@
+#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
diff --git a/ffbdevice.cpp b/ffbdevice.cpp
new file mode 100644 (file)
index 0000000..8314a3a
--- /dev/null
@@ -0,0 +1,301 @@
+#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;
+}
diff --git a/ffbdevice.h b/ffbdevice.h
new file mode 100644 (file)
index 0000000..80f2744
--- /dev/null
@@ -0,0 +1,61 @@
+#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
diff --git a/ffbeffect.cpp b/ffbeffect.cpp
new file mode 100644 (file)
index 0000000..e0493fc
--- /dev/null
@@ -0,0 +1,57 @@
+#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);
+}
diff --git a/ffbeffect.h b/ffbeffect.h
new file mode 100644 (file)
index 0000000..2d678e8
--- /dev/null
@@ -0,0 +1,42 @@
+#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
diff --git a/ffbeffectfactory.cpp b/ffbeffectfactory.cpp
new file mode 100644 (file)
index 0000000..4501c2b
--- /dev/null
@@ -0,0 +1,19 @@
+#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;
+  }
+}
diff --git a/ffbeffectfactory.h b/ffbeffectfactory.h
new file mode 100644 (file)
index 0000000..78d0b41
--- /dev/null
@@ -0,0 +1,19 @@
+#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
diff --git a/ffbeffectparameters.cpp b/ffbeffectparameters.cpp
new file mode 100644 (file)
index 0000000..9f3d705
--- /dev/null
@@ -0,0 +1,36 @@
+#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()
+{
+}
diff --git a/ffbeffectparameters.h b/ffbeffectparameters.h
new file mode 100644 (file)
index 0000000..f88b5f0
--- /dev/null
@@ -0,0 +1,23 @@
+#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
diff --git a/ffbenvelopeparameters.cpp b/ffbenvelopeparameters.cpp
new file mode 100644 (file)
index 0000000..b1eddea
--- /dev/null
@@ -0,0 +1,33 @@
+#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;
+}
diff --git a/ffbenvelopeparameters.h b/ffbenvelopeparameters.h
new file mode 100644 (file)
index 0000000..a84a4f2
--- /dev/null
@@ -0,0 +1,21 @@
+#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
diff --git a/ffbnulleffect.cpp b/ffbnulleffect.cpp
new file mode 100644 (file)
index 0000000..c9eac5f
--- /dev/null
@@ -0,0 +1,5 @@
+#include "ffbnulleffect.h"
+
+FFBNullEffect::FFBNullEffect() :
+  FFBEffect(FFBEffectTypes::NONE)
+{}
diff --git a/ffbnulleffect.h b/ffbnulleffect.h
new file mode 100644 (file)
index 0000000..96a1ffe
--- /dev/null
@@ -0,0 +1,15 @@
+#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
diff --git a/ffbperiodiceffect.cpp b/ffbperiodiceffect.cpp
new file mode 100644 (file)
index 0000000..2099deb
--- /dev/null
@@ -0,0 +1,129 @@
+#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()
+{
+}
diff --git a/ffbperiodiceffect.h b/ffbperiodiceffect.h
new file mode 100644 (file)
index 0000000..2a5b5ad
--- /dev/null
@@ -0,0 +1,23 @@
+#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
diff --git a/ffbperiodiceffectparameters.cpp b/ffbperiodiceffectparameters.cpp
new file mode 100644 (file)
index 0000000..538e1b0
--- /dev/null
@@ -0,0 +1,45 @@
+#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;
+}
+
diff --git a/ffbperiodiceffectparameters.h b/ffbperiodiceffectparameters.h
new file mode 100644 (file)
index 0000000..f5d1206
--- /dev/null
@@ -0,0 +1,25 @@
+#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
diff --git a/globals.h b/globals.h
new file mode 100644 (file)
index 0000000..2ee051a
--- /dev/null
+++ b/globals.h
@@ -0,0 +1,23 @@
+#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
diff --git a/helpers.h b/helpers.h
new file mode 100644 (file)
index 0000000..a81a737
--- /dev/null
+++ b/helpers.h
@@ -0,0 +1,9 @@
+#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
diff --git a/main.cpp b/main.cpp
new file mode 100644 (file)
index 0000000..afc9b84
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,13 @@
+#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();
+}
diff --git a/mainwindow.cpp b/mainwindow.cpp
new file mode 100644 (file)
index 0000000..9cefcfb
--- /dev/null
@@ -0,0 +1,270 @@
+#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;
+}
diff --git a/mainwindow.h b/mainwindow.h
new file mode 100644 (file)
index 0000000..7b6e373
--- /dev/null
@@ -0,0 +1,55 @@
+#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
diff --git a/mainwindow.ui b/mainwindow.ui
new file mode 100644 (file)
index 0000000..2d8b1e9
--- /dev/null
@@ -0,0 +1,198 @@
+<?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>
diff --git a/periodiceffectsettings.cpp b/periodiceffectsettings.cpp
new file mode 100644 (file)
index 0000000..4637200
--- /dev/null
@@ -0,0 +1,66 @@
+#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;
+}
diff --git a/periodiceffectsettings.h b/periodiceffectsettings.h
new file mode 100644 (file)
index 0000000..47a4ee3
--- /dev/null
@@ -0,0 +1,32 @@
+#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
diff --git a/periodiceffectsettings.ui b/periodiceffectsettings.ui
new file mode 100644 (file)
index 0000000..4ad825b
--- /dev/null
@@ -0,0 +1,138 @@
+<?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>