8#include "swatchbook_p.h"
10#include "abstractdiagram.h"
11#include "constpropagatingrawpointer.h"
12#include "constpropagatinguniquepointer.h"
13#include "genericcolor.h"
15#include "helpermath.h"
16#include "initializetranslation.h"
17#include "rgbcolorspace.h"
20#include <qapplication.h>
21#include <qcoreapplication.h>
22#include <qcoreevent.h>
24#include <qfontmetrics.h>
31#include <qnamespace.h>
33#include <qpainterpath.h>
37#include <qsharedpointer.h>
38#include <qsizepolicy.h>
39#include <qstringliteral.h>
41#include <qstyleoption.h>
42#include <qtransform.h>
45#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
46#include <qcontainerfwd.h>
48#include <qstringlist.h>
67void SwatchBookPrivate::retranslateUi()
69 const QFontMetricsF myFontMetrics(q_pointer->font());
70 auto validateWithFont = [&myFontMetrics](
const QString &string) -> QString {
73 auto ucs4 =
string.toUcs4();
75 for (
int i = 0; okay && (i < ucs4.count()); ++i) {
76 okay = myFontMetrics.inFontUcs4(ucs4.at(i));
107 m_selectionMark = validateWithFont(
tr(
"✓"));
116 m_addMark = validateWithFont(
tr(
"+"));
132SwatchBook::SwatchBook(
const QSharedPointer<PerceptualColor::RgbColorSpace> &colorSpace,
137 , d_pointer(new SwatchBookPrivate(this, swatchGrid, wideSpacing))
139 qRegisterMetaType<Swatches>();
141 d_pointer->m_rgbColorSpace = colorSpace;
143 setFocusPolicy(Qt::FocusPolicy::StrongFocus);
152 d_pointer->selectSwatch(8, 0);
159 std::optional<QStringList>());
160 d_pointer->retranslateUi();
162 d_pointer->updateColorSchemeCache();
166SwatchBook::~SwatchBook() noexcept
178 : m_swatchGrid(swatchGrid)
179 , m_wideSpacing(wideSpacing)
180 , q_pointer(backLink)
191QSize SwatchBook::sizeHint()
const
193 return minimumSizeHint();
203QSize SwatchBook::minimumSizeHint()
const
207 d_pointer->initStyleOption(&myOption);
208 const auto contentSize = d_pointer->colorPatchesSizeWithMargin();
217 const auto lineWidth = myOption.lineWidth;
218 QMargins margins{lineWidth, lineWidth, lineWidth, lineWidth};
226QColor SwatchBook::currentColor()
const
228 return d_pointer->m_currentColor;
234void SwatchBook::setCurrentColor(
const QColor &newCurrentColor)
237 QColor temp = newCurrentColor;
241 if (temp.
spec() != QColor::Spec::Rgb) {
253 if (temp == d_pointer->m_currentColor) {
257 d_pointer->m_currentColor = temp;
259 d_pointer->selectSwatchFromCurrentColor();
261 Q_EMIT currentColorChanged(temp);
268Swatches SwatchBook::swatchGrid()
const
270 return d_pointer->m_swatchGrid;
278 if (newSwatchGrid == d_pointer->m_swatchGrid) {
282 d_pointer->m_swatchGrid = newSwatchGrid;
284 d_pointer->selectSwatchFromCurrentColor();
286 Q_EMIT swatchGridChanged(newSwatchGrid);
308void SwatchBookPrivate::selectSwatch(QListSizeType newCurrentColomn, QListSizeType newCurrentRow)
310 const auto newColor = m_swatchGrid.value(newCurrentColomn, newCurrentRow);
311 if (!newColor.isValid()) {
314 m_selectedColumn = newCurrentColomn;
315 m_selectedRow = newCurrentRow;
316 if (newColor != m_currentColor) {
317 m_currentColor = newColor;
318 Q_EMIT q_pointer->currentColorChanged(newColor);
329void SwatchBookPrivate::selectSwatchFromCurrentColor()
331 if ((m_selectedColumn > 0) && (m_selectedRow > 0)) {
332 if (m_swatchGrid.value(m_selectedColumn, m_selectedRow) == m_currentColor) {
337 bool colorFound =
false;
338 const qsizetype myColumnCount = m_swatchGrid.iCount();
339 const qsizetype myRowCount = m_swatchGrid.jCount();
342 for (columnIndex = 0;
343 columnIndex < myColumnCount;
345 for (rowIndex = 0; rowIndex < myRowCount; ++rowIndex) {
346 if (m_swatchGrid.value(columnIndex, rowIndex) == m_currentColor) {
356 m_selectedColumn = columnIndex;
357 m_selectedRow = rowIndex;
359 m_selectedColumn = -1;
371int SwatchBookPrivate::horizontalPatchSpacing()
const
373 if (m_wideSpacing.testFlag(Qt::Orientation::Horizontal)) {
374 return widePatchSpacing();
376 return normalPatchSpacing();
386int SwatchBookPrivate::widePatchSpacing()
const
394 int temp = q_pointer->style()->pixelMetric(
397 q_pointer.toPointerToConstObject());
405 temp = q_pointer->style()->pixelMetric(
408 q_pointer.toPointerToConstObject());
412 temp = q_pointer->style()->pixelMetric(
415 q_pointer.toPointerToConstObject());
418 return qMax(temp, 2);
427int SwatchBookPrivate::verticalPatchSpacing()
const
429 if (m_wideSpacing.testFlag(Qt::Orientation::Vertical)) {
430 return widePatchSpacing();
432 return normalPatchSpacing();
441int SwatchBookPrivate::normalPatchSpacing()
const
443 return qMax(widePatchSpacing() / 3,
459 if (option ==
nullptr) {
462 option->
initFrom(q_pointer.toPointerToConstObject());
463 option->lineWidth = q_pointer->style()->pixelMetric(
466 q_pointer.toPointerToConstObject());
467 option->midLineWidth = 0;
489 const QRectF frameContentRectangle = q_pointer->style()->subElementRect(
492 q_pointer.toPointerToConstObject());
493 const QSizeF swatchbookContentSize = colorPatchesSizeWithMargin();
506 frameOffset.
rx() -= swatchbookContentSize.
width() / 2.;
507 frameOffset.
ry() -= swatchbookContentSize.
height() / 2.;
509 return (frameOffset + innerMarginOffset).toPoint();
526std::pair<QListSizeType, QListSizeType> SwatchBookPrivate::logicalColumnRowFromPosition(
const QPoint position)
const
528 constexpr std::pair<QListSizeType, QListSizeType> invalid(-1, -1);
530 const QSize myColorPatchSize = patchSizeOuter();
531 const int myPatchWidth = myColorPatchSize.
width();
532 const int myPatchHeight = myColorPatchSize.
height();
534 initStyleOption(&myFrameStyleOption);
535 const QPoint temp = position - offset(myFrameStyleOption);
537 if ((temp.
x() < 0) || (temp.
y() < 0)) {
541 const auto columnWidth = myPatchWidth + horizontalPatchSpacing();
542 const int xWithinPatch = temp.
x() % columnWidth;
543 if (xWithinPatch >= myPatchWidth) {
547 const auto rowHeight = myPatchHeight + verticalPatchSpacing();
548 const int yWithinPatch = temp.
y() % rowHeight;
549 if (yWithinPatch >= myPatchHeight) {
553 const int rowIndex = temp.
y() / rowHeight;
554 if (!isInRange<qsizetype>(0, rowIndex, m_swatchGrid.jCount() - 1)) {
561 const int visualColumnIndex = temp.
x() / columnWidth;
562 QListSizeType columnIndex;
563 if (q_pointer->layoutDirection() == Qt::LayoutDirection::LeftToRight) {
564 columnIndex = visualColumnIndex;
566 columnIndex = m_swatchGrid.iCount() - 1 - visualColumnIndex;
568 if (!isInRange<qsizetype>(0, columnIndex, m_swatchGrid.iCount() - 1)) {
575 return std::pair<QListSizeType, QListSizeType>(columnIndex, rowIndex);
583void SwatchBook::mousePressEvent(
QMouseEvent *event)
596 const auto logicalColumnRow = d_pointer->logicalColumnRowFromPosition(
event->pos());
597 const auto logicalColumn = logicalColumnRow.first;
598 const auto logicalRow = logicalColumnRow.second;
599 if ((logicalColumn < 0) || (logicalRow < 0)) {
606 const bool swatchIsEmpty =
607 !d_pointer->m_swatchGrid.value(logicalColumn, logicalRow).isValid();
609 if (
event->button() == Qt::MouseButton::RightButton) {
610 if (!swatchIsEmpty) {
621 [
this, logicalColumn, logicalRow]() {
622 d_pointer->m_swatchGrid.setValue(logicalColumn,
627 d_pointer->selectSwatchFromCurrentColor();
628 Q_EMIT swatchGridChanged(d_pointer->m_swatchGrid);
636 if (
event->button() == Qt::MouseButton::LeftButton) {
638 d_pointer->m_swatchGrid.setValue(logicalColumn,
640 d_pointer->m_currentColor);
641 d_pointer->selectSwatch(logicalColumn, logicalRow);
642 Q_EMIT swatchGridChanged(d_pointer->m_swatchGrid);
644 d_pointer->selectSwatch(logicalColumn, logicalRow);
658QSize SwatchBookPrivate::patchSizeOuter()
const
660 q_pointer->ensurePolished();
661 const QSize myInnerSize = patchSizeInner();
663 myOptions.
initFrom(q_pointer.toPointerToConstObject());
664 myOptions.rect.setSize(myInnerSize);
665 const auto myStyledOuterSize = q_pointer->style()->sizeFromContents(
669 q_pointer.toPointerToConstObject());
673 const int extra = 2 * cornerRadius();
674 return myStyledOuterSize.expandedTo(myInnerSize +
QSize(extra, extra));
683QSize SwatchBookPrivate::patchSizeInner()
const
685 const int metric = q_pointer->style()->pixelMetric(
688 q_pointer.toPointerToConstObject());
689 const int size = std::max({metric,
690 horizontalPatchSpacing(),
691 verticalPatchSpacing()});
692 return QSize(size, size);
701int SwatchBookPrivate::cornerRadius()
const
703 const auto defaultFrameWidth =
705 return qMax(defaultFrameWidth, 0);
722void SwatchBookPrivate::drawMark(
const QPoint offset,
725 const SwatchBookPrivate::Mark markSymbol,
726 const QListSizeType row,
727 const QListSizeType column)
const
729 widgetPainter->
save();
732 const QListSizeType visualSelectedColumnIndex =
733 (q_pointer->layoutDirection() == Qt::LayoutDirection::LeftToRight)
735 : m_swatchGrid.iCount() - 1 - column;
736 const int patchWidthOuter = patchSizeOuter().width();
737 const int patchHeightOuter = patchSizeOuter().height();
741 + (
static_cast<int>(visualSelectedColumnIndex)
742 * (patchWidthOuter + horizontalPatchSpacing())),
744 + (
static_cast<int>(row)
745 * (patchHeightOuter + verticalPatchSpacing())));
746 const int patchWidthInner = patchSizeInner().width();
747 const int patchHeightInner = patchSizeInner().height();
750 switch (markSymbol) {
751 case Mark::Selection:
752 myMark = m_selectionMark;
763 const QSize sizeDifference = patchSizeOuter() - patchSizeInner();
766 sizeDifference.
width() / 2.0,
767 sizeDifference.
height() / 2.0);
768 if (patchWidthInner > patchHeightInner) {
769 selectionMarkOffset.
rx() +=
770 ((patchWidthInner - patchHeightInner) / 2.0);
772 if (patchHeightInner > patchWidthInner) {
773 selectionMarkOffset.
ry() +=
774 ((patchHeightInner - patchWidthInner) / 2.0);
776 const int effectiveSquareSize = qMin(
779 qreal penWidth = effectiveSquareSize * 0.08;
784 widgetPainter->
setPen(pen);
786 switch (markSymbol) {
787 case Mark::Selection: {
789 0.7 * effectiveSquareSize);
790 point1 += selectedPatchOffset + selectionMarkOffset;
791 QPointF point2(0.35 * effectiveSquareSize,
792 1 * effectiveSquareSize - penWidth);
793 point2 += selectedPatchOffset + selectionMarkOffset;
794 QPointF point3(1 * effectiveSquareSize - penWidth,
796 point3 += selectedPatchOffset + selectionMarkOffset;
802 0.5 * effectiveSquareSize);
803 point1 += selectedPatchOffset + selectionMarkOffset;
805 0.5 * effectiveSquareSize);
806 point2 += selectedPatchOffset + selectionMarkOffset;
807 QPointF point3(0.5 * effectiveSquareSize,
809 point3 += selectedPatchOffset + selectionMarkOffset;
810 QPointF point4(0.5 * effectiveSquareSize,
811 1 * effectiveSquareSize - penWidth);
812 point4 += selectedPatchOffset + selectionMarkOffset;
822 textPath.
addText(0, 0, q_pointer->font(), myMark);
829 if (!boundingRectangleSize.
isEmpty()) {
835 selectedPatchOffset.
x()
836 + (patchWidthOuter - patchWidthInner) / 2,
838 selectedPatchOffset.
y()
839 + (patchHeightOuter - patchHeightInner) / 2);
842 const qreal scaleFactor = qMin(
844 patchWidthInner / boundingRectangleSize.
width(),
846 patchHeightInner / boundingRectangleSize.
height());
847 QSizeF scaledSelectionMarkSize =
848 boundingRectangleSize * scaleFactor;
850 (patchSizeInner() - scaledSelectionMarkSize) / 2;
852 textTransform.
scale(scaleFactor, scaleFactor);
878 d_pointer->initStyleOption(&frameStyleOption);
879 const int patchWidthOuter = d_pointer->patchSizeOuter().width();
880 const int patchHeightOuter = d_pointer->patchSizeOuter().height();
890 QStringLiteral(
"windowsvista"),
893 const int shrink = vistaStyle ? 1 : 0;
894 const QMargins margins(shrink, shrink, shrink, shrink);
895 auto shrunkFrameStyleOption = frameStyleOption;
896 shrunkFrameStyleOption.rect = frameStyleOption.rect - margins;
909 &shrunkFrameStyleOption,
915 const QPoint offset = d_pointer->offset(frameStyleOption);
916 const QListSizeType columnCount = d_pointer->m_swatchGrid.iCount();
917 const int myCornerRadius = d_pointer->cornerRadius();
918 QListSizeType visualColumn;
919 const auto currentScheme = d_pointer->m_colorSchemeCache;
920 const QColor addMarkColor = (currentScheme == ColorSchemeType::Dark)
923 for (
int columnIndex = 0; columnIndex < columnCount; ++columnIndex) {
925 row < d_pointer->m_swatchGrid.jCount();
928 const auto swatchColor =
929 d_pointer->m_swatchGrid.value(columnIndex, row);
930 if (swatchColor.isValid()) {
931 widgetPainter.
setBrush(swatchColor);
933 if (layoutDirection() == Qt::LayoutDirection::LeftToRight) {
934 visualColumn = columnIndex;
936 visualColumn = columnCount - 1 - columnIndex;
940 + (
static_cast<int>(visualColumn)
941 * (patchWidthOuter + d_pointer->horizontalPatchSpacing())),
943 + row * (patchHeightOuter + d_pointer->verticalPatchSpacing()),
949 if (d_pointer->m_editable) {
950 d_pointer->drawMark(offset,
953 SwatchBookPrivate::Mark::Add,
962 if (d_pointer->m_selectedColumn < 0 || d_pointer->m_selectedRow < 0) {
967 const auto selectedColor = d_pointer->m_swatchGrid.value(
968 d_pointer->m_selectedColumn,
969 d_pointer->m_selectedRow);
971 const auto colorCielchD50 = d_pointer->m_rgbColorSpace->toCielchD50(
972 selectedColor.rgba64());
973 const QColor selectionMarkColor =
974 handleColorFromBackgroundLightness(colorCielchD50.first);
975 d_pointer->drawMark(offset,
978 SwatchBookPrivate::Mark::Selection,
979 d_pointer->m_selectedRow,
980 d_pointer->m_selectedColumn);
996void SwatchBook::keyPressEvent(
QKeyEvent *event)
998 QListSizeType columnShift = 0;
999 QListSizeType rowShift = 0;
1000 const int writingDirection =
1004 switch (
event->key()) {
1012 columnShift = -1 * writingDirection;
1015 columnShift = 1 * writingDirection;
1018 rowShift = (-1) * d_pointer->m_swatchGrid.jCount();
1021 rowShift = d_pointer->m_swatchGrid.jCount();
1024 columnShift = (-1) * d_pointer->m_swatchGrid.iCount();
1027 columnShift = d_pointer->m_swatchGrid.iCount();
1049 if ((d_pointer->m_selectedColumn < 0) && (d_pointer->m_selectedRow < 0)) {
1050 d_pointer->selectSwatch(0, 0);
1054 const int accelerationFactor = 2;
1056 columnShift *= accelerationFactor;
1057 rowShift *= accelerationFactor;
1060 d_pointer->selectSwatch(
1061 qBound<QListSizeType>(0,
1062 d_pointer->m_selectedColumn + columnShift,
1063 d_pointer->m_swatchGrid.iCount() - 1),
1064 qBound<QListSizeType>(0,
1065 d_pointer->m_selectedRow + rowShift,
1066 d_pointer->m_swatchGrid.jCount() - 1));
1076void SwatchBook::changeEvent(
QEvent *event)
1078 const auto type =
event->type();
1087 d_pointer->retranslateUi();
1091 d_pointer->updateColorSchemeCache();
1101void SwatchBookPrivate::updateColorSchemeCache()
1103 m_colorSchemeCache = guessColorSchemeTypeFromWidget(q_pointer);
1110QSize SwatchBookPrivate::colorPatchesSizeWithMargin()
const
1112 q_pointer->ensurePolished();
1113 const QSize patchSize = patchSizeOuter();
1114 const int columnCount =
static_cast<int>(m_swatchGrid.iCount());
1115 const int rowCount =
static_cast<int>(m_swatchGrid.jCount());
1118 + columnCount * patchSize.
width()
1119 + (columnCount - 1) * horizontalPatchSpacing()
1123 + rowCount * patchSize.
height()
1124 + (rowCount - 1) * verticalPatchSpacing()
1126 return QSize(width, height);
1129bool SwatchBook::isEditable()
const
1131 return d_pointer->m_editable;
1137void SwatchBook::setEditable(
const bool newEditable)
1139 d_pointer->m_editable = newEditable;
1141 Q_EMIT editableChanged(newEditable);
Base class for LCH diagrams.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Type type(const QSqlDatabase &db)
void update(Part *part, const QByteArray &data, qint64 dataSize)
The namespace of this library.
Array2D< QColor > Swatches
Swatches organized in a grid.
void triggered(bool checked)
float alphaF() const const
float blueF() const const
QColor fromRgbF(float r, float g, float b, float a)
float greenF() const const
bool isValid() const const
QCoreApplication * instance()
QString tr(const char *sourceText, const char *disambiguation, int n)
void drawLine(const QLine &line)
void drawPath(const QPainterPath &path)
void drawRoundedRect(const QRect &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
void setBrush(Qt::BrushStyle style)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
void addText(const QPointF &point, const QFont &font, const QString &text)
QRectF boundingRect() const const
void translate(const QPointF &offset)
void setCapStyle(Qt::PenCapStyle style)
void setColor(const QColor &color)
void setWidthF(qreal width)
QPointF center() const const
QSizeF size() const const
QSize expandedTo(const QSize &otherSize) const const
QSize grownBy(QMargins margins) const const
qreal height() const const
bool isEmpty() const const
qreal width() const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
bool isEmpty() const const
PM_LayoutHorizontalSpacing
void initFrom(const QWidget *widget)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)