File indexing completed on 2025-01-12 03:40:53

0001 /*
0002     SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "svgitem.h"
0009 
0010 #include <QDebug>
0011 #include <QQuickWindow>
0012 #include <QRectF>
0013 #include <QSGTexture>
0014 
0015 #include "ksvg/svg.h"
0016 
0017 #include "managedtexturenode.h"
0018 
0019 #include <cmath> //floor()
0020 
0021 #include <Kirigami/Platform/PlatformTheme>
0022 #include <debug_p.h>
0023 
0024 namespace KSvg
0025 {
0026 SvgItem::SvgItem(QQuickItem *parent)
0027     : QQuickItem(parent)
0028     , m_textureChanged(false)
0029 {
0030     m_svg = new KSvg::Svg(this);
0031     setFlag(QQuickItem::ItemHasContents, true);
0032 
0033     connect(m_svg, &Svg::repaintNeeded, this, &SvgItem::updateNeeded);
0034     connect(m_svg, &Svg::repaintNeeded, this, &SvgItem::naturalSizeChanged);
0035     connect(m_svg, &Svg::sizeChanged, this, &SvgItem::naturalSizeChanged);
0036     connect(m_svg, &Svg::repaintNeeded, this, &SvgItem::elementRectChanged);
0037     connect(m_svg, &Svg::sizeChanged, this, &SvgItem::elementRectChanged);
0038 }
0039 
0040 SvgItem::~SvgItem()
0041 {
0042 }
0043 
0044 void SvgItem::componentComplete()
0045 {
0046     m_kirigamiTheme = qobject_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
0047     if (!m_kirigamiTheme) {
0048         qCWarning(LOG_KSVGQML) << "No theme!" << qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true) << this;
0049         return;
0050     }
0051 
0052     auto checkApplyTheme = [this]() {
0053         if (!m_svg->imageSet()->filePath(QStringLiteral("colors")).isEmpty()) {
0054             m_svg->clearColorOverrides();
0055         }
0056     };
0057     auto applyTheme = [this]() {
0058         if (!m_svg) {
0059             return;
0060         }
0061         if (!m_svg->imageSet()->filePath(QStringLiteral("colors")).isEmpty()) {
0062             m_svg->clearColorOverrides();
0063             return;
0064         }
0065         m_svg->setColor(Svg::Text, m_kirigamiTheme->textColor());
0066         m_svg->setColor(Svg::Background, m_kirigamiTheme->backgroundColor());
0067         m_svg->setColor(Svg::Highlight, m_kirigamiTheme->highlightColor());
0068         m_svg->setColor(Svg::HighlightedText, m_kirigamiTheme->highlightedTextColor());
0069         m_svg->setColor(Svg::PositiveText, m_kirigamiTheme->positiveTextColor());
0070         m_svg->setColor(Svg::NeutralText, m_kirigamiTheme->neutralTextColor());
0071         m_svg->setColor(Svg::NegativeText, m_kirigamiTheme->negativeTextColor());
0072     };
0073     applyTheme();
0074     connect(m_kirigamiTheme, &Kirigami::Platform::PlatformTheme::colorsChanged, this, applyTheme);
0075     connect(m_svg->imageSet(), &ImageSet::imageSetChanged, this, checkApplyTheme);
0076     connect(m_svg, &Svg::imageSetChanged, this, checkApplyTheme);
0077 
0078     QQuickItem::componentComplete();
0079 }
0080 
0081 void SvgItem::setImagePath(const QString &path)
0082 {
0083     if (!m_svg || m_svg->imagePath() == path) {
0084         return;
0085     }
0086 
0087     updateDevicePixelRatio();
0088     m_svg->setImagePath(path);
0089 
0090     Q_EMIT imagePathChanged();
0091 
0092     if (isComponentComplete()) {
0093         update();
0094     }
0095 }
0096 
0097 QString SvgItem::imagePath() const
0098 {
0099     return m_svg->imagePath();
0100 }
0101 
0102 void SvgItem::setElementId(const QString &elementID)
0103 {
0104     if (elementID == m_elementID) {
0105         return;
0106     }
0107 
0108     if (implicitWidth() <= 0) {
0109         setImplicitWidth(naturalSize().width());
0110     }
0111     if (implicitHeight() <= 0) {
0112         setImplicitHeight(naturalSize().height());
0113     }
0114 
0115     m_elementID = elementID;
0116     Q_EMIT elementIdChanged();
0117     Q_EMIT naturalSizeChanged();
0118     Q_EMIT elementRectChanged();
0119 
0120     scheduleImageUpdate();
0121 }
0122 
0123 QString SvgItem::elementId() const
0124 {
0125     return m_elementID;
0126 }
0127 
0128 void SvgItem::setSvg(KSvg::Svg *svg)
0129 {
0130     if (m_svg) {
0131         disconnect(m_svg.data(), nullptr, this, nullptr);
0132     }
0133     m_svg = svg;
0134 
0135     if (svg) {
0136         connect(svg, &Svg::repaintNeeded, this, &SvgItem::updateNeeded);
0137         connect(svg, &Svg::repaintNeeded, this, &SvgItem::naturalSizeChanged);
0138         connect(svg, &Svg::repaintNeeded, this, &SvgItem::elementRectChanged);
0139         connect(svg, &Svg::sizeChanged, this, &SvgItem::naturalSizeChanged);
0140         connect(svg, &Svg::sizeChanged, this, &SvgItem::elementRectChanged);
0141     }
0142 
0143     if (implicitWidth() <= 0) {
0144         setImplicitWidth(naturalSize().width());
0145     }
0146     if (implicitHeight() <= 0) {
0147         setImplicitHeight(naturalSize().height());
0148     }
0149 
0150     scheduleImageUpdate();
0151 
0152     Q_EMIT svgChanged();
0153     Q_EMIT naturalSizeChanged();
0154     Q_EMIT elementRectChanged();
0155     Q_EMIT imagePathChanged();
0156 }
0157 
0158 KSvg::Svg *SvgItem::svg() const
0159 {
0160     return m_svg.data();
0161 }
0162 
0163 QSizeF SvgItem::naturalSize() const
0164 {
0165     if (!m_svg) {
0166         return QSizeF();
0167     } else if (!m_elementID.isEmpty()) {
0168         return m_svg->elementSize(m_elementID);
0169     }
0170 
0171     return m_svg->size();
0172 }
0173 
0174 QRectF SvgItem::elementRect() const
0175 {
0176     if (!m_svg) {
0177         return QRectF();
0178     } else if (!m_elementID.isEmpty()) {
0179         return m_svg->elementRect(m_elementID);
0180     }
0181 
0182     return QRectF(QPointF(0, 0), m_svg->size());
0183 }
0184 
0185 QSGNode *SvgItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
0186 {
0187     Q_UNUSED(updatePaintNodeData);
0188     if (!window() || !m_svg) {
0189         delete oldNode;
0190         return nullptr;
0191     }
0192 
0193     // this is more than just an optimization, uploading a null image to QSGAtlasTexture causes a crash
0194     if (width() == 0.0 || height() == 0.0) {
0195         delete oldNode;
0196         return nullptr;
0197     }
0198 
0199     ManagedTextureNode *textureNode = static_cast<ManagedTextureNode *>(oldNode);
0200     if (!textureNode) {
0201         textureNode = new ManagedTextureNode;
0202         m_textureChanged = true;
0203     }
0204 
0205     // TODO use a heuristic to work out when to redraw
0206     // if !m_smooth and size is approximate simply change the textureNode.rect without
0207     // updating the material
0208 
0209     if (m_textureChanged || textureNode->texture()->textureSize() != QSize(width(), height())) {
0210         // despite having a valid size sometimes we still get a null QImage from KSvg::Svg
0211         // loading a null texture to an atlas fatals
0212         // Dave E fixed this in Qt in 5.3.something onwards but we need this for now
0213         if (m_image.isNull()) {
0214             delete textureNode;
0215             return nullptr;
0216         }
0217 
0218         QSharedPointer<QSGTexture> texture(window()->createTextureFromImage(m_image, QQuickWindow::TextureCanUseAtlas));
0219         textureNode->setTexture(texture);
0220         m_textureChanged = false;
0221 
0222         textureNode->setRect(0, 0, width(), height());
0223     }
0224 
0225     textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
0226 
0227     return textureNode;
0228 }
0229 
0230 void SvgItem::updateNeeded()
0231 {
0232     if (implicitWidth() <= 0) {
0233         setImplicitWidth(naturalSize().width());
0234     }
0235     if (implicitHeight() <= 0) {
0236         setImplicitHeight(naturalSize().height());
0237     }
0238     scheduleImageUpdate();
0239 }
0240 
0241 void SvgItem::scheduleImageUpdate()
0242 {
0243     polish();
0244     update();
0245 }
0246 
0247 void SvgItem::updatePolish()
0248 {
0249     QQuickItem::updatePolish();
0250 
0251     if (m_svg) {
0252         // setContainsMultipleImages has to be done there since m_svg can be shared with somebody else
0253         m_textureChanged = true;
0254         m_svg->setContainsMultipleImages(!m_elementID.isEmpty());
0255         m_image = m_svg->image(QSize(width(), height()), m_elementID);
0256     }
0257 }
0258 
0259 void SvgItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
0260 {
0261     if (newGeometry.size() != oldGeometry.size() && newGeometry.isValid()) {
0262         scheduleImageUpdate();
0263     }
0264 
0265     QQuickItem::geometryChange(newGeometry, oldGeometry);
0266 }
0267 
0268 void SvgItem::updateDevicePixelRatio()
0269 {
0270     // devicepixelratio is always set integer in the svg, so needs at least 192dpi to double up.
0271     //(it needs to be integer to have lines contained inside a svg piece to keep being pixel aligned)
0272     const auto newDevicePixelRatio = std::max<qreal>(1.0, floor(window() ? window()->devicePixelRatio() : qApp->devicePixelRatio()));
0273 
0274     if (newDevicePixelRatio != m_svg->devicePixelRatio()) {
0275         m_svg->setDevicePixelRatio(newDevicePixelRatio);
0276         m_textureChanged = true;
0277     }
0278 }
0279 
0280 void SvgItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
0281 {
0282     if (change == ItemSceneChange && value.window) {
0283         updateDevicePixelRatio();
0284     } else if (change == QQuickItem::ItemDevicePixelRatioHasChanged) {
0285         updateDevicePixelRatio();
0286     }
0287 
0288     QQuickItem::itemChange(change, value);
0289 }
0290 
0291 } // KSvg namespace
0292 
0293 #include "moc_svgitem.cpp"