File indexing completed on 2024-05-12 04:44:33
0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com> 0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT 0003 0004 #ifndef HELPER_H 0005 #define HELPER_H 0006 0007 #include "helpermath.h" 0008 #include "helperqttypes.h" 0009 #include <QtCore/qsharedpointer.h> 0010 #include <optional> 0011 #include <qcontainerfwd.h> 0012 #include <qcoreapplication.h> 0013 #include <qglobal.h> 0014 #include <qicon.h> 0015 #include <qimage.h> 0016 #include <qlist.h> 0017 #include <qmetaobject.h> 0018 #include <qpair.h> 0019 #include <qstring.h> 0020 #include <qstringliteral.h> 0021 #include <qthread.h> 0022 0023 class QColor; 0024 class QWheelEvent; 0025 class QWidget; 0026 0027 namespace PerceptualColor 0028 { 0029 0030 class RgbColorSpace; 0031 0032 /** @brief Represents the appearance of a theme. */ 0033 enum class ColorSchemeType { 0034 Light, /**< Light theme. */ 0035 Dark /**< Dark theme. */ 0036 }; 0037 0038 void drawQWidgetStyleSheetAware(QWidget *widget); 0039 0040 QString fromMnemonicToRichText(const QString &mnemonicText); 0041 0042 std::optional<ColorSchemeType> guessColorSchemeTypeFromWidget(QWidget *widget); 0043 0044 /** @internal 0045 * 0046 * @brief Convenience function template that tests if a value is in a list. 0047 * 0048 * @param first The value 0049 * @param t The list 0050 * 0051 * Usage: 0052 * @snippet testhelper.cpp isInUsage 0053 * 0054 * @returns <tt>true</tt> if “value” is in “list”. <tt>false</tt> otherwise. */ 0055 template<typename First, typename... T> 0056 bool isIn(First &&first, T &&...t) 0057 { 0058 // Solution as proposed by Nikos C. in https://stackoverflow.com/a/15181949 0059 return ((first == t) || ...); 0060 } 0061 0062 [[nodiscard]] qreal standardWheelStepCount(QWheelEvent *event); 0063 0064 [[nodiscard]] QImage transparencyBackground(qreal devicePixelRatioF); 0065 0066 /** @internal 0067 * 0068 * @brief Two-dimensional array */ 0069 template<typename T> 0070 class Array2D 0071 { 0072 public: 0073 /** @brief Constructor. 0074 * 0075 * Constructs an array with the size 0 × 0. */ 0076 Array2D() 0077 : m_iCount(0) 0078 , m_jCount(0) 0079 { 0080 } 0081 0082 /** @brief Constructor. 0083 * 0084 * @param iCount size (first dimension) 0085 * @param jCount size (second dimension) 0086 * 0087 * The elements are initialized with default-constructed values. */ 0088 Array2D(QListSizeType iCount, QListSizeType jCount) 0089 : m_iCount(iCount) 0090 , m_jCount(jCount) 0091 { 0092 if (m_iCount < 0) { 0093 m_iCount = 0; 0094 } 0095 if (m_jCount < 0) { 0096 m_jCount = 0; 0097 } 0098 const auto elementCount = m_iCount * m_jCount; 0099 m_data.reserve(elementCount); 0100 for (QListSizeType i = 0; i < elementCount; ++i) { 0101 m_data.append(T()); 0102 } 0103 } 0104 0105 /** @brief Constructor. 0106 * 0107 * @param iCount size (first dimension) 0108 * @param jCount size (second dimension) 0109 * @param init Initial values. Excess elements are ignored. Missing 0110 * elements are initialized with default-constructed values. */ 0111 Array2D(QListSizeType iCount, QListSizeType jCount, QList<T> init) 0112 : m_iCount(iCount) 0113 , m_jCount(jCount) 0114 { 0115 if (m_iCount < 0) { 0116 m_iCount = 0; 0117 } 0118 if (m_jCount < 0) { 0119 m_jCount = 0; 0120 } 0121 const auto elementCount = m_iCount * m_jCount; 0122 m_data.reserve(elementCount); 0123 for (QListSizeType i = 0; i < elementCount; ++i) { 0124 if (i < init.count()) { 0125 m_data.append(init.value(i)); 0126 } else { 0127 m_data.append(T()); 0128 } 0129 } 0130 } 0131 0132 /** @brief Set value at a given index. 0133 * 0134 * @param i index (first dimension) 0135 * @param j index (second dimension) 0136 * @param value value to set */ 0137 void setValue(QListSizeType i, QListSizeType j, const T &value) 0138 { 0139 if (isInRange<QListSizeType>(0, i, m_iCount - 1) // 0140 && isInRange<QListSizeType>(0, j, m_jCount - 1) // 0141 ) { 0142 m_data[i + m_iCount * j] = value; 0143 } 0144 } 0145 0146 /** @brief Get value at a given index. 0147 * 0148 * @param i index (first dimension) 0149 * @param j index (second dimension) 0150 * @returns If the indices are valid, the value at the given indeces. 0151 * A default-constructed value otherwise. */ 0152 T value(QListSizeType i, QListSizeType j) const 0153 { 0154 if (isInRange<QListSizeType>(0, i, m_iCount - 1) // 0155 && isInRange<QListSizeType>(0, j, m_jCount - 1) // 0156 ) { 0157 return m_data[i + m_iCount * j]; 0158 } 0159 return T(); // Default value if indices are out of bounds 0160 } 0161 0162 /** @brief Size of the first dimension. 0163 * 0164 * @returns Size of the first dimension. */ 0165 QListSizeType iCount() const 0166 { 0167 return m_iCount; 0168 } 0169 0170 /** @brief Size of the second dimension. 0171 * 0172 * @returns Size of the second dimension. */ 0173 QListSizeType jCount() const 0174 { 0175 return m_jCount; 0176 } 0177 0178 private: 0179 /** @brief Internal storage of the elements. */ 0180 QList<T> m_data; 0181 /** @brief Internal storage of @ref iCount(). */ 0182 QListSizeType m_iCount; 0183 /** @brief Internal storage of @ref jCount(). */ 0184 QListSizeType m_jCount; 0185 }; 0186 0187 /** @internal 0188 * 0189 * @brief Force processing of events in a delayed fashion. 0190 * 0191 * When there is no running event loop (like in unit tests or in tools 0192 * like the screenshot generator), some parts of the asynchronous API 0193 * of this library does not work. Calling this function fixes this by 0194 * forcing the processing of pending events, but with some delay 0195 * in-between, so that maybe existing parallel threads have also 0196 * a chance to terminate their work. 0197 * 0198 * @param msecWaitInitially Delay before starting event processing. 0199 * @param msecWaitBetweenEventLoopPasses Delay before each pass through 0200 * through the pending events. 0201 * @param numberEventLoopPasses Number of passes through the pending events. 0202 * 0203 * @internal 0204 * 0205 * @note This is declared as template to prevent that this code is compiled 0206 * into the library itself, which does <em>not</em> actually use it itself, 0207 * but includes this header file. */ 0208 template<typename T = void> 0209 void delayedEventProcessing(unsigned long msecWaitInitially = 50, unsigned long msecWaitBetweenEventLoopPasses = 50, int numberEventLoopPasses = 3) 0210 { 0211 // Some OSes might round the sleep time up to 15 ms. We do it ourself 0212 // here to make the behaviour a little bit more predictable. 0213 msecWaitInitially = qMax<unsigned long>( // 0214 msecWaitInitially, // 0215 15); 0216 msecWaitBetweenEventLoopPasses = // 0217 qMax<unsigned long>(msecWaitBetweenEventLoopPasses, 15); 0218 0219 QThread::msleep(msecWaitInitially); 0220 // Hopefully, now the render function has terminated… 0221 for (int i = 0; i < numberEventLoopPasses; ++i) { 0222 // Wait again (apparently, threaded event processing needs some time…) 0223 QThread::msleep(msecWaitBetweenEventLoopPasses); 0224 QCoreApplication::processEvents(); 0225 } 0226 } 0227 0228 [[nodiscard]] Array2D<QColor> wcsBasicColors(const QSharedPointer<PerceptualColor::RgbColorSpace> &colorSpace); 0229 0230 [[nodiscard]] QIcon qIconFromTheme(const QStringList &names, const QString &fallback, ColorSchemeType type); 0231 0232 [[nodiscard]] QPair<QString, QString> getPrefixSuffix(const QString &formatString); 0233 0234 /** @brief The full-qualified C++ identifier as QString. 0235 * 0236 * This can be useful for debugging purposes. 0237 * 0238 * @tparam T The enumeration. 0239 * 0240 * @pre The enumeration type is declared with 0241 * Q_ENUM or Q_ENUM_NS. 0242 * 0243 * @returns The full-qualified C++ identifier as QString. */ 0244 template<typename T> 0245 [[nodiscard]] QString enumerationToFullString() 0246 { 0247 const auto myMeta = QMetaEnum::fromType<T>(); 0248 const auto scope = QString::fromUtf8(myMeta.scope()); 0249 const auto name = QString::fromUtf8(myMeta.name()); 0250 return QStringLiteral("%1::%2").arg(scope, name); 0251 } 0252 0253 /** @brief The full-qualified C++ identifier as QString. 0254 * 0255 * This can be useful for debugging purposes. 0256 * 0257 * @tparam T The enumeration type. Can usually be omitted. 0258 * 0259 * @param enumerator An enumerator. 0260 * 0261 * @pre The enumeration type of the enumerator is declared with 0262 * Q_ENUM or Q_ENUM_NS. 0263 * 0264 * @returns The full-qualified C++ identifier as QString, followed by the 0265 * underlying integer value in parenthesis. If the enumerator does not 0266 * exist (for example because you have done a static_cast of an invalid 0267 * integer to the enum class), an empty String is returned instead. If 0268 * the enumerator has synonyms (that means, there exist other enumerators 0269 * that share the same integer with the current enumerator), all synonym 0270 * enumerators are returned. 0271 * 0272 * @sa @ref enumeratorToString() */ 0273 template<typename T> 0274 [[nodiscard]] QString enumeratorToFullString(const T &enumerator) 0275 { 0276 const auto value = static_cast<int>(enumerator); 0277 const auto myMeta = QMetaEnum::fromType<T>(); 0278 0279 // QMetaEnum::valueToKeys (identifier with a final s) returns all existing 0280 // (synonym) keys for a given value. But it also returns happily 0281 // fantasy strings for non-existing values. Therefore, we have check 0282 // first with QMetaEnum::valueToKeys (identifier with a final s) which 0283 // does only return one single key for each value, but is guaranteed to 0284 // return nullptr if the value has no key. 0285 if (!myMeta.valueToKey(value)) { 0286 return QString(); 0287 } 0288 0289 const auto scope = QString::fromUtf8(myMeta.scope()); 0290 const auto name = QString::fromUtf8(myMeta.name()); 0291 const auto keys = QString::fromUtf8(myMeta.valueToKeys(value)); 0292 return QStringLiteral("%1::%2::%3(%4)").arg(scope, name, keys).arg(value); 0293 } 0294 0295 /** @brief The C++ identifier as QString. 0296 * 0297 * This can be useful for debugging purposes. 0298 * 0299 * @tparam T The enumeration type. Can usually be omitted. 0300 * 0301 * @param enumerator An enumerator. 0302 * 0303 * @pre The enumeration type of the enumerator is declared with 0304 * Q_ENUM or Q_ENUM_NS. 0305 * 0306 * @returns The C++ identifier as QString, followed by the 0307 * underlying integer value in parenthesis. If the enumerator does not 0308 * exist (for example because you have done a static_cast of an invalid 0309 * integer to the enum class), an empty String is returned instead. If 0310 * the enumerator has synonyms (that means, there exist other enumerators 0311 * that share the same integer with the current enumerator), all synonym 0312 * enumerators are returned. 0313 * 0314 * @sa @ref enumeratorToFullString() */ 0315 template<typename T> 0316 [[nodiscard]] QString enumeratorToString(const T &enumerator) 0317 { 0318 const auto value = static_cast<int>(enumerator); 0319 const auto myMeta = QMetaEnum::fromType<T>(); 0320 0321 // QMetaEnum::valueToKeys (identifier with a final s) returns all existing 0322 // (synonym) keys for a given value. But it also returns happily 0323 // fantasy strings for non-existing values. Therefore, we have check 0324 // first with QMetaEnum::valueToKeys (identifier with a final s) which 0325 // does only return one single key for each value, but is guaranteed to 0326 // return nullptr if the value has no key. 0327 if (!myMeta.valueToKey(value)) { 0328 return QString(); 0329 } 0330 0331 const auto keys = QString::fromUtf8(myMeta.valueToKeys(value)); 0332 return QStringLiteral("%1(%2)").arg(keys).arg(value); 0333 } 0334 0335 } // namespace PerceptualColor 0336 0337 #endif // HELPER_H