Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
++
++ /*!
++ \example widgets/scribble
++ \title Scribble Example
++ \examplecategory {User Interface Components}
++ \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.
++ */