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.
bool operator<(const PosRange< Trait > &l, const PosRange< Trait > &r)
bool operator==(const StyleDelim &l, const StyleDelim &r)
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
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:17:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.