summaryrefslogtreecommitdiff
path: root/examples/widgets/doc/src/basicdrawing.qdoc
blob: 7413dfc823174e26741382de74407ac30736fe38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only

/*!
    \example painting/basicdrawing
    \title Basic Drawing Example
    \ingroup examples-painting
    \brief The Basic Drawing example shows how to display basic
    graphics primitives in a variety of styles using the QPainter
    class.

    \brief The Basic Drawing example shows how to display basic graphics
    primitives in a variety of styles using the QPainter class.

    QPainter performs low-level painting on widgets and other paint
    devices. The class can draw everything from simple lines to
    complex shapes like pies and chords. It can also draw aligned text
    and pixmaps. Normally, it draws in a "natural" coordinate system,
    but it can in addition do view and world transformation.

    \image basicdrawing-example.png

    The example provides a render area, displaying the currently
    active shape, and lets the user manipulate the rendered shape and
    its appearance using the QPainter parameters: The user can change
    the active shape (\uicontrol Shape), and modify the QPainter's pen (\uicontrol
    {Pen Width}, \uicontrol {Pen Style}, \uicontrol {Pen Cap}, \uicontrol {Pen Join}),
    brush (\uicontrol {Brush Style}) and render hints (\uicontrol
    Antialiasing). In addition the user can rotate a shape (\uicontrol
    Transformations); behind the scenes we use QPainter's ability to
    manipulate the coordinate system to perform the rotation.

    The Basic Drawing example consists of two classes:

    \list
    \li \c RenderArea is a custom widget that renders multiple
       copies of the currently active shape.
    \li \c Window is the application's main window displaying a
       \c RenderArea widget in addition to several parameter widgets.
    \endlist

    First we will review the \c Window class, then we will take a
    look at the \c RenderArea class.

    \section1 Window Class Definition

    The Window class inherits QWidget, and is the application's main
    window displaying a \c RenderArea widget in addition to several
    parameter widgets.

    \snippet painting/basicdrawing/window.h 0

    We declare the various widgets, and three private slots updating
    the \c RenderArea widget: The \c shapeChanged() slot updates the
    \c RenderArea widget when the user changes the currently active
    shape. We call the \c penChanged() slot when either of the
    QPainter's pen parameters changes. And the \c brushChanged() slot
    updates the \c RenderArea widget when the user changes the
    painter's brush style.

    \section1 Window Class Implementation

    In the constructor we create and initialize the various widgets
    appearing in the main application window.

    \snippet painting/basicdrawing/window.cpp 1

    First we create the \c RenderArea widget that will render the
    currently active shape. Then we create the \uicontrol Shape combobox,
    and add the associated items (i.e. the different shapes a QPainter
    can draw).

    \snippet painting/basicdrawing/window.cpp 2

    QPainter's pen is a QPen object; the QPen class defines how a
    painter should draw lines and outlines of shapes. A pen has
    several properties: Width, style, cap and join.

    A pen's width can be \e zero or greater, but the most common width
    is zero. Note that this doesn't mean 0 pixels, but implies that
    the shape is drawn as smoothly as possible although perhaps not
    mathematically correct.

    We create a QSpinBox for the \uicontrol {Pen Width} parameter.

    \snippet painting/basicdrawing/window.cpp 3

    The pen style defines the line type. The default style is solid
    (Qt::SolidLine). Setting the style to none (Qt::NoPen) tells the
    painter to not draw lines or outlines.  The pen cap defines how
    the end points of lines are drawn.  And the pen join defines how
    two lines join when multiple connected lines are drawn. The cap
    and join only apply to lines with a width of 1 pixel or greater.

    We create \l {QComboBox}es for each of the \uicontrol {Pen Style}, \uicontrol
    {Pen Cap} and \uicontrol {Pen Join} parameters, and adds the associated
    items (i.e the values of the Qt::PenStyle, Qt::PenCapStyle and
    Qt::PenJoinStyle enums respectively).

    \snippet painting/basicdrawing/window.cpp 4

    The QBrush class defines the fill pattern of shapes drawn by a
    QPainter. The default brush style is Qt::NoBrush. This style tells
    the painter to not fill shapes. The standard style for filling is
    Qt::SolidPattern.

    We create a QComboBox for the \uicontrol {Brush Style} parameter, and add
    the associated items (i.e. the values of the Qt::BrushStyle enum).

    \snippet painting/basicdrawing/window.cpp 5
    \snippet painting/basicdrawing/window.cpp 6

    Antialiasing is a feature that "smoothes" the pixels to create
    more even and less jagged lines, and can be applied using
    QPainter's render hints. QPainter::RenderHints are used to specify
    flags to QPainter that may or may not be respected by any given
    engine.

    We simply create a QCheckBox for the \uicontrol Antialiasing option.

    \snippet painting/basicdrawing/window.cpp 7

    The \uicontrol Transformations option implies a manipulation of the
    coordinate system that will appear as if the rendered shape is
    rotated in three dimensions.

    We use the QPainter::translate(), QPainter::rotate() and
    QPainter::scale() functions to implement this feature represented
    in the main application window by a simple QCheckBox.

    \snippet painting/basicdrawing/window.cpp 8

    Then we connect the parameter widgets with their associated slots
    using the static QObject::connect() function, ensuring that the \c
    RenderArea widget is updated whenever the user changes the shape,
    or any of the other parameters.

    \snippet painting/basicdrawing/window.cpp 9
    \snippet painting/basicdrawing/window.cpp 10

    Finally, we add the various widgets to a layout, and call the \c
    shapeChanged(), \c penChanged(), and \c brushChanged() slots to
    initialize the application. We also turn on antialiasing.

    \snippet painting/basicdrawing/window.cpp 11

    The \c shapeChanged() slot is called whenever the user changes the
    currently active shape.

    First we retrieve the shape the user has chosen using the
    QComboBox::itemData() function. This function returns the data for
    the given role in the given index in the combobox. We use
    QComboBox::currentIndex() to retrieve the index of the shape, and
    the role is defined by the Qt::ItemDataRole enum; \c IdRole is an
    alias for Qt::UserRole.

    Note that Qt::UserRole is only the first role that can be used for
    application-specific purposes. If you need to store different data
    in the same index, you can use different roles by simply
    incrementing the value of Qt::UserRole, for example: 'Qt::UserRole
    + 1' and 'Qt::UserRole + 2'. However, it is a good programming
    practice to give each role their own name: 'myFirstRole =
    Qt::UserRole + 1' and 'mySecondRole = Qt::UserRole + 2'. Even
    though we only need a single role in this particular example, we
    add the following line of code to the beginning of the \c
    window.cpp file.

    \snippet painting/basicdrawing/window.cpp 0

    The QComboBox::itemData() function returns the data as a QVariant,
    so we need to cast the data to \c RenderArea::Shape. If there is
    no data for the given role, the function returns
    QVariant::Invalid.

    In the end we call the \c RenderArea::setShape() slot to update
    the \c RenderArea widget.

    \snippet painting/basicdrawing/window.cpp 12

    We call the \c penChanged() slot whenever the user changes any of
    the pen parameters. Again we use the QComboBox::itemData()
    function to retrieve the parameters, and then we call the \c
    RenderArea::setPen() slot to update the \c RenderArea widget.

    \snippet painting/basicdrawing/window.cpp 13

    The brushChanged() slot is called whenever the user changes the
    brush parameter which we retrieve using the QComboBox::itemData()
    function as before.

    \snippet painting/basicdrawing/window.cpp 14

    If the brush parameter is a gradient fill, special actions are
    required.

    The QGradient class is used in combination with QBrush to specify
    gradient fills. Qt currently supports three types of gradient
    fills: linear, radial and conical. Each of these is represented by
    a subclass of QGradient: QLinearGradient, QRadialGradient and
    QConicalGradient.

    So if the brush style is Qt::LinearGradientPattern, we first
    create a QLinearGradient object with interpolation area between
    the coordinates passed as arguments to the constructor. The
    positions are specified using logical coordinates. Then we set the
    gradient's colors using the QGradient::setColorAt() function. The
    colors is defined using stop points which are composed by a
    position (between 0 and 1) and a QColor. The set of stop points
    describes how the gradient area should be filled. A gradient can
    have an arbitrary number of stop points.

    In the end we call \c RenderArea::setBrush() slot to update the \c
    RenderArea widget's brush with the QLinearGradient object.

    \snippet painting/basicdrawing/window.cpp 15

    A similar pattern of actions, as the one used for QLinearGradient,
    is used in the cases of Qt::RadialGradientPattern and
    Qt::ConicalGradientPattern.

    The only difference is the arguments passed to the constructor:
    Regarding the QRadialGradient constructor the first argument is
    the center, and the second the radial gradient's radius. The third
    argument is optional, but can be used to define the focal point of
    the gradient inside the circle (the default focal point is the
    circle center).  Regarding the QConicalGradient constructor, the
    first argument specifies the center of the conical, and the second
    specifies the start angle of the interpolation.

    \snippet painting/basicdrawing/window.cpp 16

    If the brush style is Qt::TexturePattern we create a QBrush from a
    QPixmap. Then we call \c RenderArea::setBrush() slot to update the
    \c RenderArea widget with the newly created brush.

    \snippet painting/basicdrawing/window.cpp 17

    Otherwise we simply create a brush with the given style and a
    green color, and then call \c RenderArea::setBrush() slot to
    update the \c RenderArea widget with the newly created brush.

    \section1 RenderArea Class Definition

    The \c RenderArea class inherits QWidget, and renders multiple
    copies of the currently active shape using a QPainter.

    \snippet painting/basicdrawing/renderarea.h 0

    First we define a public \c Shape enum to hold the different
    shapes that can be rendered by the widget (i.e the shapes that can
    be rendered by a QPainter). Then we reimplement the constructor as
    well as two of QWidget's public functions: \l
    {QWidget::minimumSizeHint()}{minimumSizeHint()} and \l
    {QWidget::sizeHint()}{sizeHint()}.

    We also reimplement the QWidget::paintEvent() function to be able
    to draw the currently active shape according to the specified
    parameters.

    We declare several private slots: The \c setShape() slot changes
    the \c RenderArea's shape, the \c setPen() and \c setBrush() slots
    modify the widget's pen and brush, and the \c setAntialiased() and
    \c setTransformed() slots modify the widget's respective
    properties.

    \section1 RenderArea Class Implementation

    In the constructor we initialize some of the widget's variables.

    \snippet painting/basicdrawing/renderarea.cpp 0

    We set its shape to be a \uicontrol Polygon, its antialiased property to
    be false and we load an image into the widget's pixmap
    variable. In the end we set the widget's background role, defining
    the brush from the widget's \l {QWidget::palette}{palette} that
    will be used to render the background. QPalette::Base is typically
    white.

    \snippet painting/basicdrawing/renderarea.cpp 2

    The \c RenderArea inherits QWidget's \l
    {QWidget::sizeHint()}{sizeHint} property holding the recommended
    size for the widget. If the value of this property is an invalid
    size, no size is recommended.

    The default implementation of the QWidget::sizeHint() function
    returns an invalid size if there is no layout for the widget, and
    returns the layout's preferred size otherwise.

    Our reimplementation of the function returns a QSize with a 400
    pixels width and a 200 pixels height.

    \snippet painting/basicdrawing/renderarea.cpp 1

    \c RenderArea also inherits QWidget's
    \l{QWidget::minimumSizeHint()}{minimumSizeHint} property holding
    the recommended minimum size for the widget. Again, if the value
    of this property is an invalid size, no size is recommended.

    The default implementation of QWidget::minimumSizeHint() returns
    an invalid size if there is no layout for the widget, and returns
    the layout's minimum size otherwise.

    Our reimplementation of the function returns a QSize with a 100
    pixels width and a 100 pixels height.

    \snippet painting/basicdrawing/renderarea.cpp 3
    \codeline
    \snippet painting/basicdrawing/renderarea.cpp 4
    \codeline
    \snippet painting/basicdrawing/renderarea.cpp 5

    The public \c setShape(), \c setPen() and \c setBrush() slots are
    called whenever we want to modify a \c RenderArea widget's shape,
    pen or brush. We set the shape, pen or brush according to the
    slot parameter, and call QWidget::update() to make the changes
    visible in the \c RenderArea widget.

    The QWidget::update() slot does not cause an immediate
    repaint; instead it schedules a paint event for processing when Qt
    returns to the main event loop.

    \snippet painting/basicdrawing/renderarea.cpp 6
    \codeline
    \snippet painting/basicdrawing/renderarea.cpp 7

    With the \c setAntialiased() and \c setTransformed() slots we
    change the state of the properties according to the slot
    parameter, and call the QWidget::update() slot to make the changes
    visible in the \c RenderArea widget.

    \snippet painting/basicdrawing/renderarea.cpp 8

    Then we reimplement the QWidget::paintEvent() function. The first
    thing we do is to create the graphical objects we will need to
    draw the various shapes.

    We create a vector of four \l {QPoint}s. We use this vector to
    render the \uicontrol Points, \uicontrol Polyline and \uicontrol Polygon
    shapes. Then we create a QRect, defining a rectangle in the plane,
    which we use as the bounding rectangle for all the shapes excluding
    the \uicontrol Path and the \uicontrol Pixmap.

    We also create a QPainterPath. The QPainterPath class provides a
    container for painting operations, enabling graphical shapes to be
    constructed and reused. A painter path is an object composed of a
    number of graphical building blocks, such as rectangles, ellipses,
    lines, and curves. For more information about the QPainterPath
    class, see the \l {painting/painterpaths}{Painter Paths}
    example. In this example, we create a painter path composed of one
    straight line and a Bezier curve.

    In addition we define a start angle and an arc length that we will
    use when drawing the \uicontrol Arc, \uicontrol Chord and \uicontrol Pie shapes.

    \snippet painting/basicdrawing/renderarea.cpp 9

    We create a QPainter for the \c RenderArea widget, and set the
    painters pen and brush according to the \c RenderArea's pen and
    brush. If the \uicontrol Antialiasing parameter option is checked, we
    also set the painter's render hints. QPainter::Antialiasing
    indicates that the engine should antialias edges of primitives if
    possible.

    \snippet painting/basicdrawing/renderarea.cpp 10

    Finally, we render the multiple copies of the \c RenderArea's
    shape. The number of copies is depending on the size of the \c
    RenderArea widget, and we calculate their positions using two \c
    for loops and the widgets height and width.

    For each copy we first save the current painter state (pushes the
    state onto a stack). Then we translate the coordinate system,
    using the QPainter::translate() function, to the position
    determined by the variables of the \c for loops. If we omit this
    translation of the coordinate system all the copies of the shape
    will be rendered on top of each other in the top left cormer of
    the \c RenderArea widget.

    \snippet painting/basicdrawing/renderarea.cpp 11

    If the \uicontrol Transformations parameter option is checked, we do an
    additional translation of the coordinate system before we rotate
    the coordinate system 60 degrees clockwise using the
    QPainter::rotate() function and scale it down in size using the
    QPainter::scale() function. In the end we translate the coordinate
    system back to where it was before we rotated and scaled it.

    Now, when rendering the shape, it will appear as if it was rotated
    in three dimensions.

    \snippet painting/basicdrawing/renderarea.cpp 12

    Next, we identify the \c RenderArea's shape, and render it using
    the associated QPainter drawing function:

    \list
        \li QPainter::drawLine(),
        \li QPainter::drawPoints(),
        \li QPainter::drawPolyline(),
        \li QPainter::drawPolygon(),
        \li QPainter::drawRect(),
        \li QPainter::drawRoundedRect(),
        \li QPainter::drawEllipse(),
        \li QPainter::drawArc(),
        \li QPainter::drawChord(),
        \li QPainter::drawPie(),
        \li QPainter::drawPath(),
        \li QPainter::drawText() or
        \li QPainter::drawPixmap()
    \endlist

    Before we started rendering, we saved the current painter state
    (pushes the state onto a stack). The rationale for this is that we
    calculate each shape copy's position relative to the same point in
    the coordinate system. When translating the coordinate system, we
    lose the knowledge of this point unless we save the current
    painter state \e before we start the translating process.

    \snippet painting/basicdrawing/renderarea.cpp 13

    Then, when we are finished rendering a copy of the shape we can
    restore the original painter state, with its associated coordinate
    system, using the QPainter::restore() function. In this way we
    ensure that the next shape copy will be rendered in the correct
    position.

    We could translate the coordinate system back using
    QPainter::translate() instead of saving the painter state. But
    since we in addition to translating the coordinate system (when
    the \uicontrol Transformation parameter option is checked) both rotate
    and scale the coordinate system, the easiest solution is to save
    the current painter state.
*/