Initial import from the monolithic Qt.
[profile/ivi/qtdeclarative.git] / src / declarative / graphicsitems / qdeclarativerectangle.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 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "private/qdeclarativerectangle_p.h"
43 #include "private/qdeclarativerectangle_p_p.h"
44
45 #include <QPainter>
46 #include <QStringBuilder>
47 #include <QtCore/qmath.h>
48
49 QT_BEGIN_NAMESPACE
50
51 /*!
52     \internal
53     \class QDeclarativePen
54     \brief The QDeclarativePen class provides a pen used for drawing rectangle borders on a QDeclarativeView.
55
56     By default, the pen is invalid and nothing is drawn. You must either set a color (then the default
57     width is 1) or a width (then the default color is black).
58
59     A width of 1 indicates is a single-pixel line on the border of the item being painted.
60
61     Example:
62     \qml
63     Rectangle {
64         border.width: 2
65         border.color: "red"
66     }
67     \endqml
68 */
69
70 void QDeclarativePen::setColor(const QColor &c)
71 {
72     _color = c;
73     _valid = (_color.alpha() && _width >= 1) ? true : false;
74     emit penChanged();
75 }
76
77 void QDeclarativePen::setWidth(int w)
78 {
79     if (_width == w && _valid)
80         return;
81
82     _width = w;
83     _valid = (_color.alpha() && _width >= 1) ? true : false;
84     emit penChanged();
85 }
86
87
88 /*!
89     \qmlclass GradientStop QDeclarativeGradientStop
90     \ingroup qml-basic-visual-elements
91     \since 4.7
92     \brief The GradientStop item defines the color at a position in a Gradient.
93
94     \sa Gradient
95 */
96
97 /*!
98     \qmlproperty real GradientStop::position
99     \qmlproperty color GradientStop::color
100
101     The position and color properties describe the color used at a given
102     position in a gradient, as represented by a gradient stop.
103
104     The default position is 0.0; the default color is black.
105
106     \sa Gradient
107 */
108
109 void QDeclarativeGradientStop::updateGradient()
110 {
111     if (QDeclarativeGradient *grad = qobject_cast<QDeclarativeGradient*>(parent()))
112         grad->doUpdate();
113 }
114
115 /*!
116     \qmlclass Gradient QDeclarativeGradient
117     \ingroup qml-basic-visual-elements
118     \since 4.7
119     \brief The Gradient item defines a gradient fill.
120
121     A gradient is defined by two or more colors, which will be blended seamlessly.
122
123     The colors are specified as a set of GradientStop child items, each of
124     which defines a position on the gradient from 0.0 to 1.0 and a color.
125     The position of each GradientStop is defined by setting its
126     \l{GradientStop::}{position} property; its color is defined using its
127     \l{GradientStop::}{color} property.
128
129     A gradient without any gradient stops is rendered as a solid white fill.
130
131     Note that this item is not a visual representation of a gradient. To display a
132     gradient, use a visual element (like \l Rectangle) which supports the use
133     of gradients.
134
135     \section1 Example Usage
136
137     \div {class="float-right"}
138     \inlineimage qml-gradient.png
139     \enddiv
140
141     The following example declares a \l Rectangle item with a gradient starting
142     with red, blending to yellow at one third of the height of the rectangle,
143     and ending with green:
144
145     \snippet doc/src/snippets/declarative/gradient.qml code
146
147     \clearfloat
148     \section1 Performance and Limitations
149
150     Calculating gradients can be computationally expensive compared to the use
151     of solid color fills or images. Consider using gradients for static items
152     in a user interface.
153
154     In Qt 4.7, only vertical, linear gradients can be applied to items. If you
155     need to apply different orientations of gradients, a combination of rotation
156     and clipping will need to be applied to the relevant items. This can
157     introduce additional performance requirements for your application.
158
159     The use of animations involving gradient stops may not give the desired
160     result. An alternative way to animate gradients is to use pre-generated
161     images or SVG drawings containing gradients.
162
163     \sa GradientStop
164 */
165
166 /*!
167     \qmlproperty list<GradientStop> Gradient::stops
168     This property holds the gradient stops describing the gradient.
169
170     By default, this property contains an empty list.
171
172     To set the gradient stops, define them as children of the Gradient element.
173 */
174
175 const QGradient *QDeclarativeGradient::gradient() const
176 {
177     if (!m_gradient && !m_stops.isEmpty()) {
178         m_gradient = new QLinearGradient(0,0,0,1.0);
179         for (int i = 0; i < m_stops.count(); ++i) {
180             const QDeclarativeGradientStop *stop = m_stops.at(i);
181             m_gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
182             m_gradient->setColorAt(stop->position(), stop->color());
183         }
184     }
185
186     return m_gradient;
187 }
188
189 void QDeclarativeGradient::doUpdate()
190 {
191     delete m_gradient;
192     m_gradient = 0;
193     emit updated();
194 }
195
196
197 /*!
198     \qmlclass Rectangle QDeclarativeRectangle
199     \ingroup qml-basic-visual-elements
200     \since 4.7
201     \brief The Rectangle item provides a filled rectangle with an optional border.
202     \inherits Item
203
204     Rectangle items are used to fill areas with solid color or gradients, and are
205     often used to hold other items.
206
207     \section1 Appearance
208
209     Each Rectangle item is painted using either a solid fill color, specified using
210     the \l color property, or a gradient, defined using a Gradient element and set
211     using the \l gradient property. If both a color and a gradient are specified,
212     the gradient is used.
213
214     You can add an optional border to a rectangle with its own color and thickness
215     by settting the \l border.color and \l border.width properties.
216
217     You can also create rounded rectangles using the \l radius property. Since this
218     introduces curved edges to the corners of a rectangle, it may be appropriate to
219     set the \l smooth property to improve its appearance.
220
221     \section1 Example Usage
222
223     \div {class="float-right"}
224     \inlineimage declarative-rect.png
225     \enddiv
226
227     The following example shows the effects of some of the common properties on a
228     Rectangle item, which in this case is used to create a square:
229
230     \snippet doc/src/snippets/declarative/rectangle/rectangle.qml document
231
232     \clearfloat
233     \section1 Performance
234
235     Using the \l smooth property improves the appearance of a rounded rectangle at
236     the cost of rendering performance. You should consider unsetting this property
237     for rectangles in motion, and only set it when they are stationary.
238
239     \sa Image
240 */
241
242 int QDeclarativeRectanglePrivate::doUpdateSlotIdx = -1;
243
244 QDeclarativeRectangle::QDeclarativeRectangle(QDeclarativeItem *parent)
245   : QDeclarativeItem(*(new QDeclarativeRectanglePrivate), parent)
246 {
247 }
248
249 void QDeclarativeRectangle::doUpdate()
250 {
251     Q_D(QDeclarativeRectangle);
252     d->rectImage = QPixmap();
253     const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0;
254     d->setPaintMargin((pw+1)/2);
255     update();
256 }
257
258 /*!
259     \qmlproperty int Rectangle::border.width
260     \qmlproperty color Rectangle::border.color
261
262     The width and color used to draw the border of the rectangle.
263
264     A width of 1 creates a thin line. For no line, use a width of 0 or a transparent color.
265
266     \note The width of the rectangle's border does not affect the geometry of the
267     rectangle itself or its position relative to other items if anchors are used.
268
269     If \c border.width is an odd number, the rectangle is painted at a half-pixel offset to retain
270     border smoothness. Also, the border is rendered evenly on either side of the
271     rectangle's boundaries, and the spare pixel is rendered to the right and below the
272     rectangle (as documented for QRect rendering). This can cause unintended effects if
273     \c border.width is 1 and the rectangle is \l{Item::clip}{clipped} by a parent item:
274
275     \div {class="float-right"}
276     \inlineimage rect-border-width.png
277     \enddiv
278
279     \snippet doc/src/snippets/declarative/rectangle/rect-border-width.qml 0
280
281     \clearfloat
282     Here, the innermost rectangle's border is clipped on the bottom and right edges by its
283     parent. To avoid this, the border width can be set to two instead of one.
284 */
285 QDeclarativePen *QDeclarativeRectangle::border()
286 {
287     Q_D(QDeclarativeRectangle);
288     return d->getPen();
289 }
290
291 /*!
292     \qmlproperty Gradient Rectangle::gradient
293
294     The gradient to use to fill the rectangle.
295
296     This property allows for the construction of simple vertical gradients.
297     Other gradients may by formed by adding rotation to the rectangle.
298
299     \div {class="float-left"}
300     \inlineimage declarative-rect_gradient.png
301     \enddiv
302
303     \snippet doc/src/snippets/declarative/rectangle/rectangle-gradient.qml rectangles
304     \clearfloat
305
306     If both a gradient and a color are specified, the gradient will be used.
307
308     \sa Gradient, color
309 */
310 QDeclarativeGradient *QDeclarativeRectangle::gradient() const
311 {
312     Q_D(const QDeclarativeRectangle);
313     return d->gradient;
314 }
315
316 void QDeclarativeRectangle::setGradient(QDeclarativeGradient *gradient)
317 {
318     Q_D(QDeclarativeRectangle);
319     if (d->gradient == gradient)
320         return;
321     static int updatedSignalIdx = -1;
322     if (updatedSignalIdx < 0)
323         updatedSignalIdx = QDeclarativeGradient::staticMetaObject.indexOfSignal("updated()");
324     if (d->doUpdateSlotIdx < 0)
325         d->doUpdateSlotIdx = QDeclarativeRectangle::staticMetaObject.indexOfSlot("doUpdate()");
326     if (d->gradient)
327         QMetaObject::disconnect(d->gradient, updatedSignalIdx, this, d->doUpdateSlotIdx);
328     d->gradient = gradient;
329     if (d->gradient)
330         QMetaObject::connect(d->gradient, updatedSignalIdx, this, d->doUpdateSlotIdx);
331     update();
332 }
333
334
335 /*!
336     \qmlproperty real Rectangle::radius
337     This property holds the corner radius used to draw a rounded rectangle.
338
339     If radius is non-zero, the rectangle will be painted as a rounded rectangle, otherwise it will be
340     painted as a normal rectangle. The same radius is used by all 4 corners; there is currently
341     no way to specify different radii for different corners.
342 */
343 qreal QDeclarativeRectangle::radius() const
344 {
345     Q_D(const QDeclarativeRectangle);
346     return d->radius;
347 }
348
349 void QDeclarativeRectangle::setRadius(qreal radius)
350 {
351     Q_D(QDeclarativeRectangle);
352     if (d->radius == radius)
353         return;
354
355     d->radius = radius;
356     d->rectImage = QPixmap();
357     update();
358     emit radiusChanged();
359 }
360
361 /*!
362     \qmlproperty color Rectangle::color
363     This property holds the color used to fill the rectangle.
364
365     The default color is white.
366
367     \div {class="float-right"}
368     \inlineimage rect-color.png
369     \enddiv
370
371     The following example shows rectangles with colors specified
372     using hexadecimal and named color notation:
373
374     \snippet doc/src/snippets/declarative/rectangle/rectangle-colors.qml rectangles
375
376     \clearfloat
377     If both a gradient and a color are specified, the gradient will be used.
378
379     \sa gradient
380 */
381 QColor QDeclarativeRectangle::color() const
382 {
383     Q_D(const QDeclarativeRectangle);
384     return d->color;
385 }
386
387 void QDeclarativeRectangle::setColor(const QColor &c)
388 {
389     Q_D(QDeclarativeRectangle);
390     if (d->color == c)
391         return;
392
393     d->color = c;
394     d->rectImage = QPixmap();
395     update();
396     emit colorChanged();
397 }
398
399 void QDeclarativeRectangle::generateRoundedRect()
400 {
401     Q_D(QDeclarativeRectangle);
402     if (d->rectImage.isNull()) {
403         const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0;
404         const int radius = qCeil(d->radius);    //ensure odd numbered width/height so we get 1-pixel center
405
406         QString key = QLatin1String("q_") % QString::number(pw) % d->color.name() % QString::number(d->color.alpha(), 16) % QLatin1Char('_') % QString::number(radius);
407         if (d->pen && d->pen->isValid())
408             key += d->pen->color().name() % QString::number(d->pen->color().alpha(), 16);
409
410         if (!QPixmapCache::find(key, &d->rectImage)) {
411             d->rectImage = QPixmap(radius*2 + 3 + pw*2, radius*2 + 3 + pw*2);
412             d->rectImage.fill(Qt::transparent);
413             QPainter p(&(d->rectImage));
414             p.setRenderHint(QPainter::Antialiasing);
415             if (d->pen && d->pen->isValid()) {
416                 QPen pn(QColor(d->pen->color()), d->pen->width());
417                 p.setPen(pn);
418             } else {
419                 p.setPen(Qt::NoPen);
420             }
421             p.setBrush(d->color);
422             if (pw%2)
423                 p.drawRoundedRect(QRectF(qreal(pw)/2+1, qreal(pw)/2+1, d->rectImage.width()-(pw+1), d->rectImage.height()-(pw+1)), d->radius, d->radius);
424             else
425                 p.drawRoundedRect(QRectF(qreal(pw)/2, qreal(pw)/2, d->rectImage.width()-pw, d->rectImage.height()-pw), d->radius, d->radius);
426
427             // end painting before inserting pixmap
428             // to pixmap cache to avoid a deep copy
429             p.end();
430             QPixmapCache::insert(key, d->rectImage);
431         }
432     }
433 }
434
435 void QDeclarativeRectangle::generateBorderedRect()
436 {
437     Q_D(QDeclarativeRectangle);
438     if (d->rectImage.isNull()) {
439         const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0;
440
441         QString key = QLatin1String("q_") % QString::number(pw) % d->color.name() % QString::number(d->color.alpha(), 16);
442         if (d->pen && d->pen->isValid())
443             key += d->pen->color().name() % QString::number(d->pen->color().alpha(), 16);
444
445         if (!QPixmapCache::find(key, &d->rectImage)) {
446             // Adding 5 here makes qDrawBorderPixmap() paint correctly with smooth: true
447             // See QTBUG-7999 and QTBUG-10765 for more details.
448             d->rectImage = QPixmap(pw*2 + 5, pw*2 + 5);
449             d->rectImage.fill(Qt::transparent);
450             QPainter p(&(d->rectImage));
451             p.setRenderHint(QPainter::Antialiasing);
452             if (d->pen && d->pen->isValid()) {
453                 QPen pn(QColor(d->pen->color()), d->pen->width());
454                 pn.setJoinStyle(Qt::MiterJoin);
455                 p.setPen(pn);
456             } else {
457                 p.setPen(Qt::NoPen);
458             }
459             p.setBrush(d->color);
460             if (pw%2)
461                 p.drawRect(QRectF(qreal(pw)/2+1, qreal(pw)/2+1, d->rectImage.width()-(pw+1), d->rectImage.height()-(pw+1)));
462             else
463                 p.drawRect(QRectF(qreal(pw)/2, qreal(pw)/2, d->rectImage.width()-pw, d->rectImage.height()-pw));
464
465             // end painting before inserting pixmap
466             // to pixmap cache to avoid a deep copy
467             p.end();
468             QPixmapCache::insert(key, d->rectImage);
469         }
470     }
471 }
472
473 void QDeclarativeRectangle::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
474 {
475     Q_D(QDeclarativeRectangle);
476     if (width() <= 0 || height() <= 0)
477         return;
478     if (d->radius > 0 || (d->pen && d->pen->isValid())
479         || (d->gradient && d->gradient->gradient()) ) {
480         drawRect(*p);
481     }
482     else {
483         bool oldAA = p->testRenderHint(QPainter::Antialiasing);
484         if (d->smooth)
485             p->setRenderHints(QPainter::Antialiasing, true);
486         p->fillRect(QRectF(0, 0, width(), height()), d->color);
487         if (d->smooth)
488             p->setRenderHint(QPainter::Antialiasing, oldAA);
489     }
490 }
491
492 void QDeclarativeRectangle::drawRect(QPainter &p)
493 {
494     Q_D(QDeclarativeRectangle);
495     if ((d->gradient && d->gradient->gradient())
496         || d->radius > width()/2 || d->radius > height()/2
497         || width() < 3 || height() < 3) {
498         // XXX This path is still slower than the image path
499         // Image path won't work for gradients or invalid radius though
500         bool oldAA = p.testRenderHint(QPainter::Antialiasing);
501         if (d->smooth)
502             p.setRenderHint(QPainter::Antialiasing);
503         if (d->pen && d->pen->isValid()) {
504             QPen pn(QColor(d->pen->color()), d->pen->width());
505             pn.setJoinStyle(Qt::MiterJoin);
506             p.setPen(pn);
507         } else {
508             p.setPen(Qt::NoPen);
509         }
510         if (d->gradient && d->gradient->gradient())
511             p.setBrush(*d->gradient->gradient());
512         else
513             p.setBrush(d->color);
514         const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0;
515         QRectF rect;
516         if (pw%2)
517             rect = QRectF(0.5, 0.5, width()-1, height()-1);
518         else
519             rect = QRectF(0, 0, width(), height());
520         qreal radius = d->radius;
521         if (radius > width()/2 || radius > height()/2)
522             radius = qMin(width()/2, height()/2);
523         if (radius > 0.)
524             p.drawRoundedRect(rect, radius, radius);
525         else
526             p.drawRect(rect);
527         if (d->smooth)
528             p.setRenderHint(QPainter::Antialiasing, oldAA);
529     } else {
530         bool oldAA = p.testRenderHint(QPainter::Antialiasing);
531         bool oldSmooth = p.testRenderHint(QPainter::SmoothPixmapTransform);
532         if (d->smooth)
533             p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth);
534
535         const int pw = d->pen && d->pen->isValid() ? (d->pen->width()+1)/2*2 : 0;
536
537         if (d->radius > 0)
538             generateRoundedRect();
539         else
540             generateBorderedRect();
541
542         int xOffset = (d->rectImage.width()-1)/2;
543         int yOffset = (d->rectImage.height()-1)/2;
544         Q_ASSERT(d->rectImage.width() == 2*xOffset + 1);
545         Q_ASSERT(d->rectImage.height() == 2*yOffset + 1);
546
547         // check whether we've eliminated the center completely
548         if (2*xOffset > width()+pw)
549             xOffset = (width()+pw)/2;
550         if (2*yOffset > height()+pw)
551             yOffset = (height()+pw)/2;
552
553         QMargins margins(xOffset, yOffset, xOffset, yOffset);
554         QTileRules rules(Qt::StretchTile, Qt::StretchTile);
555         //NOTE: even though our item may have qreal-based width and height, qDrawBorderPixmap only supports QRects
556         qDrawBorderPixmap(&p, QRect(-pw/2, -pw/2, width()+pw, height()+pw), margins, d->rectImage, d->rectImage.rect(), margins, rules);
557
558         if (d->smooth) {
559             p.setRenderHint(QPainter::Antialiasing, oldAA);
560             p.setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth);
561         }
562     }
563 }
564
565 /*!
566     \qmlproperty bool Rectangle::smooth
567
568     Set this property if you want the item to be smoothly scaled or
569     transformed.  Smooth filtering gives better visual quality, but is slower.  If
570     the item is displayed at its natural size, this property has no visual or
571     performance effect.
572
573     \note Generally scaling artifacts are only visible if the item is stationary on
574     the screen.  A common pattern when animating an item is to disable smooth
575     filtering at the beginning of the animation and reenable it at the conclusion.
576
577     \image rect-smooth.png
578     On this image, smooth is turned off on the top half and on on the bottom half.
579 */
580
581 QRectF QDeclarativeRectangle::boundingRect() const
582 {
583     Q_D(const QDeclarativeRectangle);
584     return QRectF(-d->paintmargin, -d->paintmargin, d->width()+d->paintmargin*2, d->height()+d->paintmargin*2);
585 }
586
587 QT_END_NAMESPACE