Perceptual Color

colordialog.cpp
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4// Own headers
5// First the interface, which forces the header to be self-contained.
6#include "colordialog.h"
7// Second, the private implementation.
8#include "colordialog_p.h" // IWYU pragma: associated
9
10#include "absolutecolor.h"
11#include "chromahuediagram.h"
12#include "cielchd50values.h"
13#include "colorpatch.h"
14#include "constpropagatingrawpointer.h"
15#include "constpropagatinguniquepointer.h"
16#include "gradientslider.h"
17#include "helper.h"
18#include "helperconstants.h"
19#include "helperconversion.h"
20#include "helperqttypes.h"
21#include "initializetranslation.h"
22#include "multispinbox.h"
23#include "multispinboxsection.h"
24#include "oklchvalues.h"
25#include "rgbcolor.h"
26#include "rgbcolorspace.h"
27#include "rgbcolorspacefactory.h"
28#include "screencolorpicker.h"
29#include "setting.h"
30#include "swatchbook.h"
31#include "wheelcolorpicker.h"
32#include <algorithm>
33#include <lcms2.h>
34#include <optional>
35#include <qaction.h>
36#include <qapplication.h>
37#include <qboxlayout.h>
38#include <qbytearray.h>
39#include <qchar.h>
40#include <qcombobox.h>
41#include <qcoreapplication.h>
42#include <qcoreevent.h>
43#include <qdatetime.h>
44#include <qdebug.h>
45#include <qdialogbuttonbox.h>
46#include <qfontmetrics.h>
47#include <qformlayout.h>
48#include <qgridlayout.h>
49#include <qgroupbox.h>
50#include <qguiapplication.h>
51#include <qicon.h>
52#include <qkeysequence.h>
53#include <qlabel.h>
54#include <qlineedit.h>
55#include <qlist.h>
56#include <qlocale.h>
57#include <qobject.h>
58#include <qpair.h>
59#include <qpointer.h>
60#include <qpushbutton.h>
61#include <qregularexpression.h>
62#include <qscopedpointer.h>
63#include <qscreen.h>
64#include <qsharedpointer.h>
65#include <qshortcut.h>
66#include <qsize.h>
67#include <qsizepolicy.h>
68#include <qspinbox.h>
69#include <qstackedlayout.h>
70#include <qstringbuilder.h>
71#include <qstringliteral.h>
72#include <qtabwidget.h>
73#include <qtoolbutton.h>
74#include <qvalidator.h>
75#include <qversionnumber.h>
76#include <qwidget.h>
77#include <utility>
78class QShowEvent;
79
80#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
81#include <qcontainerfwd.h>
82#include <qobjectdefs.h>
83#else
84#include <qstringlist.h>
85#endif
86
87#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
88#include <qstylehints.h>
89#endif
90
91namespace PerceptualColor
92{
93
94/** @brief A text with the name of the color model.
95 *
96 * @param model The signature of the color model.
97 *
98 * @returns A text with the name of the color model, or an empty
99 * QString if the model is unknown. If a translation is available,
100 * the translation is returned instead of the original English text. */
101QString ColorDialogPrivate::translateColorModel(cmsColorSpaceSignature model)
102{
103 switch (model) {
104 case cmsSigXYZData:
105 /*: @item A color model: X, Y, Z. */
106 return tr("XYZ");
107 case cmsSigLabData:
108 /*: @item A color model: Lightness, a, b. */
109 return tr("Lab");
110 case cmsSigRgbData:
111 /*: @item A color model: red, green, blue. */
112 return tr("RGB");
113 case cmsSigLuvData:
114 // return tr("Luv"); // Currently not supported.
115 return QString();
116 case cmsSigYCbCrData:
117 // return tr("YCbCr"); // Currently not supported.
118 return QString();
119 case cmsSigYxyData:
120 // return tr("Yxy"); // Currently not supported.
121 return QString();
122 case cmsSigGrayData:
123 // return tr("Grayscale"); // Currently not supported.
124 return QString();
125 case cmsSigHsvData:
126 // return tr("HSV"); // Currently not supported.
127 return QString();
128 case cmsSigHlsData:
129 // return tr("HSL"); // Currently not supported.
130 return QString();
131 case cmsSigCmykData:
132 // return tr("CMYK"); // Currently not supported.
133 return QString();
134 case cmsSigCmyData:
135 // return tr("CMY"); // Currently not supported.
136 return QString();
137 case cmsSigNamedData: // Does not exist in ICC 4.4.
138 case cmsSig2colorData:
139 case cmsSig3colorData:
140 case cmsSig4colorData:
141 case cmsSig5colorData:
142 case cmsSig6colorData:
143 case cmsSig7colorData:
144 case cmsSig8colorData:
145 case cmsSig9colorData:
146 case cmsSig10colorData:
147 case cmsSig11colorData:
148 case cmsSig12colorData:
149 case cmsSig13colorData:
150 case cmsSig14colorData:
151 case cmsSig15colorData:
152 // return tr("Named color"); // Currently not supported.
153 return QString();
154 case cmsSig1colorData:
155 case cmsSigLuvKData:
156 case cmsSigMCH1Data:
157 case cmsSigMCH2Data:
158 case cmsSigMCH3Data:
159 case cmsSigMCH4Data:
160 case cmsSigMCH5Data:
161 case cmsSigMCH6Data:
162 case cmsSigMCH7Data:
163 case cmsSigMCH8Data:
164 case cmsSigMCH9Data:
165 case cmsSigMCHAData:
166 case cmsSigMCHBData:
167 case cmsSigMCHCData:
168 case cmsSigMCHDData:
169 case cmsSigMCHEData:
170 case cmsSigMCHFData:
171 // Unhandeled: These values do not exist in ICC 4.4 standard as
172 // published at https://www.color.org/specification/ICC.1-2022-05.pdf
173 // page 35, table 19 — Data colour space signatures.
174 default:
175 break;
176 }
177 return QString();
178}
179
180/** @brief Retranslate the UI with all user-visible strings.
181 *
182 * This function updates all user-visible strings by using
183 * <tt>Qt::tr()</tt> to get up-to-date translations.
184 *
185 * This function is meant to be called at the end of the constructor and
186 * additionally after each <tt>QEvent::LanguageChange</tt> event.
187 *
188 * @note This is the same concept as
189 * <a href="https://doc.qt.io/qt-5/designer-using-a-ui-file.html">
190 * Qt Designer, which also provides a function of the same name in
191 * uic-generated code</a>.
192 *
193 * @internal
194 *
195 * @todo Add to the color-space tooltip information about available rendering
196 * intents (we have yet RgbColorSpacePrivate::intentList but do not use it
197 * anywhere) and the RGB profile illuminant? (This would have to be implemented
198 * in @ref RgbColorSpace first.)
199 *
200 * @todo As the tooltip for color-space information is quite big, would
201 * it be better to do what systemsettings does in globaldesign/fonts? They
202 * have a small button with an “i” symbol (for information), which does
203 * nothing when it’s clicked, but when hovering with the mouse, it shows
204 * the tooltip?
205 *
206 * @todo How to make tooltip information available for touch-screen users?
207 *
208 * @todo Provide the information
209 * of @ref RgbColorSpace::profileRenderingIntentDirections() in the tooltip of the
210 * color space. Example: Rendering intents: Perceptual (input, output, proof)
211 * [br] Relative (input, proof) [br] Saturation (output) [br]
212 * Absolute colorimetric (proof). Missing rendering intents are not displayed.
213 * Limiting ourself to these four standard rendering intents, leaving LittleCMS
214 * extensions out of scope. Providing i18n instead of using the rendering
215 * intent descriptions of LittleCMS.
216 */
217void ColorDialogPrivate::retranslateUi()
218{
219 /*: @item/plain Percentage value in a spinbox. Range: 0%–100%. */
220 const QPair<QString, QString> percentageInSpinbox = //
221 getPrefixSuffix(tr("%1%"));
222
223 /*: @item/plain Arc-degree value in a spinbox. Range: 0°–360°. */
224 const QPair<QString, QString> arcDegreeInSpinbox = //
225 getPrefixSuffix(tr("%1°"));
226
227 QStringList profileInfo;
228 const QString name = //
229 m_rgbColorSpace->profileName().toHtmlEscaped();
230 if (!name.isEmpty()) {
231 /*: @item:intext An information from the color profile to be added
232 to the info text about current color space. */
233 profileInfo.append(tableRow.arg(tr("Name:"), name));
234 }
235 /*: @item:intext The maximum chroma. */
236 const QString maximumCielchD50Chroma = //
237 tr("%L1 (estimated)")
238 .arg(m_rgbColorSpace->profileMaximumCielchD50Chroma(), //
239 0, //
240 'f', //
241 decimals);
242 /*: @item:intext An information from the color profile to be added
243 to the info text about current color space. */
244 profileInfo.append( //
245 tableRow.arg(tr("Maximum CIELCh-D50 chroma:"), maximumCielchD50Chroma));
246 /*: @item:intext The maximum chroma. */
247 const QString maximumOklchChroma = //
248 tr("%L1 (estimated)")
249 .arg(m_rgbColorSpace->profileMaximumOklchChroma(), //
250 0, //
251 'f', //
252 okdecimals);
253 /*: @item:intext An information from the color profile to be added
254 to the info text about current color space. */
255 profileInfo.append( //
256 tableRow.arg(tr("Maximum Oklch chroma:"), maximumOklchChroma));
257 QString profileClass;
258 switch (m_rgbColorSpace->profileClass()) {
259 case cmsSigDisplayClass:
260 /*: @item:intext The class of an ICC profile. */
261 profileClass = tr("Display profile");
262 break;
263 case cmsSigAbstractClass: // Image effect profile (Abstract profile)
264 // This ICC profile class is called "abstract
265 // profile" in the official standard. However,
266 // the name is misleading. The actual function of
267 // these ICC profiles is to apply image effects.
268 case cmsSigColorSpaceClass: // Color space conversion profile
269 case cmsSigInputClass: // Input profile
270 case cmsSigLinkClass: // Device link profile
271 case cmsSigNamedColorClass: // Named color profile
272 case cmsSigOutputClass: // Output profile
273 // These profile classes are currently not supported.
274 break;
275 }
276 if (!profileClass.isEmpty()) {
277 /*: @item:intext An information from the color profile to be added
278 to the info text about current color space. */
279 profileInfo.append( //
280 tableRow.arg(tr("Profile class:"), profileClass));
281 }
282 const QString colorModel = //
283 translateColorModel(m_rgbColorSpace->profileColorModel());
284 if (!colorModel.isEmpty()) {
285 /*: @item:intext An information from the color profile to be added
286 to the info text about current color space.
287 The color model of the color space which is described by this
288 profile. */
289 profileInfo.append(tableRow.arg(tr("Color model:"), colorModel));
290 }
291 const QString manufacturer = //
292 m_rgbColorSpace->profileManufacturer().toHtmlEscaped();
293 if (!manufacturer.isEmpty()) {
294 /*: @item:intext An information from the color profile to be added
295 to the info text about current color space.
296 This is usually the manufacturer of the device to which
297 the colour profile applies. */
298 profileInfo.append(tableRow.arg(tr("Manufacturer:"), manufacturer));
299 }
300 const QString model = //
301 m_rgbColorSpace->profileModel().toHtmlEscaped();
302 if (!model.isEmpty()) {
303 /*: @item:intext An information from the color profile to be added to
304 the info text about current color space.
305 This is usually the model identifier of the device to which
306 the colour profile applies. */
307 profileInfo.append(tableRow.arg(tr("Device model:"), (model)));
308 }
309 const QDateTime creationDateTime = //
310 m_rgbColorSpace->profileCreationDateTime();
311 if (!creationDateTime.isNull()) {
312 const auto creationDateTimeString = QLocale().toString(
313 // Date and time:
314 creationDateTime,
315 // Format:
317 /*: @item:intext An information from the color profile to be added to
318 the info text about current color space.
319 This is the date and time of the creation of the profile. */
320 profileInfo.append( //
321 tableRow.arg(tr("Created:"), (creationDateTimeString)));
322 }
323 const QVersionNumber iccVersion = m_rgbColorSpace->profileIccVersion();
324 /*: @item:intext An information from the color profile to be added to
325 the info text about current color space.
326 This is the version number of the ICC file format that is used. */
327 profileInfo.append( //
328 tableRow.arg(tr("ICC format:"), (iccVersion.toString())));
329 const bool hasMatrixShaper = //
330 m_rgbColorSpace->profileHasMatrixShaper();
331 const bool hasClut = //
332 m_rgbColorSpace->profileHasClut();
333 if (hasMatrixShaper || hasClut) {
334 const QString matrixShaperString = tableRow.arg(
335 /*: @item:intext An information from the color profile to be added
336 to the info text about current color space.
337 Wether the profile has a matrix shaper or a color lookup table
338 (CLUT) or both. */
339 tr("Implementation:"));
340 if (hasMatrixShaper && hasClut) {
341 /*: @item:intext An information from the color profile to be added
342 to the info text about current color space.
343 Wether the profile has a matrix shaper or a color lookup table
344 (CLUT) or both. */
345 profileInfo.append( //
346 matrixShaperString.arg(tr("Matrices and color lookup tables")));
347 } else if (hasMatrixShaper) {
348 /*: @item:intext An information from the color profile to be added
349 to the info text about current color space.
350 Wether the profile has a matrix shaper or a color lookup table
351 (CLUT) or both. */
352 profileInfo.append(matrixShaperString.arg(tr("Matrices")));
353 } else if (hasClut) {
354 /*: @item:intext An information from the color profile to be added
355 to the info text about current color space.
356 Wether the profile has a matrix shaper or a color lookup table
357 (CLUT) or both. */
358 profileInfo.append( //
359 matrixShaperString.arg(tr("Color lookup tables")));
360 }
361 }
362 const QString pcsColorModelText = //
363 translateColorModel(m_rgbColorSpace->profilePcsColorModel());
364 if (!pcsColorModelText.isEmpty()) {
365 /*: @item:intext An information from the color profile to be added
366 to the info text about current color space.
367 The color model of the PCS (profile connection space) which is used
368 internally by this profile. */
369 profileInfo.append( //
370 tableRow.arg(tr("PCS color model:"), pcsColorModelText));
371 }
372 const QString copyright = m_rgbColorSpace->profileCopyright();
373 if (!copyright.isEmpty()) {
374 /*: @item:intext An information from the color profile to be added
375 to the info text about current color space.
376 The copyright of this profile. */
377 profileInfo.append(tableRow.arg(tr("Copyright:"), copyright));
378 }
379 const qint64 fileSize = //
380 m_rgbColorSpace->profileFileSize();
381 if (fileSize >= 0) {
382 /*: @item:intext An information from the color profile to be added to
383 the info text about current color space.
384 This is the size of the ICC file that was read in. */
385 profileInfo.append(tableRow.arg(tr("File size:"), //
386 QLocale().formattedDataSize(fileSize)));
387 }
388 const QString fileName = //
389 m_rgbColorSpace->profileAbsoluteFilePath();
390 if (!fileName.isEmpty()) {
391 /*: @item:intext An information from the color profile to be added to
392 the info text about current color space. */
393 profileInfo.append(tableRow.arg(tr("File name:"), fileName));
394 }
395 if (profileInfo.isEmpty()) {
396 m_rgbGroupBox->setToolTip(QString());
397 } else {
398 const QString tableString = QStringLiteral(
399 "<b>%1</b><br/>"
400 "<table border=\"0\" cellpadding=\"2\" cellspacing=\"0\">"
401 "%2"
402 "</table>");
403 m_rgbGroupBox->setToolTip(richTextMarker
404 + tableString.arg(
405 /*: @info:intext Title of info text about
406 current color space (will be followed by
407 other information as available
408 in the color profile. */
409 tr("Color space information"), //
410 profileInfo.join(QString())));
411 }
412
413 /*: @label:spinbox Label for CIE’s CIELCH color model, based on Lightness,
414 * Chroma and Hue, and using the D50 illuminant as white point.*/
415 m_cielchD50SpinBoxLabel->setText(tr("&CIELCH D50:"));
416
417 /*: @label:spinbox Label for Oklch color model, based on Lightness, Chroma,
418 Hue, and using the D65 illuminant as white point. */
419 m_oklchSpinBoxLabel->setText(tr("O&klch:"));
420
421 /*: @label:spinbox Label for RGB color model, based on Red, Green, Blue. */
422 m_rgbSpinBoxLabel->setText(tr("&RGB:"));
423
424 /*: @label:textbox Label for hexadecimal RGB representation like #12ab45 */
425 m_rgbLineEditLabel->setText(tr("He&x:"));
426
427 const int swatchBookIndex = m_tabWidget->indexOf(m_swatchBookWrapperWidget);
428 if (swatchBookIndex >= 0) {
429 /*: @title:tab
430 The tab contains swatch books showing colors. */
431 const auto mnemonic = tr("&Swatch book");
432 m_tabWidget->setTabToolTip( //
433 swatchBookIndex, //
434 richTextMarker + fromMnemonicToRichText(mnemonic));
435 m_swatchBookTabShortcut->setKey(QKeySequence::mnemonic(mnemonic));
436 }
437
438 const int hueFirstIndex = m_tabWidget->indexOf(m_hueFirstWrapperWidget);
439 if (hueFirstIndex >= 0) {
440 /*: @title:tab
441 The tab contains a visual UI to choose first the hue, and in a
442 second step chroma and lightness. */
443 const auto mnemonic = tr("&Hue-based");
444 m_tabWidget->setTabToolTip( //
445 hueFirstIndex, //
446 richTextMarker + fromMnemonicToRichText(mnemonic));
447 m_hueFirstTabShortcut->setKey(QKeySequence::mnemonic(mnemonic));
448 }
449 const int lightnessFirstIndex = //
450 m_tabWidget->indexOf(m_lightnessFirstWrapperWidget);
451 if (lightnessFirstIndex >= 0) {
452 /*: @title:tab
453 The tab contains a visual UI to choose first the lightness, and in a
454 second step chroma and hue.
455 “Lightness” is different from “brightness”/“value”
456 and should therefore get a different translation. */
457 const auto mnemonic = tr("&Lightness-based");
458 m_tabWidget->setTabToolTip( //
459 lightnessFirstIndex, //
460 richTextMarker + fromMnemonicToRichText(mnemonic));
461 m_lightnessFirstTabShortcut->setKey(QKeySequence::mnemonic(mnemonic));
462 }
463 const int numericIndex = //
464 m_tabWidget->indexOf(m_numericalWidget);
465 if (numericIndex >= 0) {
466 /*: @title:tab
467 The tab contains a UI to describe the color with numbers: Spin boxes
468 and line edits containing values like “#2A7845” or “RGB 85 45 12”. */
469 const auto mnemonic = tr("&Numeric");
470 m_tabWidget->setTabToolTip( //
471 numericIndex, //
472 richTextMarker + fromMnemonicToRichText(mnemonic));
473 m_numericalTabShortcut->setKey(QKeySequence::mnemonic(mnemonic));
474 }
475
476 /*: @label:spinbox HSL (hue, saturation, lightness) */
477 m_hslSpinBoxLabel->setText(tr("HS&L:"));
478
479 /*: @label:spinbox HSV (hue, saturation, value) and HSB (hue, saturation,
480 brightness) are two different names for the very same color model. */
481 m_hsvSpinBoxLabel->setText(tr("HS&V/HSB:"));
482
483 /*: @label:spinbox HWB (hue, whiteness, blackness) */
484 m_hwbSpinBoxLabel->setText(tr("H&WB:"));
485
486 /*: @action:button */
487 m_buttonOK->setText(tr("&OK"));
488
489 /*: @action:button */
490 m_buttonCancel->setText(tr("&Cancel"));
491 /*: @info:tooltip Help text for RGB spinbox. */
492 m_rgbSpinBox->setToolTip( //
493 richTextMarker
494 + tr("<p>Red: 0⁠–⁠255</p>"
495 "<p>Green: 0⁠–⁠255</p>"
496 "<p>Blue: 0⁠–⁠255</p>"));
497
498 /*: @info:tooltip Help text for hexadecimal code. */
499 m_rgbLineEdit->setToolTip( //
500 richTextMarker
501 + tr("<p>Hexadecimal color code, as used in HTML: #RRGGBB</p>"
502 "<p>RR: two-digit code for red: 00⁠–⁠FF</p>"
503 "<p>GG: two-digit code for green: 00⁠–⁠FF</p>"
504 "<p>BB: two-digit code for blue: 00⁠–⁠FF</p>"));
505
506 /*: @info:tooltip Help text for HSL (hue, saturation, lightness).
507 Saturation: 0 means something on the grey axis; 255 means something
508 between the grey axis and the most colorful color. This is different
509 from “chroma” and should therefore get a different translation.
510 Lightness: 0 means always black; 255 means always white. This is
511 different from “brightness” and should therefore get a different
512 translation. */
513 m_hslSpinBox->setToolTip(richTextMarker
514 + tr("<p>Hue: 0°⁠–⁠360°</p>"
515 "<p>HSL-Saturation: 0%⁠–⁠100%</p>"
516 "<p>Lightness: 0%⁠–⁠100%</p>"));
517
518 /*: @info:tooltip Help text for HWB (hue, whiteness, blackness).
519 The idea behind is that the hue defines the pure (maximum colorful) color.
520 Than, white color can be added, creating a “tint”. Or black color
521 can be added, creating a “shade”. Or both can be added, creating a “tone“.
522 See https://en.wikipedia.org/wiki/Tint,_shade_and_tone for more
523 information. 0% white + 0% black = pure color. 100% white
524 + 0% black = white. 0% white + 100% black = black. 50% white + 50% black
525 = gray. 50% white + 0% black = tint. 25% white + 25% black = tone.
526 0% white + 50% black = shade. */
527 m_hwbSpinBox->setToolTip(richTextMarker
528 + tr("<p>Hue: 0°⁠–⁠360°</p>"
529 "<p>Whiteness: 0%⁠–⁠100%</p>"
530 "<p>Blackness: 0%⁠–⁠100%</p>"));
531
532 /*: @info:tooltip Help text for HSV/HSB. HSV (hue, saturation, value)
533 and HSB (hue, saturation, brightness) are two different names for the
534 very same color model. Saturation: 0 means something between black and
535 white; 255 means something between black and the most colorful color.
536 This is different from “chroma” and should therefore get a different
537 translation. Brightness/value: 0 means always black; 255 means something
538 between white and the most colorful color. This is different from
539 “lightness” and should therefore get a different translation. */
540 m_hsvSpinBox->setToolTip(richTextMarker
541 + tr("<p>Hue: 0°⁠–⁠360°</p>"
542 "<p>HSV/HSB-Saturation: 0%⁠–⁠100%</p>"
543 "<p>Brightness/Value: 0%⁠–⁠100%</p>"));
544
545 m_alphaSpinBox->setPrefix(percentageInSpinbox.first);
546 m_alphaSpinBox->setSuffix(percentageInSpinbox.second);
547
548 /*: @label:slider Accessible name for lightness slider. This is different
549 from “brightness”/“value” and should therefore get a different
550 translation. */
551 m_lchLightnessSelector->setAccessibleName(tr("Lightness"));
552
553 /*: @info:tooltip Help text for CIELCH. “lightness” is different from
554 “brightness”/“value” and should therefore get a different translation. */
555 m_cielchD50SpinBox->setToolTip(richTextMarker
556 + tr("<p>Lightness: 0%⁠–⁠100%</p>"
557 "<p>Chroma: 0⁠–⁠%L1</p>"
558 "<p>Hue: 0°⁠–⁠360°</p>")
559 .arg(CielchD50Values::maximumChroma));
560
561 constexpr double maxOklchChroma = OklchValues::maximumChroma;
562 /*: @info:tooltip Help text for Oklch. “lightness” is different from
563 “brightness”/“value” and should therefore get a different translation. */
564 m_oklchSpinBox->setToolTip(richTextMarker
565 + tr("<p>Lightness: %L1⁠–⁠%L2</p>"
566 "<p>Chroma: %L3⁠–⁠%L4</p>"
567 "<p>Hue: 0°⁠–⁠360°</p>"
568 "<p>Whitepoint: D65</p>")
569 .arg(0., 0, 'f', okdecimals)
570 .arg(1., 0, 'f', okdecimals)
571 .arg(0., 0, 'f', okdecimals)
572 .arg(maxOklchChroma, 0, 'f', okdecimals));
573
574 /*: @label:slider An opacity of 0 means completely
575 transparent. The higher the opacity value increases, the
576 more opaque the colour becomes, until it finally becomes
577 completely opaque at the highest possible opacity value. */
578 const QString opacityLabel = tr("Op&acity:");
579 m_alphaGradientSlider->setAccessibleName(opacityLabel);
580 m_alphaLabel->setText(opacityLabel);
581
582 // HSL spin box
583 QList<MultiSpinBoxSection> hslSections = //
584 m_hslSpinBox->sectionConfigurations();
585 if (hslSections.count() != 3) {
586 qWarning() //
587 << "Expected 3 sections in HSV MultiSpinBox, but got" //
588 << hslSections.count() //
589 << "instead. This is a bug in libperceptualcolor.";
590 } else {
591 hslSections[0].setPrefix(arcDegreeInSpinbox.first);
592 hslSections[0].setSuffix( //
593 arcDegreeInSpinbox.second + m_multispinboxSectionSeparator);
594 hslSections[1].setPrefix( //
595 m_multispinboxSectionSeparator + percentageInSpinbox.first);
596 hslSections[1].setSuffix( //
597 percentageInSpinbox.second + m_multispinboxSectionSeparator);
598 hslSections[2].setPrefix( //
599 m_multispinboxSectionSeparator + percentageInSpinbox.first);
600 hslSections[2].setSuffix(percentageInSpinbox.second);
601 m_hslSpinBox->setSectionConfigurations(hslSections);
602 }
603
604 // HWB spin box
605 QList<MultiSpinBoxSection> hwbSections = //
606 m_hwbSpinBox->sectionConfigurations();
607 if (hwbSections.count() != 3) {
608 qWarning() //
609 << "Expected 3 sections in HSV MultiSpinBox, but got" //
610 << hwbSections.count() //
611 << "instead. This is a bug in libperceptualcolor.";
612 } else {
613 hwbSections[0].setPrefix(arcDegreeInSpinbox.first);
614 hwbSections[0].setSuffix( //
615 arcDegreeInSpinbox.second + m_multispinboxSectionSeparator);
616 hwbSections[1].setPrefix( //
617 m_multispinboxSectionSeparator + percentageInSpinbox.first);
618 hwbSections[1].setSuffix( //
619 percentageInSpinbox.second + m_multispinboxSectionSeparator);
620 hwbSections[2].setPrefix( //
621 m_multispinboxSectionSeparator + percentageInSpinbox.first);
622 hwbSections[2].setSuffix( //
623 percentageInSpinbox.second);
624 m_hwbSpinBox->setSectionConfigurations(hwbSections);
625 }
626
627 // HSV spin box
628 QList<MultiSpinBoxSection> hsvSections = //
629 m_hsvSpinBox->sectionConfigurations();
630 if (hsvSections.count() != 3) {
631 qWarning() //
632 << "Expected 3 sections in HSV MultiSpinBox, but got" //
633 << hsvSections.count() //
634 << "instead. This is a bug in libperceptualcolor.";
635 } else {
636 hsvSections[0].setPrefix(arcDegreeInSpinbox.first);
637 hsvSections[0].setSuffix( //
638 arcDegreeInSpinbox.second + m_multispinboxSectionSeparator);
639 hsvSections[1].setPrefix( //
640 m_multispinboxSectionSeparator + percentageInSpinbox.first);
641 hsvSections[1].setSuffix( //
642 percentageInSpinbox.second + m_multispinboxSectionSeparator);
643 hsvSections[2].setPrefix( //
644 m_multispinboxSectionSeparator + percentageInSpinbox.first);
645 hsvSections[2].setSuffix(percentageInSpinbox.second);
646 m_hsvSpinBox->setSectionConfigurations(hsvSections);
647 }
648
649 // CIELCH-D50 spin box
650 QList<MultiSpinBoxSection> cielchD50Sections = //
651 m_cielchD50SpinBox->sectionConfigurations();
652 if (cielchD50Sections.count() != 3) {
653 qWarning() //
654 << "Expected 3 sections in CIELCH-D50 MultiSpinBox, but got" //
655 << cielchD50Sections.count() //
656 << "instead. This is a bug in libperceptualcolor.";
657 } else {
658 cielchD50Sections[0].setPrefix(percentageInSpinbox.first);
659 cielchD50Sections[0].setSuffix( //
660 percentageInSpinbox.second + m_multispinboxSectionSeparator);
661 cielchD50Sections[1].setPrefix(m_multispinboxSectionSeparator);
662 cielchD50Sections[1].setSuffix(m_multispinboxSectionSeparator);
663 cielchD50Sections[2].setPrefix(m_multispinboxSectionSeparator + arcDegreeInSpinbox.first);
664 cielchD50Sections[2].setSuffix(arcDegreeInSpinbox.second);
665 m_cielchD50SpinBox->setSectionConfigurations(cielchD50Sections);
666 }
667
668 // Oklch spin box
669 QList<MultiSpinBoxSection> oklchSections = //
670 m_oklchSpinBox->sectionConfigurations();
671 if (oklchSections.count() != 3) {
672 qWarning() //
673 << "Expected 3 sections in Oklch MultiSpinBox, but got" //
674 << oklchSections.count() //
675 << "instead. This is a bug in libperceptualcolor.";
676 } else {
677 oklchSections[0].setPrefix(QString());
678 oklchSections[0].setSuffix(m_multispinboxSectionSeparator);
679 oklchSections[1].setPrefix(m_multispinboxSectionSeparator);
680 oklchSections[1].setSuffix(m_multispinboxSectionSeparator);
681 oklchSections[2].setPrefix( //
682 m_multispinboxSectionSeparator + arcDegreeInSpinbox.first);
683 oklchSections[2].setSuffix(arcDegreeInSpinbox.second);
684 m_oklchSpinBox->setSectionConfigurations(oklchSections);
685 }
686
687 if (m_screenColorPickerButton) {
688 /*: @action:button (eye dropper/pipette).
689 A click on the button transforms the mouse cursor to a cross and lets
690 the user choose a color from the screen by doing a left-click.
691 Same text as in QColorDialog */
692 const auto mnemonic = tr("&Pick screen color");
693 m_screenColorPickerButton->setToolTip( //
694 richTextMarker + fromMnemonicToRichText(mnemonic));
695 m_screenColorPickerButton->setShortcut( //
696 QKeySequence::mnemonic(mnemonic));
697 }
698
699 /*: @info:tooltip Tooltip for the gamut-correction action.
700 The icon for this action is only visible in the UI while the
701 color value within the corresponding spinbox is an out-of-gamut
702 value. A click on the icon will change the spinbox’s values to
703 the nearest in-gamut color (and make the icon disappear). */
704 const auto gamutMnemonic = //
705 tr("Click to snap to nearest in-&gamut color");
706 const QString gamutTooltip = //
707 richTextMarker + fromMnemonicToRichText(gamutMnemonic);
708 const auto gamutShortcut = QKeySequence::mnemonic(gamutMnemonic);
709 m_cielchD50SpinBoxGamutAction->setToolTip(gamutTooltip);
710 m_cielchD50SpinBoxGamutAction->setShortcut(gamutShortcut);
711 m_oklchSpinBoxGamutAction->setToolTip(gamutTooltip);
712 m_oklchSpinBoxGamutAction->setShortcut(gamutShortcut);
713
714 /*: @item:inlistbox
715 The swatch grid showing the basic colors like yellow,
716 orange, red… Same text as in QColorDialog */
717 m_swatchBookSelector->setItemText(0, tr("Basic colors")); // TODO xxx Short cut? /plain or /richtext or how is the correct context marker?
718
719 /*: @item:inlistbox
720 The swatch grid showing the history of
721 previously selected colors. */
722 m_swatchBookSelector->setItemText(1, tr("History")); // TODO xxx Short cut? /plain or /richtext or how is the correct context marker?
723
724 /*: @item:inlistbox
725 The swatch grid showing the history of
726 previously selected colors. */
727 m_swatchBookSelector->setItemText(2, tr("Custom Colors")); // TODO xxx Short cut? /plain or /richtext or how is the correct context marker?
728
729 // NOTE No need to call
730 //
731 // q_pointer->adjustSize();
732 //
733 // because our layout adopts automatically to the
734 // new size of the strings. Indeed, calling
735 //
736 // q_pointer->adjustSize();
737 //
738 // would change the height (!) of the widget: While it might seem
739 // reasonable that the width changes when the strings change, the
740 // height should not. We didn’t find the reason and didn’t manage
741 // to reproduce this behaviour within the unit tests. But anyway
742 // the call is not necessary, as mentioned earlier.
743}
744
745/** @brief Reloads all icons, adapting to the current color schema and
746 * widget style. */
747void ColorDialogPrivate::reloadIcons()
748{
749 m_currentIconThemeType = guessColorSchemeTypeFromWidget(q_pointer);
750
751 static const QStringList swatchBookIcons //
752 {QStringLiteral("paint-swatch"),
753 // For “symbolic” (monochromatic) vs “full-color” icons, see
754 // https://pointieststick.com/2023/08/12/how-all-this-icon-stuff-is-going-to-work-in-plasma-6/
755 QStringLiteral("palette"),
756 QStringLiteral("palette-symbolic")};
757 const int swatchBookIndex = //
758 m_tabWidget->indexOf(m_swatchBookWrapperWidget);
759 if (swatchBookIndex >= 0) {
760 m_tabWidget->setTabIcon(swatchBookIndex, //
761 qIconFromTheme(swatchBookIcons, //
762 QStringLiteral("color-swatch"),
763 m_currentIconThemeType));
764 }
765
766 static const QStringList hueFirstIcons //
767 {
768 QStringLiteral("color-mode-hue-shift-positive"),
769 };
770 const int hueFirstIndex = //
771 m_tabWidget->indexOf(m_hueFirstWrapperWidget);
772 if (hueFirstIndex >= 0) {
773 m_tabWidget->setTabIcon(hueFirstIndex, //
774 qIconFromTheme(hueFirstIcons, //
775 QStringLiteral("steering-wheel"),
776 m_currentIconThemeType));
777 }
778
779 static const QStringList lightnessFirstIcons //
780 {
781 QStringLiteral("brightness-high"),
782 };
783 const int lightnessFirstIndex = //
784 m_tabWidget->indexOf(m_lightnessFirstWrapperWidget);
785 if (lightnessFirstIndex >= 0) {
786 m_tabWidget->setTabIcon(lightnessFirstIndex, //
787 qIconFromTheme(lightnessFirstIcons, //
788 QStringLiteral("brightness-2"),
789 m_currentIconThemeType));
790 }
791
792 static const QStringList numericIcons //
793 {
794 QStringLiteral("black_sum"),
795 };
796 const int numericIndex = //
797 m_tabWidget->indexOf(m_numericalWidget);
798 if (numericIndex >= 0) {
799 m_tabWidget->setTabIcon(numericIndex, //
800 qIconFromTheme(numericIcons, //
801 QStringLiteral("123"),
802 m_currentIconThemeType));
803 }
804
805 // Gamut button for some spin boxes
806 static const QStringList gamutIconNames //
807 {
808 QStringLiteral("data-warning"),
809 QStringLiteral("dialog-warning-symbolic"),
810 };
811 const QIcon gamutIcon = qIconFromTheme(gamutIconNames, //
812 QStringLiteral("eye-exclamation"),
813 m_currentIconThemeType);
814 m_cielchD50SpinBoxGamutAction->setIcon(gamutIcon);
815 m_oklchSpinBoxGamutAction->setIcon(gamutIcon);
816
817 static const QStringList candidates //
818 {
819 QStringLiteral("color-picker"), //
820 QStringLiteral("gtk-color-picker"), //
821 QStringLiteral("tool_color_picker"), //
822 };
823 if (!m_screenColorPickerButton.isNull()) {
824 m_screenColorPickerButton->setIcon( //
825 qIconFromTheme(candidates, //
826 QStringLiteral("color-picker"),
827 m_currentIconThemeType));
828 }
829}
830
831/** @brief Basic initialization.
832 *
833 * @param colorSpace The color space within which this widget should operate.
834 * Can be created with @ref RgbColorSpaceFactory.
835 *
836 * Code that is shared between the various overloaded constructors.
837 *
838 * @todo The RTL layout is broken for @ref SwatchBook. Thought a stretch
839 * is added in the layout, the @ref SwatchBook stays left-aligned
840 * instead of right-aligned if there is too much space. Why doesn’t this
841 * right-align? For @ref m_wheelColorPicker and @ref m_chromaHueDiagram
842 * the same code works fine! */
843void ColorDialogPrivate::initialize(const QSharedPointer<PerceptualColor::RgbColorSpace> &colorSpace)
844{
845 // Do not show the “?” button in the window title. This button is displayed
846 // by default on widgets that inherit from QDialog. But we do not want the
847 // button because we do not provide What’s-This-help anyway, so having
848 // the button would be confusing.
849 q_pointer->setWindowFlag(Qt::WindowContextHelpButtonHint, false);
850
851 // initialize color space and its dependencies
852 m_rgbColorSpace = colorSpace;
853 m_wcsBasicColors = wcsBasicColors(colorSpace);
854 m_wcsBasicDefaultColor = m_wcsBasicColors.value(4, 2);
855
856 // create the graphical selectors
857 m_swatchBookBasicColors = new SwatchBook(m_rgbColorSpace, //
858 m_wcsBasicColors, //
859 Qt::Orientation::Horizontal);
860 auto swatchBookBasicColorsLayout = new QGridLayout();
861 auto swatchBookBasicColorsLayoutWidget = new QWidget();
862 swatchBookBasicColorsLayoutWidget->setLayout(swatchBookBasicColorsLayout);
863 swatchBookBasicColorsLayoutWidget->setContentsMargins(0, 0, 0, 0);
864 swatchBookBasicColorsLayout->setContentsMargins(0, 0, 0, 0);
865 swatchBookBasicColorsLayout->addWidget(m_swatchBookBasicColors);
866 swatchBookBasicColorsLayout->setRowStretch(1, 1);
867 swatchBookBasicColorsLayout->setColumnStretch(1, 1);
868 m_swatchBookHistory = new SwatchBook(m_rgbColorSpace, //
869 Swatches(), //
870 Qt::Orientation::Vertical);
871 auto swatchBookHistoryLayout = new QGridLayout();
872 auto swatchBookHistoryLayoutWidget = new QWidget();
873 swatchBookHistoryLayoutWidget->setLayout(swatchBookHistoryLayout);
874 swatchBookHistoryLayoutWidget->setContentsMargins(0, 0, 0, 0);
875 swatchBookHistoryLayout->setContentsMargins(0, 0, 0, 0);
876 swatchBookHistoryLayout->addWidget(m_swatchBookHistory);
877 swatchBookHistoryLayout->setRowStretch(1, 1);
878 swatchBookHistoryLayout->setColumnStretch(1, 1);
879 loadHistoryFromSettingsToSwatchBook();
880 m_swatchBookCustomColors = new SwatchBook( //
881 m_rgbColorSpace, //
882 Swatches(), //
883 Qt::Orientation::Horizontal | Qt::Orientation::Vertical);
884 m_swatchBookCustomColors->setEditable(true);
885 auto swatchBookCustomColorsLayout = new QGridLayout();
886 auto swatchBookCustomColorsLayoutWidget = new QWidget();
887 swatchBookCustomColorsLayoutWidget->setLayout(swatchBookCustomColorsLayout);
888 swatchBookCustomColorsLayoutWidget->setContentsMargins(0, 0, 0, 0);
889 swatchBookCustomColorsLayout->setContentsMargins(0, 0, 0, 0);
890 swatchBookCustomColorsLayout->addWidget(m_swatchBookCustomColors);
891 swatchBookCustomColorsLayout->setRowStretch(1, 1);
892 swatchBookCustomColorsLayout->setColumnStretch(1, 1);
893 loadCustomColorsFromSettingsToSwatchBook();
894 connect(m_swatchBookCustomColors, // sender
895 &SwatchBook::swatchGridChanged, // signal
896 this, // receiver
897 [this](const Swatches &newSwatches) {
898 m_settings.customColors.setValue(newSwatches.toQList());
899 });
900 m_swatchBookStack = new QStackedLayout();
901 m_swatchBookStack->addWidget(swatchBookBasicColorsLayoutWidget);
902 m_swatchBookStack->addWidget(swatchBookHistoryLayoutWidget);
903 m_swatchBookStack->addWidget(swatchBookCustomColorsLayoutWidget);
904 QHBoxLayout *swatchBookInnerLayout = new QHBoxLayout();
905 swatchBookInnerLayout->addLayout(m_swatchBookStack);
906 swatchBookInnerLayout->addStretch();
907 QVBoxLayout *swatchBookOuterLayout = new QVBoxLayout();
908 m_swatchBookSelector = new QComboBox();
909 m_swatchBookSelector->addItem(QString());
910 m_swatchBookSelector->addItem(QString());
911 m_swatchBookSelector->addItem(QString());
912 connect(m_swatchBookSelector, //
914 this,
915 [this](int i) {
916 switch (i) {
917 case 0:
918 m_swatchBookStack->setCurrentIndex(0);
919 m_settings.swatchBookPage.setValue( //
920 PerceptualSettings::SwatchBookPage::BasicColors);
921 break;
922 case 1:
923 m_swatchBookStack->setCurrentIndex(1);
924 m_settings.swatchBookPage.setValue( //
925 PerceptualSettings::SwatchBookPage::History);
926 break;
927 case 2:
928 m_swatchBookStack->setCurrentIndex(2);
929 m_settings.swatchBookPage.setValue( //
930 PerceptualSettings::SwatchBookPage::CustomColors);
931 break;
932 };
933 });
934 swatchBookOuterLayout->addWidget(m_swatchBookSelector);
935 swatchBookOuterLayout->addLayout(swatchBookInnerLayout);
936 swatchBookOuterLayout->addStretch();
937 m_swatchBookWrapperWidget = new QWidget();
938 m_swatchBookWrapperWidget->setLayout(swatchBookOuterLayout);
939 connect(&m_settings.history, //
940 &Setting<PerceptualSettings::ColorList>::valueChanged,
941 this,
942 &ColorDialogPrivate::loadHistoryFromSettingsToSwatchBook);
943 connect(&m_settings.customColors, //
944 &Setting<PerceptualSettings::ColorList>::valueChanged,
945 this,
946 &ColorDialogPrivate::loadCustomColorsFromSettingsToSwatchBook);
947 m_wheelColorPicker = new WheelColorPicker(m_rgbColorSpace);
948 m_hueFirstWrapperWidget = new QWidget;
949 QHBoxLayout *tempHueFirstLayout = new QHBoxLayout;
950 tempHueFirstLayout->addWidget(m_wheelColorPicker);
951 m_hueFirstWrapperWidget->setLayout(tempHueFirstLayout);
952
953 m_lchLightnessSelector = new GradientSlider(m_rgbColorSpace);
954 GenericColor blackCielchD50;
955 blackCielchD50.first = 0;
956 blackCielchD50.second = 0;
957 blackCielchD50.third = 0;
958 blackCielchD50.fourth = 1;
959 GenericColor whiteCielchD50;
960 whiteCielchD50.first = 100;
961 whiteCielchD50.second = 0;
962 whiteCielchD50.third = 0;
963 whiteCielchD50.fourth = 1;
964 m_lchLightnessSelector->setColors(blackCielchD50, whiteCielchD50);
965 m_chromaHueDiagram = new ChromaHueDiagram(m_rgbColorSpace);
966 QHBoxLayout *tempLightnesFirstLayout = new QHBoxLayout();
967 tempLightnesFirstLayout->addWidget(m_lchLightnessSelector);
968 tempLightnesFirstLayout->addWidget(m_chromaHueDiagram);
969 m_lightnessFirstWrapperWidget = new QWidget();
970 m_lightnessFirstWrapperWidget->setLayout(tempLightnesFirstLayout);
971
972 initializeScreenColorPicker();
973
974 m_tabWidget = new QTabWidget;
975 // It would be good to have bigger icons. Via QStyle::pixelMetrics()
976 // we could get values for this. QStyle::PM_LargeIconSize seems to large,
977 // be we could use std::max() with QStyle::PM_ToolBarIconSize,
978 // QStyle::PM_SmallIconSize, QStyle::PM_TabBarIconSize,
979 // QStyle::PM_ButtonIconSize. But the problem is a regression in Qt6
980 // (compared to Qt5) that breaks rendering of bigger icons via
981 // QTabWidget::iconSize(): https://bugreports.qt.io/browse/QTBUG-114849
982 // Furthermore, it appears that the MacOS style does not adjust the height
983 // of the tab bar to match the icon height. This causes larger icons to
984 // simply overflow, which looks like a rendering issue. Therefore,
985 // currently we stick with the default icons size for tab bars.
986 m_tabWidget->addTab(m_swatchBookWrapperWidget, QString());
987 m_swatchBookTabShortcut = new QShortcut(q_pointer);
988 connect(m_swatchBookTabShortcut, //
990 this,
991 [this]() {
992 m_tabWidget->setCurrentIndex( //
993 m_tabWidget->indexOf(m_swatchBookWrapperWidget));
994 });
995 connect(m_swatchBookTabShortcut, //
997 this,
998 [this]() {
999 m_tabWidget->setCurrentIndex( //
1000 m_tabWidget->indexOf(m_swatchBookWrapperWidget));
1001 });
1002
1003 m_tabWidget->addTab(m_hueFirstWrapperWidget, QString());
1004 m_hueFirstTabShortcut = new QShortcut(q_pointer);
1005 connect(m_hueFirstTabShortcut, //
1007 this,
1008 [this]() {
1009 m_tabWidget->setCurrentIndex( //
1010 m_tabWidget->indexOf(m_hueFirstWrapperWidget));
1011 });
1012 connect(m_hueFirstTabShortcut, //
1014 this,
1015 [this]() {
1016 m_tabWidget->setCurrentIndex( //
1017 m_tabWidget->indexOf(m_hueFirstWrapperWidget));
1018 });
1019
1020 m_tabWidget->addTab(m_lightnessFirstWrapperWidget, QString());
1021 m_lightnessFirstTabShortcut = new QShortcut(q_pointer);
1022 connect(m_lightnessFirstTabShortcut, //
1024 this,
1025 [this]() {
1026 m_tabWidget->setCurrentIndex( //
1027 m_tabWidget->indexOf(m_lightnessFirstWrapperWidget));
1028 });
1029 connect(m_lightnessFirstTabShortcut, //
1031 this,
1032 [this]() {
1033 m_tabWidget->setCurrentIndex( //
1034 m_tabWidget->indexOf(m_lightnessFirstWrapperWidget));
1035 });
1036
1037 m_tabTable.insert(&m_swatchBookWrapperWidget, //
1038 QStringLiteral("swatch"));
1039 m_tabTable.insert(&m_hueFirstWrapperWidget, //
1040 QStringLiteral("hue-based"));
1041 m_tabTable.insert(&m_lightnessFirstWrapperWidget, //
1042 QStringLiteral("lightness-based"));
1043 m_tabTable.insert(&m_numericalWidget, //
1044 QStringLiteral("numerical"));
1045 connect(m_tabWidget, //
1047 this, //
1048 &ColorDialogPrivate::saveCurrentTab);
1049
1050 // Create the ColorPatch
1051 m_colorPatch = new ColorPatch();
1052 m_colorPatch->setMinimumSize(m_colorPatch->minimumSizeHint() * 1.5);
1053
1054 QHBoxLayout *headerLayout = new QHBoxLayout();
1055 headerLayout->addWidget(m_colorPatch, 1);
1056 m_screenColorPickerButton->setSizePolicy(QSizePolicy::Minimum, // horizontal
1057 QSizePolicy::Minimum); // vertical
1058 headerLayout->addWidget(m_screenColorPickerButton,
1059 // Do not grow the cell in the direction
1060 // of the QBoxLayout:
1061 0,
1062 // No alignment: Fill the entire cell.
1063 Qt::Alignment());
1064
1065 // Create widget for the numerical values
1066 m_numericalWidget = initializeNumericPage();
1067 m_numericalTabShortcut = new QShortcut(q_pointer);
1068 connect(m_numericalTabShortcut, //
1070 this,
1071 [this]() {
1072 m_tabWidget->setCurrentIndex( //
1073 m_tabWidget->indexOf(m_numericalWidget));
1074 });
1075 connect(m_numericalTabShortcut, //
1077 this,
1078 [this]() {
1079 m_tabWidget->setCurrentIndex( //
1080 m_tabWidget->indexOf(m_numericalWidget));
1081 });
1082
1083 // Create layout for graphical and numerical widgets
1084 m_selectorLayout = new QHBoxLayout();
1085 m_selectorLayout->addWidget(m_tabWidget);
1086 m_selectorLayout->addWidget(m_numericalWidget);
1087
1088 // Create widgets for alpha value
1089 QHBoxLayout *m_alphaLayout = new QHBoxLayout();
1090 m_alphaGradientSlider = new GradientSlider(m_rgbColorSpace, //
1091 Qt::Orientation::Horizontal);
1092 m_alphaGradientSlider->setSingleStep(singleStepAlpha);
1093 m_alphaGradientSlider->setPageStep(pageStepAlpha);
1094 m_alphaSpinBox = new QDoubleSpinBox();
1095 m_alphaSpinBox->setAlignment(Qt::AlignmentFlag::AlignRight);
1096 m_alphaSpinBox->setMinimum(0);
1097 m_alphaSpinBox->setMaximum(100);
1098 // The suffix is set in retranslateUi.
1099 m_alphaSpinBox->setDecimals(decimals);
1100 m_alphaSpinBox->setSingleStep(singleStepAlpha * 100);
1101 // m_alphaSpinBox is of type QDoubleSpinBox which does not allow to
1102 // configure the pageStep.
1103 m_alphaLabel = new QLabel();
1104 m_alphaLabel->setBuddy(m_alphaSpinBox);
1105 m_alphaLayout->addWidget(m_alphaLabel);
1106 m_alphaLayout->addWidget(m_alphaGradientSlider);
1107 m_alphaLayout->addWidget(m_alphaSpinBox);
1108
1109 // Create the default buttons
1110 // We use standard buttons, because these standard buttons are
1111 // created by Qt and have automatically the correct icons and so on
1112 // (as designated in the current platform and widget style).
1113 // Though we use standard buttons, (later) we set the text manually to
1114 // get full control over the translation. Otherwise, loading a
1115 // different translation files than the user’s QLocale::system()
1116 // default locale would not update the standard button texts.
1117 m_buttonBox = new QDialogButtonBox();
1118 // NOTE We start with the OK button, and not with the Cancel button.
1119 // This is because apparently, the first button becomes the default
1120 // one (though Qt documentation says differently). If Cancel would
1121 // be the first, it would become the default button, which is not
1122 // what we want. (Even QPushButton::setDefault() will not change this
1123 // afterwards.)
1124 m_buttonOK = m_buttonBox->addButton(QDialogButtonBox::Ok);
1125 m_buttonCancel = m_buttonBox->addButton(QDialogButtonBox::Cancel);
1126 // The Qt documentation at
1127 // https://doc.qt.io/qt-5/qcoreapplication.html#installTranslator
1128 // says that Qt::LanguageChange events are only send to top-level
1129 // widgets. However, our experience is that also the QDialogButtonBox
1130 // receives Qt::LanguageChange events and reacts on it by updating
1131 // the user-visible string of all standard buttons. We do not want
1132 // to use custom buttons because of the advantages of standard
1133 // buttons that are described above. On the other hand, we do not
1134 // want Qt to change our string because we use our own translation
1135 // here.
1136 m_buttonBox->installEventFilter(&m_languageChangeEventFilter);
1137 m_buttonOK->installEventFilter(&m_languageChangeEventFilter);
1138 m_buttonCancel->installEventFilter(&m_languageChangeEventFilter);
1139 connect(m_buttonBox, // sender
1140 &QDialogButtonBox::accepted, // signal
1141 q_pointer, // receiver
1143 connect(m_buttonBox, // sender
1144 &QDialogButtonBox::rejected, // signal
1145 q_pointer, // receiver
1147
1148 // Create the main layout
1149 QVBoxLayout *tempMainLayout = new QVBoxLayout();
1150 tempMainLayout->addLayout(headerLayout);
1151 tempMainLayout->addLayout(m_selectorLayout);
1152 tempMainLayout->addLayout(m_alphaLayout);
1153 tempMainLayout->addWidget(m_buttonBox);
1154 q_pointer->setLayout(tempMainLayout);
1155
1156 // initialize signal-slot-connections
1157 connect(m_colorPatch, // sender
1158 &ColorPatch::colorChanged, // signal
1159 this, // receiver
1160 &ColorDialogPrivate::readColorPatchValue // slot
1161 );
1162 connect(m_swatchBookBasicColors, // sender
1163 &SwatchBook::currentColorChanged, // signal
1164 this, // receiver
1165 &ColorDialogPrivate::readSwatchBookBasicColorsValue // slot
1166 );
1167 connect(m_swatchBookHistory, // sender
1168 &SwatchBook::currentColorChanged, // signal
1169 this, // receiver
1170 &ColorDialogPrivate::readSwatchBookHistoryValue // slot
1171 );
1172 connect(m_swatchBookCustomColors, // sender
1173 &SwatchBook::currentColorChanged, // signal
1174 this, // receiver
1175 &ColorDialogPrivate::readSwatchBookCustomColorsValue // slot
1176 );
1177 connect(m_rgbSpinBox, // sender
1179 this, // receiver
1180 &ColorDialogPrivate::readRgbNumericValues // slot
1181 );
1182 connect(m_rgbLineEdit, // sender
1183 &QLineEdit::textChanged, // signal
1184 this, // receiver
1185 &ColorDialogPrivate::readRgbHexValues // slot
1186 );
1187 connect(m_rgbLineEdit, // sender
1188 &QLineEdit::editingFinished, // signal
1189 this, // receiver
1190 &ColorDialogPrivate::updateRgbHexButBlockSignals // slot
1191 );
1192 connect(m_hslSpinBox, // sender
1194 this, // receiver
1195 &ColorDialogPrivate::readHslNumericValues // slot
1196 );
1197 connect(m_hwbSpinBox, // sender
1199 this, // receiver
1200 &ColorDialogPrivate::readHwbNumericValues // slot
1201 );
1202 connect(m_hsvSpinBox, // sender
1204 this, // receiver
1205 &ColorDialogPrivate::readHsvNumericValues // slot
1206 );
1207 connect(m_cielchD50SpinBox, // sender
1209 this, // receiver
1210 &ColorDialogPrivate::readLchNumericValues // slot
1211 );
1212 connect(m_cielchD50SpinBox, // sender
1214 this, // receiver
1215 &ColorDialogPrivate::updateLchButBlockSignals // slot
1216 );
1217 connect(m_oklchSpinBox, // sender
1219 this, // receiver
1220 &ColorDialogPrivate::readOklchNumericValues // slot
1221 );
1222 connect(m_oklchSpinBox, // sender
1224 this, // receiver
1225 &ColorDialogPrivate::updateOklchButBlockSignals // slot
1226 );
1227 connect(m_lchLightnessSelector, // sender
1229 this, // receiver
1230 &ColorDialogPrivate::readLightnessValue // slot
1231 );
1232 connect(m_wheelColorPicker, // sender
1234 this, // receiver
1235 &ColorDialogPrivate::readWheelColorPickerValues // slot
1236 );
1237 connect(m_chromaHueDiagram, // sender
1239 this, // receiver
1240 &ColorDialogPrivate::readChromaHueDiagramValue // slot
1241 );
1242 connect(m_alphaGradientSlider, // sender
1244 this, // receiver
1245 &ColorDialogPrivate::updateColorPatch // slot
1246 );
1247 connect(m_alphaGradientSlider, // sender
1249 this, // receiver
1250 [this](const qreal newFraction) { // lambda
1251 const QSignalBlocker blocker(m_alphaSpinBox);
1252 m_alphaSpinBox->setValue(newFraction * 100);
1253 });
1254 connect(m_alphaSpinBox, // sender
1255 QOverload<double>::of(&QDoubleSpinBox::valueChanged), // signal
1256 this, // receiver
1257 [this](const double newValue) { // lambda
1258 // m_alphaGradientSlider has range [0, 1], while the signal
1259 // has range [0, 100]. This has to be adapted:
1260 m_alphaGradientSlider->setValue(newValue / 100);
1261 });
1262
1263 // Initialize the options
1264 q_pointer->setOptions(QColorDialog::ColorDialogOption::DontUseNativeDialog);
1265
1266 // We are setting the translated default window title here instead
1267 // of setting it within retranslateUi(). This is because also QColorDialog
1268 // does not update the window title on LanguageChange events (probably
1269 // to avoid confusion, because it’s difficult to tell exactly if the
1270 // library user did or did not explicitly change the window title.
1271 /*: @title:window Default window title. Same text as in QColorDialog */
1272 q_pointer->setWindowTitle(tr("Select color"));
1273
1274 // Enable size grip
1275 // As this dialog can indeed be resized, the size grip should
1276 // be enabled. So, users can see the little triangle at the
1277 // right bottom of the dialog (or the left bottom on a
1278 // right-to-left layout). So, the user will be aware
1279 // that he can indeed resize this dialog, which is
1280 // important as the users are used to the default
1281 // platform dialog, which often do not allow resizing. Therefore,
1282 // by default, QDialog::isSizeGripEnabled() should be true.
1283 // NOTE: Some widget styles like Oxygen or Breeze leave the size grip
1284 // widget invisible; nevertheless it reacts on mouse events. Other
1285 // widget styles indeed show the size grip widget, like Fusion or
1286 // QtCurve.
1287 q_pointer->setSizeGripEnabled(true);
1288
1289 // The q_pointer’s object is still not fully initialized at this point,
1290 // but it’s base class constructor has fully run; this should be enough
1291 // to use functionality based on QWidget, so we can use it as parent.
1292 m_cielchD50SpinBoxGamutAction = new QAction(q_pointer);
1293 connect(m_cielchD50SpinBoxGamutAction, // sender
1294 &QAction::triggered, // signal
1295 this, // receiver
1296 &ColorDialogPrivate::updateLchButBlockSignals // slot
1297 );
1298 m_oklchSpinBoxGamutAction = new QAction(q_pointer);
1299 connect(m_oklchSpinBoxGamutAction, // sender
1300 &QAction::triggered, // signal
1301 this, // receiver
1302 &ColorDialogPrivate::updateOklchButBlockSignals // slot
1303 );
1304 // However, here we hide the action because initially the
1305 // current color should be in-gamut, so no need for the gamut action
1306 // to be visible.
1307 m_cielchD50SpinBoxGamutAction->setVisible(false);
1308 m_cielchD50SpinBox->addActionButton( //
1309 m_cielchD50SpinBoxGamutAction, //
1310 QLineEdit::ActionPosition::TrailingPosition);
1311 m_oklchSpinBoxGamutAction->setVisible(false);
1312 m_oklchSpinBox->addActionButton( //
1313 m_oklchSpinBoxGamutAction, //
1314 QLineEdit::ActionPosition::TrailingPosition);
1315
1316 initializeTranslation(QCoreApplication::instance(),
1317 // An empty std::optional means: If in initialization
1318 // had been done yet, repeat this initialization.
1319 // If not, do a new initialization now with default
1320 // values.
1321 std::optional<QStringList>());
1322 retranslateUi();
1323
1324 reloadIcons();
1325#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
1326 connect(qGuiApp->styleHints(), // sender
1328 this, // receiver
1329 &ColorDialogPrivate::reloadIcons);
1330#endif
1331}
1332
1333/** @brief Constructor
1334 *
1335 * @param parent pointer to the parent widget, if any
1336 * @post The @ref currentColor property is set to a default value. */
1338 : QDialog(parent)
1339 , d_pointer(new ColorDialogPrivate(this))
1340{
1341 d_pointer->initialize(RgbColorSpaceFactory::createSrgb());
1342 setCurrentColor(d_pointer->defaultColor());
1343}
1344
1345/** @brief Constructor
1346 *
1347 * @param initial the initially chosen color of the dialog
1348 * @param parent pointer to the parent widget, if any
1349 * @post The object is constructed and @ref setCurrentColor() is called
1350 * with <em>initial</em>. See @ref setCurrentColor() for the modifications
1351 * that will be applied before setting the current color. Especially, as
1352 * this dialog is constructed by default without alpha support, the
1353 * alpha channel of <em>initial</em> is ignored and a fully opaque color is
1354 * used. */
1356 : QDialog(parent)
1357 , d_pointer(new ColorDialogPrivate(this))
1358{
1359 d_pointer->initialize(RgbColorSpaceFactory::createSrgb());
1360 // Calling setCurrentColor() guaranties to update all widgets
1361 // because it always sets a valid color, even when the color
1362 // parameter was invalid. As m_currentOpaqueColor is invalid
1363 // be default, and therefor different, setCurrentColor()
1364 // guaranties to update all widgets.
1365 setCurrentColor(initial);
1366}
1367
1368/** @brief Constructor
1369 *
1370 * @param colorSpace The color space within which this widget should operate.
1371 * Can be created with @ref RgbColorSpaceFactory.
1372 * @param parent pointer to the parent widget, if any
1373 * @post The @ref currentColor property is set to a default value. */
1375 : QDialog(parent)
1376 , d_pointer(new ColorDialogPrivate(this))
1377{
1378 d_pointer->initialize(colorSpace);
1379 setCurrentColor(d_pointer->defaultColor());
1380}
1381
1382/** @brief Returns the default value for @ref ColorDialog::currentColor.
1383 *
1384 * @returns the default value for @ref ColorDialog::currentColor. Comes from
1385 * the history (if any), or otherwise from @ref m_wcsBasicDefaultColor. */
1386QColor ColorDialogPrivate::defaultColor() const
1387{
1388 const auto history = m_settings.history.value();
1389 return history.value(0, m_wcsBasicDefaultColor);
1390}
1391
1392/** @brief Constructor
1393 *
1394 * @param colorSpace The color space within which this widget should operate.
1395 * Can be created with @ref RgbColorSpaceFactory.
1396 * @param initial the initially chosen color of the dialog
1397 * @param parent pointer to the parent widget, if any
1398 * @post The object is constructed and @ref setCurrentColor() is called
1399 * with <em>initial</em>. See @ref setCurrentColor() for the modifications
1400 * that will be applied before setting the current color. Especially, as
1401 * this dialog is constructed by default without alpha support, the
1402 * alpha channel of <em>initial</em> is ignored and a fully opaque color is
1403 * used. */
1405 : QDialog(parent)
1406 , d_pointer(new ColorDialogPrivate(this))
1407{
1408 d_pointer->initialize(colorSpace);
1409 // Calling setCurrentColor() guaranties to update all widgets
1410 // because it always sets a valid color, even when the color
1411 // parameter was invalid. As m_currentOpaqueColor is invalid
1412 // be default, and therefor different, setCurrentColor()
1413 // guaranties to update all widgets.
1414 setCurrentColor(initial);
1415}
1416
1417/** @brief Destructor */
1419{
1420 // All the layouts and widgets used here are automatically child widgets
1421 // of this dialog widget. Therefor they are deleted automatically.
1422 // Also m_rgbColorSpace is of type RgbColorSpace(), which
1423 // inherits from QObject, and is a child of this dialog widget, does
1424 // not need to be deleted manually.
1425}
1426
1427/** @brief Constructor
1428 *
1429 * @param backLink Pointer to the object from which <em>this</em> object
1430 * is the private implementation. */
1431ColorDialogPrivate::ColorDialogPrivate(ColorDialog *backLink)
1432 : q_pointer(backLink)
1433{
1434}
1435
1436// No documentation here (documentation of properties
1437// and its getters are in the header)
1439{
1440 QColor temp = d_pointer->m_currentOpaqueColorRgb.rgbQColor;
1441 temp.setAlphaF( //
1442 static_cast<QColorFloatType>( //
1443 d_pointer->m_alphaGradientSlider->value()));
1444 return temp;
1445}
1446
1447/** @brief Setter for @ref currentColor property.
1448 *
1449 * @param color the new color
1450 * @post The property @ref currentColor is adapted as follows:
1451 * - If <em>color</em> is not valid, <tt>Qt::black</tt> is used instead.
1452 * - If <em>color</em>’s <tt>QColor::Spec</tt> is <em>not</em>
1453 * <tt>QColor::Spec::Rgb</tt> then it will be converted silently
1454 * to <tt>QColor::Spec::Rgb</tt>
1455 * - The RGB part of @ref currentColor will be the RGB part of <tt>color</tt>.
1456 * - The alpha channel of @ref currentColor will be the alpha channel
1457 * of <tt>color</tt> if at the moment of the function call
1458 * the <tt>QColorDialog::ColorDialogOption::ShowAlphaChannel</tt> option is
1459 * set. It will be fully opaque otherwise. */
1461{
1462 QColor temp;
1463 if (color.isValid()) {
1464 // Make sure that the QColor::spec() is QColor::Spec::Rgb.
1465 temp = color.toRgb();
1466 } else {
1467 // For invalid colors same behavior as QColorDialog
1468 temp = QColor(Qt::black);
1469 }
1470 if (testOption(ColorDialog::ColorDialogOption::ShowAlphaChannel)) {
1471 d_pointer->m_alphaGradientSlider->setValue( //
1472 static_cast<double>(temp.alphaF()));
1473 } else {
1474 d_pointer->m_alphaGradientSlider->setValue(1);
1475 }
1476 // No need to update m_alphaSpinBox as this is done
1477 // automatically by signals emitted by m_alphaGradientSlider.
1478 const RgbColor myRgbColor = RgbColor::fromRgbQColor(temp);
1479 d_pointer->setCurrentOpaqueColor(myRgbColor, nullptr);
1480}
1481
1482/** @brief Opens the dialog and connects its @ref colorSelected() signal to
1483 * the slot specified by receiver and member.
1484 *
1485 * The signal will be disconnected from the slot when the dialog is closed.
1486 *
1487 * Example:
1488 * @snippet testcolordialog.cpp ColorDialog Open
1489 *
1490 * @param receiver the object that will receive the @ref colorSelected() signal
1491 * @param member the slot that will receive the @ref colorSelected() signal */
1492void ColorDialog::open(QObject *receiver, const char *member)
1493{
1494 connect(this, // sender
1495 SIGNAL(colorSelected(QColor)), // signal
1496 receiver, // receiver
1497 member); // slot
1498 d_pointer->m_receiverToBeDisconnected = receiver;
1499 d_pointer->m_memberToBeDisconnected = member;
1500 QDialog::open();
1501}
1502
1503/** @brief Updates the color patch widget
1504 *
1505 * @post The color patch widget will show the color
1506 * of @ref m_currentOpaqueColorRgb and the alpha
1507 * value of @ref m_alphaGradientSlider. */
1508void ColorDialogPrivate::updateColorPatch()
1509{
1510 QColor tempRgbQColor = m_currentOpaqueColorRgb.rgbQColor;
1511 tempRgbQColor.setAlphaF( //
1512 static_cast<QColorFloatType>(m_alphaGradientSlider->value()));
1513 m_colorPatch->setColor(tempRgbQColor);
1514}
1515
1516/** @brief Overloaded function. */
1517void ColorDialogPrivate::setCurrentOpaqueColor(const QHash<PerceptualColor::ColorModel, PerceptualColor::GenericColor> &abs, QWidget *const ignoreWidget)
1518{
1519 const auto cielchD50 = abs.value(ColorModel::CielchD50);
1520 const auto rgb1 = m_rgbColorSpace->fromCielchD50ToRgb1(cielchD50);
1521 const auto rgb255 = GenericColor(rgb1.first * 255, //
1522 rgb1.second * 255,
1523 rgb1.third * 255);
1524 const auto rgbColor = RgbColor::fromRgb255(rgb255);
1525 setCurrentOpaqueColor(abs, rgbColor, ignoreWidget);
1526}
1527
1528/** @brief Overloaded function. */
1529void ColorDialogPrivate::setCurrentOpaqueColor(const PerceptualColor::RgbColor &rgb, QWidget *const ignoreWidget)
1530{
1531 const auto temp = rgb.rgb255;
1532 const QColor myQColor = QColor::fromRgbF( //
1533 static_cast<QColorFloatType>(temp.first / 255.), //
1534 static_cast<QColorFloatType>(temp.second / 255.), //
1535 static_cast<QColorFloatType>(temp.third / 255.));
1536 const auto cielchD50 = GenericColor( //
1537 m_rgbColorSpace->toCielchD50(myQColor.rgba64()));
1538 setCurrentOpaqueColor( //
1539 AbsoluteColor::allConversions(ColorModel::CielchD50, cielchD50),
1540 rgb,
1541 ignoreWidget);
1542}
1543
1544/** @brief Updates @ref m_currentOpaqueColorAbs, @ref m_currentOpaqueColorRgb
1545 * and affected widgets.
1546 *
1547 * @param abs The new color in absolute color models
1548 * @param rgb The new color in RGB and RGB-derived models (profile-dependant)
1549 *
1550 * @param ignoreWidget A widget that should <em>not</em> be updated. Or
1551 * <tt>nullptr</tt> to update <em>all</em> widgets.
1552 *
1553 * @post If this function is called recursively, nothing happens. Else
1554 * the color is moved into the gamut, then @ref m_currentOpaqueColorAbs and
1555 * @ref m_currentOpaqueColorRgb are updated, and the corresponding widgets
1556 * are updated (except the widget specified to be ignored – if any).
1557 *
1558 * @note Recursive functions calls are ignored. This is useful, because you
1559 * can connect signals from various widgets to this slot without having to
1560 * worry about infinite recursions. */
1561void ColorDialogPrivate::setCurrentOpaqueColor(const QHash<PerceptualColor::ColorModel, PerceptualColor::GenericColor> &abs,
1562 const PerceptualColor::RgbColor &rgb,
1563 QWidget *const ignoreWidget)
1564{
1565 const bool isIdentical = //
1566 (abs == m_currentOpaqueColorAbs) && (rgb == m_currentOpaqueColorRgb);
1567 if (m_isColorChangeInProgress || isIdentical) {
1568 // Nothing to do!
1569 return;
1570 }
1571
1572 // If we have really some work to do, block recursive calls
1573 // of this function
1574 m_isColorChangeInProgress = true;
1575
1576 // Save currentColor() for later comparison
1577 // Using currentColor() makes sure correct alpha treatment!
1578 QColor oldQColor = q_pointer->currentColor();
1579
1580 // Update m_currentOpaqueColor
1581 m_currentOpaqueColorAbs = abs;
1582 m_currentOpaqueColorRgb = rgb;
1583
1584 // Update basic colors swatch book
1585 if (m_swatchBookBasicColors != ignoreWidget) {
1586 m_swatchBookBasicColors->setCurrentColor( //
1587 m_currentOpaqueColorRgb.rgbQColor);
1588 }
1589
1590 // Update history swatch book
1591 if (m_swatchBookHistory != ignoreWidget) {
1592 m_swatchBookHistory->setCurrentColor(m_currentOpaqueColorRgb.rgbQColor);
1593 }
1594
1595 // Update custom colors swatch book
1596 if (m_swatchBookCustomColors != ignoreWidget) {
1597 m_swatchBookCustomColors->setCurrentColor(m_currentOpaqueColorRgb.rgbQColor);
1598 }
1599
1600 // Update RGB widget
1601 if (m_rgbSpinBox != ignoreWidget) {
1602 m_rgbSpinBox->setSectionValues( //
1603 m_currentOpaqueColorRgb.rgb255.toQList3());
1604 }
1605
1606 // Update HSL widget
1607 if (m_hslSpinBox != ignoreWidget) {
1608 m_hslSpinBox->setSectionValues( //
1609 m_currentOpaqueColorRgb.hsl.toQList3());
1610 }
1611
1612 // Update HWB widget
1613 if (m_hwbSpinBox != ignoreWidget) {
1614 m_hwbSpinBox->setSectionValues( //
1615 m_currentOpaqueColorRgb.hwb.toQList3());
1616 }
1617
1618 // Update HSV widget
1619 if (m_hsvSpinBox != ignoreWidget) {
1620 m_hsvSpinBox->setSectionValues( //
1621 m_currentOpaqueColorRgb.hsv.toQList3());
1622 }
1623
1624 // Update CIELCH-D50 widget
1625 const GenericColor cielchD50 = m_currentOpaqueColorAbs.value(ColorModel::CielchD50);
1626 if (m_cielchD50SpinBox != ignoreWidget) {
1627 m_cielchD50SpinBox->setSectionValues(cielchD50.toQList3());
1628 }
1629
1630 // Update Oklch widget
1631 const auto oklch = m_currentOpaqueColorAbs.value(ColorModel::OklchD65);
1632 if (m_oklchSpinBox != ignoreWidget) {
1633 m_oklchSpinBox->setSectionValues(oklch.toQList3());
1634 }
1635
1636 // Update RGB hex widget
1637 if (m_rgbLineEdit != ignoreWidget) {
1638 updateRgbHexButBlockSignals();
1639 }
1640
1641 // Update lightness selector
1642 if (m_lchLightnessSelector != ignoreWidget) {
1643 m_lchLightnessSelector->setValue( //
1644 cielchD50.first / static_cast<qreal>(100));
1645 }
1646
1647 // Update chroma-hue diagram
1648 if (m_chromaHueDiagram != ignoreWidget) {
1649 m_chromaHueDiagram->setCurrentColorCielchD50(cielchD50);
1650 }
1651
1652 // Update wheel color picker
1653 if (m_wheelColorPicker != ignoreWidget) {
1654 m_wheelColorPicker->setCurrentColorCielchD50(cielchD50);
1655 }
1656
1657 // Update alpha gradient slider
1658 if (m_alphaGradientSlider != ignoreWidget) {
1659 GenericColor tempColor;
1660 tempColor.first = cielchD50.first;
1661 tempColor.second = cielchD50.second;
1662 tempColor.third = cielchD50.third;
1663 tempColor.fourth = 0;
1664 m_alphaGradientSlider->setFirstColorCieLchD50A(tempColor);
1665 tempColor.fourth = 1;
1666 m_alphaGradientSlider->setSecondColorCieLchD50A(tempColor);
1667 }
1668
1669 // Update widgets that take alpha information
1670 if (m_colorPatch != ignoreWidget) {
1671 updateColorPatch();
1672 }
1673
1674 // Emit signal currentColorChanged() only if necessary
1675 if (q_pointer->currentColor() != oldQColor) {
1676 Q_EMIT q_pointer->currentColorChanged(q_pointer->currentColor());
1677 }
1678
1679 // End of this function. Unblock recursive
1680 // function calls before returning.
1681 m_isColorChangeInProgress = false;
1682}
1683
1684/** @brief Reads the value from the lightness selector in the dialog and
1685 * updates the dialog accordingly. */
1686void ColorDialogPrivate::readLightnessValue()
1687{
1688 if (m_isColorChangeInProgress) {
1689 // Nothing to do!
1690 return;
1691 }
1692 auto cielchD50 = m_currentOpaqueColorAbs.value(ColorModel::CielchD50);
1693 cielchD50.first = m_lchLightnessSelector->value() * 100;
1694 cielchD50 = GenericColor( //
1695 m_rgbColorSpace->reduceCielchD50ChromaToFitIntoGamut(cielchD50));
1696 setCurrentOpaqueColor( //
1697 AbsoluteColor::allConversions(ColorModel::CielchD50, cielchD50), //
1698 m_lchLightnessSelector);
1699}
1700
1701/** @brief Reads the HSL numbers in the dialog and
1702 * updates the dialog accordingly. */
1703void ColorDialogPrivate::readHslNumericValues()
1704{
1705 if (m_isColorChangeInProgress) {
1706 // Nothing to do!
1707 return;
1708 }
1709 const auto temp = RgbColor::fromHsl( //
1710 GenericColor(m_hslSpinBox->sectionValues()));
1711 setCurrentOpaqueColor(temp, m_hslSpinBox);
1712}
1713
1714/** @brief Reads the HWB numbers in the dialog and
1715 * updates the dialog accordingly. */
1716void ColorDialogPrivate::readHwbNumericValues()
1717{
1718 if (m_isColorChangeInProgress) {
1719 // Nothing to do!
1720 return;
1721 }
1722 const auto temp = RgbColor::fromHwb( //
1723 GenericColor(m_hwbSpinBox->sectionValues()));
1724 setCurrentOpaqueColor(temp, m_hwbSpinBox);
1725}
1726
1727/** @brief Reads the HSV numbers in the dialog and
1728 * updates the dialog accordingly. */
1729void ColorDialogPrivate::readHsvNumericValues()
1730{
1731 if (m_isColorChangeInProgress) {
1732 // Nothing to do!
1733 return;
1734 }
1735 const auto temp = RgbColor::fromHsv( //
1736 GenericColor(m_hsvSpinBox->sectionValues()));
1737 setCurrentOpaqueColor(temp, m_hsvSpinBox);
1738}
1739
1740/** @brief Reads the decimal RGB numbers in the dialog and
1741 * updates the dialog accordingly. */
1742void ColorDialogPrivate::readRgbNumericValues()
1743{
1744 if (m_isColorChangeInProgress) {
1745 // Nothing to do!
1746 return;
1747 }
1748 const auto temp = RgbColor::fromRgb255( //
1749 GenericColor(m_rgbSpinBox->sectionValues()));
1750 setCurrentOpaqueColor(temp, m_rgbSpinBox);
1751}
1752
1753/** @brief Reads the color of the color patch, and
1754 * updates the dialog accordingly. */
1755void ColorDialogPrivate::readColorPatchValue()
1756{
1757 if (m_isColorChangeInProgress) {
1758 // Nothing to do!
1759 return;
1760 }
1761 const QColor temp = m_colorPatch->color();
1762 if (!temp.isValid()) {
1763 // No color is currently selected!
1764 return;
1765 }
1766 const auto myRgbColor = RgbColor::fromRgbQColor(temp);
1767 setCurrentOpaqueColor(myRgbColor, m_colorPatch);
1768}
1769
1770/** @brief Reads the color of the basic colors widget, and (if any)
1771 * updates the dialog accordingly. */
1772void ColorDialogPrivate::readSwatchBookBasicColorsValue()
1773{
1774 if (m_isColorChangeInProgress) {
1775 // Nothing to do!
1776 return;
1777 }
1778 const QColor temp = m_swatchBookBasicColors->currentColor();
1779 if (!temp.isValid()) {
1780 // No color is currently selected!
1781 return;
1782 }
1783 const auto myRgbColor = RgbColor::fromRgbQColor(temp);
1784 setCurrentOpaqueColor(myRgbColor, m_swatchBookBasicColors);
1785}
1786
1787/** @brief Reads the color of the history widget, and (if any)
1788 * updates the dialog accordingly. */
1789void ColorDialogPrivate::readSwatchBookHistoryValue()
1790{
1791 if (m_isColorChangeInProgress) {
1792 // Nothing to do!
1793 return;
1794 }
1795 const QColor temp = m_swatchBookHistory->currentColor();
1796 if (!temp.isValid()) {
1797 // No color is currently selected!
1798 return;
1799 }
1800 const auto myRgbColor = RgbColor::fromRgbQColor(temp);
1801 setCurrentOpaqueColor(myRgbColor, m_swatchBookHistory);
1802}
1803
1804/**
1805 * @brief Reads the color of the custom colors widget, and (if any)
1806 * updates the dialog accordingly.
1807 */
1808void ColorDialogPrivate::readSwatchBookCustomColorsValue()
1809{
1810 if (m_isColorChangeInProgress) {
1811 // Nothing to do!
1812 return;
1813 }
1814 const QColor temp = m_swatchBookCustomColors->currentColor();
1815 if (!temp.isValid()) {
1816 // No color is currently selected!
1817 return;
1818 }
1819 const auto myRgbColor = RgbColor::fromRgbQColor(temp);
1820 setCurrentOpaqueColor(myRgbColor, m_swatchBookCustomColors);
1821}
1822
1823/** @brief Reads the color of the @ref WheelColorPicker in the dialog and
1824 * updates the dialog accordingly. */
1825void ColorDialogPrivate::readWheelColorPickerValues()
1826{
1827 if (m_isColorChangeInProgress) {
1828 // Nothing to do!
1829 return;
1830 }
1831 const auto cielchD50 = GenericColor(m_wheelColorPicker->currentColorCielchD50());
1832 setCurrentOpaqueColor( //
1833 AbsoluteColor::allConversions(ColorModel::CielchD50, cielchD50),
1834 m_wheelColorPicker);
1835}
1836
1837/** @brief Reads the color of the @ref ChromaHueDiagram in the dialog and
1838 * updates the dialog accordingly. */
1839void ColorDialogPrivate::readChromaHueDiagramValue()
1840{
1841 if (m_isColorChangeInProgress) {
1842 // Nothing to do!
1843 return;
1844 }
1845 const auto cielchD50 = GenericColor(m_chromaHueDiagram->currentColorCielchD50());
1846 setCurrentOpaqueColor( //
1847 AbsoluteColor::allConversions(ColorModel::CielchD50, cielchD50),
1848 m_chromaHueDiagram);
1849}
1850
1851/** @brief Reads the hexadecimal RGB numbers in the dialog and
1852 * updates the dialog accordingly. */
1853void ColorDialogPrivate::readRgbHexValues()
1854{
1855 if (m_isColorChangeInProgress) {
1856 // Nothing to do!
1857 return;
1858 }
1859 QString temp = m_rgbLineEdit->text();
1860 if (!temp.startsWith(QStringLiteral(u"#"))) {
1861 temp = QStringLiteral(u"#") + temp;
1862 }
1863 QColor rgb;
1864 rgb.setNamedColor(temp);
1865 if (rgb.isValid()) {
1866 const auto myRgbColor = RgbColor::fromRgbQColor(rgb);
1867 setCurrentOpaqueColor(myRgbColor, m_rgbLineEdit);
1868 } else {
1869 m_isDirtyRgbLineEdit = true;
1870 }
1871}
1872
1873/** @brief Updates the RGB Hex widget to @ref m_currentOpaqueColorRgb.
1874 *
1875 * @post The @ref m_rgbLineEdit gets the value of @ref m_currentOpaqueColorRgb.
1876 * During this operation, all signals of @ref m_rgbLineEdit are blocked. */
1877void ColorDialogPrivate::updateRgbHexButBlockSignals()
1878{
1879 QSignalBlocker mySignalBlocker(m_rgbLineEdit);
1880
1881 // m_currentOpaqueColor is supposed to be always in-gamut. However,
1882 // because of rounding issues, a conversion to an unbounded RGB
1883 // color could result in an invalid color. Therefore, we must
1884 // use a conversion to a _bounded_ RGB color.
1885 const auto &rgbFloat = m_currentOpaqueColorRgb.rgb255;
1886
1887 // We cannot rely on the convenient QColor.name() because this function
1888 // seems to use floor() instead of round(), which does not make sense in
1889 // our dialog, and it would be inconsistent with the other widgets
1890 // of the dialog. Therefore, we have to round explicitly (to integers):
1891 // This format string provides a non-localized format!
1892 // Format of the numbers:
1893 // 1) The number itself
1894 // 2) The minimal field width (2 digits)
1895 // 3) The base of the number representation (16, hexadecimal)
1896 // 4) The fill character (leading zero)
1897 const QString hexString = //
1898 QStringLiteral(u"#%1%2%3")
1899 .arg(qBound(0, qRound(rgbFloat.first), 255), //
1900 2, //
1901 16, //
1902 QChar::fromLatin1('0'))
1903 .arg(qBound(0, qRound(rgbFloat.second), 255), //
1904 2, //
1905 16, //
1906 QChar::fromLatin1('0'))
1907 .arg(qBound(0, qRound(rgbFloat.third), 255), //
1908 2, //
1909 16, //
1910 QChar::fromLatin1('0'))
1911 .toUpper(); // Convert to upper case
1912 m_rgbLineEdit->setText(hexString);
1913}
1914
1915/** @brief Updates the LCH spin box to @ref m_currentOpaqueColorAbs.
1916 *
1917 * @post The @ref m_cielchD50SpinBox gets the value of
1918 * @ref m_currentOpaqueColorAbs. During this operation, all signals of
1919 * @ref m_cielchD50SpinBox are blocked. */
1920void ColorDialogPrivate::updateLchButBlockSignals()
1921{
1922 QSignalBlocker mySignalBlocker(m_cielchD50SpinBox);
1923 const auto cielchD50 = m_currentOpaqueColorAbs.value(ColorModel::CielchD50);
1924 m_cielchD50SpinBox->setSectionValues(cielchD50.toQList3());
1925 m_cielchD50SpinBoxGamutAction->setVisible(false);
1926}
1927
1928/** @brief Updates the Oklch spin box to @ref m_currentOpaqueColorAbs.
1929 *
1930 * @post The @ref m_oklchSpinBox gets the value
1931 * of @ref m_currentOpaqueColorAbs. During this operation,
1932 * all signals of @ref m_oklchSpinBox are blocked. */
1933void ColorDialogPrivate::updateOklchButBlockSignals()
1934{
1935 QSignalBlocker mySignalBlocker(m_oklchSpinBox);
1936 const auto oklch = m_currentOpaqueColorAbs.value(ColorModel::OklchD65);
1937 m_oklchSpinBox->setSectionValues(oklch.toQList3());
1938 m_oklchSpinBoxGamutAction->setVisible(false);
1939}
1940
1941/** @brief If no @ref m_isColorChangeInProgress, reads the LCH numbers
1942 * in the dialog and updates the dialog accordingly. */
1943void ColorDialogPrivate::readLchNumericValues()
1944{
1945 if (m_isColorChangeInProgress) {
1946 // Nothing to do!
1947 return;
1948 }
1949 const GenericColor lchValues = GenericColor(m_cielchD50SpinBox->sectionValues());
1950 if (m_rgbColorSpace->isCielchD50InGamut(lchValues)) {
1951 m_cielchD50SpinBoxGamutAction->setVisible(false);
1952 } else {
1953 m_cielchD50SpinBoxGamutAction->setVisible(true);
1954 }
1955 const auto myColor = GenericColor( //
1956 m_rgbColorSpace->reduceCielchD50ChromaToFitIntoGamut(lchValues));
1957 setCurrentOpaqueColor( //
1958 AbsoluteColor::allConversions(ColorModel::CielchD50, myColor),
1959 // widget that will ignored during updating:
1960 m_cielchD50SpinBox);
1961}
1962
1963/** @brief If no @ref m_isColorChangeInProgress, reads the Oklch numbers
1964 * in the dialog and updates the dialog accordingly. */
1965void ColorDialogPrivate::readOklchNumericValues()
1966{
1967 if (m_isColorChangeInProgress) {
1968 // Nothing to do!
1969 return;
1970 }
1971 // Get final color (in necessary moving the original color into gamut).
1972 // TODO xxx This code moves into gamut based on the Cielch-D50 instead of
1973 // the Oklch gamut. This leads to wrong results, because Oklch hue is not
1974 // guaranteed to be respected. Use actually Oklch to move into gamut!
1975 GenericColor originalOklch;
1976 originalOklch.first = m_oklchSpinBox->sectionValues().value(0);
1977 originalOklch.second = m_oklchSpinBox->sectionValues().value(1);
1978 originalOklch.third = m_oklchSpinBox->sectionValues().value(2);
1979 if (m_rgbColorSpace->isOklchInGamut(originalOklch)) {
1980 m_oklchSpinBoxGamutAction->setVisible(false);
1981 } else {
1982 m_oklchSpinBoxGamutAction->setVisible(true);
1983 }
1984 const auto inGamutOklch = GenericColor( //
1985 m_rgbColorSpace->reduceOklchChromaToFitIntoGamut(originalOklch));
1986 const auto inGamutColor = //
1987 AbsoluteColor::allConversions(ColorModel::OklchD65, inGamutOklch);
1988 setCurrentOpaqueColor(inGamutColor,
1989 // widget that will ignored during updating:
1990 m_oklchSpinBox);
1991}
1992
1993/** @brief Try to initialize the screen color picker feature.
1994 *
1995 * @post If supported, @ref m_screenColorPickerButton
1996 * is created. Otherwise, it stays <tt>nullptr</tt>. */
1997void ColorDialogPrivate::initializeScreenColorPicker()
1998{
1999 auto screenPicker = new ScreenColorPicker(q_pointer);
2000 if (!screenPicker->isAvailable()) {
2001 return;
2002 }
2003 m_screenColorPickerButton = new QToolButton;
2004 screenPicker->setParent(m_screenColorPickerButton); // For better support
2005 connect(m_screenColorPickerButton,
2007 screenPicker,
2008 // Default capture by reference, but screenPicker by value
2009 [&, screenPicker]() {
2010 const auto myColor = q_pointer->currentColor();
2011 // TODO Restore QColor exactly, but could potentially produce
2012 // rounding errors: If original MultiColor was derived form
2013 // LCH, it is not guaranteed that the new MultiColor derived
2014 // from this QColor will not have rounding errors for LCH.
2015 screenPicker->startPicking( //
2016 fromFloatingToEightBit(myColor.redF()), //
2017 fromFloatingToEightBit(myColor.greenF()), //
2018 fromFloatingToEightBit(myColor.blueF()));
2019 });
2020 connect(screenPicker, //
2021 &ScreenColorPicker::newColor, //
2022 q_pointer, //
2023 [this](const double red, const double green, const double blue, const bool isSRgbGuaranteed) {
2024 Q_UNUSED(isSRgbGuaranteed) // BUG Currently, there is no color
2025 // management for the result of the screen color picking.
2026 // Instead, we should assume probably that the value is sRGB
2027 // and convert it into the actual working color space of this
2028 // widget (which might happen to be also sRGB, but could also
2029 // be different).
2030 const GenericColor rgb255 //
2031 {qBound<double>(0, red * 255, 255), //
2032 qBound<double>(0, green * 255, 255),
2033 qBound<double>(0, blue * 255, 255)};
2034 setCurrentOpaqueColor(RgbColor::fromRgb255(rgb255), nullptr);
2035 });
2036}
2037
2038/** @brief Initialize the numeric input widgets of this dialog.
2039 * @returns A pointer to a new widget that has the other, numeric input
2040 * widgets as child widgets. */
2041QWidget *ColorDialogPrivate::initializeNumericPage()
2042{
2043 // Create RGB MultiSpinBox
2044 {
2045 m_rgbSpinBox = new MultiSpinBox();
2046 QList<MultiSpinBoxSection> rgbSections;
2047 MultiSpinBoxSection mySection;
2048 mySection.setDecimals(decimals);
2049 mySection.setMinimum(0);
2050 mySection.setMaximum(255);
2051 // R
2052 mySection.setPrefix(QString());
2053 mySection.setSuffix(m_multispinboxSectionSeparator);
2054 rgbSections.append(mySection);
2055 // G
2056 mySection.setPrefix(m_multispinboxSectionSeparator);
2057 mySection.setSuffix(m_multispinboxSectionSeparator);
2058 rgbSections.append(mySection);
2059 // B
2060 mySection.setPrefix(m_multispinboxSectionSeparator);
2061 mySection.setSuffix(QString());
2062 rgbSections.append(mySection);
2063 // Not setting prefix/suffix here. This will be done in retranslateUi()…
2064 m_rgbSpinBox->setSectionConfigurations(rgbSections);
2065 }
2066
2067 // Create widget for the hex style color representation
2068 {
2069 m_rgbLineEdit = new QLineEdit();
2070 m_rgbLineEdit->setMaxLength(7);
2071 QRegularExpression tempRegularExpression( //
2072 QStringLiteral(u"#?[0-9A-Fa-f]{0,6}"));
2073 QRegularExpressionValidator *validator = new QRegularExpressionValidator( //
2074 tempRegularExpression, //
2075 q_pointer);
2076 m_rgbLineEdit->setValidator(validator);
2077 }
2078
2079 // Create HSL spin box
2080 {
2081 m_hslSpinBox = new MultiSpinBox();
2082 QList<MultiSpinBoxSection> hslSections;
2083 MultiSpinBoxSection mySection;
2084 mySection.setDecimals(decimals);
2085 // H
2086 mySection.setMinimum(0);
2087 mySection.setMaximum(360);
2088 mySection.setWrapping(true);
2089 hslSections.append(mySection);
2090 // S
2091 mySection.setMinimum(0);
2092 mySection.setMaximum(100);
2093 mySection.setWrapping(false);
2094 hslSections.append(mySection);
2095 // L
2096 mySection.setMinimum(0);
2097 mySection.setMaximum(100);
2098 mySection.setWrapping(false);
2099 hslSections.append(mySection);
2100 // Not setting prefix/suffix here. This will be done in retranslateUi()…
2101 m_hslSpinBox->setSectionConfigurations(hslSections);
2102 }
2103
2104 // Create HWB spin box
2105 {
2106 m_hwbSpinBox = new MultiSpinBox();
2107 QList<MultiSpinBoxSection> hwbSections;
2108 MultiSpinBoxSection mySection;
2109 mySection.setDecimals(decimals);
2110 // H
2111 mySection.setMinimum(0);
2112 mySection.setMaximum(360);
2113 mySection.setWrapping(true);
2114 hwbSections.append(mySection);
2115 // W
2116 mySection.setMinimum(0);
2117 mySection.setMaximum(100);
2118 mySection.setWrapping(false);
2119 hwbSections.append(mySection);
2120 // B
2121 mySection.setMinimum(0);
2122 mySection.setMaximum(100);
2123 mySection.setWrapping(false);
2124 hwbSections.append(mySection);
2125 // Not setting prefix/suffix here. This will be done in retranslateUi()…
2126 m_hwbSpinBox->setSectionConfigurations(hwbSections);
2127 }
2128
2129 // Create HSV spin box
2130 {
2131 m_hsvSpinBox = new MultiSpinBox();
2132 QList<MultiSpinBoxSection> hsvSections;
2133 MultiSpinBoxSection mySection;
2134 mySection.setDecimals(decimals);
2135 // H
2136 mySection.setMinimum(0);
2137 mySection.setMaximum(360);
2138 mySection.setWrapping(true);
2139 hsvSections.append(mySection);
2140 // S
2141 mySection.setMinimum(0);
2142 mySection.setMaximum(100);
2143 mySection.setWrapping(false);
2144 hsvSections.append(mySection);
2145 // V
2146 mySection.setMinimum(0);
2147 mySection.setMaximum(100);
2148 mySection.setWrapping(false);
2149 hsvSections.append(mySection);
2150 // Not setting prefix/suffix here. This will be done in retranslateUi()…
2151 m_hsvSpinBox->setSectionConfigurations(hsvSections);
2152 }
2153
2154 // Create RGB layout
2155 {
2156 QFormLayout *tempRgbFormLayout = new QFormLayout();
2157 m_rgbSpinBoxLabel = new QLabel();
2158 m_rgbSpinBoxLabel->setBuddy(m_rgbSpinBox);
2159 tempRgbFormLayout->addRow(m_rgbSpinBoxLabel, m_rgbSpinBox);
2160 m_rgbLineEditLabel = new QLabel();
2161 m_rgbLineEditLabel->setBuddy(m_rgbLineEdit);
2162 tempRgbFormLayout->addRow(m_rgbLineEditLabel, m_rgbLineEdit);
2163 m_hslSpinBoxLabel = new QLabel();
2164 m_hslSpinBoxLabel->setBuddy(m_hslSpinBox);
2165 tempRgbFormLayout->addRow(m_hslSpinBoxLabel, m_hslSpinBox);
2166 m_hwbSpinBoxLabel = new QLabel();
2167 m_hwbSpinBoxLabel->setBuddy(m_hwbSpinBox);
2168 tempRgbFormLayout->addRow(m_hwbSpinBoxLabel, m_hwbSpinBox);
2169 m_hsvSpinBoxLabel = new QLabel();
2170 m_hsvSpinBoxLabel->setBuddy(m_hsvSpinBox);
2171 tempRgbFormLayout->addRow(m_hsvSpinBoxLabel, m_hsvSpinBox);
2172 m_rgbGroupBox = new QGroupBox();
2173 m_rgbGroupBox->setLayout(tempRgbFormLayout);
2174 // Using the profile name as QGroupBox title. But on some styles, the
2175 // title is always shown completely, even if the text is extremly
2176 // long. As the text is out of our control, and some profiles
2177 // like Krita’s ITUR_2100_PQ_FULL.ICC have actually extremly
2178 // long names, we use eliding.
2179 const QFontMetricsF fontMetrics(m_rgbGroupBox->font());
2180 const auto elidedProfileName = fontMetrics.elidedText( //
2181 m_rgbColorSpace->profileName(),
2182 Qt::TextElideMode::ElideRight,
2183 // width (in device-independent pixels!):
2184 tempRgbFormLayout->minimumSize().width());
2185 m_rgbGroupBox->setTitle(elidedProfileName);
2186 }
2187
2188 // Create widget for the CIELCH-D50 color representation
2189 {
2190 QList<MultiSpinBoxSection> cielchD50Sections;
2191 m_cielchD50SpinBox = new MultiSpinBox;
2192 MultiSpinBoxSection mySection;
2193 mySection.setDecimals(decimals);
2194 // L
2195 mySection.setMinimum(0);
2196 mySection.setMaximum(100);
2197 mySection.setWrapping(false);
2198 cielchD50Sections.append(mySection);
2199 // C
2200 mySection.setMinimum(0);
2201 mySection.setMaximum(CielchD50Values::maximumChroma);
2202 mySection.setWrapping(false);
2203 cielchD50Sections.append(mySection);
2204 // H
2205 mySection.setMinimum(0);
2206 mySection.setMaximum(360);
2207 mySection.setWrapping(true);
2208 cielchD50Sections.append(mySection);
2209 // Not setting prefix/suffix here. This will be done in retranslateUi()…
2210 m_cielchD50SpinBox->setSectionConfigurations(cielchD50Sections);
2211 }
2212
2213 // Create widget for the Oklch color representation
2214 {
2215 QList<MultiSpinBoxSection> oklchSections;
2216 MultiSpinBoxSection mySection;
2217 m_oklchSpinBox = new MultiSpinBox;
2218 // L
2219 mySection.setMinimum(0);
2220 mySection.setMaximum(1);
2221 mySection.setSingleStep(singleStepOklabc);
2222 mySection.setWrapping(false);
2223 mySection.setDecimals(okdecimals);
2224 oklchSections.append(mySection);
2225 // C
2226 mySection.setMinimum(0);
2227 mySection.setMaximum(OklchValues::maximumChroma);
2228 mySection.setSingleStep(singleStepOklabc);
2229 mySection.setWrapping(false);
2230 mySection.setDecimals(okdecimals);
2231 oklchSections.append(mySection);
2232 // H
2233 mySection.setMinimum(0);
2234 mySection.setMaximum(360);
2235 mySection.setSingleStep(1);
2236 mySection.setWrapping(true);
2237 mySection.setDecimals(decimals);
2238 oklchSections.append(mySection);
2239 // Not setting the suffix here. This will be done in retranslateUi()…
2240 m_oklchSpinBox->setSectionConfigurations(oklchSections);
2241 }
2242
2243 // Create a global widget
2244 QWidget *tempWidget = new QWidget;
2245 QVBoxLayout *tempMainLayout = new QVBoxLayout;
2246 tempWidget->setLayout(tempMainLayout);
2248 QFormLayout *cielabFormLayout = new QFormLayout;
2249 m_oklchSpinBoxLabel = new QLabel();
2250 m_oklchSpinBoxLabel->setBuddy(m_oklchSpinBox);
2251 cielabFormLayout->addRow(m_oklchSpinBoxLabel, m_oklchSpinBox);
2252 m_cielchD50SpinBoxLabel = new QLabel();
2253 m_cielchD50SpinBoxLabel->setBuddy(m_cielchD50SpinBox);
2254 cielabFormLayout->addRow(m_cielchD50SpinBoxLabel, m_cielchD50SpinBox);
2255 tempMainLayout->addLayout(cielabFormLayout);
2256 tempMainLayout->addWidget(m_rgbGroupBox);
2257 tempMainLayout->addStretch();
2258
2259 // Return
2260 return tempWidget;
2261}
2262
2263// No documentation here (documentation of properties
2264// and its getters are in the header)
2266{
2267 return d_pointer->m_options;
2268}
2269
2270/** @brief Setter for @ref options.
2271 *
2272 * Sets a value for just one single option within @ref options.
2273 * @param option the option to set
2274 * @param on the new value of the option */
2276{
2277 QColorDialog::ColorDialogOptions temp = d_pointer->m_options;
2278 temp.setFlag(option, on);
2279 setOptions(temp);
2280}
2281
2282/** @brief Setter for @ref options
2283 * @param newOptions the new options
2284 * @post <em>All</em> options of the widget have the same state
2285 * (enabled/disabled) as in the given parameter. */
2287{
2288 if (newOptions == d_pointer->m_options) {
2289 return;
2290 }
2291
2292 // Save the new options
2293 d_pointer->m_options = newOptions;
2294 // Correct QColorDialog::ColorDialogOption::DontUseNativeDialog
2295 // which must be always on
2296 d_pointer->m_options.setFlag( //
2297 QColorDialog::ColorDialogOption::DontUseNativeDialog,
2298 true);
2299
2300 // Apply the new options (alpha value)
2301 const bool alphaVisibility = d_pointer->m_options.testFlag( //
2302 QColorDialog::ColorDialogOption::ShowAlphaChannel);
2303 d_pointer->m_alphaLabel->setVisible(alphaVisibility);
2304 d_pointer->m_alphaGradientSlider->setVisible(alphaVisibility);
2305 d_pointer->m_alphaSpinBox->setVisible(alphaVisibility);
2306
2307 // Apply the new options (buttons)
2308 d_pointer->m_buttonBox->setVisible(!d_pointer->m_options.testFlag( //
2309 QColorDialog::ColorDialogOption::NoButtons));
2310
2311 // Notify
2312 Q_EMIT optionsChanged(d_pointer->m_options);
2313}
2314
2315/** @brief Getter for @ref options
2316 *
2317 * Gets the value of just one single option within @ref options.
2318 *
2319 * @param option the requested option
2320 * @returns the value of the requested option
2321 */
2323{
2324 return d_pointer->m_options.testFlag(option);
2325}
2326
2327/** @brief Pops up a modal color dialog, lets the user choose a color, and
2328 * returns that color.
2329 *
2330 * @param colorSpace The color space within which this widget should operate.
2331 * @param initial initial value for currentColor()
2332 * @param parent parent widget of the dialog (or 0 for no parent)
2333 * @param title window title (or an empty string for the default window
2334 * title)
2335 * @param options the options() for customizing the look and feel of the
2336 * dialog
2337 * @returns selectedColor(): The color the user has selected; or an
2338 * invalid color if the user has canceled the dialog. */
2340 const QColor &initial,
2341 QWidget *parent,
2342 const QString &title,
2344{
2345 ColorDialog temp(colorSpace, parent);
2346 if (!title.isEmpty()) {
2347 temp.setWindowTitle(title);
2348 }
2349 temp.setOptions(options);
2350 // setCurrentColor() must be after setOptions()
2351 // to allow alpha channel support
2352 temp.setCurrentColor(initial);
2353 temp.exec();
2354 return temp.selectedColor();
2355}
2356
2357/** @brief Pops up a modal color dialog, lets the user choose a color, and
2358 * returns that color.
2359 *
2360 * @param initial initial value for currentColor()
2361 * @param parent parent widget of the dialog (or 0 for no parent)
2362 * @param title window title (or an empty string for the default window
2363 * title)
2364 * @param options the options() for customizing the look and feel of the
2365 * dialog
2366 * @returns selectedColor(): The color the user has selected; or an
2367 * invalid color if the user has canceled the dialog. */
2369{
2371 initial, //
2372 parent, //
2373 title, //
2374 options);
2375}
2376
2377/** @brief The color that was actually selected by the user.
2378 *
2379 * At difference to the @ref currentColor property, this function provides
2380 * the color that was actually selected by the user by clicking the OK button
2381 * or pressing the return key or another equivalent action.
2382 *
2383 * This function most useful to get the actually selected color <em>after</em>
2384 * that the dialog has been closed.
2385 *
2386 * When a dialog that had been closed or hidden is shown again,
2387 * this function returns to an invalid QColor().
2388 *
2389 * @returns Just after showing the dialog, the value is an invalid QColor. If
2390 * the user selects a color by clicking the OK button or another equivalent
2391 * action, the value is the selected color. If the user cancels the dialog
2392 * (Cancel button, or by pressing the Escape key), the value remains an
2393 * invalid QColor. */
2395{
2396 return d_pointer->m_selectedColor;
2397}
2398
2399/** @brief Setter for property <em>visible</em>
2400 *
2401 * Reimplemented from base class.
2402 *
2403 * When a dialog, that wasn't formerly visible, gets visible,
2404 * it’s @ref selectedColor value is cleared.
2405 *
2406 * @param visible holds whether or not the dialog should be visible */
2408{
2409 if (visible && (!isVisible())) {
2410 // Only delete the selected color if the dialog wasn’t visible before
2411 // and will be made visible now.
2412 d_pointer->m_selectedColor = QColor();
2413 d_pointer->applyLayoutDimensions();
2414 }
2416 // HACK If there is a QColorDialog as helper widget for the
2417 // screen color picker feature, QDialog::setVisible() sometimes
2418 // changes which is default button; however, this has only been
2419 // observed running the unit tests on KDE’s CI system running, but
2420 // not when running the unit tests locally. Force correct default button:
2421 d_pointer->m_buttonOK->setDefault(true);
2422}
2423
2424/** @brief Various updates when closing the dialog.
2425 *
2426 * Reimplemented from base class.
2427 * @param result The result with which the dialog has been closed */
2429{
2430 if (result == QDialog::DialogCode::Accepted) {
2431 d_pointer->m_selectedColor = currentColor();
2432 auto history = d_pointer->m_settings.history.value();
2433 const auto maxHistoryLenght = std::max(d_pointer->historySwatchCount, //
2434 history.count());
2435 // Remove duplicates of the new value that might exist yet in the list.
2436 history.removeAll(d_pointer->m_selectedColor);
2437 // Add the new value at the very beginning.
2438 history.prepend(d_pointer->m_selectedColor);
2439 // Adapt list length.
2440 while (history.count() > maxHistoryLenght) {
2441 history.removeLast();
2442 }
2443 d_pointer->m_settings.history.setValue(history);
2444 Q_EMIT colorSelected(d_pointer->m_selectedColor);
2445 } else {
2446 d_pointer->m_selectedColor = QColor();
2447 }
2449 if (d_pointer->m_receiverToBeDisconnected) {
2450 // This “disconnect” uses the old-style syntax, which does not
2451 // detect errors on compile time. However, we do not see a
2452 // possibility how to substitute it with the better new-style
2453 // syntax, given that d_pointer->m_memberToBeDisconnected
2454 // can contain different classes, which would be difficult
2455 // it typing the class name directly in the new syntax.
2456 disconnect(this, // sender
2457 SIGNAL(colorSelected(QColor)), // signal
2458 d_pointer->m_receiverToBeDisconnected, // receiver
2459 d_pointer->m_memberToBeDisconnected.constData() // slot
2460 );
2461 d_pointer->m_receiverToBeDisconnected = nullptr;
2462 }
2463}
2464
2465// No documentation here (documentation of properties
2466// and its getters are in the header)
2468{
2469 return d_pointer->m_layoutDimensions;
2470}
2471
2472/** @brief Setter for property @ref layoutDimensions
2473 * @param newLayoutDimensions the new layout dimensions */
2475{
2476 if (newLayoutDimensions == d_pointer->m_layoutDimensions) {
2477 return;
2478 }
2479 d_pointer->m_layoutDimensions = newLayoutDimensions;
2480 d_pointer->applyLayoutDimensions();
2481 Q_EMIT layoutDimensionsChanged(d_pointer->m_layoutDimensions);
2482}
2483
2484/** @brief Arranges the layout conforming to @ref ColorDialog::layoutDimensions
2485 *
2486 * If @ref ColorDialog::layoutDimensions is DialogLayoutDimensions::automatic
2487 * than it is first evaluated again if for the current display the collapsed
2488 * or the expanded layout is used. */
2489void ColorDialogPrivate::applyLayoutDimensions()
2490{
2491 constexpr auto collapsed = ColorDialog::DialogLayoutDimensions::Collapsed;
2492 constexpr auto expanded = ColorDialog::DialogLayoutDimensions::Expanded;
2493 // cppcheck-suppress unreadVariable // false positive
2494 constexpr auto screenSizeDependent = //
2496 int effectivelyAvailableScreenWidth;
2497 int widthThreeshold;
2498 switch (m_layoutDimensions) {
2499 case collapsed:
2500 m_layoutDimensionsEffective = collapsed;
2501 break;
2502 case expanded:
2503 m_layoutDimensionsEffective = expanded;
2504 break;
2505 case screenSizeDependent:
2506 // Note: The following code works correctly on scaled
2507 // devices (high-DPI…).
2508
2509 // We should not use more than 70% of the screen for a dialog.
2510 // That’s roughly the same as the default maximum sizes for
2511 // a QDialog.
2512 effectivelyAvailableScreenWidth = qRound( //
2513 QGuiApplication::primaryScreen()->availableSize().width() * 0.7);
2514
2515 // Now we calculate the space we need for displaying the
2516 // graphical selectors and the numerical selector at their
2517 // preferred size in an expanded layout.
2518 // Start with the size of the graphical selectors.
2519 widthThreeshold = qMax( //
2520 m_wheelColorPicker->sizeHint().width(), //
2521 m_lightnessFirstWrapperWidget->sizeHint().width());
2522 // Add the size of the numerical selector.
2523 widthThreeshold += m_numericalWidget->sizeHint().width();
2524 // Add some space for margins.
2525 widthThreeshold = qRound(widthThreeshold * 1.2);
2526
2527 // Now decide between collapsed layout and expanded layout
2528 if (effectivelyAvailableScreenWidth < widthThreeshold) {
2529 m_layoutDimensionsEffective = collapsed;
2530 } else {
2531 m_layoutDimensionsEffective = expanded;
2532 }
2533 break;
2534 default:
2535 // We should never reach this point, because we treat all possible
2536 // enum values in the switch statement.
2537 throw 0;
2538 }
2539
2540 if (m_layoutDimensionsEffective == collapsed) {
2541 if (m_selectorLayout->indexOf(m_numericalWidget) >= 0) {
2542 // Indeed we have expanded layout and have to switch to
2543 // collapsed layout…
2544 const bool oldUpdatesEnabled = m_tabWidget->updatesEnabled();
2545 m_tabWidget->setUpdatesEnabled(false);
2546 // According to the documentation of QTabWidget::addTab it is
2547 // recommended to disable visual updates during adding new
2548 // tabs. This should avoid flickering.
2549 m_tabWidget->addTab(m_numericalWidget, QString());
2550 m_tabWidget->setUpdatesEnabled(oldUpdatesEnabled);
2551 retranslateUi(); // Will put a label for the recently inserted tab.
2552 reloadIcons(); // Will put an icon for the recently inserted tab.
2553 // We don’t call m_numericalWidget->show(); because this
2554 // is controlled by the QTabWidget.
2555 // Adopt size of dialog to new layout’s size hint:
2556 q_pointer->adjustSize();
2557 }
2558 } else {
2559 if (m_selectorLayout->indexOf(m_numericalWidget) < 0) {
2560 // Indeed we have collapsed layout and have to switch to
2561 // expanded layout…
2562 m_selectorLayout->addWidget(m_numericalWidget);
2563 // We call show because the widget is hidden by removing it
2564 // from its old parent, and needs to be shown explicitly.
2565 m_numericalWidget->show();
2566 // Adopt size of dialog to new layout’s size hint:
2567 q_pointer->adjustSize();
2568 }
2569 }
2570}
2571
2572/** @brief Handle state changes.
2573 *
2574 * Implements reaction on <tt>QEvent::LanguageChange</tt>.
2575 *
2576 * Reimplemented from base class.
2577 *
2578 * @param event The event. */
2580{
2581 const auto type = event->type();
2582
2583 if (type == QEvent::LanguageChange) {
2584 // From QCoreApplication documentation:
2585 // “Installing or removing a QTranslator, or changing an installed
2586 // QTranslator generates a LanguageChange event for the
2587 // QCoreApplication instance. A QApplication instance will
2588 // propagate the event to all toplevel widgets […].
2589 // Retranslate this widget itself:
2590 d_pointer->retranslateUi();
2591 // Retranslate all child widgets that actually need to be retranslated:
2592 {
2593 QEvent eventForSwatchBookBasicColors(QEvent::LanguageChange);
2594 QApplication::sendEvent(d_pointer->m_swatchBookBasicColors, //
2595 &eventForSwatchBookBasicColors);
2596 }
2597 {
2598 QEvent eventForSwatchBookHistory(QEvent::LanguageChange);
2599 QApplication::sendEvent(d_pointer->m_swatchBookHistory, //
2600 &eventForSwatchBookHistory);
2601 }
2602 {
2603 QEvent eventForSwatchBookCustomColors(QEvent::LanguageChange);
2604 QApplication::sendEvent(d_pointer->m_swatchBookCustomColors, //
2605 &eventForSwatchBookCustomColors);
2606 }
2607 {
2608 QEvent eventForButtonOk(QEvent::LanguageChange);
2609 QApplication::sendEvent(d_pointer->m_buttonOK, //
2610 &eventForButtonOk);
2611 }
2612 {
2613 QEvent eventForButtonCancel(QEvent::LanguageChange);
2614 QApplication::sendEvent(d_pointer->m_buttonOK, //
2615 &eventForButtonCancel);
2616 }
2617 }
2618
2619 if ((type == QEvent::PaletteChange) || (type == QEvent::StyleChange)) {
2620 d_pointer->reloadIcons();
2621 }
2622
2624}
2625
2626/** @brief Handle show events.
2627 *
2628 * Reimplemented from base class.
2629 *
2630 * @param event The event.
2631 *
2632 * @internal
2633 *
2634 * On the first show event, make @ref ColorDialogPrivate::m_tabWidget use
2635 * the current tab corresponding to @ref ColorDialogPrivate::m_settings. */
2637{
2638 if (!d_pointer->everShown) {
2639 constexpr auto expValue = ColorDialog::DialogLayoutDimensions::Expanded;
2640 const bool exp = d_pointer->m_layoutDimensionsEffective == expValue;
2641 const auto tabString = exp //
2642 ? d_pointer->m_settings.tabExpanded.value() //
2643 : d_pointer->m_settings.tab.value();
2644 const auto key = d_pointer->m_tabTable.key(tabString, nullptr);
2645 if (key != nullptr) {
2646 d_pointer->m_tabWidget->setCurrentWidget(*key);
2647 }
2648 // Save the new tab explicitly. If setCurrentWidget() is not
2649 // different from the default value, it does not trigger the
2650 // QTabWidget::currentChanged() signal, resulting in the tab
2651 // not being saved. However, we want to ensure that the tab
2652 // is saved whenever the user has first seen it.
2653 d_pointer->saveCurrentTab();
2654
2655 switch (d_pointer->m_settings.swatchBookPage.value()) {
2656 case PerceptualSettings::SwatchBookPage::BasicColors:
2657 default:
2658 d_pointer->m_settings.swatchBookPage.setValue( //
2659 PerceptualSettings::SwatchBookPage::BasicColors);
2660 d_pointer->m_swatchBookSelector->setCurrentIndex(0);
2661 d_pointer->m_swatchBookStack->setCurrentIndex(0);
2662 break;
2663 case PerceptualSettings::SwatchBookPage::History:
2664 d_pointer->m_settings.swatchBookPage.setValue( //
2665 PerceptualSettings::SwatchBookPage::History);
2666 d_pointer->m_swatchBookSelector->setCurrentIndex(1);
2667 d_pointer->m_swatchBookStack->setCurrentIndex(1);
2668 break;
2669 case PerceptualSettings::SwatchBookPage::CustomColors:
2670 d_pointer->m_settings.swatchBookPage.setValue( //
2671 PerceptualSettings::SwatchBookPage::History);
2672 d_pointer->m_swatchBookSelector->setCurrentIndex(2);
2673 d_pointer->m_swatchBookStack->setCurrentIndex(2);
2674 break;
2675 }
2676
2677 d_pointer->everShown = true;
2678 }
2680}
2681
2682/** @brief Saves the current tab of @ref m_tabWidget to @ref m_settings. */
2683void ColorDialogPrivate::saveCurrentTab()
2684{
2685 const auto currentIndex = m_tabWidget->currentIndex();
2686 QWidget const *const widget = m_tabWidget->widget(currentIndex);
2687 const auto keyList = m_tabTable.keys();
2688 auto it = std::find_if( //
2689 keyList.begin(),
2690 keyList.end(),
2691 [widget](const auto &key) {
2692 return ((*key) == widget);
2693 } //
2694 );
2695 if (it != keyList.end()) {
2696 const auto tabString = m_tabTable.value(*it);
2697 constexpr auto expValue = ColorDialog::DialogLayoutDimensions::Expanded;
2698 if (m_layoutDimensionsEffective == expValue) {
2699 m_settings.tabExpanded.setValue(tabString);
2700 } else {
2701 m_settings.tab.setValue(tabString);
2702 }
2703 }
2704}
2705
2706/** @brief Loads @ref PerceptualSettings::history into
2707 * @ref m_swatchBookHistory. */
2708void ColorDialogPrivate::loadHistoryFromSettingsToSwatchBook()
2709{
2710 const Swatches historyArray(historyHSwatchCount, //
2711 historyVSwatchCount, //
2712 m_settings.history.value());
2713 m_swatchBookHistory->setSwatchGrid(historyArray);
2714}
2715
2716/** @brief Loads @ref PerceptualSettings::customColors into
2717 * @ref m_swatchBookCustomColors. */
2718void ColorDialogPrivate::loadCustomColorsFromSettingsToSwatchBook()
2719{
2720 const Swatches customColorsArray(customColorsHSwatchCount, //
2721 customColorsVSwatchCount, //
2722 m_settings.customColors.value());
2723 m_swatchBookCustomColors->setSwatchGrid(customColorsArray);
2724}
2725
2726} // namespace PerceptualColor
void currentColorCielchD50Changed(const PerceptualColor::GenericColor &newCurrentColorCielchD50)
Notify signal for property currentColorCielchD50.
A perceptually uniform color picker dialog.
void colorSelected(const QColor &color)
This signal is emitted just after the user has clicked OK to select a color to use.
static QColor getColor(const QColor &initial=Qt::white, QWidget *parent=nullptr, const QString &title=QString(), ColorDialogOptions options=ColorDialogOptions())
Pops up a modal color dialog, lets the user choose a color, and returns that color.
virtual void showEvent(QShowEvent *event) override
Handle show events.
virtual void setVisible(bool visible) override
Setter for property visible
Q_INVOKABLE void open(QObject *receiver, const char *member)
Opens the dialog and connects its colorSelected() signal to the slot specified by receiver and member...
QColorDialog::ColorDialogOptions ColorDialogOptions
Local alias for QColorDialog::ColorDialogOptions.
Q_INVOKABLE ColorDialog(QWidget *parent=nullptr)
Constructor.
ColorDialogOptions options
Various options that affect the look and feel of the dialog.
Q_INVOKABLE bool testOption(PerceptualColor::ColorDialog::ColorDialogOption option) const
Getter for KConfig Entry Options.
Q_INVOKABLE void setOption(PerceptualColor::ColorDialog::ColorDialogOption option, bool on=true)
Setter for KConfig Entry Options.
QColor currentColor
Currently selected color in the dialog.
void setCurrentColor(const QColor &color)
Setter for currentColor property.
virtual void done(int result) override
Various updates when closing the dialog.
virtual ~ColorDialog() noexcept override
Destructor.
void optionsChanged(const PerceptualColor::ColorDialog::ColorDialogOptions newOptions)
Notify signal for property KConfig Entry Options.
void setOptions(PerceptualColor::ColorDialog::ColorDialogOptions newOptions)
Setter for KConfig Entry Options.
void setLayoutDimensions(const PerceptualColor::ColorDialog::DialogLayoutDimensions newLayoutDimensions)
Setter for property layoutDimensions.
DialogLayoutDimensions
Layout dimensions.
@ Expanded
Use the large, “expanded” layout of this dialog.
@ Collapsed
Use the small, “collapsed“ layout of this dialog.
@ ScreenSizeDependent
Decide automatically between collapsed and expanded layout: collapsed is used on small screens,...
virtual void changeEvent(QEvent *event) override
Handle state changes.
void layoutDimensionsChanged(const PerceptualColor::ColorDialog::DialogLayoutDimensions newLayoutDimensions)
Notify signal for property layoutDimensions.
Q_INVOKABLE QColor selectedColor() const
The color that was actually selected by the user.
QColorDialog::ColorDialogOption ColorDialogOption
Local alias for QColorDialog::ColorDialogOption.
DialogLayoutDimensions layoutDimensions
Layout dimensions.
void colorChanged(const QColor &color)
Notify signal for property color.
void valueChanged(const qreal newValue)
Signal for value property.
void sectionValuesChanged(const QList< double > &newSectionValues)
Notify signal for property sectionValues.
static QSharedPointer< PerceptualColor::RgbColorSpace > createSrgb()
Create an sRGB color space object.
void currentColorCielchD50Changed(const PerceptualColor::GenericColor &newCurrentColorCielchD50)
Notify signal for property currentColorCielchD50.
QString name(StandardAction id)
The namespace of this library.
Array2D< QColor > Swatches
Swatches organized in a grid.
Definition helper.h:262
Swatches wcsBasicColors(const QSharedPointer< PerceptualColor::RgbColorSpace > &colorSpace)
Swatch grid derived from the basic colors as by WCS (World color survey).
Definition helper.cpp:465
@ OklchD65
The absolute Oklch color space, which by definition always and exclusively uses a D65 illuminant.
@ CielchD50
The absolute Cielch color space using a D50 illuminant.
void clicked(bool checked)
void editingFinished()
void triggered(bool checked)
void addLayout(QLayout *layout, int stretch)
void addStretch(int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
QChar fromLatin1(char c)
void setNamedColor(QLatin1StringView name)
float alphaF() const const
QColor fromRgbF(float r, float g, float b, float a)
bool isValid() const const
QRgba64 rgba64() const const
void setAlphaF(float alpha)
QColor toRgb() const const
typedef ColorDialogOptions
void currentIndexChanged(int index)
QCoreApplication * instance()
bool sendEvent(QObject *receiver, QEvent *event)
bool isNull() const const
QDialog(QWidget *parent, Qt::WindowFlags f)
virtual void accept()
virtual void done(int r)
virtual int exec()
virtual void open()
virtual void reject()
int result() const const
virtual void setVisible(bool visible) override
virtual void showEvent(QShowEvent *event) override
void valueChanged(double d)
void addRow(QLayout *layout)
virtual QSize minimumSize() const const override
QList< Key > keys() const const
T value(const Key &key) const const
QKeySequence mnemonic(const QString &text)
void editingFinished()
void textChanged(const QString &text)
void append(QList< T > &&value)
qsizetype count() const const
bool isEmpty() const const
QObject(QObject *parent)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
void activated()
void activatedAmbiguously()
int width() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toHtmlEscaped() const const
QString join(QChar separator) const const
void colorSchemeChanged(Qt::ColorScheme colorScheme)
typedef Alignment
WindowContextHelpButtonHint
void currentChanged(int index)
QString toString() const const
QWidget(QWidget *parent, Qt::WindowFlags f)
virtual void changeEvent(QEvent *event)
virtual bool event(QEvent *event) override
void setLayout(QLayout *layout)
void setSizePolicy(QSizePolicy)
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 12:03:13 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.