/* Copyright (c) 2013-2014 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 #include #include #include #include #include #include #include #include #include #include #include #include namespace options { template using map = std::map; using std::string; template // don't elide usages of the function, qvariant default implicit // conversion results in nonsensical runtime behavior -sh inline t qcruft_to_t (const QVariant& datum); template<> inline int qcruft_to_t(const QVariant& t) { return t.toInt(); } template<> inline QString qcruft_to_t(const QVariant& t) { return t.toString(); } template<> inline bool qcruft_to_t(const QVariant& t) { return t.toBool(); } template<> inline double qcruft_to_t(const QVariant& t) { return t.toDouble(); } template<> inline QVariant qcruft_to_t(const QVariant& t) { return t; } // snapshot of qsettings group at given time class group { private: map map; string name; static const QString ini_pathname() { QSettings settings(group::org); return settings.value("SettingsFile", QCoreApplication::applicationDirPath() + "/settings/default.ini" ).toString(); } public: group(const string& name) : name(name) { QSettings conf(ini_pathname(), QSettings::IniFormat); auto q_name = QString::fromStdString(name); conf.beginGroup(q_name); for (auto& k_ : conf.childKeys()) { auto tmp = k_.toUtf8(); string k(tmp); map[k] = conf.value(k_); } conf.endGroup(); } static constexpr const char* org = "opentrack"; void save() { QSettings s(ini_pathname(), QSettings::IniFormat); auto q_name = QString::fromStdString(name); s.beginGroup(q_name); for (auto& i : map) { auto k = QString::fromStdString(i.first); s.setValue(k, i.second); } s.endGroup(); } template t get(const string& k) { return qcruft_to_t(map[k]); } void put(const string& s, const QVariant& d) { map[s] = d; } bool contains(const string& s) { return map.count(s) != 0; } }; class impl_bundle : public QObject { Q_OBJECT protected: QMutex mtx; const string group_name; group saved; group transient; bool modified; impl_bundle(const impl_bundle&) = delete; impl_bundle& operator=(const impl_bundle&) = delete; public: impl_bundle(const string& group_name) : mtx(QMutex::Recursive), group_name(group_name), saved(group_name), transient(saved), modified(false) { } string name() { return group_name; } void reload() { QMutexLocker l(&mtx); saved = group(group_name); transient = saved; modified = false; } bool store_kv(const string& name, const QVariant& datum) { QMutexLocker l(&mtx); auto old = transient.get(name); if (!transient.contains(name) || datum != old) { modified = true; transient.put(name, datum); return true; } return false; } bool contains(const string& name) { QMutexLocker l(&mtx); return transient.contains(name); } template t get(const string& name) { QMutexLocker l(&mtx); return transient.get(name); } void save() { QMutexLocker l(&mtx); modified = false; saved = transient; transient.save(); } bool modifiedp() { QMutexLocker l(&mtx); return modified; } }; class opt_bundle; namespace { template struct opt_singleton { public: using pbundle = std::shared_ptr; using tt = std::tuple; private: QMutex implsgl_mtx; map implsgl_data; public: opt_singleton() : implsgl_mtx(QMutex::Recursive) {} pbundle bundle(const k& key) { QMutexLocker l(&implsgl_mtx); if (implsgl_data.count(key) != 0) return std::get<1>(implsgl_data[key]); auto shr = std::make_shared(key); implsgl_data[key] = tt(cnt(1), shr); return shr; } void bundle_decf(const k& key) { QMutexLocker l(&implsgl_mtx); if (--std::get<0>(implsgl_data[key]) == 0) implsgl_data.erase(key); } ~opt_singleton() { implsgl_data.clear(); } }; using pbundle = std::shared_ptr; using t_fact = opt_singleton; static t_fact* opt_factory = new t_fact; } static inline t_fact::pbundle bundle(const string name) { return opt_factory->bundle(name); } class opt_bundle : public impl_bundle { public: opt_bundle() : impl_bundle("i-have-no-name") {} opt_bundle(const string& group_name) : impl_bundle(group_name) {} ~opt_bundle() { opt_factory->bundle_decf(this->group_name); } }; class base_value : public QObject { Q_OBJECT #define DEFINE_SLOT(t) void setValue(t datum) { store(datum); } #define DEFINE_SIGNAL(t) void valueChanged(const t&) public: base_value(pbundle b, const string& name) : b(b), self_name(name), reentrancy_count(0) {} protected: pbundle b; string self_name; template void store(const t& datum) { reentrancy_count++; if (b->store_kv(self_name, datum)) if (reentrancy_count <= 3) emit valueChanged(datum); reentrancy_count--; } private: volatile char reentrancy_count; public slots: DEFINE_SLOT(double) DEFINE_SLOT(int) DEFINE_SLOT(QString) DEFINE_SLOT(bool) signals: DEFINE_SIGNAL(double); DEFINE_SIGNAL(int); DEFINE_SIGNAL(bool); DEFINE_SIGNAL(QString); }; static inline string string_from_qstring(const QString& datum) { auto tmp = datum.toUtf8(); return string(tmp.constData()); } template class value : public base_value { public: t operator=(const t datum) { store(datum); return datum; } static constexpr const Qt::ConnectionType DIRECT_CONNTYPE = Qt::DirectConnection; static constexpr const Qt::ConnectionType SAFE_CONNTYPE = Qt::UniqueConnection; value(pbundle b, const string& name, t def) : base_value(b, name) { if (!b->contains(name) || b->get(name).type() == QVariant::Invalid) *this = def; } value(pbundle b, const QString& name, t def) : value(b, string_from_qstring(name), def) {} value(pbundle b, const char* name, t def) : value(b, string(name), def) {} operator t() { return b->get(self_name); } }; template inline void tie_setting(value&, q*); template<> inline void tie_setting(value& 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& 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& 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& 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& 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& 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& 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& v, QLabel* lb) { lb->setText(v); base_value::connect(&v, SIGNAL(valueChanged(QString)), lb, SLOT(setText(QString)), v.SAFE_CONNTYPE); } }