| /**************************************************************************** |
| ** |
| ** 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/tablet |
| \title Tablet Example |
| \ingroup examples-widgets |
| \brief This example shows how to use a Wacom tablet in Qt applications. |
| |
| \image tabletexample.png |
| |
| 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 close 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 |
| 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. |
| (See the \l{Application Example}{application example} if you want a |
| high-level introduction to QActions.) |
| |
| \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 Displays}{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() hander, 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. |
| */ |