From 96c99b250ed4557c1f396941614fb755a3487cb1 Mon Sep 17 00:00:00 2001 From: dsc Date: Wed, 3 Jul 2019 01:27:25 +0200 Subject: [PATCH] Replace Qt.labs.settings with MoneroSettings to allow for more customization --- main.cpp | 4 + main.qml | 4 +- monero-wallet-gui.pro | 6 +- src/qt/MoneroSettings.cpp | 207 ++++++++++++++++++++++++++++++++++++++ src/qt/MoneroSettings.h | 81 +++++++++++++++ 5 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 src/qt/MoneroSettings.cpp create mode 100644 src/qt/MoneroSettings.h diff --git a/main.cpp b/main.cpp index a51568d2..a41b3c75 100644 --- a/main.cpp +++ b/main.cpp @@ -66,6 +66,7 @@ #include "qt/utils.h" #include "qt/mime.h" #include "src/qt/KeysFiles.h" +#include "src/qt/MoneroSettings.h" #include "qt/prices.h" // IOS exclusions @@ -224,6 +225,9 @@ int main(int argc, char *argv[]) // registering types for QML qmlRegisterType("moneroComponents.Clipboard", 1, 0, "Clipboard"); + // Temporary Qt.labs.settings replacement + qmlRegisterType("moneroComponents.Settings", 1, 0, "MoneroSettings"); + qmlRegisterUncreatableType("moneroComponents.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); diff --git a/main.qml b/main.qml index fb003482..1655456c 100644 --- a/main.qml +++ b/main.qml @@ -31,11 +31,11 @@ import QtQuick.Window 2.0 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 import QtQuick.Dialogs 1.2 -import Qt.labs.settings 1.0 import moneroComponents.Wallet 1.0 import moneroComponents.PendingTransaction 1.0 import moneroComponents.NetworkType 1.0 +import moneroComponents.Settings 1.0 import "components" import "components" as MoneroComponents @@ -1347,7 +1347,7 @@ ApplicationWindow { } } - Settings { + MoneroSettings { id: persistentSettings property string language property string locale diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro index 745fa1bd..d832ee12 100644 --- a/monero-wallet-gui.pro +++ b/monero-wallet-gui.pro @@ -69,7 +69,8 @@ HEADERS += \ src/qt/KeysFiles.h \ src/qt/utils.h \ src/qt/prices.h \ - src/qt/macoshelper.h + src/qt/macoshelper.h \ + src/qt/MoneroSettings.h SOURCES += main.cpp \ filter.cpp \ @@ -103,7 +104,8 @@ SOURCES += main.cpp \ src/qt/mime.cpp \ src/qt/KeysFiles.cpp \ src/qt/utils.cpp \ - src/qt/prices.cpp + src/qt/prices.cpp \ + src/qt/MoneroSettings.cpp CONFIG(DISABLE_PASS_STRENGTH_METER) { HEADERS -= src/zxcvbn-c/zxcvbn.h diff --git a/src/qt/MoneroSettings.cpp b/src/qt/MoneroSettings.cpp new file mode 100644 index 00000000..ba3e6876 --- /dev/null +++ b/src/qt/MoneroSettings.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +****************************************************************************/ +// Copyright (c) 2014-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include + +#include "src/qt/MoneroSettings.h" + +/*! + \qmlmodule moneroSettings 1.0 + \title Monero Settings QML Component + \ingroup qmlmodules + \brief Provides persistent platform-independent application settings. + + This component was introduced in order to have control over where the + configuration file is written. This is needed for Tails OS and + portable installations. + + For more information, see: https://doc.qt.io/qt-5/qml-qt-labs-settings-settings.html and + https://github.com/qt/qtdeclarative/blob/v5.12.0/src/imports/settings/qqmlsettings.cpp + + To use this module, import the module with the following line: + \code + import moneroComponents.Settings 1.0 + \endcode + + Usage: + \code + MoneroSettings { id: persistentSettings, property bool foo: true } + \endcode + + @TODO: Remove this QML component after migrating to Qt >= 5.12.0, as + `Qt.labs.settings` provides the fileName via a Q_PROPERTY +*/ + + +void MoneroSettings::load() +{ + const QMetaObject *mo = this->metaObject(); + const int offset = mo->propertyOffset(); + const int count = mo->propertyCount(); + + for (int i = offset; i < count; ++i) { + QMetaProperty property = mo->property(i); + const QVariant previousValue = readProperty(property); + const QVariant currentValue = this->m_settings->value(property.name(), previousValue); + + if (!currentValue.isNull() && (!previousValue.isValid() + || (currentValue.canConvert(previousValue.type()) && previousValue != currentValue))) { + property.write(this, currentValue); +#ifdef QT_DEBUG + qDebug() << "QQmlSettings: load" << property.name() << "setting:" << currentValue << "default:" << previousValue; +#endif + } + + // ensure that a non-existent setting gets written + // even if the property wouldn't change later + if (!this->m_settings->contains(property.name())) + this->_q_propertyChanged(); + + // setup change notifications on first load + if (!this->m_initialized && property.hasNotifySignal()) { + static const int propertyChangedIndex = mo->indexOfSlot("_q_propertyChanged()"); + int signalIndex = property.notifySignalIndex(); + QMetaObject::connect(this, signalIndex, this, propertyChangedIndex); + } + } +} + +void MoneroSettings::_q_propertyChanged() +{ + // Called on QML property change + const QMetaObject *mo = this->metaObject(); + const int offset = mo->propertyOffset(); + const int count = mo->propertyCount(); + for (int i = offset; i < count; ++i) { + const QMetaProperty &property = mo->property(i); + const QVariant value = readProperty(property); + this->m_changedProperties.insert(property.name(), value); +#ifdef QT_DEBUG + //qDebug() << "QQmlSettings: cache" << property.name() << ":" << value; +#endif + } + + if (this->m_timerId != 0) + this->killTimer(this->m_timerId); + this->m_timerId = this->startTimer(settingsWriteDelay); +} + +QVariant MoneroSettings::readProperty(const QMetaProperty &property) const +{ + QVariant var = property.read(this); + if (var.userType() == qMetaTypeId()) + var = var.value().toVariant(); + return var; +} + +void MoneroSettings::init() +{ + if (!this->m_initialized) { + this->m_settings = this->m_fileName.isEmpty() ? new QSettings() : new QSettings(this->m_fileName, QSettings::IniFormat); +#ifdef QT_DEBUG + qDebug() << "QQmlSettings: stored at" << this->m_settings->fileName(); +#endif + this->load(); + this->m_initialized = true; + } +} + +void MoneroSettings::reset() +{ + if (this->m_initialized && this->m_settings && !this->m_changedProperties.isEmpty()) + this->store(); + delete this->m_settings; +} + +void MoneroSettings::store() +{ + QHash::const_iterator it = this->m_changedProperties.constBegin(); + + while (it != this->m_changedProperties.constEnd()) { + this->m_settings->setValue(it.key(), it.value()); + +#ifdef QT_DEBUG + //qDebug() << "QQmlSettings: store" << it.key() << ":" << it.value(); +#endif + + ++it; + } + + this->m_changedProperties.clear(); +} + +void MoneroSettings::setFileName(const QString &fileName) +{ + if (fileName != this->m_fileName) { + this->reset(); + this->m_fileName = fileName; + if (this->m_initialized) + this->load(); + } +} + +QString MoneroSettings::fileName() const +{ + return this->m_fileName; +} + +void MoneroSettings::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == this->m_timerId) { + killTimer(this->m_timerId); + this->m_timerId = 0; + this->store(); + } + QObject::timerEvent(event); +} + +void MoneroSettings::componentComplete() +{ + this->init(); +} + +void MoneroSettings::classBegin() +{ +} + +MoneroSettings::MoneroSettings(QObject *parent) : + QObject(parent) +{ +} diff --git a/src/qt/MoneroSettings.h b/src/qt/MoneroSettings.h new file mode 100644 index 00000000..6de7f641 --- /dev/null +++ b/src/qt/MoneroSettings.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +****************************************************************************/ +// Copyright (c) 2014-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#ifndef MONEROSETTINGS_H +#define MONEROSETTINGS_H + +#include +#include +#include +#include +#include +#include + +static const int settingsWriteDelay = 500; // ms + +class MoneroSettings : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QString fileName READ fileName WRITE setFileName FINAL) +public: + explicit MoneroSettings(QObject *parent = nullptr); + + QString fileName() const; + void setFileName(const QString &fileName); + +public slots: + void _q_propertyChanged(); + +protected: + void timerEvent(QTimerEvent *event) override; + void classBegin() override; + void componentComplete() override; + +private: + QVariant readProperty(const QMetaProperty &property) const; + void init(); + void reset(); + void load(); + void store(); + + QHash m_changedProperties; + QSettings *m_settings; + QString m_fileName = QString(""); + bool m_initialized = false; + int m_timerId = 0; +}; + +#endif // MONEROSETTINGS_H