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