File indexing completed on 2025-02-23 04:09:00
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_guides_manager.h" 0008 0009 #include <QMenu> 0010 #include <QGuiApplication> 0011 #include "kis_guides_decoration.h" 0012 #include <KoRuler.h> 0013 #include "kis_guides_config.h" 0014 #include "kis_action_manager.h" 0015 #include "kis_action.h" 0016 #include "kis_signals_blocker.h" 0017 #include "input/kis_input_manager.h" 0018 #include "kis_coordinates_converter.h" 0019 #include "kis_zoom_manager.h" 0020 #include "kis_signal_auto_connection.h" 0021 #include "KisViewManager.h" 0022 #include "KisDocument.h" 0023 #include "kis_algebra_2d.h" 0024 #include <KoSnapGuide.h> 0025 #include "kis_snap_line_strategy.h" 0026 #include "kis_change_guides_command.h" 0027 #include "kis_snap_config.h" 0028 #include "kis_canvas2.h" 0029 #include "kis_signal_compressor.h" 0030 #include "kis_floating_message.h" 0031 0032 struct KisGuidesManager::Private 0033 { 0034 Private(KisGuidesManager *_q) 0035 : q(_q), 0036 decoration(0), 0037 invalidGuide(Qt::Horizontal, -1), 0038 currentGuide(invalidGuide), 0039 cursorSwitched(false), 0040 dragStartGuidePos(0), 0041 shouldSetModified(false) {} 0042 0043 KisGuidesManager *q; 0044 0045 KisGuidesDecoration *decoration; 0046 KisGuidesConfig guidesConfig; 0047 KisGuidesConfig oldGuidesConfig; 0048 KisSnapConfig snapConfig; 0049 QPointer<KisView> view; 0050 0051 typedef QPair<Qt::Orientation, int> GuideHandle; 0052 0053 GuideHandle findGuide(const QPointF &docPos); 0054 bool isGuideValid(const GuideHandle &h); 0055 qreal guideValue(const GuideHandle &h); 0056 void setGuideValue(const GuideHandle &h, qreal value); 0057 void deleteGuide(const GuideHandle &h); 0058 const GuideHandle invalidGuide; 0059 0060 bool updateCursor(const QPointF &docPos, bool forceDisableCursor = false); 0061 0062 void initDragStart(const GuideHandle &guide, 0063 const QPointF &dragStart, 0064 qreal guideValue, 0065 bool snapToStart); 0066 bool mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers); 0067 bool mouseReleaseHandler(const QPointF &docPos); 0068 0069 void updateSnappingStatus(const KisGuidesConfig &value); 0070 QPointF alignToPixels(const QPointF docPoint); 0071 QPointF getDocPointFromEvent(QEvent *event); 0072 Qt::MouseButton getButtonFromEvent(QEvent *event); 0073 QAction* createShortenedAction(const QString &text, const QString &parentId, QObject *parent); 0074 void syncAction(const QString &actionName, bool value); 0075 bool needsUndoCommand(); 0076 void createUndoCommandIfNeeded(); 0077 0078 GuideHandle currentGuide; 0079 0080 bool cursorSwitched; 0081 QCursor oldCursor; 0082 0083 QPointF dragStartDoc; 0084 QPointF dragPointerOffset; 0085 qreal dragStartGuidePos; 0086 0087 KisSignalAutoConnectionsStore viewConnections; 0088 0089 bool shouldSetModified; 0090 }; 0091 0092 KisGuidesManager::KisGuidesManager(QObject *parent) 0093 : QObject(parent), 0094 m_d(new Private(this)) 0095 { 0096 } 0097 0098 KisGuidesManager::~KisGuidesManager() 0099 { 0100 } 0101 0102 void KisGuidesManager::setGuidesConfig(const KisGuidesConfig &config) 0103 { 0104 if (config == m_d->guidesConfig) return; 0105 setGuidesConfigImpl(config, !config.hasSamePositionAs(m_d->guidesConfig)); 0106 slotUploadConfigToDocument(); 0107 } 0108 0109 void KisGuidesManager::slotDocumentRequestedConfig(const KisGuidesConfig &config) 0110 { 0111 if (config == m_d->guidesConfig) return; 0112 setGuidesConfigImpl(config, false); 0113 } 0114 0115 void KisGuidesManager::slotUploadConfigToDocument() 0116 { 0117 const KisGuidesConfig &value = m_d->guidesConfig; 0118 0119 KisDocument *doc = m_d->view ? m_d->view->document() : 0; 0120 if (doc) { 0121 KisSignalsBlocker b(doc); 0122 0123 // we've made KisChangeGuidesCommand post-exec, so in all situations 0124 // we will replace the whole config 0125 doc->setGuidesConfig(value); 0126 0127 value.saveStaticData(); 0128 } 0129 0130 m_d->shouldSetModified = false; 0131 } 0132 0133 void KisGuidesManager::setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified) 0134 { 0135 m_d->guidesConfig = value; 0136 0137 if (m_d->decoration && value != m_d->decoration->guidesConfig()) { 0138 m_d->decoration->setVisible(value.showGuides()); 0139 m_d->decoration->setGuidesConfig(value); 0140 } 0141 0142 m_d->shouldSetModified |= emitModified; 0143 0144 const bool shouldFilterEvent = 0145 value.showGuides() && !value.lockGuides() && value.hasGuides(); 0146 0147 attachEventFilterImpl(shouldFilterEvent); 0148 syncActionsStatus(); 0149 0150 if (!m_d->isGuideValid(m_d->currentGuide)) { 0151 m_d->updateSnappingStatus(value); 0152 } 0153 0154 if (m_d->view) { 0155 m_d->view->document()->setUnit(KoUnit(m_d->guidesConfig.unitType())); 0156 m_d->view->viewManager()->actionManager()->actionByName("ruler_pixel_multiple2")->setChecked(value.rulersMultiple2()); 0157 } 0158 0159 emit sigRequestUpdateGuidesConfig(m_d->guidesConfig); 0160 } 0161 0162 void KisGuidesManager::attachEventFilterImpl(bool value) 0163 { 0164 if (!m_d->view) return; 0165 0166 KisInputManager *inputManager = m_d->view->globalInputManager(); 0167 if (inputManager) { 0168 if (value) { 0169 inputManager->attachPriorityEventFilter(this, 100); 0170 } else { 0171 inputManager->detachPriorityEventFilter(this); 0172 } 0173 } 0174 } 0175 0176 void KisGuidesManager::Private::syncAction(const QString &actionName, bool value) 0177 { 0178 KisActionManager *actionManager = view->viewManager()->actionManager(); 0179 KisAction *action = actionManager->actionByName(actionName); 0180 KIS_ASSERT_RECOVER_RETURN(action); 0181 KisSignalsBlocker b(action); 0182 action->setChecked(value); 0183 } 0184 0185 bool KisGuidesManager::Private::needsUndoCommand() 0186 { 0187 return !(oldGuidesConfig.hasSamePositionAs(guidesConfig)); 0188 } 0189 0190 void KisGuidesManager::Private::createUndoCommandIfNeeded() 0191 { 0192 KisDocument *doc = view ? view->document() : 0; 0193 if (doc && needsUndoCommand()) { 0194 KUndo2Command *cmd = new KisChangeGuidesCommand(doc, oldGuidesConfig, guidesConfig); 0195 view->canvasBase()->addCommand(cmd); 0196 } 0197 } 0198 0199 void KisGuidesManager::syncActionsStatus() 0200 { 0201 if (!m_d->view) return; 0202 0203 m_d->syncAction("view_show_guides", m_d->guidesConfig.showGuides()); 0204 m_d->syncAction("view_lock_guides", m_d->guidesConfig.lockGuides()); 0205 m_d->syncAction("view_snap_to_guides", m_d->guidesConfig.snapToGuides()); 0206 0207 m_d->syncAction("view_snap_orthogonal", m_d->snapConfig.orthogonal()); 0208 m_d->syncAction("view_snap_node", m_d->snapConfig.node()); 0209 m_d->syncAction("view_snap_extension", m_d->snapConfig.extension()); 0210 m_d->syncAction("view_snap_intersection", m_d->snapConfig.intersection()); 0211 m_d->syncAction("view_snap_bounding_box", m_d->snapConfig.boundingBox()); 0212 m_d->syncAction("view_snap_image_bounds", m_d->snapConfig.imageBounds()); 0213 m_d->syncAction("view_snap_image_center", m_d->snapConfig.imageCenter()); 0214 m_d->syncAction("view_snap_to_pixel",m_d->snapConfig.toPixel()); 0215 } 0216 0217 void KisGuidesManager::Private::updateSnappingStatus(const KisGuidesConfig &value) 0218 { 0219 if (!view) return; 0220 0221 KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); 0222 KisSnapLineStrategy *guidesSnap = 0; 0223 0224 if (value.snapToGuides()) { 0225 guidesSnap = new KisSnapLineStrategy(KoSnapGuide::GuideLineSnapping); 0226 guidesSnap->setHorizontalLines(value.horizontalGuideLines()); 0227 guidesSnap->setVerticalLines(value.verticalGuideLines()); 0228 } 0229 0230 snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap); 0231 snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap); 0232 0233 snapGuide->enableSnapStrategy(KoSnapGuide::OrthogonalSnapping, snapConfig.orthogonal()); 0234 snapGuide->enableSnapStrategy(KoSnapGuide::NodeSnapping, snapConfig.node()); 0235 snapGuide->enableSnapStrategy(KoSnapGuide::ExtensionSnapping, snapConfig.extension()); 0236 snapGuide->enableSnapStrategy(KoSnapGuide::IntersectionSnapping, snapConfig.intersection()); 0237 snapGuide->enableSnapStrategy(KoSnapGuide::BoundingBoxSnapping, snapConfig.boundingBox()); 0238 snapGuide->enableSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, snapConfig.imageBounds()); 0239 snapGuide->enableSnapStrategy(KoSnapGuide::DocumentCenterSnapping, snapConfig.imageCenter()); 0240 snapGuide->enableSnapStrategy(KoSnapGuide::PixelSnapping, snapConfig.toPixel()); 0241 0242 snapConfig.saveStaticData(); 0243 } 0244 0245 bool KisGuidesManager::showGuides() const 0246 { 0247 return m_d->guidesConfig.showGuides(); 0248 } 0249 0250 void KisGuidesManager::setShowGuides(bool value) 0251 { 0252 m_d->guidesConfig.setShowGuides(value); 0253 setGuidesConfigImpl(m_d->guidesConfig); 0254 slotUploadConfigToDocument(); 0255 } 0256 0257 bool KisGuidesManager::lockGuides() const 0258 { 0259 return m_d->guidesConfig.lockGuides(); 0260 } 0261 0262 void KisGuidesManager::setLockGuides(bool value) 0263 { 0264 m_d->guidesConfig.setLockGuides(value); 0265 setGuidesConfigImpl(m_d->guidesConfig); 0266 slotUploadConfigToDocument(); 0267 } 0268 0269 bool KisGuidesManager::snapToGuides() const 0270 { 0271 return m_d->guidesConfig.snapToGuides(); 0272 } 0273 0274 void KisGuidesManager::setSnapToGuides(bool value) 0275 { 0276 m_d->guidesConfig.setSnapToGuides(value); 0277 setGuidesConfigImpl(m_d->guidesConfig); 0278 slotUploadConfigToDocument(); 0279 } 0280 0281 bool KisGuidesManager::rulersMultiple2() const 0282 { 0283 return m_d->guidesConfig.rulersMultiple2(); 0284 } 0285 0286 void KisGuidesManager::setRulersMultiple2(bool value) 0287 { 0288 m_d->guidesConfig.setRulersMultiple2(value); 0289 setGuidesConfigImpl(m_d->guidesConfig); 0290 slotUploadConfigToDocument(); 0291 } 0292 0293 KoUnit::Type KisGuidesManager::unitType() const 0294 { 0295 return m_d->guidesConfig.unitType(); 0296 } 0297 0298 void KisGuidesManager::setUnitType(const KoUnit::Type type) 0299 { 0300 m_d->guidesConfig.setUnitType(type); 0301 setGuidesConfigImpl(m_d->guidesConfig, false); 0302 slotUploadConfigToDocument(); 0303 } 0304 0305 void KisGuidesManager::setup(KisActionManager *actionManager) 0306 { 0307 KisAction *action = 0; 0308 0309 action = actionManager->createAction("view_show_guides"); 0310 connect(action, SIGNAL(toggled(bool)), this, SLOT(setShowGuides(bool))); 0311 0312 action = actionManager->createAction("view_lock_guides"); 0313 connect(action, SIGNAL(toggled(bool)), this, SLOT(setLockGuides(bool))); 0314 0315 action = actionManager->createAction("view_snap_to_guides"); 0316 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToGuides(bool))); 0317 0318 action = actionManager->createAction("show_snap_options_popup"); 0319 connect(action, SIGNAL(triggered()), this, SLOT(slotShowSnapOptions())); 0320 0321 action = actionManager->createAction("view_snap_orthogonal"); 0322 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapOrthogonal(bool))); 0323 0324 action = actionManager->createAction("view_snap_node"); 0325 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapNode(bool))); 0326 0327 action = actionManager->createAction("view_snap_extension"); 0328 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapExtension(bool))); 0329 0330 action = actionManager->createAction("view_snap_intersection"); 0331 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapIntersection(bool))); 0332 0333 action = actionManager->createAction("view_snap_bounding_box"); 0334 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapBoundingBox(bool))); 0335 0336 action = actionManager->createAction("view_snap_image_bounds"); 0337 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageBounds(bool))); 0338 0339 action = actionManager->createAction("view_snap_image_center"); 0340 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageCenter(bool))); 0341 0342 action = actionManager->createAction("view_snap_to_pixel"); 0343 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToPixel(bool))); 0344 0345 m_d->updateSnappingStatus(m_d->guidesConfig); 0346 syncActionsStatus(); 0347 } 0348 0349 void KisGuidesManager::setView(QPointer<KisView> view) 0350 { 0351 if (m_d->view) { 0352 KoSnapGuide *snapGuide = m_d->view->canvasBase()->snapGuide(); 0353 snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, 0); 0354 snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, false); 0355 0356 slotUploadConfigToDocument(); 0357 0358 m_d->decoration = 0; 0359 m_d->viewConnections.clear(); 0360 attachEventFilterImpl(false); 0361 } 0362 0363 m_d->view = view; 0364 0365 if (m_d->view) { 0366 KisGuidesDecoration* decoration = qobject_cast<KisGuidesDecoration*>(m_d->view->canvasBase()->decoration(GUIDES_DECORATION_ID).data()); 0367 if (!decoration) { 0368 decoration = new KisGuidesDecoration(m_d->view); 0369 m_d->view->canvasBase()->addDecoration(decoration); 0370 } 0371 m_d->decoration = decoration; 0372 0373 m_d->guidesConfig = m_d->view->document()->guidesConfig(); 0374 setGuidesConfigImpl(m_d->guidesConfig, false); 0375 0376 m_d->viewConnections.addUniqueConnection( 0377 m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation,QPoint)), 0378 this, SLOT(slotGuideCreationInProgress(Qt::Orientation,QPoint))); 0379 0380 m_d->viewConnections.addUniqueConnection( 0381 m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation,QPoint)), 0382 this, SLOT(slotGuideCreationFinished(Qt::Orientation,QPoint))); 0383 0384 m_d->viewConnections.addUniqueConnection( 0385 m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation,QPoint)), 0386 this, SLOT(slotGuideCreationInProgress(Qt::Orientation,QPoint))); 0387 0388 m_d->viewConnections.addUniqueConnection( 0389 m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation,QPoint)), 0390 this, SLOT(slotGuideCreationFinished(Qt::Orientation,QPoint))); 0391 0392 m_d->viewConnections.addUniqueConnection( 0393 m_d->view->document(), SIGNAL(sigGuidesConfigChanged(KisGuidesConfig)), 0394 this, SLOT(slotDocumentRequestedConfig(KisGuidesConfig))); 0395 } 0396 } 0397 0398 KisGuidesManager::Private::GuideHandle 0399 KisGuidesManager::Private::findGuide(const QPointF &docPos) 0400 { 0401 const int snapRadius = 16; 0402 const KoViewConverter *converter = view->canvasBase()->viewConverter(); 0403 const QPointF docPosView = converter->documentToView(docPos); 0404 0405 GuideHandle nearestGuide = invalidGuide; 0406 qreal nearestRadius = std::numeric_limits<int>::max(); 0407 0408 0409 for (int i = 0; i < guidesConfig.horizontalGuideLines().size(); i++) { 0410 const QPointF guideCoord = {0, guidesConfig.horizontalGuideLines()[i]}; 0411 const qreal guide = converter->documentToView(guideCoord).y(); 0412 const qreal radius = qAbs(docPosView.y() - guide); 0413 if (radius < snapRadius && radius < nearestRadius) { 0414 nearestGuide = GuideHandle(Qt::Horizontal, i); 0415 nearestRadius = radius; 0416 } 0417 } 0418 0419 for (int i = 0; i < guidesConfig.verticalGuideLines().size(); i++) { 0420 const QPointF guideCoord = {guidesConfig.verticalGuideLines()[i], 0}; 0421 const qreal guide = converter->documentToView(guideCoord).x(); 0422 const qreal radius = qAbs(docPosView.x() - guide); 0423 if (radius < snapRadius && radius < nearestRadius) { 0424 nearestGuide = GuideHandle(Qt::Vertical, i); 0425 nearestRadius = radius; 0426 } 0427 } 0428 0429 return nearestGuide; 0430 } 0431 0432 bool KisGuidesManager::Private::isGuideValid(const GuideHandle &h) 0433 { 0434 return h.second >= 0; 0435 } 0436 0437 qreal KisGuidesManager::Private::guideValue(const GuideHandle &h) 0438 { 0439 return h.first == Qt::Horizontal ? 0440 guidesConfig.horizontalGuideLines()[h.second] : 0441 guidesConfig.verticalGuideLines()[h.second]; 0442 } 0443 0444 void KisGuidesManager::Private::setGuideValue(const GuideHandle &h, qreal value) 0445 { 0446 if (h.first == Qt::Horizontal) { 0447 QList<qreal> guides = guidesConfig.horizontalGuideLines(); 0448 guides[h.second] = value; 0449 guidesConfig.setHorizontalGuideLines(guides); 0450 } else { 0451 QList<qreal> guides = guidesConfig.verticalGuideLines(); 0452 guides[h.second] = value; 0453 guidesConfig.setVerticalGuideLines(guides); 0454 } 0455 } 0456 0457 void KisGuidesManager::Private::deleteGuide(const GuideHandle &h) 0458 { 0459 if (h.first == Qt::Horizontal) { 0460 QList<qreal> guides = guidesConfig.horizontalGuideLines(); 0461 guides.removeAt(h.second); 0462 guidesConfig.setHorizontalGuideLines(guides); 0463 } else { 0464 QList<qreal> guides = guidesConfig.verticalGuideLines(); 0465 guides.removeAt(h.second); 0466 guidesConfig.setVerticalGuideLines(guides); 0467 } 0468 } 0469 0470 bool KisGuidesManager::Private::updateCursor(const QPointF &docPos, bool forceDisableCursor) 0471 { 0472 KisCanvas2 *canvas = view->canvasBase(); 0473 0474 const GuideHandle guide = findGuide(docPos); 0475 const bool guideValid = isGuideValid(guide) && !forceDisableCursor; 0476 0477 if (guideValid && !cursorSwitched) { 0478 oldCursor = canvas->canvasWidget()->cursor(); 0479 } 0480 0481 if (guideValid) { 0482 cursorSwitched = true; 0483 QCursor newCursor = guide.first == Qt::Horizontal ? 0484 Qt::SizeVerCursor : Qt::SizeHorCursor; 0485 canvas->canvasWidget()->setCursor(newCursor); 0486 } 0487 0488 if (!guideValid && cursorSwitched) { 0489 canvas->canvasWidget()->setCursor(oldCursor); 0490 cursorSwitched = false; 0491 } 0492 0493 return guideValid; 0494 } 0495 0496 void KisGuidesManager::Private::initDragStart(const GuideHandle &guide, 0497 const QPointF &dragStart, 0498 qreal guideValue, 0499 bool snapToStart) 0500 { 0501 currentGuide = guide; 0502 dragStartDoc = dragStart; 0503 dragStartGuidePos = guideValue; 0504 dragPointerOffset = 0505 guide.first == Qt::Horizontal ? 0506 QPointF(0, dragStartGuidePos - dragStartDoc.y()) : 0507 QPointF(dragStartGuidePos - dragStartDoc.x(), 0); 0508 0509 KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); 0510 snapGuide->reset(); 0511 0512 if (snapToStart) { 0513 KisSnapLineStrategy *strategy = new KisSnapLineStrategy(); 0514 strategy->addLine(guide.first, guideValue); 0515 snapGuide->addCustomSnapStrategy(strategy); 0516 } 0517 } 0518 0519 QPointF KisGuidesManager::Private::alignToPixels(const QPointF docPoint) 0520 { 0521 KisCanvas2 *canvas = view->canvasBase(); 0522 const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); 0523 QPoint imagePoint = converter->documentToImage(docPoint).toPoint(); 0524 return converter->imageToDocument(imagePoint); 0525 } 0526 0527 bool KisGuidesManager::Private::mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers) 0528 { 0529 if (isGuideValid(currentGuide)) { 0530 KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); 0531 const QPointF snappedPos = snapGuide->snap(docPos, dragPointerOffset, modifiers); 0532 const QPointF offset = snappedPos - dragStartDoc; 0533 const qreal newValue = dragStartGuidePos + 0534 (currentGuide.first == Qt::Horizontal ? 0535 offset.y() : offset.x()); 0536 0537 setGuideValue(currentGuide, newValue); 0538 q->setGuidesConfigImpl(guidesConfig); 0539 0540 const KisCoordinatesConverter *converter = view->canvasBase()->coordinatesConverter(); 0541 if(currentGuide.first == Qt::Horizontal) { 0542 view->canvasBase()->viewManager()->showFloatingMessage( 0543 i18n("Y: %1 px", converter->documentToImage(docPos).toPoint().y()), QIcon(), 1000 0544 , KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter); 0545 } 0546 else { 0547 view->canvasBase()->viewManager()->showFloatingMessage( 0548 i18n("X: %1 px", converter->documentToImage(docPos).toPoint().x()), QIcon(), 1000 0549 , KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter); 0550 } 0551 } 0552 0553 return updateCursor(docPos); 0554 } 0555 0556 bool KisGuidesManager::Private::mouseReleaseHandler(const QPointF &docPos) 0557 { 0558 bool result = false; 0559 KisCanvas2 *canvas = view->canvasBase(); 0560 const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); 0561 0562 if (isGuideValid(currentGuide)) { 0563 const QRectF docRect = converter->imageRectInDocumentPixels(); 0564 // TODO: enable work rect after we fix painting guides 0565 // outside canvas in openGL mode 0566 const QRectF workRect = KisAlgebra2D::blowRect(docRect, 0 /*0.2*/); 0567 if (!workRect.contains(docPos)) { 0568 deleteGuide(currentGuide); 0569 q->setGuidesConfigImpl(guidesConfig); 0570 0571 /** 0572 * When we delete a guide, it might happen that we are 0573 * deleting the last guide. Therefore we should eat the 0574 * corresponding event so that the event filter would stop 0575 * the filter processing. 0576 */ 0577 result = true; 0578 } 0579 0580 currentGuide = invalidGuide; 0581 dragStartDoc = QPointF(); 0582 dragPointerOffset = QPointF(); 0583 dragStartGuidePos = 0; 0584 0585 KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); 0586 snapGuide->reset(); 0587 0588 updateSnappingStatus(guidesConfig); 0589 } 0590 0591 q->slotUploadConfigToDocument(); 0592 createUndoCommandIfNeeded(); 0593 0594 return updateCursor(docPos) | result; 0595 } 0596 0597 QPointF KisGuidesManager::Private::getDocPointFromEvent(QEvent *event) 0598 { 0599 QPointF result; 0600 0601 KisCanvas2 *canvas = view->canvasBase(); 0602 const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); 0603 0604 if (event->type() == QEvent::Enter) { 0605 QEnterEvent *enterEvent = static_cast<QEnterEvent*>(event); 0606 result = alignToPixels(converter->widgetToDocument(enterEvent->pos())); 0607 } else if (event->type() == QEvent::MouseMove || 0608 event->type() == QEvent::MouseButtonPress || 0609 event->type() == QEvent::MouseButtonRelease) { 0610 0611 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); 0612 result = alignToPixels(converter->widgetToDocument(mouseEvent->pos())); 0613 0614 } else if (event->type() == QEvent::TabletMove || 0615 event->type() == QEvent::TabletPress || 0616 event->type() == QEvent::TabletRelease) { 0617 0618 QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event); 0619 result = alignToPixels(converter->widgetToDocument(tabletEvent->pos())); 0620 } else { 0621 // we shouldn't silently return QPointF(0,0), higher level code may 0622 // snap to some unexpected guide 0623 KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "event type is not supported!"); 0624 } 0625 0626 return result; 0627 } 0628 0629 Qt::MouseButton KisGuidesManager::Private::getButtonFromEvent(QEvent *event) 0630 { 0631 Qt::MouseButton button = Qt::NoButton; 0632 0633 if (event->type() == QEvent::MouseMove || 0634 event->type() == QEvent::MouseButtonPress || 0635 event->type() == QEvent::MouseButtonRelease) { 0636 0637 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); 0638 button = mouseEvent->button(); 0639 0640 } else if (event->type() == QEvent::TabletMove || 0641 event->type() == QEvent::TabletPress || 0642 event->type() == QEvent::TabletRelease) { 0643 0644 QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event); 0645 button = tabletEvent->button(); 0646 } 0647 0648 return button; 0649 } 0650 0651 bool KisGuidesManager::eventFilter(QObject *obj, QEvent *event) 0652 { 0653 if (!m_d->view || obj != m_d->view->canvasBase()->canvasWidget()) return false; 0654 0655 bool retval = false; 0656 0657 switch (event->type()) { 0658 case QEvent::Leave: 0659 m_d->updateCursor(QPointF(), true); 0660 break; 0661 case QEvent::Enter: 0662 case QEvent::TabletMove: 0663 case QEvent::MouseMove: { 0664 const QPointF docPos = m_d->getDocPointFromEvent(event); 0665 const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers(); 0666 0667 // we should never eat Enter events, input manager may get crazy about it 0668 retval = m_d->mouseMoveHandler(docPos, modifiers) && event->type() != QEvent::Enter; 0669 0670 break; 0671 } 0672 case QEvent::TabletPress: 0673 case QEvent::MouseButtonPress: { 0674 if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break; 0675 0676 const QPointF docPos = m_d->getDocPointFromEvent(event); 0677 const Private::GuideHandle guide = m_d->findGuide(docPos); 0678 const bool guideValid = m_d->isGuideValid(guide); 0679 0680 if (guideValid) { 0681 m_d->oldGuidesConfig = m_d->guidesConfig; 0682 m_d->initDragStart(guide, docPos, m_d->guideValue(guide), true); 0683 } 0684 0685 retval = m_d->updateCursor(docPos); 0686 0687 break; 0688 } 0689 case QEvent::TabletRelease: 0690 case QEvent::MouseButtonRelease: { 0691 if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break; 0692 0693 const QPointF docPos = m_d->getDocPointFromEvent(event); 0694 retval = m_d->mouseReleaseHandler(docPos); 0695 0696 break; 0697 } 0698 default: 0699 break; 0700 } 0701 0702 return !retval ? QObject::eventFilter(obj, event) : true; 0703 } 0704 0705 void KisGuidesManager::slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos) 0706 { 0707 if (m_d->guidesConfig.lockGuides()) return; 0708 0709 KisCanvas2 *canvas = m_d->view->canvasBase(); 0710 const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); 0711 const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); 0712 const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos)); 0713 0714 if (m_d->isGuideValid(m_d->currentGuide)) { 0715 const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers(); 0716 m_d->mouseMoveHandler(docPos, modifiers); 0717 } else { 0718 m_d->guidesConfig.setShowGuides(true); 0719 0720 m_d->oldGuidesConfig = m_d->guidesConfig; 0721 0722 if (orientation == Qt::Horizontal) { 0723 QList<qreal> guides = m_d->guidesConfig.horizontalGuideLines(); 0724 guides.append(docPos.y()); 0725 m_d->currentGuide.first = orientation; 0726 m_d->currentGuide.second = guides.size() - 1; 0727 m_d->guidesConfig.setHorizontalGuideLines(guides); 0728 m_d->initDragStart(m_d->currentGuide, docPos, docPos.y(), false); 0729 } else { 0730 QList<qreal> guides = m_d->guidesConfig.verticalGuideLines(); 0731 guides.append(docPos.x()); 0732 m_d->currentGuide.first = orientation; 0733 m_d->currentGuide.second = guides.size() - 1; 0734 m_d->guidesConfig.setVerticalGuideLines(guides); 0735 m_d->initDragStart(m_d->currentGuide, docPos, docPos.x(), false); 0736 } 0737 0738 setGuidesConfigImpl(m_d->guidesConfig); 0739 } 0740 } 0741 0742 void KisGuidesManager::slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos) 0743 { 0744 Q_UNUSED(orientation); 0745 if (m_d->guidesConfig.lockGuides()) return; 0746 0747 KisCanvas2 *canvas = m_d->view->canvasBase(); 0748 const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); 0749 const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); 0750 const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos)); 0751 0752 m_d->mouseReleaseHandler(docPos); 0753 } 0754 0755 QAction* KisGuidesManager::Private::createShortenedAction(const QString &text, const QString &parentId, QObject *parent) 0756 { 0757 KisActionManager *actionManager = view->viewManager()->actionManager(); 0758 QAction *action = 0; 0759 KisAction *parentAction = 0; 0760 0761 action = new QAction(text, parent); 0762 action->setCheckable(true); 0763 parentAction = actionManager->actionByName(parentId); 0764 action->setChecked(parentAction->isChecked()); 0765 connect(action, SIGNAL(toggled(bool)), parentAction, SLOT(setChecked(bool))); 0766 0767 return action; 0768 } 0769 0770 void KisGuidesManager::slotShowSnapOptions() 0771 { 0772 const QPoint pos = QCursor::pos(); 0773 QMenu menu; 0774 0775 menu.addSection(i18n("Snap to:")); 0776 menu.addAction(m_d->createShortenedAction(i18n("Grid"), "view_snap_to_grid", &menu)); 0777 menu.addAction(m_d->createShortenedAction(i18n("Guides"), "view_snap_to_guides", &menu)); 0778 menu.addAction(m_d->createShortenedAction(i18n("Pixel"), "view_snap_to_pixel", &menu)); 0779 menu.addAction(m_d->createShortenedAction(i18n("Orthogonal"), "view_snap_orthogonal", &menu)); 0780 0781 menu.addAction(m_d->createShortenedAction(i18n("Node"), "view_snap_node", &menu)); 0782 menu.addAction(m_d->createShortenedAction(i18n("Extension"), "view_snap_extension", &menu)); 0783 menu.addAction(m_d->createShortenedAction(i18n("Intersection"), "view_snap_intersection", &menu)); 0784 0785 menu.addAction(m_d->createShortenedAction(i18n("Bounding Box"), "view_snap_bounding_box", &menu)); 0786 menu.addAction(m_d->createShortenedAction(i18n("Image Bounds"), "view_snap_image_bounds", &menu)); 0787 menu.addAction(m_d->createShortenedAction(i18n("Image Center"), "view_snap_image_center", &menu)); 0788 0789 menu.exec(pos); 0790 } 0791 0792 void KisGuidesManager::setSnapOrthogonal(bool value) 0793 { 0794 m_d->snapConfig.setOrthogonal(value); 0795 m_d->updateSnappingStatus(m_d->guidesConfig); 0796 } 0797 0798 void KisGuidesManager::setSnapNode(bool value) 0799 { 0800 m_d->snapConfig.setNode(value); 0801 m_d->updateSnappingStatus(m_d->guidesConfig); 0802 } 0803 0804 void KisGuidesManager::setSnapExtension(bool value) 0805 { 0806 m_d->snapConfig.setExtension(value); 0807 m_d->updateSnappingStatus(m_d->guidesConfig); 0808 } 0809 0810 void KisGuidesManager::setSnapIntersection(bool value) 0811 { 0812 m_d->snapConfig.setIntersection(value); 0813 m_d->updateSnappingStatus(m_d->guidesConfig); 0814 } 0815 0816 void KisGuidesManager::setSnapBoundingBox(bool value) 0817 { 0818 m_d->snapConfig.setBoundingBox(value); 0819 m_d->updateSnappingStatus(m_d->guidesConfig); 0820 } 0821 0822 void KisGuidesManager::setSnapImageBounds(bool value) 0823 { 0824 m_d->snapConfig.setImageBounds(value); 0825 m_d->updateSnappingStatus(m_d->guidesConfig); 0826 } 0827 0828 void KisGuidesManager::setSnapImageCenter(bool value) 0829 { 0830 m_d->snapConfig.setImageCenter(value); 0831 m_d->updateSnappingStatus(m_d->guidesConfig); 0832 } 0833 0834 void KisGuidesManager::setSnapToPixel(bool value) 0835 { 0836 m_d->snapConfig.setToPixel(value); 0837 m_d->updateSnappingStatus(m_d->guidesConfig); 0838 }