Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
++
++ /*!
++ \example widgets/tablet
++ \title Tablet Example
++ \examplecategory {User Interface Components}
++ \ingroup examples-user-input
++ \brief This example shows how to use a Wacom tablet in Qt applications.
++
++ \image tabletexample.png {Application displaying a drawing area}
++
++ When you use a tablet with Qt applications, \l{QTabletEvent}s are
++ generated. You need to reimplement the \l{QWidget::}{tabletEvent()} event
++ handler if you want to handle tablet events. Events are generated when the
++ tool (stylus) used for drawing enters and leaves the proximity of the
++ tablet (i.e., when it is closed but not pressed down on it), when the tool
++ is pressed down and released from it, when the tool is moved across the
++ tablet, and when one of the buttons on the tool is pressed or released.
++
++ The information available in QTabletEvent depends on the device used.
++ This example can handle a tablet with up to three different drawing tools:
++ a stylus, an airbrush, and an art pen. For any of these the event will
++ contain the position of the tool, pressure on the tablet, button status,
++ vertical tilt, and horizontal tilt (i.e, the angle between the device and
++ the perpendicular of the tablet, if the tablet hardware can provide it).
++ The airbrush has a finger wheel; the position of this is also available
++ in the tablet event. The art pen provides rotation around the axis
++ perpendicular to the tablet surface, so that it can be used for calligraphy.
++
++ In this example we implement a drawing program. You can use the stylus to
++ draw on the tablet as you use a pencil on paper. When you draw with the
++ airbrush you get a spray of virtual paint; the finger wheel is used to
++ change the density of the spray. When you draw with the art pen, you get a
++ line whose width and endpoint angle depend on the rotation of the pen.
++ The pressure and tilt can also be assigned to change the alpha and
++ saturation values of the color and the width of the stroke.
++
++ The example consists of the following:
++
++ \list
++ \li The \c MainWindow class inherits QMainWindow, creates
++ the menus, and connects their slots and signals.
++ \li The \c TabletCanvas class inherits QWidget and
++ receives tablet events. It uses the events to paint onto an
++ offscreen pixmap, and then renders it.
++ \li The \c TabletApplication class inherits QApplication. This
++ class handles tablet proximity events.
++ \li The \c main() function creates a \c MainWindow and shows it
++ as a top level window.
++ \endlist
++
++
++ \section1 MainWindow Class Definition
++
++ The \c MainWindow creates a \c TabletCanvas and sets it as its
++ center widget.
++
++ \snippet widgets/tablet/mainwindow.h 0
++
++ \c createMenus() sets up the menus with the actions. We have one
++ QActionGroup for the actions that alter the alpha channel, color saturation
++ and line width respectively. The action groups are connected to the
++ \c setAlphaValuator(), \c setSaturationValuator(), and
++ \c setLineWidthValuator() slots, which call functions in \c TabletCanvas.
++
++ \section1 MainWindow Class Implementation
++
++ We start with a look at the constructor \c MainWindow():
++
++ \snippet widgets/tablet/mainwindow.cpp 0
++
++ In the constructor we call \c createMenus() to create all the actions and
++ menus, and set the canvas as the center widget.
++
++ \snippet widgets/tablet/mainwindow.cpp 8
++
++ At the beginning of \c createMenus() we populate the \b File menu.
++ We use an overload of \l{QMenu::}{addAction()}, introduced in Qt 5.6, to create
++ a menu item with a shortcut (and optionally an icon), add it to its menu,
++ and connect it to a slot, all with one line of code. We use QKeySequence to
++ get the platform-specific standard key shortcuts for these common menu items.
++
++ We also populate the \b Brush menu. The command to change a brush does not
++ normally have a standard shortcut, so we use \l{QObject::}{tr()} to enable
++ translating the shortcut along with the language translation of the application.
++
++ Now we will look at the creation of one group of mutually-exclusive actions
++ in a submenu of the \b Tablet menu, for selecting which property of each
++ QTabletEvent will be used to vary the translucency (alpha channel) of the
++ line being drawn or color being airbrushed.
++
++ \snippet widgets/tablet/mainwindow.cpp 9
++
++ We want the user to be able to choose whether the drawing color's alpha
++ component should be modulated by the tablet pressure, tilt, or the position
++ of the thumbwheel on the airbrush tool. We have one action for each choice,
++ and an additional action to choose not to change the alpha, that is, to keep
++ the color opaque. We make the actions checkable; the \c alphaChannelGroup
++ will then ensure that only one of the actions are checked at any time. The
++ \c triggered() signal is emitted from the group when an action is checked,
++ so we connect that to \c MainWindow::setAlphaValuator(). It will need to know
++ which property (valuator) of the QTabletEvent to pay attention to from now
++ on, so we use the QAction::data property to pass this information along.
++ (In order for this to be possible, the enum \c Valuator must be a registered
++ metatype, so that it can be inserted into a QVariant. That is accomplished
++ by the \c Q_ENUM declaration in tabletcanvas.h.)
++
++ Here is the implementation of \c setAlphaValuator():
++
++ \snippet widgets/tablet/mainwindow.cpp 2
++
++ It simply needs to retrieve the \c Valuator enum from QAction::data(), and
++ pass that to \c TabletCanvas::setAlphaChannelValuator(). If we were not
++ using the \c data property, we would instead need to compare the QAction
++ pointer itself, for example in a switch statement. But that would require
++ keeping pointers to each QAction in class variables, for comparison purposes.
++
++ Here is the implementation of \c setBrushColor():
++
++ \snippet widgets/tablet/mainwindow.cpp 1
++
++ We do lazy initialization of a QColorDialog the first time the user
++ chooses \b {Brush color...} from the menu or via the action shortcut.
++ While the dialog is open, each time the user chooses a different color,
++ \c TabletCanvas::setColor() will be called to change the drawing color.
++ Because it is a non-modal dialog, the user is free to leave the color
++ dialog open, so as to be able to conveniently and frequently change colors,
++ or close it and re-open it later.
++
++ Here is the implementation of \c save():
++
++ \snippet widgets/tablet/mainwindow.cpp 5
++
++ We use the QFileDialog to let the user select a file to save the drawing,
++ and then call \c TabletCanvas::saveImage() to actually write it to the
++ file.
++
++ Here is the implementation of \c load():
++
++ \snippet widgets/tablet/mainwindow.cpp 6
++
++ We let the user select the image file to be opened with a QFileDialog; we
++ then ask the canvas to load the image with \c loadImage().
++
++ Here is the implementation of \c about():
++
++ \snippet widgets/tablet/mainwindow.cpp 7
++
++ We show a message box with a short description of the example.
++
++
++ \section1 TabletCanvas Class Definition
++
++ The \c TabletCanvas class provides a surface on which the
++ user can draw with a tablet.
++
++ \snippet widgets/tablet/tabletcanvas.h 0
++
++ The canvas can change the alpha channel, color saturation, and line width
++ of the stroke. We have an enum listing the QTabletEvent properties with
++ which it is possible to modulate them. We keep a private variable for each:
++ \c m_alphaChannelValuator, \c m_colorSaturationValuator and
++ \c m_lineWidthValuator, and we provide accessor functions for them.
++
++ We draw on a QPixmap with \c m_pen and \c m_brush using \c m_color.
++ Each time a QTabletEvent is received, the stroke is drawn from
++ \c lastPoint to the point given in the current QTabletEvent,
++ and then the position and rotation are saved in \c lastPoint for next time.
++ The \c saveImage() and \c loadImage() functions save and load the QPixmap to disk.
++ The pixmap is drawn on the widget in \c paintEvent().
++
++ The interpretation of events from the tablet is done in \c tabletEvent(), and
++ \c paintPixmap(), \c updateBrush(), and \c updateCursor() are helper
++ functions used by \c tabletEvent().
++
++
++ \section1 TabletCanvas Class Implementation
++
++ We start with a look at the constructor:
++
++ \snippet widgets/tablet/tabletcanvas.cpp 0
++
++ In the constructor we initialize most of our class variables.
++
++ Here is the implementation of \c saveImage():
++
++ \snippet widgets/tablet/tabletcanvas.cpp 1
++
++ QPixmap implements functionality to save itself to disk, so we
++ simply call \l{QPixmap::}{save()}.
++
++ Here is the implementation of \c loadImage():
++
++ \snippet widgets/tablet/tabletcanvas.cpp 2
++
++ We simply call \l{QPixmap::}{load()}, which loads the image from \a file.
++
++ Here is the implementation of \c tabletEvent():
++
++ \snippet widgets/tablet/tabletcanvas.cpp 3
++
++ We get three kind of events to this function: \c TabletPress, \c TabletRelease,
++ and \c TabletMove, which are generated when a drawing tool is pressed down on,
++ lifed up from, or moved across the tablet. We set \c m_deviceDown to \c true
++ when a device is pressed down on the tablet; we then know that we should
++ draw when we receive move events. We have implemented \c updateBrush()
++ to update \c m_brush and \c m_pen depending on which of the tablet event
++ properties the user has chosen to pay attention to. The \c updateCursor()
++ function selects a cursor to represent the drawing tool in use, so that
++ as you hover with the tool in proximity of the tablet, you can see what
++ kind of stroke you are about to make.
++
++ \snippet widgets/tablet/tabletcanvas.cpp 12
++
++ If an art pen (\c RotationStylus) is in use, \c updateCursor()
++ is also called for each \c TabletMove event, and renders a rotated cursor
++ so that you can see the angle of the pen tip.
++
++ Here is the implementation of \c paintEvent():
++
++ \snippet widgets/tablet/tabletcanvas.cpp 4
++
++ The first time Qt calls paintEvent(), m_pixmap is default-constructed, so
++ QPixmap::isNull() returns \c true. Now that we know which screen we will be
++ rendering to, we can create a pixmap with the appropriate resolution.
++ The size of the pixmap with which we fill the window depends on the screen
++ resolution, as the example does not support zoom; and it may be that one
++ screen is \l {High DPI}{high DPI} while another is not. We need to
++ draw the background too, as the default is gray.
++
++ After that, we simply draw the pixmap to the top left of the widget.
++
++ Here is the implementation of \c paintPixmap():
++
++ \snippet widgets/tablet/tabletcanvas.cpp 5
++
++ In this function we draw on the pixmap based on the movement of the tool.
++ If the tool used on the tablet is a stylus, we want to draw a line from
++ the last-known position to the current position. We also assume that this
++ is a reasonable handling of any unknown device, but update the status bar
++ with a warning. If it is an airbrush, we want to draw a circle filled with
++ a soft gradient, whose density can depend on various event parameters.
++ By default it depends on the tangential pressure, which is the position of
++ the finger wheel on the airbrush. If the tool is a rotation stylus, we
++ simulate a felt marker by drawing trapezoidal stroke segments.
++
++ \snippet widgets/tablet/tabletcanvas.cpp 6
++
++ In \c updateBrush() we set the pen and brush used for drawing to match
++ \c m_alphaChannelValuator, \c m_lineWidthValuator, \c m_colorSaturationValuator,
++ and \c m_color. We will examine the code to set up \c m_brush and
++ \c m_pen for each of these variables:
++
++ \snippet widgets/tablet/tabletcanvas.cpp 7
++
++ We fetch the current drawingcolor's hue, saturation, value,
++ and alpha values. \c hValue and \c vValue are set to the
++ horizontal and vertical tilt as a number from 0 to 255. The
++ original values are in degrees from -60 to 60, i.e., 0 equals
++ -60, 127 equals 0, and 255 equals 60 degrees. The angle measured
++ is between the device and the perpendicular of the tablet (see
++ QTabletEvent for an illustration).
++
++ \snippet widgets/tablet/tabletcanvas.cpp 8
++
++ The alpha channel of QColor is given as a number between 0 and 255 where 0
++ is transparent and 255 is opaque, or as a floating-point number where 0 is
++ transparent and 1.0 is opaque. \l{QTabletEvent::}{pressure()} returns the
++ pressure as a qreal between 0.0 and 1.0. We get the smallest alpha values
++ (i.e., the color is most transparent) when the pen is perpendicular to the
++ tablet. We select the largest of the vertical and horizontal tilt values.
++
++ \snippet widgets/tablet/tabletcanvas.cpp 9
++
++ The color saturation in the HSV color model can be given as an integer
++ between 0 and 255 or as a floating-point value between 0 and 1. We chose to
++ represent alpha as an integer, so we call \l{QColor::}{setHsv()} with
++ integer values. That means we need to multiply the pressure to a number
++ between 0 and 255.
++
++ \snippet widgets/tablet/tabletcanvas.cpp 10
++
++ The width of the pen stroke can increase with pressure, if so chosen.
++ But when the pen width is controlled by tilt, we let the width increase
++ with the angle between the tool and the perpendicular of the tablet.
++
++ \snippet widgets/tablet/tabletcanvas.cpp 11
++
++ We finally check whether the pointer is the stylus or the eraser.
++ If it is the eraser, we set the color to the background color of
++ the pixmap and let the pressure decide the pen width, else we set
++ the colors we have decided previously in the function.
++
++
++ \section1 TabletApplication Class Definition
++
++ We inherit QApplication in this class because we want to
++ reimplement the \l{QApplication::}{event()} function.
++
++ \snippet widgets/tablet/tabletapplication.h 0
++
++ \c TabletApplication exists as a subclass of QApplication in order to
++ receive tablet proximity events and forward them to \c TabletCanvas.
++ The \c TabletEnterProximity and \c TabletLeaveProximity events are sent to
++ the QApplication object, while other tablet events are sent to the QWidget's
++ \c event() handler, which sends them on to \l{QWidget::}{tabletEvent()}.
++
++
++ \section1 TabletApplication Class Implementation
++
++ Here is the implementation of \c event():
++
++ \snippet widgets/tablet/tabletapplication.cpp 0
++
++ We use this function to handle the \c TabletEnterProximity and
++ \c TabletLeaveProximity events, which are generated when a drawing
++ tool enters or leaves the proximity of the tablet. Here we call
++ \c TabletCanvas::setTabletDevice(), which then calls \c updateCursor(),
++ which will set an appropriate cursor. This is the only reason we
++ need the proximity events; for the purpose of correct drawing, it is
++ enough for \c TabletCanvas to observe the \l{QTabletEvent::}{device()} and
++ \l{QTabletEvent::}{pointerType()} in each event that it receives.
++
++
++ \section1 The \c main() function
++
++ Here is the example's \c main() function:
++
++ \snippet widgets/tablet/main.cpp 0
++
++ Here we create a \c MainWindow and display it as a top level window. We use
++ the \c TabletApplication class. We need to set the canvas after the
++ application is created. We cannot use classes that implement event handling
++ before an QApplication object is instantiated.
++ */