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