1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickcolorinputs_p.h"
5
6#include <functional>
7
8#include <QtCore/qloggingcategory.h>
9#include <QtCore/QRegularExpression>
10#include <QtGui/QValidator>
11#include <QtQuickTemplates2/private/qquickcontainer_p_p.h>
12
13QT_BEGIN_NAMESPACE
14
15Q_STATIC_LOGGING_CATEGORY(lcColorInputs, "qt.quick.dialogs.colorinputs")
16
17class QQuickColorInputsPrivate : public QQuickContainerPrivate
18{
19public:
20 Q_DECLARE_PUBLIC(QQuickColorInputs)
21
22 void repopulate();
23 QQuickTextInput *createDelegateTextInputItem(QQmlComponent *component, const QVariantMap &initialProperties);
24 void handleHexInput();
25 void handleRedInput();
26 void handleGreenInput();
27 void handleBlueInput();
28 void handleHueInput();
29 void handleHsvSaturationInput();
30 void handleValueInput();
31 void handleHslSaturationInput();
32 void handleLightnessInput();
33 void handleAlphaInput();
34
35 QQmlComponent *m_delegate = nullptr;
36 QQuickColorInputs::Mode m_currentMode = QQuickColorInputs::Hex;
37 HSVA m_hsva;
38 bool m_showAlpha = false;
39 bool m_repopulating = false;
40};
41
42QQuickColorInputs::QQuickColorInputs(QQuickItem *parent)
43 : QQuickContainer(*(new QQuickColorInputsPrivate), parent)
44{ }
45
46QColor QQuickColorInputs::color() const
47{
48 Q_D(const QQuickColorInputs);
49 return QColor::fromHsvF(h: d->m_hsva.h, s: d->m_hsva.s, v: d->m_hsva.v, a: d->m_hsva.a);
50}
51
52void QQuickColorInputs::setColor(const QColor &c)
53{
54 Q_D(QQuickColorInputs);
55 if (color().rgba() == c.rgba())
56 return;
57
58 // If we get a QColor from an Hsv or Hsl color system,
59 // we want to get the raw values without the risk of QColor converting them,
60 // and possible deleting relevant information for achromatic cases.
61 if (c.spec() == QColor::Spec::Hsl) {
62 const auto sv = getSaturationAndValue(saturation: c.hslSaturationF(), lightness: c.lightnessF());
63 d->m_hsva.h = qBound(min: .0, val: c.hslHueF(), max: 1.0);
64 d->m_hsva.s = qBound(min: .0, val: sv.first, max: 1.0);
65 d->m_hsva.v = qBound(min: .0, val: sv.second, max: 1.0);
66 } else {
67 d->m_hsva.h = qBound(min: .0, val: c.hsvHueF(), max: 1.0);
68 d->m_hsva.s = qBound(min: .0, val: c.hsvSaturationF(), max: 1.0);
69 d->m_hsva.v = qBound(min: .0, val: c.valueF(), max: 1.0);
70 }
71
72 d->m_hsva.a = c.alphaF();
73
74 emit colorChanged(c: color());
75}
76
77int QQuickColorInputs::red() const
78{
79 return color().red();
80}
81
82int QQuickColorInputs::green() const
83{
84 return color().green();
85}
86
87int QQuickColorInputs::blue() const
88{
89 return color().blue();
90}
91
92qreal QQuickColorInputs::alpha() const
93{
94 Q_D(const QQuickColorInputs);
95 return d->m_hsva.a;
96}
97
98qreal QQuickColorInputs::hue() const
99{
100 Q_D(const QQuickColorInputs);
101 return d->m_hsva.h;
102}
103
104qreal QQuickColorInputs::hslSaturation() const
105{
106 Q_D(const QQuickColorInputs);
107 return getSaturationAndLightness(saturation: d->m_hsva.s, value: d->m_hsva.v).first;
108}
109
110qreal QQuickColorInputs::hsvSaturation() const
111{
112 Q_D(const QQuickColorInputs);
113 return d->m_hsva.s;
114}
115
116qreal QQuickColorInputs::value() const
117{
118 Q_D(const QQuickColorInputs);
119 return d->m_hsva.v;
120}
121
122qreal QQuickColorInputs::lightness() const
123{
124 Q_D(const QQuickColorInputs);
125 return getSaturationAndLightness(saturation: d->m_hsva.s, value: d->m_hsva.v).second;
126}
127
128bool QQuickColorInputs::showAlpha() const
129{
130 Q_D(const QQuickColorInputs);
131 return d->m_showAlpha;
132}
133
134void QQuickColorInputs::setShowAlpha(bool showAlpha)
135{
136 Q_D(QQuickColorInputs);
137 if (d->m_showAlpha == showAlpha)
138 return;
139
140 d->m_showAlpha = showAlpha;
141 d->repopulate();
142
143 emit showAlphaChanged(d->m_showAlpha);
144}
145
146QQuickColorInputs::Mode QQuickColorInputs::currentMode() const
147{
148 Q_D(const QQuickColorInputs);
149 return d->m_currentMode;
150}
151
152void QQuickColorInputs::setCurrentMode(Mode mode)
153{
154 Q_D(QQuickColorInputs);
155 if (d->m_currentMode == mode)
156 return;
157
158 d->m_currentMode = mode;
159 d->repopulate();
160
161 emit currentModeChanged();
162}
163
164QQmlComponent *QQuickColorInputs::delegate() const
165{
166 Q_D(const QQuickColorInputs);
167 return d->m_delegate;
168}
169
170void QQuickColorInputs::setDelegate(QQmlComponent *delegate)
171{
172 Q_D(QQuickColorInputs);
173 if (d->m_delegate == delegate)
174 return;
175 if (d->m_delegate)
176 delete d->m_delegate;
177 d->m_delegate = delegate;
178 emit delegateChanged();
179}
180
181
182void QQuickColorInputs::componentComplete()
183{
184 Q_D(QQuickColorInputs);
185 QQuickContainer::componentComplete();
186 d->repopulate();
187}
188
189QQuickTextInput *QQuickColorInputsPrivate::createDelegateTextInputItem(QQmlComponent *component, const QVariantMap &initialProperties)
190{
191 Q_Q(QQuickColorInputs);
192 QQmlContext *context = component->creationContext();
193 if (!context)
194 context = qmlContext(q);
195
196 if (!component->isBound() && initialProperties.isEmpty()) {
197 context = new QQmlContext(context, q);
198 context->setContextObject(q);
199 }
200
201 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: component->createWithInitialProperties(initialProperties, context));
202 if (textInput)
203 QQml_setParent_noEvent(object: textInput, parent: q);
204 return textInput;
205}
206
207static const QString s_percentage_pattern = QString::fromUtf8(utf8: "^(\\d+)%?$");
208static const QString s_degree_pattern = QString::fromUtf8(utf8: "(\\d+)°?$");
209static const QString s_rgba_pattern = QString::fromUtf8(utf8: "^#[0-9A-f]{6}(?:[0-9A-f]{2})?$");
210static const QString s_rgb_pattern = QString::fromUtf8(utf8: "^#[0-9A-f]{6}$");
211
212void QQuickColorInputsPrivate::repopulate()
213{
214 Q_Q(QQuickColorInputs);
215
216 if (m_repopulating)
217 return;
218
219 if (!q->delegate() || !q->contentItem()) {
220 qmlWarning(me: q) << "Both delegate and contentItem must be set before repopulating";
221 return;
222 }
223
224 QScopedValueRollback<bool> repopulateGuard(m_repopulating, true);
225
226 auto removeAllItems = [q](){
227 while (q->count() > 0)
228 q->removeItem(item: q->itemAt(index: 0));
229 };
230
231 removeAllItems();
232
233 static const QRegularExpressionValidator rgba_validator = QRegularExpressionValidator(QRegularExpression(s_rgba_pattern));
234 static const QRegularExpressionValidator rgb_validator = QRegularExpressionValidator(QRegularExpression(s_rgb_pattern));
235 static const QRegularExpressionValidator percentage_validator = QRegularExpressionValidator(QRegularExpression(s_percentage_pattern));
236 static const QRegularExpressionValidator degree_validator = QRegularExpressionValidator(QRegularExpression(s_degree_pattern));
237 static const QIntValidator intValdator = QIntValidator(0, 255);
238
239 auto addInputField = [this, q, removeAllItems](const QString &name, const QValidator *validator,
240 void (QQuickColorInputsPrivate::*handler)(),
241 std::function<QString()> textConverter) {
242 const int maxLen = m_currentMode == QQuickColorInputs::Hex ? 9 : 4;
243 const QVariantMap properties = {
244 { QStringLiteral("objectName"), QVariant::fromValue(value: name) },
245 { QStringLiteral("validator"), QVariant::fromValue(value: validator) },
246 { QStringLiteral("horizontalAlignment"), QVariant::fromValue(
247 value: m_currentMode == QQuickColorInputs::Hex ? Qt::AlignLeft : Qt::AlignHCenter) },
248 { QStringLiteral("maximumLength"), QVariant::fromValue(value: maxLen) },
249 { QStringLiteral("text"), QVariant::fromValue(value: textConverter()) }
250 };
251 if (QQuickTextInput *item = createDelegateTextInputItem(component: q->delegate(), initialProperties: properties)) {
252 connect(sender: item, signal: &QQuickTextInput::editingFinished, receiverPrivate: this, slot: handler);
253 QObject::connect(sender: q, signal: &QQuickColorInputs::colorChanged, context: item, slot: [item, textConverter](const QColor &){ item->setText(textConverter()); });
254
255 insertItem(index: q->count(), item);
256 } else {
257 qCWarning(lcColorInputs) << "Failed to create delegate for " << name;
258 removeAllItems();
259 }
260 };
261
262 switch (m_currentMode) {
263 case QQuickColorInputs::Hex:
264 addInputField(QStringLiteral("hex"), m_showAlpha ? &rgba_validator : &rgb_validator, &QQuickColorInputsPrivate::handleHexInput,
265 [q](){ return q->color().name(); });
266 break;
267 case QQuickColorInputs::Rgb:
268 addInputField(QStringLiteral("red"), &intValdator, &QQuickColorInputsPrivate::handleRedInput, [q](){ return QString::number(q->red()); });
269 addInputField(QStringLiteral("green"), &intValdator, &QQuickColorInputsPrivate::handleGreenInput, [q](){ return QString::number(q->green()); });
270 addInputField(QStringLiteral("blue"), &intValdator, &QQuickColorInputsPrivate::handleBlueInput, [q](){ return QString::number(q->blue()); });
271 if (m_showAlpha)
272 addInputField(QStringLiteral("alpha"), &percentage_validator, &QQuickColorInputsPrivate::handleAlphaInput,
273 [q](){ return QString::number(qRound(d: q->alpha() * 100)).append(QStringLiteral("%")); });
274 break;
275 case QQuickColorInputs::Hsv:
276 addInputField(QStringLiteral("hsvHue"), &degree_validator, &QQuickColorInputsPrivate::handleHueInput,
277 [q](){ return QString::number(qRound(d: q->hue() * 360)).append(QStringLiteral("°")); });
278 addInputField(QStringLiteral("hsvSaturation"), &percentage_validator, &QQuickColorInputsPrivate::handleHsvSaturationInput,
279 [q](){ return QString::number(qRound(d: q->hsvSaturation() * 100)).append(QStringLiteral("%")); });
280 addInputField(QStringLiteral("value"), &percentage_validator, &QQuickColorInputsPrivate::handleValueInput,
281 [q](){ return QString::number(qRound(d: q->value() * 100)).append(QStringLiteral("%")); });
282 if (m_showAlpha)
283 addInputField(QStringLiteral("alpha"), &percentage_validator, &QQuickColorInputsPrivate::handleAlphaInput,
284 [q](){ return QString::number(qRound(d: q->alpha() * 100)).append(QStringLiteral("%")); });
285 break;
286 case QQuickColorInputs::Hsl:
287 addInputField(QStringLiteral("hslHue"), &degree_validator, &QQuickColorInputsPrivate::handleHueInput,
288 [q](){ return QString::number(qRound(d: q->hue() * 360)).append(QStringLiteral("°")); });
289 addInputField(QStringLiteral("hslSaturation"), &percentage_validator, &QQuickColorInputsPrivate::handleHslSaturationInput,
290 [q](){ return QString::number(qRound(d: q->hslSaturation() * 100)).append(QStringLiteral("%")); });
291 addInputField(QStringLiteral("lightness"), &percentage_validator, &QQuickColorInputsPrivate::handleLightnessInput,
292 [q](){ return QString::number(qRound(d: q->lightness() * 100)).append(QStringLiteral("%")); });
293 if (m_showAlpha)
294 addInputField(QStringLiteral("alpha"), &percentage_validator, &QQuickColorInputsPrivate::handleAlphaInput,
295 [q](){ return QString::number(qRound(d: q->alpha() * 100)).append(QStringLiteral("%")); });
296 break;
297 default:
298 qCDebug(lcColorInputs) << "Unrecognised mode " << m_currentMode;
299 break;
300 }
301
302 updateImplicitContentSize();
303}
304
305void QQuickColorInputsPrivate::handleHexInput()
306{
307 Q_Q(QQuickColorInputs);
308 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender()))
309 emit q->colorModified(c: QColor::fromString(name: textInput->text()));
310}
311
312void QQuickColorInputsPrivate::handleRedInput()
313{
314 Q_Q(QQuickColorInputs);
315 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
316 QColor c = q->color();
317 c.setRed(textInput->text().toInt());
318 emit q->colorModified(c);
319 }
320}
321
322void QQuickColorInputsPrivate::handleGreenInput()
323{
324 Q_Q(QQuickColorInputs);
325 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
326 QColor c = q->color();
327 c.setGreen(textInput->text().toInt());
328 emit q->colorModified(c);
329 }
330}
331
332void QQuickColorInputsPrivate::handleBlueInput()
333{
334 Q_Q(QQuickColorInputs);
335 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
336 QColor c = q->color();
337 c.setBlue(textInput->text().toInt());
338 emit q->colorModified(c);
339 }
340}
341
342void QQuickColorInputsPrivate::handleHueInput()
343{
344 Q_Q(QQuickColorInputs);
345 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
346 static const QRegularExpression pattern(s_degree_pattern);
347 const auto match = pattern.match(subject: textInput->text());
348 if (match.hasMatch()) {
349 const auto substr = match.captured(nth: 1);
350 const qreal input = static_cast<qreal>(qBound(min: 0, val: substr.toInt(), max: 360)) / static_cast<qreal>(360);
351 const QColor c = m_currentMode == QQuickColorInputs::Hsl ? QColor::fromHslF(h: input, s: q->hslSaturation(), l: q->lightness(), a: q->alpha())
352 : QColor::fromHsvF(h: input, s: q->hsvSaturation(), v: q->value(), a: q->alpha());
353 emit q->colorModified(c);
354 }
355 }
356}
357
358void QQuickColorInputsPrivate::handleHsvSaturationInput()
359{
360 Q_Q(QQuickColorInputs);
361 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
362 static const QRegularExpression pattern(s_percentage_pattern);
363 const auto match = pattern.match(subject: textInput->text());
364 if (match.hasMatch()) {
365 const auto substr = match.captured(nth: 1);
366 const qreal input = static_cast<qreal>(qBound(min: 0, val: substr.toInt(), max: 100)) / static_cast<qreal>(100);
367 emit q->colorModified(c: QColor::fromHsvF(h: q->hue(), s: input, v: q->value(), a: q->alpha()));
368 }
369 }
370}
371
372void QQuickColorInputsPrivate::handleValueInput()
373{
374 Q_Q(QQuickColorInputs);
375 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
376 static const QRegularExpression pattern(s_percentage_pattern);
377 const auto match = pattern.match(subject: textInput->text());
378 if (match.hasMatch()) {
379 const auto substr = match.captured(nth: 1);
380 const qreal input = static_cast<qreal>(qBound(min: 0, val: substr.toInt(), max: 100)) / static_cast<qreal>(100);
381 emit q->colorModified(c: QColor::fromHsvF(h: q->hue(), s: q->hsvSaturation(), v: input, a: q->alpha()));
382 }
383 }
384}
385
386void QQuickColorInputsPrivate::handleHslSaturationInput()
387{
388 Q_Q(QQuickColorInputs);
389 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
390 static const QRegularExpression pattern(s_percentage_pattern);
391 const auto match = pattern.match(subject: textInput->text());
392 if (match.hasMatch()) {
393 const auto substr = match.captured(nth: 1);
394 const qreal input = static_cast<qreal>(qBound(min: 0, val: substr.toInt(), max: 100)) / static_cast<qreal>(100);
395 emit q->colorModified(c: QColor::fromHslF(h: q->hue(), s: input, l: q->lightness(), a: q->alpha()));
396 }
397 }
398}
399
400void QQuickColorInputsPrivate::handleLightnessInput()
401{
402 Q_Q(QQuickColorInputs);
403 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
404 static const QRegularExpression pattern(s_percentage_pattern);
405 const auto match = pattern.match(subject: textInput->text());
406 if (match.hasMatch()) {
407 const auto substr = match.captured(nth: 1);
408 const qreal input = static_cast<qreal>(qBound(min: 0, val: substr.toInt(), max: 100)) / static_cast<qreal>(100);
409 emit q->colorModified(c: QColor::fromHslF(h: q->hue(), s: q->hslSaturation(), l: input, a: q->alpha()));
410 }
411 }
412}
413
414void QQuickColorInputsPrivate::handleAlphaInput()
415{
416 Q_Q(QQuickColorInputs);
417 if (const auto textInput = qobject_cast<QQuickTextInput *>(object: q->QObject::sender())) {
418 static const QRegularExpression pattern(s_percentage_pattern);
419 const auto match = pattern.match(subject: textInput->text());
420 if (match.hasMatch()) {
421 QColor c = q->color();
422 const auto substr = match.captured(nth: 1);
423 const qreal input = static_cast<qreal>(qBound(min: 0, val: substr.toInt(), max: 100)) / static_cast<qreal>(100);
424 c.setAlphaF(input);
425 emit q->colorModified(c);
426 }
427 }
428}
429
430QT_END_NAMESPACE
431
432#include "moc_qquickcolorinputs_p.cpp"
433

source code of qtdeclarative/src/quickdialogs/quickdialogsquickimpl/qquickcolorinputs.cpp