KOSMIndoorMap

iconloader.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "iconloader_p.h"
8
9#include <QBuffer>
10#include <QByteArray>
11#include <QDebug>
12#include <QFile>
13#include <QGuiApplication>
14#include <QIconEngine>
15#include <QImageReader>
16#include <QPainter>
17#include <QXmlStreamReader>
18#include <QXmlStreamWriter>
19
20using namespace KOSMIndoorMap;
21
22/** Device pixel ratio preserving simple icon engine for our SVG assets. */
23class IconEngine : public QIconEngine
24{
25public:
26 explicit IconEngine(QIODevice *svgFile, const IconData &iconData)
27 : m_iconData(iconData)
28 , m_image(renderStyledSvg(svgFile, iconData.size))
29 {
30 }
31
32 ~IconEngine() = default;
33
34 QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) override
35 {
36 Q_UNUSED(mode);
37 Q_UNUSED(state);
38 return { m_sourceSize.isValid() ? m_sourceSize : m_image.size() / m_image.devicePixelRatio() };
39 }
40
41 QIconEngine* clone() const override
42 {
43 auto engine = new IconEngine;
44 engine->m_image = m_image;
45 return engine;
46 }
47
48 void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
49
50 static QString findSvgAsset(const QString &name);
51
52private:
53 explicit IconEngine() = default;
54 QImage renderStyledSvg(QIODevice *svgFile, const QSizeF &size);
55
56 IconData m_iconData;
57 QImage m_image;
58 QSize m_sourceSize;
59};
60
61static bool operator<(const IconData &lhs, const IconData &rhs)
62{
63 if (lhs.name == rhs.name) {
64 if (lhs.color.rgb() == rhs.color.rgb()) {
65 return lhs.size.width() < rhs.size.width();
66 }
67 return lhs.color.rgb() < rhs.color.rgb();
68 }
69 return lhs.name < rhs.name;
70}
71
72static bool operator==(const IconData &lhs, const IconData &rhs)
73{
74 return lhs.name == rhs.name && lhs.color == rhs.color && lhs.size == rhs.size;
75}
76
77QIcon IconLoader::loadIcon(const IconData &iconData) const
78{
79 // check our cache
80 auto it = std::lower_bound(m_cache.begin(), m_cache.end(), iconData, [](const auto &lhs, const auto &rhs) { return lhs.data < rhs; });
81 if (it != m_cache.end() && (*it).data == iconData) {
82 return (*it).icon;
83 }
84
85 // check if it's one of our bundled assets
86 const QString path = IconEngine::findSvgAsset(iconData.name);
87 QFile f(path);
88 if (f.open(QFile::ReadOnly)) {
89 CacheEntry entry;
90 entry.data = iconData;
91 entry.icon = QIcon(new IconEngine(&f, iconData));
92 it = m_cache.insert(it, std::move(entry));
93 return (*it).icon;
94 }
95
96 // TODO file system URLs
97
98 // XDG icons
99 const auto icon = QIcon::fromTheme(iconData.name);
100 if (icon.isNull()) {
101 qWarning() << "Failed to find icon:" << iconData.name;
102 }
103 return icon;
104}
105
106QString IconEngine::findSvgAsset(const QString &name)
107{
108 return QLatin1String(":/org.kde.kosmindoormap/assets/icons/") + name + QLatin1String(".svg");
109}
110
111void IconEngine::paint(QPainter *painter, const QRect &rect, [[maybe_unused]] QIcon::Mode mode, [[maybe_unused]] QIcon::State state)
112{
113 // check if our pre-rendered image cache has a resolution high enough for this
114 const auto threshold = std::max<int>(1, std::max(m_image.width(), m_image.height()) * 0.25);
115 if (rect.width() > m_image.width() + threshold || rect.height() > m_image.height() + threshold) {
116 QFile f(findSvgAsset(m_iconData.name));
117 if (f.open(QFile::ReadOnly)) {
118 m_image = renderStyledSvg(&f, rect.size());
119 }
120 }
121
122 painter->drawImage(rect, m_image);
123}
124
125QImage IconEngine::renderStyledSvg(QIODevice *svgFile, const QSizeF &size)
126{
127 // prepare CSS
128 const QString css = QLatin1String(".ColorScheme-Text { color:") + m_iconData.color.name(QColor::HexRgb) + QLatin1String("; }");
129
130 // inject CSS (inspired by KIconLoader)
131 QByteArray processedContents;
132 QXmlStreamReader reader(svgFile);
133 QBuffer buffer(&processedContents);
134 buffer.open(QIODevice::WriteOnly);
135 QXmlStreamWriter writer(&buffer);
136 while (!reader.atEnd()) {
137 if (reader.readNext() == QXmlStreamReader::StartElement &&
138 reader.qualifiedName() == QLatin1String("style") &&
139 reader.attributes().value(QLatin1String("id")) == QLatin1String("current-color-scheme")) {
140 writer.writeStartElement(QStringLiteral("style"));
141 writer.writeAttributes(reader.attributes());
142 writer.writeCharacters(css);
143 writer.writeEndElement();
144 while (reader.tokenType() != QXmlStreamReader::EndElement) {
145 reader.readNext();
146 }
147 } else if (reader.tokenType() != QXmlStreamReader::Invalid) {
148 writer.writeCurrentToken(reader);
149 }
150 }
151 buffer.close();
152
153 // render SVG
154 buffer.open(QIODevice::ReadOnly);
155 buffer.seek(0);
156 QImageReader imgReader(&buffer, "svg");
157 m_sourceSize = imgReader.size();
158 imgReader.setScaledSize((size.isValid() ? size.toSize() : imgReader.size()) * qGuiApp->devicePixelRatio());
159 auto img = imgReader.read();
160 img.setDevicePixelRatio(qGuiApp->devicePixelRatio());
161 return img;
162}
QString path(const QString &relativePath)
OSM-based multi-floor indoor maps for buildings.
QIcon fromTheme(const QString &name)
qreal devicePixelRatio() const const
int height() const const
QSize size() const const
int width() const const
void drawImage(const QPoint &point, const QImage &image)
int height() const const
QSize size() const const
int width() const const
bool isValid() const const
bool isValid() const const
QSize toSize() const const
bool operator==(const QGraphicsApiFilter &reference, const QGraphicsApiFilter &sample)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.