File indexing completed on 2024-05-19 04:24:58

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2008-2009 Jan Hambrecht <jaham@gmx.net>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "KoSnapGuide.h"
0008 #include "KoSnapProxy.h"
0009 #include "KoSnapStrategy.h"
0010 
0011 #include <KoPathShape.h>
0012 #include <KoPathPoint.h>
0013 #include <KoViewConverter.h>
0014 #include <KoCanvasBase.h>
0015 #include <KoCanvasResourceProvider.h>
0016 
0017 #include <QPainter>
0018 #include <QPainterPath>
0019 
0020 #include <math.h>
0021 #include "kis_pointer_utils.h"
0022 
0023 class Q_DECL_HIDDEN KoSnapGuide::Private
0024 {
0025 public:
0026     Private(KoCanvasBase *parentCanvas)
0027         : canvas(parentCanvas), additionalEditedShape(0), currentStrategy(0),
0028         active(true),
0029         snapDistance(10)
0030     {
0031     }
0032 
0033     ~Private()
0034     {
0035         strategies.clear();
0036     }
0037 
0038     KoCanvasBase *canvas;
0039     KoShape *additionalEditedShape;
0040 
0041     typedef QSharedPointer<KoSnapStrategy> KoSnapStrategySP;
0042     typedef QList<KoSnapStrategySP> StrategiesList;
0043     StrategiesList strategies;
0044     KoSnapStrategySP currentStrategy;
0045 
0046     KoSnapGuide::Strategies usedStrategies;
0047     bool active;
0048     int snapDistance;
0049     QList<KoPathPoint*> ignoredPoints;
0050     QList<KoShape*> ignoredShapes;
0051 };
0052 
0053 KoSnapGuide::KoSnapGuide(KoCanvasBase *canvas)
0054     : d(new Private(canvas))
0055 {
0056     d->strategies.append(toQShared(new GridSnapStrategy()));
0057     d->strategies.append(toQShared(new NodeSnapStrategy()));
0058     d->strategies.append(toQShared(new OrthogonalSnapStrategy()));
0059     d->strategies.append(toQShared(new ExtensionSnapStrategy()));
0060     d->strategies.append(toQShared(new IntersectionSnapStrategy()));
0061     d->strategies.append(toQShared(new BoundingBoxSnapStrategy()));
0062 }
0063 
0064 KoSnapGuide::~KoSnapGuide()
0065 {
0066 }
0067 
0068 void KoSnapGuide::setAdditionalEditedShape(KoShape *shape)
0069 {
0070     d->additionalEditedShape = shape;
0071 }
0072 
0073 KoShape *KoSnapGuide::additionalEditedShape() const
0074 {
0075     return d->additionalEditedShape;
0076 }
0077 
0078 void KoSnapGuide::enableSnapStrategy(Strategy type, bool value)
0079 {
0080     if (value) {
0081         d->usedStrategies |= type;
0082     } else {
0083         d->usedStrategies &= ~type;
0084     }
0085 }
0086 
0087 bool KoSnapGuide::isStrategyEnabled(Strategy type) const
0088 {
0089     return d->usedStrategies & type;
0090 }
0091 
0092 void KoSnapGuide::enableSnapStrategies(Strategies strategies)
0093 {
0094     d->usedStrategies = strategies;
0095 }
0096 
0097 KoSnapGuide::Strategies KoSnapGuide::enabledSnapStrategies() const
0098 {
0099     return d->usedStrategies;
0100 }
0101 
0102 bool KoSnapGuide::addCustomSnapStrategy(KoSnapStrategy *customStrategy)
0103 {
0104     if (!customStrategy || customStrategy->type() != CustomSnapping)
0105         return false;
0106 
0107     d->strategies.append(toQShared(customStrategy));
0108     return true;
0109 }
0110 
0111 void KoSnapGuide::overrideSnapStrategy(Strategy type, KoSnapStrategy *strategy)
0112 {
0113     for (auto it = d->strategies.begin(); it != d->strategies.end(); /*noop*/) {
0114         if ((*it)->type() == type) {
0115             if (strategy) {
0116                 *it = toQShared(strategy);
0117             } else {
0118                 it = d->strategies.erase(it);
0119             }
0120             return;
0121         } else {
0122             ++it;
0123         }
0124     }
0125 
0126     if (strategy) {
0127         d->strategies.append(toQShared(strategy));
0128     }
0129 }
0130 
0131 void KoSnapGuide::enableSnapping(bool on)
0132 {
0133     d->active = on;
0134 }
0135 
0136 bool KoSnapGuide::isSnapping() const
0137 {
0138     return d->active;
0139 }
0140 
0141 void KoSnapGuide::setSnapDistance(int distance)
0142 {
0143     d->snapDistance = qAbs(distance);
0144 }
0145 
0146 int KoSnapGuide::snapDistance() const
0147 {
0148     return d->snapDistance;
0149 }
0150 
0151 QPointF KoSnapGuide::snap(const QPointF &mousePosition, const QPointF &dragOffset, Qt::KeyboardModifiers modifiers)
0152 {
0153     QPointF pos = mousePosition + dragOffset;
0154     pos = snap(pos, modifiers);
0155     return pos - dragOffset;
0156 }
0157 
0158 QPointF KoSnapGuide::snap(const QPointF &mousePosition, Qt::KeyboardModifiers modifiers)
0159 {
0160     d->currentStrategy.clear();
0161 
0162     if (! d->active || (modifiers & Qt::ShiftModifier))
0163         return mousePosition;
0164 
0165     KoSnapProxy proxy(this);
0166 
0167     qreal minDistance = HUGE_VAL;
0168 
0169     qreal maxSnapDistance = d->canvas->viewConverter()->viewToDocument(QSizeF(d->snapDistance,
0170                 d->snapDistance)).width();
0171 
0172     foreach (Private::KoSnapStrategySP strategy, d->strategies) {
0173         if (d->usedStrategies & strategy->type() ||
0174             strategy->type() == GridSnapping ||
0175             strategy->type() == CustomSnapping) {
0176 
0177             if (! strategy->snap(mousePosition, &proxy, maxSnapDistance))
0178                 continue;
0179 
0180             QPointF snapCandidate = strategy->snappedPosition();
0181             qreal distance = KoSnapStrategy::squareDistance(snapCandidate, mousePosition);
0182             if (distance < minDistance) {
0183                 d->currentStrategy = strategy;
0184                 minDistance = distance;
0185             }
0186         }
0187     }
0188 
0189     if (! d->currentStrategy)
0190         return mousePosition;
0191 
0192     return d->currentStrategy->snappedPosition();
0193 }
0194 
0195 QRectF KoSnapGuide::boundingRect()
0196 {
0197     QRectF rect;
0198 
0199     if (d->currentStrategy) {
0200         rect = d->currentStrategy->decoration(*d->canvas->viewConverter()).boundingRect();
0201         return rect.adjusted(-2, -2, 2, 2);
0202     } else {
0203         return rect;
0204     }
0205 }
0206 
0207 void KoSnapGuide::paint(QPainter &painter, const KoViewConverter &converter)
0208 {
0209     if (! d->currentStrategy || ! d->active)
0210         return;
0211 
0212     QPainterPath decoration = d->currentStrategy->decoration(converter);
0213 
0214     int thickness = d->canvas->resourceManager()? d->canvas->resourceManager()->decorationThickness(): 1;
0215 
0216     painter.setBrush(Qt::NoBrush);
0217 
0218     QPen whitePen(Qt::white, thickness);
0219     whitePen.setCosmetic(true);
0220     whitePen.setStyle(Qt::SolidLine);
0221     painter.setPen(whitePen);
0222     painter.drawPath(decoration);
0223 
0224     QPen redPen(Qt::red, thickness);
0225     redPen.setCosmetic(true);
0226     redPen.setStyle(Qt::DotLine);
0227     painter.setPen(redPen);
0228     painter.drawPath(decoration);
0229 }
0230 
0231 KoCanvasBase *KoSnapGuide::canvas() const
0232 {
0233     return d->canvas;
0234 }
0235 
0236 void KoSnapGuide::setIgnoredPathPoints(const QList<KoPathPoint*> &ignoredPoints)
0237 {
0238     d->ignoredPoints = ignoredPoints;
0239 }
0240 
0241 QList<KoPathPoint*> KoSnapGuide::ignoredPathPoints() const
0242 {
0243     return d->ignoredPoints;
0244 }
0245 
0246 void KoSnapGuide::setIgnoredShapes(const QList<KoShape*> &ignoredShapes)
0247 {
0248     d->ignoredShapes = ignoredShapes;
0249 }
0250 
0251 QList<KoShape*> KoSnapGuide::ignoredShapes() const
0252 {
0253     return d->ignoredShapes;
0254 }
0255 
0256 void KoSnapGuide::reset()
0257 {
0258     d->currentStrategy.clear();
0259     d->additionalEditedShape = 0;
0260     d->ignoredPoints.clear();
0261     d->ignoredShapes.clear();
0262     // remove all custom strategies
0263     int strategyCount = d->strategies.count();
0264     for (int i = strategyCount-1; i >= 0; --i) {
0265         if (d->strategies[i]->type() == CustomSnapping) {
0266             d->strategies.removeAt(i);
0267         }
0268     }
0269 }
0270