1/*
2 SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
3 SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "svgitem.h"
9
10#include <QDebug>
11#include <QQuickWindow>
12#include <QRectF>
13#include <QSGTexture>
14
15#include "ksvg/svg.h"
16
17#include "managedtexturenode.h"
18
19#include <cmath> //floor()
20
21#include <Kirigami/Platform/PlatformTheme>
22#include <debug_p.h>
23
24namespace KSvg
25{
26SvgItem::SvgItem(QQuickItem *parent)
27 : QQuickItem(parent)
28 , m_textureChanged(false)
29{
30 m_svg = new KSvg::Svg(this);
31 setFlag(flag: QQuickItem::ItemHasContents, enabled: true);
32
33 connect(sender: m_svg, signal: &Svg::repaintNeeded, context: this, slot: &SvgItem::updateNeeded);
34 connect(sender: m_svg, signal: &Svg::repaintNeeded, context: this, slot: &SvgItem::naturalSizeChanged);
35 connect(sender: m_svg, signal: &Svg::sizeChanged, context: this, slot: &SvgItem::naturalSizeChanged);
36 connect(sender: m_svg, signal: &Svg::repaintNeeded, context: this, slot: &SvgItem::elementRectChanged);
37 connect(sender: m_svg, signal: &Svg::sizeChanged, context: this, slot: &SvgItem::elementRectChanged);
38}
39
40SvgItem::~SvgItem()
41{
42}
43
44void SvgItem::componentComplete()
45{
46 m_kirigamiTheme = qobject_cast<Kirigami::Platform::PlatformTheme *>(object: qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(obj: this, create: true));
47 if (!m_kirigamiTheme) {
48 qCWarning(LOG_KSVGQML) << "No theme!" << qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(obj: this, create: true) << this;
49 return;
50 }
51
52 auto checkApplyTheme = [this]() {
53 if (!m_svg->imageSet()->filePath(QStringLiteral("colors")).isEmpty()) {
54 m_svg->clearColorOverrides();
55 }
56 };
57 auto applyTheme = [this]() {
58 if (!m_svg) {
59 return;
60 }
61 if (!m_svg->imageSet()->filePath(QStringLiteral("colors")).isEmpty()) {
62 m_svg->clearColorOverrides();
63 return;
64 }
65 m_svg->setColor(colorName: Svg::Text, color: m_kirigamiTheme->textColor());
66 m_svg->setColor(colorName: Svg::Background, color: m_kirigamiTheme->backgroundColor());
67 m_svg->setColor(colorName: Svg::Highlight, color: m_kirigamiTheme->highlightColor());
68 m_svg->setColor(colorName: Svg::HighlightedText, color: m_kirigamiTheme->highlightedTextColor());
69 m_svg->setColor(colorName: Svg::PositiveText, color: m_kirigamiTheme->positiveTextColor());
70 m_svg->setColor(colorName: Svg::NeutralText, color: m_kirigamiTheme->neutralTextColor());
71 m_svg->setColor(colorName: Svg::NegativeText, color: m_kirigamiTheme->negativeTextColor());
72 };
73 applyTheme();
74 connect(sender: m_kirigamiTheme, signal: &Kirigami::Platform::PlatformTheme::colorsChanged, context: this, slot&: applyTheme);
75 connect(sender: m_svg->imageSet(), signal: &ImageSet::imageSetChanged, context: this, slot&: checkApplyTheme);
76 connect(sender: m_svg, signal: &Svg::imageSetChanged, context: this, slot&: checkApplyTheme);
77
78 QQuickItem::componentComplete();
79}
80
81void SvgItem::setImagePath(const QString &path)
82{
83 if (!m_svg || m_svg->imagePath() == path) {
84 return;
85 }
86
87 updateDevicePixelRatio();
88 m_svg->setImagePath(path);
89
90 Q_EMIT imagePathChanged();
91
92 if (isComponentComplete()) {
93 update();
94 }
95}
96
97QString SvgItem::imagePath() const
98{
99 return m_svg->imagePath();
100}
101
102void SvgItem::setElementId(const QString &elementID)
103{
104 if (elementID == m_elementID) {
105 return;
106 }
107
108 if (implicitWidth() <= 0) {
109 setImplicitWidth(naturalSize().width());
110 }
111 if (implicitHeight() <= 0) {
112 setImplicitHeight(naturalSize().height());
113 }
114
115 m_elementID = elementID;
116 Q_EMIT elementIdChanged();
117 Q_EMIT naturalSizeChanged();
118 Q_EMIT elementRectChanged();
119
120 scheduleImageUpdate();
121}
122
123QString SvgItem::elementId() const
124{
125 return m_elementID;
126}
127
128void SvgItem::setSvg(KSvg::Svg *svg)
129{
130 if (m_svg) {
131 disconnect(sender: m_svg.data(), signal: nullptr, receiver: this, member: nullptr);
132 }
133 m_svg = svg;
134
135 if (svg) {
136 connect(sender: svg, signal: &Svg::repaintNeeded, context: this, slot: &SvgItem::updateNeeded);
137 connect(sender: svg, signal: &Svg::repaintNeeded, context: this, slot: &SvgItem::naturalSizeChanged);
138 connect(sender: svg, signal: &Svg::repaintNeeded, context: this, slot: &SvgItem::elementRectChanged);
139 connect(sender: svg, signal: &Svg::sizeChanged, context: this, slot: &SvgItem::naturalSizeChanged);
140 connect(sender: svg, signal: &Svg::sizeChanged, context: this, slot: &SvgItem::elementRectChanged);
141 }
142
143 if (implicitWidth() <= 0) {
144 setImplicitWidth(naturalSize().width());
145 }
146 if (implicitHeight() <= 0) {
147 setImplicitHeight(naturalSize().height());
148 }
149
150 scheduleImageUpdate();
151
152 Q_EMIT svgChanged();
153 Q_EMIT naturalSizeChanged();
154 Q_EMIT elementRectChanged();
155 Q_EMIT imagePathChanged();
156}
157
158KSvg::Svg *SvgItem::svg() const
159{
160 return m_svg.data();
161}
162
163QSizeF SvgItem::naturalSize() const
164{
165 if (!m_svg) {
166 return QSizeF();
167 } else if (!m_elementID.isEmpty()) {
168 return m_svg->elementSize(elementId: m_elementID);
169 }
170
171 return m_svg->size();
172}
173
174QRectF SvgItem::elementRect() const
175{
176 if (!m_svg) {
177 return QRectF();
178 } else if (!m_elementID.isEmpty()) {
179 return m_svg->elementRect(elementId: m_elementID);
180 }
181
182 return QRectF(QPointF(0, 0), m_svg->size());
183}
184
185QSGNode *SvgItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
186{
187 Q_UNUSED(updatePaintNodeData);
188 if (!window() || !m_svg) {
189 delete oldNode;
190 return nullptr;
191 }
192
193 // this is more than just an optimization, uploading a null image to QSGAtlasTexture causes a crash
194 if (width() == 0.0 || height() == 0.0) {
195 delete oldNode;
196 return nullptr;
197 }
198
199 ManagedTextureNode *textureNode = static_cast<ManagedTextureNode *>(oldNode);
200 if (!textureNode) {
201 textureNode = new ManagedTextureNode;
202 m_textureChanged = true;
203 }
204
205 // TODO use a heuristic to work out when to redraw
206 // if !m_smooth and size is approximate simply change the textureNode.rect without
207 // updating the material
208
209 if (m_textureChanged || textureNode->texture()->textureSize() != QSize(width(), height())) {
210 // despite having a valid size sometimes we still get a null QImage from KSvg::Svg
211 // loading a null texture to an atlas fatals
212 // Dave E fixed this in Qt in 5.3.something onwards but we need this for now
213 if (m_image.isNull()) {
214 delete textureNode;
215 return nullptr;
216 }
217
218 QSharedPointer<QSGTexture> texture(window()->createTextureFromImage(image: m_image, options: QQuickWindow::TextureCanUseAtlas));
219 textureNode->setTexture(texture);
220 m_textureChanged = false;
221
222 textureNode->setRect(x: 0, y: 0, w: width(), h: height());
223 }
224
225 textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
226
227 return textureNode;
228}
229
230void SvgItem::updateNeeded()
231{
232 if (implicitWidth() <= 0) {
233 setImplicitWidth(naturalSize().width());
234 }
235 if (implicitHeight() <= 0) {
236 setImplicitHeight(naturalSize().height());
237 }
238 scheduleImageUpdate();
239}
240
241void SvgItem::scheduleImageUpdate()
242{
243 polish();
244 update();
245}
246
247void SvgItem::updatePolish()
248{
249 QQuickItem::updatePolish();
250
251 if (m_svg) {
252 // setContainsMultipleImages has to be done there since m_svg can be shared with somebody else
253 m_textureChanged = true;
254 m_svg->setContainsMultipleImages(!m_elementID.isEmpty());
255 m_image = m_svg->image(size: QSize(width(), height()), elementID: m_elementID);
256 }
257}
258
259void SvgItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
260{
261 if (newGeometry.size() != oldGeometry.size() && newGeometry.isValid()) {
262 scheduleImageUpdate();
263 }
264
265 QQuickItem::geometryChange(newGeometry, oldGeometry);
266}
267
268void SvgItem::updateDevicePixelRatio()
269{
270 // devicepixelratio is always set integer in the svg, so needs at least 192dpi to double up.
271 //(it needs to be integer to have lines contained inside a svg piece to keep being pixel aligned)
272 const auto newDevicePixelRatio = std::max<qreal>(a: 1.0, b: floor(x: window() ? window()->devicePixelRatio() : qApp->devicePixelRatio()));
273
274 if (newDevicePixelRatio != m_svg->devicePixelRatio()) {
275 m_svg->setDevicePixelRatio(newDevicePixelRatio);
276 m_textureChanged = true;
277 }
278}
279
280void SvgItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
281{
282 if (change == ItemSceneChange && value.window) {
283 updateDevicePixelRatio();
284 } else if (change == QQuickItem::ItemDevicePixelRatioHasChanged) {
285 updateDevicePixelRatio();
286 }
287
288 QQuickItem::itemChange(change, value);
289}
290
291} // KSvg namespace
292
293#include "moc_svgitem.cpp"
294

source code of ksvg/src/declarativeimports/svgitem.cpp