File indexing completed on 2024-05-12 15:56:50

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