File indexing completed on 2024-05-19 04:25:00

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2006-2007 Thomas Zander <zander@kde.org>
0003  * SPDX-FileCopyrightText: 2006-2011 Boudewijn Rempt <boud@valdyas.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 #include "KoToolProxy.h"
0008 #include "KoToolProxy_p.h"
0009 
0010 #include <QMimeData>
0011 #include <QUrl>
0012 #include <QTimer>
0013 #include <QApplication>
0014 #include <QTouchEvent>
0015 #include <QClipboard>
0016 
0017 #include <kundo2command.h>
0018 #include <KoProperties.h>
0019 
0020 #include <FlakeDebug.h>
0021 #include <klocalizedstring.h>
0022 
0023 #include "KoToolBase.h"
0024 #include "KoPointerEvent.h"
0025 #include "KoInputDevice.h"
0026 #include "KoToolManager_p.h"
0027 #include "KoToolSelection.h"
0028 #include "KoCanvasBase.h"
0029 #include "KoCanvasController.h"
0030 #include "KoShapeManager.h"
0031 #include "KoSelection.h"
0032 #include "KoShapeLayer.h"
0033 #include "KoShapeRegistry.h"
0034 #include "KoShapeController.h"
0035 #include "KoViewConverter.h"
0036 #include "KoShapeFactoryBase.h"
0037 #include "kis_assert.h"
0038 #include "kactioncollection.h"
0039 
0040 
0041 KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p)
0042     : parent(p)
0043 {
0044     scrollTimer.setInterval(100);
0045 }
0046 
0047 void KoToolProxyPrivate::timeout() // Auto scroll the canvas
0048 {
0049     Q_ASSERT(controller);
0050 
0051     QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY());
0052     QPoint origin = controller->canvas()->documentOrigin();
0053     QPoint viewPoint = widgetScrollPoint - origin - offset;
0054 
0055     QRectF mouseArea(viewPoint, QSizeF(10, 10));
0056     mouseArea.setTopLeft(mouseArea.center());
0057 
0058     controller->ensureVisible(mouseArea, true);
0059 
0060     QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY());
0061 
0062     QPoint moved = offset - newOffset;
0063     if (moved.isNull())
0064         return;
0065 
0066     widgetScrollPoint += moved;
0067 
0068     QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint);
0069     QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, QFlags<Qt::KeyboardModifier>());
0070     KoPointerEvent ev(&event, documentPoint);
0071     activeTool->mouseMoveEvent(&ev);
0072 }
0073 
0074 void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event)
0075 {
0076     if (controller == 0) return;
0077     if (!activeTool) return;
0078     if (!activeTool->wantsAutoScroll()) return;
0079     if (!event.isAccepted()) return;
0080     if (!isToolPressed) return;
0081     if (event.buttons() != Qt::LeftButton) return;
0082 
0083 
0084     widgetScrollPoint = event.pos();
0085 
0086     if (! scrollTimer.isActive())
0087         scrollTimer.start();
0088 }
0089 
0090 void KoToolProxyPrivate::selectionChanged(bool newSelection)
0091 {
0092     if (hasSelection == newSelection)
0093         return;
0094     hasSelection = newSelection;
0095     emit parent->selectionChanged(hasSelection);
0096 }
0097 
0098 bool KoToolProxyPrivate::isActiveLayerEditable()
0099 {
0100     if (!activeTool)
0101         return false;
0102 
0103     KoShapeManager * shapeManager = activeTool->canvas()->shapeManager();
0104     KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer();
0105     if (activeLayer && !activeLayer->isShapeEditable())
0106         return false;
0107     return true;
0108 }
0109 
0110 KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent)
0111     : QObject(parent),
0112       d(new KoToolProxyPrivate(this))
0113 {
0114     KoToolManager::instance()->priv()->registerToolProxy(this, canvas);
0115 
0116     connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout()));
0117 }
0118 
0119 KoToolProxy::~KoToolProxy()
0120 {
0121     delete d;
0122 }
0123 
0124 void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter)
0125 {
0126     if (d->activeTool) d->activeTool->paint(painter, converter);
0127 }
0128 
0129 void KoToolProxy::repaintDecorations()
0130 {
0131     if (d->activeTool) d->activeTool->repaintDecorations();
0132 }
0133 
0134 
0135 QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const
0136 {
0137     QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY());
0138     QPoint origin = d->controller->canvas()->documentOrigin();
0139     QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset);
0140 
0141     return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint);
0142 }
0143 
0144 KoCanvasBase* KoToolProxy::canvas() const
0145 {
0146     return d->controller->canvas();
0147 }
0148 
0149 void KoToolProxy::countMultiClick(KoPointerEvent *ev, int eventType)
0150 {
0151     QPointF globalPoint = ev->globalPos();
0152 
0153     if (d->multiClickSource != eventType) {
0154         d->multiClickCount = 0;
0155     }
0156 
0157     if (d->multiClickGlobalPoint != globalPoint) {
0158         if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5||
0159                 qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) {
0160             d->multiClickCount = 0;
0161         }
0162         d->multiClickGlobalPoint = globalPoint;
0163     }
0164 
0165     if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) {
0166         // One more multiclick;
0167         d->multiClickCount++;
0168     } else {
0169         d->multiClickTimeStamp.start();
0170         d->multiClickCount = 1;
0171         d->multiClickSource = QEvent::Type(eventType);
0172     }
0173 
0174     if (d->activeTool) {
0175         switch (d->multiClickCount) {
0176         case 0:
0177         case 1:
0178             d->activeTool->mousePressEvent(ev);
0179             break;
0180         case 2:
0181             d->activeTool->mouseDoubleClickEvent(ev);
0182             break;
0183         case 3:
0184         default:
0185             d->activeTool->mouseTripleClickEvent(ev);
0186             break;
0187         }
0188     } else {
0189         d->multiClickCount = 0;
0190         ev->ignore();
0191     }
0192 
0193 }
0194 
0195 void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point)
0196 {
0197     // We get these events exclusively from KisToolProxy - accept them
0198     event->accept();
0199 
0200     KoInputDevice id(event->device(), event->pointerType(), event->uniqueId());
0201     KoToolManager::instance()->priv()->switchInputDevice(id);
0202 
0203     KoPointerEvent ev(event, point);
0204 
0205     switch (event->type()) {
0206     case QEvent::TabletPress:
0207         countMultiClick(&ev, event->type());
0208         break;
0209     case QEvent::TabletRelease:
0210         d->scrollTimer.stop();
0211         if (d->activeTool)
0212             d->activeTool->mouseReleaseEvent(&ev);
0213         break;
0214     case QEvent::TabletMove:
0215         if (d->activeTool)
0216             d->activeTool->mouseMoveEvent(&ev);
0217         d->checkAutoScroll(ev);
0218     default:
0219         ; // ignore the rest.
0220     }
0221 
0222     d->mouseLeaveWorkaround = true;
0223     d->lastPointerEvent = ev.deepCopyEvent();
0224 }
0225 
0226 void KoToolProxy::mousePressEvent(KoPointerEvent *ev)
0227 {
0228     d->mouseLeaveWorkaround = false;
0229     KoInputDevice id;
0230     KoToolManager::instance()->priv()->switchInputDevice(id);
0231     d->mouseDownPoint = ev->pos();
0232 
0233 
0234     // this tries to make sure another mouse press event doesn't happen
0235     // before a release event happens
0236     if (d->isToolPressed) {
0237         mouseReleaseEvent(ev);
0238         d->scrollTimer.stop();
0239 
0240         if (d->activeTool) {
0241             d->activeTool->mouseReleaseEvent(ev);
0242         }
0243 
0244         d->isToolPressed = false;
0245 
0246         return;
0247     }
0248 
0249     countMultiClick(ev, QEvent::MouseButtonPress);
0250 
0251     d->isToolPressed = true;
0252 }
0253 
0254 void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point)
0255 {
0256     KoPointerEvent ev(event, point);
0257     mousePressEvent(&ev);
0258     d->lastPointerEvent = ev.deepCopyEvent();
0259 }
0260 
0261 void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point)
0262 {
0263     KoPointerEvent ev(event, point);
0264     mouseDoubleClickEvent(&ev);
0265     d->lastPointerEvent = ev.deepCopyEvent();
0266 }
0267 
0268 void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event)
0269 {
0270     // let us handle it as any other mousepress (where we then detect multi clicks
0271     mousePressEvent(event);
0272 }
0273 
0274 void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point)
0275 {
0276     KoPointerEvent ev(event, point);
0277     mouseMoveEvent(&ev);
0278     d->lastPointerEvent = ev.deepCopyEvent();
0279 }
0280 
0281 void KoToolProxy::mouseMoveEvent(KoPointerEvent *event)
0282 {
0283     if (d->mouseLeaveWorkaround) {
0284         d->mouseLeaveWorkaround = false;
0285         return;
0286     }
0287     KoInputDevice id;
0288     KoToolManager::instance()->priv()->switchInputDevice(id);
0289     if (d->activeTool == 0) {
0290         event->ignore();
0291         return;
0292     }
0293 
0294     d->activeTool->mouseMoveEvent(event);
0295 
0296     d->checkAutoScroll(*event);
0297 }
0298 
0299 void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point)
0300 {
0301     KoPointerEvent ev(event, point);
0302     mouseReleaseEvent(&ev);
0303     d->lastPointerEvent = ev.deepCopyEvent();
0304 }
0305 
0306 void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event)
0307 {
0308     d->mouseLeaveWorkaround = false;
0309     KoInputDevice id;
0310     KoToolManager::instance()->priv()->switchInputDevice(id);
0311     d->scrollTimer.stop();
0312 
0313     if (d->activeTool) {
0314         d->activeTool->mouseReleaseEvent(event);
0315     } else {
0316         event->ignore();
0317     }
0318 
0319     d->isToolPressed = false;
0320 }
0321 
0322 void KoToolProxy::keyPressEvent(QKeyEvent *event)
0323 {
0324     if (d->activeTool)
0325         d->activeTool->keyPressEvent(event);
0326     else
0327         event->ignore();
0328 }
0329 
0330 void KoToolProxy::keyReleaseEvent(QKeyEvent *event)
0331 {
0332     if (d->activeTool)
0333         d->activeTool->keyReleaseEvent(event);
0334     else
0335         event->ignore();
0336 
0337     d->isToolPressed = false;
0338 }
0339 
0340 void KoToolProxy::explicitUserStrokeEndRequest()
0341 {
0342     if (d->activeTool) {
0343         d->activeTool->explicitUserStrokeEndRequest();
0344     }
0345 }
0346 
0347 QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query) const
0348 {
0349     if (d->activeTool)
0350         return d->activeTool->inputMethodQuery(query);
0351     return QVariant();
0352 }
0353 
0354 void KoToolProxy::inputMethodEvent(QInputMethodEvent *event)
0355 {
0356     if (d->activeTool) d->activeTool->inputMethodEvent(event);
0357 }
0358 
0359 void KoToolProxy::focusInEvent(QFocusEvent *event)
0360 {
0361     if (d->activeTool) d->activeTool->focusInEvent(event);
0362 }
0363 
0364 void KoToolProxy::focusOutEvent(QFocusEvent *event)
0365 {
0366     if (d->activeTool) d->activeTool->focusOutEvent(event);
0367 }
0368 
0369 QMenu *KoToolProxy::popupActionsMenu()
0370 {
0371     return d->activeTool ? d->activeTool->popupActionsMenu() : 0;
0372 }
0373 
0374 KisPopupWidgetInterface* KoToolProxy::popupWidget()
0375 {
0376     return d->activeTool ? d->activeTool->popupWidget() : nullptr;
0377 }
0378 
0379 void KoToolProxy::setActiveTool(KoToolBase *tool)
0380 {
0381     if (d->activeTool) {
0382         disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool)));
0383         d->toolPriorityShortcuts.clear();
0384     }
0385 
0386     d->activeTool = tool;
0387 
0388     if (tool) {
0389         KisKActionCollection *collection = d->controller->actionCollection();
0390         KIS_SAFE_ASSERT_RECOVER_NOOP(collection);
0391         if (collection) {
0392             Q_FOREACH(QAction *action, collection->actions()) {
0393 
0394                 const QVariant prop = action->property("tool_action");
0395 
0396                 if (prop.isValid()) {
0397                     const QStringList tools = prop.toStringList();
0398 
0399                     if (tools.contains(d->activeTool->toolId())) {
0400                         const QList<QKeySequence> shortcuts = action->shortcuts();
0401                         std::copy(shortcuts.begin(), shortcuts.end(),
0402                                   std::back_inserter(d->toolPriorityShortcuts));
0403                     }
0404                 }
0405             }
0406         }
0407 
0408         connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool)));
0409         d->selectionChanged(hasSelection());
0410         emit toolChanged(tool->toolId());
0411     }
0412 }
0413 
0414 void KoToolProxy::touchEvent(QTouchEvent* event, const QPointF& point)
0415 {
0416     // only one "touchpoint" events should be here
0417     KoPointerEvent ev(event, point);
0418 
0419     if (!d->activeTool) return;
0420 
0421     switch (event->touchPointStates())
0422     {
0423     case Qt::TouchPointPressed:
0424         d->activeTool->mousePressEvent(&ev);
0425         break;
0426     case Qt::TouchPointMoved:
0427         d->activeTool->mouseMoveEvent(&ev);
0428         break;
0429     case Qt::TouchPointReleased:
0430         d->activeTool->mouseReleaseEvent(&ev);
0431         break;
0432     default: // don't care
0433         ;
0434     }
0435 
0436     d->lastPointerEvent = ev.deepCopyEvent();
0437 }
0438 
0439 KoPointerEvent *KoToolProxy::lastDeliveredPointerEvent() const
0440 {
0441     return d->lastPointerEvent ? &(d->lastPointerEvent->event) : 0;
0442 }
0443 
0444 QVector<QKeySequence> KoToolProxy::toolPriorityShortcuts() const
0445 {
0446     return d->toolPriorityShortcuts;
0447 }
0448 
0449 void KoToolProxyPrivate::setCanvasController(KoCanvasController *c)
0450 {
0451     controller = c;
0452 }
0453 
0454 bool KoToolProxy::hasSelection() const
0455 {
0456     return d->activeTool ? d->activeTool->hasSelection() : false;
0457 }
0458 
0459 void KoToolProxy::cut()
0460 {
0461     if (d->activeTool && d->isActiveLayerEditable())
0462         d->activeTool->cut();
0463 }
0464 
0465 void KoToolProxy::copy() const
0466 {
0467     if (d->activeTool)
0468         d->activeTool->copy();
0469 }
0470 
0471 bool KoToolProxy::paste()
0472 {
0473     bool success = false;
0474 
0475     if (d->activeTool && d->isActiveLayerEditable()) {
0476         success = d->activeTool->paste();
0477     }
0478 
0479     return success;
0480 }
0481 
0482 bool KoToolProxy::selectAll()
0483 {
0484     bool success = false;
0485 
0486     if (d->activeTool && d->isActiveLayerEditable()) {
0487         success = d->activeTool->selectAll();
0488     }
0489 
0490     return success;
0491 }
0492 
0493 void KoToolProxy::deselect()
0494 {
0495     if (d->activeTool)
0496         d->activeTool->deselect();
0497 }
0498 
0499 void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point)
0500 {
0501     if (d->activeTool)
0502         d->activeTool->dragMoveEvent(event, point);
0503 }
0504 
0505 void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event)
0506 {
0507     if (d->activeTool)
0508         d->activeTool->dragLeaveEvent(event);
0509 }
0510 
0511 void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point)
0512 {
0513     if (d->activeTool)
0514         d->activeTool->dropEvent(event, point);
0515 }
0516 
0517 void KoToolProxy::deleteSelection()
0518 {
0519     if (d->activeTool)
0520         d->activeTool->deleteSelection();
0521 }
0522 
0523 void KoToolProxy::processEvent(QEvent *e) const
0524 {
0525     if(e->type()==QEvent::ShortcutOverride
0526             && d->activeTool
0527             && d->activeTool->isInTextMode()
0528             && (static_cast<QKeyEvent*>(e)->modifiers()==Qt::NoModifier ||
0529                 static_cast<QKeyEvent*>(e)->modifiers()==Qt::ShiftModifier
0530 #ifdef Q_OS_WIN
0531             // we should disallow AltGr shortcuts if a text box is in focus
0532             || (static_cast<QKeyEvent*>(e)->modifiers()==(Qt::AltModifier | Qt::ControlModifier) &&
0533                 static_cast<QKeyEvent*>(e)->key() < Qt::Key_Escape)
0534 #endif
0535             )) {
0536         e->accept();
0537     }
0538 }
0539 
0540 void KoToolProxy::requestUndoDuringStroke()
0541 {
0542     if (d->activeTool) {
0543         d->activeTool->requestUndoDuringStroke();
0544     }
0545 }
0546 
0547 void KoToolProxy::requestRedoDuringStroke()
0548 {
0549     if (d->activeTool) {
0550         d->activeTool->requestRedoDuringStroke();
0551     }
0552 }
0553 
0554 void KoToolProxy::requestStrokeCancellation()
0555 {
0556     if (d->activeTool) {
0557         d->activeTool->requestStrokeCancellation();
0558     }
0559 }
0560 
0561 void KoToolProxy::requestStrokeEnd()
0562 {
0563     if (d->activeTool) {
0564         d->activeTool->requestStrokeEnd();
0565     }
0566 }
0567 
0568 KoToolProxyPrivate *KoToolProxy::priv()
0569 {
0570     return d;
0571 }
0572 
0573 //have to include this because of Q_PRIVATE_SLOT
0574 #include "moc_KoToolProxy.cpp"