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