9c274e93faf717de9d01c32c783fa7bd2057fb2a
[profile/ivi/qtdeclarative.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 Quick 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 Quick 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::load()
293 {
294     Q_D(QDeclarativeBorderImage);
295     if (d->progress != 0.0) {
296         d->progress = 0.0;
297         emit progressChanged(d->progress);
298     }
299
300     if (d->url.isEmpty()) {
301         d->pix.clear(this);
302         d->status = Null;
303         setImplicitWidth(0);
304         setImplicitHeight(0);
305         emit statusChanged(d->status);
306         update();
307     } else {
308         d->status = Loading;
309         if (d->url.path().endsWith(QLatin1String("sci"))) {
310 #ifndef QT_NO_LOCALFILE_OPTIMIZED_QML
311             QString lf = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(d->url);
312             if (!lf.isEmpty()) {
313                 QFile file(lf);
314                 file.open(QIODevice::ReadOnly);
315                 setGridScaledImage(QDeclarativeGridScaledImage(&file));
316             } else
317 #endif
318             {
319                 QNetworkRequest req(d->url);
320                 d->sciReply = qmlEngine(this)->networkAccessManager()->get(req);
321
322                 static int sciReplyFinished = -1;
323                 static int thisSciRequestFinished = -1;
324                 if (sciReplyFinished == -1) {
325                     sciReplyFinished =
326                         QNetworkReply::staticMetaObject.indexOfSignal("finished()");
327                     thisSciRequestFinished =
328                         QDeclarativeBorderImage::staticMetaObject.indexOfSlot("sciRequestFinished()");
329                 }
330
331                 QMetaObject::connect(d->sciReply, sciReplyFinished, this,
332                                      thisSciRequestFinished, Qt::DirectConnection);
333             }
334         } else {
335
336             QDeclarativePixmap::Options options;
337             if (d->async)
338                 options |= QDeclarativePixmap::Asynchronous;
339             if (d->cache)
340                 options |= QDeclarativePixmap::Cache;
341             d->pix.clear(this);
342             d->pix.load(qmlEngine(this), d->url, options);
343
344             if (d->pix.isLoading()) {
345                 d->pix.connectFinished(this, SLOT(requestFinished()));
346                 d->pix.connectDownloadProgress(this, SLOT(requestProgress(qint64,qint64)));
347             } else {
348                 QSize impsize = d->pix.implicitSize();
349                 setImplicitWidth(impsize.width());
350                 setImplicitHeight(impsize.height());
351
352                 if (d->pix.isReady()) {
353                     d->status = Ready;
354                 } else {
355                     d->status = Error;
356                     qmlInfo(this) << d->pix.error();
357                 }
358
359                 d->progress = 1.0;
360                 emit statusChanged(d->status);
361                 emit progressChanged(d->progress);
362                 requestFinished();
363                 update();
364             }
365         }
366     }
367
368     emit statusChanged(d->status);
369 }
370
371 /*!
372     \qmlproperty int BorderImage::border.left
373     \qmlproperty int BorderImage::border.right
374     \qmlproperty int BorderImage::border.top
375     \qmlproperty int BorderImage::border.bottom
376
377     The 4 border lines (2 horizontal and 2 vertical) break the image into 9 sections,
378     as shown below:
379
380     \image declarative-scalegrid.png
381
382     Each border line (left, right, top, and bottom) specifies an offset in pixels
383     from the respective edge of the source image. By default, each border line has
384     a value of 0.
385
386     For example, the following definition sets the bottom line 10 pixels up from
387     the bottom of the image:
388
389     \qml
390     BorderImage {
391         border.bottom: 10
392         // ...
393     }
394     \endqml
395
396     The border lines can also be specified using a
397     \l {BorderImage::source}{.sci file}.
398 */
399
400 QDeclarativeScaleGrid *QDeclarativeBorderImage::border()
401 {
402     Q_D(QDeclarativeBorderImage);
403     return d->getScaleGrid();
404 }
405
406 /*!
407     \qmlproperty enumeration BorderImage::horizontalTileMode
408     \qmlproperty enumeration BorderImage::verticalTileMode
409
410     This property describes how to repeat or stretch the middle parts of the border image.
411
412     \list
413     \o BorderImage.Stretch - Scales the image to fit to the available area.
414     \o BorderImage.Repeat - Tile the image until there is no more space. May crop the last image.
415     \o BorderImage.Round - Like Repeat, but scales the images down to ensure that the last image is not cropped.
416     \endlist
417
418     The default tile mode for each property is BorderImage.Stretch.
419 */
420 QDeclarativeBorderImage::TileMode QDeclarativeBorderImage::horizontalTileMode() const
421 {
422     Q_D(const QDeclarativeBorderImage);
423     return d->horizontalTileMode;
424 }
425
426 void QDeclarativeBorderImage::setHorizontalTileMode(TileMode t)
427 {
428     Q_D(QDeclarativeBorderImage);
429     if (t != d->horizontalTileMode) {
430         d->horizontalTileMode = t;
431         emit horizontalTileModeChanged();
432         update();
433     }
434 }
435
436 QDeclarativeBorderImage::TileMode QDeclarativeBorderImage::verticalTileMode() const
437 {
438     Q_D(const QDeclarativeBorderImage);
439     return d->verticalTileMode;
440 }
441
442 void QDeclarativeBorderImage::setVerticalTileMode(TileMode t)
443 {
444     Q_D(QDeclarativeBorderImage);
445     if (t != d->verticalTileMode) {
446         d->verticalTileMode = t;
447         emit verticalTileModeChanged();
448         update();
449     }
450 }
451
452 void QDeclarativeBorderImage::setGridScaledImage(const QDeclarativeGridScaledImage& sci)
453 {
454     Q_D(QDeclarativeBorderImage);
455     if (!sci.isValid()) {
456         d->status = Error;
457         emit statusChanged(d->status);
458     } else {
459         QDeclarativeScaleGrid *sg = border();
460         sg->setTop(sci.gridTop());
461         sg->setBottom(sci.gridBottom());
462         sg->setLeft(sci.gridLeft());
463         sg->setRight(sci.gridRight());
464         d->horizontalTileMode = sci.horizontalTileRule();
465         d->verticalTileMode = sci.verticalTileRule();
466
467         d->sciurl = d->url.resolved(QUrl(sci.pixmapUrl()));
468
469         QDeclarativePixmap::Options options;
470         if (d->async)
471             options |= QDeclarativePixmap::Asynchronous;
472         if (d->cache)
473             options |= QDeclarativePixmap::Cache;
474         d->pix.clear(this);
475         d->pix.load(qmlEngine(this), d->sciurl, options);
476
477         if (d->pix.isLoading()) {
478             static int thisRequestProgress = -1;
479             static int thisRequestFinished = -1;
480             if (thisRequestProgress == -1) {
481                 thisRequestProgress =
482                     QDeclarativeBorderImage::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)");
483                 thisRequestFinished =
484                     QDeclarativeBorderImage::staticMetaObject.indexOfSlot("requestFinished()");
485             }
486
487             d->pix.connectFinished(this, thisRequestFinished);
488             d->pix.connectDownloadProgress(this, thisRequestProgress);
489
490         } else {
491
492             QSize impsize = d->pix.implicitSize();
493             setImplicitWidth(impsize.width());
494             setImplicitHeight(impsize.height());
495
496             if (d->pix.isReady()) {
497                 d->status = Ready;
498             } else {
499                 d->status = Error;
500                 qmlInfo(this) << d->pix.error();
501             }
502
503             d->progress = 1.0;
504             emit statusChanged(d->status);
505             emit progressChanged(1.0);
506             update();
507
508         }
509     }
510 }
511
512 void QDeclarativeBorderImage::requestFinished()
513 {
514     Q_D(QDeclarativeBorderImage);
515
516     QSize impsize = d->pix.implicitSize();
517     if (d->pix.isError()) {
518         d->status = Error;
519         qmlInfo(this) << d->pix.error();
520     } else {
521         d->status = Ready;
522     }
523
524     setImplicitWidth(impsize.width());
525     setImplicitHeight(impsize.height());
526
527     if (d->sourcesize.width() != d->pix.width() || d->sourcesize.height() != d->pix.height())
528         emit sourceSizeChanged();
529
530     d->progress = 1.0;
531     emit statusChanged(d->status);
532     emit progressChanged(1.0);
533     update();
534 }
535
536 #define BORDERIMAGE_MAX_REDIRECT 16
537
538 void QDeclarativeBorderImage::sciRequestFinished()
539 {
540     Q_D(QDeclarativeBorderImage);
541
542     d->redirectCount++;
543     if (d->redirectCount < BORDERIMAGE_MAX_REDIRECT) {
544         QVariant redirect = d->sciReply->attribute(QNetworkRequest::RedirectionTargetAttribute);
545         if (redirect.isValid()) {
546             QUrl url = d->sciReply->url().resolved(redirect.toUrl());
547             setSource(url);
548             return;
549         }
550     }
551     d->redirectCount=0;
552
553     if (d->sciReply->error() != QNetworkReply::NoError) {
554         d->status = Error;
555         d->sciReply->deleteLater();
556         d->sciReply = 0;
557         emit statusChanged(d->status);
558     } else {
559         QDeclarativeGridScaledImage sci(d->sciReply);
560         d->sciReply->deleteLater();
561         d->sciReply = 0;
562         setGridScaledImage(sci);
563     }
564 }
565
566 void QDeclarativeBorderImage::doUpdate()
567 {
568     update();
569 }
570
571 void QDeclarativeBorderImage::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
572 {
573     Q_D(QDeclarativeBorderImage);
574     if (d->pix.isNull() || d->width() <= 0.0 || d->height() <= 0.0)
575         return;
576
577     bool oldAA = p->testRenderHint(QPainter::Antialiasing);
578     bool oldSmooth = p->testRenderHint(QPainter::SmoothPixmapTransform);
579     QTransform oldTransform;
580     if (d->smooth)
581         p->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth);
582     if (d->mirror) {
583         oldTransform = p->transform();
584         QTransform mirror;
585         mirror.translate(d->width(), 0).scale(-1, 1.0);
586         p->setWorldTransform(mirror * oldTransform);
587     }
588
589     const QDeclarativeScaleGrid *border = d->getScaleGrid();
590     int left = border->left();
591     int right = border->right();
592     qreal borderWidth = left + right;
593     if (borderWidth > 0.0 && d->width() < borderWidth) {
594         qreal diff = borderWidth - d->width() - 1;
595         left -= qRound(diff * qreal(left) / borderWidth);
596         right -= qRound(diff * qreal(right) / borderWidth);
597     }
598     int top = border->top();
599     int bottom = border->bottom();
600     qreal borderHeight = top + bottom;
601     if (borderHeight > 0.0 && d->height() < borderHeight) {
602         qreal diff = borderHeight - d->height() - 1;
603         top -= qRound(diff * qreal(top) / borderHeight);
604         bottom -= qRound(diff * qreal(bottom) / borderHeight);
605     }
606     QMargins margins(left, top, right, bottom);
607     QTileRules rules((Qt::TileRule)d->horizontalTileMode, (Qt::TileRule)d->verticalTileMode);
608     qDrawBorderPixmap(p, QRect(0, 0, (int)d->width(), (int)d->height()), margins, d->pix, d->pix.rect(), margins, rules);
609     if (d->smooth) {
610         p->setRenderHint(QPainter::Antialiasing, oldAA);
611         p->setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth);
612     }
613     if (d->mirror)
614         p->setWorldTransform(oldTransform);
615 }
616
617 QT_END_NAMESPACE