1/****************************************************************************
2**
3** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the QtDeclarative module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Digia. For licensing terms and
14** conditions see http://qt.digia.com/licensing. For further information
15** use the contact form at http://qt.digia.com/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Digia gives you certain additional
26** rights. These rights are described in the Digia Qt LGPL Exception
27** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28**
29** GNU General Public License Usage
30** Alternatively, this file may be used under the terms of the GNU
31** General Public License version 3.0 as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL included in the
33** packaging of this file. Please review the following information to
34** ensure the GNU General Public License version 3.0 requirements will be
35** met: http://www.gnu.org/copyleft/gpl.html.
36**
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "private/qdeclarativepainteditem_p.h"
43#include "private/qdeclarativepainteditem_p_p.h"
44
45#include <QDebug>
46#include <QPen>
47#include <QEvent>
48#include <QApplication>
49#include <QGraphicsSceneMouseEvent>
50#include <QPainter>
51#include <QPaintEngine>
52#include <qmath.h>
53
54QT_BEGIN_NAMESPACE
55
56/*!
57 \class QDeclarativePaintedItem
58 \brief The QDeclarativePaintedItem class is an abstract base class for QDeclarativeView items that want cached painting.
59 \internal
60
61 This is a convenience class for implementing items that cache their painting.
62 The contents of the item are cached behind the scenes.
63 The dirtyCache() function should be called if the contents change to
64 ensure the cache is refreshed the next time painting occurs.
65
66 To subclass QDeclarativePaintedItem, you must reimplement drawContents() to draw
67 the contents of the item.
68*/
69
70/*!
71 \fn void QDeclarativePaintedItem::drawContents(QPainter *painter, const QRect &rect)
72
73 This function is called when the cache needs to be refreshed. When
74 sub-classing QDeclarativePaintedItem this function should be implemented so as to
75 paint the contents of the item using the given \a painter for the
76 area of the contents specified by \a rect.
77*/
78
79/*!
80 \property QDeclarativePaintedItem::contentsSize
81 \brief The size of the contents
82
83 The contents size is the size of the item in regards to how it is painted
84 using the drawContents() function. This is distinct from the size of the
85 item in regards to height() and width().
86*/
87
88// XXX bug in WebKit - can call repaintRequested and other cache-changing functions from within render!
89static int inpaint=0;
90static int inpaint_clearcache=0;
91
92extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled;
93
94/*!
95 Marks areas of the cache that intersect with the given \a rect as dirty and
96 in need of being refreshed.
97
98 \sa clearCache()
99*/
100void QDeclarativePaintedItem::dirtyCache(const QRect& rect)
101{
102 Q_D(QDeclarativePaintedItem);
103 QRect srect(qCeil(rect.x()*d->contentsScale),
104 qCeil(rect.y()*d->contentsScale),
105 qCeil(rect.width()*d->contentsScale),
106 qCeil(rect.height()*d->contentsScale));
107 for (int i=0; i < d->imagecache.count(); ) {
108 QDeclarativePaintedItemPrivate::ImageCacheItem *c = d->imagecache[i];
109 QRect isect = (c->area & srect) | c->dirty;
110 if (isect == c->area && !inpaint) {
111 delete d->imagecache.takeAt(i);
112 } else {
113 c->dirty = isect;
114 ++i;
115 }
116 }
117}
118
119/*!
120 Marks the entirety of the contents cache as dirty.
121
122 \sa dirtyCache()
123*/
124void QDeclarativePaintedItem::clearCache()
125{
126 if (inpaint) {
127 inpaint_clearcache=1;
128 return;
129 }
130 Q_D(QDeclarativePaintedItem);
131 qDeleteAll(d->imagecache);
132 d->imagecache.clear();
133}
134
135/*!
136 Returns the size of the contents.
137
138 \sa setContentsSize()
139*/
140QSize QDeclarativePaintedItem::contentsSize() const
141{
142 Q_D(const QDeclarativePaintedItem);
143 return d->contentsSize;
144}
145
146/*!
147 Sets the size of the contents to the given \a size.
148
149 \sa contentsSize()
150*/
151void QDeclarativePaintedItem::setContentsSize(const QSize &size)
152{
153 Q_D(QDeclarativePaintedItem);
154 if (d->contentsSize == size) return;
155 prepareGeometryChange();
156 d->contentsSize = size;
157 clearCache();
158 update();
159 emit contentsSizeChanged();
160}
161
162qreal QDeclarativePaintedItem::contentsScale() const
163{
164 Q_D(const QDeclarativePaintedItem);
165 return d->contentsScale;
166}
167
168void QDeclarativePaintedItem::setContentsScale(qreal scale)
169{
170 Q_D(QDeclarativePaintedItem);
171 if (d->contentsScale == scale) return;
172 d->contentsScale = scale;
173 clearCache();
174 update();
175 emit contentsScaleChanged();
176}
177
178
179/*!
180 Constructs a new QDeclarativePaintedItem with the given \a parent.
181*/
182QDeclarativePaintedItem::QDeclarativePaintedItem(QDeclarativeItem *parent)
183 : QDeclarativeItem(*(new QDeclarativePaintedItemPrivate), parent)
184{
185}
186
187/*!
188 \internal
189 Constructs a new QDeclarativePaintedItem with the given \a parent and
190 initialized private data member \a dd.
191*/
192QDeclarativePaintedItem::QDeclarativePaintedItem(QDeclarativePaintedItemPrivate &dd, QDeclarativeItem *parent)
193 : QDeclarativeItem(dd, parent)
194{
195}
196
197/*!
198 Destroys the image item.
199*/
200QDeclarativePaintedItem::~QDeclarativePaintedItem()
201{
202 clearCache();
203}
204
205void QDeclarativePaintedItem::geometryChanged(const QRectF &newGeometry,
206 const QRectF &oldGeometry)
207{
208 if (newGeometry.width() != oldGeometry.width() ||
209 newGeometry.height() != oldGeometry.height())
210 clearCache();
211
212 QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
213}
214
215QVariant QDeclarativePaintedItem::itemChange(GraphicsItemChange change,
216 const QVariant &value)
217{
218 if (change == ItemVisibleHasChanged)
219 clearCache();
220
221 return QDeclarativeItem::itemChange(change, value);
222}
223
224void QDeclarativePaintedItem::setCacheFrozen(bool frozen)
225{
226 Q_D(QDeclarativePaintedItem);
227 if (d->cachefrozen == frozen)
228 return;
229 d->cachefrozen = frozen;
230 // XXX clear cache?
231}
232
233QRectF QDeclarativePaintedItem::boundingRect() const
234{
235 Q_D(const QDeclarativePaintedItem);
236 qreal w = d->mWidth;
237 QSizeF sz = d->contentsSize * d->contentsScale;
238 if (w < sz.width())
239 w = sz.width();
240 qreal h = d->mHeight;
241 if (h < sz.height())
242 h = sz.height();
243 return QRectF(0.0,0.0,w,h);
244}
245
246/*!
247 \internal
248*/
249void QDeclarativePaintedItem::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
250{
251 Q_D(QDeclarativePaintedItem);
252 const QRect content = boundingRect().toRect();
253 if (content.width() <= 0 || content.height() <= 0)
254 return;
255
256 ++inpaint;
257
258 const QTransform &x = p->deviceTransform();
259 QTransform xinv = x.inverted();
260 QRegion effectiveClip;
261 QRegion sysClip = p->paintEngine()->systemClip();
262 if (xinv.type() <= QTransform::TxScale && sysClip.numRects() < 5) {
263 // simple transform, region gets no more complicated...
264 effectiveClip = xinv.map(sysClip);
265 } else {
266 // do not make complicated regions...
267 effectiveClip = xinv.mapRect(sysClip.boundingRect());
268 }
269
270 QRegion topaint = p->clipRegion();
271 if (topaint.isEmpty()) {
272 if (effectiveClip.isEmpty())
273 topaint = QRect(0,0,p->device()->width(),p->device()->height());
274 else
275 topaint = effectiveClip;
276 } else if (!effectiveClip.isEmpty()) {
277 topaint &= effectiveClip;
278 }
279
280 topaint &= content;
281 QRegion uncached(content);
282 p->setRenderHints(QPainter::SmoothPixmapTransform, d->smooth);
283
284 int cachesize=0;
285 for (int i=0; i<d->imagecache.count(); ++i) {
286 QRect area = d->imagecache[i]->area;
287 if (topaint.contains(area)) {
288 QRectF target(area.x(), area.y(), area.width(), area.height());
289 if (!d->cachefrozen) {
290 if (!d->imagecache[i]->dirty.isNull() && topaint.contains(d->imagecache[i]->dirty)) {
291#ifdef Q_WS_MAC
292 bool oldSmooth = qt_applefontsmoothing_enabled;
293 qt_applefontsmoothing_enabled = false;
294#endif
295 QPainter qp(&d->imagecache[i]->image);
296#ifdef Q_WS_MAC
297 qt_applefontsmoothing_enabled = oldSmooth;
298#endif
299 qp.setRenderHints(QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, d->smoothCache);
300 qp.translate(-area.x(), -area.y());
301 qp.scale(d->contentsScale,d->contentsScale);
302 QRect clip = d->imagecache[i]->dirty;
303 QRect sclip(qFloor(clip.x()/d->contentsScale),
304 qFloor(clip.y()/d->contentsScale),
305 qCeil(clip.width()/d->contentsScale+clip.x()/d->contentsScale-qFloor(clip.x()/d->contentsScale)),
306 qCeil(clip.height()/d->contentsScale+clip.y()/d->contentsScale-qFloor(clip.y()/d->contentsScale)));
307 qp.setClipRect(sclip);
308 if (d->fillColor.isValid()){
309 if(d->fillColor.alpha() < 255){
310 // ### Might not work outside of raster paintengine
311 QPainter::CompositionMode prev = qp.compositionMode();
312 qp.setCompositionMode(QPainter::CompositionMode_Source);
313 qp.fillRect(sclip,d->fillColor);
314 qp.setCompositionMode(prev);
315 }else{
316 qp.fillRect(sclip,d->fillColor);
317 }
318 }
319 drawContents(&qp, sclip);
320 d->imagecache[i]->dirty = QRect();
321 }
322 }
323 p->drawPixmap(target.toRect(), d->imagecache[i]->image);
324 topaint -= area;
325 d->imagecache[i]->age=0;
326 } else {
327 d->imagecache[i]->age++;
328 }
329 cachesize += area.width()*area.height();
330 uncached -= area;
331 }
332
333 if (!topaint.isEmpty()) {
334 if (!d->cachefrozen) {
335 // Find a sensible larger area, otherwise will paint lots of tiny images.
336 QRect biggerrect = topaint.boundingRect().adjusted(-64,-64,128,128);
337 cachesize += biggerrect.width() * biggerrect.height();
338 while (d->imagecache.count() && cachesize > d->max_imagecache_size) {
339 int oldest=-1;
340 int age=-1;
341 for (int i=0; i<d->imagecache.count(); ++i) {
342 int a = d->imagecache[i]->age;
343 if (a > age) {
344 oldest = i;
345 age = a;
346 }
347 }
348 cachesize -= d->imagecache[oldest]->area.width()*d->imagecache[oldest]->area.height();
349 uncached += d->imagecache[oldest]->area;
350 delete d->imagecache.takeAt(oldest);
351 }
352 const QRegion bigger = QRegion(biggerrect) & uncached;
353 const QVector<QRect> rects = bigger.rects();
354 for (int i = 0; i < rects.count(); ++i) {
355 const QRect &r = rects.at(i);
356 QPixmap img(r.size());
357 if (d->fillColor.isValid())
358 img.fill(d->fillColor);
359 {
360#ifdef Q_WS_MAC
361 bool oldSmooth = qt_applefontsmoothing_enabled;
362 qt_applefontsmoothing_enabled = false;
363#endif
364 QPainter qp(&img);
365#ifdef Q_WS_MAC
366 qt_applefontsmoothing_enabled = oldSmooth;
367#endif
368 qp.setRenderHints(QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, d->smoothCache);
369
370 qp.translate(-r.x(),-r.y());
371 qp.scale(d->contentsScale,d->contentsScale);
372 QRect sclip(qFloor(r.x()/d->contentsScale),
373 qFloor(r.y()/d->contentsScale),
374 qCeil(r.width()/d->contentsScale+r.x()/d->contentsScale-qFloor(r.x()/d->contentsScale)),
375 qCeil(r.height()/d->contentsScale+r.y()/d->contentsScale-qFloor(r.y()/d->contentsScale)));
376 drawContents(&qp, sclip);
377 }
378 QDeclarativePaintedItemPrivate::ImageCacheItem *newitem = new QDeclarativePaintedItemPrivate::ImageCacheItem;
379 newitem->area = r;
380 newitem->image = img;
381 d->imagecache.append(newitem);
382 p->drawPixmap(r, newitem->image);
383 }
384 } else {
385 const QVector<QRect> rects = uncached.rects();
386 for (int i = 0; i < rects.count(); ++i)
387 p->fillRect(rects.at(i), Qt::lightGray);
388 }
389 }
390
391 if (inpaint_clearcache) {
392 clearCache();
393 inpaint_clearcache = 0;
394 }
395
396 --inpaint;
397}
398
399/*!
400 \qmlproperty int PaintedItem::pixelCacheSize
401
402 This property holds the maximum number of pixels of image cache to
403 allow. The default is 0.1 megapixels. The cache will not be larger
404 than the (unscaled) size of the WebView.
405*/
406/*!
407 \property QDeclarativePaintedItem::pixelCacheSize
408
409 The maximum number of pixels of image cache to allow. The default
410 is 0.1 megapixels. The cache will not be larger than the (unscaled)
411 size of the QDeclarativePaintedItem.
412*/
413int QDeclarativePaintedItem::pixelCacheSize() const
414{
415 Q_D(const QDeclarativePaintedItem);
416 return d->max_imagecache_size;
417}
418
419void QDeclarativePaintedItem::setPixelCacheSize(int pixels)
420{
421 Q_D(QDeclarativePaintedItem);
422 if (pixels < d->max_imagecache_size) {
423 int cachesize=0;
424 for (int i=0; i<d->imagecache.count(); ++i) {
425 QRect area = d->imagecache[i]->area;
426 cachesize += area.width()*area.height();
427 }
428 while (d->imagecache.count() && cachesize > pixels) {
429 int oldest=-1;
430 int age=-1;
431 for (int i=0; i<d->imagecache.count(); ++i) {
432 int a = d->imagecache[i]->age;
433 if (a > age) {
434 oldest = i;
435 age = a;
436 }
437 }
438 cachesize -= d->imagecache[oldest]->area.width()*d->imagecache[oldest]->area.height();
439 delete d->imagecache.takeAt(oldest);
440 }
441 }
442 d->max_imagecache_size = pixels;
443}
444
445
446
447/*!
448 \property QDeclarativePaintedItem::fillColor
449
450 The color to be used to fill the item prior to calling drawContents().
451 By default, this is Qt::transparent.
452
453 Performance improvements can be achieved if subclasses call this with either an
454 invalid color (QColor()), or an appropriate solid color.
455*/
456void QDeclarativePaintedItem::setFillColor(const QColor& c)
457{
458 Q_D(QDeclarativePaintedItem);
459 if (d->fillColor == c)
460 return;
461 d->fillColor = c;
462 emit fillColorChanged();
463 update();
464}
465
466QColor QDeclarativePaintedItem::fillColor() const
467{
468 Q_D(const QDeclarativePaintedItem);
469 return d->fillColor;
470}
471
472/*!
473 \qmlproperty bool PaintedItem::smoothCache
474
475 Controls whether the cached tiles of which the item is composed are
476 rendered smoothly when they are generated.
477
478 This is in addition toe Item::smooth, which controls the smooth painting of
479 the already-painted cached tiles under transformation.
480*/
481bool QDeclarativePaintedItem::smoothCache() const
482{
483 Q_D(const QDeclarativePaintedItem);
484 return d->smoothCache;
485}
486
487void QDeclarativePaintedItem::setSmoothCache(bool on)
488{
489 Q_D(QDeclarativePaintedItem);
490 if (d->smoothCache != on) {
491 d->smoothCache = on;
492 clearCache();
493 }
494}
495
496
497QT_END_NAMESPACE
498