此范例展示如何在 Qt 应用程序中使用 Wacom 数位板。
 
					当使用数位板采用 Qt 应用程序时, QTabletEvent 被生成。需要重实现 tabletEvent() 事件处理程序,若想要处理数位板事件。事件被生成,当用于绘制的工具 (手写笔) 进入和离开数位板附近时 (即:当被关闭但未被按下时),当工具被按下和释放时,当工具在数位板上移动时,及当某一工具按钮被按下或释放时。
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.
范例由以下组成:
MainWindow
							
							类继承
							
								QMainWindow
							
							, creates the menus, and connects their slots and signals.
						
TabletCanvas
							
							类继承
							
								QWidget
							
							and receives tablet events. It uses the events to paint onto an offscreen pixmap, and then renders it.
						
TabletApplication
							
							类继承
							
								QApplication
							
							. This class handles tablet proximity events.
						
main()
							
							函数创建
							
MainWindow
							
							并将它展示作为顶层窗口。
						
						The
						
MainWindow
						
						创建
						
TabletCanvas
						
						并将它设为其中心 Widget。
					
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(TabletCanvas *canvas); private slots: void setBrushColor(); void setAlphaValuator(QAction *action); void setLineWidthValuator(QAction *action); void setSaturationValuator(QAction *action); void setEventCompression(bool compress); bool save(); void load(); void clear(); void about(); private: void createMenus(); TabletCanvas *m_canvas; QColorDialog *m_colorDialog; };
						
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
						
setAlphaValuator()
						
						,
						
setSaturationValuator()
						
						,和
						
setLineWidthValuator()
						
						slots, which call functions in
						
TabletCanvas
						
						.
					
						We start with a look at the constructor
						
MainWindow()
						
						:
					
MainWindow::MainWindow(TabletCanvas *canvas) : m_canvas(canvas), m_colorDialog(nullptr) { createMenus(); setWindowTitle(tr("Tablet Example")); setCentralWidget(m_canvas); QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); }
						In the constructor we call
						
createMenus()
						
						to create all the actions and menus, and set the canvas as the center widget.
					
void MainWindow::createMenus() { QMenu *fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(tr("&Open..."), this, &MainWindow::load, QKeySequence::Open); fileMenu->addAction(tr("&Save As..."), this, &MainWindow::save, QKeySequence::SaveAs); fileMenu->addAction(tr("&New"), this, &MainWindow::clear, QKeySequence::New); fileMenu->addAction(tr("E&xit"), this, &MainWindow::close, QKeySequence::Quit); QMenu *brushMenu = menuBar()->addMenu(tr("&Brush")); brushMenu->addAction(tr("&Brush Color..."), this, &MainWindow::setBrushColor, tr("Ctrl+B"));
						At the beginning of
						
createMenus()
						
						we populate the
						
							File
						
						menu. We use an overload of
						
							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 Brush menu. The command to change a brush does not normally have a standard shortcut, so we use 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 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 application example if you want a high-level introduction to QActions.)
    QMenu *alphaChannelMenu = tabletMenu->addMenu(tr("&Alpha Channel"));
    QAction *alphaChannelPressureAction = alphaChannelMenu->addAction(tr("&Pressure"));
    alphaChannelPressureAction->setData(TabletCanvas::PressureValuator);
    alphaChannelPressureAction->setCheckable(true);
    QAction *alphaChannelTangentialPressureAction = alphaChannelMenu->addAction(tr("T&angential Pressure"));
    alphaChannelTangentialPressureAction->setData(TabletCanvas::TangentialPressureValuator);
    alphaChannelTangentialPressureAction->setCheckable(true);
    alphaChannelTangentialPressureAction->setChecked(true);
    QAction *alphaChannelTiltAction = alphaChannelMenu->addAction(tr("&Tilt"));
    alphaChannelTiltAction->setData(TabletCanvas::TiltValuator);
    alphaChannelTiltAction->setCheckable(true);
    QAction *noAlphaChannelAction = alphaChannelMenu->addAction(tr("No Alpha Channel"));
    noAlphaChannelAction->setData(TabletCanvas::NoValuator);
    noAlphaChannelAction->setCheckable(true);
    QActionGroup *alphaChannelGroup = new QActionGroup(this);
    alphaChannelGroup->addAction(alphaChannelPressureAction);
    alphaChannelGroup->addAction(alphaChannelTangentialPressureAction);
    alphaChannelGroup->addAction(alphaChannelTiltAction);
    alphaChannelGroup->addAction(noAlphaChannelAction);
    connect(alphaChannelGroup, &QActionGroup::triggered,
            this, &MainWindow::setAlphaValuator);
					
					
						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
						
alphaChannelGroup
						
						will then ensure that only one of the actions are checked at any time. The
						
triggered()
						
						signal is emitted from the group when an action is checked, so we connect that to
						
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
						
Valuator
						
						must be a registered metatype, so that it can be inserted into a
						
							QVariant
						
						. That is accomplished by the
						
Q_ENUM
						
						declaration in tabletcanvas.h.)
					
						这里是实现为
						
setAlphaValuator()
						
						:
					
void MainWindow::setAlphaValuator(QAction *action) { m_canvas->setAlphaChannelValuator(action->data().value<TabletCanvas::Valuator>()); }
						It simply needs to retrieve the
						
Valuator
						
						enum from
						
							QAction::data
						
						(), and pass that to
						
TabletCanvas::setAlphaChannelValuator()
						
						. If we were not using the
						
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.
					
						这里是实现为
						
setBrushColor()
						
						:
					
void MainWindow::setBrushColor() { if (!m_colorDialog) { m_colorDialog = new QColorDialog(this); m_colorDialog->setModal(false); m_colorDialog->setCurrentColor(m_canvas->color()); connect(m_colorDialog, &QColorDialog::colorSelected, m_canvas, &TabletCanvas::setColor); } m_colorDialog->setVisible(true); }
						We do lazy initialization of a
						
							QColorDialog
						
						the first time the user chooses
						
							Brush color...
						
						from the menu or via the action shortcut. While the dialog is open, each time the user chooses a different color,
						
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.
					
						这里是实现为
						
save()
						
						:
					
bool MainWindow::save() { QString path = QDir::currentPath() + "/untitled.png"; QString fileName = QFileDialog::getSaveFileName(this, tr("Save Picture"), path); bool success = m_canvas->saveImage(fileName); if (!success) QMessageBox::information(this, "Error Saving Picture", "Could not save the image"); return success; }
						使用
						
							QFileDialog
						
						to let the user select a file to save the drawing, and then call
						
TabletCanvas::saveImage()
						
						to actually write it to the file.
					
						这里是实现为
						
load()
						
						:
					
void MainWindow::load() { QString fileName = QFileDialog::getOpenFileName(this, tr("Open Picture"), QDir::currentPath()); if (!m_canvas->loadImage(fileName)) QMessageBox::information(this, "Error Opening Picture", "Could not open picture"); }
						We let the user select the image file to be opened with a
						
							QFileDialog
						
						; we then ask the canvas to load the image with
						
loadImage()
						
						.
					
						这里是实现为
						
about()
						
						:
					
void MainWindow::about() { QMessageBox::about(this, tr("About Tablet Example"), tr("This example shows how to use a graphics drawing tablet in Qt.")); }
We show a message box with a short description of the example.
						The
						
TabletCanvas
						
						class provides a surface on which the user can draw with a tablet.
					
class TabletCanvas : public QWidget { Q_OBJECT public: enum Valuator { PressureValuator, TangentialPressureValuator, TiltValuator, VTiltValuator, HTiltValuator, NoValuator }; Q_ENUM(Valuator) TabletCanvas(); bool saveImage(const QString &file); bool loadImage(const QString &file); void clear(); void setAlphaChannelValuator(Valuator type) { m_alphaChannelValuator = type; } void setColorSaturationValuator(Valuator type) { m_colorSaturationValuator = type; } void setLineWidthType(Valuator type) { m_lineWidthValuator = type; } void setColor(const QColor &c) { if (c.isValid()) m_color = c; } QColor color() const { return m_color; } void setTabletDevice(QTabletEvent *event) { updateCursor(event); } int maximum(int a, int b) { return a > b ? a : b; } protected: void tabletEvent(QTabletEvent *event) override; void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: void initPixmap(); void paintPixmap(QPainter &painter, QTabletEvent *event); Qt::BrushStyle brushPattern(qreal value); static qreal pressureToWidth(qreal pressure); void updateBrush(const QTabletEvent *event); void updateCursor(const QTabletEvent *event); Valuator m_alphaChannelValuator; Valuator m_colorSaturationValuator; Valuator m_lineWidthValuator; QColor m_color; QPixmap m_pixmap; QBrush m_brush; QPen m_pen; bool m_deviceDown; struct Point { QPointF pos; qreal pressure; qreal rotation; } lastPoint; };
						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:
						
m_alphaChannelValuator
						
						,
						
m_colorSaturationValuator
						
						and
						
m_lineWidthValuator
						
						, and we provide accessor functions for them.
					
						We draw on a
						
							QPixmap
						
						with
						
m_pen
						
						and
						
m_brush
						
						使用
						
m_color
						
						. Each time a
						
							QTabletEvent
						
						is received, the stroke is drawn from
						
lastPoint
						
						to the point given in the current
						
							QTabletEvent
						
						, and then the position and rotation are saved in
						
lastPoint
						
						for next time. The
						
saveImage()
						
						and
						
loadImage()
						
						functions save and load the
						
							QPixmap
						
						to disk. The pixmap is drawn on the widget in
						
paintEvent()
						
						.
					
						The interpretation of events from the tablet is done in
						
tabletEvent()
						
						,和
						
paintPixmap()
						
						,
						
updateBrush()
						
						,和
						
updateCursor()
						
						are helper functions used by
						
tabletEvent()
						
						.
					
从查看构造函数开始:
TabletCanvas::TabletCanvas() : QWidget(nullptr) , m_alphaChannelValuator(TangentialPressureValuator) , m_colorSaturationValuator(NoValuator) , m_lineWidthValuator(PressureValuator) , m_color(Qt::red) , m_brush(m_color) , m_pen(m_brush, 1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) , m_deviceDown(false) { resize(500, 500); setAutoFillBackground(true); setAttribute(Qt::WA_TabletTracking); }
In the constructor we initialize most of our class variables.
						这里是实现为
						
saveImage()
						
						:
					
bool TabletCanvas::saveImage(const QString &file) { return m_pixmap.save(file); }
QPixmap implements functionality to save itself to disk, so we simply call save() .
						这里是实现为
						
loadImage()
						
						:
					
bool TabletCanvas::loadImage(const QString &file) { bool success = m_pixmap.load(file); if (success) { update(); return true; } return false; }
We simply call load() , which loads the image from file .
						这里是实现为
						
tabletEvent()
						
						:
					
void TabletCanvas::tabletEvent(QTabletEvent *event) { switch (event->type()) { case QEvent::TabletPress: if (!m_deviceDown) { m_deviceDown = true; lastPoint.pos = event->posF(); lastPoint.pressure = event->pressure(); lastPoint.rotation = event->rotation(); } break; case QEvent::TabletMove: #ifndef Q_OS_IOS if (event->device() == QTabletEvent::RotationStylus) updateCursor(event); #endif if (m_deviceDown) { updateBrush(event); QPainter painter(&m_pixmap); paintPixmap(painter, event); lastPoint.pos = event->posF(); lastPoint.pressure = event->pressure(); lastPoint.rotation = event->rotation(); } break; case QEvent::TabletRelease: if (m_deviceDown && event->buttons() == Qt::NoButton) m_deviceDown = false; update(); break; default: break; } event->accept(); }
						We get three kind of events to this function:
						
TabletPress
						
						,
						
TabletRelease
						
						,和
						
TabletMove
						
						, which are generated when a drawing tool is pressed down on, lifed up from, or moved across the tablet. We set
						
m_deviceDown
						
						to
						
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
						
updateBrush()
						
						to update
						
m_brush
						
						and
						
m_pen
						
						depending on which of the tablet event properties the user has chosen to pay attention to. The
						
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.
					
void TabletCanvas::updateCursor(const QTabletEvent *event) { QCursor cursor; if (event->type() != QEvent::TabletLeaveProximity) { if (event->pointerType() == QTabletEvent::Eraser) { cursor = QCursor(QPixmap(":/images/cursor-eraser.png"), 3, 28); } else { switch (event->device()) { case QTabletEvent::Stylus: cursor = QCursor(QPixmap(":/images/cursor-pencil.png"), 0, 0); break; case QTabletEvent::Airbrush: cursor = QCursor(QPixmap(":/images/cursor-airbrush.png"), 3, 4); break; case QTabletEvent::RotationStylus: { QImage origImg(QLatin1String(":/images/cursor-felt-marker.png")); QImage img(32, 32, QImage::Format_ARGB32); QColor solid = m_color; solid.setAlpha(255); img.fill(solid); QPainter painter(&img); QTransform transform = painter.transform(); transform.translate(16, 16); transform.rotate(event->rotation()); painter.setTransform(transform); painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); painter.drawImage(-24, -24, origImg); painter.setCompositionMode(QPainter::CompositionMode_HardLight); painter.drawImage(-24, -24, origImg); painter.end(); cursor = QCursor(QPixmap::fromImage(img), 16, 16); } break; default: break; } } } setCursor(cursor); }
						If an art pen (
						
RotationStylus
						
						) is in use,
						
updateCursor()
						
						is also called for each
						
TabletMove
						
						event, and renders a rotated cursor so that you can see the angle of the pen tip.
					
						这里是实现为
						
paintEvent()
						
						:
					
void TabletCanvas::initPixmap() { qreal dpr = devicePixelRatioF(); QPixmap newPixmap = QPixmap(width() * dpr, height() * dpr); newPixmap.setDevicePixelRatio(dpr); newPixmap.fill(Qt::white); QPainter painter(&newPixmap); if (!m_pixmap.isNull()) painter.drawPixmap(0, 0, m_pixmap); painter.end(); m_pixmap = newPixmap; } void TabletCanvas::paintEvent(QPaintEvent *event) { if (m_pixmap.isNull()) initPixmap(); QPainter painter(this); QRect pixmapPortion = QRect(event->rect().topLeft() * devicePixelRatioF(), event->rect().size() * devicePixelRatioF()); painter.drawPixmap(event->rect().topLeft(), m_pixmap, pixmapPortion); }
						The first time Qt calls paintEvent(), m_pixmap is default-constructed, so
						
							QPixmap::isNull
						
						() 返回
						
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
						
							高 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.
						这里是实现为
						
paintPixmap()
						
						:
					
void TabletCanvas::paintPixmap(QPainter &painter, QTabletEvent *event) { static qreal maxPenRadius = pressureToWidth(1.0); painter.setRenderHint(QPainter::Antialiasing); switch (event->device()) { case QTabletEvent::Airbrush: { painter.setPen(Qt::NoPen); QRadialGradient grad(lastPoint.pos, m_pen.widthF() * 10.0); QColor color = m_brush.color(); color.setAlphaF(color.alphaF() * 0.25); grad.setColorAt(0, m_brush.color()); grad.setColorAt(0.5, Qt::transparent); painter.setBrush(grad); qreal radius = grad.radius(); painter.drawEllipse(event->posF(), radius, radius); update(QRect(event->pos() - QPoint(radius, radius), QSize(radius * 2, radius * 2))); } break; case QTabletEvent::RotationStylus: { m_brush.setStyle(Qt::SolidPattern); painter.setPen(Qt::NoPen); painter.setBrush(m_brush); QPolygonF poly; qreal halfWidth = pressureToWidth(lastPoint.pressure); QPointF brushAdjust(qSin(qDegreesToRadians(-lastPoint.rotation)) * halfWidth, qCos(qDegreesToRadians(-lastPoint.rotation)) * halfWidth); poly << lastPoint.pos + brushAdjust; poly << lastPoint.pos - brushAdjust; halfWidth = m_pen.widthF(); brushAdjust = QPointF(qSin(qDegreesToRadians(-event->rotation())) * halfWidth, qCos(qDegreesToRadians(-event->rotation())) * halfWidth); poly << event->posF() - brushAdjust; poly << event->posF() + brushAdjust; painter.drawConvexPolygon(poly); update(poly.boundingRect().toRect()); } break; case QTabletEvent::Puck: case QTabletEvent::FourDMouse: { const QString error(tr("This input device is not supported by the example.")); #if QT_CONFIG(statustip) QStatusTipEvent status(error); QApplication::sendEvent(this, &status); #else qWarning() << error; #endif } break; default: { const QString error(tr("Unknown tablet device - treating as stylus")); #if QT_CONFIG(statustip) QStatusTipEvent status(error); QApplication::sendEvent(this, &status); #else qWarning() << error; #endif } Q_FALLTHROUGH(); case QTabletEvent::Stylus: painter.setPen(m_pen); painter.drawLine(lastPoint.pos, event->posF()); update(QRect(lastPoint.pos.toPoint(), event->pos()).normalized() .adjusted(-maxPenRadius, -maxPenRadius, maxPenRadius, maxPenRadius)); break; } }
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.
        case QTabletEvent::Airbrush:
            {
                painter.setPen(Qt::NoPen);
                QRadialGradient grad(lastPoint.pos, m_pen.widthF() * 10.0);
                QColor color = m_brush.color();
                color.setAlphaF(color.alphaF() * 0.25);
                grad.setColorAt(0, m_brush.color());
                grad.setColorAt(0.5, Qt::transparent);
                painter.setBrush(grad);
                qreal radius = grad.radius();
                painter.drawEllipse(event->posF(), radius, radius);
                update(QRect(event->pos() - QPoint(radius, radius), QSize(radius * 2, radius * 2)));
            }
            break;
        case QTabletEvent::RotationStylus:
            {
                m_brush.setStyle(Qt::SolidPattern);
                painter.setPen(Qt::NoPen);
                painter.setBrush(m_brush);
                QPolygonF poly;
                qreal halfWidth = pressureToWidth(lastPoint.pressure);
                QPointF brushAdjust(qSin(qDegreesToRadians(-lastPoint.rotation)) * halfWidth,
                                    qCos(qDegreesToRadians(-lastPoint.rotation)) * halfWidth);
                poly << lastPoint.pos + brushAdjust;
                poly << lastPoint.pos - brushAdjust;
                halfWidth = m_pen.widthF();
                brushAdjust = QPointF(qSin(qDegreesToRadians(-event->rotation())) * halfWidth,
                                      qCos(qDegreesToRadians(-event->rotation())) * halfWidth);
                poly << event->posF() - brushAdjust;
                poly << event->posF() + brushAdjust;
                painter.drawConvexPolygon(poly);
                update(poly.boundingRect().toRect());
            }
            break;
					
					
						在
						
updateBrush()
						
						we set the pen and brush used for drawing to match
						
m_alphaChannelValuator
						
						,
						
m_lineWidthValuator
						
						,
						
m_colorSaturationValuator
						
						,和
						
m_color
						
						. We will examine the code to set up
						
m_brush
						
						and
						
m_pen
						
						for each of these variables:
					
void TabletCanvas::updateBrush(const QTabletEvent *event) { int hue, saturation, value, alpha; m_color.getHsv(&hue, &saturation, &value, &alpha); int vValue = int(((event->yTilt() + 60.0) / 120.0) * 255); int hValue = int(((event->xTilt() + 60.0) / 120.0) * 255);
						We fetch the current drawingcolor's hue, saturation, value, and alpha values.
						
hValue
						
						and
						
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).
					
    switch (m_alphaChannelValuator) {
        case PressureValuator:
            m_color.setAlphaF(event->pressure());
            break;
        case TangentialPressureValuator:
            if (event->device() == QTabletEvent::Airbrush)
                m_color.setAlphaF(qMax(0.01, (event->tangentialPressure() + 1.0) / 2.0));
            else
                m_color.setAlpha(255);
            break;
        case TiltValuator:
            m_color.setAlpha(maximum(abs(vValue - 127), abs(hValue - 127)));
            break;
        default:
            m_color.setAlpha(255);
    }
					
					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. 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.
    switch (m_colorSaturationValuator) {
        case VTiltValuator:
            m_color.setHsv(hue, vValue, value, alpha);
            break;
        case HTiltValuator:
            m_color.setHsv(hue, hValue, value, alpha);
            break;
        case PressureValuator:
            m_color.setHsv(hue, int(event->pressure() * 255.0), value, alpha);
            break;
        default:
            ;
    }
					
					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 setHsv() with integer values. That means we need to multiply the pressure to a number between 0 and 255.
    switch (m_lineWidthValuator) {
        case PressureValuator:
            m_pen.setWidthF(pressureToWidth(event->pressure()));
            break;
        case TiltValuator:
            m_pen.setWidthF(maximum(abs(vValue - 127), abs(hValue - 127)) / 12);
            break;
        default:
            m_pen.setWidthF(1);
    }
					
					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.
    if (event->pointerType() == QTabletEvent::Eraser) {
        m_brush.setColor(Qt::white);
        m_pen.setColor(Qt::white);
        m_pen.setWidthF(event->pressure() * 10 + 1);
    } else {
        m_brush.setColor(m_color);
        m_pen.setColor(m_color);
    }
}
					
					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.
继承 QApplication in this class because we want to reimplement the event() 函数。
class TabletApplication : public QApplication { Q_OBJECT public: TabletApplication(int &argv, char **args) : QApplication(argv, args) {} bool event(QEvent *event) override; void setCanvas(TabletCanvas *canvas) { m_canvas = canvas; } private: TabletCanvas *m_canvas; };
						
TabletApplication
						
						exists as a subclass of
						
							QApplication
						
						in order to receive tablet proximity events and forward them to
						
TabletCanvas
						
						。
						
TabletEnterProximity
						
						and
						
TabletLeaveProximity
						
						events are sent to the
						
							QApplication
						
						object, while other tablet events are sent to the
						
							QWidget
						
						's
						
event()
						
						hander, which sends them on to
						
							tabletEvent()
						
						.
					
						这里是实现为
						
event()
						
						:
					
bool TabletApplication::event(QEvent *event) { if (event->type() == QEvent::TabletEnterProximity || event->type() == QEvent::TabletLeaveProximity) { m_canvas->setTabletDevice(static_cast<QTabletEvent *>(event)); return true; } return QApplication::event(event); }
						We use this function to handle the
						
TabletEnterProximity
						
						and
						
TabletLeaveProximity
						
						events, which are generated when a drawing tool enters or leaves the proximity of the tablet. Here we call
						
TabletCanvas::setTabletDevice()
						
						, which then calls
						
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
						
TabletCanvas
						
						to observe the
						
							device()
						
						and
						
							pointerType()
						
						in each event that it receives.
					
main()
						
						function
						
					
						Here is the example's
						
main()
						
						函数:
					
int main(int argv, char *args[]) { TabletApplication app(argv, args); TabletCanvas *canvas = new TabletCanvas; app.setCanvas(canvas); MainWindow mainWindow(canvas); mainWindow.resize(500, 500); mainWindow.show(); return app.exec(); }
						Here we create a
						
MainWindow
						
						and display it as a top level window. We use the
						
TabletApplication
						
						class. We need to set the canvas after the application is created. We cannot use classes that implement event handling before an
						
							QApplication
						
						对象被实例化。
					
文件:
图像: