1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qqmlsettings_p.h"
41#include <qcoreevent.h>
42#include <qcoreapplication.h>
43#include <qloggingcategory.h>
44#include <qsettings.h>
45#include <qpointer.h>
46#include <qjsvalue.h>
47#include <qqmlinfo.h>
48#include <qdebug.h>
49#include <qhash.h>
50
51QT_BEGIN_NAMESPACE
52
53/*!
54 \qmlmodule Qt.labs.settings 1.0
55 \title Qt Labs Settings QML Types
56 \ingroup qmlmodules
57 \brief Provides persistent platform-independent application settings.
58
59 To use this module, import the module with the following line:
60
61 \code
62 import Qt.labs.settings 1.0
63 \endcode
64*/
65
66/*!
67 \qmltype Settings
68 \instantiates QQmlSettings
69 \inqmlmodule Qt.labs.settings
70 \ingroup settings
71 \brief Provides persistent platform-independent application settings.
72
73 The Settings type provides persistent platform-independent application settings.
74
75 \note This type is made available by importing the \b Qt.labs.settings module.
76 \e {Types in the Qt.labs module are not guaranteed to remain compatible
77 in future versions.}
78
79 Users normally expect an application to remember its settings (window sizes
80 and positions, options, etc.) across sessions. The Settings type enables you
81 to save and restore such application settings with the minimum of effort.
82
83 Individual setting values are specified by declaring properties within a
84 Settings element. All \l {QML Basic Types}{basic type} properties are
85 supported. The recommended approach is to use property aliases in order
86 to get automatic property updates both ways. The following example shows
87 how to use Settings to store and restore the geometry of a window.
88
89 \qml
90 import QtQuick.Window 2.1
91 import Qt.labs.settings 1.0
92
93 Window {
94 id: window
95
96 width: 800
97 height: 600
98
99 Settings {
100 property alias x: window.x
101 property alias y: window.y
102 property alias width: window.width
103 property alias height: window.height
104 }
105 }
106 \endqml
107
108 At first application startup, the window gets default dimensions specified
109 as 800x600. Notice that no default position is specified - we let the window
110 manager handle that. Later when the window geometry changes, new values will
111 be automatically stored to the persistent settings. The second application
112 run will get initial values from the persistent settings, bringing the window
113 back to the previous position and size.
114
115 A fully declarative syntax, achieved by using property aliases, comes at the
116 cost of storing persistent settings whenever the values of aliased properties
117 change. Normal properties can be used to gain more fine-grained control over
118 storing the persistent settings. The following example illustrates how to save
119 a setting on component destruction.
120
121 \qml
122 import QtQuick 2.1
123 import Qt.labs.settings 1.0
124
125 Item {
126 id: page
127
128 state: settings.state
129
130 states: [
131 State {
132 name: "active"
133 // ...
134 },
135 State {
136 name: "inactive"
137 // ...
138 }
139 ]
140
141 Settings {
142 id: settings
143 property string state: "active"
144 }
145
146 Component.onDestruction: {
147 settings.state = page.state
148 }
149 }
150 \endqml
151
152 Notice how the default value is now specified in the persistent setting property,
153 and the actual property is bound to the setting in order to get the initial value
154 from the persistent settings.
155
156 \section1 Application Identifiers
157
158 Application specific settings are identified by providing application
159 \l {QCoreApplication::applicationName}{name},
160 \l {QCoreApplication::organizationName}{organization} and
161 \l {QCoreApplication::organizationDomain}{domain}, or by specifying
162 \l fileName.
163
164 \code
165 #include <QGuiApplication>
166 #include <QQmlApplicationEngine>
167
168 int main(int argc, char *argv[])
169 {
170 QGuiApplication app(argc, argv);
171 app.setOrganizationName("Some Company");
172 app.setOrganizationDomain("somecompany.com");
173 app.setApplicationName("Amazing Application");
174
175 QQmlApplicationEngine engine("main.qml");
176 return app.exec();
177 }
178 \endcode
179
180 These are typically specified in C++ in the beginning of \c main(),
181 but can also be controlled in QML via the following properties:
182 \list
183 \li \l {Qt::application}{Qt.application.name},
184 \li \l {Qt::application}{Qt.application.organization} and
185 \li \l {Qt::application}{Qt.application.domain}.
186 \endlist
187
188 \section1 Categories
189
190 Application settings may be divided into logical categories by specifying
191 a category name via the \l category property. Using logical categories not
192 only provides a cleaner settings structure, but also prevents possible
193 conflicts between setting keys.
194
195 If several categories are required, use several Settings objects, each with
196 their own category:
197
198 \qml
199 Item {
200 id: panel
201
202 visible: true
203
204 Settings {
205 category: "OutputPanel"
206 property alias visible: panel.visible
207 // ...
208 }
209
210 Settings {
211 category: "General"
212 property alias fontSize: fontSizeSpinBox.value
213 // ...
214 }
215 }
216 \endqml
217
218 Instead of ensuring that all settings in the application have unique names,
219 the settings can be divided into unique categories that may then contain
220 settings using the same names that are used in other categories - without
221 a conflict.
222
223 \section1 Notes
224
225 The current implementation is based on \l QSettings. This imposes certain
226 limitations, such as missing change notifications. Writing a setting value
227 using one instance of Settings does not update the value in another Settings
228 instance, even if they are referring to the same setting in the same category.
229
230 The information is stored in the system registry on Windows, and in XML
231 preferences files on \macos. On other Unix systems, in the absence of a
232 standard, INI text files are used. See \l QSettings documentation for
233 more details.
234
235 \sa QSettings
236*/
237
238Q_LOGGING_CATEGORY(lcSettings, "qt.labs.settings")
239
240static const int settingsWriteDelay = 500;
241
242class QQmlSettingsPrivate
243{
244 Q_DECLARE_PUBLIC(QQmlSettings)
245
246public:
247 QQmlSettingsPrivate();
248
249 QSettings *instance() const;
250
251 void init();
252 void reset();
253
254 void load();
255 void store();
256
257 void _q_propertyChanged();
258 QVariant readProperty(const QMetaProperty &property) const;
259
260 QQmlSettings *q_ptr = nullptr;
261 int timerId = 0;
262 bool initialized = false;
263 QString category;
264 QString fileName;
265 mutable QPointer<QSettings> settings;
266 QHash<const char *, QVariant> changedProperties;
267};
268
269QQmlSettingsPrivate::QQmlSettingsPrivate() {}
270
271QSettings *QQmlSettingsPrivate::instance() const
272{
273 if (!settings) {
274 QQmlSettings *q = const_cast<QQmlSettings*>(q_func());
275 settings = fileName.isEmpty() ? new QSettings(q) : new QSettings(fileName, QSettings::IniFormat, q);
276 if (settings->status() != QSettings::NoError) {
277 // TODO: can't print out the enum due to the following error:
278 // error: C2666: 'QQmlInfo::operator <<': 15 overloads have similar conversions
279 qmlWarning(me: q) << "Failed to initialize QSettings instance. Status code is: " << int(settings->status());
280
281 if (settings->status() == QSettings::AccessError) {
282 QVector<QString> missingIdentifiers;
283 if (QCoreApplication::organizationName().isEmpty())
284 missingIdentifiers.append(t: QLatin1String("organizationName"));
285 if (QCoreApplication::organizationDomain().isEmpty())
286 missingIdentifiers.append(t: QLatin1String("organizationDomain"));
287 if (QCoreApplication::applicationName().isEmpty())
288 missingIdentifiers.append(t: QLatin1String("applicationName"));
289
290 if (!missingIdentifiers.isEmpty())
291 qmlWarning(me: q) << "The following application identifiers have not been set: " << missingIdentifiers;
292 }
293 return settings;
294 }
295
296 if (!category.isEmpty())
297 settings->beginGroup(prefix: category);
298 if (initialized)
299 q->d_func()->load();
300 }
301 return settings;
302}
303
304void QQmlSettingsPrivate::init()
305{
306 if (!initialized) {
307 qCDebug(lcSettings) << "QQmlSettings: stored at" << instance()->fileName();
308 load();
309 initialized = true;
310 }
311}
312
313void QQmlSettingsPrivate::reset()
314{
315 if (initialized && settings && !changedProperties.isEmpty())
316 store();
317 delete settings;
318}
319
320void QQmlSettingsPrivate::load()
321{
322 Q_Q(QQmlSettings);
323 const QMetaObject *mo = q->metaObject();
324 const int offset = mo->propertyOffset();
325 const int count = mo->propertyCount();
326
327 // don't save built-in properties if there aren't any qml properties
328 if (offset == 1)
329 return;
330
331 for (int i = offset; i < count; ++i) {
332 QMetaProperty property = mo->property(index: i);
333
334 const QVariant previousValue = readProperty(property);
335 const QVariant currentValue = instance()->value(key: property.name(), defaultValue: previousValue);
336
337 if (!currentValue.isNull() && (!previousValue.isValid()
338 || (currentValue.canConvert(targetTypeId: previousValue.userType()) && previousValue != currentValue))) {
339 property.write(obj: q, value: currentValue);
340 qCDebug(lcSettings) << "QQmlSettings: load" << property.name() << "setting:" << currentValue << "default:" << previousValue;
341 }
342
343 // ensure that a non-existent setting gets written
344 // even if the property wouldn't change later
345 if (!instance()->contains(key: property.name()))
346 _q_propertyChanged();
347
348 // setup change notifications on first load
349 if (!initialized && property.hasNotifySignal()) {
350 static const int propertyChangedIndex = mo->indexOfSlot(slot: "_q_propertyChanged()");
351 QMetaObject::connect(sender: q, signal_index: property.notifySignalIndex(), receiver: q, method_index: propertyChangedIndex);
352 }
353 }
354}
355
356void QQmlSettingsPrivate::store()
357{
358 QHash<const char *, QVariant>::const_iterator it = changedProperties.constBegin();
359 while (it != changedProperties.constEnd()) {
360 instance()->setValue(key: it.key(), value: it.value());
361 qCDebug(lcSettings) << "QQmlSettings: store" << it.key() << ":" << it.value();
362 ++it;
363 }
364 changedProperties.clear();
365}
366
367void QQmlSettingsPrivate::_q_propertyChanged()
368{
369 Q_Q(QQmlSettings);
370 const QMetaObject *mo = q->metaObject();
371 const int offset = mo->propertyOffset();
372 const int count = mo->propertyCount();
373 for (int i = offset; i < count; ++i) {
374 const QMetaProperty &property = mo->property(index: i);
375 const QVariant value = readProperty(property);
376 changedProperties.insert(akey: property.name(), avalue: value);
377 qCDebug(lcSettings) << "QQmlSettings: cache" << property.name() << ":" << value;
378 }
379 if (timerId != 0)
380 q->killTimer(id: timerId);
381 timerId = q->startTimer(interval: settingsWriteDelay);
382}
383
384QVariant QQmlSettingsPrivate::readProperty(const QMetaProperty &property) const
385{
386 Q_Q(const QQmlSettings);
387 QVariant var = property.read(obj: q);
388 if (var.userType() == qMetaTypeId<QJSValue>())
389 var = var.value<QJSValue>().toVariant();
390 return var;
391}
392
393QQmlSettings::QQmlSettings(QObject *parent)
394 : QObject(parent), d_ptr(new QQmlSettingsPrivate)
395{
396 Q_D(QQmlSettings);
397 d->q_ptr = this;
398}
399
400QQmlSettings::~QQmlSettings()
401{
402 Q_D(QQmlSettings);
403 d->reset(); // flush pending changes
404}
405
406/*!
407 \qmlproperty string Settings::category
408
409 This property holds the name of the settings category.
410
411 Categories can be used to group related settings together.
412*/
413QString QQmlSettings::category() const
414{
415 Q_D(const QQmlSettings);
416 return d->category;
417}
418
419void QQmlSettings::setCategory(const QString &category)
420{
421 Q_D(QQmlSettings);
422 if (d->category != category) {
423 d->reset();
424 d->category = category;
425 if (d->initialized)
426 d->load();
427 }
428}
429
430/*!
431 \qmlproperty string Settings::fileName
432
433 This property holds the path to the settings file. If the file doesn't
434 already exist, it is created.
435
436 \since Qt 5.12
437
438 \sa QSettings::fileName, QSettings::IniFormat
439*/
440QString QQmlSettings::fileName() const
441{
442 Q_D(const QQmlSettings);
443 return d->fileName;
444}
445
446void QQmlSettings::setFileName(const QString &fileName)
447{
448 Q_D(QQmlSettings);
449 if (d->fileName != fileName) {
450 d->reset();
451 d->fileName = fileName;
452 if (d->initialized)
453 d->load();
454 }
455}
456
457/*!
458 \qmlmethod var Settings::value(string key, var defaultValue)
459
460 Returns the value for setting \a key. If the setting doesn't exist,
461 returns \a defaultValue.
462
463 \since Qt 5.12
464
465 \sa QSettings::value
466*/
467QVariant QQmlSettings::value(const QString &key, const QVariant &defaultValue) const
468{
469 Q_D(const QQmlSettings);
470 return d->instance()->value(key, defaultValue);
471}
472
473/*!
474 \qmlmethod Settings::setValue(string key, var value)
475
476 Sets the value of setting \a key to \a value. If the key already exists,
477 the previous value is overwritten.
478
479 \since Qt 5.12
480
481 \sa QSettings::setValue
482*/
483void QQmlSettings::setValue(const QString &key, const QVariant &value)
484{
485 Q_D(const QQmlSettings);
486 d->instance()->setValue(key, value);
487 qCDebug(lcSettings) << "QQmlSettings: setValue" << key << ":" << value;
488}
489
490/*!
491 \qmlmethod Settings::sync()
492
493 Writes any unsaved changes to permanent storage, and reloads any
494 settings that have been changed in the meantime by another
495 application.
496
497 This function is called automatically from QSettings's destructor and
498 by the event loop at regular intervals, so you normally don't need to
499 call it yourself.
500
501 \sa QSettings::sync
502*/
503void QQmlSettings::sync()
504{
505 Q_D(QQmlSettings);
506 d->instance()->sync();
507}
508
509void QQmlSettings::classBegin()
510{
511}
512
513void QQmlSettings::componentComplete()
514{
515 Q_D(QQmlSettings);
516 d->init();
517}
518
519void QQmlSettings::timerEvent(QTimerEvent *event)
520{
521 Q_D(QQmlSettings);
522 if (event->timerId() == d->timerId) {
523 killTimer(id: d->timerId);
524 d->timerId = 0;
525
526 d->store();
527 }
528 QObject::timerEvent(event);
529}
530
531QT_END_NAMESPACE
532
533#include "moc_qqmlsettings_p.cpp"
534

source code of qtdeclarative/src/imports/settings/qqmlsettings.cpp