1 | /****************************************************************************
|
---|
2 | **
|
---|
3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
---|
4 | ** All rights reserved.
|
---|
5 | ** Contact: Nokia Corporation ([email protected])
|
---|
6 | **
|
---|
7 | ** This file is part of the documentation of the Qt Toolkit.
|
---|
8 | **
|
---|
9 | ** $QT_BEGIN_LICENSE:FDL$
|
---|
10 | ** Commercial Usage
|
---|
11 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
---|
12 | ** accordance with the Qt Commercial License Agreement provided with the
|
---|
13 | ** Software or, alternatively, in accordance with the terms contained in a
|
---|
14 | ** written agreement between you and Nokia.
|
---|
15 | **
|
---|
16 | ** GNU Free Documentation License
|
---|
17 | ** Alternatively, this file may be used under the terms of the GNU Free
|
---|
18 | ** Documentation License version 1.3 as published by the Free Software
|
---|
19 | ** Foundation and appearing in the file included in the packaging of this
|
---|
20 | ** file.
|
---|
21 | **
|
---|
22 | ** If you have questions regarding the use of this file, please contact
|
---|
23 | ** Nokia at [email protected].
|
---|
24 | ** $QT_END_LICENSE$
|
---|
25 | **
|
---|
26 | ****************************************************************************/
|
---|
27 |
|
---|
28 | /*!
|
---|
29 | \example widgets/scribble
|
---|
30 | \title Scribble Example
|
---|
31 |
|
---|
32 | The Scribble example shows how to reimplement some of QWidget's
|
---|
33 | event handlers to receive the events generated for the
|
---|
34 | application's widgets.
|
---|
35 |
|
---|
36 | We reimplement the mouse event handlers to implement drawing, the
|
---|
37 | paint event handler to update the application and the resize event
|
---|
38 | handler to optimize the application's appearance. In addition we
|
---|
39 | reimplement the close event handler to intercept the close events
|
---|
40 | before terminating the application.
|
---|
41 |
|
---|
42 | The example also demonstrates how to use QPainter to draw an image
|
---|
43 | in real time, as well as to repaint widgets.
|
---|
44 |
|
---|
45 | \image scribble-example.png Screenshot of the Scribble example
|
---|
46 |
|
---|
47 | With the Scribble application the users can draw an image. The
|
---|
48 | \gui File menu gives the users the possibility to open and edit an
|
---|
49 | existing image file, save an image and exit the application. While
|
---|
50 | drawing, the \gui Options menu allows the users to to choose the
|
---|
51 | pen color and pen width, as well as clear the screen. In addition
|
---|
52 | the \gui Help menu provides the users with information about the
|
---|
53 | Scribble example in particular, and about Qt in general.
|
---|
54 |
|
---|
55 | The example consists of two classes:
|
---|
56 |
|
---|
57 | \list
|
---|
58 | \o \c ScribbleArea is a custom widget that displays a QImage and
|
---|
59 | allows to the user to draw on it.
|
---|
60 | \o \c MainWindow provides a menu above the \c ScribbleArea.
|
---|
61 | \endlist
|
---|
62 |
|
---|
63 | We will start by reviewing the \c ScribbleArea class. Then we will
|
---|
64 | review the \c MainWindow class, which uses \c ScribbleArea.
|
---|
65 |
|
---|
66 | \section1 ScribbleArea Class Definition
|
---|
67 |
|
---|
68 | \snippet examples/widgets/scribble/scribblearea.h 0
|
---|
69 |
|
---|
70 | The \c ScribbleArea class inherits from QWidget. We reimplement
|
---|
71 | the \c mousePressEvent(), \c mouseMoveEvent() and \c
|
---|
72 | mouseReleaseEvent() functions to implement the drawing. We
|
---|
73 | reimplement the \c paintEvent() function to update the scribble
|
---|
74 | area, and the \c resizeEvent() function to ensure that the QImage
|
---|
75 | on which we draw is at least as large as the widget at any time.
|
---|
76 |
|
---|
77 | We need several public functions: \c openImage() loads an image
|
---|
78 | from a file into the scribble area, allowing the user to edit the
|
---|
79 | image; \c save() writes the currently displayed image to file; \c
|
---|
80 | clearImage() slot clears the image displayed in the scribble
|
---|
81 | area. We need the private \c drawLineTo() function to actually do
|
---|
82 | the drawing, and \c resizeImage() to change the size of a
|
---|
83 | QImage. The \c print() slot handles printing.
|
---|
84 |
|
---|
85 | We also need the following private variables:
|
---|
86 |
|
---|
87 | \list
|
---|
88 | \o \c modified is \c true if there are unsaved
|
---|
89 | changes to the image displayed in the scribble area.
|
---|
90 | \o \c scribbling is \c true while the user is pressing
|
---|
91 | the left mouse button within the scribble area.
|
---|
92 | \o \c penWidth and \c penColor hold the currently
|
---|
93 | set width and color for the pen used in the application.
|
---|
94 | \o \c image stores the image drawn by the user.
|
---|
95 | \o \c lastPoint holds the position of the cursor at the last
|
---|
96 | mouse press or mouse move event.
|
---|
97 | \endlist
|
---|
98 |
|
---|
99 | \section1 ScribbleArea Class Implementation
|
---|
100 |
|
---|
101 | \snippet examples/widgets/scribble/scribblearea.cpp 0
|
---|
102 |
|
---|
103 | In the constructor, we set the Qt::WA_StaticContents
|
---|
104 | attribute for the widget, indicating that the widget contents are
|
---|
105 | rooted to the top-left corner and don't change when the widget is
|
---|
106 | resized. Qt uses this attribute to optimize paint events on
|
---|
107 | resizes. This is purely an optimization and should only be used
|
---|
108 | for widgets whose contents are static and rooted to the top-left
|
---|
109 | corner.
|
---|
110 |
|
---|
111 | \snippet examples/widgets/scribble/scribblearea.cpp 1
|
---|
112 | \snippet examples/widgets/scribble/scribblearea.cpp 2
|
---|
113 |
|
---|
114 | In the \c openImage() function, we load the given image. Then we
|
---|
115 | resize the loaded QImage to be at least as large as the widget in
|
---|
116 | both directions using the private \c resizeImage() function and
|
---|
117 | we set the \c image member variable to be the loaded image. At
|
---|
118 | the end, we call QWidget::update() to schedule a repaint.
|
---|
119 |
|
---|
120 | \snippet examples/widgets/scribble/scribblearea.cpp 3
|
---|
121 | \snippet examples/widgets/scribble/scribblearea.cpp 4
|
---|
122 |
|
---|
123 | The \c saveImage() function creates a QImage object that covers
|
---|
124 | only the visible section of the actual \c image and saves it using
|
---|
125 | QImage::save(). If the image is successfully saved, we set the
|
---|
126 | scribble area's \c modified variable to \c false, because there is
|
---|
127 | no unsaved data.
|
---|
128 |
|
---|
129 | \snippet examples/widgets/scribble/scribblearea.cpp 5
|
---|
130 | \snippet examples/widgets/scribble/scribblearea.cpp 6
|
---|
131 | \codeline
|
---|
132 | \snippet examples/widgets/scribble/scribblearea.cpp 7
|
---|
133 | \snippet examples/widgets/scribble/scribblearea.cpp 8
|
---|
134 |
|
---|
135 | The \c setPenColor() and \c setPenWidth() functions set the
|
---|
136 | current pen color and width. These values will be used for future
|
---|
137 | drawing operations.
|
---|
138 |
|
---|
139 | \snippet examples/widgets/scribble/scribblearea.cpp 9
|
---|
140 | \snippet examples/widgets/scribble/scribblearea.cpp 10
|
---|
141 |
|
---|
142 | The public \c clearImage() slot clears the image displayed in the
|
---|
143 | scribble area. We simply fill the entire image with white, which
|
---|
144 | corresponds to RGB value (255, 255, 255). As usual when we modify
|
---|
145 | the image, we set \c modified to \c true and schedule a repaint.
|
---|
146 |
|
---|
147 | \snippet examples/widgets/scribble/scribblearea.cpp 11
|
---|
148 | \snippet examples/widgets/scribble/scribblearea.cpp 12
|
---|
149 |
|
---|
150 | For mouse press and mouse release events, we use the
|
---|
151 | QMouseEvent::button() function to find out which button caused
|
---|
152 | the event. For mose move events, we use QMouseEvent::buttons()
|
---|
153 | to find which buttons are currently held down (as an OR-combination).
|
---|
154 |
|
---|
155 | If the users press the left mouse button, we store the position
|
---|
156 | of the mouse cursor in \c lastPoint. We also make a note that the
|
---|
157 | user is currently scribbling. (The \c scribbling variable is
|
---|
158 | necessary because we can't assume that a mouse move and mouse
|
---|
159 | release event is always preceded by a mouse press event on the
|
---|
160 | same widget.)
|
---|
161 |
|
---|
162 | If the user moves the mouse with the left button pressed down or
|
---|
163 | releases the button, we call the private \c drawLineTo() function
|
---|
164 | to draw.
|
---|
165 |
|
---|
166 | \snippet examples/widgets/scribble/scribblearea.cpp 13
|
---|
167 | \snippet examples/widgets/scribble/scribblearea.cpp 14
|
---|
168 |
|
---|
169 | In the reimplementation of the \l
|
---|
170 | {QWidget::paintEvent()}{paintEvent()} function, we simply create
|
---|
171 | a QPainter for the scribble area, and draw the image.
|
---|
172 |
|
---|
173 | At this point, you might wonder why we don't just draw directly
|
---|
174 | onto the widget instead of drawing in a QImage and copying the
|
---|
175 | QImage onto screen in \c paintEvent(). There are at least three
|
---|
176 | good reasons for this:
|
---|
177 |
|
---|
178 | \list
|
---|
179 | \o The window system requires us to be able to redraw the widget
|
---|
180 | \e{at any time}. For example, if the window is minimized and
|
---|
181 | restored, the window system might have forgotten the contents
|
---|
182 | of the widget and send us a paint event. In other words, we
|
---|
183 | can't rely on the window system to remember our image.
|
---|
184 |
|
---|
185 | \o Qt normally doesn't allow us to paint outside of \c
|
---|
186 | paintEvent(). In particular, we can't paint from the mouse
|
---|
187 | event handlers. (This behavior can be changed using the
|
---|
188 | Qt::WA_PaintOnScreen widget attribute, though.)
|
---|
189 |
|
---|
190 | \o If initialized properly, a QImage is guaranteed to use 8-bit
|
---|
191 | for each color channel (red, green, blue, and alpha), whereas
|
---|
192 | a QWidget might have a lower color depth, depending on the
|
---|
193 | monitor configuration. This means that if we load a 24-bit or
|
---|
194 | 32-bit image and paint it onto a QWidget, then copy the
|
---|
195 | QWidget into a QImage again, we might lose some information.
|
---|
196 | \endlist
|
---|
197 |
|
---|
198 | \snippet examples/widgets/scribble/scribblearea.cpp 15
|
---|
199 | \snippet examples/widgets/scribble/scribblearea.cpp 16
|
---|
200 |
|
---|
201 | When the user starts the Scribble application, a resize event is
|
---|
202 | generated and an image is created and displayed in the scribble
|
---|
203 | area. We make this initial image slightly larger than the
|
---|
204 | application's main window and scribble area, to avoid always
|
---|
205 | resizing the image when the user resizes the main window (which
|
---|
206 | would be very inefficient). But when the main window becomes
|
---|
207 | larger than this initial size, the image needs to be resized.
|
---|
208 |
|
---|
209 | \snippet examples/widgets/scribble/scribblearea.cpp 17
|
---|
210 | \snippet examples/widgets/scribble/scribblearea.cpp 18
|
---|
211 |
|
---|
212 | In \c drawLineTo(), we draw a line from the point where the mouse
|
---|
213 | was located when the last mouse press or mouse move occurred, we
|
---|
214 | set \c modified to true, we generate a repaint event, and we
|
---|
215 | update \c lastPoint so that next time \c drawLineTo() is called,
|
---|
216 | we continue drawing from where we left.
|
---|
217 |
|
---|
218 | We could call the \c update() function with no parameter, but as
|
---|
219 | an easy optimization we pass a QRect that specifies the rectangle
|
---|
220 | inside the scribble are needs updating, to avoid a complete
|
---|
221 | repaint of the widget.
|
---|
222 |
|
---|
223 | \snippet examples/widgets/scribble/scribblearea.cpp 19
|
---|
224 | \snippet examples/widgets/scribble/scribblearea.cpp 20
|
---|
225 |
|
---|
226 | QImage has no nice API for resizing an image. There's a
|
---|
227 | QImage::copy() function that could do the trick, but when used to
|
---|
228 | expand an image, it fills the new areas with black, whereas we
|
---|
229 | want white.
|
---|
230 |
|
---|
231 | So the trick is to create a brand new QImage with the right size,
|
---|
232 | to fill it with white, and to draw the old image onto it using
|
---|
233 | QPainter. The new image is given the QImage::Format_RGB32
|
---|
234 | format, which means that each pixel is stored as 0xffRRGGBB
|
---|
235 | (where RR, GG, and BB are the red, green and blue
|
---|
236 | color channels, ff is the hexadecimal value 255).
|
---|
237 |
|
---|
238 | Printing is handled by the \c print() slot:
|
---|
239 |
|
---|
240 | \snippet examples/widgets/scribble/scribblearea.cpp 21
|
---|
241 |
|
---|
242 | We construct a high resolution QPrinter object for the required
|
---|
243 | output format, using a QPrintDialog to ask the user to specify a
|
---|
244 | page size and indicate how the output should be formatted on the page.
|
---|
245 |
|
---|
246 | If the dialog is accepted, we perform the task of printing to the paint
|
---|
247 | device:
|
---|
248 |
|
---|
249 | \snippet examples/widgets/scribble/scribblearea.cpp 22
|
---|
250 |
|
---|
251 | Printing an image to a file in this way is simply a matter of
|
---|
252 | painting onto the QPrinter. We scale the image to fit within the
|
---|
253 | available space on the page before painting it onto the paint
|
---|
254 | device.
|
---|
255 |
|
---|
256 | \section1 MainWindow Class Definition
|
---|
257 |
|
---|
258 | \snippet examples/widgets/scribble/mainwindow.h 0
|
---|
259 |
|
---|
260 | The \c MainWindow class inherits from QMainWindow. We reimplement
|
---|
261 | the \l{QWidget::closeEvent()}{closeEvent()} handler from QWidget.
|
---|
262 | The \c open(), \c save(), \c penColor() and \c penWidth()
|
---|
263 | slots correspond to menu entries. In addition we create four
|
---|
264 | private functions.
|
---|
265 |
|
---|
266 | We use the boolean \c maybeSave() function to check if there are
|
---|
267 | any unsaved changes. If there are unsaved changes, we give the
|
---|
268 | user the opportunity to save these changes. The function returns
|
---|
269 | \c false if the user clicks \gui Cancel. We use the \c saveFile()
|
---|
270 | function to let the user save the image currently displayed in
|
---|
271 | the scribble area.
|
---|
272 |
|
---|
273 | \section1 MainWindow Class Implementation
|
---|
274 |
|
---|
275 | \snippet examples/widgets/scribble/mainwindow.cpp 0
|
---|
276 |
|
---|
277 | In the constructor, we create a scribble area which we make the
|
---|
278 | central widget of the \c MainWindow widget. Then we create the
|
---|
279 | associated actions and menus.
|
---|
280 |
|
---|
281 | \snippet examples/widgets/scribble/mainwindow.cpp 1
|
---|
282 | \snippet examples/widgets/scribble/mainwindow.cpp 2
|
---|
283 |
|
---|
284 | Close events are sent to widgets that the users want to close,
|
---|
285 | usually by clicking \gui{File|Exit} or by clicking the \gui X
|
---|
286 | title bar button. By reimplementing the event handler, we can
|
---|
287 | intercept attempts to close the application.
|
---|
288 |
|
---|
289 | In this example, we use the close event to ask the user to save
|
---|
290 | any unsaved changes. The logic for that is located in the \c
|
---|
291 | maybeSave() function. If \c maybeSave() returns true, there are
|
---|
292 | no modifications or the users successfully saved them, and we
|
---|
293 | accept the event. The application can then terminate normally. If
|
---|
294 | \c maybeSave() returns false, the user clicked \gui Cancel, so we
|
---|
295 | "ignore" the event, leaving the application unaffected by it.
|
---|
296 |
|
---|
297 | \snippet examples/widgets/scribble/mainwindow.cpp 3
|
---|
298 | \snippet examples/widgets/scribble/mainwindow.cpp 4
|
---|
299 |
|
---|
300 | In the \c open() slot we first give the user the opportunity to
|
---|
301 | save any modifications to the currently displayed image, before a
|
---|
302 | new image is loaded into the scribble area. Then we ask the user
|
---|
303 | to choose a file and we load the file in the \c ScribbleArea.
|
---|
304 |
|
---|
305 | \snippet examples/widgets/scribble/mainwindow.cpp 5
|
---|
306 | \snippet examples/widgets/scribble/mainwindow.cpp 6
|
---|
307 |
|
---|
308 | The \c save() slot is called when the users choose the \gui {Save
|
---|
309 | As} menu entry, and then choose an entry from the format menu. The
|
---|
310 | first thing we need to do is to find out which action sent the
|
---|
311 | signal using QObject::sender(). This function returns the sender
|
---|
312 | as a QObject pointer. Since we know that the sender is an action
|
---|
313 | object, we can safely cast the QObject. We could have used a
|
---|
314 | C-style cast or a C++ \c static_cast<>(), but as a defensive
|
---|
315 | programming technique we use a qobject_cast(). The advantage is
|
---|
316 | that if the object has the wrong type, a null pointer is
|
---|
317 | returned. Crashes due to null pointers are much easier to diagnose
|
---|
318 | than crashes due to unsafe casts.
|
---|
319 |
|
---|
320 | Once we have the action, we extract the chosen format using
|
---|
321 | QAction::data(). (When the actions are created, we use
|
---|
322 | QAction::setData() to set our own custom data attached to the
|
---|
323 | action, as a QVariant. More on this when we review \c
|
---|
324 | createActions().)
|
---|
325 |
|
---|
326 | Now that we know the format, we call the private \c saveFile()
|
---|
327 | function to save the currently displayed image.
|
---|
328 |
|
---|
329 | \snippet examples/widgets/scribble/mainwindow.cpp 7
|
---|
330 | \snippet examples/widgets/scribble/mainwindow.cpp 8
|
---|
331 |
|
---|
332 | We use the \c penColor() slot to retrieve a new color from the
|
---|
333 | user with a QColorDialog. If the user chooses a new color, we
|
---|
334 | make it the scribble area's color.
|
---|
335 |
|
---|
336 | \snippet examples/widgets/scribble/mainwindow.cpp 9
|
---|
337 | \snippet examples/widgets/scribble/mainwindow.cpp 10
|
---|
338 |
|
---|
339 | To retrieve a new pen width in the \c penWidth() slot, we use
|
---|
340 | QInputDialog. The QInputDialog class provides a simple
|
---|
341 | convenience dialog to get a single value from the user. We use
|
---|
342 | the static QInputDialog::getInt() function, which combines a
|
---|
343 | QLabel and a QSpinBox. The QSpinBox is initialized with the
|
---|
344 | scribble area's pen width, allows a range from 1 to 50, a step of
|
---|
345 | 1 (meaning that the up and down arrow increment or decrement the
|
---|
346 | value by 1).
|
---|
347 |
|
---|
348 | The boolean \c ok variable will be set to \c true if the user
|
---|
349 | clicked \gui OK and to \c false if the user pressed \gui Cancel.
|
---|
350 |
|
---|
351 | \snippet examples/widgets/scribble/mainwindow.cpp 11
|
---|
352 | \snippet examples/widgets/scribble/mainwindow.cpp 12
|
---|
353 |
|
---|
354 | We implement the \c about() slot to create a message box
|
---|
355 | describing what the example is designed to show.
|
---|
356 |
|
---|
357 | \snippet examples/widgets/scribble/mainwindow.cpp 13
|
---|
358 | \snippet examples/widgets/scribble/mainwindow.cpp 14
|
---|
359 |
|
---|
360 | In the \c createAction() function we create the actions
|
---|
361 | representing the menu entries and connect them to the appropiate
|
---|
362 | slots. In particular we create the actions found in the \gui
|
---|
363 | {Save As} sub-menu. We use QImageWriter::supportedImageFormats()
|
---|
364 | to get a list of the supported formats (as a QList<QByteArray>).
|
---|
365 |
|
---|
366 | Then we iterate through the list, creating an action for each
|
---|
367 | format. We call QAction::setData() with the file format, so we
|
---|
368 | can retrieve it later as QAction::data(). We could also have
|
---|
369 | deduced the file format from the action's text, by truncating the
|
---|
370 | "...", but that would have been inelegant.
|
---|
371 |
|
---|
372 | \snippet examples/widgets/scribble/mainwindow.cpp 15
|
---|
373 | \snippet examples/widgets/scribble/mainwindow.cpp 16
|
---|
374 |
|
---|
375 | In the \c createMenu() function, we add the previously created
|
---|
376 | format actions to the \c saveAsMenu. Then we add the rest of the
|
---|
377 | actions as well as the \c saveAsMenu sub-menu to the \gui File,
|
---|
378 | \gui Options and \gui Help menus.
|
---|
379 |
|
---|
380 | The QMenu class provides a menu widget for use in menu bars,
|
---|
381 | context menus, and other popup menus. The QMenuBar class provides
|
---|
382 | a horizontal menu bar with a list of pull-down \l{QMenu}s. At the
|
---|
383 | end we put the \gui File and \gui Options menus in the \c
|
---|
384 | {MainWindow}'s menu bar, which we retrieve using the
|
---|
385 | QMainWindow::menuBar() function.
|
---|
386 |
|
---|
387 | \snippet examples/widgets/scribble/mainwindow.cpp 17
|
---|
388 | \snippet examples/widgets/scribble/mainwindow.cpp 18
|
---|
389 |
|
---|
390 | In \c mayBeSave(), we check if there are any unsaved changes. If
|
---|
391 | there are any, we use QMessageBox to give the user a warning that
|
---|
392 | the image has been modified and the opportunity to save the
|
---|
393 | modifications.
|
---|
394 |
|
---|
395 | As with QColorDialog and QFileDialog, the easiest way to create a
|
---|
396 | QMessageBox is to use its static functions. QMessageBox provides
|
---|
397 | a range of different messages arranged along two axes: severity
|
---|
398 | (question, information, warning and critical) and complexity (the
|
---|
399 | number of necessary response buttons). Here we use the \c
|
---|
400 | warning() function sice the message is rather important.
|
---|
401 |
|
---|
402 | If the user chooses to save, we call the private \c saveFile()
|
---|
403 | function. For simplicitly, we use PNG as the file format; the
|
---|
404 | user can always press \gui Cancel and save the file using another
|
---|
405 | format.
|
---|
406 |
|
---|
407 | The \c maybeSave() function returns \c false if the user clicks
|
---|
408 | \gui Cancel; otherwise it returns \c true.
|
---|
409 |
|
---|
410 | \snippet examples/widgets/scribble/mainwindow.cpp 19
|
---|
411 | \snippet examples/widgets/scribble/mainwindow.cpp 20
|
---|
412 |
|
---|
413 | In \c saveFile(), we pop up a file dialog with a file name
|
---|
414 | suggestion. The static QFileDialog::getSaveFileName() function
|
---|
415 | returns a file name selected by the user. The file does not have
|
---|
416 | to exist.
|
---|
417 | */
|
---|