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