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 | |
24 | namespace KSvg |
25 | { |
26 | SvgItem::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 | |
40 | SvgItem::~SvgItem() |
41 | { |
42 | } |
43 | |
44 | void 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 | |
81 | void 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 | |
97 | QString SvgItem::imagePath() const |
98 | { |
99 | return m_svg->imagePath(); |
100 | } |
101 | |
102 | void 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 | |
123 | QString SvgItem::elementId() const |
124 | { |
125 | return m_elementID; |
126 | } |
127 | |
128 | void 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 | |
158 | KSvg::Svg *SvgItem::svg() const |
159 | { |
160 | return m_svg.data(); |
161 | } |
162 | |
163 | QSizeF 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 | |
174 | QRectF 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 | |
185 | QSGNode *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 | |
230 | void 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 | |
241 | void SvgItem::scheduleImageUpdate() |
242 | { |
243 | polish(); |
244 | update(); |
245 | } |
246 | |
247 | void 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 | |
259 | void 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 | |
268 | void 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 | |
280 | void 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 | |