diff --git a/libs/ui/canvas/kis_guides_manager.cpp b/libs/ui/canvas/kis_guides_manager.cpp index 8d0d70e197..ec68d6bc0b 100644 --- a/libs/ui/canvas/kis_guides_manager.cpp +++ b/libs/ui/canvas/kis_guides_manager.cpp @@ -1,733 +1,742 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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_guides_manager.h" #include #include #include "kis_guides_decoration.h" #include #include "kis_guides_config.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_signals_blocker.h" #include "input/kis_input_manager.h" #include "kis_coordinates_converter.h" #include "kis_zoom_manager.h" #include "kis_signal_auto_connection.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_algebra_2d.h" #include #include "kis_snap_line_strategy.h" #include "kis_change_guides_command.h" #include "kis_snap_config.h" struct KisGuidesManager::Private { Private(KisGuidesManager *_q) : q(_q), decoration(0), invalidGuide(Qt::Horizontal, -1), currentGuide(invalidGuide), cursorSwitched(false), dragStartGuidePos(0) {} KisGuidesManager *q; KisGuidesDecoration *decoration; KisGuidesConfig guidesConfig; KisSnapConfig snapConfig; QPointer view; typedef QPair GuideHandle; GuideHandle findGuide(const QPointF &docPos); bool isGuideValid(const GuideHandle &h); qreal guideValue(const GuideHandle &h); void setGuideValue(const GuideHandle &h, qreal value); void deleteGuide(const GuideHandle &h); const GuideHandle invalidGuide; bool updateCursor(const QPointF &docPos); void initDragStart(const GuideHandle &guide, const QPointF &dragStart, qreal guideValue, bool snapToStart); bool mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers); bool mouseReleaseHandler(const QPointF &docPos); void updateSnappingStatus(const KisGuidesConfig &value); QPointF alignToPixels(const QPointF docPoint); QPointF getDocPointFromEvent(QEvent *event); Qt::MouseButton getButtonFromEvent(QEvent *event); QAction* createShortenedAction(const QString &text, const QString &parentId, QObject *parent); void syncAction(const QString &actionName, bool value); GuideHandle currentGuide; bool cursorSwitched; QCursor oldCursor; QPointF dragStartDoc; QPointF dragPointerOffset; qreal dragStartGuidePos; KisSignalAutoConnectionsStore viewConnections; }; KisGuidesManager::KisGuidesManager(QObject *parent) : QObject(parent), m_d(new Private(this)) { } KisGuidesManager::~KisGuidesManager() { } void KisGuidesManager::setGuidesConfig(const KisGuidesConfig &config) { if (config == m_d->guidesConfig) return; setGuidesConfigImpl(config, true); } void KisGuidesManager::slotDocumentRequestedConfig(const KisGuidesConfig &config) { if (config == m_d->guidesConfig) return; setGuidesConfigImpl(config, false); } void KisGuidesManager::setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified) { m_d->guidesConfig = value; if (m_d->decoration && value != m_d->decoration->guidesConfig()) { m_d->decoration->setVisible(value.showGuides()); m_d->decoration->setGuidesConfig(value); } KisDocument *doc = m_d->view ? m_d->view->document() : 0; if (doc) { KisSignalsBlocker b(doc); if (emitModified) { KUndo2Command *cmd = new KisChangeGuidesCommand(doc, value); doc->addCommand(cmd); } else { doc->setGuidesConfig(value); } value.saveStaticData(); } const bool shouldFilterEvent = value.showGuides() && !value.lockGuides() && value.hasGuides(); attachEventFilterImpl(shouldFilterEvent); syncActionsStatus(); if (!m_d->isGuideValid(m_d->currentGuide)) { m_d->updateSnappingStatus(value); } emit sigRequestUpdateGuidesConfig(m_d->guidesConfig); } void KisGuidesManager::attachEventFilterImpl(bool value) { if (!m_d->view) return; KisInputManager *inputManager = m_d->view->globalInputManager(); if (inputManager) { if (value) { inputManager->attachPriorityEventFilter(this, 100); } else { inputManager->detachPriorityEventFilter(this); } } } void KisGuidesManager::Private::syncAction(const QString &actionName, bool value) { KisActionManager *actionManager = view->viewManager()->actionManager(); KisAction *action = actionManager->actionByName(actionName); KIS_ASSERT_RECOVER_RETURN(action); KisSignalsBlocker b(action); action->setChecked(value); } void KisGuidesManager::syncActionsStatus() { if (!m_d->view) return; m_d->syncAction("view_show_guides", m_d->guidesConfig.showGuides()); m_d->syncAction("view_lock_guides", m_d->guidesConfig.lockGuides()); m_d->syncAction("view_snap_to_guides", m_d->guidesConfig.snapToGuides()); m_d->syncAction("view_snap_orthogonal", m_d->snapConfig.orthogonal()); m_d->syncAction("view_snap_node", m_d->snapConfig.node()); m_d->syncAction("view_snap_extension", m_d->snapConfig.extension()); m_d->syncAction("view_snap_intersection", m_d->snapConfig.intersection()); m_d->syncAction("view_snap_bounding_box", m_d->snapConfig.boundingBox()); m_d->syncAction("view_snap_image_bounds", m_d->snapConfig.imageBounds()); m_d->syncAction("view_snap_image_center", m_d->snapConfig.imageCenter()); } void KisGuidesManager::Private::updateSnappingStatus(const KisGuidesConfig &value) { if (!view) return; KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); KisSnapLineStrategy *guidesSnap = 0; if (value.snapToGuides()) { guidesSnap = new KisSnapLineStrategy(KoSnapGuide::GuideLineSnapping); guidesSnap->setHorizontalLines(value.horizontalGuideLines()); guidesSnap->setVerticalLines(value.verticalGuideLines()); } snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap); snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap); snapGuide->enableSnapStrategy(KoSnapGuide::OrthogonalSnapping, snapConfig.orthogonal()); snapGuide->enableSnapStrategy(KoSnapGuide::NodeSnapping, snapConfig.node()); snapGuide->enableSnapStrategy(KoSnapGuide::ExtensionSnapping, snapConfig.extension()); snapGuide->enableSnapStrategy(KoSnapGuide::IntersectionSnapping, snapConfig.intersection()); snapGuide->enableSnapStrategy(KoSnapGuide::BoundingBoxSnapping, snapConfig.boundingBox()); snapGuide->enableSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, snapConfig.imageBounds()); snapGuide->enableSnapStrategy(KoSnapGuide::DocumentCenterSnapping, snapConfig.imageCenter()); snapConfig.saveStaticData(); } bool KisGuidesManager::showGuides() const { return m_d->guidesConfig.showGuides(); } void KisGuidesManager::setShowGuides(bool value) { m_d->guidesConfig.setShowGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } bool KisGuidesManager::lockGuides() const { return m_d->guidesConfig.lockGuides(); } void KisGuidesManager::setLockGuides(bool value) { m_d->guidesConfig.setLockGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } bool KisGuidesManager::snapToGuides() const { return m_d->guidesConfig.snapToGuides(); } void KisGuidesManager::setSnapToGuides(bool value) { m_d->guidesConfig.setSnapToGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } void KisGuidesManager::setup(KisActionManager *actionManager) { KisAction *action = 0; action = actionManager->createAction("view_show_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setShowGuides(bool))); action = actionManager->createAction("view_lock_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setLockGuides(bool))); action = actionManager->createAction("view_snap_to_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToGuides(bool))); action = actionManager->createAction("show_snap_options_popup"); connect(action, SIGNAL(triggered()), this, SLOT(slotShowSnapOptions())); action = actionManager->createAction("view_snap_orthogonal"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapOrthogonal(bool))); action = actionManager->createAction("view_snap_node"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapNode(bool))); action = actionManager->createAction("view_snap_extension"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapExtension(bool))); action = actionManager->createAction("view_snap_intersection"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapIntersection(bool))); action = actionManager->createAction("view_snap_bounding_box"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapBoundingBox(bool))); action = actionManager->createAction("view_snap_image_bounds"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageBounds(bool))); action = actionManager->createAction("view_snap_image_center"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageCenter(bool))); m_d->updateSnappingStatus(m_d->guidesConfig); syncActionsStatus(); } void KisGuidesManager::setView(QPointer view) { if (m_d->view) { KoSnapGuide *snapGuide = m_d->view->canvasBase()->snapGuide(); snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, 0); snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, false); m_d->decoration = 0; m_d->viewConnections.clear(); attachEventFilterImpl(false); } m_d->view = view; if (m_d->view) { KisGuidesDecoration* decoration = qobject_cast(m_d->view->canvasBase()->decoration(GUIDES_DECORATION_ID)); if (!decoration) { decoration = new KisGuidesDecoration(m_d->view); m_d->view->canvasBase()->addDecoration(decoration); } m_d->decoration = decoration; m_d->guidesConfig = m_d->view->document()->guidesConfig(); setGuidesConfigImpl(m_d->guidesConfig, false); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation, const QPoint&)), this, SLOT(slotGuideCreationInProgress(Qt::Orientation, const QPoint&))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation, const QPoint&)), this, SLOT(slotGuideCreationFinished(Qt::Orientation, const QPoint&))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation, const QPoint&)), this, SLOT(slotGuideCreationInProgress(Qt::Orientation, const QPoint&))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation, const QPoint&)), this, SLOT(slotGuideCreationFinished(Qt::Orientation, const QPoint&))); m_d->viewConnections.addUniqueConnection( m_d->view->document(), SIGNAL(sigGuidesConfigChanged(const KisGuidesConfig &)), this, SLOT(slotDocumentRequestedConfig(const KisGuidesConfig &))); } } KisGuidesManager::Private::GuideHandle KisGuidesManager::Private::findGuide(const QPointF &docPos) { const int snapRadius = 16; GuideHandle nearestGuide = invalidGuide; qreal nearestRadius = std::numeric_limits::max(); for (int i = 0; i < guidesConfig.horizontalGuideLines().size(); i++) { const qreal guide = guidesConfig.horizontalGuideLines()[i]; const qreal radius = qAbs(docPos.y() - guide); if (radius < snapRadius && radius < nearestRadius) { nearestGuide = GuideHandle(Qt::Horizontal, i); nearestRadius = radius; } } for (int i = 0; i < guidesConfig.verticalGuideLines().size(); i++) { const qreal guide = guidesConfig.verticalGuideLines()[i]; const qreal radius = qAbs(docPos.x() - guide); if (radius < snapRadius && radius < nearestRadius) { nearestGuide = GuideHandle(Qt::Vertical, i); nearestRadius = radius; } } return nearestGuide; } bool KisGuidesManager::Private::isGuideValid(const GuideHandle &h) { return h.second >= 0; } qreal KisGuidesManager::Private::guideValue(const GuideHandle &h) { return h.first == Qt::Horizontal ? guidesConfig.horizontalGuideLines()[h.second] : guidesConfig.verticalGuideLines()[h.second]; } void KisGuidesManager::Private::setGuideValue(const GuideHandle &h, qreal value) { if (h.first == Qt::Horizontal) { QList guides = guidesConfig.horizontalGuideLines(); guides[h.second] = value; guidesConfig.setHorizontalGuideLines(guides); } else { QList guides = guidesConfig.verticalGuideLines(); guides[h.second] = value; guidesConfig.setVerticalGuideLines(guides); } } void KisGuidesManager::Private::deleteGuide(const GuideHandle &h) { if (h.first == Qt::Horizontal) { QList guides = guidesConfig.horizontalGuideLines(); guides.removeAt(h.second); guidesConfig.setHorizontalGuideLines(guides); } else { QList guides = guidesConfig.verticalGuideLines(); guides.removeAt(h.second); guidesConfig.setVerticalGuideLines(guides); } } bool KisGuidesManager::Private::updateCursor(const QPointF &docPos) { KisCanvas2 *canvas = view->canvasBase(); const GuideHandle guide = findGuide(docPos); const bool guideValid = isGuideValid(guide); if (guideValid && !cursorSwitched) { oldCursor = canvas->canvasWidget()->cursor(); } if (guideValid) { cursorSwitched = true; QCursor newCursor = guide.first == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor; canvas->canvasWidget()->setCursor(newCursor); } if (!guideValid && cursorSwitched) { canvas->canvasWidget()->setCursor(oldCursor); cursorSwitched = false; } return guideValid; } void KisGuidesManager::Private::initDragStart(const GuideHandle &guide, const QPointF &dragStart, qreal guideValue, bool snapToStart) { currentGuide = guide; dragStartDoc = dragStart; dragStartGuidePos = guideValue; dragPointerOffset = guide.first == Qt::Horizontal ? QPointF(0, dragStartGuidePos - dragStartDoc.y()) : QPointF(dragStartGuidePos - dragStartDoc.x(), 0); KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); snapGuide->reset(); if (snapToStart) { KisSnapLineStrategy *strategy = new KisSnapLineStrategy(); strategy->addLine(guide.first, guideValue); snapGuide->addCustomSnapStrategy(strategy); } } QPointF KisGuidesManager::Private::alignToPixels(const QPointF docPoint) { KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); QPoint imagePoint = converter->documentToImage(docPoint).toPoint(); return converter->imageToDocument(imagePoint); } bool KisGuidesManager::Private::mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers) { if (isGuideValid(currentGuide)) { KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); const QPointF snappedPos = snapGuide->snap(docPos, dragPointerOffset, modifiers); const QPointF offset = snappedPos - dragStartDoc; const qreal newValue = dragStartGuidePos + (currentGuide.first == Qt::Horizontal ? offset.y() : offset.x()); setGuideValue(currentGuide, newValue); q->setGuidesConfigImpl(guidesConfig); } return updateCursor(docPos); } bool KisGuidesManager::Private::mouseReleaseHandler(const QPointF &docPos) { + bool result = false; KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); if (isGuideValid(currentGuide)) { const QRectF docRect = converter->imageRectInDocumentPixels(); // TODO: enable work rect after we fix painting guides // outside canvas in openGL mode const QRectF workRect = KisAlgebra2D::blowRect(docRect, 0 /*0.2*/); if (!workRect.contains(docPos)) { deleteGuide(currentGuide); q->setGuidesConfigImpl(guidesConfig); + + /** + * When we delete a guide, it might happen that we are + * delting the last guide. Therefore we should eat the + * corresponding event so that the event filter would stop + * the filter processing. + */ + result = true; } currentGuide = invalidGuide; dragStartDoc = QPointF(); dragPointerOffset = QPointF(); dragStartGuidePos = 0; KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); snapGuide->reset(); updateSnappingStatus(guidesConfig); } - return updateCursor(docPos); + return updateCursor(docPos) | result; } QPointF KisGuidesManager::Private::getDocPointFromEvent(QEvent *event) { QPointF result; KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); result = alignToPixels(converter->widgetToDocument(mouseEvent->pos())); } else if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *tabletEvent = static_cast(event); result = alignToPixels(converter->widgetToDocument(tabletEvent->pos())); } return result; } Qt::MouseButton KisGuidesManager::Private::getButtonFromEvent(QEvent *event) { Qt::MouseButton button = Qt::NoButton; if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); button = mouseEvent->button(); } else if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *tabletEvent = static_cast(event); button = tabletEvent->button(); } return button; } bool KisGuidesManager::eventFilter(QObject *obj, QEvent *event) { if (!m_d->view || obj != m_d->view->canvasBase()->canvasWidget()) return false; bool retval = false; switch (event->type()) { case QEvent::Enter: case QEvent::Leave: case QEvent::TabletMove: case QEvent::MouseMove: { const QPointF docPos = m_d->getDocPointFromEvent(event); const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers(); retval = m_d->mouseMoveHandler(docPos, modifiers); break; } case QEvent::TabletPress: case QEvent::MouseButtonPress: { if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break; const QPointF docPos = m_d->getDocPointFromEvent(event); const Private::GuideHandle guide = m_d->findGuide(docPos); const bool guideValid = m_d->isGuideValid(guide); if (guideValid) { m_d->initDragStart(guide, docPos, m_d->guideValue(guide), true); } retval = m_d->updateCursor(docPos); break; } case QEvent::TabletRelease: case QEvent::MouseButtonRelease: { if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break; const QPointF docPos = m_d->getDocPointFromEvent(event); retval = m_d->mouseReleaseHandler(docPos); break; } default: break; } return !retval ? QObject::eventFilter(obj, event) : true; } void KisGuidesManager::slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos) { if (m_d->guidesConfig.lockGuides()) return; KisCanvas2 *canvas = m_d->view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos)); if (m_d->isGuideValid(m_d->currentGuide)) { const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers(); m_d->mouseMoveHandler(docPos, modifiers); } else { m_d->guidesConfig.setShowGuides(true); if (orientation == Qt::Horizontal) { QList guides = m_d->guidesConfig.horizontalGuideLines(); guides.append(docPos.y()); m_d->currentGuide.first = orientation; m_d->currentGuide.second = guides.size() - 1; m_d->guidesConfig.setHorizontalGuideLines(guides); m_d->initDragStart(m_d->currentGuide, docPos, docPos.y(), false); } else { QList guides = m_d->guidesConfig.verticalGuideLines(); guides.append(docPos.x()); m_d->currentGuide.first = orientation; m_d->currentGuide.second = guides.size() - 1; m_d->guidesConfig.setVerticalGuideLines(guides); m_d->initDragStart(m_d->currentGuide, docPos, docPos.x(), false); } setGuidesConfigImpl(m_d->guidesConfig); } } void KisGuidesManager::slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos) { Q_UNUSED(orientation); if (m_d->guidesConfig.lockGuides()) return; KisCanvas2 *canvas = m_d->view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos)); m_d->mouseReleaseHandler(docPos); } QAction* KisGuidesManager::Private::createShortenedAction(const QString &text, const QString &parentId, QObject *parent) { KisActionManager *actionManager = view->viewManager()->actionManager(); QAction *action = 0; KisAction *parentAction = 0; action = new QAction(text, parent); action->setCheckable(true); parentAction = actionManager->actionByName(parentId); action->setChecked(parentAction->isChecked()); connect(action, SIGNAL(toggled(bool)), parentAction, SLOT(setChecked(bool))); return action; } void KisGuidesManager::slotShowSnapOptions() { const QPoint pos = QCursor::pos(); QMenu menu; menu.addSection(i18n("Snap to:")); menu.addAction(m_d->createShortenedAction(i18n("Grid"), "view_snap_to_grid", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Guides"), "view_snap_to_guides", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Orthogonal"), "view_snap_orthogonal", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Node"), "view_snap_node", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Extension"), "view_snap_extension", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Intersection"), "view_snap_intersection", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Bounding Box"), "view_snap_bounding_box", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Image Bounds"), "view_snap_image_bounds", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Image Center"), "view_snap_image_center", &menu)); menu.exec(pos); } void KisGuidesManager::setSnapOrthogonal(bool value) { m_d->snapConfig.setOrthogonal(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapNode(bool value) { m_d->snapConfig.setNode(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapExtension(bool value) { m_d->snapConfig.setExtension(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapIntersection(bool value) { m_d->snapConfig.setIntersection(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapBoundingBox(bool value) { m_d->snapConfig.setBoundingBox(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapImageBounds(bool value) { m_d->snapConfig.setImageBounds(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapImageCenter(bool value) { m_d->snapConfig.setImageCenter(value); m_d->updateSnappingStatus(m_d->guidesConfig); } diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp index ff23241a09..4ab17cd0ae 100644 --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -1,535 +1,550 @@ /* 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() #define break_if_should_ignore_cursor_events() if (d->ignoringQtCursorEvents()) break; // Touch rejection: if touch is disabled on canvas, no need to block mouse press events #define touch_start_block_press_events() d->touchHasBlockedPressEvents = d->disableTouchOnCanvas; #define touch_stop_block_press_events() d->touchHasBlockedPressEvents = false; #define break_if_touch_blocked_press_events() if (d->touchHasBlockedPressEvents) break; #define touch_eat_one_mouse_press() if (d->disableTouchOnCanvas) d->eatOneMousePress(); KisInputManager::KisInputManager(QObject *parent) : QObject(parent), d(new Private(this)) { d->setupActions(); 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, int priority) { Private::PriorityList::iterator begin = d->priorityEventFilter.begin(); Private::PriorityList::iterator it = begin; Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(begin, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) return; it = std::find_if(begin, end, [priority] (const Private::PriorityPair &a) { return a.first > priority; }); d->priorityEventFilter.insert(it, qMakePair(priority, filter)); + d->priorityEventFilterSeqNo++; } void KisInputManager::detachPriorityEventFilter(QObject *filter) { Private::PriorityList::iterator it = d->priorityEventFilter.begin(); Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(it, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) { d->priorityEventFilter.erase(it); } } 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) { if (object != d->eventsReceiver) return false; if (d->eventEater.eventFilter(object, event)) return false; if (!d->matcher.hasRunningShortcut()) { + int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo; + for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) { const QPointer &filter = it->second; if (filter.isNull()) { it = d->priorityEventFilter.erase(it); + + d->priorityEventFilterSeqNo++; + savedPriorityEventFilterSeqNo++; continue; } if (filter->eventFilter(object, event)) return true; + + /** + * If the filter removed itself from the filters list or + * added something there, just exit the loop + */ + if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) { + return true; + } + ++it; } // 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_should_ignore_cursor_events(); 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_should_ignore_cursor_events(); 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 = KisExtendedModifiersMapper::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 = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); retval = d->matcher.keyReleased(key); } break; } case QEvent::MouseMove: { d->debugEvent(event); break_if_should_ignore_cursor_events(); 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(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); 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); 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(); d->eatOneMousePress(); break; } case QEvent::TouchBegin: touch_start_block_press_events(); touch_eat_one_mouse_press(); if (d->tryHidePopupPalette()) { retval = true; } else { 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() { auto toolManager = KoToolManager::instance(); auto tool = toolManager->toolById(canvas(), toolManager->activeToolId()); if (tool->isInTextMode()) { d->forwardAllEventsToTool = true; d->matcher.suppressAllActions(true); } else { d->forwardAllEventsToTool = false; d->matcher.suppressAllActions(false); } d->maskSyntheticEvents(tool->maskSyntheticEvents()); } 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/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp index 480f1c3a06..f89a7ecf6a 100644 --- a/libs/ui/input/kis_input_manager_p.cpp +++ b/libs/ui/input/kis_input_manager_p.cpp @@ -1,442 +1,443 @@ /* * 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" /** * 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. There are two painful cases to consider - a * mousePress event could arrive before the tabletPress event, or it could * arrive much later, e.g. after tabletRelease. The first was only seen on Linux * with Qt's XInput2 code, the solution was to hold onto mousePress events * temporarily and wait for tabletPress later, this is contained in git history * but is now removed. The second case is currently handled by the * eatOneMousePress function, which waits as long as necessary to detect and * block a single mouse press event. */ static bool isMouseEventType(QEvent::Type t) { return (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease || t == QEvent::MouseButtonDblClick); } bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event ) { Q_UNUSED(target) auto debugEvent = [&]() { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED]"); QMouseEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev,pre); } }; if (peckish && event->type() == QEvent::MouseButtonPress // Drop one mouse press following tabletPress or touchBegin && (static_cast(event)->button() == Qt::LeftButton)) { peckish = false; debugEvent(); return true; } else if (isMouseEventType(event->type()) && (hungry || (eatSyntheticEvents && static_cast(event)->source() != 0))) { // Drop mouse events if enabled or event was synthetic & synthetic events are disabled debugEvent(); return true; } return false; // All clear - let this one through! } void KisInputManager::Private::EventEater::activate() { if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) qDebug() << "Start ignoring mouse events."; hungry = true; } void KisInputManager::Private::EventEater::deactivate() { if (hungry && (KisTabletDebugger::instance()->debugEnabled())) dbgTablet << "Stop ignoring mouse events."; hungry = false; } void KisInputManager::Private::EventEater::eatOneMousePress() { // #if defined(Q_OS_WIN) // Enable on other platforms if getting full-pressure splotches peckish = true; // #endif } bool KisInputManager::Private::ignoringQtCursorEvents() { return eventEater.hungry; } void KisInputManager::Private::maskSyntheticEvents(bool value) { eventEater.eatSyntheticEvents = value; } KisInputManager::Private::Private(KisInputManager *qq) : q(qq) , moveEventCompressor(10 /* ms */, KisSignalCompressor::FIRST_ACTIVE) + , priorityEventFilterSeqNo(0) , canvasSwitcher(this, qq) { KisConfig cfg; disableTouchOnCanvas = cfg.disableTouchOnCanvas(); moveEventCompressor.setDelay(cfg.tabletEventsDelay()); testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents(); testingCompressBrushEvents = cfg.testingCompressBrushEvents(); } KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p) : QObject(p), d(_d), eatOneMouseStroke(false) { } void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas) { QObject *canvasWidget = canvas->canvasWidget(); 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); // 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); } else { delete 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(); Q_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; } 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() { eventEater.activate(); } void KisInputManager::Private::allowMouseEvents() { eventEater.deactivate(); } void KisInputManager::Private::eatOneMousePress() { eventEater.eatOneMousePress(); } 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/libs/ui/input/kis_input_manager_p.h b/libs/ui/input/kis_input_manager_p.h index 1cbe75aac4..75e99d7b9e 100644 --- a/libs/ui/input/kis_input_manager_p.h +++ b/libs/ui/input/kis_input_manager_p.h @@ -1,137 +1,138 @@ /* * 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); void setupActions(); void saveTouchEvent( QTouchEvent* event ); bool handleCompressedTabletEvent(QObject *object, QTabletEvent *tevent); KisInputManager *q; KisCanvas2 *canvas = 0; KisToolProxy *toolProxy = 0; bool forwardAllEventsToTool = false; bool ignoringQtCursorEvents(); 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; typedef QPair > PriorityPair; typedef QList PriorityList; PriorityList priorityEventFilter; + int priorityEventFilterSeqNo; void blockMouseEvents(); void allowMouseEvents(); void eatOneMousePress(); void maskSyntheticEvents(bool value); template void debugEvent(QEvent *event) { if (!KisTabletDebugger::instance()->debugEnabled()) return; QString msg1 = useBlocking && ignoringQtCursorEvents() ? "[BLOCKED] " : "[ ]"; Event *specificEvent = static_cast(event); dbgTablet << 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; }; CanvasSwitcher canvasSwitcher; struct EventEater { 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(); // On Windows, we sometimes receive mouse events very late, so watch & wait. void eatOneMousePress(); bool hungry{false}; // Continue eating mouse strokes bool peckish{false}; // Eat a single mouse press event bool eatSyntheticEvents{true}; // Mask all synthetic events }; EventEater eventEater; bool focusOnEnter = true; bool containsPointer = true; };