blob: ca79431334f39ec95ceaa8c3ff90ee1baafbe507 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** 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. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/
/*!
\example widgets/scribble
\title Scribble Example
\ingroup examples-widgets
\brief 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
\uicontrol 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 \uicontrol Options menu allows the users to choose the
pen color and pen width, as well as clear the screen. In addition
the \uicontrol 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
\li \c ScribbleArea is a custom widget that displays a QImage and
allows to the user to draw on it.
\li \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 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
\li \c modified is \c true if there are unsaved
changes to the image displayed in the scribble area.
\li \c scribbling is \c true while the user is pressing
the left mouse button within the scribble area.
\li \c penWidth and \c penColor hold the currently
set width and color for the pen used in the application.
\li \c image stores the image drawn by the user.
\li \c lastPoint holds the position of the cursor at the last
mouse press or mouse move event.
\endlist
\section1 ScribbleArea Class Implementation
\snippet 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 widgets/scribble/scribblearea.cpp 1
\snippet 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 widgets/scribble/scribblearea.cpp 3
\snippet 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 widgets/scribble/scribblearea.cpp 5
\snippet widgets/scribble/scribblearea.cpp 6
\codeline
\snippet widgets/scribble/scribblearea.cpp 7
\snippet 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 widgets/scribble/scribblearea.cpp 9
\snippet 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 widgets/scribble/scribblearea.cpp 11
\snippet 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 mouse 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 widgets/scribble/scribblearea.cpp 13
\snippet 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
\li 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.
\li 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.)
\li 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 widgets/scribble/scribblearea.cpp 15
\snippet 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 widgets/scribble/scribblearea.cpp 17
\snippet 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 widgets/scribble/scribblearea.cpp 19
\snippet 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 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 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 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 \uicontrol 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 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 widgets/scribble/mainwindow.cpp 1
\snippet widgets/scribble/mainwindow.cpp 2
Close events are sent to widgets that the users want to close,
usually by clicking \uicontrol{File|Exit} or by clicking the \uicontrol 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 \uicontrol Cancel, so we
"ignore" the event, leaving the application unaffected by it.
\snippet widgets/scribble/mainwindow.cpp 3
\snippet 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 widgets/scribble/mainwindow.cpp 5
\snippet widgets/scribble/mainwindow.cpp 6
The \c save() slot is called when the users choose the \uicontrol {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 widgets/scribble/mainwindow.cpp 7
\snippet 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 widgets/scribble/mainwindow.cpp 9
\snippet 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 \uicontrol OK and to \c false if the user pressed \uicontrol Cancel.
\snippet widgets/scribble/mainwindow.cpp 11
\snippet 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 widgets/scribble/mainwindow.cpp 13
\snippet 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 \uicontrol
{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 widgets/scribble/mainwindow.cpp 15
\snippet 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 \uicontrol File,
\uicontrol Options and \uicontrol 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 \uicontrol File and \uicontrol Options menus in the \c
{MainWindow}'s menu bar, which we retrieve using the
QMainWindow::menuBar() function.
\snippet widgets/scribble/mainwindow.cpp 17
\snippet 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 \uicontrol Cancel and save the file using another
format.
The \c maybeSave() function returns \c false if the user clicks
\uicontrol Cancel; otherwise it returns \c true.
\snippet widgets/scribble/mainwindow.cpp 19
\snippet 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.
*/