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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtdeclarative/src/core/qqmlsettings.cpp