From: alex-z Date: Fri, 29 Oct 2021 07:05:51 +0000 (+0300) Subject: Use SvgRenderer for Unified Search input icons. Refactor IconUtils. Extend unit tests. X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~18^2~120^2~1 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=0b8ab5c079e1cb1fc5ce7c7defe9e3b920889e36;p=nextcloud-desktop.git Use SvgRenderer for Unified Search input icons. Refactor IconUtils. Extend unit tests. Signed-off-by: alex-z --- diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index d276b63d3..c023f2357 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -121,6 +121,7 @@ set(client_SRCS userstatusselectormodel.cpp emojimodel.cpp fileactivitylistmodel.cpp + tray/svgimageprovider.cpp tray/syncstatussummary.cpp tray/activitydata.cpp tray/activitylistmodel.cpp diff --git a/src/gui/iconutils.cpp b/src/gui/iconutils.cpp index 82706e521..8ae50e2af 100644 --- a/src/gui/iconutils.cpp +++ b/src/gui/iconutils.cpp @@ -1,44 +1,132 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + #include "iconutils.h" #include #include +#include #include #include #include +namespace { +QString findSvgFilePath(const QString &fileName, const QStringList &possibleColors) +{ + const QString baseSvgNoColor{QString{OCC::Theme::themePrefix} + fileName}; + if (QFile::exists(baseSvgNoColor)) { + return baseSvgNoColor; + } + + for (const auto &color : possibleColors) { + const QString baseSVG{QString{OCC::Theme::themePrefix} + color + QLatin1Char('/') + fileName}; + + if (QFile::exists(baseSVG)) { + return baseSVG; + } + } + + return {}; +} +} + namespace OCC { namespace Ui { -namespace IconUtils { -QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundColor) -{ - Q_ASSERT(!fileName.isEmpty()); + Q_LOGGING_CATEGORY(lcIconUtils, "nextcloud.gui.iconutils", QtInfoMsg) + namespace IconUtils { + QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundColor) + { + Q_ASSERT(!fileName.isEmpty()); + + const auto pixmapColor = backgroundColor.isValid() + && !Theme::isDarkColor(backgroundColor) + ? QColorConstants::Svg::black + : QColorConstants::Svg::white; + + return createSvgPixmapWithCustomColor(fileName, pixmapColor); + } + + QPixmap createSvgPixmapWithCustomColor(const QString &fileName, const QColor &customColor, const QSize &size) + { + Q_ASSERT(!fileName.isEmpty()); + Q_ASSERT(customColor.isValid()); + + if (fileName.isEmpty()) { + qWarning(lcIconUtils) << "fileName is empty"; + } + + if (!customColor.isValid()) { + qWarning(lcIconUtils) << "customColor is invalid"; + } + + const auto customColorName = customColor.name(); + + const QString cacheKey = fileName + QLatin1Char(',') + customColorName; - // some icons are present in white or black only, so, we need to check both when needed - const auto iconBaseColors = QStringList({ QStringLiteral("black"), QStringLiteral("white") }); + QPixmap cachedPixmap; - const QString pixmapColor = backgroundColor.isValid() && !Theme::isDarkColor(backgroundColor) ? "black" : "white"; + // check for existing QPixmap in cache + if (QPixmapCache::find(cacheKey, &cachedPixmap)) { + return cachedPixmap; + } + + // some icons are present in white or black only, so, we need to check both when needed + const auto iconBaseColors = QStringList{QStringLiteral("black"), QStringLiteral("white")}; + + // check if there is an existing pixmap matching the custom color + if (iconBaseColors.contains(customColorName)) { + cachedPixmap = QPixmap::fromImage(QImage{QString{OCC::Theme::themePrefix} + customColorName + QLatin1Char('/') + fileName}); + QPixmapCache::insert(cacheKey, cachedPixmap); + return cachedPixmap; + } + + // find the first matching svg file + const auto sourceSvg = findSvgFilePath(fileName, iconBaseColors); - const QString cacheKey = fileName + QLatin1Char(',') + pixmapColor; + Q_ASSERT(!sourceSvg.isEmpty()); + if (sourceSvg.isEmpty()) { + qWarning(lcIconUtils) << "Failed to find base SVG file for" << cacheKey; + return {}; + } + + cachedPixmap = drawSvgWithCustomFillColor(sourceSvg, customColor, size); - QPixmap cachedPixmap; + Q_ASSERT(!cachedPixmap.isNull()); + if (cachedPixmap.isNull()) { + qWarning(lcIconUtils) << "Failed to load pixmap for" << cacheKey; + return {}; + } - if (!QPixmapCache::find(cacheKey, &cachedPixmap)) { - if (iconBaseColors.contains(pixmapColor)) { - cachedPixmap = QPixmap::fromImage(QImage(QString(Theme::themePrefix) + pixmapColor + QLatin1Char('/') + fileName)); QPixmapCache::insert(cacheKey, cachedPixmap); + return cachedPixmap; } - const auto drawSvgWithCustomFillColor = [](const QString &sourceSvgPath, const QString &fillColor) { + QPixmap drawSvgWithCustomFillColor(const QString &sourceSvgPath, const QColor &fillColor, const QSize &size) + { QSvgRenderer svgRenderer; if (!svgRenderer.load(sourceSvgPath)) { - return QPixmap(); + qCWarning(lcIconUtils) << "Could no load initial SVG image"; + return {}; } + const auto requestedSize = size.isValid() ? size : svgRenderer.defaultSize(); + // render source image - QImage svgImage(svgRenderer.defaultSize(), QImage::Format_ARGB32); + QImage svgImage(requestedSize, QImage::Format_ARGB32); { QPainter svgImagePainter(&svgImage); svgImage.fill(Qt::GlobalColor::transparent); @@ -46,7 +134,7 @@ QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundCol } // draw target image with custom fillColor - QImage image(svgRenderer.defaultSize(), QImage::Format_ARGB32); + QImage image(requestedSize, QImage::Format_ARGB32); image.fill(QColor(fillColor)); { QPainter imagePainter(&image); @@ -55,38 +143,7 @@ QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundCol } return QPixmap::fromImage(image); - }; - - // find the first matching svg among base colors, if any - const QString sourceSvg = [&]() { - for (const auto &color : iconBaseColors) { - const QString baseSVG(QString(Theme::themePrefix) + color + QLatin1Char('/') + fileName); - - if (QFile(baseSVG).exists()) { - return baseSVG; - } - } - return QString(); - }(); - - Q_ASSERT(!sourceSvg.isEmpty()); - if (sourceSvg.isEmpty()) { - qWarning("Failed to find base svg for %s", qPrintable(cacheKey)); - return {}; - } - - cachedPixmap = drawSvgWithCustomFillColor(sourceSvg, pixmapColor); - QPixmapCache::insert(cacheKey, cachedPixmap); - - Q_ASSERT(!cachedPixmap.isNull()); - if (cachedPixmap.isNull()) { - qWarning("Failed to load pixmap for %s", qPrintable(cacheKey)); - return {}; } } - - return cachedPixmap; -} -} } } diff --git a/src/gui/iconutils.h b/src/gui/iconutils.h index 136510a8a..168789db0 100644 --- a/src/gui/iconutils.h +++ b/src/gui/iconutils.h @@ -22,6 +22,8 @@ namespace OCC { namespace Ui { namespace IconUtils { QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundColor); +QPixmap createSvgPixmapWithCustomColor(const QString &fileName, const QColor &customColor, const QSize &size = {}); +QPixmap drawSvgWithCustomFillColor(const QString &sourceSvgPath, const QColor &fillColor, const QSize &size = {}); } } } diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index decb58805..ed9ed0a71 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -17,6 +17,7 @@ #include "theme.h" #include "config.h" #include "common/utility.h" +#include "tray/svgimageprovider.h" #include "tray/usermodel.h" #include "tray/unifiedsearchresultimageprovider.h" #include "configfile.h" @@ -59,6 +60,7 @@ void Systray::setTrayEngine(QQmlApplicationEngine *trayEngine) _trayEngine->addImportPath("qrc:/qml/theme"); _trayEngine->addImageProvider("avatars", new ImageProvider); + _trayEngine->addImageProvider(QLatin1String("svgimage-custom-color"), new OCC::Ui::SvgImageProvider); _trayEngine->addImageProvider(QLatin1String("unified-search-result-icon"), new UnifiedSearchResultImageProvider); } diff --git a/src/gui/tray/UnifiedSearchInputContainer.qml b/src/gui/tray/UnifiedSearchInputContainer.qml index e834944ab..3a021eda0 100644 --- a/src/gui/tray/UnifiedSearchInputContainer.qml +++ b/src/gui/tray/UnifiedSearchInputContainer.qml @@ -46,16 +46,8 @@ TextField { smooth: true; antialiasing: true mipmap: true - - source: "qrc:///client/theme/black/search.svg" + source: "image://svgimage-custom-color/search.svg" + "/" + trayWindowUnifiedSearchTextField.textFieldIconsColor sourceSize: Qt.size(parent.height * parent.textFieldIconsScaleFactor, parent.height * parent.textFieldIconsScaleFactor) - - ColorOverlay { - anchors.fill: parent - source: parent - cached: true - color: parent.parent.textFieldIconsColor - } } BusyIndicator { @@ -87,17 +79,9 @@ TextField { mipmap: true visible: parent.text - - source: "qrc:///client/theme/black/clear.svg" + source: "image://svgimage-custom-color/clear.svg" + "/" + trayWindowUnifiedSearchTextField.textFieldIconsColor sourceSize: Qt.size(parent.height * parent.textFieldIconsScaleFactor, parent.height * parent.textFieldIconsScaleFactor) - ColorOverlay { - anchors.fill: parent - cached: true - source: parent - color: parent.parent.textFieldIconsColor - } - MouseArea { id: trayWindowUnifiedSearchTextFieldClearTextButtonMouseArea diff --git a/src/gui/tray/svgimageprovider.cpp b/src/gui/tray/svgimageprovider.cpp new file mode 100644 index 000000000..2f8c1f0ba --- /dev/null +++ b/src/gui/tray/svgimageprovider.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "svgimageprovider.h" +#include "iconutils.h" + +#include + +namespace OCC { +namespace Ui { + Q_LOGGING_CATEGORY(lcSvgImageProvider, "nextcloud.gui.svgimageprovider", QtInfoMsg) + + SvgImageProvider::SvgImageProvider() + : QQuickImageProvider(QQuickImageProvider::Image) + { + } + + QImage SvgImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) + { + Q_UNUSED(size) + + Q_ASSERT(!id.isEmpty()); + + const auto idSplit = id.split(QLatin1Char('/'), Qt::SkipEmptyParts); + + if (idSplit.isEmpty()) { + qCWarning(lcSvgImageProvider) << "Image id is incorrect!"; + return {}; + } + + const auto pixmapName = idSplit.at(0); + const auto pixmapColor = idSplit.size() > 1 ? QColor(idSplit.at(1)) : QColorConstants::Svg::black; + + if (pixmapName.isEmpty() || !pixmapColor.isValid()) { + qCWarning(lcSvgImageProvider) << "Image id is incorrect!"; + return {}; + } + + return IconUtils::createSvgPixmapWithCustomColor(pixmapName, pixmapColor, requestedSize).toImage(); + } +} +} diff --git a/src/gui/tray/svgimageprovider.h b/src/gui/tray/svgimageprovider.h new file mode 100644 index 000000000..ea781b1a7 --- /dev/null +++ b/src/gui/tray/svgimageprovider.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include + +namespace OCC { +namespace Ui { + class SvgImageProvider : public QQuickImageProvider + { + public: + SvgImageProvider(); + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + }; +} +} diff --git a/test/testiconutils.cpp b/test/testiconutils.cpp index a25e9f0e6..cfdbe9478 100644 --- a/test/testiconutils.cpp +++ b/test/testiconutils.cpp @@ -29,12 +29,56 @@ public: } private slots: + void testDrawSvgWithCustomFillColor() + { + const QString blackSvgDirPath{QString{OCC::Theme::themePrefix} + QStringLiteral("black")}; + const QDir blackSvgDir(blackSvgDirPath); + const QStringList blackImages = blackSvgDir.entryList(QStringList("*.svg")); + + if (!blackImages.isEmpty()) { + QVERIFY(!OCC::Ui::IconUtils::drawSvgWithCustomFillColor(blackSvgDirPath + QLatin1Char('/') + blackImages.at(0), QColorConstants::Svg::red).isNull()); + } + + if (!blackImages.isEmpty()) { + QVERIFY(!OCC::Ui::IconUtils::drawSvgWithCustomFillColor(blackSvgDirPath + QLatin1Char('/') + blackImages.at(0), QColorConstants::Svg::green).isNull()); + } + + const QString whiteSvgDirPath{QString{OCC::Theme::themePrefix} + QStringLiteral("white")}; + const QDir whiteSvgDir(whiteSvgDirPath); + const QStringList whiteImages = whiteSvgDir.entryList(QStringList("*.svg")); + + if (!whiteImages.isEmpty()) { + QVERIFY(!OCC::Ui::IconUtils::drawSvgWithCustomFillColor(whiteSvgDirPath + QLatin1Char('/') + whiteImages.at(0), QColorConstants::Svg::blue).isNull()); + } + } + + void testCreateSvgPixmapWithCustomColor() + { + const QDir blackSvgDir(QString(QString{OCC::Theme::themePrefix}) + QStringLiteral("black")); + const QStringList blackImages = blackSvgDir.entryList(QStringList("*.svg")); + + if (!blackImages.isEmpty()) { + QVERIFY(!OCC::Ui::IconUtils::createSvgPixmapWithCustomColor(blackImages.at(0), QColorConstants::Svg::red).isNull()); + } + + if (!blackImages.isEmpty()) { + QVERIFY(!OCC::Ui::IconUtils::createSvgPixmapWithCustomColor(blackImages.at(0), QColorConstants::Svg::green).isNull()); + } + + const QDir whiteSvgDir(QString(QString{OCC::Theme::themePrefix}) + QStringLiteral("white")); + const QStringList whiteImages = whiteSvgDir.entryList(QStringList("*.svg")); + + if (!whiteImages.isEmpty()) { + QVERIFY(!OCC::Ui::IconUtils::createSvgPixmapWithCustomColor(whiteImages.at(0), QColorConstants::Svg::blue).isNull()); + } + } + void testPixmapForBackground() { - const QDir blackSvgDir(QString(OCC::Theme::themePrefix) + QStringLiteral("black")); + const QDir blackSvgDir(QString(QString{OCC::Theme::themePrefix}) + QStringLiteral("black")); const QStringList blackImages = blackSvgDir.entryList(QStringList("*.svg")); - const QDir whiteSvgDir(QString(OCC::Theme::themePrefix) + QStringLiteral("white")); + const QDir whiteSvgDir(QString(QString{OCC::Theme::themePrefix}) + QStringLiteral("white")); const QStringList whiteImages = whiteSvgDir.entryList(QStringList("*.svg")); if (blackImages.size() > 0) { @@ -46,17 +90,6 @@ private slots: // black pixmap for bright background - should not fail QVERIFY(!OCC::Ui::IconUtils::pixmapForBackground(blackImages.at(0), QColor("yellow")).isNull()); } - - const auto blackImagesExclusive = QSet(blackImages.begin(), blackImages.end()).subtract(QSet(whiteImages.begin(), whiteImages.end())); - const auto whiteImagesExclusive = QSet(whiteImages.begin(), whiteImages.end()).subtract(QSet(blackImages.begin(), blackImages.end())); - - if (blackImagesExclusive != whiteImagesExclusive) { - // black pixmap for dark background - should fail as we don't have this image in black - QVERIFY(OCC::Ui::IconUtils::pixmapForBackground(blackImagesExclusive.values().at(0), QColor("blue")).isNull()); - - // white pixmap for bright background - should fail as we don't have this image in white - QVERIFY(OCC::Ui::IconUtils::pixmapForBackground(whiteImagesExclusive.values().at(0), QColor("yellow")).isNull()); - } } };