diff --git a/krita/ui/input/kis_input_manager.cpp b/krita/ui/input/kis_input_manager.cpp index a313559706..2fc4d64310 100644 --- a/krita/ui/input/kis_input_manager.cpp +++ b/krita/ui/input/kis_input_manager.cpp @@ -1,489 +1,507 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Arjen Hiemstra * Copyright (C) 2015 Michael Abrahams * * 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. * * 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. */ #include "kis_input_manager.h" #include #include #include #include #include #include "kis_tool_proxy.h" #include #include #include #include #include #include #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_pan_action.h" #include "kis_alternate_invocation_action.h" #include "kis_rotate_canvas_action.h" #include "kis_zoom_action.h" #include "kis_show_palette_action.h" #include "kis_change_primary_setting_action.h" #include "kis_shortcut_matcher.h" #include "kis_stroke_shortcut.h" #include "kis_single_action_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_input_profile.h" #include "kis_input_profile_manager.h" #include "kis_shortcut_configuration.h" #include #include #include "kis_extended_modifiers_mapper.h" #include "kis_input_manager_p.h" template uint qHash(QPointer value) { return reinterpret_cast(value.data()); } #define start_ignore_cursor_events() d->blockMouseEvents() #define stop_ignore_cursor_events() d->allowMouseEvents() // Note: this is placeholder! #define touch_stop_block_press_events() //d->blockMouseEvents() #define touch_start_block_press_events() //d->blockMouseEvents() #define break_if_touch_blocked_press_events() // if (d->touchHasBlockedPressEvents) break; KisInputManager::KisInputManager(QObject *parent) : QObject(parent), d(new Private(this)) { connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged())); connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent())); #ifndef Q_OS_MAC QApplication::instance()-> installEventFilter(new Private::ProximityNotifier(d, this)); #endif } KisInputManager::~KisInputManager() { delete d; } void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.addCanvas(canvas); } void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.removeCanvas(canvas); } void KisInputManager::toggleTabletLogger() { KisTabletDebugger::instance()->toggleDebugging(); } void KisInputManager::attachPriorityEventFilter(QObject *filter) { d->priorityEventFilter.insert(QPointer(filter)); } void KisInputManager::detachPriorityEventFilter(QObject *filter) { d->priorityEventFilter.remove(QPointer(filter)); } void KisInputManager::setupAsEventFilter(QObject *receiver) { if (d->eventsReceiver) { d->eventsReceiver->removeEventFilter(this); } d->eventsReceiver = receiver; if (d->eventsReceiver) { d->eventsReceiver->installEventFilter(this); } } void KisInputManager::stopIgnoringEvents() { stop_ignore_cursor_events(); } void KisInputManager::slotFocusOnEnter(bool value) { if (d->focusOnEnter == value) { return; } d->focusOnEnter = value; if (d->focusOnEnter && d->containsPointer) { if (d->canvas) { d->canvas->canvasWidget()->setFocus(); } } } #if defined (__clang__) #pragma GCC diagnostic ignored "-Wswitch" #endif bool KisInputManager::eventFilter(QObject* object, QEvent* event) { - bool retval = false; - if (object != d->eventsReceiver) return retval; + if (object != d->eventsReceiver) return false; - if (true) { - foreach (QPointer filter, d->priorityEventFilter) { - if (filter.isNull()) { - d->priorityEventFilter.remove(filter); - continue; - } + // If we have saved an event, take care of it now, horribly breaking encapsulation with eventEater + if (d->eventEater.savedEvent) { + if (event->type() != QEvent::TabletPress) { + // Unless things are screwed up beyond hope, the old event was the real deal. + qDebug() << "Emitting a pocketed event"; + this->eventFilterImpl(d->eventEater.savedEvent); + } + d->eventEater.savedEvent = 0; + } + + if (d->eventEater.eventFilter(object, event)) return false; + - if (filter->eventFilter(object, event)) return true; + foreach (QPointer filter, d->priorityEventFilter) { + if (filter.isNull()) { + d->priorityEventFilter.remove(filter); + continue; } + + if (filter->eventFilter(object, event)) return true; } // KoToolProxy needs to pre-process some events to ensure the // global shortcuts (not the input manager's ones) are not // executed, in particular, this line will accept events when the // tool is in text editing, preventing shortcut triggering d->toolProxy->processEvent(event); + // Continue with the actual switch statement... + return eventFilterImpl(event); +} + +bool KisInputManager::eventFilterImpl(QEvent * event) +{ // TODO: Handle touch events correctly. + bool retval = false; + switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: { d->debugEvent(event); // break_if_touch_blocked_press_events(); QMouseEvent *mouseEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent); } event->setAccepted(retval); break; } case QEvent::MouseButtonRelease: { d->debugEvent(event); // break_if_touch_blocked_press_events(); QMouseEvent *mouseEvent = static_cast(event); retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent); event->setAccepted(retval); break; } case QEvent::ShortcutOverride: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); Qt::Key key = d->workaroundShiftAltMetaHell(keyEvent); if (!keyEvent->isAutoRepeat()) { retval = d->matcher.keyPressed(key); } else { retval = d->matcher.autoRepeatedKeyPressed(key); } /** * Workaround for temporary switching of tools by * KoCanvasControllerWidget. We don't need this switch because * we handle it ourselves. */ retval |= !d->forwardAllEventsToTool && (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Escape); break; } case QEvent::KeyRelease: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); if (!keyEvent->isAutoRepeat()) { Qt::Key key = d->workaroundShiftAltMetaHell(keyEvent); retval = d->matcher.keyReleased(key); } break; } case QEvent::MouseMove: { d->debugEvent(event); if (!d->matcher.pointerMoved(event)) { //Update the current tool so things like the brush outline gets updated. d->toolProxy->forwardHoverEvent(event); } retval = true; event->setAccepted(retval); break; } case QEvent::Wheel: { d->debugEvent(event); QWheelEvent *wheelEvent = static_cast(event); KisSingleActionShortcut::WheelAction action; if(wheelEvent->orientation() == Qt::Horizontal) { if(wheelEvent->delta() < 0) { action = KisSingleActionShortcut::WheelRight; } else { action = KisSingleActionShortcut::WheelLeft; } } else { if(wheelEvent->delta() > 0) { action = KisSingleActionShortcut::WheelUp; } else { action = KisSingleActionShortcut::WheelDown; } } //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(action, wheelEvent); break; } case QEvent::Enter: d->debugEvent(event); d->containsPointer = true; //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); //Ensure we have focus so we get key events. if (d->focusOnEnter) { d->canvas->canvasWidget()->setFocus(); } stop_ignore_cursor_events(); touch_stop_block_press_events(); d->matcher.enterEvent(); break; case QEvent::Leave: d->debugEvent(event); d->containsPointer = false; /** * We won't get a TabletProximityLeave event when the tablet * is hovering above some other widget, so restore cursor * events processing right now. */ stop_ignore_cursor_events(); touch_stop_block_press_events(); d->matcher.leaveEvent(); break; case QEvent::FocusIn: d->debugEvent(event); KisAbstractInputAction::setInputManager(this); //Clear all state so we don't have half-matched shortcuts dangling around. d->matcher.reinitialize(); { // Emulate pressing of the key that are already pressed KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); foreach (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::KeyPress, key, modifiers); - eventFilter(object, &kevent); + eventFilterImpl(&kevent); } } stop_ignore_cursor_events(); break; case QEvent::TabletRelease: { // break_if_touch_blocked_press_events(); d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent); retval = true; event->setAccepted(true); stop_ignore_cursor_events(); break; } case QEvent::TabletMove: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); if (!d->matcher.pointerMoved(tabletEvent)) { d->toolProxy->forwardHoverEvent(tabletEvent); } retval = true; event->setAccepted(true); /** * The flow of tablet events means the tablet is in the * proximity area, so activate it even when the * TabletEnterProximity event was missed (may happen when * changing focus of the window with tablet in the proximity * area) */ start_ignore_cursor_events(); break; } case QEvent::TabletPress: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent); } event->setAccepted(true); retval = true; start_ignore_cursor_events(); break; } case QEvent::TouchBegin: touch_start_block_press_events(); KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchBeginEvent(static_cast(event)); event->accept(); // d->resetSavedTabletEvent(event->type()); break; case QEvent::TouchUpdate: touch_start_block_press_events(); KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchUpdateEvent(static_cast(event)); event->accept(); // d->resetSavedTabletEvent(event->type()); break; case QEvent::TouchEnd: touch_stop_block_press_events(); d->saveTouchEvent(static_cast(event)); retval = d->matcher.touchEndEvent(static_cast(event)); event->accept(); // d->resetSavedTabletEvent(event->type()); delete d->lastTouchEvent; d->lastTouchEvent = 0; break; default: break; } return !retval ? d->processUnhandledEvent(event) : true; } void KisInputManager::slotCompressedMoveEvent() { if (d->compressedMoveEvent) { // touch_stop_block_press_events(); (void) d->handleCompressedTabletEvent(d->eventsReceiver, d->compressedMoveEvent.data()); d->compressedMoveEvent.reset(); dbgKrita << "Compressed move event received."; } else { dbgKrita << "Unexpected empty move event"; } } KisCanvas2* KisInputManager::canvas() const { return d->canvas; } KisToolProxy* KisInputManager::toolProxy() const { return d->toolProxy; } QTouchEvent *KisInputManager::lastTouchEvent() const { return d->lastTouchEvent; } void KisInputManager::slotToolChanged() { QString toolId = KoToolManager::instance()->activeToolId(); if (toolId == "ArtisticTextToolFactoryID" || toolId == "TextToolFactory_ID") { d->forwardAllEventsToTool = true; d->matcher.suppressAllActions(true); } else { d->forwardAllEventsToTool = false; d->matcher.suppressAllActions(false); } } QPointF KisInputManager::widgetToDocument(const QPointF& position) { const QPointF half = QPointF(.5f, .5f); QPointF pixel = position + half; return d->canvas->coordinatesConverter()->widgetToDocument(pixel); } void KisInputManager::profileChanged() { d->matcher.clearShortcuts(); KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile(); if (profile) { const QList shortcuts = profile->allShortcuts(); for (KisShortcutConfiguration * const shortcut : shortcuts) { dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name(); switch(shortcut->type()) { case KisShortcutConfiguration::KeyCombinationType: d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys()); break; case KisShortcutConfiguration::MouseButtonType: d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons()); break; case KisShortcutConfiguration::MouseWheelType: d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel()); break; case KisShortcutConfiguration::GestureType: d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture()); break; default: break; } } } else { dbgKrita << "No Input Profile Found: canvas interaction will be impossible"; } } diff --git a/krita/ui/input/kis_input_manager.h b/krita/ui/input/kis_input_manager.h index dc4439bab1..3eaeb6d545 100644 --- a/krita/ui/input/kis_input_manager.h +++ b/krita/ui/input/kis_input_manager.h @@ -1,124 +1,125 @@ /* This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * 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. * * 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. */ #ifndef KIS_INPUTMANAGER_H #define KIS_INPUTMANAGER_H #include #include class QPointF; class QTouchEvent; class KisToolProxy; class KisCanvas2; /** * \brief Central object to manage canvas input. * * The Input Manager class manages all canvas input. It is created * by KisCanvas2 and processes all events related to input sent to the * canvas. * * The Input Manager keeps track of a set of actions and a set of * shortcuts. The actions are pre-defined while the shortcuts are * set from configuration. * * For each event, it will try to determine if there is a shortcut that * matches the input. It will then activate this action and pass all * consecutive events on to this action. * * \sa KisAbstractInputAction * * \todo Implement shortcut configuration */ class KRITAUI_EXPORT KisInputManager : public QObject { Q_OBJECT public: /** * Constructor. */ KisInputManager(QObject *parent); /** * Destructor. */ ~KisInputManager(); void addTrackedCanvas(KisCanvas2 *canvas); void removeTrackedCanvas(KisCanvas2 *canvas); void toggleTabletLogger(); /** * Installs the input manager as an event filter for \p receiver. * Please note that KisInputManager is supposed to handle events * for a single receiver only. This is defined by the fact that it * resends some of the events back through the Qt's queue to the * reciever. That is why the input manager will assert when it gets * an event with wrong destination. */ void setupAsEventFilter(QObject *receiver); /** * Event filter method. Overridden from QObject. */ bool eventFilter(QObject* object, QEvent* event ); + bool eventFilterImpl(QEvent * event); void attachPriorityEventFilter(QObject *filter); void detachPriorityEventFilter(QObject *filter); /** * Return the canvas this input manager is associated with. */ KisCanvas2 *canvas() const; /** * The tool proxy of the current application. */ KisToolProxy *toolProxy() const; /** * Touch events are special, too. * * \return a touch event if there was one, otherwise 0 */ QTouchEvent *lastTouchEvent() const; /** * Convert a widget position to a document position. */ QPointF widgetToDocument(const QPointF &position); public Q_SLOTS: void stopIgnoringEvents(); void slotFocusOnEnter(bool value); private Q_SLOTS: void slotToolChanged(); void profileChanged(); void slotCompressedMoveEvent(); private: class Private; Private* const d; }; #endif // KIS_INPUTMANAGER_H diff --git a/krita/ui/input/kis_input_manager_p.cpp b/krita/ui/input/kis_input_manager_p.cpp index 0c34aaa0d1..5f7ea9e8c3 100644 --- a/krita/ui/input/kis_input_manager_p.cpp +++ b/krita/ui/input/kis_input_manager_p.cpp @@ -1,409 +1,454 @@ /* * Copyright (C) 2015 Michael Abrahams * * 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. * * 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. */ #include "kis_input_manager_p.h" #include #include #include #include #include "kis_input_manager.h" #include "kis_config.h" #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_input_profile_manager.h" -// Note: this class is intended to model all event masking logic. -class EventEater : public QObject -{ -public: - EventEater() : QObject(0), hungry(false) {} +/** + * This hungry class EventEater encapsulates event masking logic. + * + * Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after + * tablet events. Those events are sent in order to allow widgets that haven't + * implemented tablet specific functionality to seamlessly behave as if one were + * using a mouse. These synthetic events are *supposed* to be optional, or at + * least come with a flag saying "This is a fake event!!" but neither of those + * methods is trustworthy. (This is correct as of Qt 5.4 + Xorg.) + * + * Qt 5.4 provides no reliable way to see if a user's tablet is being hovered + * over the pad, since it converts all tablethover events into mousemove, with + * no option to turn this off. Moreover, sometimes the MouseButtonPress event + * from the tapping their tablet happens BEFORE the TabletPress event. This + * means we have to resort to a somewhat complicated logic. What makes this + * truly a joke is that we are not guaranteed to observe TabletProximityEnter + * events when we're using a tablet, either, you may only see an Enter event. + * + * Once we see tablet events heading our way, we can say pretty confidently that + * every mouse event is fake. The only problem is the boundary case at the + * beginning of a stroke: since a newly arriving MousePress event may be a fake + * synthetic one or an actual press, and the only context that can tell us this + * is if the event *after* it is a TabletPress. The solution is to store one + * MousePress event in the EventEater until the next event arrives. Once it sees + * the event following, it will know whether to forward the MouseMove event or + * to toss it away. If the event after a MousePress event is a tablet event, the + * MousePress was synthetic, so toss it. If the event was a MouseMove, it was a + * real mouse click, so keep it. If it is possible that the arrival of events + * can be off by more than one, this solution will not work. On the other + * hand, that would be simply preposterous. + */ - bool eventFilter(QObject* /*object*/, QEvent* event ) +bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event ) +{ + if ((hungry && (event->type() == QEvent::MouseMove || + event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonRelease)) + // || (peckish && (event->type() == QEvent::MouseButtonPress)) + ) { - if ((hungry && (event->type() == QEvent::MouseMove || - event->type() == QEvent::MouseButtonPress || - event->type() == QEvent::MouseButtonRelease)) || - (peckish && (event->type() == QEvent::MouseButtonPress))) { - if (KisTabletDebugger::instance()->debugEnabled()) { - QString pre = QString("[BLOCKED]"); - QMouseEvent *ev = static_cast(event); - dbgInput << KisTabletDebugger::instance()->eventToString(*ev,pre); - } - peckish = false; - return true; + // Chow down + if (KisTabletDebugger::instance()->debugEnabled()) { + QString pre = QString("[BLOCKED]"); + QMouseEvent *ev = static_cast(event); + dbgInput << KisTabletDebugger::instance()->eventToString(*ev,pre); } - return false; + peckish = false; + return true; } - - void activate() + else if ((event->type() == QEvent::MouseButtonPress) /* Need to scrutinize */ && + (!savedEvent)) /* Otherwise we enter a loop repeatedly storing the same event */ { - if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) - dbgInput << "Ignoring mouse events."; - hungry = true; + QMouseEvent *mouseEvent = static_cast(event); + // Pocket the event and decide what to do with it later + // savedEvent = *(static_cast(event)); + savedEvent = new QMouseEvent(QEvent::MouseButtonPress, + mouseEvent->pos(), + mouseEvent->windowPos(), + mouseEvent->screenPos(), + mouseEvent->button(), + mouseEvent->buttons(), + mouseEvent->modifiers()); + savedTarget = target; + mouseEvent->accept(); + return true; } - void deactivate() - { - if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) - dbgInput << "Accepting mouse events."; - hungry = false; - } + return false; // All clear - let this one through! +} - void eatOneMousePress() - { - peckish = true; - } - bool isActive() - { - return hungry; - } +void KisInputManager::Private::EventEater::activate() +{ + if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) + dbgInput << "Ignoring mouse events."; + hungry = true; +} + +void KisInputManager::Private::EventEater::deactivate() +{ + if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) + dbgInput << "Accepting mouse events."; + hungry = false; +} + +// This would be a solution if we had reliable proximity events. SIGH +// void eatOneMousePress() +// { +// peckish = true; +// } -private: - bool hungry; // Continue eating mouse strokes - bool peckish; // Eat a single mouse press event -}; +bool KisInputManager::Private::EventEater::isActive() +{ + return hungry; +} -Q_GLOBAL_STATIC(EventEater, globalEventEater); bool KisInputManager::Private::ignoreQtCursorEvents() { - return globalEventEater->isActive(); + return eventEater.isActive(); } KisInputManager::Private::Private(KisInputManager *qq) : q(qq) , moveEventCompressor(10 /* ms */, KisSignalCompressor::FIRST_ACTIVE) - , canvasSwitcher(this, q) + , canvasSwitcher(this, qq) { KisConfig cfg; disableTouchOnCanvas = cfg.disableTouchOnCanvas(); moveEventCompressor.setDelay(cfg.tabletEventsDelay()); testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents(); testingCompressBrushEvents = cfg.testingCompressBrushEvents(); setupActions(); - - qApp->installEventFilter(globalEventEater); } KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p) : QObject(p), d(_d), eatOneMouseStroke(false) { } void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas) { - QObject *widget = canvas->canvasWidget(); + QObject *canvasWidget = canvas->canvasWidget(); - if (!canvasResolver.contains(widget)) { - canvasResolver.insert(widget, canvas); - d->q->setupAsEventFilter(widget); - widget->installEventFilter(this); + if (!canvasResolver.contains(canvasWidget)) { + canvasResolver.insert(canvasWidget, canvas); + d->q->setupAsEventFilter(canvasWidget); + canvasWidget->installEventFilter(this); d->canvas = canvas; d->toolProxy = dynamic_cast(canvas->toolProxy()); } else { KIS_ASSERT_RECOVER_RETURN(d->canvas == canvas); } } void KisInputManager::Private::CanvasSwitcher::removeCanvas(KisCanvas2 *canvas) { QObject *widget = canvas->canvasWidget(); canvasResolver.remove(widget); if (d->eventsReceiver == widget) { d->q->setupAsEventFilter(0); } widget->removeEventFilter(this); } bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event ) { if (canvasResolver.contains(object)) { switch (event->type()) { case QEvent::FocusIn: { QFocusEvent *fevent = static_cast(event); eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason); KisCanvas2 *canvas = canvasResolver.value(object); d->canvas = canvas; d->toolProxy = dynamic_cast(canvas->toolProxy()); d->q->setupAsEventFilter(object); object->removeEventFilter(this); object->installEventFilter(this); QEvent event(QEvent::Enter); d->q->eventFilter(object, &event); break; } case QEvent::Wheel: { QWidget *widget = static_cast(object); widget->setFocus(); break; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::TabletPress: case QEvent::TabletRelease: if (eatOneMouseStroke) { eatOneMouseStroke--; return true; } break; case QEvent::MouseButtonDblClick: if (eatOneMouseStroke) { return true; } break; default: break; } } return QObject::eventFilter(object, event); } KisInputManager::Private::ProximityNotifier::ProximityNotifier(KisInputManager::Private *_d, QObject *p) : QObject(p), d(_d) {} bool KisInputManager::Private::ProximityNotifier::eventFilter(QObject* object, QEvent* event ) { switch (event->type()) { case QEvent::TabletEnterProximity: d->debugEvent(event); - globalEventEater->eatOneMousePress(); - // d->blockMouseEvents(); Qt sends fake mouse events instead of hover events, so disable this for now. + // Tablet proximity events are unreliable AND fake mouse events do not + // necessarily come after tablet events, so this is insufficient. + // d->eventEater.eatOneMousePress(); + + // Qt sends fake mouse events instead of hover events, so not very useful. + d->blockMouseEvents(); break; case QEvent::TabletLeaveProximity: d->debugEvent(event); d->allowMouseEvents(); break; default: break; } return QObject::eventFilter(object, event); } void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, Qt::MouseButtons buttons) { KisStrokeShortcut *strokeShortcut = new KisStrokeShortcut(action, index); QList buttonList; if(buttons & Qt::LeftButton) { buttonList << Qt::LeftButton; } if(buttons & Qt::RightButton) { buttonList << Qt::RightButton; } if(buttons & Qt::MidButton) { buttonList << Qt::MidButton; } if(buttons & Qt::XButton1) { buttonList << Qt::XButton1; } if(buttons & Qt::XButton2) { buttonList << Qt::XButton2; } if (buttonList.size() > 0) { strokeShortcut->setButtons(QSet::fromList(modifiers), QSet::fromList(buttonList)); matcher.addShortcut(strokeShortcut); } } void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int index, const QList &keys) { if (keys.size() == 0) return; KisSingleActionShortcut *keyShortcut = new KisSingleActionShortcut(action, index); //Note: Ordering is important here, Shift + V is different from V + Shift, //which is the reason we use the last key here since most users will enter //shortcuts as "Shift + V". Ideally this should not happen, but this is //the way the shortcut matcher is currently implemented. QList allKeys = keys; Qt::Key key = allKeys.takeLast(); QSet modifiers = QSet::fromList(allKeys); keyShortcut->setKey(modifiers, key); matcher.addShortcut(keyShortcut); } void KisInputManager::Private::addWheelShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction) { KisSingleActionShortcut *keyShortcut = new KisSingleActionShortcut(action, index); KisSingleActionShortcut::WheelAction a; switch(wheelAction) { case KisShortcutConfiguration::WheelUp: a = KisSingleActionShortcut::WheelUp; break; case KisShortcutConfiguration::WheelDown: a = KisSingleActionShortcut::WheelDown; break; case KisShortcutConfiguration::WheelLeft: a = KisSingleActionShortcut::WheelLeft; break; case KisShortcutConfiguration::WheelRight: a = KisSingleActionShortcut::WheelRight; break; default: return; } keyShortcut->setWheel(QSet::fromList(modifiers), a); matcher.addShortcut(keyShortcut); } void KisInputManager::Private::addTouchShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { KisTouchShortcut *shortcut = new KisTouchShortcut(action, index); switch(gesture) { case KisShortcutConfiguration::PinchGesture: shortcut->setMinimumTouchPoints(2); shortcut->setMaximumTouchPoints(2); break; case KisShortcutConfiguration::PanGesture: shortcut->setMinimumTouchPoints(3); shortcut->setMaximumTouchPoints(10); break; default: break; } matcher.addShortcut(shortcut); } void KisInputManager::Private::setupActions() { QList actions = KisInputProfileManager::instance()->actions(); foreach(KisAbstractInputAction *action, actions) { KisToolInvocationAction *toolAction = dynamic_cast(action); if(toolAction) { defaultInputAction = toolAction; } } connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged())); if(KisInputProfileManager::instance()->currentProfile()) { q->profileChanged(); } } bool KisInputManager::Private::processUnhandledEvent(QEvent *event) { bool retval = false; if (forwardAllEventsToTool || event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { defaultInputAction->processUnhandledEvent(event); retval = true; } return retval && !forwardAllEventsToTool; } Qt::Key KisInputManager::Private::workaroundShiftAltMetaHell(const QKeyEvent *keyEvent) { Qt::Key key = (Qt::Key)keyEvent->key(); if (keyEvent->key() == Qt::Key_Meta && keyEvent->modifiers().testFlag(Qt::ShiftModifier)) { key = Qt::Key_Alt; } return key; } bool KisInputManager::Private::tryHidePopupPalette() { if (canvas->isPopupPaletteVisible()) { canvas->slotShowPopupPalette(); return true; } return false; } #ifdef HAVE_X11 inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y()); } inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y()); } #endif void KisInputManager::Private::saveTouchEvent( QTouchEvent* event ) { delete lastTouchEvent; lastTouchEvent = new QTouchEvent(event->type(), event->device(), event->modifiers(), event->touchPointStates(), event->touchPoints()); } void KisInputManager::Private::blockMouseEvents() { - globalEventEater->activate(); + eventEater.activate(); } void KisInputManager::Private::allowMouseEvents() { - globalEventEater->deactivate(); + eventEater.deactivate(); } bool KisInputManager::Private::handleCompressedTabletEvent(QObject *object, QTabletEvent *tevent) { if(object == 0) return false; bool retval = false; retval = q->eventFilter(object, tevent); if (!retval && !tevent->isAccepted()) { dbgInput << "Rejected a compressed tablet event."; } return retval; } diff --git a/krita/ui/input/kis_input_manager_p.h b/krita/ui/input/kis_input_manager_p.h index 7c03742df8..4742bd55b2 100644 --- a/krita/ui/input/kis_input_manager_p.h +++ b/krita/ui/input/kis_input_manager_p.h @@ -1,117 +1,136 @@ /* * Copyright (C) 2015 Michael Abrahams * * 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. * * 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. */ #include #include #include #include #include #include #include "kis_input_manager.h" #include "kis_shortcut_matcher.h" #include "kis_shortcut_configuration.h" #include "kis_canvas2.h" #include "kis_tool_proxy.h" #include "kis_signal_compressor.h" #include "input/kis_tablet_debugger.h" class KisToolInvocationAction; class KisInputManager::Private { public: Private(KisInputManager *qq); bool tryHidePopupPalette(); void addStrokeShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, Qt::MouseButtons buttons); void addKeyShortcut(KisAbstractInputAction* action, int index,const QList &keys); void addTouchShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture ); void addWheelShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction); bool processUnhandledEvent(QEvent *event); Qt::Key workaroundShiftAltMetaHell(const QKeyEvent *keyEvent); void setupActions(); void saveTouchEvent( QTouchEvent* event ); bool handleCompressedTabletEvent(QObject *object, QTabletEvent *tevent); KisInputManager *q; KisCanvas2 *canvas = 0; KisToolProxy *toolProxy = 0; bool forwardAllEventsToTool = false; bool ignoreQtCursorEvents(); bool disableTouchOnCanvas = false; bool touchHasBlockedPressEvents = false; KisShortcutMatcher matcher; QTouchEvent *lastTouchEvent = 0; KisToolInvocationAction *defaultInputAction = 0; QObject *eventsReceiver = 0; KisSignalCompressor moveEventCompressor; QScopedPointer compressedMoveEvent; bool testingAcceptCompressedTabletEvents = false; bool testingCompressBrushEvents = false; QSet > priorityEventFilter; void blockMouseEvents(); void allowMouseEvents(); template void debugEvent(QEvent *event) { if (!KisTabletDebugger::instance()->debugEnabled()) return; QString msg1 = useBlocking && ignoreQtCursorEvents() ? "[BLOCKED] " : "[ ]"; Event *specificEvent = static_cast(event); dbgInput << KisTabletDebugger::instance()->eventToString(*specificEvent, msg1); } class ProximityNotifier : public QObject { public: ProximityNotifier(Private *_d, QObject *p); bool eventFilter(QObject* object, QEvent* event ); private: KisInputManager::Private *d; }; class CanvasSwitcher : public QObject { public: CanvasSwitcher(Private *_d, QObject *p); void addCanvas(KisCanvas2 *canvas); void removeCanvas(KisCanvas2 *canvas); bool eventFilter(QObject* object, QEvent* event ); private: KisInputManager::Private *d; QMap canvasResolver; int eatOneMouseStroke; }; + class EventEater + { + public: + bool eventFilter(QObject* target, QEvent* event); + + // This should be called after we're certain a tablet stroke has started. + void activate(); + // This should be called after a tablet stroke has ended. + void deactivate(); + bool isActive(); + + QEvent *savedEvent; + QObject *savedTarget; // More storage + private: + bool hungry{false}; // Continue eating mouse strokes + bool peckish{false}; // Eat a single mouse press event + }; + CanvasSwitcher canvasSwitcher; + EventEater eventEater; bool focusOnEnter = true; bool containsPointer = true; };