summaryrefslogtreecommitdiff
path: root/doc/src/examples/scribble.qdoc
blob: 3d6a8b18dc5beb0215f754d76cfb4bd63c5c1a8f (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
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** GNU Free Documentation License
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms
** and conditions contained in a signed written agreement between you
** and Nokia.
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
    \example widgets/scribble
    \title Scribble Example

    The Scribble example shows how to reimplement some of QWidget's
    event handlers to receive the events generated for the
    application's widgets.

    We reimplement the mouse event handlers to implement drawing, the
    paint event handler to update the application and the resize event
    handler to optimize the application's appearance. In addition we
    reimplement the close event handler to intercept the close events
    before terminating the application.

    The example also demonstrates how to use QPainter to draw an image
    in real time, as well as to repaint widgets.

    \image scribble-example.png Screenshot of the Scribble example

    With the Scribble application the users can draw an image.  The
    \gui File menu gives the users the possibility to open and edit an
    existing image file, save an image and exit the application. While
    drawing, the \gui Options menu allows the users to to choose the
    pen color and pen width, as well as clear the screen. In addition
    the \gui Help menu provides the users with information about the
    Scribble example in particular, and about Qt in general.

    The example consists of two classes:

    \list
    \o \c ScribbleArea is a custom widget that displays a QImage and
       allows to the user to draw on it.
    \o \c MainWindow provides a menu above the \c ScribbleArea.
    \endlist

    We will start by reviewing the \c ScribbleArea class. Then we will
    review the \c MainWindow class, which uses \c ScribbleArea.

    \section1 ScribbleArea Class Definition

    \snippet examples/widgets/scribble/scribblearea.h 0

    The \c ScribbleArea class inherits from QWidget. We reimplement
    the \c mousePressEvent(), \c mouseMoveEvent() and \c
    mouseReleaseEvent() functions to implement the drawing. We
    reimplement the \c paintEvent() function to update the scribble
    area, and the \c resizeEvent() function to ensure that the QImage
    on which we draw is at least as large as the widget at any time.

    We need several public functions: \c openImage() loads an image
    from a file into the scribble area, allowing the user to edit the
    image; \c save() writes the currently displayed image to file; \c
    clearImage() slot clears the image displayed in the scribble
    area. We need the private \c drawLineTo() function to actually do
    the drawing, and \c resizeImage() to change the size of a
    QImage. The \c print() slot handles printing.

    We also need the following private variables:

    \list
    \o \c modified is \c true if there are unsaved
       changes to the image displayed in the scribble area.
    \o \c scribbling is \c true while the user is pressing
       the left mouse button within the scribble area.
    \o \c penWidth and \c penColor hold the currently
       set width and color for the pen used in the application.
    \o \c image stores the image drawn by the user.
    \o \c lastPoint holds the position of the cursor at the last
       mouse press or mouse move event.
    \endlist

    \section1 ScribbleArea Class Implementation

    \snippet examples/widgets/scribble/scribblearea.cpp 0

    In the constructor, we set the Qt::WA_StaticContents
    attribute for the widget, indicating that the widget contents are
    rooted to the top-left corner and don't change when the widget is
    resized. Qt uses this attribute to optimize paint events on
    resizes. This is purely an optimization and should only be used
    for widgets whose contents are static and rooted to the top-left
    corner.

    \snippet examples/widgets/scribble/scribblearea.cpp 1
    \snippet examples/widgets/scribble/scribblearea.cpp 2

    In the \c openImage() function, we load the given image. Then we
    resize the loaded QImage to be at least as large as the widget in
    both directions using the private \c resizeImage() function and
    we set the \c image member variable to be the loaded image. At
    the end, we call QWidget::update() to schedule a repaint.

    \snippet examples/widgets/scribble/scribblearea.cpp 3
    \snippet examples/widgets/scribble/scribblearea.cpp 4

    The \c saveImage() function creates a QImage object that covers
    only the visible section of the actual \c image and saves it using
    QImage::save(). If the image is successfully saved, we set the
    scribble area's \c modified variable to \c false, because there is
    no unsaved data.

    \snippet examples/widgets/scribble/scribblearea.cpp 5
    \snippet examples/widgets/scribble/scribblearea.cpp 6
    \codeline
    \snippet examples/widgets/scribble/scribblearea.cpp 7
    \snippet examples/widgets/scribble/scribblearea.cpp 8

    The \c setPenColor() and \c setPenWidth() functions set the
    current pen color and width. These values will be used for future
    drawing operations.

    \snippet examples/widgets/scribble/scribblearea.cpp 9
    \snippet examples/widgets/scribble/scribblearea.cpp 10

    The public \c clearImage() slot clears the image displayed in the
    scribble area. We simply fill the entire image with white, which
    corresponds to RGB value (255, 255, 255). As usual when we modify
    the image, we set \c modified to \c true and schedule a repaint.

    \snippet examples/widgets/scribble/scribblearea.cpp 11
    \snippet examples/widgets/scribble/scribblearea.cpp 12

    For mouse press and mouse release events, we use the
    QMouseEvent::button() function to find out which button caused
    the event. For mose move events, we use QMouseEvent::buttons()
    to find which buttons are currently held down (as an OR-combination).

    If the users press the left mouse button, we store the position
    of the mouse cursor in \c lastPoint. We also make a note that the
    user is currently scribbling. (The \c scribbling variable is
    necessary because we can't assume that a mouse move and mouse
    release event is always preceded by a mouse press event on the
    same widget.)

    If the user moves the mouse with the left button pressed down or
    releases the button, we call the private \c drawLineTo() function
    to draw.

    \snippet examples/widgets/scribble/scribblearea.cpp 13
    \snippet examples/widgets/scribble/scribblearea.cpp 14

    In the reimplementation of the \l
    {QWidget::paintEvent()}{paintEvent()} function, we simply create
    a QPainter for the scribble area, and draw the image.

    At this point, you might wonder why we don't just draw directly
    onto the widget instead of drawing in a QImage and copying the
    QImage onto screen in \c paintEvent(). There are at least three
    good reasons for this:

    \list
    \o The window system requires us to be able to redraw the widget
       \e{at any time}. For example, if the window is minimized and
       restored, the window system might have forgotten the contents
       of the widget and send us a paint event. In other words, we
       can't rely on the window system to remember our image.

    \o Qt normally doesn't allow us to paint outside of \c
       paintEvent(). In particular, we can't paint from the mouse
       event handlers. (This behavior can be changed using the
       Qt::WA_PaintOnScreen widget attribute, though.)

    \o If initialized properly, a QImage is guaranteed to use 8-bit
       for each color channel (red, green, blue, and alpha), whereas
       a QWidget might have a lower color depth, depending on the
       monitor configuration. This means that if we load a 24-bit or
       32-bit image and paint it onto a QWidget, then copy the
       QWidget into a QImage again, we might lose some information.
    \endlist

    \snippet examples/widgets/scribble/scribblearea.cpp 15
    \snippet examples/widgets/scribble/scribblearea.cpp 16

    When the user starts the Scribble application, a resize event is
    generated and an image is created and displayed in the scribble
    area. We make this initial image slightly larger than the
    application's main window and scribble area, to avoid always
    resizing the image when the user resizes the main window (which
    would be very inefficient). But when the main window becomes
    larger than this initial size, the image needs to be resized.

    \snippet examples/widgets/scribble/scribblearea.cpp 17
    \snippet examples/widgets/scribble/scribblearea.cpp 18

    In \c drawLineTo(), we draw a line from the point where the mouse
    was located when the last mouse press or mouse move occurred, we
    set \c modified to true, we generate a repaint event, and we
    update \c lastPoint so that next time \c drawLineTo() is called,
    we continue drawing from where we left.

    We could call the \c update() function with no parameter, but as
    an easy optimization we pass a QRect that specifies the rectangle
    inside the scribble are needs updating, to avoid a complete
    repaint of the widget.

    \snippet examples/widgets/scribble/scribblearea.cpp 19
    \snippet examples/widgets/scribble/scribblearea.cpp 20

    QImage has no nice API for resizing an image. There's a
    QImage::copy() function that could do the trick, but when used to
    expand an image, it fills the new areas with black, whereas we
    want white.

    So the trick is to create a brand new QImage with the right size,
    to fill it with white, and to draw the old image onto it using
    QPainter. The new image is given the QImage::Format_RGB32
    format, which means that each pixel is stored as 0xffRRGGBB
    (where RR, GG, and BB are the red, green and blue
    color channels, ff is the hexadecimal value 255).

    Printing is handled by the \c print() slot:

    \snippet examples/widgets/scribble/scribblearea.cpp 21

    We construct a high resolution QPrinter object for the required
    output format, using a QPrintDialog to ask the user to specify a
    page size and indicate how the output should be formatted on the page.

    If the dialog is accepted, we perform the task of printing to the paint
    device:

    \snippet examples/widgets/scribble/scribblearea.cpp 22

    Printing an image to a file in this way is simply a matter of
    painting onto the QPrinter. We scale the image to fit within the
    available space on the page before painting it onto the paint
    device.

    \section1 MainWindow Class Definition

    \snippet examples/widgets/scribble/mainwindow.h 0

    The \c MainWindow class inherits from QMainWindow. We reimplement
    the \l{QWidget::closeEvent()}{closeEvent()} handler from QWidget.
    The \c open(), \c save(), \c penColor() and \c penWidth()
    slots correspond to menu entries. In addition we create four
    private functions.

    We use the boolean \c maybeSave() function to check if there are
    any unsaved changes. If there are unsaved changes, we give the
    user the opportunity to save these changes. The function returns
    \c false if the user clicks \gui Cancel. We use the \c saveFile()
    function to let the user save the image currently displayed in
    the scribble area.

    \section1 MainWindow Class Implementation

    \snippet examples/widgets/scribble/mainwindow.cpp 0

    In the constructor, we create a scribble area which we make the
    central widget of the \c MainWindow widget. Then we create the
    associated actions and menus.

    \snippet examples/widgets/scribble/mainwindow.cpp 1
    \snippet examples/widgets/scribble/mainwindow.cpp 2

    Close events are sent to widgets that the users want to close,
    usually by clicking \gui{File|Exit} or by clicking the \gui X
    title bar button. By reimplementing the event handler, we can
    intercept attempts to close the application.

    In this example, we use the close event to ask the user to save
    any unsaved changes. The logic for that is located in the \c
    maybeSave() function. If \c maybeSave() returns true, there are
    no modifications or the users successfully saved them, and we
    accept the event. The application can then terminate normally. If
    \c maybeSave() returns false, the user clicked \gui Cancel, so we
    "ignore" the event, leaving the application unaffected by it.

    \snippet examples/widgets/scribble/mainwindow.cpp 3
    \snippet examples/widgets/scribble/mainwindow.cpp 4

    In the \c open() slot we first give the user the opportunity to
    save any modifications to the currently displayed image, before a
    new image is loaded into the scribble area. Then we ask the user
    to choose a file and we load the file in the \c ScribbleArea.

    \snippet examples/widgets/scribble/mainwindow.cpp 5
    \snippet examples/widgets/scribble/mainwindow.cpp 6

    The \c save() slot is called when the users choose the \gui {Save
    As} menu entry, and then choose an entry from the format menu. The
    first thing we need to do is to find out which action sent the
    signal using QObject::sender(). This function returns the sender
    as a QObject pointer. Since we know that the sender is an action
    object, we can safely cast the QObject. We could have used a
    C-style cast or a C++ \c static_cast<>(), but as a defensive
    programming technique we use a qobject_cast(). The advantage is
    that if the object has the wrong type, a null pointer is
    returned. Crashes due to null pointers are much easier to diagnose
    than crashes due to unsafe casts.

    Once we have the action, we extract the chosen format using
    QAction::data(). (When the actions are created, we use
    QAction::setData() to set our own custom data attached to the
    action, as a QVariant. More on this when we review \c
    createActions().)

    Now that we know the format, we call the private \c saveFile()
    function to save the currently displayed image.

    \snippet examples/widgets/scribble/mainwindow.cpp 7
    \snippet examples/widgets/scribble/mainwindow.cpp 8

    We use the \c penColor() slot to retrieve a new color from the
    user with a QColorDialog. If the user chooses a new color, we
    make it the scribble area's color.

    \snippet examples/widgets/scribble/mainwindow.cpp 9
    \snippet examples/widgets/scribble/mainwindow.cpp 10

    To retrieve a new pen width in the \c penWidth() slot, we use
    QInputDialog. The QInputDialog class provides a simple
    convenience dialog to get a single value from the user. We use
    the static QInputDialog::getInt() function, which combines a
    QLabel and a QSpinBox. The QSpinBox is initialized with the
    scribble area's pen width, allows a range from 1 to 50, a step of
    1 (meaning that the up and down arrow increment or decrement the
    value by 1).

    The boolean \c ok variable will be set to \c true if the user
    clicked \gui OK and to \c false if the user pressed \gui Cancel.

    \snippet examples/widgets/scribble/mainwindow.cpp 11
    \snippet examples/widgets/scribble/mainwindow.cpp 12

    We implement the \c about() slot to create a message box
    describing what the example is designed to show.

    \snippet examples/widgets/scribble/mainwindow.cpp 13
    \snippet examples/widgets/scribble/mainwindow.cpp 14

    In the \c createAction() function we create the actions
    representing the menu entries and connect them to the appropriate
    slots. In particular we create the actions found in the \gui
    {Save As} sub-menu. We use QImageWriter::supportedImageFormats()
    to get a list of the supported formats (as a QList<QByteArray>).

    Then we iterate through the list, creating an action for each
    format. We call QAction::setData() with the file format, so we
    can retrieve it later as QAction::data(). We could also have
    deduced the file format from the action's text, by truncating the
    "...", but that would have been inelegant.

    \snippet examples/widgets/scribble/mainwindow.cpp 15
    \snippet examples/widgets/scribble/mainwindow.cpp 16

    In the \c createMenu() function, we add the previously created
    format actions to the \c saveAsMenu. Then we add the rest of the
    actions as well as the \c saveAsMenu sub-menu to the \gui File,
    \gui Options and \gui Help menus.

    The QMenu class provides a menu widget for use in menu bars,
    context menus, and other popup menus. The QMenuBar class provides
    a horizontal menu bar with a list of pull-down \l{QMenu}s. At the
    end we put the \gui File and \gui Options menus in the \c
    {MainWindow}'s menu bar, which we retrieve using the
    QMainWindow::menuBar() function.

    \snippet examples/widgets/scribble/mainwindow.cpp 17
    \snippet examples/widgets/scribble/mainwindow.cpp 18

    In \c mayBeSave(), we check if there are any unsaved changes. If
    there are any, we use QMessageBox to give the user a warning that
    the image has been modified and the opportunity to save the
    modifications.

    As with QColorDialog and QFileDialog, the easiest way to create a
    QMessageBox is to use its static functions. QMessageBox provides
    a range of different messages arranged along two axes: severity
    (question, information, warning and critical) and complexity (the
    number of necessary response buttons). Here we use the \c
    warning() function sice the message is rather important.

    If the user chooses to save, we call the private \c saveFile()
    function. For simplicitly, we use PNG as the file format; the
    user can always press \gui Cancel and save the file using another
    format.

    The \c maybeSave() function returns \c false if the user clicks
    \gui Cancel; otherwise it returns \c true.

    \snippet examples/widgets/scribble/mainwindow.cpp 19
    \snippet examples/widgets/scribble/mainwindow.cpp 20

    In \c saveFile(), we pop up a file dialog with a file name
    suggestion. The static QFileDialog::getSaveFileName() function
    returns a file name selected by the user. The file does not have
    to exist.
*/