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(¢er); 0730 centerPainter.setCompositionMode(QPainter::CompositionMode_Source); 0731 q->paint(¢erPainter, 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"