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