File indexing completed on 2024-09-01 04:30:07

0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT
0003 
0004 #ifndef SETTING_H
0005 #define SETTING_H
0006 
0007 #include "settingbase.h"
0008 #include "settings.h"
0009 #include <qcolor.h>
0010 #include <qdebug.h>
0011 #include <qfilesystemwatcher.h>
0012 #include <qglobal.h>
0013 #include <qlist.h>
0014 #include <qmetatype.h>
0015 #include <qobject.h>
0016 #include <qsettings.h>
0017 #include <qstring.h>
0018 #include <qstringliteral.h>
0019 
0020 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0021 #include <qtmetamacros.h>
0022 #else
0023 #include <qobjectdefs.h>
0024 #endif
0025 
0026 namespace PerceptualColor
0027 {
0028 
0029 /** @internal
0030  *
0031  * @brief A single setting within @ref Settings.
0032  *
0033  * @tparam T Type of the setting value. The type must qualify for
0034  *           registration as QMetaType and also provide a stream
0035  *           operator. @ref PerceptualSettings::ColorList and many
0036  *           build-in types qualify.) */
0037 template<typename T>
0038 class Setting final : public SettingBase
0039 {
0040 public:
0041     Setting(const QString &key, Settings *settings, QObject *parent = nullptr);
0042     virtual ~Setting() override;
0043 
0044     T value() const;
0045 
0046     void setValue(const T &newValue);
0047 
0048 private:
0049     // Prevent copy and assignment operations to force that only references
0050     // to the instance are possible.
0051     Setting(const Setting &) = delete;
0052     Setting &operator=(const Setting &) = delete;
0053 
0054     /** @brief Internal storage for the value. */
0055     T m_value = T();
0056 
0057     void updateFromQSettings();
0058 
0059     /** @internal @brief Only for unit tests. */
0060     friend class TestSetting;
0061 };
0062 
0063 /** @brief Constructor.
0064  *
0065  * @param key <tt>QSettings</tt> key for the value.
0066  * For maximum portability:
0067  * - No upper case should ever be used.
0068  *   (Some systems, like the INI that we are using, are case-insensitive.
0069  *   And even if we always use INI, having both capital and small letters
0070  *   is error-prone because typos are not checked by the compiler.)
0071  * - Only the letters a-z should be used.
0072  *   (Also, some characters like the backslash are not allowed on some
0073  *   platforms.)
0074  * - “group/key”: Each key has exactly one group. Don't use subgroups.
0075  *   Use the class name as group name.
0076  *   (This makes the settings file well readable for humans. Missing
0077  *   groups are confusing because the system generates a “General”
0078  *   group which is not easy to understand. And using class identifiers
0079  *   helps to understand the structure of the settings file.)
0080  * - In C++, use “const” variables to define key strings, instead of
0081  *   manually typing the key strings.
0082  *   (This avoids typing errors.)
0083  * @param settings Corresponding @ref Settings object. This object must
0084  *        stay available during the live-time of this object.
0085  * @param parent The parent object (if any).
0086  *
0087  * @warning You must not create more than one instance with the same
0088  * combination between key and @ref Settings object. This would result
0089  * in undefined behaviour. (Probably some values would be out-of-sync.) */
0090 template<typename T>
0091 Setting<T>::Setting(const QString &key, Settings *settings, QObject *parent)
0092     : SettingBase(key, settings, parent)
0093 {
0094     // QSettings seems to use indirectly QMetaType::load() which requires
0095     // to register all custom types as QMetaType.
0096     qRegisterMetaType<T>();
0097 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
0098     // Also stream operators are required.
0099     qRegisterMetaTypeStreamOperators<T>();
0100 #endif
0101 
0102     // Initialize the internal value:
0103     updateFromQSettings();
0104 
0105     // Make sure further updates are processed.
0106     connect(settings, //
0107             &Settings::updatedAfterFileChange, //
0108             this, //
0109             &PerceptualColor::Setting<T>::updateFromQSettings //
0110     );
0111 }
0112 
0113 /** @brief Destructor. */
0114 template<typename T>
0115 Setting<T>::~Setting()
0116 {
0117 }
0118 
0119 /** @brief Updates the value to the corresponding value
0120  * from @ref underlyingQSettings().
0121  *
0122  * Only reads from @ref underlyingQSettings() and does never write back
0123  * to @ref underlyingQSettings(). */
0124 template<typename T>
0125 void Setting<T>::updateFromQSettings()
0126 {
0127     // WARNING: Do not use the setter, as this may trigger
0128     // unnecessary file writes even if the property hasn't changed. If
0129     // another instance tries to write to the same file at the same time,
0130     // it could cause a deadlock since our code would perform two file
0131     // access operations. Another process could potentially lock the file
0132     // just in between the two writes, leading to a deadlock. To prevent
0133     // such issues, our code only reads from QSettings and never writes
0134     // back directly or indirectly. Instead, we modify the property's
0135     // internal storage directly and emit the notify signal if necessary.
0136 
0137     const QVariant newValueVariant = underlyingQSettings()->value(m_key);
0138     const T newValue = newValueVariant.value<T>();
0139     if (newValue != m_value) {
0140         m_value = newValue;
0141         Q_EMIT valueChanged();
0142     }
0143 }
0144 
0145 /** @brief Getter.
0146  *  @returns the value. */
0147 template<typename T>
0148 T Setting<T>::value() const
0149 {
0150     return m_value;
0151 }
0152 
0153 /** @brief Setter.
0154  *
0155  * @param newValue The new value. */
0156 template<typename T>
0157 void Setting<T>::setValue(const T &newValue)
0158 {
0159     if (newValue != m_value) {
0160         m_value = newValue;
0161         const auto newVariant = QVariant::fromValue<T>(m_value);
0162         underlyingQSettings()->setValue(m_key, newVariant);
0163         Q_EMIT valueChanged();
0164     }
0165 }
0166 
0167 } // namespace PerceptualColor
0168 
0169 #endif // SETTING_H