libs/flake

KoSnapGuide.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002  * Copyright (C) 2008-2009 Jan Hambrecht <jaham@gmx.net>
00003  *
00004  * This library is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU Library General Public
00006  * License as published by the Free Software Foundation; either
00007  * version 2 of the License, or (at your option) any later version.
00008  *
00009  * This library is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * Library General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU Library General Public License
00015  * along with this library; see the file COPYING.LIB.  If not, write to
00016  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018  */
00019 
00020 #include "KoSnapGuide.h"
00021 #include "KoSnapStrategy.h"
00022 
00023 #include <KoPathShape.h>
00024 #include <KoPathPoint.h>
00025 #include <KoViewConverter.h>
00026 #include <KoCanvasBase.h>
00027 #include <KoShapeManager.h>
00028 
00029 #include <QtGui/QPainter>
00030 
00031 #include <math.h>
00032 
00033 class KoSnapGuide::Private
00034 {
00035 public:
00036     Private(KoCanvasBase *parentCanvas)
00037     : canvas(parentCanvas), editedShape(0), currentStrategy(0)
00038     , usedStrategies(0), active(true), snapDistance(10)
00039     {
00040     }
00041 
00042     ~Private()
00043     {
00044         qDeleteAll( strategies );
00045         strategies.clear();
00046     }
00047 
00048     KoCanvasBase * canvas;
00049     KoShape * editedShape;
00050 
00051     QList<KoSnapStrategy*> strategies;
00052     KoSnapStrategy * currentStrategy;
00053 
00054     int usedStrategies;
00055     bool active;
00056     int snapDistance;
00057     QList<KoPathPoint*> ignoredPoints;
00058     QList<KoShape*> ignoredShapes;
00059 };
00060 
00061 KoSnapGuide::KoSnapGuide(KoCanvasBase * canvas)
00062 : d(new Private(canvas))
00063 {
00064     d->strategies.append(new GridSnapStrategy());
00065     d->strategies.append(new NodeSnapStrategy());
00066     d->strategies.append(new OrthogonalSnapStrategy());
00067     d->strategies.append(new ExtensionSnapStrategy());
00068     d->strategies.append(new IntersectionSnapStrategy());
00069     d->strategies.append(new BoundingBoxSnapStrategy());
00070     d->strategies.append(new LineGuideSnapStrategy());
00071 }
00072 
00073 KoSnapGuide::~KoSnapGuide()
00074 {
00075     delete d;
00076 }
00077 
00078 void KoSnapGuide::setEditedShape(KoShape * shape)
00079 {
00080     d->editedShape = shape;
00081 }
00082 
00083 KoShape * KoSnapGuide::editedShape() const
00084 {
00085     return d->editedShape;
00086 }
00087 
00088 void KoSnapGuide::enableSnapStrategies(int strategies)
00089 {
00090     d->usedStrategies = strategies;
00091 }
00092 
00093 int KoSnapGuide::enabledSnapStrategies() const
00094 {
00095     return d->usedStrategies;
00096 }
00097 
00098 bool KoSnapGuide::addCustomSnapStrategy(KoSnapStrategy * customStrategy)
00099 {
00100     if (!customStrategy || customStrategy->type() != KoSnapStrategy::Custom)
00101         return false;
00102 
00103     d->strategies.append(customStrategy);
00104     return true;
00105 }
00106 
00107 void KoSnapGuide::enableSnapping(bool on)
00108 {
00109     d->active = on;
00110 }
00111 
00112 bool KoSnapGuide::isSnapping() const
00113 {
00114     return d->active;
00115 }
00116 
00117 void KoSnapGuide::setSnapDistance(int distance)
00118 {
00119     d->snapDistance = qAbs(distance);
00120 }
00121 
00122 int KoSnapGuide::snapDistance() const
00123 {
00124     return d->snapDistance;
00125 }
00126 
00127 QPointF KoSnapGuide::snap(const QPointF &mousePosition, Qt::KeyboardModifiers modifiers)
00128 {
00129     d->currentStrategy = 0;
00130 
00131     if (! d->active || (modifiers & Qt::ShiftModifier))
00132         return mousePosition;
00133 
00134     KoSnapProxy proxy(this);
00135 
00136     qreal minDistance = HUGE_VAL;
00137 
00138     qreal maxSnapDistance = d->canvas->viewConverter()->viewToDocument(QSizeF(d->snapDistance, d->snapDistance)).width();
00139 
00140     foreach(KoSnapStrategy * strategy, d->strategies) {
00141         if (d->usedStrategies & strategy->type()
00142             || strategy->type() == KoSnapStrategy::Grid
00143             || strategy->type() == KoSnapStrategy::Custom) {
00144             if (! strategy->snap(mousePosition, &proxy, maxSnapDistance))
00145                 continue;
00146 
00147             QPointF snapCandidate = strategy->snappedPosition();
00148             qreal distance = KoSnapStrategy::squareDistance(snapCandidate, mousePosition);
00149             if (distance < minDistance) {
00150                 d->currentStrategy = strategy;
00151                 minDistance = distance;
00152             }
00153         }
00154     }
00155 
00156     if (! d->currentStrategy)
00157         return mousePosition;
00158 
00159     return d->currentStrategy->snappedPosition();
00160 }
00161 
00162 QRectF KoSnapGuide::boundingRect()
00163 {
00164     QRectF rect;
00165 
00166     if (d->currentStrategy) {
00167         rect = d->currentStrategy->decoration(*d->canvas->viewConverter()).boundingRect();
00168         return rect.adjusted(-2, -2, 2, 2);
00169     } else {
00170         return rect;
00171     };
00172 }
00173 
00174 void KoSnapGuide::paint(QPainter &painter, const KoViewConverter &converter)
00175 {
00176     if (! d->currentStrategy || ! d->active)
00177         return;
00178 
00179     QPainterPath decoration = d->currentStrategy->decoration(converter);
00180 
00181     painter.setBrush(Qt::NoBrush);
00182 
00183     QPen whitePen(Qt::white);
00184     whitePen.setStyle(Qt::SolidLine);
00185     painter.setPen(whitePen);
00186     painter.drawPath(decoration);
00187 
00188     QPen redPen(Qt::red);
00189     redPen.setStyle(Qt::DotLine);
00190     painter.setPen(redPen);
00191     painter.drawPath(decoration);
00192 }
00193 
00194 KoCanvasBase * KoSnapGuide::canvas() const
00195 {
00196     return d->canvas;
00197 }
00198 
00199 void KoSnapGuide::setIgnoredPathPoints(const QList<KoPathPoint*> &ignoredPoints)
00200 {
00201     d->ignoredPoints = ignoredPoints;
00202 }
00203 
00204 QList<KoPathPoint*> KoSnapGuide::ignoredPathPoints() const
00205 {
00206     return d->ignoredPoints;
00207 }
00208 
00209 void KoSnapGuide::setIgnoredShapes(const QList<KoShape*> &ignoredShapes)
00210 {
00211     d->ignoredShapes = ignoredShapes;
00212 }
00213 
00214 QList<KoShape*> KoSnapGuide::ignoredShapes() const
00215 {
00216     return d->ignoredShapes;
00217 }
00218 
00219 void KoSnapGuide::reset()
00220 {
00221     d->currentStrategy = 0;
00222     d->editedShape = 0;
00223     d->ignoredPoints.clear();
00224     d->ignoredShapes.clear();
00225     // remove all custom strategies
00226     int strategyCount = d->strategies.count();
00227     for(int i = strategyCount-1; i >= 0; --i) {
00228         if (d->strategies[i]->type() == KoSnapStrategy::Custom) {
00229             delete d->strategies[i];
00230             d->strategies.removeAt(i);
00231         }
00232     }
00233 }
00234 
00236 // snap proxy
00238 
00239 KoSnapProxy::KoSnapProxy(KoSnapGuide * snapGuide)
00240         : m_snapGuide(snapGuide)
00241 {
00242 }
00243 
00244 QList<QPointF> KoSnapProxy::pointsInRect(const QRectF &rect)
00245 {
00246     QList<QPointF> points;
00247     QList<KoShape*> shapes = shapesInRect(rect);
00248     foreach(KoShape * shape, shapes) {
00249         foreach(const QPointF & point, pointsFromShape(shape)) {
00250             if (rect.contains(point))
00251                 points.append(point);
00252         }
00253     }
00254 
00255     return points;
00256 }
00257 
00258 QList<KoShape*> KoSnapProxy::shapesInRect(const QRectF &rect, bool omitEditedShape)
00259 {
00260     QList<KoShape*> shapes = m_snapGuide->canvas()->shapeManager()->shapesAt(rect);
00261     foreach(KoShape * shape, m_snapGuide->ignoredShapes()) {
00262         int index = shapes.indexOf(shape);
00263         if (index >= 0)
00264             shapes.removeAt(index);
00265     }
00266     if (! omitEditedShape && m_snapGuide->editedShape()) {
00267         QRectF bound = m_snapGuide->editedShape()->boundingRect();
00268         if (rect.intersects(bound) || rect.contains(bound))
00269             shapes.append(m_snapGuide->editedShape());
00270     }
00271     return shapes;
00272 }
00273 
00274 QList<QPointF> KoSnapProxy::pointsFromShape(KoShape * shape)
00275 {
00276     QList<QPointF> snapPoints;
00277     // no snapping to hidden shapes
00278     if (! shape->isVisible(true))
00279         return snapPoints;
00280 
00281     // return the special snap points of the shape
00282     snapPoints += shape->snapData().snapPoints();
00283 
00284     KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
00285     if (path) {
00286         QMatrix m = path->absoluteTransformation(0);
00287 
00288         QList<KoPathPoint*> ignoredPoints = m_snapGuide->ignoredPathPoints();
00289 
00290         int subpathCount = path->subpathCount();
00291         for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
00292             int pointCount = path->pointCountSubpath(subpathIndex);
00293             for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
00294                 KoPathPoint * p = path->pointByIndex(KoPathPointIndex(subpathIndex, pointIndex));
00295                 if (! p || ignoredPoints.contains(p))
00296                     continue;
00297 
00298                 snapPoints.append(m.map(p->point()));
00299             }
00300         }
00301     }
00302     else
00303     {
00304         // add the bounding box corners as default snap points
00305         QRectF bbox = shape->boundingRect();
00306         snapPoints.append(bbox.topLeft());
00307         snapPoints.append(bbox.topRight());
00308         snapPoints.append(bbox.bottomRight());
00309         snapPoints.append(bbox.bottomLeft());
00310     }
00311 
00312     return snapPoints;
00313 }
00314 
00315 QList<KoPathSegment> KoSnapProxy::segmentsInRect(const QRectF &rect)
00316 {
00317     QList<KoShape*> shapes = shapesInRect(rect, true);
00318     QList<KoPathPoint*> ignoredPoints = m_snapGuide->ignoredPathPoints();
00319 
00320     QList<KoPathSegment> segments;
00321     foreach(KoShape * shape, shapes) {
00322         QList<KoPathSegment> shapeSegments;
00323         QRectF rectOnShape = shape->documentToShape(rect);
00324         KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
00325         if (path) {
00326             shapeSegments = path->segmentsAt(rectOnShape);
00327         } else {
00328             foreach(const KoPathSegment & s, shape->snapData().snapSegments()) {
00329                 QRectF controlRect = s.controlPointRect();
00330                 if (! rect.intersects(controlRect) && ! controlRect.contains(rect))
00331                     continue;
00332                 QRectF bound = s.boundingRect();
00333                 if (! rect.intersects(bound) && ! bound.contains(rect))
00334                     continue;
00335                 shapeSegments.append(s);
00336             }
00337         }
00338 
00339         QMatrix m = shape->absoluteTransformation(0);
00340         // transform segments to document coordinates
00341         foreach(const KoPathSegment & s, shapeSegments) {
00342             if (ignoredPoints.contains(s.first()) || ignoredPoints.contains(s.second()))
00343                 continue;
00344             segments.append(s.mapped(m));
00345         }
00346     }
00347     return segments;
00348 }
00349 
00350 QList<KoShape*> KoSnapProxy::shapes(bool omitEditedShape)
00351 {
00352     QList<KoShape*> allShapes = m_snapGuide->canvas()->shapeManager()->shapes();
00353     QList<KoShape*> filteredShapes;
00354     QList<KoShape*> ignoredShapes = m_snapGuide->ignoredShapes();
00355 
00356     // filter all hidden and ignored shapes
00357     foreach(KoShape * shape, allShapes) {
00358         if (! shape->isVisible(true))
00359             continue;
00360         if (ignoredShapes.contains(shape))
00361             continue;
00362 
00363         filteredShapes.append(shape);
00364     }
00365     if (! omitEditedShape && m_snapGuide->editedShape())
00366         filteredShapes.append(m_snapGuide->editedShape());
00367 
00368     return filteredShapes;
00369 }
00370 
00371 KoCanvasBase * KoSnapProxy::canvas()
00372 {
00373     return m_snapGuide->canvas();
00374 }