File indexing completed on 2023-10-01 04:11:44

0001 /*
0002     SPDX-FileCopyrightText: 2008-2010 Aaron Seigo <aseigo@kde.org>
0003     SPDX-FileCopyrightText: 2008-2010 Marco Martin <notmart@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "framesvg.h"
0009 #include "private/framesvg_p.h"
0010 
0011 #include <string>
0012 
0013 #include <QAtomicInt>
0014 #include <QBitmap>
0015 #include <QCryptographicHash>
0016 #include <QPainter>
0017 #include <QRegion>
0018 #include <QSize>
0019 #include <QStringBuilder>
0020 #include <QTimer>
0021 
0022 #include <QDebug>
0023 
0024 #include "debug_p.h"
0025 #include "private/framesvg_helpers.h"
0026 #include "private/svg_p.h"
0027 #include "theme.h"
0028 
0029 namespace Plasma
0030 {
0031 QHash<ThemePrivate *, QHash<uint, QWeakPointer<FrameData>>> FrameSvgPrivate::s_sharedFrames;
0032 
0033 // Any attempt to generate a frame whose width or height is larger than this
0034 // will be rejected
0035 static const int MAX_FRAME_SIZE = 100000;
0036 
0037 FrameData::~FrameData()
0038 {
0039     FrameSvgPrivate::s_sharedFrames[theme].remove(cacheId);
0040 }
0041 
0042 FrameSvg::FrameSvg(QObject *parent)
0043     : Svg(parent)
0044     , d(new FrameSvgPrivate(this))
0045 {
0046     connect(this, &FrameSvg::repaintNeeded, this, std::bind(&FrameSvgPrivate::updateNeeded, d));
0047 }
0048 
0049 FrameSvg::~FrameSvg()
0050 {
0051     delete d;
0052 }
0053 
0054 void FrameSvg::setImagePath(const QString &path)
0055 {
0056     if (path == imagePath()) {
0057         return;
0058     }
0059 
0060     clearCache();
0061 
0062     setContainsMultipleImages(true);
0063     Svg::d->setImagePath(path);
0064     if (!d->repaintBlocked) {
0065         d->updateFrameData(Svg::d->lastModified);
0066     }
0067 }
0068 
0069 void FrameSvg::setEnabledBorders(const EnabledBorders borders)
0070 {
0071     if (borders == d->enabledBorders) {
0072         return;
0073     }
0074 
0075     d->enabledBorders = borders;
0076 
0077     if (!d->repaintBlocked) {
0078         d->updateFrameData(Svg::d->lastModified);
0079     }
0080 }
0081 
0082 FrameSvg::EnabledBorders FrameSvg::enabledBorders() const
0083 {
0084     return d->enabledBorders;
0085 }
0086 
0087 void FrameSvg::setElementPrefix(Plasma::Types::Location location)
0088 {
0089     switch (location) {
0090     case Types::TopEdge:
0091         setElementPrefix(QStringLiteral("north"));
0092         break;
0093     case Types::BottomEdge:
0094         setElementPrefix(QStringLiteral("south"));
0095         break;
0096     case Types::LeftEdge:
0097         setElementPrefix(QStringLiteral("west"));
0098         break;
0099     case Types::RightEdge:
0100         setElementPrefix(QStringLiteral("east"));
0101         break;
0102     default:
0103         setElementPrefix(QString());
0104         break;
0105     }
0106 
0107     d->location = location;
0108 }
0109 
0110 void FrameSvg::setElementPrefix(const QString &prefix)
0111 {
0112     if (prefix.isEmpty() || !hasElement(prefix % QLatin1String("-center"))) {
0113         d->prefix.clear();
0114     } else {
0115         d->prefix = prefix;
0116         if (!d->prefix.isEmpty()) {
0117             d->prefix += QLatin1Char('-');
0118         }
0119     }
0120     d->requestedPrefix = prefix;
0121 
0122     d->location = Types::Floating;
0123 
0124     if (!d->repaintBlocked) {
0125         d->updateFrameData(Svg::d->lastModified);
0126     }
0127 }
0128 
0129 bool FrameSvg::hasElementPrefix(const QString &prefix) const
0130 {
0131     // for now it simply checks if a center element exists,
0132     // because it could make sense for certain themes to not have all the elements
0133     if (prefix.isEmpty()) {
0134         return hasElement(QStringLiteral("center"));
0135     }
0136     if (prefix.endsWith(QLatin1Char('-'))) {
0137         return hasElement(prefix % QLatin1String("center"));
0138     }
0139 
0140     return hasElement(prefix % QLatin1String("-center"));
0141 }
0142 
0143 bool FrameSvg::hasElementPrefix(Plasma::Types::Location location) const
0144 {
0145     switch (location) {
0146     case Types::TopEdge:
0147         return hasElementPrefix(QStringLiteral("north"));
0148     case Types::BottomEdge:
0149         return hasElementPrefix(QStringLiteral("south"));
0150     case Types::LeftEdge:
0151         return hasElementPrefix(QStringLiteral("west"));
0152     case Types::RightEdge:
0153         return hasElementPrefix(QStringLiteral("east"));
0154     default:
0155         return hasElementPrefix(QString());
0156     }
0157 }
0158 
0159 QString FrameSvg::prefix()
0160 {
0161     return d->requestedPrefix;
0162 }
0163 
0164 void FrameSvg::resizeFrame(const QSizeF &size)
0165 {
0166     if (imagePath().isEmpty()) {
0167         return;
0168     }
0169 
0170     if (size.isEmpty()) {
0171 #ifndef NDEBUG
0172         // qCDebug(LOG_PLASMA) << "Invalid size" << size;
0173 #endif
0174         return;
0175     }
0176 
0177     if (d->frame && size.toSize() == d->frame->frameSize) {
0178         return;
0179     }
0180     d->pendingFrameSize = size.toSize();
0181 
0182     if (!d->repaintBlocked) {
0183         d->updateFrameData(Svg::d->lastModified, FrameSvgPrivate::UpdateFrame);
0184     }
0185 }
0186 
0187 QSizeF FrameSvg::frameSize() const
0188 {
0189     if (!d->frame) {
0190         return QSize(-1, -1);
0191     } else {
0192         return d->frameSize(d->frame.data());
0193     }
0194 }
0195 
0196 qreal FrameSvg::marginSize(const Plasma::Types::MarginEdge edge) const
0197 {
0198     if (!d->frame) {
0199         return .0;
0200     }
0201 
0202     if (d->frame->noBorderPadding) {
0203         return .0;
0204     }
0205 
0206     switch (edge) {
0207     case Plasma::Types::TopMargin:
0208         return d->frame->topMargin;
0209 
0210     case Plasma::Types::LeftMargin:
0211         return d->frame->leftMargin;
0212 
0213     case Plasma::Types::RightMargin:
0214         return d->frame->rightMargin;
0215 
0216     // Plasma::BottomMargin
0217     default:
0218         return d->frame->bottomMargin;
0219     }
0220 }
0221 
0222 qreal FrameSvg::insetSize(const Plasma::Types::MarginEdge edge) const
0223 {
0224     if (!d->frame) {
0225         return .0;
0226     }
0227 
0228     if (d->frame->noBorderPadding) {
0229         return .0;
0230     }
0231 
0232     switch (edge) {
0233     case Plasma::Types::TopMargin:
0234         return d->frame->insetTopMargin;
0235 
0236     case Plasma::Types::LeftMargin:
0237         return d->frame->insetLeftMargin;
0238 
0239     case Plasma::Types::RightMargin:
0240         return d->frame->insetRightMargin;
0241 
0242     // Plasma::BottomMargin
0243     default:
0244         return d->frame->insetBottomMargin;
0245     }
0246 }
0247 
0248 qreal FrameSvg::fixedMarginSize(const Plasma::Types::MarginEdge edge) const
0249 {
0250     if (!d->frame) {
0251         return .0;
0252     }
0253 
0254     if (d->frame->noBorderPadding) {
0255         return .0;
0256     }
0257 
0258     switch (edge) {
0259     case Plasma::Types::TopMargin:
0260         return d->frame->fixedTopMargin;
0261 
0262     case Plasma::Types::LeftMargin:
0263         return d->frame->fixedLeftMargin;
0264 
0265     case Plasma::Types::RightMargin:
0266         return d->frame->fixedRightMargin;
0267 
0268     // Plasma::BottomMargin
0269     default:
0270         return d->frame->fixedBottomMargin;
0271     }
0272 }
0273 
0274 void FrameSvg::getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
0275 {
0276     if (!d->frame || d->frame->noBorderPadding) {
0277         left = top = right = bottom = 0;
0278         return;
0279     }
0280 
0281     top = d->frame->topMargin;
0282     left = d->frame->leftMargin;
0283     right = d->frame->rightMargin;
0284     bottom = d->frame->bottomMargin;
0285 }
0286 
0287 void FrameSvg::getFixedMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
0288 {
0289     if (!d->frame || d->frame->noBorderPadding) {
0290         left = top = right = bottom = 0;
0291         return;
0292     }
0293 
0294     top = d->frame->fixedTopMargin;
0295     left = d->frame->fixedLeftMargin;
0296     right = d->frame->fixedRightMargin;
0297     bottom = d->frame->fixedBottomMargin;
0298 }
0299 
0300 void FrameSvg::getInset(qreal &left, qreal &top, qreal &right, qreal &bottom) const
0301 {
0302     if (!d->frame || d->frame->noBorderPadding) {
0303         left = top = right = bottom = 0;
0304         return;
0305     }
0306 
0307     top = d->frame->insetTopMargin;
0308     left = d->frame->insetLeftMargin;
0309     right = d->frame->insetRightMargin;
0310     bottom = d->frame->insetBottomMargin;
0311 }
0312 
0313 QRectF FrameSvg::contentsRect() const
0314 {
0315     if (d->frame) {
0316         QRectF rect(QPoint(0, 0), d->frame->frameSize);
0317         return rect.adjusted(d->frame->leftMargin, d->frame->topMargin, -d->frame->rightMargin, -d->frame->bottomMargin);
0318     } else {
0319         return QRectF();
0320     }
0321 }
0322 
0323 QPixmap FrameSvg::alphaMask() const
0324 {
0325     // FIXME: the distinction between overlay and
0326     return d->alphaMask();
0327 }
0328 
0329 QRegion FrameSvg::mask() const
0330 {
0331     QRegion result;
0332     if (!d->frame) {
0333         return result;
0334     }
0335 
0336     uint id = qHash(d->cacheId(d->frame.data(), QString()), SvgRectsCache::s_seed);
0337 
0338     QRegion *obj = d->frame->cachedMasks.object(id);
0339 
0340     if (!obj) {
0341         QPixmap alphaMask = d->alphaMask();
0342         const qreal dpr = alphaMask.devicePixelRatio();
0343 
0344         // region should always be in logical pixels, resize pixmap to be in the logical sizes
0345         if (alphaMask.devicePixelRatio() != 1.0) {
0346             alphaMask = alphaMask.scaled(alphaMask.width() / dpr, alphaMask.height() / dpr);
0347         }
0348 
0349         obj = new QRegion(QBitmap(alphaMask.mask()));
0350 
0351         result = *obj;
0352         d->frame->cachedMasks.insert(id, obj);
0353     } else {
0354         result = *obj;
0355     }
0356     return result;
0357 }
0358 
0359 void FrameSvg::setCacheAllRenderedFrames(bool cache)
0360 {
0361     if (d->cacheAll && !cache) {
0362         clearCache();
0363     }
0364 
0365     d->cacheAll = cache;
0366 }
0367 
0368 bool FrameSvg::cacheAllRenderedFrames() const
0369 {
0370     return d->cacheAll;
0371 }
0372 
0373 void FrameSvg::clearCache()
0374 {
0375     if (d->frame) {
0376         d->frame->cachedBackground = QPixmap();
0377         d->frame->cachedMasks.clear();
0378     }
0379     if (d->maskFrame) {
0380         d->maskFrame->cachedBackground = QPixmap();
0381         d->maskFrame->cachedMasks.clear();
0382     }
0383 }
0384 
0385 QPixmap FrameSvg::framePixmap()
0386 {
0387     if (d->frame->cachedBackground.isNull()) {
0388         d->generateBackground(d->frame);
0389     }
0390 
0391     return d->frame->cachedBackground;
0392 }
0393 
0394 void FrameSvg::paintFrame(QPainter *painter, const QRectF &target, const QRectF &source)
0395 {
0396     if (d->frame->cachedBackground.isNull()) {
0397         d->generateBackground(d->frame);
0398         if (d->frame->cachedBackground.isNull()) {
0399             return;
0400         }
0401     }
0402 
0403     painter->drawPixmap(target, d->frame->cachedBackground, source.isValid() ? source : target);
0404 }
0405 
0406 void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos)
0407 {
0408     if (d->frame->cachedBackground.isNull()) {
0409         d->generateBackground(d->frame);
0410         if (d->frame->cachedBackground.isNull()) {
0411             return;
0412         }
0413     }
0414 
0415     painter->drawPixmap(pos, d->frame->cachedBackground);
0416 }
0417 
0418 int FrameSvg::minimumDrawingHeight()
0419 {
0420     if (d->frame) {
0421         return d->frame->fixedTopHeight + d->frame->fixedBottomHeight;
0422     }
0423     return 0;
0424 }
0425 
0426 int FrameSvg::minimumDrawingWidth()
0427 {
0428     if (d->frame) {
0429         return d->frame->fixedRightWidth + d->frame->fixedLeftWidth;
0430     }
0431     return 0;
0432     
0433 }
0434 
0435 //#define DEBUG_FRAMESVG_CACHE
0436 FrameSvgPrivate::~FrameSvgPrivate() = default;
0437 
0438 QPixmap FrameSvgPrivate::alphaMask()
0439 {
0440     QString maskPrefix;
0441 
0442     if (q->hasElement(QLatin1String("mask-") % prefix % QLatin1String("center"))) {
0443         maskPrefix = QStringLiteral("mask-");
0444     }
0445 
0446     if (maskPrefix.isNull()) {
0447         if (frame->cachedBackground.isNull()) {
0448             generateBackground(frame);
0449         }
0450         return frame->cachedBackground;
0451     }
0452 
0453     // We are setting the prefix only temporary to generate
0454     // the needed mask image
0455     const QString maskRequestedPrefix = requestedPrefix.isEmpty() ? QStringLiteral("mask") : maskPrefix % requestedPrefix;
0456     maskPrefix = maskPrefix % prefix;
0457 
0458     if (!maskFrame) {
0459         maskFrame = lookupOrCreateMaskFrame(frame, maskPrefix, maskRequestedPrefix);
0460         if (!maskFrame->cachedBackground.isNull()) {
0461             return maskFrame->cachedBackground;
0462         }
0463         updateSizes(maskFrame);
0464         generateBackground(maskFrame);
0465         return maskFrame->cachedBackground;
0466     }
0467 
0468     const bool shouldUpdate = (maskFrame->enabledBorders != frame->enabledBorders //
0469                                || maskFrame->frameSize != frameSize(frame.data()) //
0470                                || maskFrame->imagePath != frame->imagePath);
0471     if (shouldUpdate) {
0472         maskFrame = lookupOrCreateMaskFrame(frame, maskPrefix, maskRequestedPrefix);
0473         if (!maskFrame->cachedBackground.isNull()) {
0474             return maskFrame->cachedBackground;
0475         }
0476         updateSizes(maskFrame);
0477     }
0478 
0479     if (maskFrame->cachedBackground.isNull()) {
0480         generateBackground(maskFrame);
0481         // When we take the maskFrame from cache, the pixel ratio gets
0482         // reset to 1
0483         maskFrame->cachedBackground.setDevicePixelRatio(q->devicePixelRatio());
0484     }
0485 
0486     return maskFrame->cachedBackground;
0487 }
0488 
0489 QSharedPointer<FrameData>
0490 FrameSvgPrivate::lookupOrCreateMaskFrame(const QSharedPointer<FrameData> &frame, const QString &maskPrefix, const QString &maskRequestedPrefix)
0491 {
0492     const uint key = qHash(cacheId(frame.data(), maskPrefix));
0493     QSharedPointer<FrameData> mask = s_sharedFrames[q->theme()->d].value(key);
0494 
0495     // See if we can find a suitable candidate in the shared frames.
0496     // If there is one, use it.
0497     if (mask) {
0498         return mask;
0499     }
0500 
0501     mask.reset(new FrameData(*frame.data(), q));
0502     mask->prefix = maskPrefix;
0503     mask->requestedPrefix = maskRequestedPrefix;
0504     mask->theme = q->theme()->d;
0505     mask->imagePath = frame->imagePath;
0506     mask->enabledBorders = frame->enabledBorders;
0507     mask->frameSize = frameSize(frame).toSize();
0508     mask->cacheId = key;
0509     mask->lastModified = frame->lastModified;
0510     s_sharedFrames[q->theme()->d].insert(key, mask);
0511 
0512     return mask;
0513 }
0514 
0515 void FrameSvgPrivate::generateBackground(const QSharedPointer<FrameData> &frame)
0516 {
0517     if (!frame->cachedBackground.isNull() || !q->hasElementPrefix(frame->prefix)) {
0518         return;
0519     }
0520 
0521     const uint id = qHash(cacheId(frame.data(), frame->prefix));
0522 
0523     bool frameCached = !frame->cachedBackground.isNull();
0524     bool overlayCached = false;
0525     // TODO KF6: Kill Overlays
0526     const bool overlayAvailable = !frame->prefix.startsWith(QLatin1String("mask-")) && q->hasElement(frame->prefix % QLatin1String("overlay"));
0527     QPixmap overlay;
0528     if (q->isUsingRenderingCache()) {
0529         frameCached = q->theme()->findInCache(QString::number(id), frame->cachedBackground, frame->lastModified) && !frame->cachedBackground.isNull();
0530 
0531         if (overlayAvailable) {
0532             const uint overlayId = qHash(cacheId(frame.data(), frame->prefix % QLatin1String("overlay")));
0533             overlayCached = q->theme()->findInCache(QString::number(overlayId), overlay, frame->lastModified) && !overlay.isNull();
0534         }
0535     }
0536 
0537     if (!frameCached) {
0538         generateFrameBackground(frame);
0539     }
0540 
0541     // Overlays
0542     QSize overlaySize;
0543     QPoint actualOverlayPos = QPoint(0, 0);
0544     if (overlayAvailable && !overlayCached) {
0545         overlaySize = q->elementSize(frame->prefix % QLatin1String("overlay"));
0546 
0547         if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-pos-right"))) {
0548             actualOverlayPos.setX(frame->frameSize.width() - overlaySize.width());
0549         } else if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-pos-bottom"))) {
0550             actualOverlayPos.setY(frame->frameSize.height() - overlaySize.height());
0551             // Stretched or Tiled?
0552         } else if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-stretch"))) {
0553             overlaySize = frameSize(frame).toSize();
0554         } else {
0555             if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-horizontal"))) {
0556                 overlaySize.setWidth(frameSize(frame).width());
0557             }
0558             if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-vertical"))) {
0559                 overlaySize.setHeight(frameSize(frame).height());
0560             }
0561         }
0562 
0563         overlay = alphaMask();
0564         QPainter overlayPainter(&overlay);
0565         overlayPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
0566         // Tiling?
0567         if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-horizontal"))
0568             || q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-vertical"))) {
0569             QSize s = q->size();
0570             q->resize(q->elementSize(frame->prefix % QLatin1String("overlay")));
0571 
0572             overlayPainter.drawTiledPixmap(QRect(QPoint(0, 0), overlaySize), q->pixmap(frame->prefix % QLatin1String("overlay")));
0573             q->resize(s);
0574         } else {
0575             q->paint(&overlayPainter, QRect(actualOverlayPos, overlaySize), frame->prefix % QLatin1String("overlay"));
0576         }
0577 
0578         overlayPainter.end();
0579     }
0580 
0581     if (!frameCached) {
0582         cacheFrame(frame->prefix, frame->cachedBackground, overlayCached ? overlay : QPixmap());
0583     }
0584 
0585     if (!overlay.isNull()) {
0586         QPainter p(&frame->cachedBackground);
0587         p.setCompositionMode(QPainter::CompositionMode_SourceOver);
0588         p.drawPixmap(actualOverlayPos, overlay, QRect(actualOverlayPos, overlaySize));
0589     }
0590 }
0591 
0592 void FrameSvgPrivate::generateFrameBackground(const QSharedPointer<FrameData> &frame)
0593 {
0594     // qCDebug(LOG_PLASMA) << "generating background";
0595     const QSize size = frameSize(frame).toSize() * q->devicePixelRatio();
0596 
0597     if (!size.isValid()) {
0598 #ifndef NDEBUG
0599         // qCDebug(LOG_PLASMA) << "Invalid frame size" << size;
0600 #endif
0601         return;
0602     }
0603     if (size.width() >= MAX_FRAME_SIZE || size.height() >= MAX_FRAME_SIZE) {
0604         qCWarning(LOG_PLASMA) << "Not generating frame background for a size whose width or height is more than" << MAX_FRAME_SIZE << size;
0605         return;
0606     }
0607 
0608     frame->cachedBackground = QPixmap(size);
0609     frame->cachedBackground.fill(Qt::transparent);
0610     QPainter p(&frame->cachedBackground);
0611     p.setCompositionMode(QPainter::CompositionMode_Source);
0612     p.setRenderHint(QPainter::SmoothPixmapTransform);
0613 
0614     QRect contentRect = contentGeometry(frame, size);
0615     paintCenter(p, frame, contentRect, size);
0616 
0617     paintCorner(p, frame, FrameSvg::LeftBorder | FrameSvg::TopBorder, contentRect);
0618     paintCorner(p, frame, FrameSvg::RightBorder | FrameSvg::TopBorder, contentRect);
0619     paintCorner(p, frame, FrameSvg::LeftBorder | FrameSvg::BottomBorder, contentRect);
0620     paintCorner(p, frame, FrameSvg::RightBorder | FrameSvg::BottomBorder, contentRect);
0621 
0622     // Sides
0623     const int leftHeight = q->elementSize(frame->prefix % QLatin1String("left")).height();
0624     paintBorder(p, frame, FrameSvg::LeftBorder, QSize(frame->leftWidth, leftHeight) * q->devicePixelRatio(), contentRect);
0625     const int rightHeight = q->elementSize(frame->prefix % QLatin1String("right")).height();
0626     paintBorder(p, frame, FrameSvg::RightBorder, QSize(frame->rightWidth, rightHeight) * q->devicePixelRatio(), contentRect);
0627 
0628     const int topWidth = q->elementSize(frame->prefix % QLatin1String("top")).width();
0629     paintBorder(p, frame, FrameSvg::TopBorder, QSize(topWidth, frame->topHeight) * q->devicePixelRatio(), contentRect);
0630     const int bottomWidth = q->elementSize(frame->prefix % QLatin1String("bottom")).width();
0631     paintBorder(p, frame, FrameSvg::BottomBorder, QSize(bottomWidth, frame->bottomHeight) * q->devicePixelRatio(), contentRect);
0632     p.end();
0633 
0634     frame->cachedBackground.setDevicePixelRatio(q->devicePixelRatio());
0635 }
0636 
0637 QRect FrameSvgPrivate::contentGeometry(const QSharedPointer<FrameData> &frame, const QSize &size) const
0638 {
0639     const QSize contentSize(size.width() - frame->leftWidth * q->devicePixelRatio() - frame->rightWidth * q->devicePixelRatio(),
0640                             size.height() - frame->topHeight * q->devicePixelRatio() - frame->bottomHeight * q->devicePixelRatio());
0641     QRect contentRect(QPoint(0, 0), contentSize);
0642     if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(frame->prefix % QLatin1String("left"))) {
0643         contentRect.translate(frame->leftWidth * q->devicePixelRatio(), 0);
0644     }
0645 
0646     // Corners
0647     if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(frame->prefix % QLatin1String("top"))) {
0648         contentRect.translate(0, frame->topHeight * q->devicePixelRatio());
0649     }
0650     return contentRect;
0651 }
0652 
0653 void FrameSvgPrivate::updateFrameData(uint lastModified, UpdateType updateType)
0654 {
0655     auto fd = frame;
0656     uint newKey = 0;
0657 
0658     if (fd) {
0659         const uint oldKey = fd->cacheId;
0660 
0661         const QString oldPath = fd->imagePath;
0662         const FrameSvg::EnabledBorders oldBorders = fd->enabledBorders;
0663         const QSize currentSize = fd->frameSize;
0664 
0665         fd->enabledBorders = enabledBorders;
0666         fd->frameSize = pendingFrameSize;
0667         fd->imagePath = q->imagePath();
0668 
0669         newKey = qHash(cacheId(fd.data(), prefix));
0670 
0671         // reset frame to old values
0672         fd->enabledBorders = oldBorders;
0673         fd->frameSize = currentSize;
0674         fd->imagePath = oldPath;
0675 
0676         // FIXME: something more efficient than string comparison?
0677         if (oldKey == newKey) {
0678             return;
0679         }
0680 
0681         // qCDebug(LOG_PLASMA) << "looking for" << newKey;
0682         auto newFd = FrameSvgPrivate::s_sharedFrames[q->theme()->d].value(newKey);
0683         if (newFd) {
0684             // qCDebug(LOG_PLASMA) << "FOUND IT!" << newFd->refcount;
0685             // we've found a match, use that one
0686             Q_ASSERT(newKey == newFd.lock()->cacheId);
0687             frame = newFd;
0688             return;
0689         }
0690 
0691         fd.reset(new FrameData(*fd, q));
0692     } else {
0693         fd.reset(new FrameData(q, QString()));
0694     }
0695 
0696     frame = fd;
0697     fd->prefix = prefix;
0698     fd->requestedPrefix = requestedPrefix;
0699     // updateSizes();
0700     fd->enabledBorders = enabledBorders;
0701     fd->frameSize = pendingFrameSize;
0702     fd->imagePath = q->imagePath();
0703     fd->lastModified = lastModified;
0704     // was fd just created empty now?
0705     if (newKey == 0) {
0706         newKey = qHash(cacheId(fd.data(), prefix));
0707     }
0708 
0709     // we know it isn't in s_sharedFrames due to the check above, so insert it now
0710     FrameSvgPrivate::s_sharedFrames[q->theme()->d].insert(newKey, fd);
0711     fd->cacheId = newKey;
0712     fd->theme = q->theme()->d;
0713     if (updateType == UpdateFrameAndMargins) {
0714         updateAndSignalSizes();
0715     } else {
0716         updateSizes(frame);
0717     }
0718 }
0719 
0720 void FrameSvgPrivate::paintCenter(QPainter &p, const QSharedPointer<FrameData> &frame, const QRect &contentRect, const QSize &fullSize)
0721 {
0722     if (!contentRect.isEmpty()) {
0723         const QString centerElementId = frame->prefix % QLatin1String("center");
0724         if (frame->tileCenter) {
0725             QSize centerTileSize = q->elementSize(centerElementId);
0726             QPixmap center(centerTileSize);
0727             center.fill(Qt::transparent);
0728 
0729             QPainter centerPainter(&center);
0730             centerPainter.setCompositionMode(QPainter::CompositionMode_Source);
0731             q->paint(&centerPainter, QRect(QPoint(0, 0), centerTileSize), centerElementId);
0732 
0733             if (frame->composeOverBorder) {
0734                 p.drawTiledPixmap(QRect(QPoint(0, 0), fullSize), center);
0735             } else {
0736                 p.drawTiledPixmap(FrameSvgHelpers::sectionRect(FrameSvg::NoBorder, contentRect, fullSize * q->devicePixelRatio()), center);
0737             }
0738         } else {
0739             if (frame->composeOverBorder) {
0740                 q->paint(&p, QRect(QPoint(0, 0), fullSize), centerElementId);
0741             } else {
0742                 q->paint(&p, FrameSvgHelpers::sectionRect(FrameSvg::NoBorder, contentRect, fullSize * q->devicePixelRatio()), centerElementId);
0743             }
0744         }
0745     }
0746 
0747     if (frame->composeOverBorder) {
0748         p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0749         p.drawPixmap(QRect(QPoint(0, 0), fullSize), alphaMask());
0750         p.setCompositionMode(QPainter::CompositionMode_SourceOver);
0751     }
0752 }
0753 
0754 void FrameSvgPrivate::paintBorder(QPainter &p,
0755                                   const QSharedPointer<FrameData> &frame,
0756                                   const FrameSvg::EnabledBorders borders,
0757                                   const QSize &size,
0758                                   const QRect &contentRect) const
0759 {
0760     QString side = frame->prefix % FrameSvgHelpers::borderToElementId(borders);
0761     if (frame->enabledBorders & borders && q->hasElement(side) && !size.isEmpty()) {
0762         if (frame->stretchBorders) {
0763             q->paint(&p, FrameSvgHelpers::sectionRect(borders, contentRect, frame->frameSize * q->devicePixelRatio()), side);
0764         } else {
0765             QPixmap px(size);
0766             px.fill(Qt::transparent);
0767 
0768             QPainter sidePainter(&px);
0769             sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
0770             q->paint(&sidePainter, QRect(QPoint(0, 0), size), side);
0771 
0772             p.drawTiledPixmap(FrameSvgHelpers::sectionRect(borders, contentRect, frame->frameSize * q->devicePixelRatio()), px);
0773         }
0774     }
0775 }
0776 
0777 void FrameSvgPrivate::paintCorner(QPainter &p, const QSharedPointer<FrameData> &frame, Plasma::FrameSvg::EnabledBorders border, const QRect &contentRect) const
0778 {
0779     // Draw the corner only if both borders in both directions are enabled.
0780     if ((frame->enabledBorders & border) != border) {
0781         return;
0782     }
0783     const QString corner = frame->prefix % FrameSvgHelpers::borderToElementId(border);
0784     if (q->hasElement(corner)) {
0785         q->paint(&p, FrameSvgHelpers::sectionRect(border, contentRect, frame->frameSize * q->devicePixelRatio()), corner);
0786     }
0787 }
0788 
0789 SvgPrivate::CacheId FrameSvgPrivate::cacheId(FrameData *frame, const QString &prefixToSave) const
0790 {
0791     const QSize size = frameSize(frame).toSize();
0792     return SvgPrivate::CacheId{double(size.width()),
0793                                double(size.height()),
0794                                frame->imagePath,
0795                                prefixToSave,
0796                                q->status(),
0797                                q->devicePixelRatio(),
0798                                q->scaleFactor(),
0799                                q->colorGroup(),
0800                                (uint)frame->enabledBorders,
0801                                q->Svg::d->lastModified};
0802 }
0803 
0804 void FrameSvgPrivate::cacheFrame(const QString &prefixToSave, const QPixmap &background, const QPixmap &overlay)
0805 {
0806     if (!q->isUsingRenderingCache()) {
0807         return;
0808     }
0809 
0810     // insert background
0811     if (!frame) {
0812         return;
0813     }
0814 
0815     const uint id = qHash(cacheId(frame.data(), prefixToSave));
0816 
0817     // qCDebug(LOG_PLASMA)<<"Saving to cache frame"<<id;
0818 
0819     q->theme()->insertIntoCache(QString::number(id), background, QString::number((qint64)q, 16) % prefixToSave);
0820 
0821     if (!overlay.isNull()) {
0822         // insert overlay
0823         const uint overlayId = qHash(cacheId(frame.data(), frame->prefix % QLatin1String("overlay")));
0824         q->theme()->insertIntoCache(QString::number(overlayId), overlay, QString::number((qint64)q, 16) % prefixToSave % QLatin1String("overlay"));
0825     }
0826 }
0827 
0828 void FrameSvgPrivate::updateSizes(FrameData *frame) const
0829 {
0830     // qCDebug(LOG_PLASMA) << "!!!!!!!!!!!!!!!!!!!!!! updating sizes" << prefix;
0831     Q_ASSERT(frame);
0832 
0833     QSize s = q->size();
0834     q->resize();
0835     if (!frame->cachedBackground.isNull()) {
0836         frame->cachedBackground = QPixmap();
0837     }
0838 
0839     // This function needs to do a lot of string creation, since we have four
0840     // sides with matching margins and insets. Rather than creating a new string
0841     // every time for these, create a single buffer that can contain a full
0842     // element name and pass that around using views, so we save a lot of
0843     // allocations.
0844     QString nameBuffer;
0845     const auto offset = frame->prefix.length();
0846     nameBuffer.reserve(offset + 30);
0847     nameBuffer.append(frame->prefix);
0848 
0849     // This uses UTF16 literals to avoid having to create QLatin1String and then
0850     // converting that to a QString temporary for the replace operation.
0851     // Additionally, we use a template parameter to provide us the compile-time
0852     // length of the literal so we don't need to calculate that.
0853     auto createName = [&nameBuffer, offset]<std::size_t length>(const char16_t(&name)[length]) {
0854         nameBuffer.replace(offset, length - 1, reinterpret_cast<const QChar *>(name), length);
0855         return QStringView(nameBuffer).mid(0, offset + length - 1);
0856     };
0857 
0858     // This has the same size regardless the border is enabled or not
0859     frame->fixedTopHeight = q->elementSize(createName(u"top")).height();
0860 
0861     if (auto topMargin = q->elementRect(createName(u"hint-top-margin")); topMargin.isValid()) {
0862         frame->fixedTopMargin = topMargin.height();
0863     } else {
0864         frame->fixedTopMargin = frame->fixedTopHeight;
0865     }
0866 
0867     // The same, but its size depends from the margin being enabled
0868     if (frame->enabledBorders & FrameSvg::TopBorder) {
0869         frame->topMargin = frame->fixedTopMargin;
0870         frame->topHeight = frame->fixedTopHeight;
0871     } else {
0872         frame->topMargin = frame->topHeight = 0;
0873     }
0874 
0875     if (auto topInset = q->elementRect(createName(u"hint-top-inset")); topInset.isValid()) {
0876         frame->insetTopMargin = topInset.height();
0877     } else {
0878         frame->insetTopMargin = -1;
0879     }
0880 
0881     frame->fixedLeftWidth = q->elementSize(createName(u"left")).width();
0882 
0883     if (auto leftMargin = q->elementRect(createName(u"hint-left-margin")); leftMargin.isValid()) {
0884         frame->fixedLeftMargin = leftMargin.width();
0885     } else {
0886         frame->fixedLeftMargin = frame->fixedLeftWidth;
0887     }
0888 
0889     if (frame->enabledBorders & FrameSvg::LeftBorder) {
0890         frame->leftMargin = frame->fixedLeftMargin;
0891         frame->leftWidth = frame->fixedLeftWidth;
0892     } else {
0893         frame->leftMargin = frame->leftWidth = 0;
0894     }
0895 
0896     if (auto leftInset = q->elementRect(createName(u"hint-left-inset")); leftInset.isValid()) {
0897         frame->insetLeftMargin = leftInset.width();
0898     } else {
0899         frame->insetLeftMargin = -1;
0900     }
0901 
0902     frame->fixedRightWidth = q->elementSize(createName(u"right")).width();
0903 
0904     if (auto rightMargin = q->elementRect(createName(u"hint-right-margin")); rightMargin.isValid()) {
0905         frame->fixedRightMargin = rightMargin.width();
0906     } else {
0907         frame->fixedRightMargin = frame->fixedRightWidth;
0908     }
0909 
0910     if (frame->enabledBorders & FrameSvg::RightBorder) {
0911         frame->rightMargin = frame->fixedRightMargin;
0912         frame->rightWidth = frame->fixedRightWidth;
0913     } else {
0914         frame->rightMargin = frame->rightWidth = 0;
0915     }
0916 
0917     if (auto rightInset = q->elementRect(createName(u"hint-right-inset")); rightInset.isValid()) {
0918         frame->insetRightMargin = rightInset.width();
0919     } else {
0920         frame->insetRightMargin = -1;
0921     }
0922 
0923     frame->fixedBottomHeight = q->elementSize(createName(u"bottom")).height();
0924 
0925     if (auto bottomMargin = q->elementRect(createName(u"hint-bottom-margin")); bottomMargin.isValid()) {
0926         frame->fixedBottomMargin = bottomMargin.height();
0927     } else {
0928         frame->fixedBottomMargin = frame->fixedBottomHeight;
0929     }
0930 
0931     if (frame->enabledBorders & FrameSvg::BottomBorder) {
0932         frame->bottomMargin = frame->fixedBottomMargin;
0933         frame->bottomHeight = frame->fixedBottomHeight;
0934     } else {
0935         frame->bottomMargin = frame->bottomHeight = 0;
0936     }
0937 
0938     if (auto bottomInset = q->elementRect(createName(u"hint-bottom-inset")); bottomInset.isValid()) {
0939         frame->insetBottomMargin = bottomInset.height();
0940     } else {
0941         frame->insetBottomMargin = -1;
0942     }
0943 
0944     static const QString maskPrefix = QStringLiteral("mask-");
0945     static const QString hintTileCenter = QStringLiteral("hint-tile-center");
0946     static const QString hintNoBorderPadding = QStringLiteral("hint-no-border-padding");
0947     static const QString hintStretchBorders = QStringLiteral("hint-stretch-borders");
0948 
0949     frame->composeOverBorder = (q->hasElement(createName(u"hint-compose-over-border")) && q->hasElement(maskPrefix % createName(u"center")));
0950 
0951     // since it's rectangular, topWidth and bottomWidth must be the same
0952     // the ones that don't have a frame->prefix is for retrocompatibility
0953     frame->tileCenter = (q->hasElement(hintTileCenter) || q->hasElement(createName(u"hint-tile-center")));
0954     frame->noBorderPadding = (q->hasElement(hintNoBorderPadding) || q->hasElement(createName(u"hint-no-border-padding")));
0955     frame->stretchBorders = (q->hasElement(hintStretchBorders) || q->hasElement(createName(u"hint-stretch-borders")));
0956     q->resize(s);
0957 }
0958 
0959 void FrameSvgPrivate::updateNeeded()
0960 {
0961     q->setElementPrefix(requestedPrefix);
0962     // frame not created yet?
0963     if (!frame) {
0964         return;
0965     }
0966     q->clearCache();
0967     updateSizes(frame);
0968 }
0969 
0970 void FrameSvgPrivate::updateAndSignalSizes()
0971 {
0972     // frame not created yet?
0973     if (!frame) {
0974         return;
0975     }
0976     updateSizes(frame);
0977     Q_EMIT q->repaintNeeded();
0978 }
0979 
0980 QSizeF FrameSvgPrivate::frameSize(FrameData *frame) const
0981 {
0982     if (!frame) {
0983         return QSizeF();
0984     }
0985 
0986     if (!frame->frameSize.isValid()) {
0987         updateSizes(frame);
0988         frame->frameSize = q->size();
0989     }
0990 
0991     return frame->frameSize;
0992 }
0993 
0994 QString FrameSvg::actualPrefix() const
0995 {
0996     return d->prefix;
0997 }
0998 
0999 bool FrameSvg::isRepaintBlocked() const
1000 {
1001     return d->repaintBlocked;
1002 }
1003 
1004 void FrameSvg::setRepaintBlocked(bool blocked)
1005 {
1006     d->repaintBlocked = blocked;
1007 
1008     if (!blocked) {
1009         d->updateFrameData(Svg::d->lastModified);
1010     }
1011 }
1012 
1013 } // Plasma namespace
1014 
1015 #include "moc_framesvg.cpp"