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