/* 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 <memory> #include <tuple> #include <map> #include <cinttypes> #include <vector> #include <memory> #include <QObject> #include <QSettings> #include <QString> #include <QVariant> #include <QMutex> #include <QMutexLocker> #include <QWidget> #include <QComboBox> #include <QCheckBox> #include <QDoubleSpinBox> #include <QSpinBox> #include <QSlider> #include <QLineEdit> #include <QLabel> #include <QTabWidget> #include <QCoreApplication> #include <QFileInfo> #include <QDir> #include <QStandardPaths> #include <QApplication> #include <QMetaType> #include <QDataStream> #include <QDebug> #include "export.hpp" #include "slider.hpp" template<typename t> using mem = std::shared_ptr<t>; #define OPENTRACK_CONFIG_FILENAME_KEY "settings-filename" #define OPENTRACK_DEFAULT_CONFIG "default.ini" #define OPENTRACK_ORG "opentrack-2.3" namespace options { namespace { class custom_type_initializer { custom_type_initializer(); static custom_type_initializer singleton; }; } template<typename k, typename v> using map = std::map<k, v>; // snapshot of qsettings group at given time class OPENTRACK_COMPAT_EXPORT group { private: 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 mem<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(); } }; class OPENTRACK_COMPAT_EXPORT impl_bundle : public QObject { Q_OBJECT protected: QMutex mtx; const QString group_name; group saved; group transient; impl_bundle(const impl_bundle&) = delete; impl_bundle& operator=(const impl_bundle&) = delete; signals: void reloading(); void saving() const; public: impl_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); } }; namespace detail { class OPENTRACK_COMPAT_EXPORT opt_bundle final : public impl_bundle { public: opt_bundle(const QString& group_name); ~opt_bundle(); }; struct OPENTRACK_COMPAT_EXPORT opt_singleton { public: using k = QString; using v = opt_bundle; using cnt = int; using pbundle = std::shared_ptr<v>; using tt = std::tuple<cnt, std::weak_ptr<v>>; private: QMutex implsgl_mtx; map<k, tt> implsgl_data; public: opt_singleton(); pbundle bundle(const k& key); void bundle_decf(const k& key); }; OPENTRACK_COMPAT_EXPORT opt_singleton& singleton(); } using pbundle = std::shared_ptr<detail::opt_bundle>; inline pbundle bundle(const QString& name) { return detail::singleton().bundle(name); } #define OPENTRACK_DEFINE_SLOT(t) void setValue(t datum) { store(datum); } #define OPENTRACK_DEFINE_SIGNAL(t) void valueChanged(t) class OPENTRACK_COMPAT_EXPORT base_value : public QObject { Q_OBJECT public: QString name() const { return self_name; } base_value(pbundle b, const QString& 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&); protected: pbundle b; QString self_name; template<typename t> void store(const t& datum) { b->store_kv(self_name, QVariant::fromValue(datum)); emit valueChanged(static_cast<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&) 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_> class value : public base_value { template<typename t__, typename Enable = void> struct get_t { using t = t__; }; // Qt uses int a lot in slots so use it for all enums template<typename t__> struct get_t<t__, typename std::enable_if<std::is_enum<t__>::value>::type> //{ using t = typename std::underlying_type<t__>::type; }; { using t = int; }; using t = t_; public: using underlying_t = typename get_t<t_>::t; t operator=(const t datum) { store(static_cast<underlying_t>(datum)); return datum; } static constexpr const Qt::ConnectionType DIRECT_CONNTYPE = Qt::AutoConnection; static constexpr const Qt::ConnectionType SAFE_CONNTYPE = Qt::QueuedConnection; value(pbundle 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(pbundle b, const char* name, t def) : value(b, QString(name), def) { } operator t() const { t val = b->contains(self_name) ? static_cast<t>(b->get<underlying_t>(self_name)) : def; return detail::value_get_traits<t>::get(val, def); } void reload() override { *this = static_cast<t>(*this); } private: t def; }; struct OPENTRACK_COMPAT_EXPORT opts { pbundle b; opts(const QString& name); opts& operator=(const opts&) = delete; opts(const opts&) = delete; ~opts(); }; 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.DIRECT_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.DIRECT_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.DIRECT_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.DIRECT_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.DIRECT_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.DIRECT_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.DIRECT_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.DIRECT_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_max = sv.max(); const double sv_min = sv.min(); const double sv_c = sv_max - sv_min; 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) -> void { if (q_diff <= 0 || pos <= 0) v = slider_value(sv_min, sv_min, sv_max); else v = slider_value((pos - q_min) * sv_c / (double)q_diff + 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) -> void { w->setValue(int(value * q_diff) + q_min); }, v.DIRECT_CONNTYPE); } }