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

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