Perceptual Color

setting.h
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4#ifndef SETTING_H
5#define SETTING_H
6
7#include "settingbase.h"
8#include "settings.h"
9#include <QMetaEnum>
10#include <qcolor.h>
11#include <qdebug.h>
12#include <qfilesystemwatcher.h>
13#include <qglobal.h>
14#include <qlist.h>
15#include <qmetatype.h>
16#include <qobject.h>
17#include <qsettings.h>
18#include <qstring.h>
19#include <qstringliteral.h>
20
21#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
22#include <qtmetamacros.h>
23#else
24#include <qobjectdefs.h>
25#endif
26
27namespace PerceptualColor
28{
29
30/** @internal
31 *
32 * @brief A single setting within @ref Settings.
33 *
34 * @tparam T Type of the setting value. The type must qualify for registration
35 * as <tt>QMetaType</tt> and also provide a stream operator.
36 * @ref PerceptualSettings::ColorList and many build-in types
37 * qualify. However, enum types only qualify if both, they are
38 * declared with <tt>Q_ENUM</tt> <em>and</em> their underlying type
39 * is <tt>int</tt>. */
40template<typename T>
41class Setting final : public SettingBase
42{
43public:
44 Setting(const QString &key, Settings *settings, QObject *parent = nullptr);
45 virtual ~Setting() override;
46
47 // cppcheck-suppress returnByReference // false positive
48 T value() const;
49
50 void setValue(const T &newValue);
51
52private:
53 // Prevent copy and assignment operations to force that only references
54 // to the instance are possible.
55 Setting(const Setting &) = delete;
56 Setting &operator=(const Setting &) = delete;
57
58 /** @brief If the type is an enum type or not. */
59 static constexpr bool m_isEnum = std::is_enum_v<T>;
60
61 /** @brief Meta data for enum types. */
62 QMetaEnum m_qMetaEnum;
63
64 /** @brief Internal storage for the value. */
65 T m_value = T();
66
67 void updateFromQSettings();
68
69 /** @internal @brief Only for unit tests. */
70 friend class TestSetting;
71};
72
73/** @brief Constructor.
74 *
75 * @param key <tt>QSettings</tt> key for the value.
76 * For maximum portability:
77 * - No upper case should ever be used.
78 * (Some systems, like the INI that we are using, are case-insensitive.
79 * And even if we always use INI, having both capital and small letters
80 * is error-prone because typos are not checked by the compiler.)
81 * - Only the letters a-z should be used.
82 * (Also, some characters like the backslash are not allowed on some
83 * platforms.)
84 * - “group/key”: Each key has exactly one group. Don't use subgroups.
85 * Use the class name as group name.
86 * (This makes the settings file well readable for humans. Missing
87 * groups are confusing because the system generates a “General”
88 * group which is not easy to understand. And using class identifiers
89 * helps to understand the structure of the settings file.)
90 * - In C++, use “const” variables to define key strings, instead of
91 * manually typing the key strings.
92 * (This avoids typing errors.)
93 * @param settings Corresponding @ref Settings object. This object must
94 * stay available during the live-time of this object.
95 * @param parent The parent object (if any).
96 *
97 * @warning You must not create more than one instance with the same
98 * combination between key and @ref Settings object. This would result
99 * in undefined behaviour. (Probably some values would be out-of-sync.) */
100template<typename T>
101Setting<T>::Setting(const QString &key, Settings *settings, QObject *parent)
102 : SettingBase(key, settings, parent)
103{
104 if constexpr (m_isEnum) {
105 static_assert( //
106 std::is_same_v<std::underlying_type_t<T>, int>, //
107 // Reason: We do conversions using QMetaEnum, which uses “int”.
108 "If 'typename T' is an enum, its underlying type must be 'int'.");
109 }
110
111 // QSettings seems to use indirectly QMetaType::load() which requires
112 // to register all custom types as QMetaType.
113 qRegisterMetaType<T>();
114#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
115 // Also stream operators are required.
116 qRegisterMetaTypeStreamOperators<T>();
117#endif
118
119 if constexpr (m_isEnum) {
120 m_qMetaEnum = QMetaEnum::fromType<T>();
121 }
122
123 // Initialize the internal value:
124 updateFromQSettings();
125
126 // Make sure further updates are processed.
127 connect(settings, //
128 &Settings::updatedAfterFileChange, //
129 this, //
130 &PerceptualColor::Setting<T>::updateFromQSettings //
131 );
132}
133
134/** @brief Destructor. */
135template<typename T>
136Setting<T>::~Setting()
137{
138}
139
140/** @brief Updates the value to the corresponding value
141 * from @ref underlyingQSettings().
142 *
143 * Only reads from @ref underlyingQSettings() and does never write back
144 * to @ref underlyingQSettings(). */
145template<typename T>
146void Setting<T>::updateFromQSettings()
147{
148 // WARNING: Do not use the setter, as this may trigger
149 // unnecessary file writes even if the property hasn't changed. If
150 // another instance tries to write to the same file at the same time,
151 // it could cause a deadlock since our code would perform two file
152 // access operations. Another process could potentially lock the file
153 // just in between the two writes, leading to a deadlock. To prevent
154 // such issues, our code only reads from QSettings and never writes
155 // back directly or indirectly. Instead, we modify the property's
156 // internal storage directly and emit the notify signal if necessary.
157
158 // Get new value.
159 const QVariant newValueVariant = underlyingQSettings()->value(m_key);
160 T newValue;
161 if constexpr (m_isEnum) {
162 const QByteArray byteArray = newValueVariant.toString().toUtf8();
163 const int enumInteger = m_qMetaEnum.keysToValue(byteArray.constData());
164 newValue = static_cast<T>(enumInteger);
165 } else {
166 newValue = newValueVariant.value<T>();
167 }
168
169 // Apply new value.
170 if (newValue != m_value) {
171 m_value = newValue;
172 Q_EMIT valueChanged();
173 }
174}
175
176/** @brief Getter.
177 * @returns the value. */
178template<typename T>
179T Setting<T>::value() const
180{
181 return m_value;
182}
183
184/** @brief Setter.
185 *
186 * @param newValue The new value. */
187template<typename T>
188void Setting<T>::setValue(const T &newValue)
189{
190 if (newValue != m_value) {
191 m_value = newValue;
192 if constexpr (m_isEnum) {
193 const auto newValueUnderlying = //
194 static_cast<int>(newValue);
195 const QString string = QString::fromUtf8( //
196 m_qMetaEnum.valueToKeys(newValueUnderlying));
197 underlyingQSettings()->setValue(m_key, string);
198 } else {
199 const auto newVariant = QVariant::fromValue<T>(m_value);
200 underlyingQSettings()->setValue(m_key, newVariant);
201 }
202 Q_EMIT valueChanged();
203 }
204}
205
206} // namespace PerceptualColor
207
208#endif // SETTING_H
The namespace of this library.
const char * constData() const const
QMetaEnum fromType()
QObject * parent() const const
QString fromUtf8(QByteArrayView str)
QByteArray toUtf8() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)
QString toString() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:18:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.