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"