diff options
author | Maximilian Goldstein <max.goldstein@qt.io> | 2021-01-20 14:48:05 +0100 |
---|---|---|
committer | Maximilian Goldstein <max.goldstein@qt.io> | 2021-01-20 17:28:10 +0100 |
commit | 0fb373c9a2ebc31f76b27bfa5c2e59dadefeaf08 (patch) | |
tree | 8df5099729d1e7fd9a973b4caf8fa1db7ca31de0 /src/labs | |
parent | a738c3566790c6e9abeea3c4c3dacfceaa82f66a (diff) |
Qt.labs.settings: Make plugin optional
This moves the settings types into a new library and is meant to make
them availabe to the QML compiler at some point in the future.
Task-number: QTBUG-90487
Change-Id: I986615b08ea8c1a7312b9d9c6ae0b13c03fb5497
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/labs')
-rw-r--r-- | src/labs/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/labs/settings/CMakeLists.txt | 21 | ||||
-rw-r--r-- | src/labs/settings/qqmlsettings.cpp | 534 | ||||
-rw-r--r-- | src/labs/settings/qqmlsettings_p.h | 105 | ||||
-rw-r--r-- | src/labs/settings/qqmlsettingsglobal_p.h | 73 |
5 files changed, 734 insertions, 0 deletions
diff --git a/src/labs/CMakeLists.txt b/src/labs/CMakeLists.txt new file mode 100644 index 0000000000..b1f8c7c142 --- /dev/null +++ b/src/labs/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(settings) diff --git a/src/labs/settings/CMakeLists.txt b/src/labs/settings/CMakeLists.txt new file mode 100644 index 0000000000..23b5018dd5 --- /dev/null +++ b/src/labs/settings/CMakeLists.txt @@ -0,0 +1,21 @@ +qt_internal_add_module(LabsSettings + GENERATE_METATYPES + SOURCES + qqmlsettings.cpp qqmlsettings_p.h + qqmlsettingsglobal_p.h + DEFINES + QT_BUILD_LABSSETTINGS_LIB + PUBLIC_LIBRARIES + Qt::Core + Qt::Qml +) + +set_target_properties(LabsSettings PROPERTIES + QT_QML_MODULE_INSTALL_QMLTYPES TRUE + QT_QML_MODULE_VERSION ${CMAKE_PROJECT_VERSION} + QT_QML_MODULE_URI Qt.labs.settings + QT_QMLTYPES_FILENAME plugins.qmltypes + QT_QML_MODULE_INSTALL_DIR "${INSTALL_QMLDIR}/Qt/labs/settings" +) + +qt6_qml_type_registration(LabsSettings) diff --git a/src/labs/settings/qqmlsettings.cpp b/src/labs/settings/qqmlsettings.cpp new file mode 100644 index 0000000000..ff8ec5e065 --- /dev/null +++ b/src/labs/settings/qqmlsettings.cpp @@ -0,0 +1,534 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlsettings_p.h" +#include <qcoreevent.h> +#include <qcoreapplication.h> +#include <qloggingcategory.h> +#include <qsettings.h> +#include <qpointer.h> +#include <qjsvalue.h> +#include <qqmlinfo.h> +#include <qdebug.h> +#include <qhash.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmlmodule Qt.labs.settings 1.0 + \title Qt Labs Settings QML Types + \ingroup qmlmodules + \brief Provides persistent platform-independent application settings. + + To use this module, import the module with the following line: + + \code + import Qt.labs.settings + \endcode +*/ + +/*! + \qmltype Settings + \instantiates QQmlSettings + \inqmlmodule Qt.labs.settings + \ingroup settings + \brief Provides persistent platform-independent application settings. + + The Settings type provides persistent platform-independent application settings. + + \note This type is made available by importing the \b Qt.labs.settings module. + \e {Types in the Qt.labs module are not guaranteed to remain compatible + in future versions.} + + Users normally expect an application to remember its settings (window sizes + and positions, options, etc.) across sessions. The Settings type enables you + to save and restore such application settings with the minimum of effort. + + Individual setting values are specified by declaring properties within a + Settings element. All \l {QML Basic Types}{basic type} properties are + supported. The recommended approach is to use property aliases in order + to get automatic property updates both ways. The following example shows + how to use Settings to store and restore the geometry of a window. + + \qml + import QtQuick.Window + import Qt.labs.settings + + Window { + id: window + + width: 800 + height: 600 + + Settings { + property alias x: window.x + property alias y: window.y + property alias width: window.width + property alias height: window.height + } + } + \endqml + + At first application startup, the window gets default dimensions specified + as 800x600. Notice that no default position is specified - we let the window + manager handle that. Later when the window geometry changes, new values will + be automatically stored to the persistent settings. The second application + run will get initial values from the persistent settings, bringing the window + back to the previous position and size. + + A fully declarative syntax, achieved by using property aliases, comes at the + cost of storing persistent settings whenever the values of aliased properties + change. Normal properties can be used to gain more fine-grained control over + storing the persistent settings. The following example illustrates how to save + a setting on component destruction. + + \qml + import QtQuick + import Qt.labs.settings + + Item { + id: page + + state: settings.state + + states: [ + State { + name: "active" + // ... + }, + State { + name: "inactive" + // ... + } + ] + + Settings { + id: settings + property string state: "active" + } + + Component.onDestruction: { + settings.state = page.state + } + } + \endqml + + Notice how the default value is now specified in the persistent setting property, + and the actual property is bound to the setting in order to get the initial value + from the persistent settings. + + \section1 Application Identifiers + + Application specific settings are identified by providing application + \l {QCoreApplication::applicationName}{name}, + \l {QCoreApplication::organizationName}{organization} and + \l {QCoreApplication::organizationDomain}{domain}, or by specifying + \l fileName. + + \code + #include <QGuiApplication> + #include <QQmlApplicationEngine> + + int main(int argc, char *argv[]) + { + QGuiApplication app(argc, argv); + app.setOrganizationName("Some Company"); + app.setOrganizationDomain("somecompany.com"); + app.setApplicationName("Amazing Application"); + + QQmlApplicationEngine engine("main.qml"); + return app.exec(); + } + \endcode + + These are typically specified in C++ in the beginning of \c main(), + but can also be controlled in QML via the following properties: + \list + \li \l {Qt::application}{Qt.application.name}, + \li \l {Qt::application}{Qt.application.organization} and + \li \l {Qt::application}{Qt.application.domain}. + \endlist + + \section1 Categories + + Application settings may be divided into logical categories by specifying + a category name via the \l category property. Using logical categories not + only provides a cleaner settings structure, but also prevents possible + conflicts between setting keys. + + If several categories are required, use several Settings objects, each with + their own category: + + \qml + Item { + id: panel + + visible: true + + Settings { + category: "OutputPanel" + property alias visible: panel.visible + // ... + } + + Settings { + category: "General" + property alias fontSize: fontSizeSpinBox.value + // ... + } + } + \endqml + + Instead of ensuring that all settings in the application have unique names, + the settings can be divided into unique categories that may then contain + settings using the same names that are used in other categories - without + a conflict. + + \section1 Notes + + The current implementation is based on \l QSettings. This imposes certain + limitations, such as missing change notifications. Writing a setting value + using one instance of Settings does not update the value in another Settings + instance, even if they are referring to the same setting in the same category. + + The information is stored in the system registry on Windows, and in XML + preferences files on \macos. On other Unix systems, in the absence of a + standard, INI text files are used. See \l QSettings documentation for + more details. + + \sa QSettings +*/ + +Q_LOGGING_CATEGORY(lcSettings, "qt.labs.settings") + +static const int settingsWriteDelay = 500; + +class QQmlSettingsPrivate +{ + Q_DECLARE_PUBLIC(QQmlSettings) + +public: + QQmlSettingsPrivate(); + + QSettings *instance() const; + + void init(); + void reset(); + + void load(); + void store(); + + void _q_propertyChanged(); + QVariant readProperty(const QMetaProperty &property) const; + + QQmlSettings *q_ptr = nullptr; + int timerId = 0; + bool initialized = false; + QString category; + QString fileName; + mutable QPointer<QSettings> settings; + QHash<const char *, QVariant> changedProperties; +}; + +QQmlSettingsPrivate::QQmlSettingsPrivate() {} + +QSettings *QQmlSettingsPrivate::instance() const +{ + if (!settings) { + QQmlSettings *q = const_cast<QQmlSettings*>(q_func()); + settings = fileName.isEmpty() ? new QSettings(q) : new QSettings(fileName, QSettings::IniFormat, q); + if (settings->status() != QSettings::NoError) { + // TODO: can't print out the enum due to the following error: + // error: C2666: 'QQmlInfo::operator <<': 15 overloads have similar conversions + qmlWarning(q) << "Failed to initialize QSettings instance. Status code is: " << int(settings->status()); + + if (settings->status() == QSettings::AccessError) { + QVector<QString> missingIdentifiers; + if (QCoreApplication::organizationName().isEmpty()) + missingIdentifiers.append(QLatin1String("organizationName")); + if (QCoreApplication::organizationDomain().isEmpty()) + missingIdentifiers.append(QLatin1String("organizationDomain")); + if (QCoreApplication::applicationName().isEmpty()) + missingIdentifiers.append(QLatin1String("applicationName")); + + if (!missingIdentifiers.isEmpty()) + qmlWarning(q) << "The following application identifiers have not been set: " << missingIdentifiers; + } + return settings; + } + + if (!category.isEmpty()) + settings->beginGroup(category); + if (initialized) + q->d_func()->load(); + } + return settings; +} + +void QQmlSettingsPrivate::init() +{ + if (!initialized) { + qCDebug(lcSettings) << "QQmlSettings: stored at" << instance()->fileName(); + load(); + initialized = true; + } +} + +void QQmlSettingsPrivate::reset() +{ + if (initialized && settings && !changedProperties.isEmpty()) + store(); + delete settings; +} + +void QQmlSettingsPrivate::load() +{ + Q_Q(QQmlSettings); + const QMetaObject *mo = q->metaObject(); + const int offset = mo->propertyOffset(); + const int count = mo->propertyCount(); + + // don't save built-in properties if there aren't any qml properties + if (offset == 1) + return; + + for (int i = offset; i < count; ++i) { + QMetaProperty property = mo->property(i); + + const QVariant previousValue = readProperty(property); + const QVariant currentValue = instance()->value(property.name(), previousValue); + + if (!currentValue.isNull() && (!previousValue.isValid() + || (currentValue.canConvert(previousValue.metaType()) + && previousValue != currentValue))) { + property.write(q, currentValue); + qCDebug(lcSettings) << "QQmlSettings: load" << property.name() << "setting:" << currentValue << "default:" << previousValue; + } + + // ensure that a non-existent setting gets written + // even if the property wouldn't change later + if (!instance()->contains(property.name())) + _q_propertyChanged(); + + // setup change notifications on first load + if (!initialized && property.hasNotifySignal()) { + static const int propertyChangedIndex = mo->indexOfSlot("_q_propertyChanged()"); + QMetaObject::connect(q, property.notifySignalIndex(), q, propertyChangedIndex); + } + } +} + +void QQmlSettingsPrivate::store() +{ + QHash<const char *, QVariant>::const_iterator it = changedProperties.constBegin(); + while (it != changedProperties.constEnd()) { + instance()->setValue(it.key(), it.value()); + qCDebug(lcSettings) << "QQmlSettings: store" << it.key() << ":" << it.value(); + ++it; + } + changedProperties.clear(); +} + +void QQmlSettingsPrivate::_q_propertyChanged() +{ + Q_Q(QQmlSettings); + const QMetaObject *mo = q->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); + changedProperties.insert(property.name(), value); + qCDebug(lcSettings) << "QQmlSettings: cache" << property.name() << ":" << value; + } + if (timerId != 0) + q->killTimer(timerId); + timerId = q->startTimer(settingsWriteDelay); +} + +QVariant QQmlSettingsPrivate::readProperty(const QMetaProperty &property) const +{ + Q_Q(const QQmlSettings); + QVariant var = property.read(q); + if (var.userType() == qMetaTypeId<QJSValue>()) + var = var.value<QJSValue>().toVariant(); + return var; +} + +QQmlSettings::QQmlSettings(QObject *parent) + : QObject(parent), d_ptr(new QQmlSettingsPrivate) +{ + Q_D(QQmlSettings); + d->q_ptr = this; +} + +QQmlSettings::~QQmlSettings() +{ + Q_D(QQmlSettings); + d->reset(); // flush pending changes +} + +/*! + \qmlproperty string Settings::category + + This property holds the name of the settings category. + + Categories can be used to group related settings together. +*/ +QString QQmlSettings::category() const +{ + Q_D(const QQmlSettings); + return d->category; +} + +void QQmlSettings::setCategory(const QString &category) +{ + Q_D(QQmlSettings); + if (d->category != category) { + d->reset(); + d->category = category; + if (d->initialized) + d->load(); + } +} + +/*! + \qmlproperty string Settings::fileName + + This property holds the path to the settings file. If the file doesn't + already exist, it is created. + + \since Qt 5.12 + + \sa QSettings::fileName, QSettings::IniFormat +*/ +QString QQmlSettings::fileName() const +{ + Q_D(const QQmlSettings); + return d->fileName; +} + +void QQmlSettings::setFileName(const QString &fileName) +{ + Q_D(QQmlSettings); + if (d->fileName != fileName) { + d->reset(); + d->fileName = fileName; + if (d->initialized) + d->load(); + } +} + +/*! + \qmlmethod var Settings::value(string key, var defaultValue) + + Returns the value for setting \a key. If the setting doesn't exist, + returns \a defaultValue. + + \since Qt 5.12 + + \sa QSettings::value +*/ +QVariant QQmlSettings::value(const QString &key, const QVariant &defaultValue) const +{ + Q_D(const QQmlSettings); + return d->instance()->value(key, defaultValue); +} + +/*! + \qmlmethod Settings::setValue(string key, var value) + + Sets the value of setting \a key to \a value. If the key already exists, + the previous value is overwritten. + + \since Qt 5.12 + + \sa QSettings::setValue +*/ +void QQmlSettings::setValue(const QString &key, const QVariant &value) +{ + Q_D(const QQmlSettings); + d->instance()->setValue(key, value); + qCDebug(lcSettings) << "QQmlSettings: setValue" << key << ":" << value; +} + +/*! + \qmlmethod Settings::sync() + + Writes any unsaved changes to permanent storage, and reloads any + settings that have been changed in the meantime by another + application. + + This function is called automatically from QSettings's destructor and + by the event loop at regular intervals, so you normally don't need to + call it yourself. + + \sa QSettings::sync +*/ +void QQmlSettings::sync() +{ + Q_D(QQmlSettings); + d->instance()->sync(); +} + +void QQmlSettings::classBegin() +{ +} + +void QQmlSettings::componentComplete() +{ + Q_D(QQmlSettings); + d->init(); +} + +void QQmlSettings::timerEvent(QTimerEvent *event) +{ + Q_D(QQmlSettings); + if (event->timerId() == d->timerId) { + killTimer(d->timerId); + d->timerId = 0; + + d->store(); + } + QObject::timerEvent(event); +} + +QT_END_NAMESPACE + +#include "moc_qqmlsettings_p.cpp" diff --git a/src/labs/settings/qqmlsettings_p.h b/src/labs/settings/qqmlsettings_p.h new file mode 100644 index 0000000000..1739f3934a --- /dev/null +++ b/src/labs/settings/qqmlsettings_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLSETTINGS_P_H +#define QQMLSETTINGS_P_H + +#include "qqmlsettingsglobal_p.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/qqml.h> +#include <QtCore/qobject.h> +#include <QtCore/qscopedpointer.h> +#include <QtQml/qqmlparserstatus.h> + +QT_BEGIN_NAMESPACE + +class QQmlSettingsPrivate; + +class Q_LABSSETTINGS_PRIVATE_EXPORT QQmlSettings : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QString category READ category WRITE setCategory FINAL) + Q_PROPERTY(QString fileName READ fileName WRITE setFileName FINAL) + QML_NAMED_ELEMENT(Settings) + QML_ADDED_IN_VERSION(1, 0) + +public: + explicit QQmlSettings(QObject *parent = 0); + ~QQmlSettings(); + + QString category() const; + void setCategory(const QString &category); + + QString fileName() const; + void setFileName(const QString &fileName); + + Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + Q_INVOKABLE void setValue(const QString &key, const QVariant &value); + Q_INVOKABLE void sync(); + +protected: + void timerEvent(QTimerEvent *event) override; + + void classBegin() override; + void componentComplete() override; + +private: + Q_DISABLE_COPY(QQmlSettings) + Q_DECLARE_PRIVATE(QQmlSettings) + QScopedPointer<QQmlSettingsPrivate> d_ptr; + Q_PRIVATE_SLOT(d_func(), void _q_propertyChanged()) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlSettings) + +#endif // QQMLSETTINGS_P_H diff --git a/src/labs/settings/qqmlsettingsglobal_p.h b/src/labs/settings/qqmlsettingsglobal_p.h new file mode 100644 index 0000000000..c0bea2585e --- /dev/null +++ b/src/labs/settings/qqmlsettingsglobal_p.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTLABSSETTINGSGLOBAL_P_H +#define QTLABSSETTINGSGLOBAL_P_H + +#include <QtCore/qglobal.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +#if !defined(QT_STATIC) +# if defined(QT_BUILD_LABSSETTINGS_LIB) +# define Q_LABSSETTINGS_EXPORT Q_DECL_EXPORT +# else +# define Q_LABSSETTINGS_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_LABSSETTINGS_EXPORT +#endif +#define Q_LABSSETTINGS_PRIVATE_EXPORT Q_LABSSETTINGS_EXPORT + +QT_END_NAMESPACE + +void Q_LABSSETTINGS_PRIVATE_EXPORT qml_register_types_Qt_labs_settings(); + +#endif // QTLABSSETTINGSGLOBAL_P_H |