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