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 | |
15 | QT_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 | |
204 | Q_LOGGING_CATEGORY(lcSettings, "qt.labs.settings" ) |
205 | |
206 | static const int settingsWriteDelay = 500; |
207 | |
208 | class QQmlSettingsPrivate |
209 | { |
210 | Q_DECLARE_PUBLIC(QQmlSettings) |
211 | |
212 | public: |
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 | |
235 | QQmlSettingsPrivate::QQmlSettingsPrivate() {} |
236 | |
237 | QSettings *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 | |
270 | void QQmlSettingsPrivate::init() |
271 | { |
272 | if (!initialized) { |
273 | qCDebug(lcSettings) << "QQmlSettings: stored at" << instance()->fileName(); |
274 | load(); |
275 | initialized = true; |
276 | } |
277 | } |
278 | |
279 | void QQmlSettingsPrivate::reset() |
280 | { |
281 | if (initialized && settings && !changedProperties.isEmpty()) |
282 | store(); |
283 | delete settings; |
284 | } |
285 | |
286 | void 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 | |
325 | void 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 | |
336 | void 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 | |
353 | QVariant 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 | |
362 | QQmlSettings::QQmlSettings(QObject *parent) |
363 | : QObject(parent), d_ptr(new QQmlSettingsPrivate) |
364 | { |
365 | Q_D(QQmlSettings); |
366 | d->q_ptr = this; |
367 | } |
368 | |
369 | QQmlSettings::~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 | */ |
382 | QString QQmlSettings::category() const |
383 | { |
384 | Q_D(const QQmlSettings); |
385 | return d->category; |
386 | } |
387 | |
388 | void 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 | */ |
409 | QString QQmlSettings::fileName() const |
410 | { |
411 | Q_D(const QQmlSettings); |
412 | return d->fileName; |
413 | } |
414 | |
415 | void 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 | */ |
436 | QVariant 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 | */ |
452 | void 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 | */ |
472 | void QQmlSettings::sync() |
473 | { |
474 | Q_D(QQmlSettings); |
475 | d->instance()->sync(); |
476 | } |
477 | |
478 | void QQmlSettings::classBegin() |
479 | { |
480 | } |
481 | |
482 | void 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 | |
491 | void 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 | |
503 | QT_END_NAMESPACE |
504 | |
505 | #include "moc_qqmlsettings_p.cpp" |
506 | |