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 }