diff options
Diffstat (limited to 'doc/src/examples/scribble.qdoc')
-rw-r--r-- | doc/src/examples/scribble.qdoc | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/doc/src/examples/scribble.qdoc b/doc/src/examples/scribble.qdoc new file mode 100644 index 0000000..4dc1783 --- /dev/null +++ b/doc/src/examples/scribble.qdoc @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $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, which + contains the interesting, then we will take a look at the \c + MainWindow class that uses it. + + \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::getInteger() 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 appropiate + 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. +*/ |