From c68bd5a5fe54ae9671cb03fae1fbb21fd6b621a8 Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Tue, 9 Aug 2016 15:06:49 +0200 Subject: spline-widget: only rename files Adjust usages. --- filter-accela/ftnoir_filter_accela.h | 2 +- filter-accela/ftnoir_filter_accela_dialog.cpp | 2 +- gui/curve-config.cpp | 2 +- gui/curve-config.h | 2 +- gui/mapping.ui | 2 +- opentrack-logic/mappings.hpp | 2 +- opentrack-logic/tracker.h | 2 +- spline-widget/functionconfig.cpp | 343 ------------------- spline-widget/functionconfig.h | 70 ---- spline-widget/qfunctionconfigurator.cpp | 471 -------------------------- spline-widget/qfunctionconfigurator.h | 84 ----- spline-widget/spline-widget.cpp | 471 ++++++++++++++++++++++++++ spline-widget/spline-widget.hpp | 84 +++++ spline-widget/spline.cpp | 343 +++++++++++++++++++ spline-widget/spline.hpp | 70 ++++ 15 files changed, 975 insertions(+), 975 deletions(-) delete mode 100644 spline-widget/functionconfig.cpp delete mode 100644 spline-widget/functionconfig.h delete mode 100644 spline-widget/qfunctionconfigurator.cpp delete mode 100644 spline-widget/qfunctionconfigurator.h create mode 100644 spline-widget/spline-widget.cpp create mode 100644 spline-widget/spline-widget.hpp create mode 100644 spline-widget/spline.cpp create mode 100644 spline-widget/spline.hpp diff --git a/filter-accela/ftnoir_filter_accela.h b/filter-accela/ftnoir_filter_accela.h index da011228..7eda8c9e 100644 --- a/filter-accela/ftnoir_filter_accela.h +++ b/filter-accela/ftnoir_filter_accela.h @@ -7,7 +7,7 @@ #pragma once #include "ui_ftnoir_accela_filtercontrols.h" #include "opentrack/plugin-api.hpp" -#include "spline-widget/functionconfig.h" +#include "spline-widget/spline.hpp" #include #include #include diff --git a/filter-accela/ftnoir_filter_accela_dialog.cpp b/filter-accela/ftnoir_filter_accela_dialog.cpp index ddccc2a7..7d257711 100644 --- a/filter-accela/ftnoir_filter_accela_dialog.cpp +++ b/filter-accela/ftnoir_filter_accela_dialog.cpp @@ -10,7 +10,7 @@ #include #include #include "opentrack/plugin-api.hpp" -#include "spline-widget/qfunctionconfigurator.h" +#include "spline-widget/spline-widget.hpp" #include FilterControls::FilterControls() : diff --git a/gui/curve-config.cpp b/gui/curve-config.cpp index 07564ffa..3e262c48 100644 --- a/gui/curve-config.cpp +++ b/gui/curve-config.cpp @@ -8,6 +8,7 @@ #include "curve-config.h" #include "opentrack-logic/main-settings.hpp" +#include "spline-widget/spline-widget.hpp" MapWidget::MapWidget(Mappings& m) : m(m) { @@ -17,7 +18,6 @@ MapWidget::MapWidget(Mappings& m) : reload(); - setFont(qApp->font()); connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); diff --git a/gui/curve-config.h b/gui/curve-config.h index f13e5501..2124aa81 100644 --- a/gui/curve-config.h +++ b/gui/curve-config.h @@ -3,7 +3,7 @@ #include "opentrack-logic/mappings.hpp" #include "ui_mapping.h" -class MapWidget: public QWidget +class MapWidget final : public QWidget { Q_OBJECT public: diff --git a/gui/mapping.ui b/gui/mapping.ui index c0966909..3e3a54ff 100644 --- a/gui/mapping.ui +++ b/gui/mapping.ui @@ -383,7 +383,7 @@ QFunctionConfigurator QWidget -
spline-widget/qfunctionconfigurator.h
+
spline-widget/spline-widget.hpp
diff --git a/opentrack-logic/mappings.hpp b/opentrack-logic/mappings.hpp index 8e7d3b35..9475ecb4 100644 --- a/opentrack-logic/mappings.hpp +++ b/opentrack-logic/mappings.hpp @@ -10,7 +10,7 @@ #include #include "opentrack-compat/options.hpp" using namespace options; -#include "spline-widget/functionconfig.h" +#include "spline-widget/spline.hpp" #include "main-settings.hpp" class Mapping { diff --git a/opentrack-logic/tracker.h b/opentrack-logic/tracker.h index 58b478bb..91ac3f2f 100644 --- a/opentrack-logic/tracker.h +++ b/opentrack-logic/tracker.h @@ -17,7 +17,7 @@ #include "simple-mat.hpp" #include "selected-libraries.hpp" -#include "spline-widget/functionconfig.h" +#include "spline-widget/spline.hpp" #include "main-settings.hpp" #include "opentrack-compat/options.hpp" #include "tracklogger.hpp" diff --git a/spline-widget/functionconfig.cpp b/spline-widget/functionconfig.cpp deleted file mode 100644 index 2ac725f6..00000000 --- a/spline-widget/functionconfig.cpp +++ /dev/null @@ -1,343 +0,0 @@ -/* Copyright (c) 2012-2016, Stanislaw Halik - - * Permission to use, copy, modify, and/or distribute this - * software for any purpose with or without fee is hereby granted, - * provided that the above copyright notice and this permission - * notice appear in all copies. - */ - -#include "functionconfig.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -void Map::setTrackingActive(bool blnActive) -{ - activep = blnActive; -} - -Map::Map() : Map(0, 0) -{ -} - -void Map::removeAllPoints() -{ - QMutexLocker foo(&_mutex); - cur.input.clear(); - reload(); -} - -void Map::setMaxInput(qreal max_input) -{ - QMutexLocker l(&_mutex); - max_x = max_input; -} - -void Map::setMaxOutput(qreal max_output) -{ - QMutexLocker l(&_mutex); - max_y = max_output; -} - -qreal Map::maxInput() const -{ - QMutexLocker l(&_mutex); - return max_x; -} - -qreal Map::maxOutput() const -{ - QMutexLocker l(&_mutex); - return max_y; -} - -Map::Map(qreal maxx, qreal maxy) : - _mutex(QMutex::Recursive), - max_x(0), - max_y(0), - activep(false) -{ - setMaxInput(maxx); - setMaxOutput(maxy); - if (cur.input.size() == 0) - cur.input.push_back(QPointF(maxx, maxy)); - reload(); -} - -float Map::getValue(float x) -{ - QMutexLocker foo(&_mutex); - float q = x * precision(); - int xi = (int)q; - float yi = getValueInternal(xi); - float yiplus1 = getValueInternal(xi+1); - float f = (q-xi); - float ret = yiplus1 * f + yi * (1.0f - f); // at least do a linear interpolation. - last_input_value.setX(std::fabs(x)); - last_input_value.setY(std::fabs(ret)); - return ret; -} - -bool Map::getLastPoint(QPointF& point ) -{ - QMutexLocker foo(&_mutex); - point = last_input_value; - return activep; -} - -float Map::getValueInternal(int x) -{ - float sign = x < 0 ? -1 : 1; - x = abs(x); - float ret; - unsigned sz = cur.data.size(); - if (sz == 0) - ret = 0; - else - ret = cur.data[std::min(unsigned(x), sz-1u)]; - return ret * sign; -} - -static QPointF ensureInBounds(const QList& points, int i) -{ - int siz = points.size(); - if (siz == 0 || i < 0) - return QPointF(0, 0); - if (siz > i) - return points[i]; - return points[siz - 1]; -} - -static bool sortFn(const QPointF& one, const QPointF& two) -{ - return one.x() < two.x(); -} - -void Map::reload() -{ - if (cur.input.size()) - { - std::stable_sort(cur.input.begin(), cur.input.end(), sortFn); - - QList input = cur.input; - auto& data = cur.data; - - data = std::vector(value_count); - const float mult = precision(); - const unsigned mult_ = unsigned(mult * 30); - - const unsigned sz = data.size(); - - for (unsigned i = 0; i < sz; i++) - data[i] = -1; - - if (input.size() == 1 && input[0].x() > 1e-2) - { - const float x = float(input[0].x()); - const float y = float(input[0].y()); - const unsigned max = unsigned(x * mult); - for (unsigned k = 0; k < max; k++) { - if (k < sz) - data[k] = y * k / max; - } - } - else if (input[0].x() > 1e-2) - input.prepend(QPointF(0, 0)); - - for (int i = 0; i < int(sz); i++) - { - const QPointF p0 = ensureInBounds(input, i - 1); - const QPointF p1 = ensureInBounds(input, i); - const QPointF p2 = ensureInBounds(input, i + 1); - const QPointF p3 = ensureInBounds(input, i + 2); - - const float p0_x = p0.x(), p1_x = p1.x(), p2_x = p2.x(), p3_x = p3.x(); - const float p0_y = p0.y(), p1_y = p1.y(), p2_y = p2.y(), p3_y = p3.y(); - - // multiplier helps fill in all the x's needed - const unsigned end = std::min(sz, unsigned(p2_x * mult_)); - const unsigned start = unsigned(p1_x * mult); - - for (unsigned j = start; j < end; j++) - { - const float t = (j - start) / (float) (end - start); - const float t2 = t*t; - const float t3 = t*t*t; - - const int x = .5f * ((2 * p1_x) + - (-p0_x + p2_x) * t + - (2 * p0_x - 5 * p1_x + 4 * p2_x - p3_x) * t2 + - (-p0_x + 3 * p1_x - 3 * p2_x + p3_x) * t3) - * mult; - - const float y = .5f * ((2 * p1_y) + - (-p0_y + p2_y) * t + - (2 * p0_y - 5 * p1_y + 4 * p2_y - p3_y) * t2 + - (-p0_y + 3 * p1_y - 3 * p2_y + p3_y) * t3); - - if (x >= 0 && x < (int)sz) - data[x] = y; - } - } - - float last = 0; - for (unsigned i = 0; i < sz; i++) - { - if (data[i] < 0) - data[i] = last; - last = data[i]; - } - } - else - cur.data.clear(); -} - -void Map::removePoint(int i) -{ - QMutexLocker foo(&_mutex); - if (i >= 0 && i < cur.input.size()) - { - cur.input.removeAt(i); - reload(); - } -} - -void Map::addPoint(QPointF pt) -{ - QMutexLocker foo(&_mutex); - cur.input.append(pt); - reload(); - std::stable_sort(cur.input.begin(), cur.input.end(), sortFn); -} - -void Map::movePoint(int idx, QPointF pt) -{ - QMutexLocker foo(&_mutex); - if (idx >= 0 && idx < cur.input.size()) - { - cur.input[idx] = pt; - // we don't allow points to be reordered, but sort due to possible caller logic error - std::stable_sort(cur.input.begin(), cur.input.end(), sortFn); - reload(); - } -} - -const QList Map::getPoints() -{ - QMutexLocker foo(&_mutex); - return cur.input; -} - -void Map::invalidate_unsaved_settings() -{ - QMutexLocker foo(&_mutex); - cur = saved; - reload(); -} - -void Map::loadSettings(QSettings& settings, const QString& title) -{ - QMutexLocker foo(&_mutex); - QPointF newPoint; - QList points; - settings.beginGroup(QString("Curves-%1").arg(title)); - - int max = settings.value("point-count", 0).toInt(); - - for (int i = 0; i < max; i++) - { - newPoint = QPointF(settings.value(QString("point-%1-x").arg(i), 0).toDouble(), - settings.value(QString("point-%1-y").arg(i), 0).toDouble()); - if (newPoint.x() > max_x) - newPoint.setX(max_x); - if (newPoint.y() > max_y) - newPoint.setY(max_y); - points.append(newPoint); - } - - settings.endGroup(); - - if (max == 0) - points.append(QPointF(maxInput(), maxOutput())); - - cur.input = points; - reload(); - saved = cur; -} - -bool Map::State::operator==(const State& other) const -{ - if (input.size() != other.input.size()) - return false; - - const int sz = input.size(); - - using std::fabs; - - for (int i = 0; i < sz; i++) - { - const qreal eps = 1e-3; - - if (fabs(input[i].x() - other.input[i].x()) > eps || - fabs(input[i].y() - other.input[i].y()) > eps) - { - return false; - } - } - return true; -} - -void Map::saveSettings(QSettings& settings, const QString& title) -{ - QMutexLocker foo(&_mutex); - - if (cur == saved) - return; - - qDebug() << "spline-widget: saving" << title; - - settings.beginGroup(QStringLiteral("Curves-%1").arg(title)); - - if (cur.input.size() == 0) - cur.input.push_back(QPointF(max_x, max_y)); - - const int max = cur.input.size(); - settings.setValue("point-count", max); - - for (int i = 0; i < max; i++) - { - settings.setValue(QString("point-%1-x").arg(i), cur.input[i].x()); - settings.setValue(QString("point-%1-y").arg(i), cur.input[i].y()); - } - - for (int i = max; true; i++) - { - QString x = QString("point-%1-x").arg(i); - if (!settings.contains(x)) - break; - settings.remove(x); - settings.remove(QString("point-%1-y").arg(i)); - } - - saved = cur; - - settings.endGroup(); -} - - -int Map::precision() const -{ - if (cur.input.size()) - return (value_count-1) / std::max(1.f, (cur.input[cur.input.size() - 1].x())); - return 1; -} diff --git a/spline-widget/functionconfig.h b/spline-widget/functionconfig.h deleted file mode 100644 index 5d83aefd..00000000 --- a/spline-widget/functionconfig.h +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2012-2016, Stanislaw Halik - - * Permission to use, copy, modify, and/or distribute this - * software for any purpose with or without fee is hereby granted, - * provided that the above copyright notice and this permission - * notice appear in all copies. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include "opentrack-compat/qcopyable-mutex.hpp" - -#ifdef BUILD_spline_widget -# define SPLINE_WIDGET_EXPORT Q_DECL_EXPORT -#else -# define SPLINE_WIDGET_EXPORT Q_DECL_IMPORT -#endif - -class SPLINE_WIDGET_EXPORT Map -{ -private: - int precision() const; - void reload(); - float getValueInternal(int x); - - struct State - { - QList input; - std::vector data; - bool operator==(const State& s) const; - }; - - MyMutex _mutex; - QPointF last_input_value; - State cur, saved; - qreal max_x, max_y; - volatile bool activep; - - static constexpr int value_count = 10000; -public: - qreal maxInput() const; - qreal maxOutput() const; - Map(); - Map(qreal maxx, qreal maxy); - - float getValue(float x); - bool getLastPoint(QPointF& point); - void removePoint(int i); - void removeAllPoints(); - - void addPoint(QPointF pt); - void movePoint(int idx, QPointF pt); - const QList getPoints(); - void setMaxInput(qreal MaxInput); - void setMaxOutput(qreal MaxOutput); - - void saveSettings(QSettings& settings, const QString& title); - void loadSettings(QSettings& settings, const QString& title); - void invalidate_unsaved_settings(); - - void setTrackingActive(bool blnActive); -}; diff --git a/spline-widget/qfunctionconfigurator.cpp b/spline-widget/qfunctionconfigurator.cpp deleted file mode 100644 index 6f08389f..00000000 --- a/spline-widget/qfunctionconfigurator.cpp +++ /dev/null @@ -1,471 +0,0 @@ -/* Copyright (c) 2012-2016 Stanislaw Halik - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#include "opentrack-compat/options.hpp" -using namespace options; -#include "spline-widget/qfunctionconfigurator.h" -#include -#include -#include -#include -#include -#include - -QFunctionConfigurator::QFunctionConfigurator(QWidget *parent) : - QWidget(parent), - _config(nullptr), - moving_control_point_idx(-1), - snap_x(0), - snap_y(0), - _draw_function(true), - _preview_only(false) -{ - update_range(); - setMouseTracking(true); -} - -void QFunctionConfigurator::setConfig(Map* config, const QString& name) -{ - mem iniFile = group::ini_file(); - if (name != "") - config->loadSettings(*iniFile, name); - _config = config; - _draw_function = true; - update_range(); - update(); -} - -void QFunctionConfigurator::set_preview_only(bool val) -{ - _preview_only = val; -} - -bool QFunctionConfigurator::is_preview_only() const -{ - return _preview_only; -} - -void QFunctionConfigurator::drawBackground() -{ - if (!_config) - return; - _background = QPixmap(width(), height()); - - QPainter painter(&_background); - painter.fillRect(rect(), QColor::fromRgb(204, 204, 204)); - - QColor bg_color(112, 154, 209); - if (!isEnabled() && !_preview_only) - bg_color = QColor(176,176,180); - painter.fillRect(pixel_bounds, bg_color); - - QFont font; - font.setPointSize(8); - painter.setFont(font); - QFontMetrics metrics(font); - - QColor color__(176, 190, 209, 127); - - if (!isEnabled()) - color__ = QColor(70, 90, 100, 96); - - QPen pen(color__, 1, Qt::SolidLine); - - const int xstep = 10, ystep = 10; - const qreal maxx = _config->maxInput() + ystep; - const qreal maxy = _config->maxOutput() + xstep; - - // horizontal grid - for (int i = 0; i < maxy; i += xstep) - { - const qreal y = pixel_bounds.height() - i * c.y() + pixel_bounds.y(); - drawLine(&painter, - QPointF(pixel_bounds.x(), y), - QPointF(pixel_bounds.x() + pixel_bounds.width(), y), - pen); - painter.drawText(QRectF(10, - y - metrics.height()/2, - pixel_bounds.left(), - metrics.height()), - QString::number(i)); - } - - // vertical grid - for (int i = 0; i < maxx; i += ystep) - { - const qreal x = pixel_bounds.x() + i * c.x(); - drawLine(&painter, - QPointF(x, pixel_bounds.y()), - QPointF(x, pixel_bounds.y() + pixel_bounds.height()), - pen); - const QString text = QString::number(i); - painter.drawText(QRectF(x - metrics.width(text)/2, - pixel_bounds.height() + 10 + metrics.height(), - metrics.width(text), - metrics.height()), - text); - } -} - -void QFunctionConfigurator::drawFunction() -{ - if (!_config) - return; - - _function = _background; - QPainter painter(&_function); - painter.setRenderHint(QPainter::Antialiasing, true); - - QList points = _config->getPoints(); - - const int alpha = !isEnabled() ? 64 : 120; - if (!_preview_only) - { - for (int i = 0; i < points.size(); i++) - { - drawPoint(&painter, - point_to_pixel(points[i]), - QColor(200, 200, 210, alpha), - isEnabled() ? QColor(50, 100, 120, 200) : QColor(200, 200, 200, 96)); - } - } - - QColor color = spline_color; - - if (!isEnabled() && !_preview_only) - { - const int avg = int(float(color.red() + color.green() + color.blue())/3); - color = QColor(int(float(color.red() + avg) * .5f), - int(float(color.green() + avg) * .5f), - int(float(color.blue() + avg) * .5f), - 96); - } - - QPen pen(color, 1.2, Qt::SolidLine); - - const qreal step_ = line_length_pixels / c.x(); - const qreal step = std::max(1e-2, step_); - const qreal max = _config->maxInput(); - - painter.save(); - painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - - QPointF prev = point_to_pixel(QPointF(0, 0)); - for (qreal i = 0; i < max; i += step) - { - const qreal val = qreal(_config->getValue(float(i))); - QPointF cur = point_to_pixel(QPointF(i, val)); - painter.drawLine(prev, cur); - prev = cur; - } - - { - const qreal val = _config->getValue(float(max)); - QPointF last = point_to_pixel(QPointF(max, val)); - painter.drawLine(prev, last); - } - - painter.restore(); -} - -void QFunctionConfigurator::paintEvent(QPaintEvent *e) -{ - QPainter p(this); - - if (_background.isNull()) - { - _draw_function = true; - drawBackground(); - } - - if (_draw_function) { - _draw_function = false; - drawFunction(); - } - - p.drawPixmap(e->rect(), _function); - - if (_config) - { - QPen pen(Qt::white, 1, Qt::SolidLine); - QList points = _config->getPoints(); - if (points.size() && - moving_control_point_idx >= 0 && - moving_control_point_idx < points.size()) - { - if (points[0].x() > 1e-2) - points.prepend(QPointF(0, 0)); - QPointF prev = point_to_pixel(points[0]); - for (int i = 1; i < points.size(); i++) - { - auto tmp = point_to_pixel(points[i]); - drawLine(&p, prev, tmp, pen); - prev = tmp; - } - } - - // If the Tracker is active, the 'Last Point' it requested is recorded. - // Show that point on the graph, with some lines to assist. - // This new feature is very handy for tweaking the curves! - QPointF last; - if (_config->getLastPoint(last) && isEnabled()) - { - QPointF pixel_pos = point_to_pixel(last); - drawPoint(&p, pixel_pos, QColor(255, 0, 0, 120)); - } - } -} - -void QFunctionConfigurator::drawPoint(QPainter *painter, const QPointF &pos, QColor colBG, QColor border) -{ - painter->save(); - painter->setPen(border); - painter->setBrush( colBG ); - painter->drawEllipse(QRectF(pos.x() - point_size, - pos.y() - point_size, - point_size*2, point_size*2)); - painter->restore(); -} - -void QFunctionConfigurator::drawLine(QPainter *painter, const QPointF &start, const QPointF &end, QPen &pen) -{ - painter->save(); - painter->setPen(pen); - painter->setBrush(Qt::NoBrush); - painter->drawLine(start, end); - painter->restore(); -} - -void QFunctionConfigurator::mousePressEvent(QMouseEvent *e) -{ - if (!_config || !isEnabled()) - return; - QList points = _config->getPoints(); - if (e->button() == Qt::LeftButton) - { - bool bTouchingPoint = false; - moving_control_point_idx = -1; - if (_config) - { - for (int i = 0; i < points.size(); i++) - { - if (point_within_pixel(points[i], e->pos())) - { - bTouchingPoint = true; - moving_control_point_idx = i; - break; - } - } - if (!bTouchingPoint) - { - bool too_close = false; - const auto pos = e->pos(); - - for (int i = 0; i < points.size(); i++) - { - const QPointF pt = point_to_pixel(points[i]); - const auto x = pt.x() - pos.x(); - if (point_closeness_limit * point_closeness_limit >= x * x) - { - too_close = true; - break; - } - } - - if (!too_close) - _config->addPoint(pixel_coord_to_point(e->pos())); - } - } - } - - if (e->button() == Qt::RightButton) - { - if (_config) - { - int found_pt = -1; - for (int i = 0; i < points.size(); i++) - { - if (point_within_pixel(points[i], e->pos())) - { - found_pt = i; - break; - } - } - - if (found_pt != -1) - { - _config->removePoint(found_pt); - } - moving_control_point_idx = -1; - } - } - _draw_function = true; - update(); -} - -void QFunctionConfigurator::mouseMoveEvent(QMouseEvent *e) -{ - if (!_config || !isEnabled()) - return; - - QList points = _config->getPoints(); - - if (moving_control_point_idx != -1 && - moving_control_point_idx < points.size()) - { - setCursor(Qt::ClosedHandCursor); - - bool overlap = false; - - QPointF pix = e->pos(); - QPointF new_pt = pixel_coord_to_point(pix); - - for (int i = 0; i < 2; i++) - { - bool bad = false; - if (moving_control_point_idx + 1 < points.size()) - { - auto other = points[moving_control_point_idx+1]; - auto other_pix = point_to_pixel(other); - bad = pix.x() + point_closeness_limit > other_pix.x(); - if (i == 0 && bad) - { - pix.setX(other_pix.x() - point_closeness_limit); - new_pt = pixel_coord_to_point(pix); - } - else - overlap |= bad; - } - if (moving_control_point_idx != 0) - { - auto other = points[moving_control_point_idx-1]; - auto other_pix = point_to_pixel(other); - bad = pix.x() - point_closeness_limit < other_pix.x(); - if (i == 0 && bad) - { - pix.setX(other_pix.x() + point_closeness_limit); - new_pt = pixel_coord_to_point(pix); - } - else - overlap |= bad; - } - if (!bad) - break; - } - - if (!overlap) - { - points[moving_control_point_idx] = new_pt; - _config->movePoint(moving_control_point_idx, new_pt); - _draw_function = true; - update(); - } - } - else - { - bool is_on_point = false; - for (int i = 0; i < points.size(); i++) - { - const QPoint pos = e->pos(); - if (point_within_pixel(points[i], pos)) - { - is_on_point = true; - break; - } - } - - if (is_on_point) - { - setCursor(Qt::CrossCursor); - } - else - { - setCursor(Qt::ArrowCursor); - } - } -} - -void QFunctionConfigurator::mouseReleaseEvent(QMouseEvent *e) -{ - if (!_config || !isEnabled()) - return; - - if (e->button() == Qt::LeftButton) - { - mouseMoveEvent(e); - setCursor(Qt::ArrowCursor); - moving_control_point_idx = -1; - - _draw_function = true; - update(); - } -} - -void QFunctionConfigurator::update_range() -{ - if (!_config) - return; - - const int w = width(), h = height(); - const int mwl = 40, mhl = 20; - const int mwr = 15, mhr = 35; - - pixel_bounds = QRectF(mwl, mhl, (w - mwl - mwr), (h - mhl - mhr)); - c = QPointF(pixel_bounds.width() / _config->maxInput(), pixel_bounds.height() / _config->maxOutput()); - _draw_function = true; - - _background = QPixmap(); - _function = QPixmap(); - - update(); -} - -bool QFunctionConfigurator::point_within_pixel(const QPointF &pt, const QPointF &pixel) -{ - QPointF tmp = pixel - point_to_pixel(pt); - return sqrt(QPointF::dotProduct(tmp, tmp)) < point_size; -} - -QPointF QFunctionConfigurator::pixel_coord_to_point(const QPointF& point) -{ - if (!_config) - return QPointF(-1, -1); - - using std::round; - - qreal x = round((point.x() - pixel_bounds.x()) / c.x()); - qreal y = round((pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y()); - - if (snap_x > 0) - x -= int(x) % snap_x; - if (snap_y > 0) - y -= int(y) % snap_y; - - if (x < 0) - x = 0; - if (x > _config->maxInput()) - x = _config->maxInput(); - - if (y < 0) - y = 0; - if (y > _config->maxOutput()) - y = _config->maxOutput(); - - return QPointF(x, y); -} - -QPointF QFunctionConfigurator::point_to_pixel(const QPointF& point) -{ - return QPointF(pixel_bounds.x() + point.x() * c.x(), - pixel_bounds.y() + pixel_bounds.height() - point.y() * c.y()); -} - -void QFunctionConfigurator::resizeEvent(QResizeEvent *) -{ - update_range(); -} diff --git a/spline-widget/qfunctionconfigurator.h b/spline-widget/qfunctionconfigurator.h deleted file mode 100644 index b1cf7afc..00000000 --- a/spline-widget/qfunctionconfigurator.h +++ /dev/null @@ -1,84 +0,0 @@ -/* Copyright (c) 2012-2016 Stanislaw Halik - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -// Adapted to FaceTrackNoIR by Wim Vriend. - -#pragma once - -#include -#include -#include -#include "functionconfig.h" -#include "opentrack/plugin-api.hpp" - -class SPLINE_WIDGET_EXPORT QFunctionConfigurator : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QColor colorBezier READ colorBezier WRITE setColorBezier) - Q_PROPERTY(bool is_preview_only READ is_preview_only WRITE set_preview_only) -public: - QFunctionConfigurator(QWidget *parent = 0); - - Map* config(); - void setConfig(Map* config, const QString &name); - - QColor colorBezier() const - { - return spline_color; - } - void setColorBezier(QColor color) - { - spline_color = color; - update(); - } - void force_redraw() - { - _background = QPixmap(); - update(); - } - void set_preview_only(bool val); - bool is_preview_only() const; - void set_snap(int x, int y) { snap_x = x; snap_y = y; } - void get_snap(int& x, int& y) const { x = snap_x; y = snap_y; } -protected slots: - void paintEvent(QPaintEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; -private: - void drawBackground(); - void drawFunction(); - void drawPoint(QPainter *painter, const QPointF &pt, QColor colBG, QColor border = QColor(50, 100, 120, 200)); - void drawLine(QPainter *painter, const QPointF &start, const QPointF &end, QPen& pen); - bool point_within_pixel(const QPointF& pt, const QPointF& pixel); -protected: - void resizeEvent(QResizeEvent *) override; -private: - void update_range(); - - QPointF pixel_coord_to_point (const QPointF& point); - QPointF point_to_pixel(const QPointF& point); - - Map* _config; - - // bounds of the rectangle user can interact with - QRectF pixel_bounds; - - int moving_control_point_idx; - QPointF c; - - QColor spline_color; - - QPixmap _background; - QPixmap _function; - int snap_x, snap_y; - bool _draw_function, _preview_only; - - static constexpr int line_length_pixels = 3; - static constexpr int point_size = 4; - static constexpr int point_closeness_limit = 5; -}; diff --git a/spline-widget/spline-widget.cpp b/spline-widget/spline-widget.cpp new file mode 100644 index 00000000..ee2e3c73 --- /dev/null +++ b/spline-widget/spline-widget.cpp @@ -0,0 +1,471 @@ +/* Copyright (c) 2012-2016 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include "opentrack-compat/options.hpp" +using namespace options; +#include "spline-widget.hpp" +#include +#include +#include +#include +#include +#include + +QFunctionConfigurator::QFunctionConfigurator(QWidget *parent) : + QWidget(parent), + _config(nullptr), + moving_control_point_idx(-1), + snap_x(0), + snap_y(0), + _draw_function(true), + _preview_only(false) +{ + update_range(); + setMouseTracking(true); +} + +void QFunctionConfigurator::setConfig(Map* config, const QString& name) +{ + mem iniFile = group::ini_file(); + if (name != "") + config->loadSettings(*iniFile, name); + _config = config; + _draw_function = true; + update_range(); + update(); +} + +void QFunctionConfigurator::set_preview_only(bool val) +{ + _preview_only = val; +} + +bool QFunctionConfigurator::is_preview_only() const +{ + return _preview_only; +} + +void QFunctionConfigurator::drawBackground() +{ + if (!_config) + return; + _background = QPixmap(width(), height()); + + QPainter painter(&_background); + painter.fillRect(rect(), QColor::fromRgb(204, 204, 204)); + + QColor bg_color(112, 154, 209); + if (!isEnabled() && !_preview_only) + bg_color = QColor(176,176,180); + painter.fillRect(pixel_bounds, bg_color); + + QFont font; + font.setPointSize(8); + painter.setFont(font); + QFontMetrics metrics(font); + + QColor color__(176, 190, 209, 127); + + if (!isEnabled()) + color__ = QColor(70, 90, 100, 96); + + QPen pen(color__, 1, Qt::SolidLine); + + const int xstep = 10, ystep = 10; + const qreal maxx = _config->maxInput() + ystep; + const qreal maxy = _config->maxOutput() + xstep; + + // horizontal grid + for (int i = 0; i < maxy; i += xstep) + { + const qreal y = pixel_bounds.height() - i * c.y() + pixel_bounds.y(); + drawLine(&painter, + QPointF(pixel_bounds.x(), y), + QPointF(pixel_bounds.x() + pixel_bounds.width(), y), + pen); + painter.drawText(QRectF(10, + y - metrics.height()/2, + pixel_bounds.left(), + metrics.height()), + QString::number(i)); + } + + // vertical grid + for (int i = 0; i < maxx; i += ystep) + { + const qreal x = pixel_bounds.x() + i * c.x(); + drawLine(&painter, + QPointF(x, pixel_bounds.y()), + QPointF(x, pixel_bounds.y() + pixel_bounds.height()), + pen); + const QString text = QString::number(i); + painter.drawText(QRectF(x - metrics.width(text)/2, + pixel_bounds.height() + 10 + metrics.height(), + metrics.width(text), + metrics.height()), + text); + } +} + +void QFunctionConfigurator::drawFunction() +{ + if (!_config) + return; + + _function = _background; + QPainter painter(&_function); + painter.setRenderHint(QPainter::Antialiasing, true); + + QList points = _config->getPoints(); + + const int alpha = !isEnabled() ? 64 : 120; + if (!_preview_only) + { + for (int i = 0; i < points.size(); i++) + { + drawPoint(&painter, + point_to_pixel(points[i]), + QColor(200, 200, 210, alpha), + isEnabled() ? QColor(50, 100, 120, 200) : QColor(200, 200, 200, 96)); + } + } + + QColor color = spline_color; + + if (!isEnabled() && !_preview_only) + { + const int avg = int(float(color.red() + color.green() + color.blue())/3); + color = QColor(int(float(color.red() + avg) * .5f), + int(float(color.green() + avg) * .5f), + int(float(color.blue() + avg) * .5f), + 96); + } + + QPen pen(color, 1.2, Qt::SolidLine); + + const qreal step_ = line_length_pixels / c.x(); + const qreal step = std::max(1e-2, step_); + const qreal max = _config->maxInput(); + + painter.save(); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + + QPointF prev = point_to_pixel(QPointF(0, 0)); + for (qreal i = 0; i < max; i += step) + { + const qreal val = qreal(_config->getValue(float(i))); + QPointF cur = point_to_pixel(QPointF(i, val)); + painter.drawLine(prev, cur); + prev = cur; + } + + { + const qreal val = _config->getValue(float(max)); + QPointF last = point_to_pixel(QPointF(max, val)); + painter.drawLine(prev, last); + } + + painter.restore(); +} + +void QFunctionConfigurator::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + + if (_background.isNull()) + { + _draw_function = true; + drawBackground(); + } + + if (_draw_function) { + _draw_function = false; + drawFunction(); + } + + p.drawPixmap(e->rect(), _function); + + if (_config) + { + QPen pen(Qt::white, 1, Qt::SolidLine); + QList points = _config->getPoints(); + if (points.size() && + moving_control_point_idx >= 0 && + moving_control_point_idx < points.size()) + { + if (points[0].x() > 1e-2) + points.prepend(QPointF(0, 0)); + QPointF prev = point_to_pixel(points[0]); + for (int i = 1; i < points.size(); i++) + { + auto tmp = point_to_pixel(points[i]); + drawLine(&p, prev, tmp, pen); + prev = tmp; + } + } + + // If the Tracker is active, the 'Last Point' it requested is recorded. + // Show that point on the graph, with some lines to assist. + // This new feature is very handy for tweaking the curves! + QPointF last; + if (_config->getLastPoint(last) && isEnabled()) + { + QPointF pixel_pos = point_to_pixel(last); + drawPoint(&p, pixel_pos, QColor(255, 0, 0, 120)); + } + } +} + +void QFunctionConfigurator::drawPoint(QPainter *painter, const QPointF &pos, QColor colBG, QColor border) +{ + painter->save(); + painter->setPen(border); + painter->setBrush( colBG ); + painter->drawEllipse(QRectF(pos.x() - point_size, + pos.y() - point_size, + point_size*2, point_size*2)); + painter->restore(); +} + +void QFunctionConfigurator::drawLine(QPainter *painter, const QPointF &start, const QPointF &end, QPen &pen) +{ + painter->save(); + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + painter->drawLine(start, end); + painter->restore(); +} + +void QFunctionConfigurator::mousePressEvent(QMouseEvent *e) +{ + if (!_config || !isEnabled()) + return; + QList points = _config->getPoints(); + if (e->button() == Qt::LeftButton) + { + bool bTouchingPoint = false; + moving_control_point_idx = -1; + if (_config) + { + for (int i = 0; i < points.size(); i++) + { + if (point_within_pixel(points[i], e->pos())) + { + bTouchingPoint = true; + moving_control_point_idx = i; + break; + } + } + if (!bTouchingPoint) + { + bool too_close = false; + const auto pos = e->pos(); + + for (int i = 0; i < points.size(); i++) + { + const QPointF pt = point_to_pixel(points[i]); + const auto x = pt.x() - pos.x(); + if (point_closeness_limit * point_closeness_limit >= x * x) + { + too_close = true; + break; + } + } + + if (!too_close) + _config->addPoint(pixel_coord_to_point(e->pos())); + } + } + } + + if (e->button() == Qt::RightButton) + { + if (_config) + { + int found_pt = -1; + for (int i = 0; i < points.size(); i++) + { + if (point_within_pixel(points[i], e->pos())) + { + found_pt = i; + break; + } + } + + if (found_pt != -1) + { + _config->removePoint(found_pt); + } + moving_control_point_idx = -1; + } + } + _draw_function = true; + update(); +} + +void QFunctionConfigurator::mouseMoveEvent(QMouseEvent *e) +{ + if (!_config || !isEnabled()) + return; + + QList points = _config->getPoints(); + + if (moving_control_point_idx != -1 && + moving_control_point_idx < points.size()) + { + setCursor(Qt::ClosedHandCursor); + + bool overlap = false; + + QPointF pix = e->pos(); + QPointF new_pt = pixel_coord_to_point(pix); + + for (int i = 0; i < 2; i++) + { + bool bad = false; + if (moving_control_point_idx + 1 < points.size()) + { + auto other = points[moving_control_point_idx+1]; + auto other_pix = point_to_pixel(other); + bad = pix.x() + point_closeness_limit > other_pix.x(); + if (i == 0 && bad) + { + pix.setX(other_pix.x() - point_closeness_limit); + new_pt = pixel_coord_to_point(pix); + } + else + overlap |= bad; + } + if (moving_control_point_idx != 0) + { + auto other = points[moving_control_point_idx-1]; + auto other_pix = point_to_pixel(other); + bad = pix.x() - point_closeness_limit < other_pix.x(); + if (i == 0 && bad) + { + pix.setX(other_pix.x() + point_closeness_limit); + new_pt = pixel_coord_to_point(pix); + } + else + overlap |= bad; + } + if (!bad) + break; + } + + if (!overlap) + { + points[moving_control_point_idx] = new_pt; + _config->movePoint(moving_control_point_idx, new_pt); + _draw_function = true; + update(); + } + } + else + { + bool is_on_point = false; + for (int i = 0; i < points.size(); i++) + { + const QPoint pos = e->pos(); + if (point_within_pixel(points[i], pos)) + { + is_on_point = true; + break; + } + } + + if (is_on_point) + { + setCursor(Qt::CrossCursor); + } + else + { + setCursor(Qt::ArrowCursor); + } + } +} + +void QFunctionConfigurator::mouseReleaseEvent(QMouseEvent *e) +{ + if (!_config || !isEnabled()) + return; + + if (e->button() == Qt::LeftButton) + { + mouseMoveEvent(e); + setCursor(Qt::ArrowCursor); + moving_control_point_idx = -1; + + _draw_function = true; + update(); + } +} + +void QFunctionConfigurator::update_range() +{ + if (!_config) + return; + + const int w = width(), h = height(); + const int mwl = 40, mhl = 20; + const int mwr = 15, mhr = 35; + + pixel_bounds = QRectF(mwl, mhl, (w - mwl - mwr), (h - mhl - mhr)); + c = QPointF(pixel_bounds.width() / _config->maxInput(), pixel_bounds.height() / _config->maxOutput()); + _draw_function = true; + + _background = QPixmap(); + _function = QPixmap(); + + update(); +} + +bool QFunctionConfigurator::point_within_pixel(const QPointF &pt, const QPointF &pixel) +{ + QPointF tmp = pixel - point_to_pixel(pt); + return sqrt(QPointF::dotProduct(tmp, tmp)) < point_size; +} + +QPointF QFunctionConfigurator::pixel_coord_to_point(const QPointF& point) +{ + if (!_config) + return QPointF(-1, -1); + + using std::round; + + qreal x = round((point.x() - pixel_bounds.x()) / c.x()); + qreal y = round((pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y()); + + if (snap_x > 0) + x -= int(x) % snap_x; + if (snap_y > 0) + y -= int(y) % snap_y; + + if (x < 0) + x = 0; + if (x > _config->maxInput()) + x = _config->maxInput(); + + if (y < 0) + y = 0; + if (y > _config->maxOutput()) + y = _config->maxOutput(); + + return QPointF(x, y); +} + +QPointF QFunctionConfigurator::point_to_pixel(const QPointF& point) +{ + return QPointF(pixel_bounds.x() + point.x() * c.x(), + pixel_bounds.y() + pixel_bounds.height() - point.y() * c.y()); +} + +void QFunctionConfigurator::resizeEvent(QResizeEvent *) +{ + update_range(); +} diff --git a/spline-widget/spline-widget.hpp b/spline-widget/spline-widget.hpp new file mode 100644 index 00000000..cc416b60 --- /dev/null +++ b/spline-widget/spline-widget.hpp @@ -0,0 +1,84 @@ +/* Copyright (c) 2012-2016 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +// Adapted to FaceTrackNoIR by Wim Vriend. + +#pragma once + +#include +#include +#include +#include "spline.hpp" +#include "opentrack/plugin-api.hpp" + +class SPLINE_WIDGET_EXPORT QFunctionConfigurator : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QColor colorBezier READ colorBezier WRITE setColorBezier) + Q_PROPERTY(bool is_preview_only READ is_preview_only WRITE set_preview_only) +public: + QFunctionConfigurator(QWidget *parent = 0); + + Map* config(); + void setConfig(Map* config, const QString &name); + + QColor colorBezier() const + { + return spline_color; + } + void setColorBezier(QColor color) + { + spline_color = color; + update(); + } + void force_redraw() + { + _background = QPixmap(); + update(); + } + void set_preview_only(bool val); + bool is_preview_only() const; + void set_snap(int x, int y) { snap_x = x; snap_y = y; } + void get_snap(int& x, int& y) const { x = snap_x; y = snap_y; } +protected slots: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; +private: + void drawBackground(); + void drawFunction(); + void drawPoint(QPainter *painter, const QPointF &pt, QColor colBG, QColor border = QColor(50, 100, 120, 200)); + void drawLine(QPainter *painter, const QPointF &start, const QPointF &end, QPen& pen); + bool point_within_pixel(const QPointF& pt, const QPointF& pixel); +protected: + void resizeEvent(QResizeEvent *) override; +private: + void update_range(); + + QPointF pixel_coord_to_point (const QPointF& point); + QPointF point_to_pixel(const QPointF& point); + + Map* _config; + + // bounds of the rectangle user can interact with + QRectF pixel_bounds; + + int moving_control_point_idx; + QPointF c; + + QColor spline_color; + + QPixmap _background; + QPixmap _function; + int snap_x, snap_y; + bool _draw_function, _preview_only; + + static constexpr int line_length_pixels = 3; + static constexpr int point_size = 4; + static constexpr int point_closeness_limit = 5; +}; diff --git a/spline-widget/spline.cpp b/spline-widget/spline.cpp new file mode 100644 index 00000000..7bac6b78 --- /dev/null +++ b/spline-widget/spline.cpp @@ -0,0 +1,343 @@ +/* Copyright (c) 2012-2016, Stanislaw Halik + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#include "spline.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +void Map::setTrackingActive(bool blnActive) +{ + activep = blnActive; +} + +Map::Map() : Map(0, 0) +{ +} + +void Map::removeAllPoints() +{ + QMutexLocker foo(&_mutex); + cur.input.clear(); + reload(); +} + +void Map::setMaxInput(qreal max_input) +{ + QMutexLocker l(&_mutex); + max_x = max_input; +} + +void Map::setMaxOutput(qreal max_output) +{ + QMutexLocker l(&_mutex); + max_y = max_output; +} + +qreal Map::maxInput() const +{ + QMutexLocker l(&_mutex); + return max_x; +} + +qreal Map::maxOutput() const +{ + QMutexLocker l(&_mutex); + return max_y; +} + +Map::Map(qreal maxx, qreal maxy) : + _mutex(QMutex::Recursive), + max_x(0), + max_y(0), + activep(false) +{ + setMaxInput(maxx); + setMaxOutput(maxy); + if (cur.input.size() == 0) + cur.input.push_back(QPointF(maxx, maxy)); + reload(); +} + +float Map::getValue(float x) +{ + QMutexLocker foo(&_mutex); + float q = x * precision(); + int xi = (int)q; + float yi = getValueInternal(xi); + float yiplus1 = getValueInternal(xi+1); + float f = (q-xi); + float ret = yiplus1 * f + yi * (1.0f - f); // at least do a linear interpolation. + last_input_value.setX(std::fabs(x)); + last_input_value.setY(std::fabs(ret)); + return ret; +} + +bool Map::getLastPoint(QPointF& point ) +{ + QMutexLocker foo(&_mutex); + point = last_input_value; + return activep; +} + +float Map::getValueInternal(int x) +{ + float sign = x < 0 ? -1 : 1; + x = abs(x); + float ret; + unsigned sz = cur.data.size(); + if (sz == 0) + ret = 0; + else + ret = cur.data[std::min(unsigned(x), sz-1u)]; + return ret * sign; +} + +static QPointF ensureInBounds(const QList& points, int i) +{ + int siz = points.size(); + if (siz == 0 || i < 0) + return QPointF(0, 0); + if (siz > i) + return points[i]; + return points[siz - 1]; +} + +static bool sortFn(const QPointF& one, const QPointF& two) +{ + return one.x() < two.x(); +} + +void Map::reload() +{ + if (cur.input.size()) + { + std::stable_sort(cur.input.begin(), cur.input.end(), sortFn); + + QList input = cur.input; + auto& data = cur.data; + + data = std::vector(value_count); + const float mult = precision(); + const unsigned mult_ = unsigned(mult * 30); + + const unsigned sz = data.size(); + + for (unsigned i = 0; i < sz; i++) + data[i] = -1; + + if (input.size() == 1 && input[0].x() > 1e-2) + { + const float x = float(input[0].x()); + const float y = float(input[0].y()); + const unsigned max = unsigned(x * mult); + for (unsigned k = 0; k < max; k++) { + if (k < sz) + data[k] = y * k / max; + } + } + else if (input[0].x() > 1e-2) + input.prepend(QPointF(0, 0)); + + for (int i = 0; i < int(sz); i++) + { + const QPointF p0 = ensureInBounds(input, i - 1); + const QPointF p1 = ensureInBounds(input, i); + const QPointF p2 = ensureInBounds(input, i + 1); + const QPointF p3 = ensureInBounds(input, i + 2); + + const float p0_x = p0.x(), p1_x = p1.x(), p2_x = p2.x(), p3_x = p3.x(); + const float p0_y = p0.y(), p1_y = p1.y(), p2_y = p2.y(), p3_y = p3.y(); + + // multiplier helps fill in all the x's needed + const unsigned end = std::min(sz, unsigned(p2_x * mult_)); + const unsigned start = unsigned(p1_x * mult); + + for (unsigned j = start; j < end; j++) + { + const float t = (j - start) / (float) (end - start); + const float t2 = t*t; + const float t3 = t*t*t; + + const int x = .5f * ((2 * p1_x) + + (-p0_x + p2_x) * t + + (2 * p0_x - 5 * p1_x + 4 * p2_x - p3_x) * t2 + + (-p0_x + 3 * p1_x - 3 * p2_x + p3_x) * t3) + * mult; + + const float y = .5f * ((2 * p1_y) + + (-p0_y + p2_y) * t + + (2 * p0_y - 5 * p1_y + 4 * p2_y - p3_y) * t2 + + (-p0_y + 3 * p1_y - 3 * p2_y + p3_y) * t3); + + if (x >= 0 && x < (int)sz) + data[x] = y; + } + } + + float last = 0; + for (unsigned i = 0; i < sz; i++) + { + if (data[i] < 0) + data[i] = last; + last = data[i]; + } + } + else + cur.data.clear(); +} + +void Map::removePoint(int i) +{ + QMutexLocker foo(&_mutex); + if (i >= 0 && i < cur.input.size()) + { + cur.input.removeAt(i); + reload(); + } +} + +void Map::addPoint(QPointF pt) +{ + QMutexLocker foo(&_mutex); + cur.input.append(pt); + reload(); + std::stable_sort(cur.input.begin(), cur.input.end(), sortFn); +} + +void Map::movePoint(int idx, QPointF pt) +{ + QMutexLocker foo(&_mutex); + if (idx >= 0 && idx < cur.input.size()) + { + cur.input[idx] = pt; + // we don't allow points to be reordered, but sort due to possible caller logic error + std::stable_sort(cur.input.begin(), cur.input.end(), sortFn); + reload(); + } +} + +const QList Map::getPoints() +{ + QMutexLocker foo(&_mutex); + return cur.input; +} + +void Map::invalidate_unsaved_settings() +{ + QMutexLocker foo(&_mutex); + cur = saved; + reload(); +} + +void Map::loadSettings(QSettings& settings, const QString& title) +{ + QMutexLocker foo(&_mutex); + QPointF newPoint; + QList points; + settings.beginGroup(QString("Curves-%1").arg(title)); + + int max = settings.value("point-count", 0).toInt(); + + for (int i = 0; i < max; i++) + { + newPoint = QPointF(settings.value(QString("point-%1-x").arg(i), 0).toDouble(), + settings.value(QString("point-%1-y").arg(i), 0).toDouble()); + if (newPoint.x() > max_x) + newPoint.setX(max_x); + if (newPoint.y() > max_y) + newPoint.setY(max_y); + points.append(newPoint); + } + + settings.endGroup(); + + if (max == 0) + points.append(QPointF(maxInput(), maxOutput())); + + cur.input = points; + reload(); + saved = cur; +} + +bool Map::State::operator==(const State& other) const +{ + if (input.size() != other.input.size()) + return false; + + const int sz = input.size(); + + using std::fabs; + + for (int i = 0; i < sz; i++) + { + const qreal eps = 1e-3; + + if (fabs(input[i].x() - other.input[i].x()) > eps || + fabs(input[i].y() - other.input[i].y()) > eps) + { + return false; + } + } + return true; +} + +void Map::saveSettings(QSettings& settings, const QString& title) +{ + QMutexLocker foo(&_mutex); + + if (cur == saved) + return; + + qDebug() << "spline-widget: saving" << title; + + settings.beginGroup(QStringLiteral("Curves-%1").arg(title)); + + if (cur.input.size() == 0) + cur.input.push_back(QPointF(max_x, max_y)); + + const int max = cur.input.size(); + settings.setValue("point-count", max); + + for (int i = 0; i < max; i++) + { + settings.setValue(QString("point-%1-x").arg(i), cur.input[i].x()); + settings.setValue(QString("point-%1-y").arg(i), cur.input[i].y()); + } + + for (int i = max; true; i++) + { + QString x = QString("point-%1-x").arg(i); + if (!settings.contains(x)) + break; + settings.remove(x); + settings.remove(QString("point-%1-y").arg(i)); + } + + saved = cur; + + settings.endGroup(); +} + + +int Map::precision() const +{ + if (cur.input.size()) + return (value_count-1) / std::max(1.f, (cur.input[cur.input.size() - 1].x())); + return 1; +} diff --git a/spline-widget/spline.hpp b/spline-widget/spline.hpp new file mode 100644 index 00000000..5d83aefd --- /dev/null +++ b/spline-widget/spline.hpp @@ -0,0 +1,70 @@ +/* Copyright (c) 2012-2016, Stanislaw Halik + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "opentrack-compat/qcopyable-mutex.hpp" + +#ifdef BUILD_spline_widget +# define SPLINE_WIDGET_EXPORT Q_DECL_EXPORT +#else +# define SPLINE_WIDGET_EXPORT Q_DECL_IMPORT +#endif + +class SPLINE_WIDGET_EXPORT Map +{ +private: + int precision() const; + void reload(); + float getValueInternal(int x); + + struct State + { + QList input; + std::vector data; + bool operator==(const State& s) const; + }; + + MyMutex _mutex; + QPointF last_input_value; + State cur, saved; + qreal max_x, max_y; + volatile bool activep; + + static constexpr int value_count = 10000; +public: + qreal maxInput() const; + qreal maxOutput() const; + Map(); + Map(qreal maxx, qreal maxy); + + float getValue(float x); + bool getLastPoint(QPointF& point); + void removePoint(int i); + void removeAllPoints(); + + void addPoint(QPointF pt); + void movePoint(int idx, QPointF pt); + const QList getPoints(); + void setMaxInput(qreal MaxInput); + void setMaxOutput(qreal MaxOutput); + + void saveSettings(QSettings& settings, const QString& title); + void loadSettings(QSettings& settings, const QString& title); + void invalidate_unsaved_settings(); + + void setTrackingActive(bool blnActive); +}; -- cgit v1.2.3