Line data Source code
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
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
91 : import Qt.labs.settings
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
123 : import Qt.labs.settings
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 0 : Q_LOGGING_CATEGORY(lcSettings, "qt.labs.settings")
239 :
240 : static const int settingsWriteDelay = 500;
241 :
242 : class QQmlSettingsPrivate
243 : {
244 0 : 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 0 : QQmlSettingsPrivate::QQmlSettingsPrivate() {}
270 :
271 0 : QSettings* QQmlSettingsPrivate::instance() const
272 : {
273 0 : if (!settings) {
274 0 : QQmlSettings* q = const_cast<QQmlSettings*>(q_func());
275 0 : settings = fileName.isEmpty() ? new QSettings(q) : new QSettings(fileName, QSettings::IniFormat, q);
276 0 : 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 0 : qmlWarning(q) << "Failed to initialize QSettings instance. Status code is: " << int(settings->status());
280 :
281 0 : if (settings->status() == QSettings::AccessError) {
282 0 : QVector<QString> missingIdentifiers;
283 0 : if (QCoreApplication::organizationName().isEmpty()) {
284 0 : missingIdentifiers.append(QLatin1String("organizationName"));
285 : }
286 0 : if (QCoreApplication::organizationDomain().isEmpty()) {
287 0 : missingIdentifiers.append(QLatin1String("organizationDomain"));
288 : }
289 0 : if (QCoreApplication::applicationName().isEmpty()) {
290 0 : missingIdentifiers.append(QLatin1String("applicationName"));
291 : }
292 :
293 0 : if (!missingIdentifiers.isEmpty()) {
294 0 : qmlWarning(q) << "The following application identifiers have not been set: " << missingIdentifiers;
295 : }
296 0 : }
297 0 : return settings;
298 : }
299 :
300 0 : if (!category.isEmpty()) {
301 0 : settings->beginGroup(category);
302 : }
303 0 : if (initialized) {
304 0 : q->d_func()->load();
305 : }
306 : }
307 0 : return settings;
308 : }
309 :
310 0 : void QQmlSettingsPrivate::init()
311 : {
312 0 : if (!initialized) {
313 0 : qCDebug(lcSettings) << "QQmlSettings: stored at" << instance()->fileName();
314 0 : load();
315 0 : initialized = true;
316 : }
317 0 : }
318 :
319 0 : void QQmlSettingsPrivate::reset()
320 : {
321 0 : if (initialized && settings && !changedProperties.isEmpty()) {
322 0 : store();
323 : }
324 0 : delete settings;
325 0 : }
326 :
327 0 : void QQmlSettingsPrivate::load()
328 : {
329 0 : Q_Q(QQmlSettings);
330 0 : const QMetaObject* mo = q->metaObject();
331 0 : const int offset = mo->propertyOffset();
332 0 : const int count = mo->propertyCount();
333 :
334 : // don't save built-in properties if there aren't any qml properties
335 0 : if (offset == 1) {
336 0 : return;
337 : }
338 :
339 0 : for (int i = offset; i < count; ++i) {
340 0 : QMetaProperty property = mo->property(i);
341 :
342 0 : const QVariant previousValue = readProperty(property);
343 0 : const QVariant currentValue = instance()->value(property.name(), previousValue);
344 :
345 0 : if (!currentValue.isNull() && (!previousValue.isValid()
346 0 : || (currentValue.canConvert(previousValue.metaType())
347 0 : && previousValue != currentValue))) {
348 0 : property.write(q, currentValue);
349 0 : qCDebug(lcSettings) << "QQmlSettings: load" << property.name() << "setting:" << currentValue << "default:" << previousValue;
350 : }
351 :
352 : // ensure that a non-existent setting gets written
353 : // even if the property wouldn't change later
354 0 : if (!instance()->contains(property.name())) {
355 0 : _q_propertyChanged();
356 : }
357 :
358 : // setup change notifications on first load
359 0 : if (!initialized && property.hasNotifySignal()) {
360 0 : static const int propertyChangedIndex = mo->indexOfSlot("_q_propertyChanged()");
361 0 : QMetaObject::connect(q, property.notifySignalIndex(), q, propertyChangedIndex);
362 : }
363 0 : }
364 : }
365 :
366 0 : void QQmlSettingsPrivate::store()
367 : {
368 0 : QHash<const char*, QVariant>::const_iterator it = changedProperties.constBegin();
369 0 : while (it != changedProperties.constEnd()) {
370 0 : instance()->setValue(it.key(), it.value());
371 0 : qCDebug(lcSettings) << "QQmlSettings: store" << it.key() << ":" << it.value();
372 0 : ++it;
373 : }
374 0 : changedProperties.clear();
375 0 : }
376 :
377 0 : void QQmlSettingsPrivate::_q_propertyChanged()
378 : {
379 0 : Q_Q(QQmlSettings);
380 0 : const QMetaObject* mo = q->metaObject();
381 0 : const int offset = mo->propertyOffset();
382 0 : const int count = mo->propertyCount();
383 0 : for (int i = offset; i < count; ++i) {
384 0 : const QMetaProperty& property = mo->property(i);
385 0 : const QVariant value = readProperty(property);
386 0 : changedProperties.insert(property.name(), value);
387 0 : qCDebug(lcSettings) << "QQmlSettings: cache" << property.name() << ":" << value;
388 0 : }
389 0 : if (timerId != 0) {
390 0 : q->killTimer(timerId);
391 : }
392 0 : timerId = q->startTimer(settingsWriteDelay);
393 0 : }
394 :
395 0 : QVariant QQmlSettingsPrivate::readProperty(const QMetaProperty& property) const
396 : {
397 0 : Q_Q(const QQmlSettings);
398 0 : QVariant var = property.read(q);
399 0 : if (var.userType() == qMetaTypeId<QJSValue>()) {
400 0 : var = var.value<QJSValue>().toVariant();
401 : }
402 0 : return var;
403 0 : }
404 :
405 : #define Q_PD(Class) Class##Private* const pd = d_func()
406 :
407 0 : QQmlSettings::QQmlSettings(QObject* parent)
408 0 : : QObject(parent), d_ptr(new QQmlSettingsPrivate)
409 : {
410 0 : Q_PD(QQmlSettings);
411 0 : pd->q_ptr = this;
412 0 : }
413 :
414 0 : QQmlSettings::~QQmlSettings()
415 : {
416 0 : Q_PD(QQmlSettings);
417 0 : pd->reset(); // flush pending changes
418 0 : }
419 :
420 : /*!
421 : \qmlproperty string Settings::category
422 :
423 : This property holds the name of the settings category.
424 :
425 : Categories can be used to group related settings together.
426 : */
427 0 : QString QQmlSettings::category() const
428 : {
429 0 : Q_PD(const QQmlSettings);
430 0 : return pd->category;
431 : }
432 :
433 0 : void QQmlSettings::setCategory(const QString& category)
434 : {
435 0 : Q_PD(QQmlSettings);
436 0 : if (pd->category != category) {
437 0 : pd->reset();
438 0 : pd->category = category;
439 0 : if (pd->initialized) {
440 0 : pd->load();
441 : }
442 : }
443 0 : }
444 :
445 : /*!
446 : \qmlproperty string Settings::fileName
447 :
448 : This property holds the path to the settings file. If the file doesn't
449 : already exist, it is created.
450 :
451 : \since Qt 5.12
452 :
453 : \sa QSettings::fileName, QSettings::IniFormat
454 : */
455 0 : QString QQmlSettings::fileName() const
456 : {
457 0 : Q_PD(const QQmlSettings);
458 0 : return pd->fileName;
459 : }
460 :
461 0 : void QQmlSettings::setFileName(const QString& fileName)
462 : {
463 0 : Q_PD(QQmlSettings);
464 0 : if (pd->fileName != fileName) {
465 0 : pd->reset();
466 0 : pd->fileName = fileName;
467 0 : if (pd->initialized) {
468 0 : pd->load();
469 : }
470 : }
471 0 : }
472 :
473 : /*!
474 : \qmlmethod var Settings::value(string key, var defaultValue)
475 :
476 : Returns the value for setting \a key. If the setting doesn't exist,
477 : returns \a defaultValue.
478 :
479 : \since Qt 5.12
480 :
481 : \sa QSettings::value
482 : */
483 0 : QVariant QQmlSettings::value(const QString& key, const QVariant& defaultValue) const
484 : {
485 0 : Q_PD(const QQmlSettings);
486 0 : return pd->instance()->value(key, defaultValue);
487 : }
488 :
489 : /*!
490 : \qmlmethod Settings::setValue(string key, var value)
491 :
492 : Sets the value of setting \a key to \a value. If the key already exists,
493 : the previous value is overwritten.
494 :
495 : \since Qt 5.12
496 :
497 : \sa QSettings::setValue
498 : */
499 0 : void QQmlSettings::setValue(const QString& key, const QVariant& value)
500 : {
501 0 : Q_PD(const QQmlSettings);
502 0 : pd->instance()->setValue(key, value);
503 0 : qCDebug(lcSettings) << "QQmlSettings: setValue" << key << ":" << value;
504 0 : }
505 :
506 : /*!
507 : \qmlmethod Settings::sync()
508 :
509 : Writes any unsaved changes to permanent storage, and reloads any
510 : settings that have been changed in the meantime by another
511 : application.
512 :
513 : This function is called automatically from QSettings's destructor and
514 : by the event loop at regular intervals, so you normally don't need to
515 : call it yourself.
516 :
517 : \sa QSettings::sync
518 : */
519 0 : void QQmlSettings::sync()
520 : {
521 0 : Q_PD(QQmlSettings);
522 0 : pd->instance()->sync();
523 0 : }
524 :
525 0 : void QQmlSettings::classBegin()
526 : {
527 0 : }
528 :
529 0 : void QQmlSettings::componentComplete()
530 : {
531 0 : Q_PD(QQmlSettings);
532 0 : pd->init();
533 0 : }
534 :
535 0 : void QQmlSettings::timerEvent(QTimerEvent* event)
536 : {
537 0 : Q_PD(QQmlSettings);
538 0 : if (event->timerId() == pd->timerId) {
539 0 : killTimer(pd->timerId);
540 0 : pd->timerId = 0;
541 :
542 0 : pd->store();
543 : }
544 0 : QObject::timerEvent(event);
545 0 : }
546 :
547 : QT_END_NAMESPACE
548 :
549 : #include "moc_qqmlsettings_p.cpp"
|