diff --git a/src/declarativeimports/core/framesvgitem.cpp b/src/declarativeimports/core/framesvgitem.cpp index 6f528459e..922b347bc 100644 --- a/src/declarativeimports/core/framesvgitem.cpp +++ b/src/declarativeimports/core/framesvgitem.cpp @@ -1,626 +1,644 @@ /* * Copyright 2010 Marco Martin * Copyright 2014 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "framesvgitem.h" #include #include #include #include #include #include #include #include #include #include //floor() namespace Plasma { Q_GLOBAL_STATIC(ImageTexturesCache, s_cache) class FrameNode : public QSGNode { public: FrameNode(const QString& prefix, FrameSvg* svg) : QSGNode() , leftWidth(0) , rightWidth(0) , topHeight(0) , bottomHeight(0) { if (svg->enabledBorders() & FrameSvg::LeftBorder) leftWidth = svg->elementSize(prefix % QLatin1String("left")).width(); if (svg->enabledBorders() & FrameSvg::RightBorder) rightWidth = svg->elementSize(prefix % QLatin1String("right")).width(); if (svg->enabledBorders() & FrameSvg::TopBorder) topHeight = svg->elementSize(prefix % QLatin1String("top")).height(); if (svg->enabledBorders() & FrameSvg::BottomBorder) bottomHeight = svg->elementSize(prefix % QLatin1String("bottom")).height(); } QRect contentsRect(const QSize& size) const { const QSize contentSize(size.width() - leftWidth - rightWidth, size.height() - topHeight - bottomHeight); return QRect(QPoint(leftWidth, topHeight), contentSize); } private: int leftWidth; int rightWidth; int topHeight; int bottomHeight; }; class FrameItemNode : public ManagedTextureNode { public: enum FitMode { //render SVG at native resolution then stretch it in openGL FastStretch, //on resize re-render the part of the frame from the SVG Stretch, Tile }; FrameItemNode(FrameSvgItem* frameSvg, FrameSvg::EnabledBorders borders, FitMode fitMode, QSGNode* parent) : ManagedTextureNode() , m_frameSvg(frameSvg) , m_border(borders) , m_lastParent(parent) , m_fitMode(fitMode) { m_lastParent->appendChildNode(this); if (m_fitMode == Tile) { if (m_border == FrameSvg::TopBorder || m_border == FrameSvg::BottomBorder || m_border == FrameSvg::NoBorder) { static_cast(material())->setHorizontalWrapMode(QSGTexture::Repeat); static_cast(opaqueMaterial())->setHorizontalWrapMode(QSGTexture::Repeat); } if (m_border == FrameSvg::LeftBorder || m_border == FrameSvg::RightBorder || m_border == FrameSvg::NoBorder) { static_cast(material())->setVerticalWrapMode(QSGTexture::Repeat); static_cast(opaqueMaterial())->setVerticalWrapMode(QSGTexture::Repeat); } } if (m_fitMode == Tile || m_fitMode == FastStretch) { QString elementId = m_frameSvg->frameSvg()->actualPrefix() + FrameSvgHelpers::borderToElementId(m_border); m_elementNativeSize = m_frameSvg->frameSvg()->elementSize(elementId); if (m_elementNativeSize.isEmpty()) { //if the default element is empty, we can avoid the slower tiling path //this also avoids a divide by 0 error m_fitMode = FastStretch; } updateTexture(m_elementNativeSize, elementId); } } void updateTexture(const QSize &size, const QString &elementId) { QQuickWindow::CreateTextureOptions options; //Qt < 5.3.2. has a crash on some atlas textures #if (QT_VERSION > QT_VERSION_CHECK(5, 3, 2)) if (m_fitMode != Tile) { options = QQuickWindow::TextureCanUseAtlas; } #endif setTexture(s_cache->loadTexture(m_frameSvg->window(), m_frameSvg->frameSvg()->image(size, elementId), options)); } void reposition(const QRect& frameGeometry, QSize& fullSize) { QRect nodeRect = FrameSvgHelpers::sectionRect(m_border, frameGeometry, fullSize); //ensure we're not passing a weird rectangle to updateTexturedRectGeometry if(!nodeRect.isValid() || nodeRect.isEmpty()) nodeRect = QRect(); //the position of the relevant texture within this texture ID. //for atlas' this will only be a small part of the texture QRectF textureRect; if (m_fitMode == Tile) { textureRect = QRectF(0,0,1,1); //we can never be in an atlas for tiled images. //if tiling horizontally if (m_border == FrameSvg::TopBorder || m_border == FrameSvg::BottomBorder || m_border == FrameSvg::NoBorder) { textureRect.setWidth(nodeRect.width() / m_elementNativeSize.width()); } //if tiling vertically if (m_border == FrameSvg::LeftBorder || m_border == FrameSvg::RightBorder || m_border == FrameSvg::NoBorder) { textureRect.setHeight(nodeRect.height() / m_elementNativeSize.height()); } } else if (m_fitMode == Stretch) { QString prefix = m_frameSvg->frameSvg()->actualPrefix(); QString elementId = prefix + FrameSvgHelpers::borderToElementId(m_border); //re-render the SVG at new size updateTexture(nodeRect.size(), elementId); textureRect = texture()->normalizedTextureSubRect(); } else if (texture()) { // for fast stretch. textureRect = texture()->normalizedTextureSubRect(); } QSGGeometry::updateTexturedRectGeometry(geometry(), nodeRect, textureRect); markDirty(QSGNode::DirtyGeometry); } private: FrameSvgItem* m_frameSvg; FrameSvg::EnabledBorders m_border; QSGNode *m_lastParent; QSize m_elementNativeSize; FitMode m_fitMode; }; FrameSvgItemMargins::FrameSvgItemMargins(Plasma::FrameSvg *frameSvg, QObject *parent) : QObject(parent), m_frameSvg(frameSvg), m_fixed(false) { //qDebug() << "margins at: " << left() << top() << right() << bottom(); } qreal FrameSvgItemMargins::left() const { if (m_fixed) { return m_frameSvg->fixedMarginSize(Types::LeftMargin); } else { return m_frameSvg->marginSize(Types::LeftMargin); } } qreal FrameSvgItemMargins::top() const { if (m_fixed) { return m_frameSvg->fixedMarginSize(Types::TopMargin); } else { return m_frameSvg->marginSize(Types::TopMargin); } } qreal FrameSvgItemMargins::right() const { if (m_fixed) { return m_frameSvg->fixedMarginSize(Types::RightMargin); } else { return m_frameSvg->marginSize(Types::RightMargin); } } qreal FrameSvgItemMargins::bottom() const { if (m_fixed) { return m_frameSvg->fixedMarginSize(Types::BottomMargin); } else { return m_frameSvg->marginSize(Types::BottomMargin); } } qreal FrameSvgItemMargins::horizontal() const { return left() + right(); } qreal FrameSvgItemMargins::vertical() const { return top() + bottom(); } +QVector FrameSvgItemMargins::margins() const +{ + qreal left, top, right, bottom; + m_frameSvg->getMargins(left, top, right, bottom); + return {left, top, right, bottom}; +} + void FrameSvgItemMargins::update() { emit marginsChanged(); } void FrameSvgItemMargins::setFixed(bool fixed) { if (fixed == m_fixed) { return; } m_fixed = fixed; emit marginsChanged(); } bool FrameSvgItemMargins::isFixed() const { return m_fixed; } FrameSvgItem::FrameSvgItem(QQuickItem *parent) : QQuickItem(parent), m_margins(nullptr), m_fixedMargins(nullptr), m_textureChanged(false), m_sizeChanged(false), m_fastPath(true) { m_frameSvg = new Plasma::FrameSvg(this); setFlag(ItemHasContents, true); connect(m_frameSvg, &FrameSvg::repaintNeeded, this, &FrameSvgItem::doUpdate); connect(&Units::instance(), &Units::devicePixelRatioChanged, this, &FrameSvgItem::updateDevicePixelRatio); connect(m_frameSvg, &Svg::fromCurrentThemeChanged, this, &FrameSvgItem::fromCurrentThemeChanged); connect(m_frameSvg, &Svg::statusChanged, this, &FrameSvgItem::statusChanged); } FrameSvgItem::~FrameSvgItem() { } +class CheckMarginsChange +{ +public: + CheckMarginsChange(FrameSvgItemMargins *margins) + : m_oldMargins(margins ? margins->margins() : QVector()), m_margins(margins) + {} + + ~CheckMarginsChange() { + if (m_margins && m_margins->margins() != m_oldMargins) { + m_margins->update(); + } + } + + const QVector m_oldMargins; + FrameSvgItemMargins *const m_margins; +}; + void FrameSvgItem::setImagePath(const QString &path) { if (m_frameSvg->imagePath() == path) { return; } + CheckMarginsChange checkMargins(m_margins); + CheckMarginsChange checkFixedMargins(m_fixedMargins); + updateDevicePixelRatio(); m_frameSvg->setImagePath(path); if (implicitWidth() <= 0) { setImplicitWidth(m_frameSvg->marginSize(Plasma::Types::LeftMargin) + m_frameSvg->marginSize(Plasma::Types::RightMargin)); } if (implicitHeight() <= 0) { setImplicitHeight(m_frameSvg->marginSize(Plasma::Types::TopMargin) + m_frameSvg->marginSize(Plasma::Types::BottomMargin)); } emit imagePathChanged(); - if (m_margins) { - m_margins->update(); - } - if (m_fixedMargins) { - m_fixedMargins->update(); - } if (isComponentComplete()) { applyPrefixes(); m_frameSvg->resizeFrame(QSizeF(width(), height())); m_textureChanged = true; update(); } } QString FrameSvgItem::imagePath() const { return m_frameSvg->imagePath(); } void FrameSvgItem::setPrefix(const QVariant &prefixes) { QStringList prefixList; //is this a simple string? if (prefixes.canConvert()) { prefixList << prefixes.toString(); } else if (prefixes.canConvert()) { prefixList = prefixes.toStringList(); } if (m_prefixes == prefixList) { return; } + CheckMarginsChange checkMargins(m_margins); + CheckMarginsChange checkFixedMargins(m_fixedMargins); + m_prefixes = prefixList; applyPrefixes(); if (implicitWidth() <= 0) { setImplicitWidth(m_frameSvg->marginSize(Plasma::Types::LeftMargin) + m_frameSvg->marginSize(Plasma::Types::RightMargin)); } if (implicitHeight() <= 0) { setImplicitHeight(m_frameSvg->marginSize(Plasma::Types::TopMargin) + m_frameSvg->marginSize(Plasma::Types::BottomMargin)); } emit prefixChanged(); - if (m_margins) { - m_margins->update(); - } - if (m_fixedMargins) { - m_fixedMargins->update(); - } if (isComponentComplete()) { m_frameSvg->resizeFrame(QSizeF(width(), height())); m_textureChanged = true; update(); } } QVariant FrameSvgItem::prefix() const { return m_prefixes; } QString FrameSvgItem::usedPrefix() const { return m_frameSvg->prefix(); } FrameSvgItemMargins *FrameSvgItem::margins() { if (!m_margins) { m_margins = new FrameSvgItemMargins(m_frameSvg, this); } return m_margins; } FrameSvgItemMargins *FrameSvgItem::fixedMargins() { if (!m_fixedMargins) { m_fixedMargins = new FrameSvgItemMargins(m_frameSvg, this); m_fixedMargins->setFixed(true); } return m_fixedMargins; } void FrameSvgItem::setColorGroup(Plasma::Theme::ColorGroup group) { if (m_frameSvg->colorGroup() == group) { return; } m_frameSvg->setColorGroup(group); emit colorGroupChanged(); } Plasma::Theme::ColorGroup FrameSvgItem::colorGroup() const { return m_frameSvg->colorGroup(); } bool FrameSvgItem::fromCurrentTheme() const { return m_frameSvg->fromCurrentTheme(); } void FrameSvgItem::setStatus(Plasma::Svg::Status status) { m_frameSvg->setStatus(status); } Plasma::Svg::Status FrameSvgItem::status() const { return m_frameSvg->status(); } void FrameSvgItem::setEnabledBorders(const Plasma::FrameSvg::EnabledBorders borders) { if (m_frameSvg->enabledBorders() == borders) { return; } + CheckMarginsChange checkMargins(m_margins); + m_frameSvg->setEnabledBorders(borders); emit enabledBordersChanged(); m_textureChanged = true; - if (m_margins) { - m_margins->update(); - } update(); } Plasma::FrameSvg::EnabledBorders FrameSvgItem::enabledBorders() const { return m_frameSvg->enabledBorders(); } bool FrameSvgItem::hasElementPrefix(const QString &prefix) const { return m_frameSvg->hasElementPrefix(prefix); } void FrameSvgItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { if (isComponentComplete()) { m_frameSvg->resizeFrame(newGeometry.size()); m_sizeChanged = true; } QQuickItem::geometryChanged(newGeometry, oldGeometry); } void FrameSvgItem::doUpdate() { + CheckMarginsChange checkMargins(m_margins); + CheckMarginsChange checkFixedMargins(m_fixedMargins); + //if the theme changed, the available prefix may have changed as well applyPrefixes(); if (implicitWidth() <= 0) { setImplicitWidth(m_frameSvg->marginSize(Plasma::Types::LeftMargin) + m_frameSvg->marginSize(Plasma::Types::RightMargin)); } if (implicitHeight() <= 0) { setImplicitHeight(m_frameSvg->marginSize(Plasma::Types::TopMargin) + m_frameSvg->marginSize(Plasma::Types::BottomMargin)); } QString prefix = m_frameSvg->actualPrefix(); bool hasOverlay = !prefix.startsWith(QLatin1String("mask-")) && m_frameSvg->hasElement(prefix % QLatin1String("overlay")); bool hasComposeOverBorder = m_frameSvg->hasElement(prefix % QLatin1String("hint-compose-over-border")) && m_frameSvg->hasElement(QLatin1String("mask-") % prefix % QLatin1String("center")); m_fastPath = !hasOverlay && !hasComposeOverBorder; //software rendering (at time of writing Qt5.10) doesn't seem to like our tiling/stretching in the 9-tiles. //also when using QPainter it's arguably faster to create and cache pixmaps of the whole frame, which is what the slow path does #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) if (QQuickWindow::sceneGraphBackend() == QLatin1String("software")) { m_fastPath = false; } #endif m_textureChanged = true; update(); - if (m_margins) { - m_margins->update(); - } - if (m_fixedMargins) { - m_fixedMargins->update(); - } + emit repaintNeeded(); } Plasma::FrameSvg *FrameSvgItem::frameSvg() const { return m_frameSvg; } QSGNode *FrameSvgItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) { if (!window() || !m_frameSvg || (!m_frameSvg->hasElementPrefix(m_frameSvg->actualPrefix()) && !m_frameSvg->hasElementPrefix(m_frameSvg->prefix()))) { delete oldNode; return nullptr; } if (m_fastPath) { if (m_textureChanged) { delete oldNode; oldNode = 0; } if (!oldNode) { QString prefix = m_frameSvg->actualPrefix(); oldNode = new FrameNode(prefix, m_frameSvg); bool tileCenter = (m_frameSvg->hasElement(QStringLiteral("hint-tile-center")) || m_frameSvg->hasElement(prefix % QLatin1String("hint-tile-center"))); bool stretchBorders = (m_frameSvg->hasElement(QStringLiteral("hint-stretch-borders")) || m_frameSvg->hasElement(prefix % QLatin1String("hint-stretch-borders"))); FrameItemNode::FitMode borderFitMode = stretchBorders ? FrameItemNode::Stretch : FrameItemNode::Tile; FrameItemNode::FitMode centerFitMode = tileCenter ? FrameItemNode::Tile: FrameItemNode::Stretch; new FrameItemNode(this, FrameSvg::NoBorder, centerFitMode, oldNode); new FrameItemNode(this, FrameSvg::TopBorder | FrameSvg::LeftBorder, FrameItemNode::FastStretch, oldNode); new FrameItemNode(this, FrameSvg::TopBorder | FrameSvg::RightBorder, FrameItemNode::FastStretch, oldNode); new FrameItemNode(this, FrameSvg::TopBorder, borderFitMode, oldNode); new FrameItemNode(this, FrameSvg::BottomBorder, borderFitMode, oldNode); new FrameItemNode(this, FrameSvg::BottomBorder | FrameSvg::LeftBorder, FrameItemNode::FastStretch, oldNode); new FrameItemNode(this, FrameSvg::BottomBorder | FrameSvg::RightBorder, FrameItemNode::FastStretch, oldNode); new FrameItemNode(this, FrameSvg::LeftBorder, borderFitMode, oldNode); new FrameItemNode(this, FrameSvg::RightBorder, borderFitMode, oldNode); m_sizeChanged = true; m_textureChanged = false; } if (m_sizeChanged) { FrameNode* frameNode = static_cast(oldNode); QSize frameSize(width(), height()); QRect geometry = frameNode->contentsRect(frameSize); QSGNode *node = oldNode->firstChild(); while (node) { static_cast(node)->reposition(geometry, frameSize); node = node->nextSibling(); } m_sizeChanged = false; } } else { ManagedTextureNode *textureNode = dynamic_cast(oldNode); if (!textureNode) { delete oldNode; textureNode = new ManagedTextureNode; textureNode->setFiltering(QSGTexture::Nearest); m_textureChanged = true; //force updating the texture on our newly created node oldNode = textureNode; } if ((m_textureChanged || m_sizeChanged) || textureNode->texture()->textureSize() != m_frameSvg->size()) { QImage image = m_frameSvg->framePixmap().toImage(); textureNode->setTexture(s_cache->loadTexture(window(), image)); textureNode->setRect(0, 0, width(), height()); m_textureChanged = false; m_sizeChanged = false; } } return oldNode; } void FrameSvgItem::classBegin() { QQuickItem::classBegin(); m_frameSvg->setRepaintBlocked(true); } void FrameSvgItem::componentComplete() { + CheckMarginsChange checkMargins(m_margins); + CheckMarginsChange checkFixedMargins(m_fixedMargins); + QQuickItem::componentComplete(); m_frameSvg->resizeFrame(QSize(width(), height())); m_frameSvg->setRepaintBlocked(false); m_textureChanged = true; } void FrameSvgItem::updateDevicePixelRatio() { //devicepixelratio is always set integer in the svg, so needs at least 192dpi to double up. //(it needs to be integer to have lines contained inside a svg piece to keep being pixel aligned) if (window()) { m_frameSvg->setDevicePixelRatio(qMax(1.0, floor(window()->devicePixelRatio()))); } else { m_frameSvg->setDevicePixelRatio(qMax(1.0, floor(qApp->devicePixelRatio()))); } m_frameSvg->setScaleFactor(qMax(1.0, floor(Units::instance().devicePixelRatio()))); m_textureChanged = true; } void FrameSvgItem::applyPrefixes() { if (m_frameSvg->imagePath().isEmpty()) { return; } const QString oldPrefix = m_frameSvg->prefix(); if (m_prefixes.isEmpty()) { m_frameSvg->setElementPrefix(QString()); if (oldPrefix != m_frameSvg->prefix()) { emit usedPrefixChanged(); } return; } bool found = false; for (const QString &prefix : m_prefixes) { if (m_frameSvg->hasElementPrefix(prefix)) { m_frameSvg->setElementPrefix(prefix); found = true; break; } } if (!found) { //this setElementPrefix is done to keep the same behavior as before, when it was a simple string m_frameSvg->setElementPrefix(m_prefixes.last()); } if (oldPrefix != m_frameSvg->prefix()) { emit usedPrefixChanged(); } } } // Plasma namespace diff --git a/src/declarativeimports/core/framesvgitem.h b/src/declarativeimports/core/framesvgitem.h index c5bcdad57..82d91d583 100644 --- a/src/declarativeimports/core/framesvgitem.h +++ b/src/declarativeimports/core/framesvgitem.h @@ -1,263 +1,266 @@ /*************************************************************************** * Copyright 2010 Marco Martin * * Copyright 2014 David Edmundson * * * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef FRAMESVGITEM_P #define FRAMESVGITEM_P #include #include #include #include "units.h" namespace Plasma { class FrameSvg; /** * @class FrameSvgItemMargins * * @short The sizes of a frame's margins. */ class FrameSvgItemMargins : public QObject { Q_OBJECT /** * Width in pixels of the left margin. */ Q_PROPERTY(qreal left READ left NOTIFY marginsChanged) /** * Height in pixels of the top margin. */ Q_PROPERTY(qreal top READ top NOTIFY marginsChanged) /** * Width in pixels of the right margin. */ Q_PROPERTY(qreal right READ right NOTIFY marginsChanged) /** * Height in pixels of the bottom margin. */ Q_PROPERTY(qreal bottom READ bottom NOTIFY marginsChanged) /** * Width in pixels of the left and right margins combined. */ Q_PROPERTY(qreal horizontal READ horizontal NOTIFY marginsChanged) /** * Height in pixels of the top and bottom margins combined. */ Q_PROPERTY(qreal vertical READ vertical NOTIFY marginsChanged) public: FrameSvgItemMargins(Plasma::FrameSvg *frameSvg, QObject *parent = 0); qreal left() const; qreal top() const; qreal right() const; qreal bottom() const; qreal horizontal() const; qreal vertical() const; + /// returns a vector with left, top, right, bottom + QVector margins() const; + void setFixed(bool fixed); bool isFixed() const; public Q_SLOTS: void update(); Q_SIGNALS: void marginsChanged(); private: FrameSvg *m_frameSvg; bool m_fixed; }; /** * @class FrameSvgItem * * @short Provides an SVG with borders. * * It is exposed as org.kde.plasma.core.FrameSvgItem */ class FrameSvgItem : public QQuickItem { Q_OBJECT Q_INTERFACES(QQmlParserStatus) /** * Theme relative path of the svg, like "widgets/background" */ Q_PROPERTY(QString imagePath READ imagePath WRITE setImagePath NOTIFY imagePathChanged) /** * prefix for the 9 piece svg, like "pushed" or "normal" for the button * see http://techbase.kde.org/Development/Tutorials/Plasma/ThemeDetails * for a list of paths and prefixes * It can also be an array of strings, specifying a fallback chain in case * the first element isn't found in the theme, eg ["toolbutton-normal", "normal"] * so it's easy to keep backwards compatibility with old themes * (Note: fallback chain is supported only @since 5.32) */ Q_PROPERTY(QVariant prefix READ prefix WRITE setPrefix NOTIFY prefixChanged) /** * the actual prefix that was used, if a fallback chain array was set as "prefix" * @since 5.34 */ Q_PROPERTY(QString usedPrefix READ usedPrefix NOTIFY usedPrefixChanged) /** * The margins of the frame, read only * @see FrameSvgItemMargins */ Q_PROPERTY(QObject *margins READ margins CONSTANT) /** * The margins of the frame, regardless if they are enabled or not * read only * @see FrameSvgItemMargins */ Q_PROPERTY(QObject *fixedMargins READ fixedMargins CONSTANT) /** * The borders that will be rendered, it's a flag combination of: * NoBorder * TopBorder * BottomBorder * LeftBorder * RightBorder */ Q_PROPERTY(Plasma::FrameSvg::EnabledBorders enabledBorders READ enabledBorders WRITE setEnabledBorders NOTIFY enabledBordersChanged) /** * Holds whether the current svg is present in the current theme and NO fallback is involved */ Q_PROPERTY(bool fromCurrentTheme READ fromCurrentTheme NOTIFY fromCurrentThemeChanged) /** * Set a color group for the FrameSvgItem. * if the Svg uses stylesheets and has elements * that are eithe TextColor or BackgroundColor class, * make them use ButtonTextColor/ButtonBackgroundColor * or ViewTextColor/ViewBackgroundColor, ComplementaryTextColor etc. */ Q_PROPERTY(Plasma::Theme::ColorGroup colorGroup READ colorGroup WRITE setColorGroup NOTIFY colorGroupChanged) /** * Sets the image in a selected status. * Svgs can be colored with system color themes, if the status is selected, * the TextColor will become HighlightedText color and BackgroundColor * will become HighlightColor, making the svg graphics (for instance an icon) * will look correct together selected text * @see Plasma::Svg::status * @since 5.23 */ Q_PROPERTY(Plasma::Svg::Status status READ status WRITE setStatus NOTIFY statusChanged) public: /** * @return true if the svg has the necessary elements with the given prefix * to draw a frame * @param prefix the given prefix we want to check if drawable */ Q_INVOKABLE bool hasElementPrefix(const QString &prefix) const; /// @cond INTERNAL_DOCS FrameSvgItem(QQuickItem *parent = 0); ~FrameSvgItem(); void setImagePath(const QString &path); QString imagePath() const; void setPrefix(const QVariant &prefix); QVariant prefix() const; QString usedPrefix() const; void setEnabledBorders(const Plasma::FrameSvg::EnabledBorders borders); Plasma::FrameSvg::EnabledBorders enabledBorders() const; FrameSvgItemMargins *margins(); FrameSvgItemMargins *fixedMargins(); void setColorGroup(Plasma::Theme::ColorGroup group); Plasma::Theme::ColorGroup colorGroup() const; bool fromCurrentTheme() const; void setStatus(Plasma::Svg::Status status); Plasma::Svg::Status status() const; void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; /** * Only to be used from inside this library, is not intended to be invokable */ Plasma::FrameSvg *frameSvg() const; QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; protected: void classBegin() Q_DECL_OVERRIDE; void componentComplete() Q_DECL_OVERRIDE; /// @endcond Q_SIGNALS: void imagePathChanged(); void prefixChanged(); void enabledBordersChanged(); void fromCurrentThemeChanged(); void colorGroupChanged(); void repaintNeeded(); void statusChanged(); void usedPrefixChanged(); private Q_SLOTS: void doUpdate(); void updateDevicePixelRatio(); private: void applyPrefixes(); Plasma::FrameSvg *m_frameSvg; FrameSvgItemMargins *m_margins; FrameSvgItemMargins *m_fixedMargins; QStringList m_prefixes; bool m_textureChanged; bool m_sizeChanged; bool m_fastPath; }; } #endif diff --git a/src/plasma/framesvg.cpp b/src/plasma/framesvg.cpp index 3696b09ce..6c1b24145 100644 --- a/src/plasma/framesvg.cpp +++ b/src/plasma/framesvg.cpp @@ -1,1034 +1,1034 @@ /* * Copyright 2008-2010 by Aaron Seigo * Copyright 2008-2010 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "framesvg.h" #include "private/framesvg_p.h" #include #include #include #include #include #include #include #include #include #include "theme.h" #include "private/svg_p.h" #include "private/framesvg_helpers.h" #include "debug_p.h" namespace Plasma { QHash > FrameSvgPrivate::s_sharedFrames; // Any attempt to generate a frame whose width or height is larger than this // will be rejected static const int MAX_FRAME_SIZE = 100000; FrameData::~FrameData() { for (auto it = references.constBegin(), end = references.constEnd(); it != end; ++it) { if (it.key()->d->frame == this) { it.key()->d->frame = nullptr; } } } FrameSvg::FrameSvg(QObject *parent) : Svg(parent), d(new FrameSvgPrivate(this)) { connect(this, &FrameSvg::repaintNeeded, this, std::bind(&FrameSvgPrivate::updateNeeded, d)); d->frame = nullptr; } FrameSvg::~FrameSvg() { delete d; } void FrameSvg::setImagePath(const QString &path) { if (path == imagePath()) { return; } clearCache(); setContainsMultipleImages(true); Svg::d->setImagePath(path); if (!d->repaintBlocked) { d->updateFrameData(); } } void FrameSvg::setEnabledBorders(const EnabledBorders borders) { if (borders == d->enabledBorders) { return; } d->enabledBorders = borders; if (!d->repaintBlocked) { d->updateFrameData(); } } FrameSvg::EnabledBorders FrameSvg::enabledBorders() const { return d->enabledBorders; } void FrameSvg::setElementPrefix(Plasma::Types::Location location) { switch (location) { case Types::TopEdge: setElementPrefix(QStringLiteral("north")); break; case Types::BottomEdge: setElementPrefix(QStringLiteral("south")); break; case Types::LeftEdge: setElementPrefix(QStringLiteral("west")); break; case Types::RightEdge: setElementPrefix(QStringLiteral("east")); break; default: setElementPrefix(QString()); break; } d->location = location; } void FrameSvg::setElementPrefix(const QString &prefix) { if (!hasElement(prefix % QLatin1String("-center"))) { d->prefix.clear(); } else { d->prefix = prefix; if (!d->prefix.isEmpty()) { d->prefix += '-'; } } d->requestedPrefix = prefix; d->location = Types::Floating; if (!d->repaintBlocked) { d->updateFrameData(); } } bool FrameSvg::hasElementPrefix(const QString &prefix) const { //for now it simply checks if a center element exists, //because it could make sense for certain themes to not have all the elements if (prefix.isEmpty()) { return hasElement(QStringLiteral("center")); } else { return hasElement(prefix % QLatin1String("-center")); } } bool FrameSvg::hasElementPrefix(Plasma::Types::Location location) const { switch (location) { case Types::TopEdge: return hasElementPrefix(QStringLiteral("north")); break; case Types::BottomEdge: return hasElementPrefix(QStringLiteral("south")); break; case Types::LeftEdge: return hasElementPrefix(QStringLiteral("west")); break; case Types::RightEdge: return hasElementPrefix(QStringLiteral("east")); break; default: return hasElementPrefix(QString()); break; } } QString FrameSvg::prefix() { return d->requestedPrefix; } void FrameSvg::resizeFrame(const QSizeF &size) { if (imagePath().isEmpty()) { return; } if (size.isEmpty()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Invalid size" << size; #endif return; } if (d->frame && size.toSize() == d->frame->frameSize) { return; } d->pendingFrameSize = size.toSize(); if (!d->repaintBlocked) { d->updateFrameData(FrameSvgPrivate::UpdateFrame); } } QSizeF FrameSvg::frameSize() const { if (!d->frame) { return QSize(-1, -1); } else { return d->frameSize(d->frame); } } qreal FrameSvg::marginSize(const Plasma::Types::MarginEdge edge) const { if (!d->frame) { return .0; } if (d->frame->noBorderPadding) { return .0; } switch (edge) { case Plasma::Types::TopMargin: return d->frame->topMargin; break; case Plasma::Types::LeftMargin: return d->frame->leftMargin; break; case Plasma::Types::RightMargin: return d->frame->rightMargin; break; //Plasma::BottomMargin default: return d->frame->bottomMargin; break; } } qreal FrameSvg::fixedMarginSize(const Plasma::Types::MarginEdge edge) const { if (!d->frame) { return .0; } if (d->frame->noBorderPadding) { return .0; } switch (edge) { case Plasma::Types::TopMargin: return d->frame->fixedTopMargin; break; case Plasma::Types::LeftMargin: return d->frame->fixedLeftMargin; break; case Plasma::Types::RightMargin: return d->frame->fixedRightMargin; break; //Plasma::BottomMargin default: return d->frame->fixedBottomMargin; break; } } void FrameSvg::getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const { - if (d->frame->noBorderPadding) { + if (!d->frame || d->frame->noBorderPadding) { left = top = right = bottom = 0; return; } top = d->frame->topMargin; left = d->frame->leftMargin; right = d->frame->rightMargin; bottom = d->frame->bottomMargin; } void FrameSvg::getFixedMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const { - if (d->frame->noBorderPadding) { + if (!d->frame || d->frame->noBorderPadding) { left = top = right = bottom = 0; return; } top = d->frame->fixedTopMargin; left = d->frame->fixedLeftMargin; right = d->frame->fixedRightMargin; bottom = d->frame->fixedBottomMargin; } QRectF FrameSvg::contentsRect() const { if (d->frame) { QRectF rect(QPoint(0,0), d->frame->frameSize); return rect.adjusted(d->frame->leftMargin, d->frame->topMargin, -d->frame->rightMargin, -d->frame->bottomMargin); } else { return QRectF(); } } QPixmap FrameSvg::alphaMask() const { //FIXME: the distinction between overlay and return d->alphaMask(); } QRegion FrameSvg::mask() const { QString id = d->cacheId(d->frame, QString()); QRegion* obj = d->frame->cachedMasks.object(id); QRegion result; if (!obj) { obj = new QRegion(QBitmap(d->alphaMask().mask())); result = *obj; d->frame->cachedMasks.insert(id, obj); } else { result = *obj; } return result; } void FrameSvg::setCacheAllRenderedFrames(bool cache) { if (d->cacheAll && !cache) { clearCache(); } d->cacheAll = cache; } bool FrameSvg::cacheAllRenderedFrames() const { return d->cacheAll; } void FrameSvg::clearCache() { if (d->frame) { d->frame->cachedBackground = QPixmap(); } if (d->maskFrame) { d->maskFrame->cachedBackground = QPixmap(); } } QPixmap FrameSvg::framePixmap() { if (d->frame->cachedBackground.isNull()) { d->generateBackground(d->frame); } return d->frame->cachedBackground; } void FrameSvg::paintFrame(QPainter *painter, const QRectF &target, const QRectF &source) { if (d->frame->cachedBackground.isNull()) { d->generateBackground(d->frame); if (d->frame->cachedBackground.isNull()) { return; } } painter->drawPixmap(target, d->frame->cachedBackground, source.isValid() ? source : target); } void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos) { if (d->frame->cachedBackground.isNull()) { d->generateBackground(d->frame); if (d->frame->cachedBackground.isNull()) { return; } } painter->drawPixmap(pos, d->frame->cachedBackground); } //#define DEBUG_FRAMESVG_CACHE FrameSvgPrivate::~FrameSvgPrivate() { #ifdef DEBUG_FRAMESVG_CACHE #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "*************" << q << q->imagePath() << "****************"; #endif #endif // we remove all references from this widget to the frame, and delete it if we're the // last user if (frame && frame->removeRefs(q)) { const QString key = cacheId(frame, frame->prefix); #ifdef DEBUG_FRAMESVG_CACHE #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "2. Removing it" << key << frame << frame->refcount() << s_sharedFrames[theme()->d].contains(key); #endif #endif s_sharedFrames[frame->theme].remove(key); delete frame; } //same thing for maskFrame if (maskFrame && maskFrame->removeRefs(q)) { const QString key = cacheId(maskFrame, maskFrame->prefix); s_sharedFrames[maskFrame->theme].remove(key); delete maskFrame; } #ifdef DEBUG_FRAMESVG_CACHE QHashIterator it2(s_sharedFrames[theme()->d]); int shares = 0; while (it2.hasNext()) { it2.next(); const int rc = it2.value()->refcount(); if (rc == 0) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << " LOST!" << it2.key() << rc << it2.value();// << it2.value()->references; #endif } else { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << " " << it2.key() << rc << it2.value(); #endif foreach (FrameSvg *data, it2.value()->references.keys()) { #ifndef NDEBUG qCDebug(LOG_PLASMA) << " " << (void *)data << it2.value()->references[data]; #endif } shares += rc - 1; } } #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "#####################################" << s_sharedFrames[theme()->d].count() << ", pixmaps saved:" << shares; #endif #endif frame = nullptr; maskFrame = nullptr; } QPixmap FrameSvgPrivate::alphaMask() { QString maskPrefix; if (q->hasElement(QLatin1String("mask-") % prefix % QLatin1String("center"))) { maskPrefix = QStringLiteral("mask-"); } if (maskPrefix.isNull()) { if (frame->cachedBackground.isNull()) { generateBackground(frame); if (frame->cachedBackground.isNull()) { return QPixmap(); } } return frame->cachedBackground; } else { // We are setting the prefix only temporary to generate // the needed mask image const QString maskRequestedPrefix = requestedPrefix.isEmpty() ? QStringLiteral("mask") : maskPrefix % requestedPrefix; maskPrefix = maskPrefix % prefix; if (!maskFrame) { const QString key = cacheId(frame, maskPrefix); // see if we can find a suitable candidate in the shared frames // if successful, ref and insert, otherwise create a new one // and insert that into both the shared frames and our frames. maskFrame = s_sharedFrames[q->theme()->d].value(key); if (maskFrame) { maskFrame->ref(q); } else { maskFrame = new FrameData(*frame, q); maskFrame->prefix = maskPrefix; maskFrame->requestedPrefix = maskRequestedPrefix; maskFrame->theme = q->theme()->d; maskFrame->imagePath = q->imagePath(); s_sharedFrames[q->theme()->d].insert(key, maskFrame); } maskFrame->enabledBorders = frame->enabledBorders; updateSizes(maskFrame); } const QString oldKey = cacheId(maskFrame, maskPrefix); maskFrame->enabledBorders = frame->enabledBorders; if (maskFrame->cachedBackground.isNull() || maskFrame->frameSize != frameSize(frame)) { maskFrame->frameSize = frameSize(frame).toSize(); const QString newKey = cacheId(maskFrame, maskPrefix); if (s_sharedFrames[q->theme()->d].contains(oldKey)) { s_sharedFrames[q->theme()->d].remove(oldKey); s_sharedFrames[q->theme()->d].insert(newKey, maskFrame); } maskFrame->cachedBackground = QPixmap(); generateBackground(maskFrame); if (maskFrame->cachedBackground.isNull()) { return QPixmap(); } } return maskFrame->cachedBackground; } } void FrameSvgPrivate::generateBackground(FrameData *frame) { if (!frame->cachedBackground.isNull() || !q->hasElementPrefix(frame->requestedPrefix)) { return; } const QString id = cacheId(frame, frame->prefix); bool frameCached = !frame->cachedBackground.isNull(); bool overlayCached = false; const bool overlayAvailable = !frame->prefix.startsWith(QLatin1String("mask-")) && q->hasElement(frame->prefix % QLatin1String("overlay")); QPixmap overlay; if (q->isUsingRenderingCache()) { frameCached = q->theme()->findInCache(id, frame->cachedBackground) && !frame->cachedBackground.isNull(); if (overlayAvailable) { overlayCached = q->theme()->findInCache(QLatin1String("overlay_") % id, overlay) && !overlay.isNull(); } } if (!frameCached) { generateFrameBackground(frame); } //Overlays QSize overlaySize; QPoint actualOverlayPos = QPoint(0, 0); if (overlayAvailable && !overlayCached) { overlaySize = q->elementSize(frame->prefix % QLatin1String("overlay")); if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-pos-right"))) { actualOverlayPos.setX(frame->frameSize.width() - overlaySize.width()); } else if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-pos-bottom"))) { actualOverlayPos.setY(frame->frameSize.height() - overlaySize.height()); //Stretched or Tiled? } else if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-stretch"))) { overlaySize = frameSize(frame).toSize(); } else { if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-horizontal"))) { overlaySize.setWidth(frameSize(frame).width()); } if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-vertical"))) { overlaySize.setHeight(frameSize(frame).height()); } } overlay = alphaMask(); QPainter overlayPainter(&overlay); overlayPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); //Tiling? if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-horizontal")) || q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-vertical"))) { QSize s = q->size(); q->resize(q->elementSize(frame->prefix % QLatin1String("overlay"))); overlayPainter.drawTiledPixmap(QRect(QPoint(0, 0), overlaySize), q->pixmap(frame->prefix % QLatin1String("overlay"))); q->resize(s); } else { q->paint(&overlayPainter, QRect(actualOverlayPos, overlaySize), frame->prefix % QLatin1String("overlay")); } overlayPainter.end(); } if (!frameCached) { cacheFrame(frame->prefix, frame->cachedBackground, overlayCached ? overlay : QPixmap()); } if (!overlay.isNull()) { QPainter p(&frame->cachedBackground); p.setCompositionMode(QPainter::CompositionMode_SourceOver); p.drawPixmap(actualOverlayPos, overlay, QRect(actualOverlayPos, overlaySize)); } } void FrameSvgPrivate::generateFrameBackground(FrameData *frame) { //qCDebug(LOG_PLASMA) << "generating background"; const QSize size = frameSize(frame).toSize() * q->devicePixelRatio(); if (!size.isValid()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Invalid frame size" << size; #endif return; } if (size.width() >= MAX_FRAME_SIZE || size.height() >= MAX_FRAME_SIZE) { qCWarning(LOG_PLASMA) << "Not generating frame background for a size whose width or height is more than" << MAX_FRAME_SIZE << size; return; } frame->cachedBackground = QPixmap(size); frame->cachedBackground.fill(Qt::transparent); QPainter p(&frame->cachedBackground); p.setCompositionMode(QPainter::CompositionMode_Source); p.setRenderHint(QPainter::SmoothPixmapTransform); QRect contentRect = contentGeometry(frame, size); paintCenter(p, frame, contentRect, size); paintCorner(p, frame, FrameSvg::LeftBorder|FrameSvg::TopBorder, contentRect); paintCorner(p, frame, FrameSvg::RightBorder|FrameSvg::TopBorder, contentRect); paintCorner(p, frame, FrameSvg::LeftBorder|FrameSvg::BottomBorder, contentRect); paintCorner(p, frame, FrameSvg::RightBorder|FrameSvg::BottomBorder, contentRect); // Sides const int leftHeight = q->elementSize(frame->prefix % QLatin1String("left")).height(); paintBorder(p, frame, FrameSvg::LeftBorder, QSize(frame->leftWidth, leftHeight) * q->devicePixelRatio(), contentRect); paintBorder(p, frame, FrameSvg::RightBorder, QSize(frame->rightWidth, leftHeight) * q->devicePixelRatio(), contentRect); const int topWidth = q->elementSize(frame->prefix % QLatin1String("top")).width(); paintBorder(p, frame, FrameSvg::TopBorder, QSize(topWidth, frame->topHeight) * q->devicePixelRatio(), contentRect); paintBorder(p, frame, FrameSvg::BottomBorder, QSize(topWidth, frame->bottomHeight) * q->devicePixelRatio(), contentRect); p.end(); frame->cachedBackground.setDevicePixelRatio(q->devicePixelRatio()); } QRect FrameSvgPrivate::contentGeometry(FrameData* frame, const QSize& size) const { const QSize contentSize(size.width() - frame->leftWidth * q->devicePixelRatio() - frame->rightWidth * q->devicePixelRatio(), size.height() - frame->topHeight * q->devicePixelRatio() - frame->bottomHeight * q->devicePixelRatio()); QRect contentRect(QPoint(0,0), contentSize); if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(frame->prefix % QLatin1String("left"))) { contentRect.translate(frame->leftWidth * q->devicePixelRatio(), 0); } // Corners if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(frame->prefix % QLatin1String("top"))) { contentRect.translate(0, frame->topHeight * q->devicePixelRatio()); } return contentRect; } void FrameSvgPrivate::updateFrameData(UpdateType updateType) { FrameData *fd = frame; QString newKey; if (fd) { const QString oldKey = cacheId(fd, fd->prefix); const QString oldPath = fd->imagePath; const FrameSvg::EnabledBorders oldBorders = fd->enabledBorders; const QSize currentSize = fd->frameSize; fd->enabledBorders = enabledBorders; fd->frameSize = pendingFrameSize; fd->imagePath = q->imagePath(); newKey = cacheId(fd, prefix); //reset frame to old values fd->enabledBorders = oldBorders; fd->frameSize = currentSize; fd->imagePath = oldPath; //FIXME: something more efficient than string comparison? if (oldKey == newKey) { return; } //qCDebug(LOG_PLASMA) << "looking for" << newKey; FrameData *newFd = FrameSvgPrivate::s_sharedFrames[q->theme()->d].value(newKey); if (newFd) { //qCDebug(LOG_PLASMA) << "FOUND IT!" << newFd->refcount; // we've found a math, so insert that new one and ref it .. newFd->ref(q); frame = newFd; //.. then deref the old one and if it's no longer used, get rid of it if (fd->deref(q)) { //const QString oldKey = cacheId(fd, prefix); //qCDebug(LOG_PLASMA) << "1. Removing it" << oldKey << fd->refcount; FrameSvgPrivate::s_sharedFrames[fd->theme].remove(oldKey); delete fd; } return; } if (fd->refcount() == 1) { // we're the only user of it, let's remove it from the shared keys // we don't want to deref it, however, as we'll still be using it FrameSvgPrivate::s_sharedFrames[fd->theme].remove(oldKey); } else { // others are using it, but we wish to change its size. so deref it, // then create a copy of it (we're automatically ref'd via the ctor), // then insert it into our frames. fd->deref(q); fd = new FrameData(*fd, q); } } else { fd = new FrameData(q, QString()); } frame = fd; fd->prefix = prefix; fd->requestedPrefix = requestedPrefix; //updateSizes(); fd->enabledBorders = enabledBorders; fd->frameSize = pendingFrameSize; fd->imagePath = q->imagePath(); //was fd just created empty now? if (newKey.isEmpty()) { newKey = cacheId(fd, prefix); } // we know it isn't in s_sharedFrames due to the check above, so insert it now FrameSvgPrivate::s_sharedFrames[q->theme()->d].insert(newKey, fd); fd->theme = q->theme()->d; if (updateType == UpdateFrameAndMargins) { updateAndSignalSizes(); } else { updateSizes(frame); } } void FrameSvgPrivate::paintCenter(QPainter& p, FrameData* frame, const QRect& contentRect, const QSize& fullSize) { if (!contentRect.isEmpty()) { const QString centerElementId = frame->prefix % QLatin1String("center"); if (frame->tileCenter) { QSize centerTileSize = q->elementSize(centerElementId); QPixmap center(centerTileSize); center.fill(Qt::transparent); QPainter centerPainter(¢er); centerPainter.setCompositionMode(QPainter::CompositionMode_Source); q->paint(¢erPainter, QRect(QPoint(0, 0), centerTileSize),centerElementId); if (frame->composeOverBorder) { p.drawTiledPixmap(QRect(QPoint(0, 0), fullSize), center); } else { p.drawTiledPixmap(FrameSvgHelpers::sectionRect(FrameSvg::NoBorder, contentRect, fullSize * q->devicePixelRatio()), center); } } else { if (frame->composeOverBorder) { q->paint(&p, QRect(QPoint(0, 0), fullSize), centerElementId); } else { q->paint(&p, FrameSvgHelpers::sectionRect(FrameSvg::NoBorder, contentRect, fullSize * q->devicePixelRatio()), centerElementId); } } } if (frame->composeOverBorder) { p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.drawPixmap(QRect(QPoint(0, 0), fullSize), alphaMask()); p.setCompositionMode(QPainter::CompositionMode_SourceOver); } } void FrameSvgPrivate::paintBorder(QPainter& p, FrameData* frame, const FrameSvg::EnabledBorders borders, const QSize& size, const QRect& contentRect) const { QString side = frame->prefix % FrameSvgHelpers::borderToElementId(borders); if (frame->enabledBorders & borders && q->hasElement(side) && !size.isEmpty()) { if (frame->stretchBorders) { q->paint(&p, FrameSvgHelpers::sectionRect(borders, contentRect, frame->frameSize * q->devicePixelRatio()), side); } else { QPixmap px(size); px.fill(Qt::transparent); QPainter sidePainter(&px); sidePainter.setCompositionMode(QPainter::CompositionMode_Source); q->paint(&sidePainter, QRect(QPoint(0, 0), size), side); p.drawTiledPixmap(FrameSvgHelpers::sectionRect(borders, contentRect, frame->frameSize * q->devicePixelRatio()), px); } } } void FrameSvgPrivate::paintCorner(QPainter& p, FrameData* frame, Plasma::FrameSvg::EnabledBorders border, const QRect& contentRect) const { QString corner = frame->prefix % FrameSvgHelpers::borderToElementId(border); if (frame->enabledBorders & border && q->hasElement(corner)) { q->paint(&p, FrameSvgHelpers::sectionRect(border, contentRect, frame->frameSize * q->devicePixelRatio()), corner); } } QString FrameSvgPrivate::cacheId(FrameData *frame, const QString &prefixToSave) const { const QSize size = frameSize(frame).toSize(); const QLatin1Char s('_'); return QString::number(frame->enabledBorders) % s % QString::number(size.width()) % s % QString::number(size.height()) % s % QString::number(q->scaleFactor()) % s % QString::number(q->devicePixelRatio()) % s % prefixToSave % s % frame->imagePath; } void FrameSvgPrivate::cacheFrame(const QString &prefixToSave, const QPixmap &background, const QPixmap &overlay) { if (!q->isUsingRenderingCache()) { return; } //insert background if (!frame) { return; } const QString id = cacheId(frame, prefixToSave); //qCDebug(LOG_PLASMA)<<"Saving to cache frame"<theme()->insertIntoCache(id, background, QString::number((qint64)q, 16) % prefixToSave); if (!overlay.isNull()) { //insert overlay q->theme()->insertIntoCache(QLatin1String("overlay_") % id, overlay, QString::number((qint64)q, 16) % prefixToSave % QLatin1String("overlay")); } } void FrameSvgPrivate::updateSizes(FrameData *frame) const { //qCDebug(LOG_PLASMA) << "!!!!!!!!!!!!!!!!!!!!!! updating sizes" << prefix; Q_ASSERT(frame); QSize s = q->size(); q->resize(); if (!frame->cachedBackground.isNull()) { frame->cachedBackground = QPixmap(); } //This has the same size regardless the border is enabled or not frame->fixedTopHeight = q->elementSize(frame->prefix % QLatin1String("top")).height(); int hintTopMargin = -1; if (q->hasElement(frame->prefix % QLatin1String("hint-top-margin"))) { hintTopMargin = q->elementSize(frame->prefix % QLatin1String("hint-top-margin")).height(); frame->fixedTopMargin = hintTopMargin; } else { frame->fixedTopMargin = frame->fixedTopHeight; } //The same, but its size depends from the margin being enabled if (frame->enabledBorders & FrameSvg::TopBorder) { frame->topHeight = frame->fixedTopHeight; if (hintTopMargin > -1) { frame->topMargin = hintTopMargin; } else { frame->topMargin = frame->topHeight; } } else { frame->topMargin = frame->topHeight = 0; } frame->fixedLeftWidth = q->elementSize(frame->prefix % QLatin1String("left")).width(); int hintLeftMargin = -1; if (q->hasElement(frame->prefix % QLatin1String("hint-left-margin"))) { hintLeftMargin = q->elementSize(frame->prefix % QLatin1String("hint-left-margin")).width(); frame->fixedLeftMargin = hintLeftMargin; } else { frame->fixedLeftMargin = frame->fixedLeftWidth; } if (frame->enabledBorders & FrameSvg::LeftBorder) { frame->leftWidth = frame->fixedLeftWidth; if (hintLeftMargin > -1) { frame->leftMargin = hintLeftMargin; } else { frame->leftMargin = frame->leftWidth; } } else { frame->leftMargin = frame->leftWidth = 0; } frame->fixedRightWidth = q->elementSize(frame->prefix % QLatin1String("right")).width(); int hintRightMargin = -1; if (q->hasElement(frame->prefix % QLatin1String("hint-right-margin"))) { hintRightMargin = q->elementSize(frame->prefix % QLatin1String("hint-right-margin")).width(); frame->fixedRightMargin = hintRightMargin; } else { frame->fixedRightMargin = frame->fixedRightWidth; } if (frame->enabledBorders & FrameSvg::RightBorder) { frame->rightWidth = frame->fixedRightWidth; if (hintRightMargin > -1) { frame->rightMargin = hintRightMargin; } else { frame->rightMargin = frame->rightWidth; } } else { frame->rightMargin = frame->rightWidth = 0; } frame->fixedBottomHeight = q->elementSize(frame->prefix % QLatin1String("bottom")).height(); int hintBottomMargin = -1; if (q->hasElement(frame->prefix % QLatin1String("hint-bottom-margin"))) { hintBottomMargin = q->elementSize(frame->prefix % QLatin1String("hint-bottom-margin")).height(); frame->fixedBottomMargin = hintBottomMargin; } else { frame->fixedBottomMargin = frame->fixedBottomHeight; } if (frame->enabledBorders & FrameSvg::BottomBorder) { frame->bottomHeight = frame->fixedBottomHeight; if (hintBottomMargin > -1) { frame->bottomMargin = hintBottomMargin; } else { frame->bottomMargin = frame->bottomHeight; } } else { frame->bottomMargin = frame->bottomHeight = 0; } frame->composeOverBorder = (q->hasElement(frame->prefix % QLatin1String("hint-compose-over-border")) && q->hasElement(QLatin1String("mask-") % frame->prefix % QLatin1String("center"))); //since it's rectangular, topWidth and bottomWidth must be the same //the ones that don't have a frame->prefix is for retrocompatibility frame->tileCenter = (q->hasElement(QStringLiteral("hint-tile-center")) || q->hasElement(frame->prefix % QLatin1String("hint-tile-center"))); frame->noBorderPadding = (q->hasElement(QStringLiteral("hint-no-border-padding")) || q->hasElement(frame->prefix % QLatin1String("hint-no-border-padding"))); frame->stretchBorders = (q->hasElement(QStringLiteral("hint-stretch-borders")) || q->hasElement(frame->prefix % QLatin1String("hint-stretch-borders"))); q->resize(s); } void FrameSvgPrivate::updateNeeded() { q->setElementPrefix(requestedPrefix); //frame not created yet? if (!frame) { return; } q->clearCache(); updateSizes(frame); } void FrameSvgPrivate::updateAndSignalSizes() { //frame not created yet? if (!frame) { return; } updateSizes(frame); emit q->repaintNeeded(); } QSizeF FrameSvgPrivate::frameSize(FrameData *frame) const { if (!frame) { return QSizeF(); } if (!frame->frameSize.isValid()) { updateSizes(frame); frame->frameSize = q->size(); } return frame->frameSize; } void FrameData::ref(FrameSvg *svg) { references[svg] = references[svg] + 1; //qCDebug(LOG_PLASMA) << this << svg << references[svg]; } bool FrameData::deref(FrameSvg *svg) { references[svg] = references[svg] - 1; //qCDebug(LOG_PLASMA) << this << svg << references[svg]; if (references[svg] < 1) { references.remove(svg); } return references.isEmpty(); } bool FrameData::removeRefs(FrameSvg *svg) { references.remove(svg); return references.isEmpty(); } bool FrameData::isUsed() const { return !references.isEmpty(); } int FrameData::refcount() const { return references.count(); } QString FrameSvg::actualPrefix() const { return d->prefix; } bool FrameSvg::isRepaintBlocked() const { return d->repaintBlocked; } void FrameSvg::setRepaintBlocked(bool blocked) { d->repaintBlocked = blocked; if (!blocked) { d->updateFrameData(); } } } // Plasma namespace #include "moc_framesvg.cpp"