diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2016-08-17 21:28:45 +0200 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2016-08-17 21:34:53 +0200 |
commit | cb33be1c50b68d6022f344ddac923c7aac3a11d5 (patch) | |
tree | 1840e3f9829aa69f10f81804f655e97be3f2eca8 /options | |
parent | 41ba8aa5329c16a797140dc23650ef45f42753a3 (diff) |
move options framework into its own library
- adjust usages
- add support for QList signals and metatype
Diffstat (limited to 'options')
-rw-r--r-- | options/CMakeLists.txt | 4 | ||||
-rw-r--r-- | options/bundle.cpp | 140 | ||||
-rw-r--r-- | options/bundle.hpp | 84 | ||||
-rw-r--r-- | options/defs.hpp | 5 | ||||
-rw-r--r-- | options/export.hpp | 27 | ||||
-rw-r--r-- | options/group.cpp | 114 | ||||
-rw-r--r-- | options/group.hpp | 42 | ||||
-rw-r--r-- | options/metatype.cpp | 23 | ||||
-rw-r--r-- | options/metatype.hpp | 36 | ||||
-rw-r--r-- | options/options.hpp | 16 | ||||
-rw-r--r-- | options/scoped.cpp | 14 | ||||
-rw-r--r-- | options/scoped.hpp | 19 | ||||
-rw-r--r-- | options/slider.cpp | 75 | ||||
-rw-r--r-- | options/slider.hpp | 69 | ||||
-rw-r--r-- | options/tie.hpp | 212 | ||||
-rw-r--r-- | options/value.hpp | 167 |
16 files changed, 1047 insertions, 0 deletions
diff --git a/options/CMakeLists.txt b/options/CMakeLists.txt new file mode 100644 index 00000000..25749bf5 --- /dev/null +++ b/options/CMakeLists.txt @@ -0,0 +1,4 @@ +opentrack_boilerplate(opentrack-options NO-COMPAT BIN) +if(NOT WIN32 AND NOT APPLE) + target_link_libraries(opentrack-options rt) +endif() diff --git a/options/bundle.cpp b/options/bundle.cpp new file mode 100644 index 00000000..a60810c5 --- /dev/null +++ b/options/bundle.cpp @@ -0,0 +1,140 @@ +#include "bundle.hpp" + +namespace options +{ + +namespace detail { + +bundle::bundle(const QString& group_name) + : + mtx(QMutex::Recursive), + group_name(group_name), + saved(group_name), + transient(saved) +{ +} + +void bundle::reload() +{ + { + QMutexLocker l(&mtx); + saved = group(group_name); + transient = saved; + } + emit reloading(); +} + +void bundle::store_kv(const QString& name, const QVariant& datum) +{ + QMutexLocker l(&mtx); + + transient.put(name, datum); +} + +bool bundle::contains(const QString &name) const +{ + QMutexLocker l(const_cast<QMutex*>(&mtx)); + return transient.contains(name); +} + +void bundle::save() +{ + if (group_name == "") + return; + + bool modified_ = false; + + { + QMutexLocker l(&mtx); + if (saved != transient) + { + qDebug() << "bundle" << group_name << "changed, saving"; + modified_ = true; + saved = transient; + saved.save(); + } + } + + if (modified_) + emit saving(); +} + +bool bundle::modifiedp() const // XXX unused +{ + QMutexLocker l(const_cast<QMutex*>(&mtx)); + return transient != saved; +} + +void bundler::bundle_decf(const bundler::k& key) +{ + QMutexLocker l(&implsgl_mtx); + + if (--std::get<0>(implsgl_data[key]) == 0) + { + qDebug() << "bundle -" << key; + + implsgl_data.erase(key); + } +} + +void bundler::after_profile_changed_() +{ + QMutexLocker l(&implsgl_mtx); + + for (auto& kv : implsgl_data) + { + tt& tuple = kv.second; + std::weak_ptr<v>& bundle = std::get<1>(tuple); + + mem<v> bundle_ = bundle.lock(); + if (bundle_) + { + qDebug() << "bundle: reverting" << kv.first << "due to profile change"; + bundle_->reload(); + } + } +} + +void bundler::refresh_all_bundles() +{ + singleton().after_profile_changed_(); +} + +bundler::bundler() : implsgl_mtx(QMutex::Recursive) +{ +} + +bundler::~bundler() +{ + qDebug() << "exit: bundle singleton"; +} + +std::shared_ptr<bundler::v> bundler::make_bundle(const bundler::k &key) +{ + QMutexLocker l(&implsgl_mtx); + + if (implsgl_data.count(key) != 0) + { + auto shared = std::get<1>(implsgl_data[key]).lock(); + if (shared != nullptr) + return shared; + } + + qDebug() << "bundle +" << key; + + std::shared_ptr<v> shr(new v(key), [this](v* val) { bundle_decf(val->name()); }); + + implsgl_data[key] = tt(1, shr); + return shr; +} + +OPENTRACK_OPTIONS_EXPORT bundler& singleton() +{ + static bundler ret; + return ret; +} + +} // end options::detail + +} // end options + diff --git a/options/bundle.hpp b/options/bundle.hpp new file mode 100644 index 00000000..56d48f60 --- /dev/null +++ b/options/bundle.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include <memory> +#include <tuple> +#include <map> +#include <memory> + +#include <QObject> +#include <QString> +#include <QVariant> +#include <QMutex> +#include <QMutexLocker> + +#include <QDebug> + +#include "group.hpp" +#include "compat/util.hpp" +#include "export.hpp" + +namespace options { + +namespace detail { + +class OPENTRACK_OPTIONS_EXPORT bundle final : public QObject +{ + Q_OBJECT +protected: + QMutex mtx; + const QString group_name; + group saved; + group transient; + bundle(const bundle&) = delete; + bundle& operator=(const bundle&) = delete; +signals: + void reloading(); + void saving() const; +public: + bundle(const QString& group_name); + QString name() { return group_name; } + void reload(); + void store_kv(const QString& name, const QVariant& datum); + bool contains(const QString& name) const; + void save(); + bool modifiedp() const; + + template<typename t> + t get(const QString& name) const + { + QMutexLocker l(const_cast<QMutex*>(&mtx)); + return transient.get<t>(name); + } +}; + +struct OPENTRACK_OPTIONS_EXPORT bundler +{ +public: + using k = QString; + using v = bundle; + using cnt = int; + using tt = std::tuple<cnt, std::weak_ptr<v>>; +private: + QMutex implsgl_mtx; + std::map<k, tt> implsgl_data; + void after_profile_changed_(); +public: + bundler(); + ~bundler(); + std::shared_ptr<v> make_bundle(const k& key); + void bundle_decf(const k& key); + static void refresh_all_bundles(); +}; + +OPENTRACK_OPTIONS_EXPORT bundler& singleton(); +} + +using bundle_type = detail::bundle; +using bundle = std::shared_ptr<bundle_type>; + +inline bundle make_bundle(const QString& name) +{ + return detail::singleton().make_bundle(name); +} + +} diff --git a/options/defs.hpp b/options/defs.hpp new file mode 100644 index 00000000..8464c784 --- /dev/null +++ b/options/defs.hpp @@ -0,0 +1,5 @@ +#pragma once + +#define OPENTRACK_CONFIG_FILENAME_KEY "settings-filename" +#define OPENTRACK_DEFAULT_CONFIG "default.ini" +#define OPENTRACK_ORG "opentrack-2.3" diff --git a/options/export.hpp b/options/export.hpp new file mode 100644 index 00000000..c430540f --- /dev/null +++ b/options/export.hpp @@ -0,0 +1,27 @@ +#pragma once + +#ifdef BUILD_options +# ifdef _WIN32 +# define OPENTRACK_OPTIONS_LINKAGE __declspec(dllexport) +# else +# define OPENTRACK_OPTIONS_LINKAGE +# endif + +# ifndef _MSC_VER +# define OPENTRACK_OPTIONS_EXPORT __attribute__ ((visibility ("default"))) OPENTRACK_OPTIONS_LINKAGE +# else +# define OPENTRACK_OPTIONS_EXPORT OPENTRACK_OPTIONS_LINKAGE +# endif +#else + #ifdef _WIN32 + # define OPENTRACK_OPTIONS_LINKAGE __declspec(dllimport) + #else + # define OPENTRACK_OPTIONS_LINKAGE + #endif + + #ifndef _MSC_VER + # define OPENTRACK_OPTIONS_EXPORT __attribute__ ((visibility ("default"))) OPENTRACK_OPTIONS_LINKAGE + #else + # define OPENTRACK_OPTIONS_EXPORT OPENTRACK_OPTIONS_LINKAGE + #endif +#endif diff --git a/options/group.cpp b/options/group.cpp new file mode 100644 index 00000000..8aa381fc --- /dev/null +++ b/options/group.cpp @@ -0,0 +1,114 @@ +#include "group.hpp" +#include "defs.hpp" +#include <QStandardPaths> +#include <QDir> +#include <QDebug> + +namespace options { + +group::group(const QString& name) : name(name) +{ + if (name == "") + return; + + auto conf = ini_file(); + conf->beginGroup(name); + for (auto& k_ : conf->childKeys()) + { + auto tmp = k_.toUtf8(); + QString k(tmp); + kvs[k] = conf->value(k_); + } + conf->endGroup(); +} + +void group::save() const +{ + if (name == "") + return; + + auto s = ini_file(); + s->beginGroup(name); + for (auto& i : kvs) + s->setValue(i.first, i.second); + s->endGroup(); +} + +void group::put(const QString &s, const QVariant &d) +{ + kvs[s] = d; +} + +bool group::contains(const QString &s) const +{ + return kvs.count(s) != 0; +} + +QString group::ini_directory() +{ + const auto dirs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); + if (dirs.size() == 0) + return ""; + if (QDir(dirs[0]).mkpath(OPENTRACK_ORG)) + return dirs[0] + "/" OPENTRACK_ORG; + return ""; +} + +QString group::ini_filename() +{ + QSettings settings(OPENTRACK_ORG); + return settings.value(OPENTRACK_CONFIG_FILENAME_KEY, OPENTRACK_DEFAULT_CONFIG).toString(); +} + +QString group::ini_pathname() +{ + const auto dir = ini_directory(); + if (dir == "") + return ""; + return dir + "/" + ini_filename(); +} + +const QStringList group::ini_list() +{ + const auto dirname = ini_directory(); + if (dirname == "") + return QStringList(); + QDir settings_dir(dirname); + QStringList list = settings_dir.entryList( QStringList { "*.ini" } , QDir::Files, QDir::Name ); + std::sort(list.begin(), list.end()); + return list; +} + +const std::shared_ptr<QSettings> group::ini_file() +{ + const auto pathname = ini_pathname(); + if (pathname != "") + return std::make_shared<QSettings>(ini_pathname(), QSettings::IniFormat); + return std::make_shared<QSettings>(); +} + +bool group::operator==(const group& other) const +{ + for (const auto& kv : kvs) + { + const QVariant val = other.get<QVariant>(kv.first); + if (!other.contains(kv.first) || kv.second != val) + { + qDebug() << "bundle" << name << "modified" << "key" << kv.first << "-" << val << "<>" << kv.second; + return false; + } + } + + for (const auto& kv : other.kvs) + { + const QVariant val = get<QVariant>(kv.first); + if (!contains(kv.first) || kv.second != val) + { + qDebug() << "bundle" << name << "modified" << "key" << kv.first << "-" << kv.second << "<>" << val; + return false; + } + } + return true; +} + +} diff --git a/options/group.hpp b/options/group.hpp new file mode 100644 index 00000000..0deddfa8 --- /dev/null +++ b/options/group.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "export.hpp" +#include <map> +#include <memory> +#include <QString> +#include <QList> +#include <QVariant> +#include <QSettings> + +namespace options { + +// snapshot of qsettings group at given time +class OPENTRACK_OPTIONS_EXPORT group final +{ +private: + std::map<QString, QVariant> kvs; + QString name; +public: + group(const QString& name); + void save() const; + void put(const QString& s, const QVariant& d); + bool contains(const QString& s) const; + static QString ini_directory(); + static QString ini_filename(); + static QString ini_pathname(); + static const QStringList ini_list(); + static const std::shared_ptr<QSettings> ini_file(); + bool operator==(const group& other) const; + bool operator!=(const group& other) const { return !(*this == other); } + + template<typename t> + t get(const QString& k) const + { + auto value = kvs.find(k); + if (value != kvs.cend()) + return value->second.value<t>(); + return t(); + } +}; + +} diff --git a/options/metatype.cpp b/options/metatype.cpp new file mode 100644 index 00000000..8f92f385 --- /dev/null +++ b/options/metatype.cpp @@ -0,0 +1,23 @@ +#include "slider.hpp" +#include "metatype.hpp" + +#define OPENTRACK_REGISTER_METATYPE(t) ::options::detail::custom_type_initializer::declare_for_type<t>(#t) + +namespace options { +namespace detail { + +custom_type_initializer::custom_type_initializer() +{ + OPENTRACK_REGISTER_METATYPE(slider_value); + OPENTRACK_REGISTER_METATYPE(QList<double>); + OPENTRACK_REGISTER_METATYPE(QList<float>); + OPENTRACK_REGISTER_METATYPE(QList<int>); + OPENTRACK_REGISTER_METATYPE(QList<bool>); + OPENTRACK_REGISTER_METATYPE(QList<QString>); + OPENTRACK_REGISTER_METATYPE(QList<QPointF>); +} + +const custom_type_initializer custom_type_initializer::singleton; + +} +} diff --git a/options/metatype.hpp b/options/metatype.hpp new file mode 100644 index 00000000..68480b55 --- /dev/null +++ b/options/metatype.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include <QMetaType> +#include <QList> +#include <QString> +#include <QPointF> +#include <QDataStream> +#include <QDebug> + +#include "export.hpp" + +Q_DECLARE_METATYPE(QList<double>) +Q_DECLARE_METATYPE(QList<float>) +Q_DECLARE_METATYPE(QList<int>) +Q_DECLARE_METATYPE(QList<bool>) +Q_DECLARE_METATYPE(QList<QString>) +Q_DECLARE_METATYPE(QList<QPointF>) + +namespace options { +namespace detail { + +struct custom_type_initializer final +{ + static const custom_type_initializer singleton; + + custom_type_initializer(); + + template<typename t> static inline void declare_for_type(const char* str) + { + qRegisterMetaType<t>(str); + qRegisterMetaTypeStreamOperators<t>(); + } +}; + +} +} diff --git a/options/options.hpp b/options/options.hpp new file mode 100644 index 00000000..ebc480f3 --- /dev/null +++ b/options/options.hpp @@ -0,0 +1,16 @@ +/* Copyright (c) 2013-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 "defs.hpp" +#include "bundle.hpp" +#include "group.hpp" +#include "slider.hpp" +#include "value.hpp" +#include "tie.hpp" +#include "metatype.hpp" +#include "scoped.hpp" diff --git a/options/scoped.cpp b/options/scoped.cpp new file mode 100644 index 00000000..e1ddbae2 --- /dev/null +++ b/options/scoped.cpp @@ -0,0 +1,14 @@ +#include "scoped.hpp" + +namespace options { + +opts::~opts() +{ + b->reload(); +} + +opts::opts(const QString &name) : b(make_bundle(name)) +{ +} + +} diff --git a/options/scoped.hpp b/options/scoped.hpp new file mode 100644 index 00000000..52ae383d --- /dev/null +++ b/options/scoped.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "bundle.hpp" +#include <QString> + +#include "export.hpp" + +namespace options { + +struct OPENTRACK_OPTIONS_EXPORT opts +{ + bundle b; + opts(const QString& name); + opts& operator=(const opts&) = delete; + opts(const opts&) = delete; + ~opts(); +}; + +} diff --git a/options/slider.cpp b/options/slider.cpp new file mode 100644 index 00000000..8d46d51b --- /dev/null +++ b/options/slider.cpp @@ -0,0 +1,75 @@ +#include "slider.hpp" +#include <cmath> + +namespace options { + +slider_value::slider_value(float cur, float min, float max) : + cur_(cur), + min_(min), + max_(max) +{ + if (min_ > max_) + min_ = max_; + if (cur_ > max_) + cur_ = max; + if (cur_ < min_) + cur_ = min_; +} + +slider_value::slider_value(const slider_value& v) : slider_value(v.cur(), v.min(), v.max()) +{ +} + + + +slider_value::slider_value() : slider_value(0.f, 0.f, 0.f) +{ +} + +slider_value& slider_value::operator=(const slider_value& v) +{ + cur_ = v.cur_; + + min_ = v.min_; + max_ = v.max_; + + return *this; +} + +bool slider_value::operator==(const slider_value& v) const +{ + using std::fabs; + + static constexpr float eps = 1e-3f; + + return (fabs(v.cur_ - cur_) < eps && + fabs(v.min_ - min_) < eps && + fabs(v.max_ - max_) < eps); +} + +slider_value slider_value::update_from_slider(int pos, int q_min, int q_max) const +{ + slider_value v(*this); + + const int q_diff = q_max - q_min; + const double sv_pos = q_diff == 0 + ? -1e6 + : (((pos - q_min) * (v.max() - v.min())) / q_diff + v.min()); + + if (sv_pos < v.min()) + v = slider_value(v.min(), v.min(), v.max()); + else if (sv_pos > v.max()) + v = slider_value(v.max(), v.min(), v.max()); + else + v = slider_value(sv_pos, v.min(), v.max()); + return v; +} + +int slider_value::to_slider_pos(int q_min, int q_max) const +{ + const int q_diff = q_max - q_min; + + return int(std::round(((cur() - min() * q_diff) / (max() - min())) + q_min)); +} + +} // end ns options diff --git a/options/slider.hpp b/options/slider.hpp new file mode 100644 index 00000000..1d02f17f --- /dev/null +++ b/options/slider.hpp @@ -0,0 +1,69 @@ +/* Copyright (c) 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 "export.hpp" +#include <QMetaType> +#include <QDataStream> +#include <QDebug> + +namespace options +{ + class OPENTRACK_OPTIONS_EXPORT slider_value final + { + float cur_, min_, max_; + public: + slider_value(float cur, float min, float max); + + template<typename t, typename u, typename v> slider_value(t cur, u min, v max) : + cur_(float(cur)), + min_(float(min)), + max_(float(max)) + {} + + slider_value(const slider_value& v); + slider_value(); + slider_value& operator=(const slider_value& v); + bool operator==(const slider_value& v) const; + operator float() const { return cur_; } + double cur() const { return double(cur_); } + double min() const { return double(min_); } + double max() const { return double(max_); } + slider_value update_from_slider(int pos, int q_min, int q_max) const; + int to_slider_pos(int q_min, int q_max) const; + }; +} + +QT_BEGIN_NAMESPACE + +inline QDebug operator << (QDebug dbg, const options::slider_value& val) +{ + return dbg << val.cur(); +} + +inline QDataStream& operator << (QDataStream& out, const options::slider_value& v) +{ + out << float(v.cur()) + << float(v.min()) + << float(v.max()); + return out; +} + +inline QDataStream& operator >> (QDataStream& in, options::slider_value& v) +{ + float cur, min, max; + in >> cur; + in >> min; + in >> max; + v = options::slider_value(cur, min, max); + return in; +} + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(options::slider_value) diff --git a/options/tie.hpp b/options/tie.hpp new file mode 100644 index 00000000..2969f8d4 --- /dev/null +++ b/options/tie.hpp @@ -0,0 +1,212 @@ +#pragma once + +#include "value.hpp" + +#include <QComboBox> +#include <QCheckBox> +#include <QDoubleSpinBox> +#include <QSpinBox> +#include <QSlider> +#include <QLineEdit> +#include <QLabel> +#include <QTabWidget> + +#include <cmath> + +namespace options { + +template<typename t, typename q> +inline void tie_setting(value<t>&, q*); + +template<typename t> +inline +typename std::enable_if<std::is_enum<t>::value>::type +tie_setting(value<t>& v, QComboBox* cb) +{ + cb->setCurrentIndex(cb->findData((unsigned)static_cast<t>(v))); + v = static_cast<t>(cb->currentData().toInt()); + + // QObject::connect plays badly with std::bind of std::shared_ptr. Data seems to get freed. + // Direct accesses of cb->currentData within arbitrary thread context cause crashes as well. + // Hence we go for a verbose implementation. + + std::vector<int> enum_cases; + enum_cases.reserve(unsigned(cb->count())); + + for (int i = 0; i < cb->count(); i++) + enum_cases.push_back(cb->itemData(i).toInt()); + + struct fn1 + { + value<t>& v; + QComboBox* cb; + std::vector<int> enum_cases; + + fn1(value<t>& v, QComboBox* cb, const std::vector<int>& enum_cases) : + v(v), + cb(cb), + enum_cases(enum_cases) + { + } + + void operator()(int idx) + { + if (idx < 0 || idx >= (int)enum_cases.size()) + v = static_cast<t>(-1); + else + v = static_cast<t>(t(std::intptr_t(enum_cases[idx]))); + } + }; + + struct fn2 + { + value<t>& v; + QComboBox* cb; + std::vector<int> enum_cases; + + fn2(value<t>& v, QComboBox* cb, const std::vector<int>& enum_cases) : + v(v), + cb(cb), + enum_cases(enum_cases) + { + } + + void operator()(int val) + { + for (unsigned i = 0; i < enum_cases.size(); i++) + { + if (val == enum_cases[i]) + { + cb->setCurrentIndex(i); + return; + } + } + cb->setCurrentIndex(-1); + } + }; + + base_value::connect(cb, + static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), + &v, + fn1(v, cb, enum_cases), + v.DIRECT_CONNTYPE); + base_value::connect(&v, + static_cast<void (base_value::*)(int)>(&base_value::valueChanged), + cb, + fn2(v, cb, enum_cases), + v.DIRECT_CONNTYPE); +} + +template<> +inline void tie_setting(value<int>& v, QComboBox* cb) +{ + cb->setCurrentIndex(v); + v = cb->currentIndex(); + base_value::connect(cb, SIGNAL(currentIndexChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE); + base_value::connect(&v, SIGNAL(valueChanged(int)), cb, SLOT(setCurrentIndex(int)), v.SAFE_CONNTYPE); +} + +template<> +inline void tie_setting(value<QString>& v, QComboBox* cb) +{ + cb->setCurrentText(v); + v = cb->currentText(); + base_value::connect(cb, SIGNAL(currentTextChanged(QString)), &v, SLOT(setValue(QString)), v.DIRECT_CONNTYPE); + base_value::connect(&v, SIGNAL(valueChanged(QString)), cb, SLOT(setCurrentText(QString)), v.SAFE_CONNTYPE); +} + +template<> +inline void tie_setting(value<bool>& v, QCheckBox* cb) +{ + cb->setChecked(v); + base_value::connect(cb, SIGNAL(toggled(bool)), &v, SLOT(setValue(bool)), v.DIRECT_CONNTYPE); + base_value::connect(&v, SIGNAL(valueChanged(bool)), cb, SLOT(setChecked(bool)), v.SAFE_CONNTYPE); +} + +template<> +inline void tie_setting(value<double>& v, QDoubleSpinBox* dsb) +{ + dsb->setValue(v); + base_value::connect(dsb, SIGNAL(valueChanged(double)), &v, SLOT(setValue(double)), v.DIRECT_CONNTYPE); + base_value::connect(&v, SIGNAL(valueChanged(double)), dsb, SLOT(setValue(double)), v.SAFE_CONNTYPE); +} + +template<> +inline void tie_setting(value<int>& v, QSpinBox* sb) +{ + sb->setValue(v); + base_value::connect(sb, SIGNAL(valueChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE); + base_value::connect(&v, SIGNAL(valueChanged(int)), sb, SLOT(setValue(int)), v.SAFE_CONNTYPE); +} + +template<> +inline void tie_setting(value<int>& v, QSlider* sl) +{ + sl->setValue(v); + v = sl->value(); + base_value::connect(sl, SIGNAL(valueChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE); + base_value::connect(&v, SIGNAL(valueChanged(int)), sl, SLOT(setValue(int)), v.SAFE_CONNTYPE); +} + +template<> +inline void tie_setting(value<QString>& v, QLineEdit* le) +{ + le->setText(v); + base_value::connect(le, SIGNAL(textChanged(QString)), &v, SLOT(setValue(QString)), v.DIRECT_CONNTYPE); + base_value::connect(&v, SIGNAL(valueChanged(QString)),le, SLOT(setText(QString)), v.SAFE_CONNTYPE); +} + +template<> +inline void tie_setting(value<QString>& v, QLabel* lb) +{ + lb->setText(v); + base_value::connect(&v, SIGNAL(valueChanged(QString)), lb, SLOT(setText(QString)), v.DIRECT_CONNTYPE); +} + +template<> +inline void tie_setting(value<int>& v, QTabWidget* t) +{ + t->setCurrentIndex(v); + base_value::connect(t, SIGNAL(currentChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE); + base_value::connect(&v, SIGNAL(valueChanged(int)), t, SLOT(setCurrentIndex(int)), v.SAFE_CONNTYPE); +} + +template<> +inline void tie_setting(value<slider_value>& v, QSlider* w) +{ + // we can't get these at runtime since signals cross threads + const int q_min = w->minimum(); + const int q_max = w->maximum(); + const int q_diff = q_max - q_min; + + slider_value sv(v); + + const double sv_c = sv.max() - sv.min(); + + using std::round; + + w->setValue(int((sv.cur() - sv.min()) / sv_c * q_diff + q_min)); + v = slider_value(q_diff <= 0 ? 0 : (w->value() - q_min) * sv_c / (double)q_diff + sv.min(), sv.min(), sv.max()); + + base_value::connect(w, + &QSlider::valueChanged, + &v, + [=, &v](int pos) { + const int q_diff = q_max - q_min; + if (q_diff <= 0 || pos <= 0) + v = slider_value(sv.min(), sv.min(), sv.max()); + else + { + const float c = float(sv.max()-sv.min()); + v = slider_value((pos - q_min) * c / (float)q_diff + float(sv.min()), sv.min(), sv.max()); + } + }, + v.DIRECT_CONNTYPE); + base_value::connect(&v, + static_cast<void(base_value::*)(double)>(&base_value::valueChanged), + w, + [=](double value) { w->setValue(int(round(value * q_diff) + q_min)); }, + v.SAFE_CONNTYPE); +} + +} diff --git a/options/value.hpp b/options/value.hpp new file mode 100644 index 00000000..4d8d69f8 --- /dev/null +++ b/options/value.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include "bundle.hpp" +#include "slider.hpp" +#include <type_traits> +#include <QString> +#include <QPointF> +#include <QList> + +#define OPENTRACK_DEFINE_SLOT(t) void setValue(t datum) { store(datum); } +#define OPENTRACK_DEFINE_SIGNAL(t) void valueChanged(t) +namespace options { + +namespace detail { +template<typename t> struct value_type_traits { using type = t;}; +template<> struct value_type_traits<QString> { using type = const QString&; }; +template<> struct value_type_traits<slider_value> { using type = const slider_value&; }; +template<typename u> struct value_type_traits<QList<u>> +{ + using type = const QList<typename std::remove_const<typename std::remove_reference<u>::type>::type>&; +}; +template<typename t> using value_type_t = typename value_type_traits<t>::type; +} + +class OPENTRACK_OPTIONS_EXPORT base_value : public QObject +{ + Q_OBJECT +public: + QString name() const { return self_name; } + base_value(bundle b, const QString& name) : b(b), self_name(name) {} +signals: + OPENTRACK_DEFINE_SIGNAL(double); + OPENTRACK_DEFINE_SIGNAL(float); + OPENTRACK_DEFINE_SIGNAL(int); + OPENTRACK_DEFINE_SIGNAL(bool); + OPENTRACK_DEFINE_SIGNAL(const QString&); + OPENTRACK_DEFINE_SIGNAL(const slider_value&); + OPENTRACK_DEFINE_SIGNAL(const QPointF&); + + OPENTRACK_DEFINE_SIGNAL(const QList<double>&); + OPENTRACK_DEFINE_SIGNAL(const QList<float>&); + OPENTRACK_DEFINE_SIGNAL(const QList<int>&); + OPENTRACK_DEFINE_SIGNAL(const QList<bool>&); + OPENTRACK_DEFINE_SIGNAL(const QList<QString>&); + OPENTRACK_DEFINE_SIGNAL(const QList<slider_value>&); + OPENTRACK_DEFINE_SIGNAL(const QList<QPointF>&); +protected: + bundle b; + QString self_name; + + template<typename t> + void store(const t& datum) + { + b->store_kv(self_name, QVariant::fromValue(datum)); + emit valueChanged(static_cast<detail::value_type_t<t>>(datum)); + } + + void store(float datum) + { + store(double(datum)); + } + +public slots: + OPENTRACK_DEFINE_SLOT(double) + OPENTRACK_DEFINE_SLOT(int) + OPENTRACK_DEFINE_SLOT(bool) + OPENTRACK_DEFINE_SLOT(const QString&) + OPENTRACK_DEFINE_SLOT(const slider_value&) + OPENTRACK_DEFINE_SLOT(const QPointF&) + + OPENTRACK_DEFINE_SLOT(const QList<double>&) + OPENTRACK_DEFINE_SLOT(const QList<float>&) + OPENTRACK_DEFINE_SLOT(const QList<int>&) + OPENTRACK_DEFINE_SLOT(const QList<bool>&) + OPENTRACK_DEFINE_SLOT(const QList<QString>&) + OPENTRACK_DEFINE_SLOT(const QList<slider_value>&) + OPENTRACK_DEFINE_SLOT(const QList<QPointF>&) + public slots: + virtual void reload() = 0; +}; + +namespace detail { +template<typename t> +struct value_get_traits +{ + static inline t get(const t& val, const t&) + { + return val; + } +}; + +template<> +struct value_get_traits<slider_value> +{ + using t = slider_value; + static inline t get(const t& val, const t& def) + { + return t(val.cur(), def.min(), def.max()); + } +}; + +template<typename t, typename Enable = void> +struct value_element_type +{ + using type = typename std::remove_reference<typename std::remove_cv<t>::type>::type; +}; + +// Qt uses int a lot in slots so use it for all enums +template<typename t> +struct value_element_type<t, typename std::enable_if<std::is_enum<t>::value>::type> +{ + using type = int; +}; + +template<typename t> using value_element_type_t = typename value_element_type<t>::type; + +} + +template<typename t> +class value final : public base_value +{ +public: + using element_type = detail::value_element_type_t<t>; + + t operator=(const t& datum) + { + store(static_cast<element_type>(datum)); + return datum; + } + + static constexpr const Qt::ConnectionType DIRECT_CONNTYPE = Qt::AutoConnection; + static constexpr const Qt::ConnectionType SAFE_CONNTYPE = Qt::QueuedConnection; + + value(bundle b, const QString& name, t def) : base_value(b, name), def(def) + { + QObject::connect(b.get(), SIGNAL(reloading()), + this, SLOT(reload()), + DIRECT_CONNTYPE); + if (!b->contains(name) || b->get<QVariant>(name).type() == QVariant::Invalid) + *this = def; + } + + value(bundle b, const char* name, t def) : value(b, QString(name), def) + { + } + + t get() const + { + t val = b->contains(self_name) + ? static_cast<t>(b->get<element_type>(self_name)) + : def; + return detail::value_get_traits<t>::get(val, def); + } + + operator t() const { return get(); } + + void reload() override + { + *this = static_cast<t>(*this); + } + +private: + t def; +}; + + +} |