1 /***************************************************************************
2   qgssettings.h
3   --------------------------------------
4   Date                 : January 2017
5   Copyright            : (C) 2017 by Alessandro Pasotti
6   Email                : apasotti at boundlessgeo dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 
17 #ifndef QGSSETTINGS_H
18 #define QGSSETTINGS_H
19 
20 #include <QSettings>
21 #include <QMetaEnum>
22 
23 #include "qgis_core.h"
24 #include "qgis_sip.h"
25 #include "qgslogger.h"
26 
27 /**
28  * \ingroup core
29  * \class QgsSettings
30  *
31  * \brief This class is a composition of two QSettings instances:
32  *
33  * - the main QSettings instance is the standard User Settings and
34  * - the second one (Global Settings) is meant to provide read-only
35  *   pre-configuration and defaults to the first one.
36  *
37  * For a given settings key, the function call to value(key, default) will return
38  * the first existing setting in the order specified below:
39  *
40  * - User Settings
41  * - Global Settings
42  * - Default Value
43  *
44  * The path to the Global Settings storage can be set before constructing the QgsSettings
45  * objects, with a static call to:
46  * static bool setGlobalSettingsPath( QString path );
47  *
48  * QgsSettings provides some shortcuts to get/set namespaced settings from/to a specific section:
49  *
50  * - Core
51  * - Gui
52  * - Server
53  * - Plugins
54  * - Auth
55  * - App
56  * - Providers
57  * - Misc
58  *
59  * \since QGIS 3.0
60  */
61 class CORE_EXPORT QgsSettings : public QObject
62 {
63     Q_OBJECT
64   public:
65 
66     //! Sections for namespaced settings
67     enum Section
68     {
69       NoSection,
70       Core,
71       Gui,
72       Server,
73       Plugins,
74       Auth,
75       App,
76       Providers,
77       Expressions,
78       Misc,
79       Gps, //!< GPS section, since QGIS 3.22
80     };
81 
82     /**
83      * Constructs a QgsSettings object for accessing settings of the application
84      * called application from the organization called organization, and with parent parent.
85      */
86     explicit QgsSettings( const QString &organization,
87                           const QString &application = QString(), QObject *parent = nullptr );
88 
89     /**
90      * Constructs a QgsSettings object for accessing settings of the application called application
91      * from the organization called organization, and with parent parent.
92      *
93      * If scope is QSettings::UserScope, the QSettings object searches user-specific settings first,
94      * before it searches system-wide settings as a fallback. If scope is QSettings::SystemScope,
95      * the QSettings object ignores user-specific settings and provides access to system-wide settings.
96      *
97      * The storage format is set to QSettings::NativeFormat (i.e. calling setDefaultFormat() before
98      * calling this constructor has no effect).
99      *
100      * If no application name is given, the QSettings object will only access the organization-wide
101      * locations.
102      */
103     QgsSettings( QSettings::Scope scope, const QString &organization,
104                  const QString &application = QString(), QObject *parent = nullptr );
105 
106     /**
107      * Constructs a QgsSettings object for accessing settings of the application called application
108      * from the organization called organization, and with parent parent.
109      *
110      * If scope is QSettings::UserScope, the QSettings object searches user-specific settings first,
111      * before it searches system-wide settings as a fallback. If scope is QSettings::SystemScope,
112      * the QSettings object ignores user-specific settings and provides access to system-wide settings.
113      *
114      * If format is QSettings::NativeFormat, the native API is used for storing settings. If format
115      * is QSettings::IniFormat, the INI format is used.
116      *
117      * If no application name is given, the QSettings object will only access the organization-wide
118      * locations.
119      */
120     QgsSettings( QSettings::Format format, QSettings::Scope scope, const QString &organization,
121                  const QString &application = QString(), QObject *parent = nullptr );
122 
123     /**
124      * Constructs a QgsSettings object for accessing the settings stored in the file called fileName,
125      * with parent parent. If the file doesn't already exist, it is created.
126      *
127      * If format is QSettings::NativeFormat, the meaning of fileName depends on the platform. On Unix,
128      * fileName is the name of an INI file. On macOS and iOS, fileName is the name of a .plist file.
129      * On Windows, fileName is a path in the system registry.
130      *
131      * If format is QSettings::IniFormat, fileName is the name of an INI file.
132      *
133      * \warning This function is provided for convenience. It works well for accessing INI or .plist
134      * files generated by Qt, but might fail on some syntaxes found in such files originated by
135      * other programs. In particular, be aware of the following limitations:
136      *
137      * - QgsSettings provides no way of reading INI "path" entries, i.e., entries with unescaped slash characters.
138      *   (This is because these entries are ambiguous and cannot be resolved automatically.)
139      * - In INI files, QSettings uses the @ character as a metacharacter in some contexts, to encode
140      *   Qt-specific data types (e.g., \@Rect), and might therefore misinterpret it when it occurs
141      *   in pure INI files.
142      */
143     QgsSettings( const QString &fileName, QSettings::Format format, QObject *parent = nullptr );
144 
145     /**
146      * Constructs a QgsSettings object for accessing settings of the application and organization
147      * set previously with a call to QCoreApplication::setOrganizationName(),
148      * QCoreApplication::setOrganizationDomain(), and QCoreApplication::setApplicationName().
149      *
150      * The scope is QSettings::UserScope and the format is defaultFormat() (QSettings::NativeFormat
151      * by default). Use setDefaultFormat() before calling this constructor to change the default
152      * format used by this constructor.
153      */
154     explicit QgsSettings( QObject *parent = nullptr );
155     ~QgsSettings() override;
156 
157     /**
158      * Appends prefix to the current group.
159      * The current group is automatically prepended to all keys specified to QSettings.
160      * In addition, query functions such as childGroups(), childKeys(), and allKeys()
161      * are based on the group. By default, no group is set.
162      */
163     void beginGroup( const QString &prefix, QgsSettings::Section section = QgsSettings::NoSection );
164     //! Resets the group to what it was before the corresponding beginGroup() call.
165     void endGroup();
166 
167     /**
168      * Returns the current group.
169      * \see beginGroup()
170      * \see endGroup()
171      * \since QGIS 3.6
172      */
173     QString group() const;
174 
175     //! Returns a list of all keys, including subkeys, that can be read using the QSettings object.
176     QStringList allKeys() const;
177     //! Returns a list of all top-level keys that can be read using the QSettings object.
178     QStringList childKeys() const;
179     //! Returns a list of all key top-level groups that contain keys that can be read using the QSettings object.
180     QStringList childGroups() const;
181     //! Returns a list of all key top-level groups (same as childGroups) but only for groups defined in global settings.
182     QStringList globalChildGroups() const;
183     //! Returns the path to the Global Settings QSettings storage file
184     static QString globalSettingsPath();
185     //! Sets the Global Settings QSettings storage file
186     static bool setGlobalSettingsPath( const QString &path );
187     //! Adds prefix to the current group and starts reading from an array. Returns the size of the array.
188     int beginReadArray( const QString &prefix );
189 
190     /**
191      * Adds prefix to the current group and starts writing an array of size size.
192      * If size is -1 (the default), it is automatically determined based on the indexes of the entries written.
193      * \note This will completely shadow any existing array with the same name in the global settings
194      */
195     void beginWriteArray( const QString &prefix, int size = -1 );
196     //! Closes the array that was started using beginReadArray() or beginWriteArray().
197     void endArray();
198 
199     /**
200      * Sets the current array index to i. Calls to functions such as setValue(), value(),
201      * remove(), and contains() will operate on the array entry at that index.
202      */
203     void setArrayIndex( int i );
204 
205     /**
206      * Sets the value of setting key to value. If the key already exists, the previous value is overwritten.
207      * An optional Section argument can be used to set a value to a specific Section.
208      */
209     void setValue( const QString &key, const QVariant &value, QgsSettings::Section section = QgsSettings::NoSection );
210 
211     /**
212      * Returns the value for setting key. If the setting doesn't exist, it will be
213      * searched in the Global Settings and if not found, returns defaultValue.
214      * If no default value is specified, a default QVariant is returned.
215      * An optional Section argument can be used to get a value from a specific Section.
216      */
217 #ifndef SIP_RUN
218     QVariant value( const QString &key, const QVariant &defaultValue = QVariant(),
219                     Section section = NoSection ) const;
220 #else
221     SIP_PYOBJECT value( const QString &key, const QVariant &defaultValue = QVariant(),
222                         SIP_PYOBJECT type = 0,
223                         QgsSettings::Section section = QgsSettings::NoSection ) const / ReleaseGIL /;
224     % MethodCode
225     typedef PyObject *( *pyqt5_from_qvariant_by_type )( QVariant &value, PyObject *type );
226     QVariant value;
227 
228     // QSettings has an internal mutex so release the GIL to avoid the possibility of deadlocks.
229     Py_BEGIN_ALLOW_THREADS
230     value = sipCpp->value( *a0, *a1, a3 );
231     Py_END_ALLOW_THREADS
232 
233     pyqt5_from_qvariant_by_type f = ( pyqt5_from_qvariant_by_type ) sipImportSymbol( "pyqt5_from_qvariant_by_type" );
234     sipRes = f( value, a2 );
235 
236     sipIsErr = !sipRes;
237     % End
238 #endif
239 
240 #ifndef SIP_RUN
241 
242     /**
243      * Returns the setting value for a setting based on an enum.
244      * This forces the output to be a valid and existing entry of the enum.
245      * Hence if the setting value is incorrect, the given default value is returned.
246      * This tries first with setting as a string (as the enum) and then as an integer value.
247      * \note The enum needs to be declared with Q_ENUM, and flags with Q_FLAG (not Q_FLAGS).
248      * \note for Python bindings, a custom implementation is achieved in Python directly
249      * \see setEnumValue
250      * \see flagValue
251      */
252     template <class T>
253     T enumValue( const QString &key, const T &defaultValue,
254                  const Section section = NoSection )
255     {
256       const QMetaEnum metaEnum = QMetaEnum::fromType<T>();
257       Q_ASSERT( metaEnum.isValid() );
258       if ( !metaEnum.isValid() )
259       {
260         QgsDebugMsg( QStringLiteral( "Invalid metaenum. Enum probably misses Q_ENUM or Q_FLAG declaration." ) );
261       }
262 
263       T v;
264       bool ok = false;
265 
266       if ( metaEnum.isValid() )
267       {
268         // read as string
269         QByteArray ba = value( key, metaEnum.valueToKey( static_cast<const int>( defaultValue ) ), section ).toString().toUtf8();
270         const char *vs = ba.data();
271         v = static_cast<T>( metaEnum.keyToValue( vs, &ok ) );
272         if ( ok )
273           return v;
274       }
275 
276       // if failed, try to read as int (old behavior)
277       // this code shall be removed later (probably after QGIS 3.4 LTR for 3.6)
278       // then the method could be marked as const
279       v = static_cast<T>( value( key, static_cast<const int>( defaultValue ), section ).toInt( &ok ) );
280       if ( metaEnum.isValid() )
281       {
282         if ( !ok || !metaEnum.valueToKey( static_cast<int>( v ) ) )
283         {
284           v = defaultValue;
285         }
286         else
287         {
288           // found setting as an integer
289           // convert the setting to the new form (string)
290           setEnumValue( key, v, section );
291         }
292       }
293 
294       return v;
295     }
296 
297     /**
298      * Set the value of a setting based on an enum.
299      * The setting will be saved as string.
300      * \note The enum needs to be declared with Q_ENUM, and flags with Q_FLAG (not Q_FLAGS).
301      * \see enumValue
302      * \see setFlagValue
303      */
304     template <class T>
305     void setEnumValue( const QString &key, const T &value,
306                        const Section section = NoSection )
307     {
308       const QMetaEnum metaEnum = QMetaEnum::fromType<T>();
309       Q_ASSERT( metaEnum.isValid() );
310       if ( metaEnum.isValid() )
311       {
312         setValue( key, metaEnum.valueToKey( static_cast<const int>( value ) ), section );
313       }
314       else
315       {
316         QgsDebugMsg( QStringLiteral( "Invalid metaenum. Enum probably misses Q_ENUM or Q_FLAG declaration." ) );
317       }
318     }
319 
320     /**
321      * Returns the setting value for a setting based on a flag.
322      * This forces the output to be a valid and existing entry of the flag.
323      * Hence if the setting value is incorrect, the given default value is returned.
324      * This tries first with setting as a string (using a byte array) and then as an integer value.
325      * \note The flag needs to be declared with Q_FLAG (not Q_FLAGS).
326      * \note for Python bindings, a custom implementation is achieved in Python directly.
327      * \see setFlagValue
328      * \see enumValue
329      */
330     template <class T>
331     T flagValue( const QString &key, const T &defaultValue,
332                  const Section section = NoSection )
333     {
334       const QMetaEnum metaEnum = QMetaEnum::fromType<T>();
335       Q_ASSERT( metaEnum.isValid() );
336       if ( !metaEnum.isValid() )
337       {
338         QgsDebugMsg( QStringLiteral( "Invalid metaenum. Enum probably misses Q_ENUM or Q_FLAG declaration." ) );
339       }
340 
341       T v = defaultValue;
342       bool ok = false;
343 
344       if ( metaEnum.isValid() )
345       {
346         // read as string
347         QByteArray ba = value( key, metaEnum.valueToKeys( static_cast< const int >( defaultValue ) ) ).toString().toUtf8();
348         const char *vs = ba.data();
349         v = static_cast<T>( metaEnum.keysToValue( vs, &ok ) );
350       }
351       if ( !ok )
352       {
353         // if failed, try to read as int
354         const int intValue = value( key, static_cast<const int>( defaultValue ), section ).toInt( &ok );
355         if ( metaEnum.isValid() )
356         {
357           if ( ok )
358           {
359             // check that the int value does correspond to a flag
360             // see https://stackoverflow.com/a/68495949/1548052
361             const QByteArray keys = metaEnum.valueToKeys( intValue );
362             const int intValueCheck = metaEnum.keysToValue( keys );
363             if ( intValue != intValueCheck )
364             {
365               v = defaultValue;
366             }
367             else
368             {
369               // found property as an integer
370               v = T( intValue );
371               // convert the property to the new form (string)
372               // this code could be removed
373               // then the method could be marked as const
374               setFlagValue( key, v );
375             }
376           }
377           else
378           {
379             v = defaultValue;
380           }
381         }
382       }
383 
384       return v;
385     }
386 
387     /**
388      * Set the value of a setting based on a flag.
389      * The setting will be saved as string.
390      * \note The flag needs to be declared with Q_FLAG (not Q_FLAGS).
391      * \see flagValue
392      * \see setEnumValue
393      */
394     template <class T>
395     void setFlagValue( const QString &key, const T &value,
396                        const Section section = NoSection )
397     {
398       const QMetaEnum metaEnum = QMetaEnum::fromType<T>();
399       Q_ASSERT( metaEnum.isValid() );
400       if ( metaEnum.isValid() )
401       {
402         setValue( key, metaEnum.valueToKeys( static_cast< const int >( value ) ), section );
403       }
404       else
405       {
406         QgsDebugMsg( QStringLiteral( "Invalid metaenum. Enum probably misses Q_ENUM or Q_FLAG declaration." ) );
407       }
408     }
409 #endif
410 
411     /**
412      * Returns TRUE if there exists a setting called key; returns FALSE otherwise.
413      * If a group is set using beginGroup(), key is taken to be relative to that group.
414      */
415     bool contains( const QString &key, QgsSettings::Section section = QgsSettings::NoSection ) const;
416     //! Returns the path where settings written using this QSettings object are stored.
417     QString fileName() const;
418 
419     /**
420      * Writes any unsaved changes to permanent storage, and reloads any settings that have been
421      * changed in the meantime by another application.
422      * This function is called automatically from QSettings's destructor and by the event
423      * loop at regular intervals, so you normally don't need to call it yourself.
424      */
425     void sync();
426     //! Removes the setting key and any sub-settings of key in a section.
427     void remove( const QString &key, QgsSettings::Section section = QgsSettings::NoSection );
428     //! Returns the sanitized and prefixed key
429     QString prefixedKey( const QString &key, QgsSettings::Section section ) const;
430     //! Removes all entries in the user settings
431     void clear();
432 
433   private:
434     void init();
435     QString sanitizeKey( const QString &key ) const;
436     QSettings *mUserSettings = nullptr;
437     QSettings *mGlobalSettings = nullptr;
438     bool mUsingGlobalArray = false;
439     Q_DISABLE_COPY( QgsSettings )
440 
441 };
442 
443 #endif // QGSSETTINGS_H
444