diff options
Diffstat (limited to 'compat/options.hpp')
| -rw-r--r-- | compat/options.hpp | 481 | 
1 files changed, 481 insertions, 0 deletions
| diff --git a/compat/options.hpp b/compat/options.hpp new file mode 100644 index 00000000..40aca2de --- /dev/null +++ b/compat/options.hpp @@ -0,0 +1,481 @@ +/* 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" + +#include "util.hpp" + +#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) +        { +        } + +        t get() 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); +        } + +        operator t() const { return get(); } + +        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.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_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.SAFE_CONNTYPE); +    } +} + | 
