/* Copyright (c) 2013-2015 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 <string>
#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 <cinttypes>
#include <QDebug>
#include <memory>
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 "TrackHat opentrack-2.3"
namespace options {
template<typename k, typename v>
using map = std::map<k, v>;
using std::string;
template<typename t>
// 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<int>(const QVariant& t)
{
return t.toInt();
}
template<>
inline QString qcruft_to_t<QString>(const QVariant& t)
{
return t.toString();
}
template<>
inline bool qcruft_to_t<bool>(const QVariant& t)
{
return t.toBool();
}
template<>
inline double qcruft_to_t<double>(const QVariant& t)
{
return t.toDouble();
}
template<>
inline QVariant qcruft_to_t<QVariant>(const QVariant& t)
{
return t;
}
// snapshot of qsettings group at given time
class group {
private:
map<string, QVariant> kvs;
string name;
public:
group(const string& name) : name(name)
{
auto conf = ini_file();
auto q_name = QString::fromStdString(name);
conf->beginGroup(q_name);
for (auto& k_ : conf->childKeys())
{
auto tmp = k_.toUtf8();
string k(tmp);
kvs[k] = conf->value(k_);
}
conf->endGroup();
}
void save()
{
auto s = ini_file();
auto q_name = QString::fromStdString(name);
s->beginGroup(q_name);
for (auto& i : kvs)
{
auto k = QString::fromStdString(i.first);
s->setValue(k, i.second);
}
s->endGroup();
s->sync();
}
template<typename t>
t get(const string& k)
{
return qcruft_to_t<t>(kvs[k]);
}
void put(const string& s, const QVariant& d)
{
kvs[s] = d;
}
bool contains(const string& s)
{
return kvs.count(s) != 0;
}
static QString 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 "";
}
static QString ini_filename()
{
QSettings settings(OPENTRACK_ORG);
return settings.value(OPENTRACK_CONFIG_FILENAME_KEY, OPENTRACK_DEFAULT_CONFIG).toString();
}
static QString ini_pathname()
{
const auto dir = ini_directory();
if (dir == "")
return "";
QSettings settings(OPENTRACK_ORG);
return dir + "/" + settings.value(OPENTRACK_CONFIG_FILENAME_KEY, OPENTRACK_DEFAULT_CONFIG).toString();
}
static const QStringList ini_list()
{
const auto dirname = ini_directory();
if (dirname == "")
return QStringList();
QDir settings_dir(dirname);
return settings_dir.entryList( QStringList { "*.ini" } , QDir::Files, QDir::Name );
}
static const mem<QSettings> ini_file()
{
const auto pathname = ini_pathname();
if (pathname != "")
return std::make_shared<QSettings>(ini_pathname(), QSettings::IniFormat);
return std::make_shared<QSettings>();
}
};
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;
signals:
void reloading();
void saving();
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;
}
emit reloading();
}
void store_kv(const string& name, const QVariant& datum)
{
QMutexLocker l(&mtx);
auto old = transient.get<QVariant>(name);
if (!transient.contains(name) || datum != old)
{
modified = true;
transient.put(name, datum);
}
}
bool contains(const string& name)
{
QMutexLocker l(&mtx);
return transient.contains(name);
}
template<typename t>
t get(const string& name)
{
QMutexLocker l(&mtx);
return transient.get<t>(name);
}
void save()
{
{
QMutexLocker l(&mtx);
modified = false;
saved = transient;
transient.save();
}
emit saving();
}
bool modifiedp() {
QMutexLocker l(&mtx);
return modified;
}
};
class opt_bundle;
namespace
{
template<typename k, typename v, typename cnt = int>
struct opt_singleton
{
public:
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() : implsgl_mtx(QMutex::Recursive) {}
static opt_singleton<k, v>& datum()
{
static auto ret = std::make_shared<opt_singleton<k, v>>();
return *ret;
}
pbundle bundle(const 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 +" << QString::fromStdString(key);
auto shr = std::make_shared<v>(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);
}
};
using pbundle = std::shared_ptr<opt_bundle>;
using t_fact = opt_singleton<string, opt_bundle>;
}
static inline t_fact::pbundle bundle(const string name) { return t_fact::datum().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()
{
qDebug() << "bundle -" << QString::fromStdString(group_name);
t_fact::datum().bundle_decf(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(t)
public:
string name() { return self_name; }
base_value(pbundle b, const string& name) : b(b), self_name(name) {}
signals:
DEFINE_SIGNAL(double);
DEFINE_SIGNAL(int);
DEFINE_SIGNAL(bool);
DEFINE_SIGNAL(QString);
protected:
pbundle b;
string self_name;
template<typename t>
void store(const t& datum)
{
b->store_kv(self_name, datum);
emit valueChanged(static_cast<t>(datum));
}
public slots:
DEFINE_SLOT(double)
DEFINE_SLOT(int)
DEFINE_SLOT(QString)
DEFINE_SLOT(bool)
public slots:
virtual void reload() = 0;
};
static inline string string_from_qstring(const QString& datum)
{
auto tmp = datum.toUtf8();
return string(tmp.constData());
}
template<typename t>
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), 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 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->contains(self_name) ? b->get<t>(self_name) : def;
}
void reload() override {
*this = static_cast<t>(*this);
}
private:
t def;
};
struct opts
{
pbundle b;
opts(const std::string& name) : b(bundle(name)) {}
~opts()
{
b->reload();
}
};
template<typename t, typename q>
inline void tie_setting(value<t>&, q*);
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.SAFE_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);
}
}