// 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.
*/
