1 /* This file is part of the KDE project
2  * Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include "KoSnapStrategy.h"
21 #include "KoSnapProxy.h"
22 #include "KoSnapGuide.h"
23 #include <KoPathShape.h>
24 #include <KoPathPoint.h>
25 #include <KoPathSegment.h>
26 #include <KoCanvasBase.h>
27 #include <KoViewConverter.h>
28 #include <KoGuidesData.h>
29 
30 #include <QPainter>
31 #include <QPainterPath>
32 
33 #include <cmath>
34 
KoSnapStrategy(KoSnapGuide::Strategy type)35 KoSnapStrategy::KoSnapStrategy(KoSnapGuide::Strategy type)
36     : m_snapType(type)
37 {
38 }
39 
snappedPosition() const40 QPointF KoSnapStrategy::snappedPosition() const
41 {
42     return m_snappedPosition;
43 }
44 
setSnappedPosition(const QPointF & position)45 void KoSnapStrategy::setSnappedPosition(const QPointF &position)
46 {
47     m_snappedPosition = position;
48 }
49 
type() const50 KoSnapGuide::Strategy KoSnapStrategy::type() const
51 {
52     return m_snapType;
53 }
54 
squareDistance(const QPointF & p1,const QPointF & p2)55 qreal KoSnapStrategy::squareDistance(const QPointF &p1, const QPointF &p2)
56 {
57     const qreal dx = p1.x() - p2.x();
58     const qreal dy = p1.y() - p2.y();
59 
60     return dx*dx + dy*dy;
61 }
62 
scalarProduct(const QPointF & p1,const QPointF & p2)63 qreal KoSnapStrategy::scalarProduct(const QPointF &p1, const QPointF &p2)
64 {
65     return p1.x() * p2.x() + p1.y() * p2.y();
66 }
67 
OrthogonalSnapStrategy()68 OrthogonalSnapStrategy::OrthogonalSnapStrategy()
69     : KoSnapStrategy(KoSnapGuide::OrthogonalSnapping)
70 {
71 }
72 
snap(const QPointF & mousePosition,KoSnapProxy * proxy,qreal maxSnapDistance)73 bool OrthogonalSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
74 {
75     Q_ASSERT(std::isfinite(maxSnapDistance));
76     QPointF horzSnap, vertSnap;
77     qreal minVertDist = HUGE_VAL;
78     qreal minHorzDist = HUGE_VAL;
79 
80     QList<KoShape*> shapes = proxy->shapes();
81     for (KoShape * shape : shapes) {
82         QVector<QPointF> points = proxy->pointsFromShape(shape);
83         // There exists a problem on msvc with for(each) and QVector<QPointF>
84         for (int i = 0; i < points.count(); ++i) {
85             const QPointF point(points[i]);
86             qreal dx = fabs(point.x() - mousePosition.x());
87             if (dx < minHorzDist && dx < maxSnapDistance) {
88                 minHorzDist = dx;
89                 horzSnap = point;
90             }
91             qreal dy = fabs(point.y() - mousePosition.y());
92             if (dy < minVertDist && dy < maxSnapDistance) {
93                 minVertDist = dy;
94                 vertSnap = point;
95             }
96         }
97     }
98 
99     QPointF snappedPoint = mousePosition;
100 
101     if (minHorzDist < HUGE_VAL)
102         snappedPoint.setX(horzSnap.x());
103     if (minVertDist < HUGE_VAL)
104         snappedPoint.setY(vertSnap.y());
105 
106     if (minHorzDist < HUGE_VAL)
107         m_hLine = QLineF(horzSnap, snappedPoint);
108     else
109         m_hLine = QLineF();
110 
111     if (minVertDist < HUGE_VAL)
112         m_vLine = QLineF(vertSnap, snappedPoint);
113     else
114         m_vLine = QLineF();
115 
116     setSnappedPosition(snappedPoint);
117 
118     return (minHorzDist < HUGE_VAL || minVertDist < HUGE_VAL);
119 }
120 
decoration(const KoViewConverter &) const121 QPainterPath OrthogonalSnapStrategy::decoration(const KoViewConverter &/*converter*/) const
122 {
123     QPainterPath decoration;
124     if (! m_hLine.isNull()) {
125         decoration.moveTo(m_hLine.p1());
126         decoration.lineTo(m_hLine.p2());
127     }
128     if (! m_vLine.isNull()) {
129         decoration.moveTo(m_vLine.p1());
130         decoration.lineTo(m_vLine.p2());
131     }
132     return decoration;
133 }
134 
NodeSnapStrategy()135 NodeSnapStrategy::NodeSnapStrategy()
136     : KoSnapStrategy(KoSnapGuide::NodeSnapping)
137 {
138 }
139 
snap(const QPointF & mousePosition,KoSnapProxy * proxy,qreal maxSnapDistance)140 bool NodeSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
141 {
142     Q_ASSERT(std::isfinite(maxSnapDistance));
143     const qreal maxDistance = maxSnapDistance * maxSnapDistance;
144     qreal minDistance = HUGE_VAL;
145 
146     QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
147     rect.moveCenter(mousePosition);
148     QVector<QPointF> points = proxy->pointsInRect(rect);
149     QPointF snappedPoint = mousePosition;
150 
151     // There exists a problem on msvc with for(each) and QVector<QPointF>
152     for (int i = 0; i < points.count(); ++i) {
153         const QPointF point(points[i]);
154         qreal distance = squareDistance(mousePosition, point);
155         if (distance < maxDistance && distance < minDistance) {
156             snappedPoint = point;
157             minDistance = distance;
158         }
159     }
160 
161     setSnappedPosition(snappedPoint);
162 
163     return (minDistance < HUGE_VAL);
164 }
165 
decoration(const KoViewConverter & converter) const166 QPainterPath NodeSnapStrategy::decoration(const KoViewConverter &converter) const
167 {
168     QRectF unzoomedRect = converter.viewToDocument(QRectF(0, 0, 11, 11));
169     unzoomedRect.moveCenter(snappedPosition());
170     QPainterPath decoration;
171     decoration.addEllipse(unzoomedRect);
172     return decoration;
173 }
174 
ExtensionSnapStrategy()175 ExtensionSnapStrategy::ExtensionSnapStrategy()
176     : KoSnapStrategy(KoSnapGuide::ExtensionSnapping)
177 {
178 }
179 
snap(const QPointF & mousePosition,KoSnapProxy * proxy,qreal maxSnapDistance)180 bool ExtensionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
181 {
182     Q_ASSERT(std::isfinite(maxSnapDistance));
183 
184     const qreal maxDistance = maxSnapDistance * maxSnapDistance;
185     // NOTE: HUGE_VAL with qreal, which can be either float or double,
186     // is not necessarily ideal, but seems to work good enough
187     qreal minDistances[2] = { (qreal)HUGE_VAL, (qreal)HUGE_VAL };
188 
189     QPointF snappedPoints[2] = { mousePosition, mousePosition };
190     QPointF startPoints[2];
191 
192     QList<KoShape*> shapes = proxy->shapes(true);
193 
194     for (KoShape * shape : shapes) {
195         KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
196         if (! path) {
197             continue;
198         }
199         QTransform matrix = path->absoluteTransformation(0);
200 
201         const int subpathCount = path->subpathCount();
202         for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
203             if (path->isClosedSubpath(subpathIndex))
204                 continue;
205 
206             int pointCount = path->subpathPointCount(subpathIndex);
207 
208             // check the extension from the start point
209             KoPathPoint * first = path->pointByIndex(KoPathPointIndex(subpathIndex, 0));
210             QPointF firstSnapPosition = mousePosition;
211             if (snapToExtension(firstSnapPosition, first, matrix)) {
212                 qreal distance = squareDistance(firstSnapPosition, mousePosition);
213                 if (distance < maxDistance) {
214                     if (distance < minDistances[0]) {
215                         minDistances[1] = minDistances[0];
216                         snappedPoints[1] = snappedPoints[0];
217                         startPoints[1] = startPoints[0];
218 
219                         minDistances[0] = distance;
220                         snappedPoints[0] = firstSnapPosition;
221                         startPoints[0] = matrix.map(first->point());
222                     }
223                     else if (distance < minDistances[1]) {
224                         minDistances[1] = distance;
225                         snappedPoints[1] = firstSnapPosition;
226                         startPoints[1] = matrix.map(first->point());
227                     }
228                 }
229             }
230 
231             // now check the extension from the last point
232             KoPathPoint * last = path->pointByIndex(KoPathPointIndex(subpathIndex, pointCount - 1));
233             QPointF lastSnapPosition = mousePosition;
234             if (snapToExtension(lastSnapPosition, last, matrix)) {
235                 qreal distance = squareDistance(lastSnapPosition, mousePosition);
236                 if (distance < maxDistance) {
237                     if (distance < minDistances[0]) {
238                         minDistances[1] = minDistances[0];
239                         snappedPoints[1] = snappedPoints[0];
240                         startPoints[1] = startPoints[0];
241 
242                         minDistances[0] = distance;
243                         snappedPoints[0] = lastSnapPosition;
244                         startPoints[0] = matrix.map(last->point());
245                     }
246                     else if (distance < minDistances[1]) {
247                         minDistances[1] = distance;
248                         snappedPoints[1] = lastSnapPosition;
249                         startPoints[1] = matrix.map(last->point());
250                     }
251                 }
252             }
253         }
254     }
255 
256     m_lines.clear();
257     // if we have to extension near our mouse position, they might have an intersection
258     // near our mouse position which we want to use as the snapped position
259     if (minDistances[0] < HUGE_VAL && minDistances[1] < HUGE_VAL) {
260         // check if intersection of extension lines is near mouse position
261         KoPathSegment s1(startPoints[0], snappedPoints[0] + snappedPoints[0]-startPoints[0]);
262         KoPathSegment s2(startPoints[1], snappedPoints[1] + snappedPoints[1]-startPoints[1]);
263         QVector<QPointF> isects = s1.intersections(s2);
264         if (isects.count() == 1 && squareDistance(isects[0], mousePosition) < maxDistance) {
265             // add both extension lines
266             m_lines.append(QLineF(startPoints[0], isects[0]));
267             m_lines.append(QLineF(startPoints[1], isects[0]));
268             setSnappedPosition(isects[0]);
269         }
270         else {
271             // only add nearest extension line of both
272             uint index = minDistances[0] < minDistances[1] ? 0 : 1;
273             m_lines.append(QLineF(startPoints[index], snappedPoints[index]));
274             setSnappedPosition(snappedPoints[index]);
275         }
276     }
277     else  if (minDistances[0] < HUGE_VAL) {
278         m_lines.append(QLineF(startPoints[0], snappedPoints[0]));
279         setSnappedPosition(snappedPoints[0]);
280     }
281     else if (minDistances[1] < HUGE_VAL) {
282         m_lines.append(QLineF(startPoints[1], snappedPoints[1]));
283         setSnappedPosition(snappedPoints[1]);
284     }
285     else {
286         // none of the extension lines is near our mouse position
287         return false;
288     }
289     return true;
290 }
291 
decoration(const KoViewConverter &) const292 QPainterPath ExtensionSnapStrategy::decoration(const KoViewConverter &/*converter*/) const
293 {
294     QPainterPath decoration;
295     for (const QLineF &line : m_lines) {
296         decoration.moveTo(line.p1());
297         decoration.lineTo(line.p2());
298     }
299     return decoration;
300 }
301 
snapToExtension(QPointF & position,KoPathPoint * point,const QTransform & matrix)302 bool ExtensionSnapStrategy::snapToExtension(QPointF &position, KoPathPoint * point, const QTransform &matrix)
303 {
304     Q_ASSERT(point);
305     QPointF direction = extensionDirection(point, matrix);
306     if (direction.isNull())
307         return false;
308 
309     QPointF extensionStart = matrix.map(point->point());
310     QPointF extensionStop = matrix.map(point->point()) + direction;
311     float posOnExtension = project(extensionStart, extensionStop, position);
312     if (posOnExtension < 0.0)
313         return false;
314 
315     position = extensionStart + posOnExtension * direction;
316     return true;
317 }
318 
project(const QPointF & lineStart,const QPointF & lineEnd,const QPointF & point)319 qreal ExtensionSnapStrategy::project(const QPointF &lineStart, const QPointF &lineEnd, const QPointF &point)
320 {
321     // This is how the returned value should be used to get the
322     // projectionPoint: ProjectionPoint = lineStart(1-resultingReal) + resultingReal*lineEnd;
323 
324     QPointF diff = lineEnd - lineStart;
325     QPointF relPoint = point - lineStart;
326     qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
327     if (diffLength == 0.0)
328         return 0.0;
329 
330     diff /= diffLength;
331     // project mouse position relative to stop position on extension line
332     qreal scalar = relPoint.x() * diff.x() + relPoint.y() * diff.y();
333     return scalar /= diffLength;
334 }
335 
extensionDirection(KoPathPoint * point,const QTransform & matrix)336 QPointF ExtensionSnapStrategy::extensionDirection(KoPathPoint * point, const QTransform &matrix)
337 {
338     Q_ASSERT(point);
339 
340     KoPathShape * path = point->parent();
341     KoPathPointIndex index = path->pathPointIndex(point);
342 
343     // check if it is a start point
344     if (point->properties() & KoPathPoint::StartSubpath) {
345         if (point->activeControlPoint2()) {
346             return matrix.map(point->point()) - matrix.map(point->controlPoint2());
347         } else {
348             KoPathPoint * next = path->pointByIndex(KoPathPointIndex(index.first, index.second + 1));
349             if (! next){
350                 return QPointF();
351             }
352             else if (next->activeControlPoint1()) {
353                 return matrix.map(point->point()) - matrix.map(next->controlPoint1());
354             }
355             else {
356                 return matrix.map(point->point()) - matrix.map(next->point());
357             }
358         }
359     }
360     else {
361         if (point->activeControlPoint1()) {
362             return matrix.map(point->point()) - matrix.map(point->controlPoint1());
363         }
364         else {
365             KoPathPoint * prev = path->pointByIndex(KoPathPointIndex(index.first, index.second - 1));
366             if (! prev){
367                 return QPointF();
368             }
369             else if (prev->activeControlPoint2()) {
370                 return matrix.map(point->point()) - matrix.map(prev->controlPoint2());
371             }
372             else {
373                 return matrix.map(point->point()) - matrix.map(prev->point());
374             }
375         }
376     }
377 }
378 
IntersectionSnapStrategy()379 IntersectionSnapStrategy::IntersectionSnapStrategy()
380     : KoSnapStrategy(KoSnapGuide::IntersectionSnapping)
381 {
382 }
383 
snap(const QPointF & mousePosition,KoSnapProxy * proxy,qreal maxSnapDistance)384 bool IntersectionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance)
385 {
386     Q_ASSERT(std::isfinite(maxSnapDistance));
387     const qreal maxDistance = maxSnapDistance * maxSnapDistance;
388     qreal minDistance = HUGE_VAL;
389 
390     QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
391     rect.moveCenter(mousePosition);
392     QPointF snappedPoint = mousePosition;
393 
394     QList<KoPathSegment> segments = proxy->segmentsInRect(rect);
395     int segmentCount = segments.count();
396     for (int i = 0; i < segmentCount; ++i) {
397         const KoPathSegment &s1 = segments[i];
398         for (int j = i + 1; j < segmentCount; ++j) {
399             QVector<QPointF> isects = s1.intersections(segments[j]);
400             // There exists a problem on msvc with for(each) and QVector<QPointF>
401             for (int a = 0; a < isects.count(); ++a) {
402                 const QPointF& point(isects[a]);
403                 if (! rect.contains(point))
404                     continue;
405                 qreal distance = squareDistance(mousePosition, point);
406                 if (distance < maxDistance && distance < minDistance) {
407                     snappedPoint = point;
408                     minDistance = distance;
409                 }
410             }
411         }
412     }
413 
414     setSnappedPosition(snappedPoint);
415 
416     return (minDistance < HUGE_VAL);
417 }
418 
decoration(const KoViewConverter & converter) const419 QPainterPath IntersectionSnapStrategy::decoration(const KoViewConverter &converter) const
420 {
421     QRectF unzoomedRect = converter.viewToDocument(QRectF(0, 0, 11, 11));
422     unzoomedRect.moveCenter(snappedPosition());
423     QPainterPath decoration;
424     decoration.addRect(unzoomedRect);
425     return decoration;
426 }
427 
GridSnapStrategy()428 GridSnapStrategy::GridSnapStrategy()
429     : KoSnapStrategy(KoSnapGuide::GridSnapping)
430 {
431 }
432 
snap(const QPointF & mousePosition,KoSnapProxy * proxy,qreal maxSnapDistance)433 bool GridSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance)
434 {
435     Q_ASSERT(std::isfinite(maxSnapDistance));
436     if (! proxy->canvas()->snapToGrid())
437         return false;
438 
439     // The 1e-10 here is a workaround for some weird division problem.
440     // 360.00062366 / 2.83465058 gives 127 'exactly' when shown as a qreal,
441     // but when casting into an int, we get 126. In fact it's 127 - 5.64e-15 !
442     qreal gridX, gridY;
443     proxy->canvas()->gridSize(&gridX, &gridY);
444 
445     // we want to snap to the nearest grid point, so calculate
446     // the grid rows/columns before and after the points position
447     int col = static_cast<int>(mousePosition.x() / gridX + 1e-10);
448     int nextCol = col + 1;
449     int row = static_cast<int>(mousePosition.y() / gridY + 1e-10);
450     int nextRow = row + 1;
451 
452     // now check which grid line has less distance to the point
453     qreal distToCol = qAbs(col * gridX - mousePosition.x());
454     qreal distToNextCol = qAbs(nextCol * gridX - mousePosition.x());
455 
456     if (distToCol > distToNextCol) {
457         col = nextCol;
458         distToCol = distToNextCol;
459     }
460 
461     qreal distToRow = qAbs(row * gridY - mousePosition.y());
462     qreal distToNextRow = qAbs(nextRow * gridY - mousePosition.y());
463     if (distToRow > distToNextRow) {
464         row = nextRow;
465         distToRow = distToNextRow;
466     }
467 
468     QPointF snappedPoint = mousePosition;
469 
470     const qreal distance = distToCol * distToCol + distToRow * distToRow;
471     const qreal maxDistance = maxSnapDistance * maxSnapDistance;
472     // now check if we are inside the snap distance
473     if (distance < maxDistance) {
474         snappedPoint = QPointF(col * gridX, row * gridY);
475     }
476 
477     setSnappedPosition(snappedPoint);
478 
479     return (distance < maxDistance);
480 }
481 
decoration(const KoViewConverter & converter) const482 QPainterPath GridSnapStrategy::decoration(const KoViewConverter &converter) const
483 {
484     QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
485     QPainterPath decoration;
486     decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0));
487     decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0));
488     decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height()));
489     decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height()));
490     return decoration;
491 }
492 
BoundingBoxSnapStrategy()493 BoundingBoxSnapStrategy::BoundingBoxSnapStrategy()
494     : KoSnapStrategy(KoSnapGuide::BoundingBoxSnapping)
495 {
496 }
497 
snap(const QPointF & mousePosition,KoSnapProxy * proxy,qreal maxSnapDistance)498 bool BoundingBoxSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance)
499 {
500     Q_ASSERT(std::isfinite(maxSnapDistance));
501     const qreal maxDistance = maxSnapDistance * maxSnapDistance;
502     qreal minDistance = HUGE_VAL;
503 
504     QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
505 
506     rect.moveCenter(mousePosition);
507     QPointF snappedPoint = mousePosition;
508 
509     KoFlake::Position pointId[5] = {
510         KoFlake::TopLeftCorner,
511         KoFlake::TopRightCorner,
512         KoFlake::BottomRightCorner,
513         KoFlake::BottomLeftCorner,
514         KoFlake::CenteredPosition
515     };
516 
517     QList<KoShape*> shapes = proxy->shapesInRect(rect, true);
518     for (KoShape * shape : shapes) {
519         qreal shapeMinDistance = HUGE_VAL;
520         // first check the corner and center points
521         for (int i = 0; i < 5; ++i) {
522             m_boxPoints[i] = shape->absolutePosition(pointId[i]);
523             qreal d = squareDistance(mousePosition, m_boxPoints[i]);
524             if (d < minDistance && d < maxDistance) {
525                 shapeMinDistance = d;
526                 minDistance = d;
527                 snappedPoint = m_boxPoints[i];
528             }
529         }
530         // prioritize points over edges
531         if (shapeMinDistance < maxDistance)
532             continue;
533 
534         // now check distances to edges of bounding box
535         for (int i = 0; i < 4; ++i) {
536             QPointF pointOnLine;
537             qreal d = squareDistanceToLine(m_boxPoints[i], m_boxPoints[(i+1)%4], mousePosition, pointOnLine);
538             if (d < minDistance && d < maxDistance) {
539                 minDistance = d;
540                 snappedPoint = pointOnLine;
541             }
542         }
543     }
544     setSnappedPosition(snappedPoint);
545 
546     return (minDistance < maxDistance);
547 }
548 
squareDistanceToLine(const QPointF & lineA,const QPointF & lineB,const QPointF & point,QPointF & pointOnLine)549 qreal BoundingBoxSnapStrategy::squareDistanceToLine(const QPointF &lineA, const QPointF &lineB, const QPointF &point, QPointF &pointOnLine)
550 {
551     QPointF diff = lineB - lineA;
552     if(lineA == lineB)
553         return HUGE_VAL;
554     const qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
555 
556     // project mouse position relative to start position on line
557     const qreal scalar = KoSnapStrategy::scalarProduct(point - lineA, diff / diffLength);
558 
559     if (scalar < 0.0 || scalar > diffLength)
560         return HUGE_VAL;
561     // calculate vector between relative mouse position and projected mouse position
562     pointOnLine = lineA + scalar / diffLength * diff;
563     QPointF distVec = pointOnLine - point;
564     return distVec.x()*distVec.x() + distVec.y()*distVec.y();
565 }
566 
decoration(const KoViewConverter & converter) const567 QPainterPath BoundingBoxSnapStrategy::decoration(const KoViewConverter &converter) const
568 {
569     QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
570 
571     QPainterPath decoration;
572     decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), unzoomedSize.height()));
573     decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), unzoomedSize.height()));
574     decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), -unzoomedSize.height()));
575     decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), -unzoomedSize.height()));
576 
577     return decoration;
578 }
579 
LineGuideSnapStrategy()580 LineGuideSnapStrategy::LineGuideSnapStrategy()
581     : KoSnapStrategy(KoSnapGuide::GuideLineSnapping)
582 {
583 }
584 
snap(const QPointF & mousePosition,KoSnapProxy * proxy,qreal maxSnapDistance)585 bool LineGuideSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
586 {
587     Q_ASSERT(std::isfinite(maxSnapDistance));
588 
589     KoGuidesData * guidesData = proxy->canvas()->guidesData();
590 
591     if (!guidesData || !guidesData->showGuideLines())
592         return false;
593 
594     QPointF snappedPoint = mousePosition;
595     m_orientation = 0;
596 
597     qreal minHorzDistance = maxSnapDistance;
598     for (qreal guidePos : guidesData->horizontalGuideLines()) {
599         qreal distance = qAbs(guidePos - mousePosition.y());
600         if (distance < minHorzDistance) {
601             snappedPoint.setY(guidePos);
602             minHorzDistance = distance;
603             m_orientation |= Qt::Horizontal;
604         }
605     }
606     qreal minVertSnapDistance = maxSnapDistance;
607     for (qreal guidePos : guidesData->verticalGuideLines()) {
608         qreal distance = qAbs(guidePos - mousePosition.x());
609         if (distance < minVertSnapDistance) {
610             snappedPoint.setX(guidePos);
611             minVertSnapDistance = distance;
612             m_orientation |= Qt::Vertical;
613         }
614     }
615     setSnappedPosition(snappedPoint);
616     return (minHorzDistance < maxSnapDistance || minVertSnapDistance < maxSnapDistance);
617 }
618 
decoration(const KoViewConverter & converter) const619 QPainterPath LineGuideSnapStrategy::decoration(const KoViewConverter &converter) const
620 {
621     QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
622     Q_ASSERT(unzoomedSize.isValid());
623 
624     QPainterPath decoration;
625     if (m_orientation & Qt::Horizontal) {
626         decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0));
627         decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0));
628     }
629     if (m_orientation & Qt::Vertical) {
630         decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height()));
631         decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height()));
632     }
633 
634     return decoration;
635 }
636