KoSnapGuide.cpp 9.36 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* This file is part of the KDE project
 * Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

20 21
#include "KoSnapGuide.h"
#include "KoSnapStrategy.h"
22

23 24 25
#include <KoPathShape.h>
#include <KoPathPoint.h>
#include <KoViewConverter.h>
26
#include <KoCanvasBase.h>
27
#include <KoShapeManager.h>
28 29 30 31 32

#include <QtGui/QPainter>

#include <math.h>

33

34 35 36
KoSnapGuide::KoSnapGuide(KoCanvasBase * canvas)
        : m_canvas(canvas), m_editedShape(0), m_currentStrategy(0)
        , m_usedStrategies(0), m_active(true), m_snapDistance(10)
37
{
38 39 40 41 42 43 44
    m_strategies.append(new GridSnapStrategy());
    m_strategies.append(new NodeSnapStrategy());
    m_strategies.append(new OrthogonalSnapStrategy());
    m_strategies.append(new ExtensionSnapStrategy());
    m_strategies.append(new IntersectionSnapStrategy());
    m_strategies.append(new BoundingBoxSnapStrategy());
    m_strategies.append(new LineGuideSnapStrategy());
45 46
}

47
KoSnapGuide::~KoSnapGuide()
48
{
Jan Hambrecht's avatar
Jan Hambrecht committed
49 50
    qDeleteAll( m_strategies );
    m_strategies.clear();
51 52
}

53
void KoSnapGuide::setEditedShape(KoShape * shape)
54
{
55
    m_editedShape = shape;
56 57
}

58
KoShape * KoSnapGuide::editedShape() const
59
{
60
    return m_editedShape;
61 62
}

63
void KoSnapGuide::enableSnapStrategies(int strategies)
64 65 66 67
{
    m_usedStrategies = strategies;
}

68
int KoSnapGuide::enabledSnapStrategies() const
69
{
70 71 72
    return m_usedStrategies;
}

73
void KoSnapGuide::enableSnapping(bool on)
74 75 76 77
{
    m_active = on;
}

78
bool KoSnapGuide::isSnapping() const
79 80 81 82
{
    return m_active;
}

83
void KoSnapGuide::setSnapDistance(int distance)
84
{
85
    m_snapDistance = qAbs(distance);
86 87
}

88
int KoSnapGuide::snapDistance() const
89 90 91 92
{
    return m_snapDistance;
}

93
QPointF KoSnapGuide::snap(const QPointF &mousePosition, Qt::KeyboardModifiers modifiers)
94
{
95 96
    m_currentStrategy = 0;

97
    if (! m_active || (modifiers & Qt::ShiftModifier))
98 99
        return mousePosition;

100
    KoSnapProxy proxy(this);
101

102
    qreal minDistance = HUGE_VAL;
103

104
    qreal maxSnapDistance = m_canvas->viewConverter()->viewToDocument(QSizeF(m_snapDistance, m_snapDistance)).width();
105

106 107 108
    foreach(KoSnapStrategy * strategy, m_strategies) {
        if (m_usedStrategies & strategy->type() || strategy->type() == KoSnapStrategy::Grid) {
            if (! strategy->snap(mousePosition, &proxy, maxSnapDistance))
109 110 111
                continue;

            QPointF snapCandidate = strategy->snappedPosition();
112 113
            qreal distance = KoSnapStrategy::fastDistance(snapCandidate, mousePosition);
            if (distance < minDistance) {
114 115 116 117 118 119
                m_currentStrategy = strategy;
                minDistance = distance;
            }
        }
    }

120
    if (! m_currentStrategy)
121 122 123
        return mousePosition;

    return m_currentStrategy->snappedPosition();
124 125
}

126
QRectF KoSnapGuide::boundingRect()
127
{
128 129
    QRectF rect;

130 131 132 133
    if (m_currentStrategy) {
        rect = m_currentStrategy->decoration(*m_canvas->viewConverter()).boundingRect();
        return rect.adjusted(-2, -2, 2, 2);
    } else {
134 135
        return rect;
    };
136 137
}

138
void KoSnapGuide::paint(QPainter &painter, const KoViewConverter &converter)
139
{
140
    if (! m_currentStrategy || ! m_active)
141 142
        return;

143
    QPainterPath decoration = m_currentStrategy->decoration(converter);
144

145
    painter.setBrush(Qt::NoBrush);
146

147 148 149 150
    QPen whitePen(Qt::white);
    whitePen.setStyle(Qt::SolidLine);
    painter.setPen(whitePen);
    painter.drawPath(decoration);
151

152 153 154 155
    QPen redPen(Qt::red);
    redPen.setStyle(Qt::DotLine);
    painter.setPen(redPen);
    painter.drawPath(decoration);
156 157
}

158
KoCanvasBase * KoSnapGuide::canvas() const
159 160 161 162
{
    return m_canvas;
}

163
void KoSnapGuide::setIgnoredPathPoints(const QList<KoPathPoint*> &ignoredPoints)
164 165 166 167
{
    m_ignoredPoints = ignoredPoints;
}

168
QList<KoPathPoint*> KoSnapGuide::ignoredPathPoints() const
169 170 171 172
{
    return m_ignoredPoints;
}

173
void KoSnapGuide::setIgnoredShapes(const QList<KoShape*> &ignoredShapes)
174 175 176 177 178 179 180 181 182
{
    m_ignoredShapes = ignoredShapes;
}

QList<KoShape*> KoSnapGuide::ignoredShapes() const
{
    return m_ignoredShapes;
}

183 184 185 186 187
void KoSnapGuide::reset()
{
    m_currentStrategy = 0;
    m_editedShape = 0;
    m_ignoredPoints.clear();
188
    m_ignoredShapes.clear();
189 190
}

191 192 193 194
/////////////////////////////////////////////////////////
// snap proxy
/////////////////////////////////////////////////////////

195 196
KoSnapProxy::KoSnapProxy(KoSnapGuide * snapGuide)
        : m_snapGuide(snapGuide)
197 198 199
{
}

200
QList<QPointF> KoSnapProxy::pointsInRect(const QRectF &rect)
201 202
{
    QList<QPointF> points;
203 204
    QList<KoShape*> shapes = shapesInRect(rect);
    foreach(KoShape * shape, shapes) {
Jan Hambrecht's avatar
Jan Hambrecht committed
205
        foreach(const QPointF & point, pointsFromShape(shape)) {
206 207
            if (rect.contains(point))
                points.append(point);
208 209 210 211 212 213
        }
    }

    return points;
}

214
QList<KoShape*> KoSnapProxy::shapesInRect(const QRectF &rect, bool omitEditedShape)
215
{
216 217 218 219 220
    QList<KoShape*> shapes = m_snapGuide->canvas()->shapeManager()->shapesAt(rect);
    foreach(KoShape * shape, m_snapGuide->ignoredShapes()) {
        int index = shapes.indexOf(shape);
        if (index >= 0)
            shapes.removeAt(index);
221
    }
222
    if (! omitEditedShape && m_snapGuide->editedShape()) {
223
        QRectF bound = m_snapGuide->editedShape()->boundingRect();
224 225
        if (rect.intersects(bound) || rect.contains(bound))
            shapes.append(m_snapGuide->editedShape());
226 227 228 229
    }
    return shapes;
}

230
QList<QPointF> KoSnapProxy::pointsFromShape(KoShape * shape)
231
{
232
    QList<QPointF> snapPoints;
Jan Hambrecht's avatar
Jan Hambrecht committed
233
    // no snapping to hidden shapes
234
    if (! shape->isVisible(true))
Jan Hambrecht's avatar
Jan Hambrecht committed
235
        return snapPoints;
236 237 238

    // return the special snap points of the shape
    snapPoints += shape->snapData().snapPoints();
239

240 241
    KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
    if (path) {
242
        QMatrix m = path->absoluteTransformation(0);
243

244
        QList<KoPathPoint*> ignoredPoints = m_snapGuide->ignoredPathPoints();
245

246
        int subpathCount = path->subpathCount();
247 248 249 250 251
        for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
            int pointCount = path->pointCountSubpath(subpathIndex);
            for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
                KoPathPoint * p = path->pointByIndex(KoPathPointIndex(subpathIndex, pointIndex));
                if (! p || ignoredPoints.contains(p))
252
                    continue;
253

254
                snapPoints.append(m.map(p->point()));
255
            }
256 257
        }

258
        if (shape == m_snapGuide->editedShape())
259 260
            snapPoints.removeLast();
    }
261 262 263 264 265 266 267 268 269
    else
    {
        // add the bounding box corners as default snap points
        QRectF bbox = shape->boundingRect();
        snapPoints.append(bbox.topLeft());
        snapPoints.append(bbox.topRight());
        snapPoints.append(bbox.bottomRight());
        snapPoints.append(bbox.bottomLeft());
    }
270

271
    return snapPoints;
272 273
}

274
QList<KoPathSegment> KoSnapProxy::segmentsInRect(const QRectF &rect)
275
{
276
    QList<KoShape*> shapes = shapesInRect(rect, true);
277
    QList<KoPathPoint*> ignoredPoints = m_snapGuide->ignoredPathPoints();
278 279

    QList<KoPathSegment> segments;
280
    foreach(KoShape * shape, shapes) {
281
        QList<KoPathSegment> shapeSegments;
282 283 284 285 286
        QRectF rectOnShape = shape->documentToShape(rect);
        KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
        if (path) {
            shapeSegments = path->segmentsAt(rectOnShape);
        } else {
Jan Hambrecht's avatar
Jan Hambrecht committed
287
            foreach(const KoPathSegment & s, shape->snapData().snapSegments()) {
288
                QRectF controlRect = s.controlPointRect();
289
                if (! rect.intersects(controlRect) && ! controlRect.contains(rect))
290 291
                    continue;
                QRectF bound = s.boundingRect();
292
                if (! rect.intersects(bound) && ! bound.contains(rect))
293
                    continue;
294
                shapeSegments.append(s);
295 296 297 298 299
            }
        }

        QMatrix m = shape->absoluteTransformation(0);
        // transform segments to document coordinates
Jan Hambrecht's avatar
Jan Hambrecht committed
300
        foreach(const KoPathSegment & s, shapeSegments) {
301
            if (ignoredPoints.contains(s.first()) || ignoredPoints.contains(s.second()))
302
                continue;
303
            segments.append(s.mapped(m));
304 305 306 307 308
        }
    }
    return segments;
}

309
QList<KoShape*> KoSnapProxy::shapes(bool omitEditedShape)
310
{
Jan Hambrecht's avatar
Jan Hambrecht committed
311 312 313 314 315
    QList<KoShape*> allShapes = m_snapGuide->canvas()->shapeManager()->shapes();
    QList<KoShape*> filteredShapes;
    QList<KoShape*> ignoredShapes = m_snapGuide->ignoredShapes();

    // filter all hidden and ignored shapes
316 317
    foreach(KoShape * shape, allShapes) {
        if (! shape->isVisible(true))
Jan Hambrecht's avatar
Jan Hambrecht committed
318
            continue;
319
        if (ignoredShapes.contains(shape))
Jan Hambrecht's avatar
Jan Hambrecht committed
320 321
            continue;

322
        filteredShapes.append(shape);
323
    }
324 325
    if (! omitEditedShape && m_snapGuide->editedShape())
        filteredShapes.append(m_snapGuide->editedShape());
Jan Hambrecht's avatar
Jan Hambrecht committed
326 327

    return filteredShapes;
328
}
329 330 331 332 333

KoCanvasBase * KoSnapProxy::canvas()
{
    return m_snapGuide->canvas();
}