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