1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
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 
setColor(const QColor & c)70 void QDeclarativePen::setColor(const QColor &c)
71 {
72     _color = c;
73     _valid = (_color.alpha() && _width >= 1) ? true : false;
74     emit penChanged();
75 }
76 
setWidth(int w)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 
updateGradient()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 
gradient() const175 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 
doUpdate()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 
QDeclarativeRectangle(QDeclarativeItem * parent)244 QDeclarativeRectangle::QDeclarativeRectangle(QDeclarativeItem *parent)
245   : QDeclarativeItem(*(new QDeclarativeRectanglePrivate), parent)
246 {
247 }
248 
doUpdate()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 */
border()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 */
gradient() const310 QDeclarativeGradient *QDeclarativeRectangle::gradient() const
311 {
312     Q_D(const QDeclarativeRectangle);
313     return d->gradient;
314 }
315 
setGradient(QDeclarativeGradient * gradient)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 */
radius() const343 qreal QDeclarativeRectangle::radius() const
344 {
345     Q_D(const QDeclarativeRectangle);
346     return d->radius;
347 }
348 
setRadius(qreal radius)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 */
color() const381 QColor QDeclarativeRectangle::color() const
382 {
383     Q_D(const QDeclarativeRectangle);
384     return d->color;
385 }
386 
setColor(const QColor & c)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 
generateRoundedRect()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 
generateBorderedRect()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 
paint(QPainter * p,const QStyleOptionGraphicsItem *,QWidget *)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 
drawRect(QPainter & p)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 
boundingRect() const581 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
588