You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/krita/ui/kis_canvas.cc

1356 lines
42 KiB

/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2004-2006 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.g
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
Some of the X11-specific event handling code is based upon code from
src/kernel/qapplication_x11.cpp from the Qt GUI Toolkit and is subject
to the following license and copyright:
****************************************************************************
**
**
** Implementation of X11 startup routines and event handling
**
** Created : 931029
**
** Copyright (C) 1992-2003 Trolltech AS. All rights reserved.
**
** This file is part of the kernel module of the Qt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
** licenses for Unix/X11 may use this file in accordance with the Qt Commercial
** License Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
** information about Qt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for QPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/
#include <qcursor.h>
#include "kis_canvas.h"
#include "kis_cursor.h"
#include "kis_move_event.h"
#include "kis_button_press_event.h"
#include "kis_button_release_event.h"
#include "kis_double_click_event.h"
#include "kis_config.h"
#include "kis_qpaintdevice_canvas.h"
#include "kis_opengl_canvas.h"
#include "kis_config.h"
#include "kis_input_device.h"
#include "fixx11h.h"
#ifdef Q_WS_X11
#include <qdesktopwidget.h>
#include <qapplication.h>
#include <X11/keysym.h>
bool KisCanvasWidget::X11SupportInitialised = false;
long KisCanvasWidget::X11AltMask = 0;
long KisCanvasWidget::X11MetaMask = 0;
#if defined(EXTENDED_X11_TABLET_SUPPORT)
int KisCanvasWidget::X11DeviceMotionNotifyEvent = -1;
int KisCanvasWidget::X11DeviceButtonPressEvent = -1;
int KisCanvasWidget::X11DeviceButtonReleaseEvent = -1;
int KisCanvasWidget::X11ProximityInEvent = -1;
int KisCanvasWidget::X11ProximityOutEvent = -1;
//X11XIDTabletDeviceMap KisCanvasWidget::X11TabletDeviceMap;
std::map<XID, KisCanvasWidget::X11TabletDevice> KisCanvasWidget::X11TabletDeviceMap;
#endif // EXTENDED_X11_TABLET_SUPPORT
#endif // Q_WS_X11
KisCanvasWidget::KisCanvasWidget()
{
m_enableMoveEventCompressionHint = false;
m_lastPressure = 0;
#ifdef Q_WS_X11
if (!X11SupportInitialised) {
initX11Support();
}
m_lastRootX = -1;
m_lastRootY = -1;
#endif
}
KisCanvasWidget::~KisCanvasWidget()
{
}
void KisCanvasWidget::widgetGotPaintEvent(QPaintEvent *e)
{
emit sigGotPaintEvent(e);
}
void KisCanvasWidget::widgetGotMousePressEvent(QMouseEvent *e)
{
KisButtonPressEvent ke(KisInputDevice::mouse(), e->pos(), e->globalPos(), PRESSURE_DEFAULT, 0, 0, e->button(), e->state());
buttonPressEvent(&ke);
}
void KisCanvasWidget::widgetGotMouseReleaseEvent(QMouseEvent *e)
{
KisButtonReleaseEvent ke(KisInputDevice::mouse(), e->pos(), e->globalPos(), PRESSURE_DEFAULT, 0, 0, e->button(), e->state());
buttonReleaseEvent(&ke);
}
void KisCanvasWidget::widgetGotMouseDoubleClickEvent(QMouseEvent *e)
{
KisDoubleClickEvent ke(KisInputDevice::mouse(), e->pos(), e->globalPos(), PRESSURE_DEFAULT, 0, 0, e->button(), e->state());
doubleClickEvent(&ke);
}
void KisCanvasWidget::widgetGotMouseMoveEvent(QMouseEvent *e)
{
KisMoveEvent ke(KisInputDevice::mouse(), e->pos(), e->globalPos(), PRESSURE_DEFAULT, 0, 0, e->state());
moveEvent(&ke);
}
void KisCanvasWidget::widgetGotTabletEvent(QTabletEvent *e)
{
KisInputDevice device;
switch (e->device()) {
default:
case QTabletEvent::NoDevice:
case QTabletEvent::Stylus:
device = KisInputDevice::stylus();
break;
case QTabletEvent::Puck:
device = KisInputDevice::puck();
break;
case QTabletEvent::Eraser:
device = KisInputDevice::eraser();
break;
}
double pressure = e->pressure() / 255.0;
if (e->type() == QEvent::TabletPress) {
KisButtonPressEvent ke(device, e->pos(), e->globalPos(), pressure, e->xTilt(), e->yTilt(), Qt::LeftButton, Qt::NoButton);
translateTabletEvent(&ke);
}
else
if (e->type() == QEvent::TabletRelease) {
KisButtonReleaseEvent ke(device, e->pos(), e->globalPos(), pressure, e->xTilt(), e->yTilt(), Qt::LeftButton, Qt::NoButton);
translateTabletEvent(&ke);
}
else {
KisMoveEvent ke(device, e->pos(), e->globalPos(), pressure, e->xTilt(), e->yTilt(), Qt::NoButton);
translateTabletEvent(&ke);
#ifdef Q_WS_X11
// Fix the problem that when you change from using a tablet device to the mouse,
// the first mouse button event is not recognised. This is because we handle
// X11 core mouse move events directly so Qt does not get to see them. This breaks
// the tablet event accept/ignore mechanism, causing Qt to consume the first
// mouse button event it sees, instead of a mouse move. 'Ignoring' tablet move events
// stops Qt from stealing the next mouse button event. This does not affect the
// tablet aware tools as they do not care about mouse moves while the tablet device is
// drawing.
e->ignore();
#endif
}
}
void KisCanvasWidget::widgetGotEnterEvent(QEvent *e)
{
emit sigGotEnterEvent(e);
}
void KisCanvasWidget::widgetGotLeaveEvent(QEvent *e)
{
emit sigGotLeaveEvent(e);
}
void KisCanvasWidget::widgetGotWheelEvent(QWheelEvent *e)
{
emit sigGotMouseWheelEvent(e);
}
void KisCanvasWidget::widgetGotKeyPressEvent(QKeyEvent *e)
{
emit sigGotKeyPressEvent(e);
}
void KisCanvasWidget::widgetGotKeyReleaseEvent(QKeyEvent *e)
{
emit sigGotKeyReleaseEvent(e);
}
void KisCanvasWidget::widgetGotDragEnterEvent(QDragEnterEvent *e)
{
emit sigGotDragEnterEvent(e);
}
void KisCanvasWidget::widgetGotDropEvent(QDropEvent *e)
{
emit sigGotDropEvent(e);
}
void KisCanvasWidget::moveEvent(KisMoveEvent *e)
{
emit sigGotMoveEvent(e);
}
void KisCanvasWidget::buttonPressEvent(KisButtonPressEvent *e)
{
QWidget *widget = dynamic_cast<QWidget *>(this);
Q_ASSERT(widget != 0);
if (widget) {
widget->setFocus();
}
emit sigGotButtonPressEvent(e);
}
void KisCanvasWidget::buttonReleaseEvent(KisButtonReleaseEvent *e)
{
emit sigGotButtonReleaseEvent(e);
}
void KisCanvasWidget::doubleClickEvent(KisDoubleClickEvent *e)
{
emit sigGotDoubleClickEvent(e);
}
void KisCanvasWidget::translateTabletEvent(KisEvent *e)
{
bool checkThresholdOnly = false;
if (e->type() == KisEvent::ButtonPressEvent || e->type() == KisEvent::ButtonReleaseEvent) {
KisButtonEvent *b = static_cast<KisButtonEvent *>(e);
if (b->button() == Qt::MidButton || b->button() == Qt::RightButton) {
if (e->type() == KisEvent::ButtonPressEvent) {
buttonPressEvent(static_cast<KisButtonPressEvent *>(e));
} else {
buttonReleaseEvent(static_cast<KisButtonReleaseEvent *>(e));
}
checkThresholdOnly = true;
}
}
// Use pressure threshold to detect 'left button' press/release
if (e->pressure() >= PRESSURE_THRESHOLD && m_lastPressure < PRESSURE_THRESHOLD) {
KisButtonPressEvent ke(e->device(), e->pos(), e->globalPos(), e->pressure(), e->xTilt(), e->yTilt(), Qt::LeftButton, e->state());
buttonPressEvent(&ke);
} else if (e->pressure() < PRESSURE_THRESHOLD && m_lastPressure >= PRESSURE_THRESHOLD) {
KisButtonReleaseEvent ke(e->device(), e->pos(), e->globalPos(), e->pressure(), e->xTilt(), e->yTilt(), Qt::LeftButton, e->state());
buttonReleaseEvent(&ke);
} else {
if (!checkThresholdOnly) {
KisMoveEvent ke(e->device(), e->pos(), e->globalPos(), e->pressure(), e->xTilt(), e->yTilt(), e->state());
moveEvent(&ke);
}
}
m_lastPressure = e->pressure();
}
#ifdef Q_WS_X11
void KisCanvasWidget::initX11Support()
{
if (X11SupportInitialised)
{
return;
}
X11SupportInitialised = true;
Display *x11Display = QApplication::desktop()->x11Display();
// Look at the modifier mapping and get the correct masks for alt/meta
XModifierKeymap *map = XGetModifierMapping(x11Display);
if (map) {
int mapIndex = 0;
for (int maskIndex = 0; maskIndex < 8; maskIndex++) {
for (int i = 0; i < map->max_keypermod; i++) {
if (map->modifiermap[mapIndex]) {
KeySym sym = XKeycodeToKeysym(x11Display, map->modifiermap[mapIndex], 0);
if (X11AltMask == 0 && (sym == XK_Alt_L || sym == XK_Alt_R)) {
X11AltMask = 1 << maskIndex;
}
if (X11MetaMask == 0 && (sym == XK_Meta_L || sym == XK_Meta_R)) {
X11MetaMask = 1 << maskIndex;
}
}
mapIndex++;
}
}
XFreeModifiermap(map);
}
else {
// Assume defaults
X11AltMask = Mod1Mask;
X11MetaMask = Mod4Mask;
}
#if defined(EXTENDED_X11_TABLET_SUPPORT)
int numDevices = 0;
const XDeviceInfo *devices = XListInputDevices(x11Display, &numDevices);
if (devices != NULL) {
XID lastStylusSeen = 0;
XID lastEraserSeen = 0;
bool foundStylus = false;
bool foundEraser = false;
for (int i = 0; i < numDevices; i++) {
const XDeviceInfo *device = devices + i;
X11TabletDevice tabletDevice(device);
if (tabletDevice.mightBeTabletDevice()) {
tabletDevice.readSettingsFromConfig();
QString lowerCaseName = tabletDevice.name().lower();
// Find the devices that Qt will use as its stylus and eraser devices.
if (!foundStylus || !foundEraser) {
if (lowerCaseName.startsWith("stylus") || lowerCaseName.startsWith("pen")) {
lastStylusSeen = device->id;
foundStylus = true;
}
else if (lowerCaseName.startsWith("eraser")) {
lastEraserSeen = device->id;
foundEraser = true;
}
}
X11TabletDeviceMap[device->id] = tabletDevice;
// Event types are device-independent. Store any
// the device supports.
if (tabletDevice.buttonPressEvent() >= 0) {
X11DeviceButtonPressEvent = tabletDevice.buttonPressEvent();
}
if (tabletDevice.buttonReleaseEvent() >= 0) {
X11DeviceButtonReleaseEvent = tabletDevice.buttonReleaseEvent();
}
if (tabletDevice.motionNotifyEvent() >= 0) {
X11DeviceMotionNotifyEvent = tabletDevice.motionNotifyEvent();
}
if (tabletDevice.proximityInEvent() >= 0) {
X11ProximityInEvent = tabletDevice.proximityInEvent();
}
if (tabletDevice.proximityOutEvent() >= 0) {
X11ProximityOutEvent = tabletDevice.proximityOutEvent();
}
}
}
// Allocate input devices.
for (X11XIDTabletDeviceMap::iterator it = X11TabletDeviceMap.begin(); it != X11TabletDeviceMap.end(); ++it) {
X11TabletDevice& tabletDevice = (*it).second;
if (foundStylus && tabletDevice.id() == lastStylusSeen) {
tabletDevice.setInputDevice(KisInputDevice::stylus());
} else if (foundEraser && tabletDevice.id() == lastEraserSeen) {
tabletDevice.setInputDevice(KisInputDevice::eraser());
} else {
tabletDevice.setInputDevice(KisInputDevice::allocateInputDevice());
}
}
XFreeDeviceList(const_cast<XDeviceInfo *>(devices));
}
#endif // EXTENDED_X11_TABLET_SUPPORT
}
Qt::ButtonState KisCanvasWidget::translateX11ButtonState(int state)
{
int buttonState = 0;
if (state & Button1Mask)
buttonState |= Qt::LeftButton;
if (state & Button2Mask)
buttonState |= Qt::MidButton;
if (state & Button3Mask)
buttonState |= Qt::RightButton;
if (state & ShiftMask)
buttonState |= Qt::ShiftButton;
if (state & ControlMask)
buttonState |= Qt::ControlButton;
if (state & X11AltMask)
buttonState |= Qt::AltButton;
if (state & X11MetaMask)
buttonState |= Qt::MetaButton;
return static_cast<Qt::ButtonState>(buttonState);
}
Qt::ButtonState KisCanvasWidget::translateX11Button(unsigned int X11Button)
{
Qt::ButtonState qtButton;
switch (X11Button) {
case Button1:
qtButton = Qt::LeftButton;
break;
case Button2:
qtButton = Qt::MidButton;
break;
case Button3:
qtButton = Qt::RightButton;
break;
default:
qtButton = Qt::NoButton;
}
return qtButton;
}
#if defined(EXTENDED_X11_TABLET_SUPPORT)
KisCanvasWidget::X11TabletDevice::X11TabletDevice()
{
m_mightBeTabletDevice = false;
m_inputDevice = KisInputDevice::unknown();
m_enabled = false;
m_xAxis = NoAxis;
m_yAxis = NoAxis;
m_pressureAxis = NoAxis;
m_xTiltAxis = NoAxis;
m_yTiltAxis = NoAxis;
m_wheelAxis = NoAxis;
m_toolIDAxis = NoAxis;
m_serialNumberAxis = NoAxis;
m_buttonPressEvent = -1;
m_buttonReleaseEvent = -1;
m_motionNotifyEvent = -1;
m_proximityInEvent = -1;
m_proximityOutEvent = -1;
}
KisCanvasWidget::X11TabletDevice::X11TabletDevice(const XDeviceInfo *deviceInfo)
{
m_mightBeTabletDevice = false;
m_inputDevice = KisInputDevice::unknown();
m_enabled = false;
m_xAxis = NoAxis;
m_yAxis = NoAxis;
m_pressureAxis = NoAxis;
m_xTiltAxis = NoAxis;
m_yTiltAxis = NoAxis;
m_wheelAxis = NoAxis;
m_toolIDAxis = NoAxis;
m_serialNumberAxis = NoAxis;
m_deviceId = deviceInfo->id;
m_name = deviceInfo->name;
// Get the ranges of the valuators
XAnyClassPtr classInfo = const_cast<XAnyClassPtr>(deviceInfo->inputclassinfo);
for (int i = 0; i < deviceInfo->num_classes; i++) {
if (classInfo->c_class == ValuatorClass) {
const XValuatorInfo *valuatorInfo = reinterpret_cast<const XValuatorInfo *>(classInfo);
// Need at least x, y, and pressure.
if (valuatorInfo->num_axes >= 3) {
for (unsigned int axis = 0; axis < valuatorInfo->num_axes; axis++) {
m_axisInfo.append(valuatorInfo->axes[axis]);
}
m_mightBeTabletDevice = true;
}
}
classInfo = reinterpret_cast<XAnyClassPtr>(reinterpret_cast<char *>(classInfo) + classInfo->length);
}
// Determine the event types it supports. We're only interested in
// buttons and motion at the moment.
m_buttonPressEvent = -1;
m_buttonReleaseEvent = -1;
m_motionNotifyEvent = -1;
m_proximityInEvent = -1;
m_proximityOutEvent = -1;
m_XDevice = XOpenDevice(QApplication::desktop()->x11Display(), m_deviceId);
if (m_XDevice != NULL) {
for (int i = 0; i < m_XDevice->num_classes; i++) {
XEventClass eventClass;
if (m_XDevice->classes[i].input_class == ButtonClass) {
DeviceButtonPress(m_XDevice, m_buttonPressEvent, eventClass);
m_eventClassList.append(eventClass);
DeviceButtonRelease(m_XDevice, m_buttonReleaseEvent, eventClass);
m_eventClassList.append(eventClass);
}
else
if (m_XDevice->classes[i].input_class == ValuatorClass) {
DeviceMotionNotify(m_XDevice, m_motionNotifyEvent, eventClass);
m_eventClassList.append(eventClass);
}
else
if (m_XDevice->classes[i].input_class == ProximityClass) {
ProximityIn(m_XDevice, m_proximityInEvent, eventClass);
m_eventClassList.append(eventClass);
ProximityOut(m_XDevice, m_proximityOutEvent, eventClass);
m_eventClassList.append(eventClass);
}
}
// Note: We don't XCloseXDevice() since Qt will have already opened
// it, and only one XCloseDevice() call closes it for all opens.
}
if (m_buttonPressEvent == -1 || m_buttonReleaseEvent == -1 || m_motionNotifyEvent == -1) {
m_mightBeTabletDevice = false;
}
}
void KisCanvasWidget::X11TabletDevice::setEnabled(bool enabled)
{
m_enabled = enabled;
}
bool KisCanvasWidget::X11TabletDevice::enabled() const
{
return m_enabled;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::numAxes() const
{
return m_axisInfo.count();
}
void KisCanvasWidget::X11TabletDevice::setXAxis(Q_INT32 axis)
{
m_xAxis = axis;
}
void KisCanvasWidget::X11TabletDevice::setYAxis(Q_INT32 axis)
{
m_yAxis = axis;
}
void KisCanvasWidget::X11TabletDevice::setPressureAxis(Q_INT32 axis)
{
m_pressureAxis = axis;
}
void KisCanvasWidget::X11TabletDevice::setXTiltAxis(Q_INT32 axis)
{
m_xTiltAxis = axis;
}
void KisCanvasWidget::X11TabletDevice::setYTiltAxis(Q_INT32 axis)
{
m_yTiltAxis = axis;
}
void KisCanvasWidget::X11TabletDevice::setWheelAxis(Q_INT32 axis)
{
m_wheelAxis = axis;
}
void KisCanvasWidget::X11TabletDevice::setToolIDAxis(Q_INT32 axis)
{
m_toolIDAxis = axis;
}
void KisCanvasWidget::X11TabletDevice::setSerialNumberAxis(Q_INT32 axis)
{
m_serialNumberAxis = axis;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::xAxis() const
{
return m_xAxis;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::yAxis() const
{
return m_yAxis;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::pressureAxis() const
{
return m_pressureAxis;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::xTiltAxis() const
{
return m_xTiltAxis;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::yTiltAxis() const
{
return m_yTiltAxis;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::wheelAxis() const
{
return m_wheelAxis;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::toolIDAxis() const
{
return m_toolIDAxis;
}
Q_INT32 KisCanvasWidget::X11TabletDevice::serialNumberAxis() const
{
return m_serialNumberAxis;
}
void KisCanvasWidget::X11TabletDevice::readSettingsFromConfig()
{
KisConfig cfg;
m_enabled = cfg.tabletDeviceEnabled(m_name);
m_xAxis = cfg.tabletDeviceAxis(m_name, "XAxis", DefaultAxis);
m_yAxis = cfg.tabletDeviceAxis(m_name, "YAxis", DefaultAxis);
m_pressureAxis = cfg.tabletDeviceAxis(m_name, "PressureAxis", DefaultAxis);
m_xTiltAxis = cfg.tabletDeviceAxis(m_name, "XTiltAxis", DefaultAxis);
m_yTiltAxis = cfg.tabletDeviceAxis(m_name, "YTiltAxis", DefaultAxis);
m_wheelAxis = cfg.tabletDeviceAxis(m_name, "WheelAxis", DefaultAxis);
m_toolIDAxis = cfg.tabletDeviceAxis(m_name, "ToolIDAxis", DefaultAxis);
m_serialNumberAxis = cfg.tabletDeviceAxis(m_name, "SerialNumberAxis", DefaultAxis);
if (!m_enabled && m_xAxis == DefaultAxis && m_yAxis == DefaultAxis && m_pressureAxis == DefaultAxis &&
m_xTiltAxis == DefaultAxis && m_yTiltAxis == DefaultAxis && m_wheelAxis == DefaultAxis &&
m_toolIDAxis == DefaultAxis && m_serialNumberAxis == DefaultAxis) {
// This is the first time this device has been seen. Set up default values, assuming
// it's a Wacom pad.
m_xAxis = 0;
m_yAxis = 1;
m_pressureAxis = 2;
if (m_axisInfo.count() >= 4) {
m_xTiltAxis = 3;
} else {
m_xTiltAxis = NoAxis;
}
if (m_axisInfo.count() >= 5) {
m_yTiltAxis = 4;
} else {
m_yTiltAxis = NoAxis;
}
if (m_axisInfo.count() >= 6) {
m_wheelAxis = 5;
} else {
m_wheelAxis = NoAxis;
}
// Available since driver version 0.7.2.
if (m_axisInfo.count() >= 7) {
m_toolIDAxis = 6;
} else {
m_toolIDAxis = NoAxis;
}
if (m_axisInfo.count() >= 8) {
m_serialNumberAxis = 7;
} else {
m_serialNumberAxis = NoAxis;
}
}
}
void KisCanvasWidget::X11TabletDevice::writeSettingsToConfig()
{
KisConfig cfg;
cfg.setTabletDeviceEnabled(m_name, m_enabled);
cfg.setTabletDeviceAxis(m_name, "XAxis", m_xAxis);
cfg.setTabletDeviceAxis(m_name, "YAxis", m_yAxis);
cfg.setTabletDeviceAxis(m_name, "PressureAxis", m_pressureAxis);
cfg.setTabletDeviceAxis(m_name, "XTiltAxis", m_xTiltAxis);
cfg.setTabletDeviceAxis(m_name, "YTiltAxis", m_yTiltAxis);
cfg.setTabletDeviceAxis(m_name, "WheelAxis", m_wheelAxis);
cfg.setTabletDeviceAxis(m_name, "ToolIDAxis", m_toolIDAxis);
cfg.setTabletDeviceAxis(m_name, "SerialNumberAxis", m_serialNumberAxis);
}
void KisCanvasWidget::X11TabletDevice::enableEvents(QWidget *widget) const
{
if (!m_eventClassList.isEmpty()) {
int result = XSelectExtensionEvent(widget->x11AppDisplay(), widget->handle(),
const_cast<XEventClass*>(&m_eventClassList[0]),
m_eventClassList.count());
if (result != Success) {
kdDebug(41001) << "Failed to select extension events for " << m_name << endl;
}
}
}
double KisCanvasWidget::X11TabletDevice::translateAxisValue(int value, const XAxisInfo& axisInfo) const
{
int axisRange = axisInfo.max_value - axisInfo.min_value;
double translatedValue = 0;
if (axisRange != 0) {
translatedValue = (static_cast<double>(value) - axisInfo.min_value) / axisRange;
if (axisInfo.min_value < 0) {
translatedValue -= 0.5;
}
}
return translatedValue;
}
KisCanvasWidget::X11TabletDevice::State::State(const KisPoint& pos, double pressure, const KisVector2D& tilt, double wheel,
Q_UINT32 toolID, Q_UINT32 serialNumber)
: m_pos(pos),
m_pressure(pressure),
m_tilt(tilt),
m_wheel(wheel),
m_toolID(toolID),
m_serialNumber(serialNumber)
{
}
KisCanvasWidget::X11TabletDevice::State KisCanvasWidget::X11TabletDevice::translateAxisData(const int *axisData) const
{
KisPoint pos(0, 0);
if (m_xAxis != NoAxis && m_yAxis != NoAxis) {
pos = KisPoint(translateAxisValue(axisData[m_xAxis], m_axisInfo[m_xAxis]),
translateAxisValue(axisData[m_yAxis], m_axisInfo[m_yAxis]));
}
double pressure = PRESSURE_DEFAULT;
if (m_pressureAxis != NoAxis) {
pressure = translateAxisValue(axisData[m_pressureAxis], m_axisInfo[m_pressureAxis]);
}
KisVector2D tilt = KisVector2D(0, 0);
Q_UINT32 toolID = 0;
Q_UINT32 serialNumber = 0;
if (m_xTiltAxis != NoAxis) {
// Latest wacom driver returns the tool id and serial number in
// the upper 16 bits of the x and y tilts and wheel.
int xTiltAxisValue = (Q_INT16)(axisData[m_xTiltAxis] & 0xffff);
toolID = ((Q_UINT32)axisData[m_xTiltAxis] >> 16) & 0xffff;
tilt.setX(translateAxisValue(xTiltAxisValue, m_axisInfo[m_xTiltAxis]));
}
if (m_yTiltAxis != NoAxis) {
int yTiltAxisValue = (Q_INT16)(axisData[m_yTiltAxis] & 0xffff);
serialNumber = (Q_UINT32)axisData[m_yTiltAxis] & 0xffff0000;
tilt.setY(translateAxisValue(yTiltAxisValue, m_axisInfo[m_yTiltAxis]));
}
double wheel = 0;
if (m_wheelAxis != NoAxis) {
int wheelAxisValue = (Q_INT16)(axisData[m_wheelAxis] & 0xffff);
serialNumber |= ((Q_UINT32)axisData[m_wheelAxis] >> 16) & 0xffff;
wheel = translateAxisValue(wheelAxisValue, m_axisInfo[m_wheelAxis]);
}
//QString ids;
//ids.sprintf("Tool ID: %8x Serial Number: %8x", toolID, serialNumber);
return State(pos, pressure, tilt, wheel, toolID, serialNumber);
}
KisCanvasWidget::X11XIDTabletDeviceMap& KisCanvasWidget::tabletDeviceMap()
{
return X11TabletDeviceMap;
}
void KisCanvasWidget::selectTabletDeviceEvents(QWidget *widget)
{
for (X11XIDTabletDeviceMap::const_iterator it = X11TabletDeviceMap.begin(); it != X11TabletDeviceMap.end(); ++it) {
const X11TabletDevice& device = (*it).second;
if (device.enabled()) {
device.enableEvents(widget);
}
}
}
#endif // EXTENDED_X11_TABLET_SUPPORT
bool KisCanvasWidget::x11Event(XEvent *event, Display *x11Display, WId winId, QPoint widgetOriginPos)
{
if (event->type == MotionNotify) {
// Mouse move
if (!m_enableMoveEventCompressionHint) {
XMotionEvent motion = event->xmotion;
QPoint globalPos(motion.x_root, motion.y_root);
if (globalPos.x() != m_lastRootX || globalPos.y() != m_lastRootY) {
int state = translateX11ButtonState(motion.state);
QPoint pos(motion.x, motion.y);
QMouseEvent e(QEvent::MouseMove, pos, globalPos, Qt::NoButton, state);
widgetGotMouseMoveEvent(&e);
}
m_lastRootX = globalPos.x();
m_lastRootY = globalPos.y();
return true;
}
else {
return false;
}
}
else
#if defined(EXTENDED_X11_TABLET_SUPPORT)
if (event->type == X11DeviceMotionNotifyEvent || event->type == X11DeviceButtonPressEvent || event->type == X11DeviceButtonReleaseEvent) {
// Tablet event.
int deviceId;
const int *axisData;
Qt::ButtonState button;
Qt::ButtonState buttonState;
if (event->type == X11DeviceMotionNotifyEvent) {
// Tablet move
const XDeviceMotionEvent *motion = reinterpret_cast<const XDeviceMotionEvent *>(event);
XEvent mouseEvent;
// Look for an accompanying core event.
if (XCheckTypedWindowEvent(x11Display, winId, MotionNotify, &mouseEvent)) {
if (motion->time == mouseEvent.xmotion.time) {
// Do nothing
} else {
XPutBackEvent(x11Display, &mouseEvent);
}
}
if (m_enableMoveEventCompressionHint) {
while (true) {
// Look for another motion notify in the queue and skip
// to that if found.
if (!XCheckTypedWindowEvent(x11Display, winId, X11DeviceMotionNotifyEvent, &mouseEvent)) {
break;
}
motion = reinterpret_cast<const XDeviceMotionEvent *>(&mouseEvent);
XEvent coreMotionEvent;
// Look for an accompanying core event.
if (!XCheckTypedWindowEvent(x11Display, winId, MotionNotify, &coreMotionEvent)) {
// Do nothing
}
}
}
deviceId = motion->deviceid;
axisData = motion->axis_data;
button = Qt::NoButton;
buttonState = translateX11ButtonState(motion->state);
}
else
if (event->type == X11DeviceButtonPressEvent) {
// Tablet button press
const XDeviceButtonPressedEvent *buttonPressed = reinterpret_cast<const XDeviceButtonPressedEvent *>(event);
deviceId = buttonPressed->deviceid;
axisData = buttonPressed->axis_data;
button = translateX11Button(buttonPressed->button);
buttonState = translateX11ButtonState(buttonPressed->state);
if (QApplication::activePopupWidget() == 0) {
XEvent mouseEvent;
// Look for and swallow an accompanying core event, but only if there's
// no active popup, as that needs to see it.
if (XCheckTypedWindowEvent(x11Display, winId, ButtonPress, &mouseEvent)) {
if (buttonPressed->time == mouseEvent.xbutton.time) {
// Do nothing
}
else {
XPutBackEvent(x11Display, &mouseEvent);
}
}
}
}
else {
// Tablet button release
const XDeviceButtonReleasedEvent *buttonReleased = reinterpret_cast<const XDeviceButtonReleasedEvent *>(event);
deviceId = buttonReleased->deviceid;
axisData = buttonReleased->axis_data;
button = translateX11Button(buttonReleased->button);
buttonState = translateX11ButtonState(buttonReleased->state);
if (QApplication::activePopupWidget() == 0) {
XEvent mouseEvent;
// Look for and swallow an accompanying core event, but only if there's
// no active popup, as that needs to see it.
if (XCheckTypedWindowEvent(x11Display, winId, ButtonRelease, &mouseEvent)) {
if (buttonReleased->time == mouseEvent.xbutton.time) {
// Do nothing
}
else {
XPutBackEvent(x11Display, &mouseEvent);
}
}
}
}
X11XIDTabletDeviceMap::const_iterator it = X11TabletDeviceMap.find(deviceId);
if (it != X11TabletDeviceMap.end()) {
const X11TabletDevice& tabletDevice = (*it).second;
if (tabletDevice.enabled()) {
X11TabletDevice::State deviceState = tabletDevice.translateAxisData(axisData);
// Map normalised position coordinates to screen coordinates
QDesktopWidget *desktop = QApplication::desktop();
KisPoint globalPos(deviceState.pos().x() * desktop->width(), deviceState.pos().y() * desktop->height());
// Convert screen coordinates to widget coordinates
KisPoint pos = globalPos - KoPoint( widgetOriginPos );
// Map tilt to -60 - +60 degrees
KisVector2D tilt(deviceState.tilt().x() * 60, deviceState.tilt().y() * 60);
if (event->type == X11DeviceMotionNotifyEvent) {
KisMoveEvent e(tabletDevice.inputDevice(), pos, globalPos, deviceState.pressure(), tilt.x(), tilt.y(), buttonState);
translateTabletEvent(&e);
}
else
if (event->type == X11DeviceButtonPressEvent) {
KisButtonPressEvent e(tabletDevice.inputDevice(), pos, globalPos, deviceState.pressure(), tilt.x(), tilt.y(), button, buttonState);
translateTabletEvent(&e);
}
else {
KisButtonReleaseEvent e(tabletDevice.inputDevice(), pos, globalPos, deviceState.pressure(), tilt.x(), tilt.y(), button, buttonState);
translateTabletEvent(&e);
}
}
// Consume the event even if the device is disabled otherwise Qt will
// process it and send a QTabletEvent.
return true;
}
else {
return false;
}
}
else
#endif // EXTENDED_X11_TABLET_SUPPORT
{
return false;
}
}
#if defined(EXTENDED_X11_TABLET_SUPPORT)
KisInputDevice KisCanvasWidget::findActiveInputDevice()
{
X11XIDTabletDeviceMap::const_iterator it;
for (it = X11TabletDeviceMap.begin(); it != X11TabletDeviceMap.end(); ++it) {
const X11TabletDevice& tabletDevice = (*it).second;
XDeviceState *deviceState = XQueryDeviceState(QApplication::desktop()->x11Display(),
tabletDevice.xDevice());
// If your the laptop sleeps, and you remove the mouse from the usb
// port, then on wake-up Krita can crash because the above call will
// return 0.
if (!deviceState) continue;
const XInputClass *inputClass = deviceState->data;
bool deviceIsInProximity = false;
for (int i = 0; i < deviceState->num_classes; i++) {
if (inputClass->c_class == ValuatorClass) {
const XValuatorState *valuatorState = reinterpret_cast<const XValuatorState *>(inputClass);
if ((valuatorState->mode & ProximityState) == InProximity) {
deviceIsInProximity = true;
break;
}
}
inputClass = reinterpret_cast<const XInputClass *>(reinterpret_cast<const char *>(inputClass) + inputClass->length);
}
XFreeDeviceState(deviceState);
if (deviceIsInProximity && tabletDevice.enabled()) {
return tabletDevice.inputDevice();
}
}
return KisInputDevice::mouse();
}
#endif // EXTENDED_X11_TABLET_SUPPORT
#endif // Q_WS_X11
/*************************************************************************/
#define QPAINTDEVICE_CANVAS_WIDGET false
#define OPENGL_CANVAS_WIDGET true
KisCanvas::KisCanvas(QWidget *parent, const char *name)
{
m_parent = parent;
m_name = name;
m_enableMoveEventCompressionHint = false;
m_canvasWidget = 0;
m_useOpenGL = false;
createCanvasWidget(QPAINTDEVICE_CANVAS_WIDGET);
}
KisCanvas::~KisCanvas()
{
delete m_canvasWidget;
}
#ifdef HAVE_GL
void KisCanvas::createCanvasWidget(bool useOpenGL, QGLWidget *sharedContextWidget)
#else
void KisCanvas::createCanvasWidget(bool useOpenGL)
#endif
{
delete m_canvasWidget;
#ifndef HAVE_GL
useOpenGL = false;
#else
if (useOpenGL && !QGLFormat::hasOpenGL()) {
kdDebug(41001) << "Tried to create OpenGL widget when system doesn't have OpenGL\n";
useOpenGL = false;
}
if (useOpenGL) {
m_canvasWidget = new KisOpenGLCanvasWidget(m_parent, m_name.latin1(), sharedContextWidget);
} else
#endif
{
m_canvasWidget = new KisQPaintDeviceCanvasWidget(m_parent, m_name.latin1());
}
m_useOpenGL = useOpenGL;
Q_CHECK_PTR(m_canvasWidget);
QWidget *widget = dynamic_cast<QWidget *>(m_canvasWidget);
widget->setBackgroundMode(QWidget::NoBackground);
widget->setMouseTracking(true);
widget->setAcceptDrops(true);
m_canvasWidget->enableMoveEventCompressionHint(m_enableMoveEventCompressionHint);
#if defined(EXTENDED_X11_TABLET_SUPPORT)
selectTabletDeviceEvents();
#endif
connect(m_canvasWidget, SIGNAL(sigGotPaintEvent(QPaintEvent *)), SIGNAL(sigGotPaintEvent(QPaintEvent *)));
connect(m_canvasWidget, SIGNAL(sigGotEnterEvent(QEvent*)), SIGNAL(sigGotEnterEvent(QEvent*)));
connect(m_canvasWidget, SIGNAL(sigGotLeaveEvent(QEvent*)), SIGNAL(sigGotLeaveEvent(QEvent*)));
connect(m_canvasWidget, SIGNAL(sigGotMouseWheelEvent(QWheelEvent*)), SIGNAL(sigGotMouseWheelEvent(QWheelEvent*)));
connect(m_canvasWidget, SIGNAL(sigGotKeyPressEvent(QKeyEvent*)), SIGNAL(sigGotKeyPressEvent(QKeyEvent*)));
connect(m_canvasWidget, SIGNAL(sigGotKeyReleaseEvent(QKeyEvent*)), SIGNAL(sigGotKeyReleaseEvent(QKeyEvent*)));
connect(m_canvasWidget, SIGNAL(sigGotDragEnterEvent(QDragEnterEvent*)), SIGNAL(sigGotDragEnterEvent(QDragEnterEvent*)));
connect(m_canvasWidget, SIGNAL(sigGotDropEvent(QDropEvent*)), SIGNAL(sigGotDropEvent(QDropEvent*)));
connect(m_canvasWidget, SIGNAL(sigGotMoveEvent(KisMoveEvent *)), SIGNAL(sigGotMoveEvent(KisMoveEvent *)));
connect(m_canvasWidget, SIGNAL(sigGotButtonPressEvent(KisButtonPressEvent *)), SIGNAL(sigGotButtonPressEvent(KisButtonPressEvent *)));
connect(m_canvasWidget, SIGNAL(sigGotButtonReleaseEvent(KisButtonReleaseEvent *)), SIGNAL(sigGotButtonReleaseEvent(KisButtonReleaseEvent *)));
connect(m_canvasWidget, SIGNAL(sigGotDoubleClickEvent(KisDoubleClickEvent *)), SIGNAL(sigGotDoubleClickEvent(KisDoubleClickEvent *)));
}
void KisCanvas::createQPaintDeviceCanvas()
{
createCanvasWidget(QPAINTDEVICE_CANVAS_WIDGET);
}
#ifdef HAVE_GL
void KisCanvas::createOpenGLCanvas(QGLWidget *sharedContextWidget)
{
createCanvasWidget(OPENGL_CANVAS_WIDGET, sharedContextWidget);
}
#endif
bool KisCanvas::isOpenGLCanvas() const
{
return m_useOpenGL;
}
void KisCanvas::enableMoveEventCompressionHint(bool enableMoveCompression)
{
m_enableMoveEventCompressionHint = enableMoveCompression;
if (m_canvasWidget != 0) {
m_canvasWidget->enableMoveEventCompressionHint(enableMoveCompression);
}
}
QWidget *KisCanvas::QPaintDeviceWidget() const
{
if (m_useOpenGL) {
return 0;
} else {
return dynamic_cast<QWidget *>(m_canvasWidget);
}
}
#ifdef HAVE_GL
QGLWidget *KisCanvas::OpenGLWidget() const
{
if (m_useOpenGL) {
return dynamic_cast<QGLWidget *>(m_canvasWidget);
} else {
return 0;
}
}
#endif
KisCanvasWidgetPainter *KisCanvas::createPainter()
{
Q_ASSERT(m_canvasWidget != 0);
return m_canvasWidget->createPainter();
}
KisCanvasWidget *KisCanvas::canvasWidget() const
{
return m_canvasWidget;
}
void KisCanvas::setGeometry(int x, int y, int width, int height)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->setGeometry(x, y, width, height);
}
void KisCanvas::show()
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->show();
}
void KisCanvas::hide()
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->hide();
}
int KisCanvas::width() const
{
Q_ASSERT(m_canvasWidget);
return dynamic_cast<QWidget *>(m_canvasWidget)->width();
}
int KisCanvas::height() const
{
Q_ASSERT(m_canvasWidget);
return dynamic_cast<QWidget *>(m_canvasWidget)->height();
}
void KisCanvas::update()
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->update();
}
void KisCanvas::update(const QRect& r)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->update(r);
}
void KisCanvas::update(int x, int y, int width, int height)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->update(x, y, width, height);
}
void KisCanvas::repaint()
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->repaint();
}
void KisCanvas::repaint(bool erase)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->repaint(erase);
}
void KisCanvas::repaint(int x, int y, int width, int height, bool erase)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->repaint(x, y, width, height, erase);
}
void KisCanvas::repaint(const QRect& r, bool erase)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->repaint(r, erase);
}
void KisCanvas::repaint(const QRegion& r, bool erase)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->repaint(r, erase);
}
bool KisCanvas::isUpdatesEnabled() const
{
Q_ASSERT(m_canvasWidget);
return dynamic_cast<QWidget *>(m_canvasWidget)->isUpdatesEnabled();
}
void KisCanvas::setUpdatesEnabled(bool updatesEnabled)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->setUpdatesEnabled(updatesEnabled);
}
void KisCanvas::updateGeometry()
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->updateGeometry();
}
void KisCanvas::setFocusPolicy(QWidget::FocusPolicy focusPolicy)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->setFocusPolicy(focusPolicy);
}
const QCursor& KisCanvas::cursor() const
{
Q_ASSERT(m_canvasWidget);
return dynamic_cast<QWidget *>(m_canvasWidget)->cursor();
}
void KisCanvas::setCursor(const QCursor& cursor)
{
Q_ASSERT(m_canvasWidget);
dynamic_cast<QWidget *>(m_canvasWidget)->setCursor(cursor);
}
#if defined(EXTENDED_X11_TABLET_SUPPORT)
void KisCanvas::selectTabletDeviceEvents()
{
Q_ASSERT(m_canvasWidget);
m_canvasWidget->selectTabletDeviceEvents();
}
#endif
bool KisCanvas::cursorIsOverCanvas() const
{
if (QApplication::activePopupWidget() != 0) {
return false;
}
if (QApplication::activeModalWidget() != 0) {
return false;
}
QWidget *canvasWidget = dynamic_cast<QWidget *>(m_canvasWidget);
Q_ASSERT(canvasWidget != 0);
if (canvasWidget) {
if (QApplication::widgetAt(QCursor::pos(), true) == canvasWidget) {
return true;
}
}
return false;
}
void KisCanvas::handleKeyEvent(QEvent *e)
{
QKeyEvent *ke = dynamic_cast<QKeyEvent *>(e);
Q_ASSERT(ke != 0);
if (ke) {
QWidget *canvasWidget = dynamic_cast<QWidget *>(m_canvasWidget);
Q_ASSERT(canvasWidget != 0);
if (canvasWidget) {
canvasWidget->setFocus();
if (e->type() == QEvent::KeyPress) {
emit sigGotKeyPressEvent(ke);
} else {
emit sigGotKeyReleaseEvent(ke);
}
}
}
}
#include "kis_canvas.moc"