Perceptual Color

helper.h
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4#ifndef HELPER_H
5#define HELPER_H
6
7#include "helpermath.h"
8#include "helperqttypes.h"
9#include <QtCore/qsharedpointer.h>
10#include <optional>
11#include <qcolor.h>
12#include <qcontainerfwd.h>
13#include <qcoreapplication.h>
14#include <qglobal.h>
15#include <qicon.h>
16#include <qimage.h>
17#include <qlist.h>
18#include <qmetaobject.h>
19#include <qmetatype.h>
20#include <qpair.h>
21#include <qstring.h>
22#include <qstringliteral.h>
23#include <qthread.h>
24
25class QWheelEvent;
26class QWidget;
27
28namespace PerceptualColor
29{
30
31class RgbColorSpace;
32
33/** @brief Represents the appearance of a theme.
34 *
35 * @todo Substitute this by
36 * <a href="https://doc-snapshots.qt.io/qt6-dev/qt.html#ColorScheme-enum"><tt>
37 * enum class Qt::ColorScheme</tt></a> which is available since Qt 6.
38 */
39enum class ColorSchemeType {
40 Light, /**< Light theme. */
41 Dark /**< Dark theme. */
42};
43
44void drawQWidgetStyleSheetAware(QWidget *widget);
45
46QString fromMnemonicToRichText(const QString &mnemonicText);
47
48std::optional<ColorSchemeType> guessColorSchemeTypeFromWidget(QWidget *widget);
49
50/** @internal
51 *
52 * @brief Convenience function template that tests if a value is in a list.
53 *
54 * @param first The value
55 * @param t The list
56 *
57 * Usage:
58 * @snippet testhelper.cpp isInUsage
59 *
60 * @returns <tt>true</tt> if “value” is in “list”. <tt>false</tt> otherwise. */
61template<typename First, typename... T>
62bool isIn(First &&first, T &&...t)
63{
64 // Solution as proposed by Nikos C. in https://stackoverflow.com/a/15181949
65 return ((first == t) || ...);
66}
67
68[[nodiscard]] qreal standardWheelStepCount(QWheelEvent *event);
69
70[[nodiscard]] QImage transparencyBackground(qreal devicePixelRatioF);
71
72/** @internal
73 *
74 * @brief Two-dimensional array */
75template<typename T>
76class Array2D
77{
78public:
79 /** @brief Constructor.
80 *
81 * Constructs an array with the size 0 × 0. */
82 Array2D()
83 : m_iCount(0)
84 , m_jCount(0)
85 {
86 }
87
88 /** @brief Constructor.
89 *
90 * @param iCount size (first dimension)
91 * @param jCount size (second dimension)
92 *
93 * The elements are initialized with default-constructed values. */
94 Array2D(QListSizeType iCount, QListSizeType jCount)
95 : m_iCount(iCount)
96 , m_jCount(jCount)
97 {
98 if (m_iCount < 0) {
99 m_iCount = 0;
100 }
101 if (m_jCount < 0) {
102 m_jCount = 0;
103 }
104 const auto elementCount = m_iCount * m_jCount;
105 m_data.reserve(elementCount);
106 for (QListSizeType i = 0; i < elementCount; ++i) {
107 m_data.append(T());
108 }
109 }
110
111 /** @brief Constructor.
112 *
113 * @param iCount size (first dimension)
114 * @param jCount size (second dimension)
115 * @param init Initial values. Excess elements are ignored. Missing
116 * elements are initialized with default-constructed values. */
117 Array2D(QListSizeType iCount, QListSizeType jCount, QList<T> init)
118 : m_iCount(iCount)
119 , m_jCount(jCount)
120 {
121 if (m_iCount < 0) {
122 m_iCount = 0;
123 }
124 if (m_jCount < 0) {
125 m_jCount = 0;
126 }
127 const auto elementCount = m_iCount * m_jCount;
128 m_data.reserve(elementCount);
129 for (QListSizeType i = 0; i < elementCount; ++i) {
130 if (i < init.count()) {
131 m_data.append(init.value(i));
132 } else {
133 m_data.append(T());
134 }
135 }
136 }
137
138 // Define also these functions recommended by “rule of five”:
139 /** @brief Default copy assignment operator
140 * @param other the object to copy
141 * @returns The default implementation’s return value. */
142 Array2D &operator=(const Array2D &other) = default; // clazy:exclude=function-args-by-value
143 /** @brief Default move assignment operator
144 *
145 * @param other the object to move-assign
146 *
147 * @returns The default implementation’s return value. */
148 Array2D &operator=(Array2D &&other) = default;
149 /** @brief Default copy constructor
150 * @param other the object to copy */
151 Array2D(const Array2D &other) = default;
152 /** @brief Default move constructor
153 * @param other the object to move */
154 Array2D(Array2D &&other) = default;
155
156 /** @brief Equal operator
157 *
158 * @param other The object to compare with.
159 *
160 * @returns <tt>true</tt> if equal, <tt>false</tt> otherwise. */
161 bool operator==(const Array2D &other) const
162 {
163 return ( //
164 (m_data == other.m_data) //
165 && (m_iCount == other.m_iCount) //
166 && (m_jCount == other.m_jCount) //
167 );
168 }
169
170 /** @brief Unequal operator
171 *
172 * @param other The object to compare with.
173 *
174 * @returns <tt>true</tt> if unequal, <tt>false</tt> otherwise. */
175 bool operator!=(const Array2D &other) const
176 {
177 return !(*this == other);
178 }
179
180 /** @brief Set value at a given index.
181 *
182 * @param i index (first dimension)
183 * @param j index (second dimension)
184 * @param value value to set */
185 void setValue(QListSizeType i, QListSizeType j, const T &value)
186 {
187 if (isInRange<QListSizeType>(0, i, m_iCount - 1) //
188 && isInRange<QListSizeType>(0, j, m_jCount - 1) //
189 ) {
190 m_data[i + m_iCount * j] = value;
191 }
192 }
193
194 /** @brief Get value at a given index.
195 *
196 * @param i index (first dimension)
197 * @param j index (second dimension)
198 * @returns If the indices are valid, the value at the given indeces.
199 * A default-constructed value otherwise. */
200 T value(QListSizeType i, QListSizeType j) const
201 {
202 if (isInRange<QListSizeType>(0, i, m_iCount - 1) //
203 && isInRange<QListSizeType>(0, j, m_jCount - 1) //
204 ) {
205 return m_data[i + m_iCount * j];
206 }
207 return T(); // Default value if indices are out of bounds
208 }
209
210 /** @brief Size of the first dimension.
211 *
212 * @returns Size of the first dimension. */
213 QListSizeType iCount() const
214 {
215 return m_iCount;
216 }
217
218 /** @brief Size of the second dimension.
219 *
220 * @returns Size of the second dimension. */
221 QListSizeType jCount() const
222 {
223 return m_jCount;
224 }
225
226private:
227 /** @brief Internal storage of the elements. */
228 QList<T> m_data;
229 /** @brief Internal storage of @ref iCount(). */
230 QListSizeType m_iCount;
231 /** @brief Internal storage of @ref jCount(). */
232 QListSizeType m_jCount;
233};
234
235/** @brief Swatches organized in a grid.
236 *
237 * This type is declared as type to Qt’s type system via
238 * <tt>Q_DECLARE_METATYPE</tt>. Depending on your use case (for
239 * example if you want to use for <em>queued</em> signal-slot connections),
240 * you might consider calling <tt>qRegisterMetaType()</tt> for
241 * this type, once you have a QApplication object. */
242using Swatches = Array2D<QColor>;
243
244/** @internal
245 *
246 * @brief Force processing of events in a delayed fashion.
247 *
248 * When there is no running event loop (like in unit tests or in tools
249 * like the screenshot generator), some parts of the asynchronous API
250 * of this library does not work. Calling this function fixes this by
251 * forcing the processing of pending events, but with some delay
252 * in-between, so that maybe existing parallel threads have also
253 * a chance to terminate their work.
254 *
255 * @param msecWaitInitially Delay before starting event processing.
256 * @param msecWaitBetweenEventLoopPasses Delay before each pass through
257 * through the pending events.
258 * @param numberEventLoopPasses Number of passes through the pending events.
259 *
260 * @internal
261 *
262 * @note This is declared as template to prevent that this code is compiled
263 * into the library itself, which does <em>not</em> actually use it itself,
264 * but includes this header file. */
265template<typename T = void>
266void delayedEventProcessing(unsigned long msecWaitInitially = 50, unsigned long msecWaitBetweenEventLoopPasses = 50, int numberEventLoopPasses = 3)
267{
268 // Some OSes might round the sleep time up to 15 ms. We do it ourself
269 // here to make the behaviour a little bit more predictable.
270 msecWaitInitially = qMax<unsigned long>( //
271 msecWaitInitially, //
272 15);
273 msecWaitBetweenEventLoopPasses = //
274 qMax<unsigned long>(msecWaitBetweenEventLoopPasses, 15);
275
276 QThread::msleep(msecWaitInitially);
277 // Hopefully, now the render function has terminated…
278 for (int i = 0; i < numberEventLoopPasses; ++i) {
279 // Wait again (apparently, threaded event processing needs some time…)
280 QThread::msleep(msecWaitBetweenEventLoopPasses);
282 }
283}
284
286
287[[nodiscard]] QIcon qIconFromTheme(const QStringList &names, const QString &fallback, ColorSchemeType type);
288
289[[nodiscard]] QPair<QString, QString> getPrefixSuffix(const QString &formatString);
290
291/** @brief The full-qualified C++ identifier as QString.
292 *
293 * This can be useful for debugging purposes.
294 *
295 * @tparam T The enumeration.
296 *
297 * @pre The enumeration type is declared with
298 * Q_ENUM or Q_ENUM_NS.
299 *
300 * @returns The full-qualified C++ identifier as QString. */
301template<typename T>
303{
304 const auto myMeta = QMetaEnum::fromType<T>();
305 const auto scope = QString::fromUtf8(myMeta.scope());
306 const auto name = QString::fromUtf8(myMeta.name());
307 return QStringLiteral("%1::%2").arg(scope, name);
308}
309
310/** @brief The full-qualified C++ identifier as QString.
311 *
312 * This can be useful for debugging purposes.
313 *
314 * @tparam T The enumeration type. Can usually be omitted.
315 *
316 * @param enumerator An enumerator.
317 *
318 * @pre The enumeration type of the enumerator is declared with
319 * Q_ENUM or Q_ENUM_NS.
320 *
321 * @returns The full-qualified C++ identifier as QString, followed by the
322 * underlying integer value in parenthesis. If the enumerator does not
323 * exist (for example because you have done a static_cast of an invalid
324 * integer to the enum class), an empty String is returned instead. If
325 * the enumerator has synonyms (that means, there exist other enumerators
326 * that share the same integer with the current enumerator), all synonym
327 * enumerators are returned.
328 *
329 * @sa @ref enumeratorToString() */
330template<typename T>
331[[nodiscard]] QString enumeratorToFullString(const T &enumerator)
332{
333 const auto value = static_cast<int>(enumerator);
334 const auto myMeta = QMetaEnum::fromType<T>();
335
336 // QMetaEnum::valueToKeys (identifier with a final s) returns all existing
337 // (synonym) keys for a given value. But it also returns happily
338 // fantasy strings for non-existing values. Therefore, we have check
339 // first with QMetaEnum::valueToKeys (identifier with a final s) which
340 // does only return one single key for each value, but is guaranteed to
341 // return nullptr if the value has no key.
342 if (!myMeta.valueToKey(value)) {
343 return QString();
344 }
345
346 const auto scope = QString::fromUtf8(myMeta.scope());
347 const auto name = QString::fromUtf8(myMeta.name());
348 const auto keys = QString::fromUtf8(myMeta.valueToKeys(value));
349 return QStringLiteral("%1::%2::%3(%4)").arg(scope, name, keys).arg(value);
350}
351
352/** @brief The C++ identifier as QString.
353 *
354 * This can be useful for debugging purposes.
355 *
356 * @tparam T The enumeration type. Can usually be omitted.
357 *
358 * @param enumerator An enumerator.
359 *
360 * @pre The enumeration type of the enumerator is declared with
361 * Q_ENUM or Q_ENUM_NS.
362 *
363 * @returns The C++ identifier as QString, followed by the
364 * underlying integer value in parenthesis. If the enumerator does not
365 * exist (for example because you have done a static_cast of an invalid
366 * integer to the enum class), an empty String is returned instead. If
367 * the enumerator has synonyms (that means, there exist other enumerators
368 * that share the same integer with the current enumerator), all synonym
369 * enumerators are returned.
370 *
371 * @sa @ref enumeratorToFullString() */
372template<typename T>
373[[nodiscard]] QString enumeratorToString(const T &enumerator)
374{
375 const auto value = static_cast<int>(enumerator);
376 const auto myMeta = QMetaEnum::fromType<T>();
377
378 // QMetaEnum::valueToKeys (identifier with a final s) returns all existing
379 // (synonym) keys for a given value. But it also returns happily
380 // fantasy strings for non-existing values. Therefore, we have check
381 // first with QMetaEnum::valueToKeys (identifier with a final s) which
382 // does only return one single key for each value, but is guaranteed to
383 // return nullptr if the value has no key.
384 if (!myMeta.valueToKey(value)) {
385 return QString();
386 }
387
388 const auto keys = QString::fromUtf8(myMeta.valueToKeys(value));
389 return QStringLiteral("%1(%2)").arg(keys).arg(value);
390}
391
392} // namespace PerceptualColor
393
394// Use Q_DECLARE_METATYPE with this data type.
395// Attention: This must be done outside of all name-spaces.
396Q_DECLARE_METATYPE(PerceptualColor::Swatches)
397
398#endif // HELPER_H
The namespace of this library.
QString enumerationToFullString()
The full-qualified C++ identifier as QString.
Definition helper.h:302
Array2D< QColor > Swatches
Swatches organized in a grid.
Definition helper.h:242
QString enumeratorToString(const T &enumerator)
The C++ identifier as QString.
Definition helper.h:373
QString enumeratorToFullString(const T &enumerator)
The full-qualified C++ identifier as QString.
Definition helper.h:331
ColorSchemeType
Represents the appearance of a theme.
Definition helper.h:39
Swatches wcsBasicColors(const QSharedPointer< PerceptualColor::RgbColorSpace > &colorSpace)
Swatch grid derived from the basic colors as by WCS (World color survey).
Definition helper.cpp:459
QCA_EXPORT void init()
void processEvents(QEventLoop::ProcessEventsFlags flags)
void append(QList< T > &&value)
void reserve(qsizetype size)
QMetaEnum fromType()
QString fromUtf8(QByteArrayView str)
void msleep(unsigned long msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:57:18 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.