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

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