Merge branch 4.7 into qt-4.8-from-4.7
[qt:qt.git] / src / declarative / graphicsitems / qdeclarativeborderimage.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "private/qdeclarativeborderimage_p.h"
43 #include "private/qdeclarativeborderimage_p_p.h"
44
45 #include <qdeclarativeinfo.h>
46 #include <private/qdeclarativeengine_p.h>
47
48 #include <QNetworkRequest>
49 #include <QNetworkReply>
50 #include <QFile>
51
52 QT_BEGIN_NAMESPACE
53
54 /*!
55     \qmlclass BorderImage QDeclarativeBorderImage
56     \brief The BorderImage element provides an image that can be used as a border.
57     \inherits Item
58     \since 4.7
59     \ingroup qml-basic-visual-elements
60
61     The BorderImage element is used to create borders out of images by scaling or tiling
62     parts of each image.
63
64     A BorderImage element breaks a source image, specified using the \l url property,
65     into 9 regions, as shown below:
66
67     \image declarative-scalegrid.png
68
69     When the image is scaled, regions of the source image are scaled or tiled to
70     create the displayed border image in the following way:
71
72     \list
73     \i The corners (regions 1, 3, 7, and 9) are not scaled at all.
74     \i Regions 2 and 8 are scaled according to
75        \l{BorderImage::horizontalTileMode}{horizontalTileMode}.
76     \i Regions 4 and 6 are scaled according to
77        \l{BorderImage::verticalTileMode}{verticalTileMode}.
78     \i The middle (region 5) is scaled according to both
79        \l{BorderImage::horizontalTileMode}{horizontalTileMode} and
80        \l{BorderImage::verticalTileMode}{verticalTileMode}.
81     \endlist
82
83     The regions of the image are defined using the \l border property group, which
84     describes the distance from each edge of the source image to use as a border.
85
86     \section1 Example Usage
87
88     The following examples show the effects of the different modes on an image.
89     Guide lines are overlaid onto the image to show the different regions of the
90     image as described above.
91
92     \beginfloatleft
93     \image qml-borderimage-normal-image.png
94     \endfloat
95
96     An unscaled image is displayed using an Image element. The \l border property is
97     used to determine the parts of the image that will lie inside the unscaled corner
98     areas and the parts that will be stretched horizontally and vertically.
99
100     \snippet doc/src/snippets/declarative/borderimage/normal-image.qml normal image
101
102     \clearfloat
103     \beginfloatleft
104     \image qml-borderimage-scaled.png
105     \endfloat
106
107     A BorderImage element is used to display the image, and it is given a size that is
108     larger than the original image. Since the \l horizontalTileMode property is set to
109     \l{BorderImage::horizontalTileMode}{BorderImage.Stretch}, the parts of image in
110     regions 2 and 8 are stretched horizontally. Since the \l verticalTileMode property
111     is set to \l{BorderImage::verticalTileMode}{BorderImage.Stretch}, the parts of image
112     in regions 4 and 6 are stretched vertically.
113
114     \snippet doc/src/snippets/declarative/borderimage/borderimage-scaled.qml scaled border image
115
116     \clearfloat
117     \beginfloatleft
118     \image qml-borderimage-tiled.png
119     \endfloat
120
121     Again, a large BorderImage element is used to display the image. With the
122     \l horizontalTileMode property set to \l{BorderImage::horizontalTileMode}{BorderImage.Repeat},
123     the parts of image in regions 2 and 8 are tiled so that they fill the space at the
124     top and bottom of the element. Similarly, the \l verticalTileMode property is set to
125     \l{BorderImage::verticalTileMode}{BorderImage.Repeat}, the parts of image in regions
126     4 and 6 are tiled so that they fill the space at the left and right of the element.
127
128     \snippet doc/src/snippets/declarative/borderimage/borderimage-tiled.qml tiled border image
129
130     \clearfloat
131     In some situations, the width of regions 2 and 8 may not be an exact multiple of the width
132     of the corresponding regions in the source image. Similarly, the height of regions 4 and 6
133     may not be an exact multiple of the height of the corresponding regions. It can be useful
134     to use \l{BorderImage::horizontalTileMode}{BorderImage.Round} instead of
135     \l{BorderImage::horizontalTileMode}{BorderImage.Repeat} in cases like these.
136
137     The \l{declarative/imageelements/borderimage}{BorderImage example} shows how a BorderImage
138     can be used to simulate a shadow effect on a rectangular item.
139
140     \section1 Quality and Performance
141
142     By default, any scaled regions of the image are rendered without smoothing to improve
143     rendering speed. Setting the \l smooth property improves rendering quality of scaled
144     regions, but may slow down rendering.
145
146     The source image may not be loaded instantaneously, depending on its original location.
147     Loading progress can be monitored with the \l progress property.
148
149     \sa Image, AnimatedImage
150  */
151
152 /*!
153     \qmlproperty bool BorderImage::asynchronous
154
155     Specifies that images on the local filesystem should be loaded
156     asynchronously in a separate thread.  The default value is
157     false, causing the user interface thread to block while the
158     image is loaded.  Setting \a asynchronous to true is useful where
159     maintaining a responsive user interface is more desirable
160     than having images immediately visible.
161
162     Note that this property is only valid for images read from the
163     local filesystem.  Images loaded via a network resource (e.g. HTTP)
164     are always loaded asynchonously.
165 */
166 QDeclarativeBorderImage::QDeclarativeBorderImage(QDeclarativeItem *parent)
167   : QDeclarativeImageBase(*(new QDeclarativeBorderImagePrivate), parent)
168 {
169 }
170
171 QDeclarativeBorderImage::~QDeclarativeBorderImage()
172 {
173     Q_D(QDeclarativeBorderImage);
174     if (d->sciReply)
175         d->sciReply->deleteLater();
176 }
177 /*!
178     \qmlproperty enumeration BorderImage::status
179
180     This property describes the status of image loading.  It can be one of:
181
182     \list
183     \o BorderImage.Null - no image has been set
184     \o BorderImage.Ready - the image has been loaded
185     \o BorderImage.Loading - the image is currently being loaded
186     \o BorderImage.Error - an error occurred while loading the image
187     \endlist
188
189     \sa progress
190 */
191
192 /*!
193     \qmlproperty real BorderImage::progress
194
195     This property holds the progress of image loading, from 0.0 (nothing loaded)
196     to 1.0 (finished).
197
198     \sa status
199 */
200
201 /*!
202     \qmlproperty bool BorderImage::smooth
203
204     Set this property if you want the image to be smoothly filtered when scaled or
205     transformed.  Smooth filtering gives better visual quality, but is slower.  If
206     the image is displayed at its natural size, this property has no visual or
207     performance effect.
208
209     By default, this property is set to false.
210
211     \note Generally scaling artifacts are only visible if the image is stationary on
212     the screen.  A common pattern when animating an image is to disable smooth
213     filtering at the beginning of the animation and enable it at the conclusion.
214 */
215
216 /*!
217     \qmlproperty bool BorderImage::cache
218     \since QtQuick 1.1
219
220     Specifies whether the image should be cached. The default value is
221     true. Setting \a cache to false is useful when dealing with large images,
222     to make sure that they aren't cached at the expense of small 'ui element' images.
223 */
224
225 /*!
226     \qmlproperty bool BorderImage::mirror
227     \since QtQuick 1.1
228
229     This property holds whether the image should be horizontally inverted
230     (effectively displaying a mirrored image).
231
232     The default value is false.
233 */
234
235 /*!
236     \qmlproperty url BorderImage::source
237
238     This property holds the URL that refers to the source image.
239
240     BorderImage can handle any image format supported by Qt, loaded from any
241     URL scheme supported by Qt.
242
243     This property can also be used to refer to .sci files, which are
244     written in a QML-specific, text-based format that specifies the
245     borders, the image file and the tile rules for a given border image.
246
247     The following .sci file sets the borders to 10 on each side for the
248     image \c picture.png:
249
250     \code
251     border.left: 10
252     border.top: 10
253     border.bottom: 10
254     border.right: 10
255     source: "picture.png"
256     \endcode
257
258     The URL may be absolute, or relative to the URL of the component.
259
260     \sa QDeclarativeImageProvider
261 */
262
263 /*!
264     \qmlproperty QSize BorderImage::sourceSize
265
266     This property holds the actual width and height of the loaded image.
267
268     In BorderImage, this property is read-only.
269
270     \sa Image::sourceSize
271 */
272 void QDeclarativeBorderImage::setSource(const QUrl &url)
273 {
274     Q_D(QDeclarativeBorderImage);
275     //equality is fairly expensive, so we bypass for simple, common case
276     if ((d->url.isEmpty() == url.isEmpty()) && url == d->url)
277         return;
278
279     if (d->sciReply) {
280         d->sciReply->deleteLater();
281         d->sciReply = 0;
282     }
283
284     d->url = url;
285     d->sciurl = QUrl();
286     emit sourceChanged(d->url);
287
288     if (isComponentComplete())
289         load();
290 }
291
292 void QDeclarativeBorderImage::setSourceSize(const QSize& size)
293 {
294     Q_UNUSED(size);
295     qmlInfo(this) << "Setting sourceSize for borderImage not supported";
296 }
297
298 void QDeclarativeBorderImage::load()
299 {
300     Q_D(QDeclarativeBorderImage);
301     if (d->progress != 0.0) {
302         d->progress = 0.0;
303         emit progressChanged(d->progress);
304     }
305
306     if (d->url.isEmpty()) {
307         d->pix.clear(this);
308         d->status = Null;
309         setImplicitWidth(0);
310         setImplicitHeight(0);
311         emit statusChanged(d->status);
312         update();
313     } else {
314         d->status = Loading;
315         if (d->url.path().endsWith(QLatin1String("sci"))) {
316 #ifndef QT_NO_LOCALFILE_OPTIMIZED_QML
317             QString lf = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(d->url);
318             if (!lf.isEmpty()) {
319                 QFile file(lf);
320                 file.open(QIODevice::ReadOnly);
321                 setGridScaledImage(QDeclarativeGridScaledImage(&file));
322             } else
323 #endif
324             {
325                 QNetworkRequest req(d->url);
326                 d->sciReply = qmlEngine(this)->networkAccessManager()->get(req);
327
328                 static int sciReplyFinished = -1;
329                 static int thisSciRequestFinished = -1;
330                 if (sciReplyFinished == -1) {
331                     sciReplyFinished =
332                         QNetworkReply::staticMetaObject.indexOfSignal("finished()");
333                     thisSciRequestFinished =
334                         QDeclarativeBorderImage::staticMetaObject.indexOfSlot("sciRequestFinished()");
335                 }
336
337                 QMetaObject::connect(d->sciReply, sciReplyFinished, this,
338                                      thisSciRequestFinished, Qt::DirectConnection);
339             }
340         } else {
341
342             QDeclarativePixmap::Options options;
343             if (d->async)
344                 options |= QDeclarativePixmap::Asynchronous;
345             if (d->cache)
346                 options |= QDeclarativePixmap::Cache;
347             d->pix.clear(this);
348             d->pix.load(qmlEngine(this), d->url, options);
349
350             if (d->pix.isLoading()) {
351                 d->pix.connectFinished(this, SLOT(requestFinished()));
352                 d->pix.connectDownloadProgress(this, SLOT(requestProgress(qint64,qint64)));
353             } else {
354                 QSize impsize = d->pix.implicitSize();
355                 setImplicitWidth(impsize.width());
356                 setImplicitHeight(impsize.height());
357
358                 if (d->pix.isReady()) {
359                     d->status = Ready;
360                 } else {
361                     d->status = Error;
362                     qmlInfo(this) << d->pix.error();
363                 }
364
365                 d->progress = 1.0;
366                 emit statusChanged(d->status);
367                 emit progressChanged(d->progress);
368                 requestFinished();
369                 update();
370             }
371         }
372     }
373
374     emit statusChanged(d->status);
375 }
376
377 /*!
378     \qmlproperty int BorderImage::border.left
379     \qmlproperty int BorderImage::border.right
380     \qmlproperty int BorderImage::border.top
381     \qmlproperty int BorderImage::border.bottom
382
383     The 4 border lines (2 horizontal and 2 vertical) break the image into 9 sections,
384     as shown below:
385
386     \image declarative-scalegrid.png
387
388     Each border line (left, right, top, and bottom) specifies an offset in pixels
389     from the respective edge of the source image. By default, each border line has
390     a value of 0.
391
392     For example, the following definition sets the bottom line 10 pixels up from
393     the bottom of the image:
394
395     \qml
396     BorderImage {
397         border.bottom: 10
398         // ...
399     }
400     \endqml
401
402     The border lines can also be specified using a
403     \l {BorderImage::source}{.sci file}.
404 */
405
406 QDeclarativeScaleGrid *QDeclarativeBorderImage::border()
407 {
408     Q_D(QDeclarativeBorderImage);
409     return d->getScaleGrid();
410 }
411
412 /*!
413     \qmlproperty enumeration BorderImage::horizontalTileMode
414     \qmlproperty enumeration BorderImage::verticalTileMode
415
416     This property describes how to repeat or stretch the middle parts of the border image.
417
418     \list
419     \o BorderImage.Stretch - Scales the image to fit to the available area.
420     \o BorderImage.Repeat - Tile the image until there is no more space. May crop the last image.
421     \o BorderImage.Round - Like Repeat, but scales the images down to ensure that the last image is not cropped.
422     \endlist
423
424     The default tile mode for each property is BorderImage.Stretch.
425 */
426 QDeclarativeBorderImage::TileMode QDeclarativeBorderImage::horizontalTileMode() const
427 {
428     Q_D(const QDeclarativeBorderImage);
429     return d->horizontalTileMode;
430 }
431
432 void QDeclarativeBorderImage::setHorizontalTileMode(TileMode t)
433 {
434     Q_D(QDeclarativeBorderImage);
435     if (t != d->horizontalTileMode) {
436         d->horizontalTileMode = t;
437         emit horizontalTileModeChanged();
438         update();
439     }
440 }
441
442 QDeclarativeBorderImage::TileMode QDeclarativeBorderImage::verticalTileMode() const
443 {
444     Q_D(const QDeclarativeBorderImage);
445     return d->verticalTileMode;
446 }
447
448 void QDeclarativeBorderImage::setVerticalTileMode(TileMode t)
449 {
450     Q_D(QDeclarativeBorderImage);
451     if (t != d->verticalTileMode) {
452         d->verticalTileMode = t;
453         emit verticalTileModeChanged();
454         update();
455     }
456 }
457
458 void QDeclarativeBorderImage::setGridScaledImage(const QDeclarativeGridScaledImage& sci)
459 {
460     Q_D(QDeclarativeBorderImage);
461     if (!sci.isValid()) {
462         d->status = Error;
463         emit statusChanged(d->status);
464     } else {
465         QDeclarativeScaleGrid *sg = border();
466         sg->setTop(sci.gridTop());
467         sg->setBottom(sci.gridBottom());
468         sg->setLeft(sci.gridLeft());
469         sg->setRight(sci.gridRight());
470         d->horizontalTileMode = sci.horizontalTileRule();
471         d->verticalTileMode = sci.verticalTileRule();
472
473         d->sciurl = d->url.resolved(QUrl(sci.pixmapUrl()));
474
475         QDeclarativePixmap::Options options;
476         if (d->async)
477             options |= QDeclarativePixmap::Asynchronous;
478         if (d->cache)
479             options |= QDeclarativePixmap::Cache;
480         d->pix.clear(this);
481         d->pix.load(qmlEngine(this), d->sciurl, options);
482
483         if (d->pix.isLoading()) {
484             static int thisRequestProgress = -1;
485             static int thisRequestFinished = -1;
486             if (thisRequestProgress == -1) {
487                 thisRequestProgress =
488                     QDeclarativeBorderImage::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)");
489                 thisRequestFinished =
490                     QDeclarativeBorderImage::staticMetaObject.indexOfSlot("requestFinished()");
491             }
492
493             d->pix.connectFinished(this, thisRequestFinished);
494             d->pix.connectDownloadProgress(this, thisRequestProgress);
495
496         } else {
497
498             QSize impsize = d->pix.implicitSize();
499             setImplicitWidth(impsize.width());
500             setImplicitHeight(impsize.height());
501
502             if (d->pix.isReady()) {
503                 d->status = Ready;
504             } else {
505                 d->status = Error;
506                 qmlInfo(this) << d->pix.error();
507             }
508
509             d->progress = 1.0;
510             emit statusChanged(d->status);
511             emit progressChanged(1.0);
512             update();
513
514         }
515     }
516 }
517
518 void QDeclarativeBorderImage::requestFinished()
519 {
520     Q_D(QDeclarativeBorderImage);
521
522     QSize impsize = d->pix.implicitSize();
523     if (d->pix.isError()) {
524         d->status = Error;
525         qmlInfo(this) << d->pix.error();
526     } else {
527         d->status = Ready;
528     }
529
530     setImplicitWidth(impsize.width());
531     setImplicitHeight(impsize.height());
532
533     if (d->sourcesize.width() != d->pix.width() || d->sourcesize.height() != d->pix.height())
534         emit sourceSizeChanged();
535
536     d->progress = 1.0;
537     emit statusChanged(d->status);
538     emit progressChanged(1.0);
539     update();
540 }
541
542 #define BORDERIMAGE_MAX_REDIRECT 16
543
544 void QDeclarativeBorderImage::sciRequestFinished()
545 {
546     Q_D(QDeclarativeBorderImage);
547
548     d->redirectCount++;
549     if (d->redirectCount < BORDERIMAGE_MAX_REDIRECT) {
550         QVariant redirect = d->sciReply->attribute(QNetworkRequest::RedirectionTargetAttribute);
551         if (redirect.isValid()) {
552             QUrl url = d->sciReply->url().resolved(redirect.toUrl());
553             setSource(url);
554             return;
555         }
556     }
557     d->redirectCount=0;
558
559     if (d->sciReply->error() != QNetworkReply::NoError) {
560         d->status = Error;
561         d->sciReply->deleteLater();
562         d->sciReply = 0;
563         emit statusChanged(d->status);
564     } else {
565         QDeclarativeGridScaledImage sci(d->sciReply);
566         d->sciReply->deleteLater();
567         d->sciReply = 0;
568         setGridScaledImage(sci);
569     }
570 }
571
572 void QDeclarativeBorderImage::doUpdate()
573 {
574     update();
575 }
576
577 void QDeclarativeBorderImage::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
578 {
579     Q_D(QDeclarativeBorderImage);
580     if (d->pix.isNull() || d->width() <= 0.0 || d->height() <= 0.0)
581         return;
582
583     bool oldAA = p->testRenderHint(QPainter::Antialiasing);
584     bool oldSmooth = p->testRenderHint(QPainter::SmoothPixmapTransform);
585     QTransform oldTransform;
586     if (d->smooth)
587         p->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth);
588     if (d->mirror) {
589         oldTransform = p->transform();
590         QTransform mirror;
591         mirror.translate(d->width(), 0).scale(-1, 1.0);
592         p->setWorldTransform(mirror * oldTransform);
593     }
594
595     const QDeclarativeScaleGrid *border = d->getScaleGrid();
596     int left = border->left();
597     int right = border->right();
598     qreal borderWidth = left + right;
599     if (borderWidth > 0.0 && d->width() < borderWidth) {
600         qreal diff = borderWidth - d->width() - 1;
601         left -= qRound(diff * qreal(left) / borderWidth);
602         right -= qRound(diff * qreal(right) / borderWidth);
603     }
604     int top = border->top();
605     int bottom = border->bottom();
606     qreal borderHeight = top + bottom;
607     if (borderHeight > 0.0 && d->height() < borderHeight) {
608         qreal diff = borderHeight - d->height() - 1;
609         top -= qRound(diff * qreal(top) / borderHeight);
610         bottom -= qRound(diff * qreal(bottom) / borderHeight);
611     }
612     QMargins margins(left, top, right, bottom);
613     QTileRules rules((Qt::TileRule)d->horizontalTileMode, (Qt::TileRule)d->verticalTileMode);
614     qDrawBorderPixmap(p, QRect(0, 0, (int)d->width(), (int)d->height()), margins, d->pix, d->pix.rect(), margins, rules);
615     if (d->smooth) {
616         p->setRenderHint(QPainter::Antialiasing, oldAA);
617         p->setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth);
618     }
619     if (d->mirror)
620         p->setWorldTransform(oldTransform);
621 }
622
623 QT_END_NAMESPACE