From 076e17c608fae7cad0ecf472f650d636308ee97b Mon Sep 17 00:00:00 2001 From: David Hurka Date: Thu, 3 Sep 2020 11:02:13 +0000 Subject: [PATCH] Create GuiUtils functions createColorIcon() and createOpacityIcon() * createColorIcon creates a QIcon which visualizes the given colors using rectangles. A background icon can be provided, in that case only the lowest 25% of the icon will be filled by the rectangles. * createOpacityIcon creates a QIcon that visualizes a given opacity using the current foreground color and a checkerboard background. These functions are now used in place of colorIcon, colorPicerIcon, opacityIcon in AnnotationActionHandler. The new functions have some advantages: support most common icon sizes, and dark color schemes. --- ui/annotationactionhandler.cpp | 70 ++++---------------------- ui/guiutils.cpp | 90 ++++++++++++++++++++++++++++++++++ ui/guiutils.h | 34 ++++++++++++- 3 files changed, 133 insertions(+), 61 deletions(-) diff --git a/ui/annotationactionhandler.cpp b/ui/annotationactionhandler.cpp index 8d5cb3f41..3a41bdea2 100644 --- a/ui/annotationactionhandler.cpp +++ b/ui/annotationactionhandler.cpp @@ -89,10 +89,7 @@ public: void populateQuickAnnotations(); KSelectAction *colorPickerAction(AnnotationColor colorType); - const QIcon colorIcon(const QColor &color); const QIcon widthIcon(double width); - const QIcon colorPickerIcon(const QString &iconName, const QColor &color); - const QIcon opacityIcon(double opacity); const QIcon stampIcon(const QString &stampIconName); void selectTool(int toolID); @@ -154,7 +151,7 @@ const QList> AnnotationActionHandlerPrivate::defaultColor const QList AnnotationActionHandlerPrivate::widthStandardValues = {1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5}; -const QList AnnotationActionHandlerPrivate::opacityStandardValues = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; +const QList AnnotationActionHandlerPrivate::opacityStandardValues = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}; QAction *AnnotationActionHandlerPrivate::selectActionItem(KSelectAction *aList, QAction *aCustomCurrent, double value, const QList &defaultValues, const QIcon &icon, const QString &label) { @@ -241,8 +238,8 @@ void AnnotationActionHandlerPrivate::parseTool(int toolID) // if the opacity value is not a default one, insert a new action in the opacity list if (annElement.hasAttribute(QStringLiteral("opacity"))) { - double opacity = 100 * annElement.attribute(QStringLiteral("opacity")).toDouble(); - aCustomOpacity = selectActionItem(aOpacity, aCustomOpacity, opacity, opacityStandardValues, opacityIcon(opacity), i18nc("@item:inlistbox", "%1\%", opacity)); + double opacity = annElement.attribute(QStringLiteral("opacity")).toDouble(); + aCustomOpacity = selectActionItem(aOpacity, aCustomOpacity, opacity, opacityStandardValues, GuiUtils::createOpacityIcon(opacity), i18nc("@item:inlistbox", "%1\%", opacity * 100)); } else { aOpacity->setCurrentItem(opacityStandardValues.size() - 1); // 100 % } @@ -269,11 +266,11 @@ void AnnotationActionHandlerPrivate::updateConfigActions(const QString &annotTyp const bool isStamp = annotType == QStringLiteral("stamp"); if (isTypewriter) { - aColor->setIcon(colorPickerIcon(QStringLiteral("format-text-color"), currentColor)); + aColor->setIcon(GuiUtils::createColorIcon({currentColor}, QIcon::fromTheme(QStringLiteral("format-text-color")))); } else { - aColor->setIcon(colorPickerIcon(QStringLiteral("format-stroke-color"), currentColor)); + aColor->setIcon(GuiUtils::createColorIcon({currentColor}, QIcon::fromTheme(QStringLiteral("format-stroke-color")))); } - aInnerColor->setIcon(colorPickerIcon(QStringLiteral("format-fill-color"), currentInnerColor)); + aInnerColor->setIcon(GuiUtils::createColorIcon({currentInnerColor}, QIcon::fromTheme(QStringLiteral("format-fill-color")))); aAddToQuickTools->setEnabled(isAnnotationSelected); aWidth->setEnabled(isLine || isShape); @@ -393,7 +390,7 @@ KSelectAction *AnnotationActionHandlerPrivate::colorPickerAction(AnnotationColor aColorPicker->setToolBarMode(KSelectAction::MenuMode); for (const auto &colorNameValue : colorList) { QColor color(colorNameValue.second); - QAction *aColor = new QAction(colorIcon(color), i18nc("@item:inlistbox Color name", "%1", colorNameValue.first), q); + QAction *aColor = new QAction(GuiUtils::createColorIcon({color}, QIcon(), GuiUtils::VisualizeTransparent), i18nc("@item:inlistbox Color name", "%1", colorNameValue.first), q); aColorPicker->addAction(aColor); QObject::connect(aColor, &QAction::triggered, q, [this, colorType, color]() { slotSetColor(colorType, color); }); } @@ -415,53 +412,6 @@ const QIcon AnnotationActionHandlerPrivate::widthIcon(double width) return QIcon(pm); } -const QIcon AnnotationActionHandlerPrivate::colorPickerIcon(const QString &iconName, const QColor &color) -{ - QIcon icon = QIcon::fromTheme(iconName); - if (!color.isValid()) { - return icon; - } - QSize iconSize = QSize(32, 32); - QPixmap pm = icon.pixmap(iconSize); - QPainter p(&pm); - p.fillRect(0, iconSize.height() - 8, iconSize.width(), 8, color); - p.end(); - return QIcon(pm); -} - -const QIcon AnnotationActionHandlerPrivate::colorIcon(const QColor &color) -{ - QSize iconSize = QSize(32, 32); - QPixmap pm(iconSize); - QPainter p(&pm); - if (color == Qt::transparent) { - p.fillRect(0, 0, iconSize.width(), iconSize.height(), Qt::white); - p.setPen(QPen(Qt::red, 2)); - p.drawLine(iconSize.width() - 1, 0, 0, iconSize.height() - 1); - } else { - p.fillRect(0, 0, iconSize.width(), iconSize.height(), color); - } - p.setPen(Qt::black); - p.drawRect(0, 0, iconSize.width() - 1, iconSize.height() - 1); - p.end(); - return QIcon(pm); -} - -const QIcon AnnotationActionHandlerPrivate::opacityIcon(double opacity) -{ - QPixmap pm(32, 32); - pm.fill(Qt::transparent); - QPainter p(&pm); - p.setRenderHint(QPainter::Antialiasing); - p.setPen(Qt::NoPen); - QColor color(Qt::black); - color.setAlpha(opacity * 255 / 100); - p.setBrush(QBrush(color)); - p.drawRect(4, 4, 24, 24); - p.end(); - return QIcon(pm); -} - const QIcon AnnotationActionHandlerPrivate::stampIcon(const QString &stampIconName) { QPixmap stampPix = GuiUtils::loadStamp(stampIconName, 32); @@ -683,10 +633,10 @@ AnnotationActionHandler::AnnotationActionHandler(PageViewAnnotator *parent, KAct // Opacity list d->aOpacity = new KSelectAction(QIcon::fromTheme(QStringLiteral("edit-opacity")), i18nc("@action:intoolbar Current annotation config option", "Opacity"), this); d->aOpacity->setToolBarMode(KSelectAction::MenuMode); - for (auto opacity : d->opacityStandardValues) { - KToggleAction *ann = new KToggleAction(d->opacityIcon(opacity), QStringLiteral("%1\%").arg(opacity), this); + for (double opacity : d->opacityStandardValues) { + KToggleAction *ann = new KToggleAction(GuiUtils::createOpacityIcon(opacity), QStringLiteral("%1\%").arg(opacity * 100), this); d->aOpacity->addAction(ann); - connect(ann, &QAction::triggered, this, [this, opacity]() { d->annotator->setAnnotationOpacity(opacity / 100); }); + connect(ann, &QAction::triggered, this, [this, opacity]() { d->annotator->setAnnotationOpacity(opacity); }); } connect(d->aAddToQuickTools, &QAction::triggered, d->annotator, &PageViewAnnotator::addToQuickAnnotations); diff --git a/ui/guiutils.cpp b/ui/guiutils.cpp index fa658ea93..ade0f3f89 100644 --- a/ui/guiutils.cpp +++ b/ui/guiutils.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -278,4 +279,93 @@ void colorizeImage(QImage &grayImage, const QColor &color, unsigned int destAlph } } +QIcon createColorIcon(const QList &colors, const QIcon &background, ColorIconFlags flags) +{ + QIcon colorIcon; + + // Create a pixmap for each common icon size. + for (int size : {16, 22, 24, 32, 48}) { + QPixmap pixmap(QSize(size, size) * qApp->devicePixelRatio()); + pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + // Configure hairlines for visualization of outline or transparency (visualizeTransparent): + painter.setPen(QPen(qApp->palette().color(QPalette::Active, QPalette::WindowText), 0)); + painter.setBrush(Qt::NoBrush); + + if (background.isNull()) { + // Full-size color rectangles. + // Draw rectangles left to right: + for (int i = 0; i < colors.count(); ++i) { + if (!colors.at(i).isValid()) { + continue; + } + QRect rect(QPoint(size * i / colors.count(), 0), QPoint(size * (i + 1) / colors.count(), size)); + if ((flags & VisualizeTransparent) && (colors.at(i) == Qt::transparent)) { + painter.drawLine(rect.topLeft(), rect.bottomRight()); + painter.drawLine(rect.bottomLeft(), rect.topRight()); + } else { + painter.fillRect(rect, colors.at(i)); + } + } + + // Draw hairline outline: + // To get the hairline on the outermost pixels, we shrink the rectangle by a half pixel on each edge. + const qreal halfPixelWidth = 0.5 / pixmap.devicePixelRatio(); + painter.drawRect(QRectF(QPointF(halfPixelWidth, halfPixelWidth), QPointF(qreal(size) - halfPixelWidth, qreal(size) - halfPixelWidth))); + } else { + // Lower 25% color rectangles. + // Draw background icon: + background.paint(&painter, QRect(QPoint(0, 0), QSize(size, size))); + + // Draw rectangles left to right: + for (int i = 0; i < colors.count(); ++i) { + if (!colors.at(i).isValid()) { + continue; + } + QRect rect(QPoint(size * i / colors.count(), size * 3 / 4), QPoint(size * (i + 1) / colors.count(), size)); + if ((flags & VisualizeTransparent) && (colors.at(i) == Qt::transparent)) { + painter.drawLine(rect.topLeft(), rect.bottomRight()); + painter.drawLine(rect.bottomLeft(), rect.topRight()); + } else { + painter.fillRect(rect, colors.at(i)); + } + } + } + + painter.end(); + colorIcon.addPixmap(pixmap); + } + + return colorIcon; +} + +QIcon createOpacityIcon(qreal opacity) +{ + QIcon opacityIcon; + + // Create a pixmap for each common icon size. + for (int size : {16, 22, 24, 32, 48}) { + QPixmap pixmap(QSize(size, size) * qApp->devicePixelRatio()); + pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + painter.setPen(Qt::NoPen); + painter.setBrush(qApp->palette().color(QPalette::Active, QPalette::WindowText)); + + // Checkerboard pattern + painter.drawRect(QRectF(QPoint(0, 0), QPoint(size, size) / 2)); + painter.drawRect(QRectF(QPoint(size, size) / 2, QPoint(size, size))); + + // Opacity + painter.setOpacity(opacity); + painter.drawRect(QRect(QPoint(0, 0), QPoint(size, size))); + + painter.end(); + opacityIcon.addPixmap(pixmap); + } + + return opacityIcon; +} + } diff --git a/ui/guiutils.h b/ui/guiutils.h index 8f39f5f1e..c20b9be03 100644 --- a/ui/guiutils.h +++ b/ui/guiutils.h @@ -10,9 +10,10 @@ #ifndef OKULAR_GUIUTILS_H #define OKULAR_GUIUTILS_H +#include +#include #include -class QColor; class QImage; class QPixmap; class QSize; @@ -65,6 +66,37 @@ Okular::Movie *renditionMovieFromScreenAnnotation(const Okular::ScreenAnnotation // colorize a gray image to the given color void colorizeImage(QImage &image, const QColor &color, unsigned int alpha = 255); + +enum ColorIconFlags { + NoFlags = 0x0, + VisualizeTransparent = 0x1 ///< Visualizes Qt::transparent with a cross. +}; + +/** + * Paints color rectangles on the lower 25% of an icon. + * + * If no icon is given, the whole icon square is filled, and a 1px border is added. + * + * Examples: + * * Different icons for different kinds of color selection, like fill and outline. + * * Selection of a color scheme preset, where each scheme has a different icon and ~1..3 specific colors. + * + * @param colors Which color rectangles to paint, from left to right (even on RTL). Colors may be transparent. Invalid colors are skipped. + * @param background Which icon to use as background. + * @param flags Special wishes. + * + * @returns A newly created QIcon. + */ +QIcon createColorIcon(const QList &colors, const QIcon &background = QIcon(), ColorIconFlags flags = NoFlags); + +/** + * Creates an opacity icon, using QPalette foreground color + * painted on top of a checkerboard pattern using @p opacity. + * + * @param opacity 0 = invisible, 1 = opaque. + */ +QIcon createOpacityIcon(qreal opacity); + } #endif