// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // Qt-Security score:significant reason:default #include "qquickcolorinputs_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcColorInputs, "qt.quick.dialogs.colorinputs") class QQuickColorInputsPrivate : public QQuickContainerPrivate { public: Q_DECLARE_PUBLIC(QQuickColorInputs) void repopulate(); QQuickTextInput *createDelegateTextInputItem(QQmlComponent *component, const QVariantMap &initialProperties); void handleHexInput(); void handleRedInput(); void handleGreenInput(); void handleBlueInput(); void handleHueInput(); void handleHsvSaturationInput(); void handleValueInput(); void handleHslSaturationInput(); void handleLightnessInput(); void handleAlphaInput(); QQmlComponent *m_delegate = nullptr; QQuickColorInputs::Mode m_currentMode = QQuickColorInputs::Hex; HSVA m_hsva; bool m_showAlpha = false; bool m_repopulating = false; }; QQuickColorInputs::QQuickColorInputs(QQuickItem *parent) : QQuickContainer(*(new QQuickColorInputsPrivate), parent) { } QColor QQuickColorInputs::color() const { Q_D(const QQuickColorInputs); return QColor::fromHsvF(d->m_hsva.h, d->m_hsva.s, d->m_hsva.v, d->m_hsva.a); } void QQuickColorInputs::setColor(const QColor &c) { Q_D(QQuickColorInputs); if (color().rgba() == c.rgba()) return; // If we get a QColor from an Hsv or Hsl color system, // we want to get the raw values without the risk of QColor converting them, // and possible deleting relevant information for achromatic cases. if (c.spec() == QColor::Spec::Hsl) { const auto sv = getSaturationAndValue(c.hslSaturationF(), c.lightnessF()); d->m_hsva.h = qBound(.0, c.hslHueF(), 1.0); d->m_hsva.s = qBound(.0, sv.first, 1.0); d->m_hsva.v = qBound(.0, sv.second, 1.0); } else { d->m_hsva.h = qBound(.0, c.hsvHueF(), 1.0); d->m_hsva.s = qBound(.0, c.hsvSaturationF(), 1.0); d->m_hsva.v = qBound(.0, c.valueF(), 1.0); } d->m_hsva.a = c.alphaF(); emit colorChanged(color()); } int QQuickColorInputs::red() const { return color().red(); } int QQuickColorInputs::green() const { return color().green(); } int QQuickColorInputs::blue() const { return color().blue(); } qreal QQuickColorInputs::alpha() const { Q_D(const QQuickColorInputs); return d->m_hsva.a; } qreal QQuickColorInputs::hue() const { Q_D(const QQuickColorInputs); return d->m_hsva.h; } qreal QQuickColorInputs::hslSaturation() const { Q_D(const QQuickColorInputs); return getSaturationAndLightness(d->m_hsva.s, d->m_hsva.v).first; } qreal QQuickColorInputs::hsvSaturation() const { Q_D(const QQuickColorInputs); return d->m_hsva.s; } qreal QQuickColorInputs::value() const { Q_D(const QQuickColorInputs); return d->m_hsva.v; } qreal QQuickColorInputs::lightness() const { Q_D(const QQuickColorInputs); return getSaturationAndLightness(d->m_hsva.s, d->m_hsva.v).second; } bool QQuickColorInputs::showAlpha() const { Q_D(const QQuickColorInputs); return d->m_showAlpha; } void QQuickColorInputs::setShowAlpha(bool showAlpha) { Q_D(QQuickColorInputs); if (d->m_showAlpha == showAlpha) return; d->m_showAlpha = showAlpha; d->repopulate(); emit showAlphaChanged(d->m_showAlpha); } QQuickColorInputs::Mode QQuickColorInputs::currentMode() const { Q_D(const QQuickColorInputs); return d->m_currentMode; } void QQuickColorInputs::setCurrentMode(Mode mode) { Q_D(QQuickColorInputs); if (d->m_currentMode == mode) return; d->m_currentMode = mode; d->repopulate(); emit currentModeChanged(); } QQmlComponent *QQuickColorInputs::delegate() const { Q_D(const QQuickColorInputs); return d->m_delegate; } void QQuickColorInputs::setDelegate(QQmlComponent *delegate) { Q_D(QQuickColorInputs); if (d->m_delegate == delegate) return; d->m_delegate = delegate; emit delegateChanged(); } void QQuickColorInputs::componentComplete() { Q_D(QQuickColorInputs); QQuickContainer::componentComplete(); d->repopulate(); } QQuickTextInput *QQuickColorInputsPrivate::createDelegateTextInputItem(QQmlComponent *component, const QVariantMap &initialProperties) { Q_Q(QQuickColorInputs); QQmlContext *context = component->creationContext(); if (!context) context = qmlContext(q); if (!component->isBound() && initialProperties.isEmpty()) { context = new QQmlContext(context, q); context->setContextObject(q); } QQuickTextInput *textInput = qobject_cast(component->createWithInitialProperties(initialProperties, context)); if (textInput) QQml_setParent_noEvent(textInput, q); return textInput; } static const QString s_percentage_pattern = QString::fromUtf8("^(\\d+)%?$"); static const QString s_degree_pattern = QString::fromUtf8("(\\d+)°?$"); static const QString s_rgba_pattern = QString::fromUtf8("^#[0-9A-f]{6}(?:[0-9A-f]{2})?$"); static const QString s_rgb_pattern = QString::fromUtf8("^#[0-9A-f]{6}$"); void QQuickColorInputsPrivate::repopulate() { Q_Q(QQuickColorInputs); if (m_repopulating) return; if (!q->delegate() || !q->contentItem()) { qmlWarning(q) << "Both delegate and contentItem must be set before repopulating"; return; } QScopedValueRollback repopulateGuard(m_repopulating, true); auto removeAllItems = [q](){ while (q->count() > 0) q->removeItem(q->itemAt(0)); }; removeAllItems(); static const QRegularExpressionValidator rgba_validator = QRegularExpressionValidator(QRegularExpression(s_rgba_pattern)); static const QRegularExpressionValidator rgb_validator = QRegularExpressionValidator(QRegularExpression(s_rgb_pattern)); static const QRegularExpressionValidator percentage_validator = QRegularExpressionValidator(QRegularExpression(s_percentage_pattern)); static const QRegularExpressionValidator degree_validator = QRegularExpressionValidator(QRegularExpression(s_degree_pattern)); static const QIntValidator intValdator = QIntValidator(0, 255); auto addInputField = [this, q, removeAllItems](const QString &name, const QValidator *validator, void (QQuickColorInputsPrivate::*handler)(), std::function textConverter) { const int maxLen = m_currentMode == QQuickColorInputs::Hex ? 9 : 4; const QVariantMap properties = { { QStringLiteral("objectName"), QVariant::fromValue(name) }, { QStringLiteral("validator"), QVariant::fromValue(validator) }, { QStringLiteral("horizontalAlignment"), QVariant::fromValue( m_currentMode == QQuickColorInputs::Hex ? Qt::AlignLeft : Qt::AlignHCenter) }, { QStringLiteral("maximumLength"), QVariant::fromValue(maxLen) }, { QStringLiteral("text"), QVariant::fromValue(textConverter()) } }; if (QQuickTextInput *item = createDelegateTextInputItem(q->delegate(), properties)) { connect(item, &QQuickTextInput::editingFinished, this, handler); QObject::connect(q, &QQuickColorInputs::colorChanged, item, [item, textConverter](const QColor &){ item->setText(textConverter()); }); insertItem(q->count(), item); } else { qCWarning(lcColorInputs) << "Failed to create delegate for " << name; removeAllItems(); } }; switch (m_currentMode) { case QQuickColorInputs::Hex: addInputField(QStringLiteral("hex"), m_showAlpha ? &rgba_validator : &rgb_validator, &QQuickColorInputsPrivate::handleHexInput, [q](){ return q->color().name(); }); break; case QQuickColorInputs::Rgb: addInputField(QStringLiteral("red"), &intValdator, &QQuickColorInputsPrivate::handleRedInput, [q](){ return QString::number(q->red()); }); addInputField(QStringLiteral("green"), &intValdator, &QQuickColorInputsPrivate::handleGreenInput, [q](){ return QString::number(q->green()); }); addInputField(QStringLiteral("blue"), &intValdator, &QQuickColorInputsPrivate::handleBlueInput, [q](){ return QString::number(q->blue()); }); if (m_showAlpha) addInputField(QStringLiteral("alpha"), &percentage_validator, &QQuickColorInputsPrivate::handleAlphaInput, [q](){ return QString::number(qRound(q->alpha() * 100)).append(QStringLiteral("%")); }); break; case QQuickColorInputs::Hsv: addInputField(QStringLiteral("hsvHue"), °ree_validator, &QQuickColorInputsPrivate::handleHueInput, [q](){ return QString::number(qRound(q->hue() * 360)).append(QStringLiteral("°")); }); addInputField(QStringLiteral("hsvSaturation"), &percentage_validator, &QQuickColorInputsPrivate::handleHsvSaturationInput, [q](){ return QString::number(qRound(q->hsvSaturation() * 100)).append(QStringLiteral("%")); }); addInputField(QStringLiteral("value"), &percentage_validator, &QQuickColorInputsPrivate::handleValueInput, [q](){ return QString::number(qRound(q->value() * 100)).append(QStringLiteral("%")); }); if (m_showAlpha) addInputField(QStringLiteral("alpha"), &percentage_validator, &QQuickColorInputsPrivate::handleAlphaInput, [q](){ return QString::number(qRound(q->alpha() * 100)).append(QStringLiteral("%")); }); break; case QQuickColorInputs::Hsl: addInputField(QStringLiteral("hslHue"), °ree_validator, &QQuickColorInputsPrivate::handleHueInput, [q](){ return QString::number(qRound(q->hue() * 360)).append(QStringLiteral("°")); }); addInputField(QStringLiteral("hslSaturation"), &percentage_validator, &QQuickColorInputsPrivate::handleHslSaturationInput, [q](){ return QString::number(qRound(q->hslSaturation() * 100)).append(QStringLiteral("%")); }); addInputField(QStringLiteral("lightness"), &percentage_validator, &QQuickColorInputsPrivate::handleLightnessInput, [q](){ return QString::number(qRound(q->lightness() * 100)).append(QStringLiteral("%")); }); if (m_showAlpha) addInputField(QStringLiteral("alpha"), &percentage_validator, &QQuickColorInputsPrivate::handleAlphaInput, [q](){ return QString::number(qRound(q->alpha() * 100)).append(QStringLiteral("%")); }); break; default: qCDebug(lcColorInputs) << "Unrecognised mode " << m_currentMode; break; } updateImplicitContentSize(); } void QQuickColorInputsPrivate::handleHexInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) emit q->colorModified(QColor::fromString(textInput->text())); } void QQuickColorInputsPrivate::handleRedInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { QColor c = q->color(); c.setRed(textInput->text().toInt()); emit q->colorModified(c); } } void QQuickColorInputsPrivate::handleGreenInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { QColor c = q->color(); c.setGreen(textInput->text().toInt()); emit q->colorModified(c); } } void QQuickColorInputsPrivate::handleBlueInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { QColor c = q->color(); c.setBlue(textInput->text().toInt()); emit q->colorModified(c); } } void QQuickColorInputsPrivate::handleHueInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { static const QRegularExpression pattern(s_degree_pattern); const auto match = pattern.match(textInput->text()); if (match.hasMatch()) { const auto substr = match.captured(1); const qreal input = static_cast(qBound(0, substr.toInt(), 360)) / static_cast(360); const QColor c = m_currentMode == QQuickColorInputs::Hsl ? QColor::fromHslF(input, q->hslSaturation(), q->lightness(), q->alpha()) : QColor::fromHsvF(input, q->hsvSaturation(), q->value(), q->alpha()); emit q->colorModified(c); } } } void QQuickColorInputsPrivate::handleHsvSaturationInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { static const QRegularExpression pattern(s_percentage_pattern); const auto match = pattern.match(textInput->text()); if (match.hasMatch()) { const auto substr = match.captured(1); const qreal input = static_cast(qBound(0, substr.toInt(), 100)) / static_cast(100); emit q->colorModified(QColor::fromHsvF(q->hue(), input, q->value(), q->alpha())); } } } void QQuickColorInputsPrivate::handleValueInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { static const QRegularExpression pattern(s_percentage_pattern); const auto match = pattern.match(textInput->text()); if (match.hasMatch()) { const auto substr = match.captured(1); const qreal input = static_cast(qBound(0, substr.toInt(), 100)) / static_cast(100); emit q->colorModified(QColor::fromHsvF(q->hue(), q->hsvSaturation(), input, q->alpha())); } } } void QQuickColorInputsPrivate::handleHslSaturationInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { static const QRegularExpression pattern(s_percentage_pattern); const auto match = pattern.match(textInput->text()); if (match.hasMatch()) { const auto substr = match.captured(1); const qreal input = static_cast(qBound(0, substr.toInt(), 100)) / static_cast(100); emit q->colorModified(QColor::fromHslF(q->hue(), input, q->lightness(), q->alpha())); } } } void QQuickColorInputsPrivate::handleLightnessInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { static const QRegularExpression pattern(s_percentage_pattern); const auto match = pattern.match(textInput->text()); if (match.hasMatch()) { const auto substr = match.captured(1); const qreal input = static_cast(qBound(0, substr.toInt(), 100)) / static_cast(100); emit q->colorModified(QColor::fromHslF(q->hue(), q->hslSaturation(), input, q->alpha())); } } } void QQuickColorInputsPrivate::handleAlphaInput() { Q_Q(QQuickColorInputs); if (const auto textInput = qobject_cast(q->QObject::sender())) { static const QRegularExpression pattern(s_percentage_pattern); const auto match = pattern.match(textInput->text()); if (match.hasMatch()) { QColor c = q->color(); const auto substr = match.captured(1); const qreal input = static_cast(qBound(0, substr.toInt(), 100)) / static_cast(100); c.setAlphaF(input); emit q->colorModified(c); } } } QT_END_NAMESPACE #include "moc_qquickcolorinputs_p.cpp"