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