72457a3fc482fa00ba9819b8917dd30216923539
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickborderimage.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
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 "qquickborderimage_p.h"
43 #include "qquickborderimage_p_p.h"
44 #include "qquickninepatchnode_p.h"
45
46 #include <QtDeclarative/qdeclarativeinfo.h>
47 #include <QtCore/qfile.h>
48
49 #include <private/qdeclarativeengine_p.h>
50
51 QT_BEGIN_NAMESPACE
52
53
54 /*!
55     \qmlclass BorderImage QQuickBorderImage
56     \inqmlmodule QtQuick 2
57     \brief The BorderImage element provides an image that can be used as a border.
58     \inherits Item
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 QtQuick2::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 asynchronously.
165 */
166 QQuickBorderImage::QQuickBorderImage(QQuickItem *parent)
167 : QQuickImageBase(*(new QQuickBorderImagePrivate), parent)
168 {
169 }
170
171 QQuickBorderImage::~QQuickBorderImage()
172 {
173     Q_D(QQuickBorderImage);
174     if (d->sciReply)
175         d->sciReply->deleteLater();
176 }
177
178 /*!
179     \qmlproperty enumeration QtQuick2::BorderImage::status
180
181     This property describes the status of image loading.  It can be one of:
182
183     \list
184     \o BorderImage.Null - no image has been set
185     \o BorderImage.Ready - the image has been loaded
186     \o BorderImage.Loading - the image is currently being loaded
187     \o BorderImage.Error - an error occurred while loading the image
188     \endlist
189
190     \sa progress
191 */
192
193 /*!
194     \qmlproperty real QtQuick2::BorderImage::progress
195
196     This property holds the progress of image loading, from 0.0 (nothing loaded)
197     to 1.0 (finished).
198
199     \sa status
200 */
201
202 /*!
203     \qmlproperty bool QtQuick2::BorderImage::smooth
204
205     Set this property if you want the image to be smoothly filtered when scaled or
206     transformed.  Smooth filtering gives better visual quality, but is slower.  If
207     the image is displayed at its natural size, this property has no visual or
208     performance effect.
209
210     By default, this property is set to false.
211
212     \note Generally scaling artifacts are only visible if the image is stationary on
213     the screen.  A common pattern when animating an image is to disable smooth
214     filtering at the beginning of the animation and enable it at the conclusion.
215 */
216
217 /*!
218     \qmlproperty bool QtQuick2::BorderImage::cache
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 QtQuick2::BorderImage::mirror
227
228     This property holds whether the image should be horizontally inverted
229     (effectively displaying a mirrored image).
230
231     The default value is false.
232 */
233
234 /*!
235     \qmlproperty url QtQuick2::BorderImage::source
236
237     This property holds the URL that refers to the source image.
238
239     BorderImage can handle any image format supported by Qt, loaded from any
240     URL scheme supported by Qt.
241
242     This property can also be used to refer to .sci files, which are
243     written in a QML-specific, text-based format that specifies the
244     borders, the image file and the tile rules for a given border image.
245
246     The following .sci file sets the borders to 10 on each side for the
247     image \c picture.png:
248
249     \code
250     border.left: 10
251     border.top: 10
252     border.bottom: 10
253     border.right: 10
254     source: "picture.png"
255     \endcode
256
257     The URL may be absolute, or relative to the URL of the component.
258
259     \sa QDeclarativeImageProvider
260 */
261
262 /*!
263     \qmlproperty QSize QtQuick2::BorderImage::sourceSize
264
265     This property holds the actual width and height of the loaded image.
266
267     In BorderImage, this property is read-only.
268
269     \sa Image::sourceSize
270 */
271 void QQuickBorderImage::setSource(const QUrl &url)
272 {
273     Q_D(QQuickBorderImage);
274     //equality is fairly expensive, so we bypass for simple, common case
275     if ((d->url.isEmpty() == url.isEmpty()) && url == d->url)
276         return;
277
278     if (d->sciReply) {
279         d->sciReply->deleteLater();
280         d->sciReply = 0;
281     }
282
283     d->url = url;
284     d->sciurl = QUrl();
285     emit sourceChanged(d->url);
286
287     if (isComponentComplete())
288         load();
289 }
290
291 void QQuickBorderImage::load()
292 {
293     Q_D(QQuickBorderImage);
294     if (d->progress != 0.0) {
295         d->progress = 0.0;
296         emit progressChanged(d->progress);
297     }
298
299     if (d->url.isEmpty()) {
300         d->pix.clear(this);
301         d->status = Null;
302         setImplicitSize(0, 0);
303         emit statusChanged(d->status);
304         update();
305     } else {
306         d->status = Loading;
307         if (d->url.path().endsWith(QLatin1String("sci"))) {
308             QString lf = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(d->url);
309             if (!lf.isEmpty()) {
310                 QFile file(lf);
311                 file.open(QIODevice::ReadOnly);
312                 setGridScaledImage(QQuickGridScaledImage(&file));
313             } else {
314                 QNetworkRequest req(d->url);
315                 d->sciReply = qmlEngine(this)->networkAccessManager()->get(req);
316                 FAST_CONNECT(d->sciReply, SIGNAL(finished()), this, SLOT(sciRequestFinished()))
317             }
318         } else {
319
320             QDeclarativePixmap::Options options;
321             if (d->async)
322                 options |= QDeclarativePixmap::Asynchronous;
323             if (d->cache)
324                 options |= QDeclarativePixmap::Cache;
325             d->pix.clear(this);
326             d->pix.load(qmlEngine(this), d->url, options);
327
328             if (d->pix.isLoading()) {
329                 d->pix.connectFinished(this, SLOT(requestFinished()));
330                 d->pix.connectDownloadProgress(this, SLOT(requestProgress(qint64,qint64)));
331             } else {
332                 QSize impsize = d->pix.implicitSize();
333                 setImplicitSize(impsize.width(), impsize.height());
334
335                 if (d->pix.isReady()) {
336                     d->status = Ready;
337                 } else {
338                     d->status = Error;
339                     qmlInfo(this) << d->pix.error();
340                 }
341
342                 d->progress = 1.0;
343                 emit statusChanged(d->status);
344                 emit progressChanged(d->progress);
345                 update();
346             }
347         }
348     }
349
350     emit statusChanged(d->status);
351 }
352
353 /*!
354     \qmlproperty int QtQuick2::BorderImage::border.left
355     \qmlproperty int QtQuick2::BorderImage::border.right
356     \qmlproperty int QtQuick2::BorderImage::border.top
357     \qmlproperty int QtQuick2::BorderImage::border.bottom
358
359     The 4 border lines (2 horizontal and 2 vertical) break the image into 9 sections,
360     as shown below:
361
362     \image declarative-scalegrid.png
363
364     Each border line (left, right, top, and bottom) specifies an offset in pixels
365     from the respective edge of the source image. By default, each border line has
366     a value of 0.
367
368     For example, the following definition sets the bottom line 10 pixels up from
369     the bottom of the image:
370
371     \qml
372     BorderImage {
373         border.bottom: 10
374         // ...
375     }
376     \endqml
377
378     The border lines can also be specified using a
379     \l {BorderImage::source}{.sci file}.
380 */
381
382 QQuickScaleGrid *QQuickBorderImage::border()
383 {
384     Q_D(QQuickBorderImage);
385     return d->getScaleGrid();
386 }
387
388 /*!
389     \qmlproperty enumeration QtQuick2::BorderImage::horizontalTileMode
390     \qmlproperty enumeration QtQuick2::BorderImage::verticalTileMode
391
392     This property describes how to repeat or stretch the middle parts of the border image.
393
394     \list
395     \o BorderImage.Stretch - Scales the image to fit to the available area.
396     \o BorderImage.Repeat - Tile the image until there is no more space. May crop the last image.
397     \o BorderImage.Round - Like Repeat, but scales the images down to ensure that the last image is not cropped.
398     \endlist
399
400     The default tile mode for each property is BorderImage.Stretch.
401 */
402 QQuickBorderImage::TileMode QQuickBorderImage::horizontalTileMode() const
403 {
404     Q_D(const QQuickBorderImage);
405     return d->horizontalTileMode;
406 }
407
408 void QQuickBorderImage::setHorizontalTileMode(TileMode t)
409 {
410     Q_D(QQuickBorderImage);
411     if (t != d->horizontalTileMode) {
412         d->horizontalTileMode = t;
413         emit horizontalTileModeChanged();
414         update();
415     }
416 }
417
418 QQuickBorderImage::TileMode QQuickBorderImage::verticalTileMode() const
419 {
420     Q_D(const QQuickBorderImage);
421     return d->verticalTileMode;
422 }
423
424 void QQuickBorderImage::setVerticalTileMode(TileMode t)
425 {
426     Q_D(QQuickBorderImage);
427     if (t != d->verticalTileMode) {
428         d->verticalTileMode = t;
429         emit verticalTileModeChanged();
430         update();
431     }
432 }
433
434 void QQuickBorderImage::setGridScaledImage(const QQuickGridScaledImage& sci)
435 {
436     Q_D(QQuickBorderImage);
437     if (!sci.isValid()) {
438         d->status = Error;
439         emit statusChanged(d->status);
440     } else {
441         QQuickScaleGrid *sg = border();
442         sg->setTop(sci.gridTop());
443         sg->setBottom(sci.gridBottom());
444         sg->setLeft(sci.gridLeft());
445         sg->setRight(sci.gridRight());
446         d->horizontalTileMode = sci.horizontalTileRule();
447         d->verticalTileMode = sci.verticalTileRule();
448
449         d->sciurl = d->url.resolved(QUrl(sci.pixmapUrl()));
450
451         QDeclarativePixmap::Options options;
452         if (d->async)
453             options |= QDeclarativePixmap::Asynchronous;
454         if (d->cache)
455             options |= QDeclarativePixmap::Cache;
456         d->pix.clear(this);
457         d->pix.load(qmlEngine(this), d->sciurl, options);
458
459         if (d->pix.isLoading()) {
460             static int thisRequestProgress = -1;
461             static int thisRequestFinished = -1;
462             if (thisRequestProgress == -1) {
463                 thisRequestProgress =
464                     QQuickBorderImage::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)");
465                 thisRequestFinished =
466                     QQuickBorderImage::staticMetaObject.indexOfSlot("requestFinished()");
467             }
468
469             d->pix.connectFinished(this, thisRequestFinished);
470             d->pix.connectDownloadProgress(this, thisRequestProgress);
471
472         } else {
473
474             QSize impsize = d->pix.implicitSize();
475             setImplicitSize(impsize.width(), impsize.height());
476
477             if (d->pix.isReady()) {
478                 d->status = Ready;
479             } else {
480                 d->status = Error;
481                 qmlInfo(this) << d->pix.error();
482             }
483
484             d->progress = 1.0;
485             emit statusChanged(d->status);
486             emit progressChanged(1.0);
487             update();
488
489         }
490     }
491 }
492
493 void QQuickBorderImage::requestFinished()
494 {
495     Q_D(QQuickBorderImage);
496
497     QSize impsize = d->pix.implicitSize();
498     if (d->pix.isError()) {
499         d->status = Error;
500         qmlInfo(this) << d->pix.error();
501     } else {
502         d->status = Ready;
503     }
504
505     setImplicitSize(impsize.width(), impsize.height());
506
507     if (d->sourcesize.width() != d->pix.width() || d->sourcesize.height() != d->pix.height())
508         emit sourceSizeChanged();
509
510     d->progress = 1.0;
511     emit statusChanged(d->status);
512     emit progressChanged(1.0);
513     update();
514 }
515
516 #define BORDERIMAGE_MAX_REDIRECT 16
517
518 void QQuickBorderImage::sciRequestFinished()
519 {
520     Q_D(QQuickBorderImage);
521
522     d->redirectCount++;
523     if (d->redirectCount < BORDERIMAGE_MAX_REDIRECT) {
524         QVariant redirect = d->sciReply->attribute(QNetworkRequest::RedirectionTargetAttribute);
525         if (redirect.isValid()) {
526             QUrl url = d->sciReply->url().resolved(redirect.toUrl());
527             setSource(url);
528             return;
529         }
530     }
531     d->redirectCount=0;
532
533     if (d->sciReply->error() != QNetworkReply::NoError) {
534         d->status = Error;
535         d->sciReply->deleteLater();
536         d->sciReply = 0;
537         emit statusChanged(d->status);
538     } else {
539         QQuickGridScaledImage sci(d->sciReply);
540         d->sciReply->deleteLater();
541         d->sciReply = 0;
542         setGridScaledImage(sci);
543     }
544 }
545
546 void QQuickBorderImage::doUpdate()
547 {
548     update();
549 }
550
551 QSGNode *QQuickBorderImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
552 {
553     Q_D(QQuickBorderImage);
554
555     QSGTexture *texture = d->sceneGraphContext()->textureForFactory(d->pix.textureFactory());
556
557     if (!texture || width() <= 0 || height() <= 0) {
558         delete oldNode;
559         return 0;
560     }
561
562     QQuickNinePatchNode *node = static_cast<QQuickNinePatchNode *>(oldNode);
563
564     if (!node) {
565         node = new QQuickNinePatchNode();
566     }
567
568     node->setTexture(texture);
569
570     // Don't implicitly create the scalegrid in the rendering thread...
571     if (d->border) {
572         const QQuickScaleGrid *border = d->getScaleGrid();
573         node->setInnerRect(QRectF(border->left(),
574                                   border->top(),
575                                   qMax(1, d->pix.width() - border->right() - border->left()),
576                                   qMax(1, d->pix.height() - border->bottom() - border->top())));
577     } else {
578         node->setInnerRect(QRectF(0, 0, d->pix.width(), d->pix.height()));
579     }
580     node->setRect(QRectF(0, 0, width(), height()));
581     node->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
582     node->setHorzontalTileMode(d->horizontalTileMode);
583     node->setVerticalTileMode(d->verticalTileMode);
584     node->setMirror(d->mirror);
585     node->update();
586
587     return node;
588 }
589
590 void QQuickBorderImage::pixmapChange()
591 {
592     Q_D(QQuickBorderImage);
593
594     d->pixmapChanged = true;
595
596     // When the pixmap changes, such as being deleted, we need to update the textures
597     update();
598 }
599
600 QT_END_NAMESPACE