Plasma

framesvg.cpp
1 /*
2  SPDX-FileCopyrightText: 2008-2010 Aaron Seigo <[email protected]>
3  SPDX-FileCopyrightText: 2008-2010 Marco Martin <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "framesvg.h"
9 #include "private/framesvg_p.h"
10 
11 #include <string>
12 
13 #include <QAtomicInt>
14 #include <QBitmap>
15 #include <QCryptographicHash>
16 #include <QPainter>
17 #include <QRegion>
18 #include <QSize>
19 #include <QStringBuilder>
20 #include <QTimer>
21 
22 #include <QDebug>
23 
24 #include "debug_p.h"
25 #include "private/framesvg_helpers.h"
26 #include "private/svg_p.h"
27 #include "theme.h"
28 
29 namespace Plasma
30 {
31 QHash<ThemePrivate *, QHash<uint, QWeakPointer<FrameData>>> FrameSvgPrivate::s_sharedFrames;
32 
33 // Any attempt to generate a frame whose width or height is larger than this
34 // will be rejected
35 static const int MAX_FRAME_SIZE = 100000;
36 
37 FrameData::~FrameData()
38 {
39  FrameSvgPrivate::s_sharedFrames[theme].remove(cacheId);
40 }
41 
42 FrameSvg::FrameSvg(QObject *parent)
43  : Svg(parent)
44  , d(new FrameSvgPrivate(this))
45 {
46  connect(this, &FrameSvg::repaintNeeded, this, std::bind(&FrameSvgPrivate::updateNeeded, d));
47 }
48 
49 FrameSvg::~FrameSvg()
50 {
51  delete d;
52 }
53 
55 {
56  if (path == imagePath()) {
57  return;
58  }
59 
60  clearCache();
61 
63  Svg::d->setImagePath(path);
64  if (!d->repaintBlocked) {
65  d->updateFrameData(Svg::d->lastModified);
66  }
67 }
68 
70 {
71  if (borders == d->enabledBorders) {
72  return;
73  }
74 
75  d->enabledBorders = borders;
76 
77  if (!d->repaintBlocked) {
78  d->updateFrameData(Svg::d->lastModified);
79  }
80 }
81 
82 FrameSvg::EnabledBorders FrameSvg::enabledBorders() const
83 {
84  return d->enabledBorders;
85 }
86 
88 {
89  switch (location) {
90  case Types::TopEdge:
91  setElementPrefix(QStringLiteral("north"));
92  break;
93  case Types::BottomEdge:
94  setElementPrefix(QStringLiteral("south"));
95  break;
96  case Types::LeftEdge:
97  setElementPrefix(QStringLiteral("west"));
98  break;
99  case Types::RightEdge:
100  setElementPrefix(QStringLiteral("east"));
101  break;
102  default:
104  break;
105  }
106 
107  d->location = location;
108 }
109 
111 {
112  if (prefix.isEmpty() || !hasElement(prefix % QLatin1String("-center"))) {
113  d->prefix.clear();
114  } else {
115  d->prefix = prefix;
116  if (!d->prefix.isEmpty()) {
117  d->prefix += QLatin1Char('-');
118  }
119  }
120  d->requestedPrefix = prefix;
121 
122  d->location = Types::Floating;
123 
124  if (!d->repaintBlocked) {
125  d->updateFrameData(Svg::d->lastModified);
126  }
127 }
128 
129 bool FrameSvg::hasElementPrefix(const QString &prefix) const
130 {
131  // for now it simply checks if a center element exists,
132  // because it could make sense for certain themes to not have all the elements
133  if (prefix.isEmpty()) {
134  return hasElement(QStringLiteral("center"));
135  }
136  if (prefix.endsWith(QLatin1Char('-'))) {
137  return hasElement(prefix % QLatin1String("center"));
138  }
139 
140  return hasElement(prefix % QLatin1String("-center"));
141 }
142 
144 {
145  switch (location) {
146  case Types::TopEdge:
147  return hasElementPrefix(QStringLiteral("north"));
148  case Types::BottomEdge:
149  return hasElementPrefix(QStringLiteral("south"));
150  case Types::LeftEdge:
151  return hasElementPrefix(QStringLiteral("west"));
152  case Types::RightEdge:
153  return hasElementPrefix(QStringLiteral("east"));
154  default:
155  return hasElementPrefix(QString());
156  }
157 }
158 
160 {
161  return d->requestedPrefix;
162 }
163 
164 void FrameSvg::resizeFrame(const QSizeF &size)
165 {
166  if (imagePath().isEmpty()) {
167  return;
168  }
169 
170  if (size.isEmpty()) {
171 #ifndef NDEBUG
172  // qCDebug(LOG_PLASMA) << "Invalid size" << size;
173 #endif
174  return;
175  }
176 
177  if (d->frame && size.toSize() == d->frame->frameSize) {
178  return;
179  }
180  d->pendingFrameSize = size.toSize();
181 
182  if (!d->repaintBlocked) {
183  d->updateFrameData(Svg::d->lastModified, FrameSvgPrivate::UpdateFrame);
184  }
185 }
186 
188 {
189  if (!d->frame) {
190  return QSize(-1, -1);
191  } else {
192  return d->frameSize(d->frame.data());
193  }
194 }
195 
197 {
198  if (!d->frame) {
199  return .0;
200  }
201 
202  if (d->frame->noBorderPadding) {
203  return .0;
204  }
205 
206  switch (edge) {
208  return d->frame->topMargin;
209 
211  return d->frame->leftMargin;
212 
214  return d->frame->rightMargin;
215 
216  // Plasma::BottomMargin
217  default:
218  return d->frame->bottomMargin;
219  }
220 }
221 
223 {
224  if (!d->frame) {
225  return .0;
226  }
227 
228  if (d->frame->noBorderPadding) {
229  return .0;
230  }
231 
232  switch (edge) {
234  return d->frame->insetTopMargin;
235 
237  return d->frame->insetLeftMargin;
238 
240  return d->frame->insetRightMargin;
241 
242  // Plasma::BottomMargin
243  default:
244  return d->frame->insetBottomMargin;
245  }
246 }
247 
249 {
250  if (!d->frame) {
251  return .0;
252  }
253 
254  if (d->frame->noBorderPadding) {
255  return .0;
256  }
257 
258  switch (edge) {
260  return d->frame->fixedTopMargin;
261 
263  return d->frame->fixedLeftMargin;
264 
266  return d->frame->fixedRightMargin;
267 
268  // Plasma::BottomMargin
269  default:
270  return d->frame->fixedBottomMargin;
271  }
272 }
273 
274 void FrameSvg::getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
275 {
276  if (!d->frame || d->frame->noBorderPadding) {
277  left = top = right = bottom = 0;
278  return;
279  }
280 
281  top = d->frame->topMargin;
282  left = d->frame->leftMargin;
283  right = d->frame->rightMargin;
284  bottom = d->frame->bottomMargin;
285 }
286 
287 void FrameSvg::getFixedMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
288 {
289  if (!d->frame || d->frame->noBorderPadding) {
290  left = top = right = bottom = 0;
291  return;
292  }
293 
294  top = d->frame->fixedTopMargin;
295  left = d->frame->fixedLeftMargin;
296  right = d->frame->fixedRightMargin;
297  bottom = d->frame->fixedBottomMargin;
298 }
299 
300 void FrameSvg::getInset(qreal &left, qreal &top, qreal &right, qreal &bottom) const
301 {
302  if (!d->frame || d->frame->noBorderPadding) {
303  left = top = right = bottom = 0;
304  return;
305  }
306 
307  top = d->frame->insetTopMargin;
308  left = d->frame->insetLeftMargin;
309  right = d->frame->insetRightMargin;
310  bottom = d->frame->insetBottomMargin;
311 }
312 
314 {
315  if (d->frame) {
316  QRectF rect(QPoint(0, 0), d->frame->frameSize);
317  return rect.adjusted(d->frame->leftMargin, d->frame->topMargin, -d->frame->rightMargin, -d->frame->bottomMargin);
318  } else {
319  return QRectF();
320  }
321 }
322 
324 {
325  // FIXME: the distinction between overlay and
326  return d->alphaMask();
327 }
328 
330 {
331  QRegion result;
332  if (!d->frame) {
333  return result;
334  }
335 
336  uint id = qHash(d->cacheId(d->frame.data(), QString()), SvgRectsCache::s_seed);
337 
338  QRegion *obj = d->frame->cachedMasks.object(id);
339 
340  if (!obj) {
341  QPixmap alphaMask = d->alphaMask();
342  const qreal dpr = alphaMask.devicePixelRatio();
343 
344  // region should always be in logical pixels, resize pixmap to be in the logical sizes
345  if (alphaMask.devicePixelRatio() != 1.0) {
347  }
348 
349  obj = new QRegion(QBitmap(alphaMask.mask()));
350 
351  result = *obj;
352  d->frame->cachedMasks.insert(id, obj);
353  } else {
354  result = *obj;
355  }
356  return result;
357 }
358 
360 {
361  if (d->cacheAll && !cache) {
362  clearCache();
363  }
364 
365  d->cacheAll = cache;
366 }
367 
369 {
370  return d->cacheAll;
371 }
372 
374 {
375  if (d->frame) {
376  d->frame->cachedBackground = QPixmap();
377  d->frame->cachedMasks.clear();
378  }
379  if (d->maskFrame) {
380  d->maskFrame->cachedBackground = QPixmap();
381  d->maskFrame->cachedMasks.clear();
382  }
383 }
384 
386 {
387  if (d->frame->cachedBackground.isNull()) {
388  d->generateBackground(d->frame);
389  }
390 
391  return d->frame->cachedBackground;
392 }
393 
394 void FrameSvg::paintFrame(QPainter *painter, const QRectF &target, const QRectF &source)
395 {
396  if (d->frame->cachedBackground.isNull()) {
397  d->generateBackground(d->frame);
398  if (d->frame->cachedBackground.isNull()) {
399  return;
400  }
401  }
402 
403  painter->drawPixmap(target, d->frame->cachedBackground, source.isValid() ? source : target);
404 }
405 
406 void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos)
407 {
408  if (d->frame->cachedBackground.isNull()) {
409  d->generateBackground(d->frame);
410  if (d->frame->cachedBackground.isNull()) {
411  return;
412  }
413  }
414 
415  painter->drawPixmap(pos, d->frame->cachedBackground);
416 }
417 
419 {
420  if (d->frame) {
421  return d->frame->fixedTopHeight + d->frame->fixedBottomHeight;
422  }
423  return 0;
424 }
425 
427 {
428  if (d->frame) {
429  return d->frame->fixedRightWidth + d->frame->fixedLeftWidth;
430  }
431  return 0;
432 
433 }
434 
435 //#define DEBUG_FRAMESVG_CACHE
436 FrameSvgPrivate::~FrameSvgPrivate() = default;
437 
438 QPixmap FrameSvgPrivate::alphaMask()
439 {
440  QString maskPrefix;
441 
442  if (q->hasElement(QLatin1String("mask-") % prefix % QLatin1String("center"))) {
443  maskPrefix = QStringLiteral("mask-");
444  }
445 
446  if (maskPrefix.isNull()) {
447  if (frame->cachedBackground.isNull()) {
448  generateBackground(frame);
449  }
450  return frame->cachedBackground;
451  }
452 
453  // We are setting the prefix only temporary to generate
454  // the needed mask image
455  const QString maskRequestedPrefix = requestedPrefix.isEmpty() ? QStringLiteral("mask") : maskPrefix % requestedPrefix;
456  maskPrefix = maskPrefix % prefix;
457 
458  if (!maskFrame) {
459  maskFrame = lookupOrCreateMaskFrame(frame, maskPrefix, maskRequestedPrefix);
460  if (!maskFrame->cachedBackground.isNull()) {
461  return maskFrame->cachedBackground;
462  }
463  updateSizes(maskFrame);
464  generateBackground(maskFrame);
465  return maskFrame->cachedBackground;
466  }
467 
468  const bool shouldUpdate = (maskFrame->enabledBorders != frame->enabledBorders //
469  || maskFrame->frameSize != frameSize(frame.data()) //
470  || maskFrame->imagePath != frame->imagePath);
471  if (shouldUpdate) {
472  maskFrame = lookupOrCreateMaskFrame(frame, maskPrefix, maskRequestedPrefix);
473  if (!maskFrame->cachedBackground.isNull()) {
474  return maskFrame->cachedBackground;
475  }
476  updateSizes(maskFrame);
477  }
478 
479  if (maskFrame->cachedBackground.isNull()) {
480  generateBackground(maskFrame);
481  // When we take the maskFrame from cache, the pixel ratio gets
482  // reset to 1
483  maskFrame->cachedBackground.setDevicePixelRatio(q->devicePixelRatio());
484  }
485 
486  return maskFrame->cachedBackground;
487 }
488 
490 FrameSvgPrivate::lookupOrCreateMaskFrame(const QSharedPointer<FrameData> &frame, const QString &maskPrefix, const QString &maskRequestedPrefix)
491 {
492  const uint key = qHash(cacheId(frame.data(), maskPrefix));
493  QSharedPointer<FrameData> mask = s_sharedFrames[q->theme()->d].value(key);
494 
495  // See if we can find a suitable candidate in the shared frames.
496  // If there is one, use it.
497  if (mask) {
498  return mask;
499  }
500 
501  mask.reset(new FrameData(*frame.data(), q));
502  mask->prefix = maskPrefix;
503  mask->requestedPrefix = maskRequestedPrefix;
504  mask->theme = q->theme()->d;
505  mask->imagePath = frame->imagePath;
506  mask->enabledBorders = frame->enabledBorders;
507  mask->frameSize = frameSize(frame).toSize();
508  mask->cacheId = key;
509  mask->lastModified = frame->lastModified;
510  s_sharedFrames[q->theme()->d].insert(key, mask);
511 
512  return mask;
513 }
514 
515 void FrameSvgPrivate::generateBackground(const QSharedPointer<FrameData> &frame)
516 {
517  if (!frame->cachedBackground.isNull() || !q->hasElementPrefix(frame->prefix)) {
518  return;
519  }
520 
521  const uint id = qHash(cacheId(frame.data(), frame->prefix));
522 
523  bool frameCached = !frame->cachedBackground.isNull();
524  bool overlayCached = false;
525  // TODO KF6: Kill Overlays
526  const bool overlayAvailable = !frame->prefix.startsWith(QLatin1String("mask-")) && q->hasElement(frame->prefix % QLatin1String("overlay"));
527  QPixmap overlay;
528  if (q->isUsingRenderingCache()) {
529  frameCached = q->theme()->findInCache(QString::number(id), frame->cachedBackground, frame->lastModified) && !frame->cachedBackground.isNull();
530 
531  if (overlayAvailable) {
532  const uint overlayId = qHash(cacheId(frame.data(), frame->prefix % QLatin1String("overlay")));
533  overlayCached = q->theme()->findInCache(QString::number(overlayId), overlay, frame->lastModified) && !overlay.isNull();
534  }
535  }
536 
537  if (!frameCached) {
538  generateFrameBackground(frame);
539  }
540 
541  // Overlays
542  QSize overlaySize;
543  QPoint actualOverlayPos = QPoint(0, 0);
544  if (overlayAvailable && !overlayCached) {
545  overlaySize = q->elementSize(frame->prefix % QLatin1String("overlay"));
546 
547  if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-pos-right"))) {
548  actualOverlayPos.setX(frame->frameSize.width() - overlaySize.width());
549  } else if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-pos-bottom"))) {
550  actualOverlayPos.setY(frame->frameSize.height() - overlaySize.height());
551  // Stretched or Tiled?
552  } else if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-stretch"))) {
553  overlaySize = frameSize(frame).toSize();
554  } else {
555  if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-horizontal"))) {
556  overlaySize.setWidth(frameSize(frame).width());
557  }
558  if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-vertical"))) {
559  overlaySize.setHeight(frameSize(frame).height());
560  }
561  }
562 
563  overlay = alphaMask();
564  QPainter overlayPainter(&overlay);
565  overlayPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
566  // Tiling?
567  if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-horizontal"))
568  || q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-vertical"))) {
569  QSize s = q->size();
570  q->resize(q->elementSize(frame->prefix % QLatin1String("overlay")));
571 
572  overlayPainter.drawTiledPixmap(QRect(QPoint(0, 0), overlaySize), q->pixmap(frame->prefix % QLatin1String("overlay")));
573  q->resize(s);
574  } else {
575  q->paint(&overlayPainter, QRect(actualOverlayPos, overlaySize), frame->prefix % QLatin1String("overlay"));
576  }
577 
578  overlayPainter.end();
579  }
580 
581  if (!frameCached) {
582  cacheFrame(frame->prefix, frame->cachedBackground, overlayCached ? overlay : QPixmap());
583  }
584 
585  if (!overlay.isNull()) {
586  QPainter p(&frame->cachedBackground);
587  p.setCompositionMode(QPainter::CompositionMode_SourceOver);
588  p.drawPixmap(actualOverlayPos, overlay, QRect(actualOverlayPos, overlaySize));
589  }
590 }
591 
592 void FrameSvgPrivate::generateFrameBackground(const QSharedPointer<FrameData> &frame)
593 {
594  // qCDebug(LOG_PLASMA) << "generating background";
595  const QSize size = frameSize(frame).toSize() * q->devicePixelRatio();
596 
597  if (!size.isValid()) {
598 #ifndef NDEBUG
599  // qCDebug(LOG_PLASMA) << "Invalid frame size" << size;
600 #endif
601  return;
602  }
603  if (size.width() >= MAX_FRAME_SIZE || size.height() >= MAX_FRAME_SIZE) {
604  qCWarning(LOG_PLASMA) << "Not generating frame background for a size whose width or height is more than" << MAX_FRAME_SIZE << size;
605  return;
606  }
607 
608  frame->cachedBackground = QPixmap(size);
609  frame->cachedBackground.fill(Qt::transparent);
610  QPainter p(&frame->cachedBackground);
611  p.setCompositionMode(QPainter::CompositionMode_Source);
612  p.setRenderHint(QPainter::SmoothPixmapTransform);
613 
614  QRect contentRect = contentGeometry(frame, size);
615  paintCenter(p, frame, contentRect, size);
616 
617  paintCorner(p, frame, FrameSvg::LeftBorder | FrameSvg::TopBorder, contentRect);
618  paintCorner(p, frame, FrameSvg::RightBorder | FrameSvg::TopBorder, contentRect);
619  paintCorner(p, frame, FrameSvg::LeftBorder | FrameSvg::BottomBorder, contentRect);
620  paintCorner(p, frame, FrameSvg::RightBorder | FrameSvg::BottomBorder, contentRect);
621 
622  // Sides
623  const int leftHeight = q->elementSize(frame->prefix % QLatin1String("left")).height();
624  paintBorder(p, frame, FrameSvg::LeftBorder, QSize(frame->leftWidth, leftHeight) * q->devicePixelRatio(), contentRect);
625  const int rightHeight = q->elementSize(frame->prefix % QLatin1String("right")).height();
626  paintBorder(p, frame, FrameSvg::RightBorder, QSize(frame->rightWidth, rightHeight) * q->devicePixelRatio(), contentRect);
627 
628  const int topWidth = q->elementSize(frame->prefix % QLatin1String("top")).width();
629  paintBorder(p, frame, FrameSvg::TopBorder, QSize(topWidth, frame->topHeight) * q->devicePixelRatio(), contentRect);
630  const int bottomWidth = q->elementSize(frame->prefix % QLatin1String("bottom")).width();
631  paintBorder(p, frame, FrameSvg::BottomBorder, QSize(bottomWidth, frame->bottomHeight) * q->devicePixelRatio(), contentRect);
632  p.end();
633 
634  frame->cachedBackground.setDevicePixelRatio(q->devicePixelRatio());
635 }
636 
637 QRect FrameSvgPrivate::contentGeometry(const QSharedPointer<FrameData> &frame, const QSize &size) const
638 {
639  const QSize contentSize(size.width() - frame->leftWidth * q->devicePixelRatio() - frame->rightWidth * q->devicePixelRatio(),
640  size.height() - frame->topHeight * q->devicePixelRatio() - frame->bottomHeight * q->devicePixelRatio());
641  QRect contentRect(QPoint(0, 0), contentSize);
642  if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(frame->prefix % QLatin1String("left"))) {
643  contentRect.translate(frame->leftWidth * q->devicePixelRatio(), 0);
644  }
645 
646  // Corners
647  if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(frame->prefix % QLatin1String("top"))) {
648  contentRect.translate(0, frame->topHeight * q->devicePixelRatio());
649  }
650  return contentRect;
651 }
652 
653 void FrameSvgPrivate::updateFrameData(uint lastModified, UpdateType updateType)
654 {
655  auto fd = frame;
656  uint newKey = 0;
657 
658  if (fd) {
659  const uint oldKey = fd->cacheId;
660 
661  const QString oldPath = fd->imagePath;
662  const FrameSvg::EnabledBorders oldBorders = fd->enabledBorders;
663  const QSize currentSize = fd->frameSize;
664 
665  fd->enabledBorders = enabledBorders;
666  fd->frameSize = pendingFrameSize;
667  fd->imagePath = q->imagePath();
668 
669  newKey = qHash(cacheId(fd.data(), prefix));
670 
671  // reset frame to old values
672  fd->enabledBorders = oldBorders;
673  fd->frameSize = currentSize;
674  fd->imagePath = oldPath;
675 
676  // FIXME: something more efficient than string comparison?
677  if (oldKey == newKey) {
678  return;
679  }
680 
681  // qCDebug(LOG_PLASMA) << "looking for" << newKey;
682  auto newFd = FrameSvgPrivate::s_sharedFrames[q->theme()->d].value(newKey);
683  if (newFd) {
684  // qCDebug(LOG_PLASMA) << "FOUND IT!" << newFd->refcount;
685  // we've found a match, use that one
686  Q_ASSERT(newKey == newFd.lock()->cacheId);
687  frame = newFd;
688  return;
689  }
690 
691  fd.reset(new FrameData(*fd, q));
692  } else {
693  fd.reset(new FrameData(q, QString()));
694  }
695 
696  frame = fd;
697  fd->prefix = prefix;
698  fd->requestedPrefix = requestedPrefix;
699  // updateSizes();
700  fd->enabledBorders = enabledBorders;
701  fd->frameSize = pendingFrameSize;
702  fd->imagePath = q->imagePath();
703  fd->lastModified = lastModified;
704  // was fd just created empty now?
705  if (newKey == 0) {
706  newKey = qHash(cacheId(fd.data(), prefix));
707  }
708 
709  // we know it isn't in s_sharedFrames due to the check above, so insert it now
710  FrameSvgPrivate::s_sharedFrames[q->theme()->d].insert(newKey, fd);
711  fd->cacheId = newKey;
712  fd->theme = q->theme()->d;
713  if (updateType == UpdateFrameAndMargins) {
714  updateAndSignalSizes();
715  } else {
716  updateSizes(frame);
717  }
718 }
719 
720 void FrameSvgPrivate::paintCenter(QPainter &p, const QSharedPointer<FrameData> &frame, const QRect &contentRect, const QSize &fullSize)
721 {
722  if (!contentRect.isEmpty()) {
723  const QString centerElementId = frame->prefix % QLatin1String("center");
724  if (frame->tileCenter) {
725  QSize centerTileSize = q->elementSize(centerElementId);
726  QPixmap center(centerTileSize);
727  center.fill(Qt::transparent);
728 
729  QPainter centerPainter(&center);
730  centerPainter.setCompositionMode(QPainter::CompositionMode_Source);
731  q->paint(&centerPainter, QRect(QPoint(0, 0), centerTileSize), centerElementId);
732 
733  if (frame->composeOverBorder) {
734  p.drawTiledPixmap(QRect(QPoint(0, 0), fullSize), center);
735  } else {
736  p.drawTiledPixmap(FrameSvgHelpers::sectionRect(FrameSvg::NoBorder, contentRect, fullSize * q->devicePixelRatio()), center);
737  }
738  } else {
739  if (frame->composeOverBorder) {
740  q->paint(&p, QRect(QPoint(0, 0), fullSize), centerElementId);
741  } else {
742  q->paint(&p, FrameSvgHelpers::sectionRect(FrameSvg::NoBorder, contentRect, fullSize * q->devicePixelRatio()), centerElementId);
743  }
744  }
745  }
746 
747  if (frame->composeOverBorder) {
749  p.drawPixmap(QRect(QPoint(0, 0), fullSize), alphaMask());
751  }
752 }
753 
754 void FrameSvgPrivate::paintBorder(QPainter &p,
755  const QSharedPointer<FrameData> &frame,
756  const FrameSvg::EnabledBorders borders,
757  const QSize &size,
758  const QRect &contentRect) const
759 {
760  QString side = frame->prefix % FrameSvgHelpers::borderToElementId(borders);
761  if (frame->enabledBorders & borders && q->hasElement(side) && !size.isEmpty()) {
762  if (frame->stretchBorders) {
763  q->paint(&p, FrameSvgHelpers::sectionRect(borders, contentRect, frame->frameSize * q->devicePixelRatio()), side);
764  } else {
765  QPixmap px(size);
766  px.fill(Qt::transparent);
767 
768  QPainter sidePainter(&px);
769  sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
770  q->paint(&sidePainter, QRect(QPoint(0, 0), size), side);
771 
772  p.drawTiledPixmap(FrameSvgHelpers::sectionRect(borders, contentRect, frame->frameSize * q->devicePixelRatio()), px);
773  }
774  }
775 }
776 
777 void FrameSvgPrivate::paintCorner(QPainter &p, const QSharedPointer<FrameData> &frame, Plasma::FrameSvg::EnabledBorders border, const QRect &contentRect) const
778 {
779  // Draw the corner only if both borders in both directions are enabled.
780  if ((frame->enabledBorders & border) != border) {
781  return;
782  }
783  const QString corner = frame->prefix % FrameSvgHelpers::borderToElementId(border);
784  if (q->hasElement(corner)) {
785  q->paint(&p, FrameSvgHelpers::sectionRect(border, contentRect, frame->frameSize * q->devicePixelRatio()), corner);
786  }
787 }
788 
789 SvgPrivate::CacheId FrameSvgPrivate::cacheId(FrameData *frame, const QString &prefixToSave) const
790 {
791  const QSize size = frameSize(frame).toSize();
792  return SvgPrivate::CacheId{double(size.width()),
793  double(size.height()),
794  frame->imagePath,
795  prefixToSave,
796  q->status(),
797  q->devicePixelRatio(),
798  q->scaleFactor(),
799  q->colorGroup(),
800  (uint)frame->enabledBorders,
801  q->Svg::d->lastModified};
802 }
803 
804 void FrameSvgPrivate::cacheFrame(const QString &prefixToSave, const QPixmap &background, const QPixmap &overlay)
805 {
806  if (!q->isUsingRenderingCache()) {
807  return;
808  }
809 
810  // insert background
811  if (!frame) {
812  return;
813  }
814 
815  const uint id = qHash(cacheId(frame.data(), prefixToSave));
816 
817  // qCDebug(LOG_PLASMA)<<"Saving to cache frame"<<id;
818 
819  q->theme()->insertIntoCache(QString::number(id), background, QString::number((qint64)q, 16) % prefixToSave);
820 
821  if (!overlay.isNull()) {
822  // insert overlay
823  const uint overlayId = qHash(cacheId(frame.data(), frame->prefix % QLatin1String("overlay")));
824  q->theme()->insertIntoCache(QString::number(overlayId), overlay, QString::number((qint64)q, 16) % prefixToSave % QLatin1String("overlay"));
825  }
826 }
827 
828 void FrameSvgPrivate::updateSizes(FrameData *frame) const
829 {
830  // qCDebug(LOG_PLASMA) << "!!!!!!!!!!!!!!!!!!!!!! updating sizes" << prefix;
831  Q_ASSERT(frame);
832 
833  QSize s = q->size();
834  q->resize();
835  if (!frame->cachedBackground.isNull()) {
836  frame->cachedBackground = QPixmap();
837  }
838 
839  // This function needs to do a lot of string creation, since we have four
840  // sides with matching margins and insets. Rather than creating a new string
841  // every time for these, create a single buffer that can contain a full
842  // element name and pass that around using views, so we save a lot of
843  // allocations.
844  QString nameBuffer;
845  const auto offset = frame->prefix.length();
846  nameBuffer.reserve(offset + 30);
847  nameBuffer.append(frame->prefix);
848 
849  // This uses UTF16 literals to avoid having to create QLatin1String and then
850  // converting that to a QString temporary for the replace operation.
851  // Additionally, we use a template parameter to provide us the compile-time
852  // length of the literal so we don't need to calculate that.
853  auto createName = [&nameBuffer, offset]<std::size_t length>(const char16_t(&name)[length]) {
854  nameBuffer.replace(offset, length - 1, reinterpret_cast<const QChar *>(name), length);
855  return QStringView(nameBuffer).mid(0, offset + length - 1);
856  };
857 
858  // This has the same size regardless the border is enabled or not
859  frame->fixedTopHeight = q->elementSize(createName(u"top")).height();
860 
861  if (auto topMargin = q->elementRect(createName(u"hint-top-margin")); topMargin.isValid()) {
862  frame->fixedTopMargin = topMargin.height();
863  } else {
864  frame->fixedTopMargin = frame->fixedTopHeight;
865  }
866 
867  // The same, but its size depends from the margin being enabled
868  if (frame->enabledBorders & FrameSvg::TopBorder) {
869  frame->topMargin = frame->fixedTopMargin;
870  frame->topHeight = frame->fixedTopHeight;
871  } else {
872  frame->topMargin = frame->topHeight = 0;
873  }
874 
875  if (auto topInset = q->elementRect(createName(u"hint-top-inset")); topInset.isValid()) {
876  frame->insetTopMargin = topInset.height();
877  } else {
878  frame->insetTopMargin = -1;
879  }
880 
881  frame->fixedLeftWidth = q->elementSize(createName(u"left")).width();
882 
883  if (auto leftMargin = q->elementRect(createName(u"hint-left-margin")); leftMargin.isValid()) {
884  frame->fixedLeftMargin = leftMargin.width();
885  } else {
886  frame->fixedLeftMargin = frame->fixedLeftWidth;
887  }
888 
889  if (frame->enabledBorders & FrameSvg::LeftBorder) {
890  frame->leftMargin = frame->fixedLeftMargin;
891  frame->leftWidth = frame->fixedLeftWidth;
892  } else {
893  frame->leftMargin = frame->leftWidth = 0;
894  }
895 
896  if (auto leftInset = q->elementRect(createName(u"hint-left-inset")); leftInset.isValid()) {
897  frame->insetLeftMargin = leftInset.width();
898  } else {
899  frame->insetLeftMargin = -1;
900  }
901 
902  frame->fixedRightWidth = q->elementSize(createName(u"right")).width();
903 
904  if (auto rightMargin = q->elementRect(createName(u"hint-right-margin")); rightMargin.isValid()) {
905  frame->fixedRightMargin = rightMargin.width();
906  } else {
907  frame->fixedRightMargin = frame->fixedRightWidth;
908  }
909 
910  if (frame->enabledBorders & FrameSvg::RightBorder) {
911  frame->rightMargin = frame->fixedRightMargin;
912  frame->rightWidth = frame->fixedRightWidth;
913  } else {
914  frame->rightMargin = frame->rightWidth = 0;
915  }
916 
917  if (auto rightInset = q->elementRect(createName(u"hint-right-inset")); rightInset.isValid()) {
918  frame->insetRightMargin = rightInset.width();
919  } else {
920  frame->insetRightMargin = -1;
921  }
922 
923  frame->fixedBottomHeight = q->elementSize(createName(u"bottom")).height();
924 
925  if (auto bottomMargin = q->elementRect(createName(u"hint-bottom-margin")); bottomMargin.isValid()) {
926  frame->fixedBottomMargin = bottomMargin.height();
927  } else {
928  frame->fixedBottomMargin = frame->fixedBottomHeight;
929  }
930 
931  if (frame->enabledBorders & FrameSvg::BottomBorder) {
932  frame->bottomMargin = frame->fixedBottomMargin;
933  frame->bottomHeight = frame->fixedBottomHeight;
934  } else {
935  frame->bottomMargin = frame->bottomHeight = 0;
936  }
937 
938  if (auto bottomInset = q->elementRect(createName(u"hint-bottom-inset")); bottomInset.isValid()) {
939  frame->insetBottomMargin = bottomInset.height();
940  } else {
941  frame->insetBottomMargin = -1;
942  }
943 
944  static const QString maskPrefix = QStringLiteral("mask-");
945  static const QString hintTileCenter = QStringLiteral("hint-tile-center");
946  static const QString hintNoBorderPadding = QStringLiteral("hint-no-border-padding");
947  static const QString hintStretchBorders = QStringLiteral("hint-stretch-borders");
948 
949  frame->composeOverBorder = (q->hasElement(createName(u"hint-compose-over-border")) && q->hasElement(maskPrefix % createName(u"center")));
950 
951  // since it's rectangular, topWidth and bottomWidth must be the same
952  // the ones that don't have a frame->prefix is for retrocompatibility
953  frame->tileCenter = (q->hasElement(hintTileCenter) || q->hasElement(createName(u"hint-tile-center")));
954  frame->noBorderPadding = (q->hasElement(hintNoBorderPadding) || q->hasElement(createName(u"hint-no-border-padding")));
955  frame->stretchBorders = (q->hasElement(hintStretchBorders) || q->hasElement(createName(u"hint-stretch-borders")));
956  q->resize(s);
957 }
958 
959 void FrameSvgPrivate::updateNeeded()
960 {
961  q->setElementPrefix(requestedPrefix);
962  // frame not created yet?
963  if (!frame) {
964  return;
965  }
966  q->clearCache();
967  updateSizes(frame);
968 }
969 
970 void FrameSvgPrivate::updateAndSignalSizes()
971 {
972  // frame not created yet?
973  if (!frame) {
974  return;
975  }
976  updateSizes(frame);
977  Q_EMIT q->repaintNeeded();
978 }
979 
980 QSizeF FrameSvgPrivate::frameSize(FrameData *frame) const
981 {
982  if (!frame) {
983  return QSizeF();
984  }
985 
986  if (!frame->frameSize.isValid()) {
987  updateSizes(frame);
988  frame->frameSize = q->size();
989  }
990 
991  return frame->frameSize;
992 }
993 
995 {
996  return d->prefix;
997 }
998 
1000 {
1001  return d->repaintBlocked;
1002 }
1003 
1005 {
1006  d->repaintBlocked = blocked;
1007 
1008  if (!blocked) {
1009  d->updateFrameData(Svg::d->lastModified);
1010  }
1011 }
1012 
1013 } // Plasma namespace
SmoothPixmapTransform
Q_INVOKABLE QSizeF frameSize() const
Definition: framesvg.cpp:187
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isNull() const const
const T value(const Key &key) const const
@ LeftMargin
The left margin.
Definition: plasma.h:261
T * data() const const
bool isValid() const const
Namespace for everything in libplasma.
Definition: datamodel.cpp:14
QString number(int n, int base)
qreal devicePixelRatio() const const
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
bool isNull() const const
Q_INVOKABLE bool cacheAllRenderedFrames() const
Definition: framesvg.cpp:368
void drawTiledPixmap(const QRectF &rectangle, const QPixmap &pixmap, const QPointF &position)
Q_INVOKABLE void setCacheAllRenderedFrames(bool cache)
Sets whether saving all the rendered prefixes in a cache or not.
Definition: framesvg.cpp:359
Q_INVOKABLE int minimumDrawingWidth()
This will return the minimum width required to correctly draw this SVG.
Definition: framesvg.cpp:426
bool isEmpty() const const
Q_INVOKABLE void getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
Convenience method that extracts the size of the four margins in the four output parameters The disab...
Definition: framesvg.cpp:274
QStringView mid(qsizetype start) const const
@ BottomEdge
Along the bottom of the screen.
Definition: plasma.h:165
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
void reserve(int size)
int width() const const
@ LeftEdge
Along the left side of the screen.
Definition: plasma.h:166
Q_INVOKABLE QRegion mask() const
Returns a mask that tightly contains the fully opaque areas of the svg.
Definition: framesvg.cpp:329
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setWidth(int width)
QHash::iterator insert(const Key &key, const T &value)
bool isEmpty() const const
Q_INVOKABLE QString prefix()
Returns the prefix for SVG elements of the FrameSvg (including a '-' at the end if not empty)
Definition: framesvg.cpp:159
Q_INVOKABLE QPixmap framePixmap()
Returns a pixmap of the SVG represented by this object.
Definition: framesvg.cpp:385
int height() const const
@ TopMargin
The top margin.
Definition: plasma.h:259
Q_INVOKABLE void setElementPrefix(Plasma::Types::Location location)
Sets the prefix (.
Definition: framesvg.cpp:87
Q_INVOKABLE qreal insetSize(const Plasma::Types::MarginEdge edge) const
Returns the insets margin size given the margin edge we want.
Definition: framesvg.cpp:222
QPixmap alphaMask() const
Definition: framesvg.cpp:323
Q_INVOKABLE bool hasElementPrefix(const QString &prefix) const
Definition: framesvg.cpp:129
void setX(int x)
void setY(int y)
bool isEmpty() const const
Q_INVOKABLE void getFixedMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
Convenience method that extracts the size of the four margins in the four output parameters Compared ...
Definition: framesvg.cpp:287
int length() const const
void setContainsMultipleImages(bool multiple)
Set whether the SVG contains a single image or multiple ones.
Definition: svg.cpp:1115
Q_INVOKABLE void paintFrame(QPainter *painter, const QRectF &target, const QRectF &source=QRectF())
Paints the loaded SVG with the elements that represents the border.
Definition: framesvg.cpp:394
QSize toSize() const const
@ RightMargin
The right margin.
Definition: plasma.h:262
KCALENDARCORE_EXPORT uint qHash(const KCalendarCore::Period &key)
@ Floating
Free floating.
Definition: plasma.h:159
bool isRepaintBlocked() const
Definition: framesvg.cpp:999
Q_INVOKABLE qreal marginSize(const Plasma::Types::MarginEdge edge) const
Returns the margin size given the margin edge we want If the given margin is disabled,...
Definition: framesvg.cpp:196
int height() const const
QBitmap mask() const const
void setRepaintBlocked(bool blocked)
If we will do several changes at once in the frame properties, such as prefix, enabled borders and si...
Definition: framesvg.cpp:1004
Q_INVOKABLE qreal fixedMarginSize(const Plasma::Types::MarginEdge edge) const
Returns the margin size given the margin edge we want.
Definition: framesvg.cpp:248
bool isValid() const const
QString & replace(int position, int n, QChar after)
Q_INVOKABLE void getInset(qreal &left, qreal &top, qreal &right, qreal &bottom) const
Convenience method that extracts the size of the four inset margins in the four output parameters.
Definition: framesvg.cpp:300
bool isNull() const const
void setCompositionMode(QPainter::CompositionMode mode)
A theme aware image-centric SVG class.
Definition: svg.h:42
Q_INVOKABLE int minimumDrawingHeight()
This will return the minimum height required to correctly draw this SVG.
Definition: framesvg.cpp:418
QPixmap scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
Q_INVOKABLE QRectF contentsRect() const
Definition: framesvg.cpp:313
Q_INVOKABLE void setImagePath(const QString &path) override
Loads a new Svg.
Definition: framesvg.cpp:54
bool isEmpty() const const
int remove(const Key &key)
Location
The Location enumeration describes where on screen an element, such as an Applet or its managing cont...
Definition: plasma.h:158
QString actualPrefix() const
Definition: framesvg.cpp:994
@ TopEdge
Along the top of the screen.
Definition: plasma.h:164
void translate(int dx, int dy)
Q_INVOKABLE bool hasElement(const QString &elementId) const
Check whether an element exists in the loaded SVG.
Definition: svg.cpp:1082
Q_INVOKABLE void clearCache()
Deletes the internal cache freeing memory: use this if you want to switch the rendered element and yo...
Definition: framesvg.cpp:373
void setEnabledBorders(const EnabledBorders borders)
Sets what borders should be painted.
Definition: framesvg.cpp:69
QTextStream & center(QTextStream &stream)
CompositionMode_SourceIn
@ RightEdge
Along the right side of the screen.
Definition: plasma.h:167
Q_INVOKABLE void resizeFrame(const QSizeF &size)
Resize the frame maintaining the same border size.
Definition: framesvg.cpp:164
void setHeight(int height)
QString & append(QChar ch)
transparent
int width() const const
void repaintNeeded()
Emitted whenever the SVG data has changed in such a way that a repaint is required.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Feb 7 2023 04:15:02 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.