00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "KoSnapStrategy.h"
00021 #include "KoSnapGuide.h"
00022 #include <KoPathShape.h>
00023 #include <KoPathPoint.h>
00024 #include <KoCanvasBase.h>
00025 #include <KoViewConverter.h>
00026 #include <KoGuidesData.h>
00027
00028 #include <QtGui/QPainter>
00029
00030
00031 #include <math.h>
00032
00033
00034 KoSnapStrategy::KoSnapStrategy(KoSnapStrategy::SnapType type)
00035 : m_snapType(type)
00036 {
00037 }
00038
00039 QPointF KoSnapStrategy::snappedPosition() const
00040 {
00041 return m_snappedPosition;
00042 }
00043
00044 void KoSnapStrategy::setSnappedPosition(const QPointF &position)
00045 {
00046 m_snappedPosition = position;
00047 }
00048
00049 KoSnapStrategy::SnapType KoSnapStrategy::type() const
00050 {
00051 return m_snapType;
00052 }
00053
00054 qreal KoSnapStrategy::squareDistance(const QPointF &p1, const QPointF &p2)
00055 {
00056 qreal dx = p1.x() - p2.x();
00057 qreal dy = p1.y() - p2.y();
00058 return dx*dx + dy*dy;
00059 }
00060
00061 qreal KoSnapStrategy::scalarProduct(const QPointF &p1, const QPointF &p2)
00062 {
00063 return p1.x() * p2.x() + p1.y() * p2.y();
00064 }
00065
00066 OrthogonalSnapStrategy::OrthogonalSnapStrategy()
00067 : KoSnapStrategy(KoSnapStrategy::Orthogonal)
00068 {
00069 }
00070
00071 bool OrthogonalSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
00072 {
00073 QPointF horzSnap, vertSnap;
00074 qreal minVertDist = HUGE_VAL;
00075 qreal minHorzDist = HUGE_VAL;
00076
00077 QList<KoShape*> shapes = proxy->shapes();
00078 foreach(KoShape * shape, shapes) {
00079 QList<QPointF> points = proxy->pointsFromShape(shape);
00080 foreach(const QPointF & point, points) {
00081 qreal dx = fabs(point.x() - mousePosition.x());
00082 if (dx < minHorzDist && dx < maxSnapDistance) {
00083 minHorzDist = dx;
00084 horzSnap = point;
00085 }
00086 qreal dy = fabs(point.y() - mousePosition.y());
00087 if (dy < minVertDist && dy < maxSnapDistance) {
00088 minVertDist = dy;
00089 vertSnap = point;
00090 }
00091 }
00092 }
00093
00094 QPointF snappedPoint = mousePosition;
00095
00096 if (minHorzDist < HUGE_VAL)
00097 snappedPoint.setX(horzSnap.x());
00098 if (minVertDist < HUGE_VAL)
00099 snappedPoint.setY(vertSnap.y());
00100
00101 if (minHorzDist < HUGE_VAL)
00102 m_hLine = QLineF(horzSnap, snappedPoint);
00103 else
00104 m_hLine = QLineF();
00105
00106 if (minVertDist < HUGE_VAL)
00107 m_vLine = QLineF(vertSnap, snappedPoint);
00108 else
00109 m_vLine = QLineF();
00110
00111 setSnappedPosition(snappedPoint);
00112
00113 return (minHorzDist < HUGE_VAL || minVertDist < HUGE_VAL);
00114 }
00115
00116 QPainterPath OrthogonalSnapStrategy::decoration(const KoViewConverter &converter) const
00117 {
00118 Q_UNUSED(converter);
00119
00120 QPainterPath decoration;
00121 if (! m_hLine.isNull()) {
00122 decoration.moveTo(m_hLine.p1());
00123 decoration.lineTo(m_hLine.p2());
00124 }
00125 if (! m_vLine.isNull()) {
00126 decoration.moveTo(m_vLine.p1());
00127 decoration.lineTo(m_vLine.p2());
00128 }
00129 return decoration;
00130 }
00131
00132 NodeSnapStrategy::NodeSnapStrategy()
00133 : KoSnapStrategy(KoSnapStrategy::Node)
00134 {
00135 }
00136
00137 bool NodeSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
00138 {
00139 qreal maxDistance = maxSnapDistance * maxSnapDistance;
00140 qreal minDistance = HUGE_VAL;
00141
00142 QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
00143 rect.moveCenter(mousePosition);
00144 QList<QPointF> points = proxy->pointsInRect(rect);
00145
00146 QPointF snappedPoint = mousePosition;
00147
00148 foreach(const QPointF & point, points) {
00149 qreal distance = squareDistance(mousePosition, point);
00150 if (distance < maxDistance && distance < minDistance) {
00151 snappedPoint = point;
00152 minDistance = distance;
00153 }
00154 }
00155
00156 setSnappedPosition(snappedPoint);
00157
00158 return (minDistance < HUGE_VAL);
00159 }
00160
00161 QPainterPath NodeSnapStrategy::decoration(const KoViewConverter &converter) const
00162 {
00163 QRectF unzoomedRect = converter.viewToDocument(QRectF(0, 0, 11, 11));
00164 unzoomedRect.moveCenter(snappedPosition());
00165 QPainterPath decoration;
00166 decoration.addEllipse(unzoomedRect);
00167 return decoration;
00168 }
00169
00170 ExtensionSnapStrategy::ExtensionSnapStrategy()
00171 : KoSnapStrategy(KoSnapStrategy::Extension)
00172 {
00173 }
00174
00175 bool ExtensionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
00176 {
00177 qreal maxDistance = maxSnapDistance * maxSnapDistance;
00178 qreal minDistances[2] = { HUGE_VAL, HUGE_VAL };
00179
00180 QPointF snappedPoints[2] = { mousePosition, mousePosition };
00181 QPointF startPoints[2];
00182
00183 QList<KoShape*> shapes = proxy->shapes(true);
00184 foreach(KoShape * shape, shapes) {
00185 KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
00186 if (! path)
00187 continue;
00188
00189 QMatrix matrix = path->absoluteTransformation(0);
00190
00191 int subpathCount = path->subpathCount();
00192 for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
00193 if (path->isClosedSubpath(subpathIndex))
00194 continue;
00195
00196 int pointCount = path->pointCountSubpath(subpathIndex);
00197
00198
00199 KoPathPoint * first = path->pointByIndex(KoPathPointIndex(subpathIndex, 0));
00200 QPointF firstSnapPosition = mousePosition;
00201 if (snapToExtension(firstSnapPosition, first, matrix)) {
00202 qreal distance = squareDistance(firstSnapPosition, mousePosition);
00203 if (distance < maxDistance) {
00204 if (distance < minDistances[0]) {
00205 minDistances[1] = minDistances[0];
00206 snappedPoints[1] = snappedPoints[0];
00207 startPoints[1] = startPoints[0];
00208
00209 minDistances[0] = distance;
00210 snappedPoints[0] = firstSnapPosition;
00211 startPoints[0] = matrix.map(first->point());
00212 }
00213 else if (distance < minDistances[1]) {
00214 minDistances[1] = distance;
00215 snappedPoints[1] = firstSnapPosition;
00216 startPoints[1] = matrix.map(first->point());
00217 }
00218 }
00219 }
00220
00221
00222 KoPathPoint * last = path->pointByIndex(KoPathPointIndex(subpathIndex, pointCount - 1));
00223 QPointF lastSnapPosition = mousePosition;
00224 if (snapToExtension(lastSnapPosition, last, matrix)) {
00225 qreal distance = squareDistance(lastSnapPosition, mousePosition);
00226 if (distance < maxDistance) {
00227 if (distance < minDistances[0]) {
00228 minDistances[1] = minDistances[0];
00229 snappedPoints[1] = snappedPoints[0];
00230 startPoints[1] = startPoints[0];
00231
00232 minDistances[0] = distance;
00233 snappedPoints[0] = lastSnapPosition;
00234 startPoints[0] = matrix.map(last->point());
00235 }
00236 else if (distance < minDistances[1]) {
00237 minDistances[1] = distance;
00238 snappedPoints[1] = lastSnapPosition;
00239 startPoints[1] = matrix.map(last->point());
00240 }
00241 }
00242 }
00243 }
00244 }
00245
00246 m_lines.clear();
00247
00248
00249 if (minDistances[0] < HUGE_VAL && minDistances[1] < HUGE_VAL ) {
00250
00251 KoPathSegment s1( startPoints[0], snappedPoints[0] + snappedPoints[0]-startPoints[0] );
00252 KoPathSegment s2( startPoints[1], snappedPoints[1] + snappedPoints[1]-startPoints[1] );
00253 QList<QPointF> isects = s1.intersections( s2 );
00254 if (isects.count() == 1 && squareDistance(isects[0], mousePosition) < maxDistance) {
00255
00256 m_lines.append( QLineF(startPoints[0], isects[0]) );
00257 m_lines.append( QLineF(startPoints[1], isects[0]) );
00258 setSnappedPosition(isects[0]);
00259 }
00260 else {
00261
00262 uint index = minDistances[0] < minDistances[1] ? 0 : 1;
00263 m_lines.append( QLineF(startPoints[index], snappedPoints[index]) );
00264 setSnappedPosition(snappedPoints[index]);
00265 }
00266 }
00267 else if (minDistances[0] < HUGE_VAL) {
00268 m_lines.append( QLineF(startPoints[0], snappedPoints[0]) );
00269 setSnappedPosition(snappedPoints[0]);
00270 }
00271 else if (minDistances[1] < HUGE_VAL) {
00272 m_lines.append( QLineF(startPoints[1], snappedPoints[1]) );
00273 setSnappedPosition(snappedPoints[1]);
00274 }
00275 else {
00276
00277 return false;
00278 }
00279
00280 return true;
00281 }
00282
00283 QPainterPath ExtensionSnapStrategy::decoration(const KoViewConverter &converter) const
00284 {
00285 Q_UNUSED(converter);
00286
00287 QPainterPath decoration;
00288 foreach( const QLineF & line, m_lines ) {
00289 decoration.moveTo(line.p1());
00290 decoration.lineTo(line.p2());
00291 }
00292 return decoration;
00293 }
00294
00295 bool ExtensionSnapStrategy::snapToExtension(QPointF &position, KoPathPoint * point, const QMatrix &matrix)
00296 {
00297 QPointF direction = extensionDirection(point, matrix);
00298 if (direction.isNull())
00299 return false;
00300
00301 QPointF extensionStart = matrix.map(point->point());
00302 QPointF extensionStop = matrix.map(point->point()) + direction;
00303 float posOnExtension = project(extensionStart, extensionStop, position);
00304 if (posOnExtension < 0.0)
00305 return false;
00306
00307 position = extensionStart + posOnExtension * direction;
00308 return true;
00309 }
00310
00311 qreal ExtensionSnapStrategy::project(const QPointF &lineStart, const QPointF &lineEnd, const QPointF &point)
00312 {
00313 QPointF diff = lineEnd - lineStart;
00314 QPointF relPoint = point - lineStart;
00315 qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
00316 if (diffLength == 0.0)
00317 return 0.0;
00318
00319 diff /= diffLength;
00320
00321 qreal scalar = relPoint.x() * diff.x() + relPoint.y() * diff.y();
00322 return scalar /= diffLength;
00323 }
00324
00325 QPointF ExtensionSnapStrategy::extensionDirection(KoPathPoint * point, const QMatrix &matrix)
00326 {
00327 KoPathShape * path = point->parent();
00328 KoPathPointIndex index = path->pathPointIndex(point);
00329
00331 if (point->properties() & KoPathPoint::StartSubpath) {
00332 if (point->activeControlPoint2()) {
00333 return matrix.map(point->point()) - matrix.map(point->controlPoint2());
00334 } else {
00335 KoPathPoint * next = path->pointByIndex(KoPathPointIndex(index.first, index.second + 1));
00336 if (! next)
00337 return QPointF();
00338 else if (next->activeControlPoint1())
00339 return matrix.map(point->point()) - matrix.map(next->controlPoint1());
00340 else
00341 return matrix.map(point->point()) - matrix.map(next->point());
00342 }
00343 } else {
00344 if (point->activeControlPoint1()) {
00345 return matrix.map(point->point()) - matrix.map(point->controlPoint1());
00346 } else {
00347 KoPathPoint * prev = path->pointByIndex(KoPathPointIndex(index.first, index.second - 1));
00348 if (! prev)
00349 return QPointF();
00350 else if (prev->activeControlPoint2())
00351 return matrix.map(point->point()) - matrix.map(prev->controlPoint2());
00352 else
00353 return matrix.map(point->point()) - matrix.map(prev->point());
00354 }
00355 }
00356 }
00357
00358 IntersectionSnapStrategy::IntersectionSnapStrategy()
00359 : KoSnapStrategy(KoSnapStrategy::Intersection)
00360 {
00361 }
00362
00363 bool IntersectionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
00364 {
00365 qreal maxDistance = maxSnapDistance * maxSnapDistance;
00366 qreal minDistance = HUGE_VAL;
00367
00368 QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
00369 rect.moveCenter(mousePosition);
00370 QPointF snappedPoint = mousePosition;
00371
00372 QList<KoPathSegment> segments = proxy->segmentsInRect(rect);
00373
00374
00375 int segmentCount = segments.count();
00376 for (int i = 0; i < segmentCount; ++i) {
00377 const KoPathSegment& s1 = segments[i];
00378 for (int j = i + 1; j < segmentCount; ++j) {
00379 QList<QPointF> isects = s1.intersections(segments[j]);
00380
00381 foreach(const QPointF &point, isects) {
00382 if (! rect.contains(point))
00383 continue;
00384 qreal distance = squareDistance(mousePosition, point);
00385 if (distance < maxDistance && distance < minDistance) {
00386 snappedPoint = point;
00387 minDistance = distance;
00388 }
00389 }
00390 }
00391 }
00392
00393 setSnappedPosition(snappedPoint);
00394
00395 return (minDistance < HUGE_VAL);
00396 }
00397
00398 QPainterPath IntersectionSnapStrategy::decoration(const KoViewConverter &converter) const
00399 {
00400 QRectF unzoomedRect = converter.viewToDocument(QRectF(0, 0, 11, 11));
00401 unzoomedRect.moveCenter(snappedPosition());
00402 QPainterPath decoration;
00403 decoration.addRect(unzoomedRect);
00404 return decoration;
00405 }
00406
00407 GridSnapStrategy::GridSnapStrategy()
00408 : KoSnapStrategy(KoSnapStrategy::Grid)
00409 {
00410 }
00411
00412 bool GridSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
00413 {
00414 if (! proxy->canvas()->snapToGrid())
00415 return false;
00416
00417
00418
00419
00420 qreal gridX, gridY;
00421 proxy->canvas()->gridSize(&gridX, &gridY);
00422
00423
00424
00425 int col = static_cast<int>(mousePosition.x() / gridX + 1e-10);
00426 int nextCol = col + 1;
00427 int row = static_cast<int>(mousePosition.y() / gridY + 1e-10);
00428 int nextRow = row + 1;
00429
00430
00431 qreal distToCol = qAbs(col * gridX - mousePosition.x());
00432 qreal distToNextCol = qAbs(nextCol * gridX - mousePosition.x());
00433 if (distToCol > distToNextCol) {
00434 col = nextCol;
00435 distToCol = distToNextCol;
00436 }
00437
00438 qreal distToRow = qAbs(row * gridY - mousePosition.y());
00439 qreal distToNextRow = qAbs(nextRow * gridY - mousePosition.y());
00440 if (distToRow > distToNextRow) {
00441 row = nextRow;
00442 distToRow = distToNextRow;
00443 }
00444
00445 QPointF snappedPoint = mousePosition;
00446
00447 qreal distance = distToCol * distToCol + distToRow * distToRow;
00448 qreal maxDistance = maxSnapDistance * maxSnapDistance;
00449
00450 if (distance < maxDistance) {
00451 snappedPoint = QPointF(col * gridX, row * gridY);
00452 }
00453
00454 setSnappedPosition(snappedPoint);
00455
00456 return (distance < maxDistance);
00457 }
00458
00459 QPainterPath GridSnapStrategy::decoration(const KoViewConverter &converter) const
00460 {
00461 QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
00462 QPainterPath decoration;
00463 decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0));
00464 decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0));
00465 decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height()));
00466 decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height()));
00467 return decoration;
00468 }
00469
00470 BoundingBoxSnapStrategy::BoundingBoxSnapStrategy()
00471 : KoSnapStrategy(KoSnapStrategy::BoundingBox)
00472 {
00473 }
00474
00475 bool BoundingBoxSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
00476 {
00477 qreal maxDistance = maxSnapDistance * maxSnapDistance;
00478 qreal minDistance = HUGE_VAL;
00479
00480 QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
00481 rect.moveCenter(mousePosition);
00482 QPointF snappedPoint = mousePosition;
00483
00484 KoFlake::Position pointId[5] = {
00485 KoFlake::TopLeftCorner,
00486 KoFlake::TopRightCorner,
00487 KoFlake::BottomRightCorner,
00488 KoFlake::BottomLeftCorner,
00489 KoFlake::CenteredPosition
00490 };
00491
00492 QList<KoShape*> shapes = proxy->shapesInRect(rect, true);
00493 foreach(KoShape * shape, shapes) {
00494 qreal shapeMinDistance = HUGE_VAL;
00495
00496 for (int i = 0; i < 5; ++i) {
00497 m_boxPoints[i] = shape->absolutePosition(pointId[i]);
00498 qreal d = squareDistance(mousePosition, m_boxPoints[i]);
00499 if (d < minDistance && d < maxDistance) {
00500 shapeMinDistance = d;
00501 minDistance = d;
00502 snappedPoint = m_boxPoints[i];
00503 }
00504 }
00505
00506 if (shapeMinDistance < maxDistance)
00507 continue;
00508
00509
00510 for (int i = 0; i < 4; ++i) {
00511 QPointF pointOnLine;
00512 qreal d = squareDistanceToLine(m_boxPoints[i], m_boxPoints[(i+1)%4], mousePosition, pointOnLine);
00513 if (d < minDistance && d < maxDistance) {
00514 minDistance = d;
00515 snappedPoint = pointOnLine;
00516 }
00517 }
00518 }
00519
00520 setSnappedPosition(snappedPoint);
00521
00522 return (minDistance < maxDistance);
00523
00524 }
00525
00526 qreal BoundingBoxSnapStrategy::squareDistanceToLine(const QPointF &lineA, const QPointF &lineB, const QPointF &point, QPointF &pointOnLine)
00527 {
00528 QPointF diff = lineB - lineA;
00529 qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
00530 if (diffLength == 0.0f)
00531 return HUGE_VAL;
00532
00533 qreal scalar = KoSnapStrategy::scalarProduct(point - lineA, diff / diffLength);
00534 if (scalar < 0.0 || scalar > diffLength)
00535 return HUGE_VAL;
00536
00537 pointOnLine = lineA + scalar / diffLength * diff;
00538 QPointF distVec = pointOnLine - point;
00539 return distVec.x()*distVec.x() + distVec.y()*distVec.y();
00540 }
00541
00542 QPainterPath BoundingBoxSnapStrategy::decoration(const KoViewConverter &converter) const
00543 {
00544 QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
00545
00546 QPainterPath decoration;
00547 decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), unzoomedSize.height()));
00548 decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), unzoomedSize.height()));
00549 decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), -unzoomedSize.height()));
00550 decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), -unzoomedSize.height()));
00551
00552 return decoration;
00553 }
00554
00555 LineGuideSnapStrategy::LineGuideSnapStrategy()
00556 : KoSnapStrategy(KoSnapStrategy::GuideLine)
00557 {
00558 }
00559
00560 bool LineGuideSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
00561 {
00562 KoGuidesData * guidesData = proxy->canvas()->guidesData();
00563 if (! guidesData || ! guidesData->showGuideLines())
00564 return false;
00565
00566 QPointF snappedPoint = mousePosition;
00567 m_orientation = 0;
00568
00569 qreal minHorzDistance = maxSnapDistance;
00570 foreach(qreal guidePos, guidesData->horizontalGuideLines()) {
00571 qreal distance = qAbs(guidePos - mousePosition.y());
00572 if (distance < minHorzDistance) {
00573 snappedPoint.setY(guidePos);
00574 minHorzDistance = distance;
00575 m_orientation |= Qt::Horizontal;
00576 }
00577 }
00578 qreal minVertSnapDistance = maxSnapDistance;
00579 foreach(qreal guidePos, guidesData->verticalGuideLines()) {
00580 qreal distance = qAbs(guidePos - mousePosition.x());
00581 if (distance < minVertSnapDistance) {
00582 snappedPoint.setX(guidePos);
00583 minVertSnapDistance = distance;
00584 m_orientation |= Qt::Vertical;
00585 }
00586 }
00587
00588 setSnappedPosition(snappedPoint);
00589
00590 return (minHorzDistance < maxSnapDistance || minVertSnapDistance < maxSnapDistance);
00591 }
00592
00593 QPainterPath LineGuideSnapStrategy::decoration(const KoViewConverter &converter) const
00594 {
00595 QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
00596 QPainterPath decoration;
00597 if (m_orientation & Qt::Horizontal) {
00598 decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0));
00599 decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0));
00600 }
00601 if (m_orientation & Qt::Vertical) {
00602 decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height()));
00603 decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height()));
00604 }
00605 return decoration;
00606 }