KoShapeManager.cpp 12.9 KB
Newer Older
Thomas Zander's avatar
Thomas Zander committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/* This file is part of the KDE project

   Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
   Copyright (C) 2006 Thomas Zander <zander@kde.org>

   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.
*/

#include "KoShapeManager.h"
#include "KoSelection.h"
#include "KoShape.h"
25
#include "KoShapeConnection.h"
Thomas Zander's avatar
Thomas Zander committed
26 27 28
#include "KoCanvasBase.h"
#include "KoShapeContainer.h"
#include "KoShapeBorderModel.h"
29
#include "KoShapeGroup.h"
30
#include "KoToolProxy.h"
Thomas Zander's avatar
Thomas Zander committed
31 32

#include <QPainter>
33 34 35 36
#include <kdebug.h>

class KoShapeManager::Private {
public:
37
    Private(KoCanvasBase *c) : canvas(c), tree(4, 2), connectionTree(4, 2) {
38 39 40 41 42 43
        selection = new KoSelection();
    }
    QList<KoShape *> shapes;
    KoSelection * selection;
    KoCanvasBase * canvas;
    KoRTree<KoShape *> tree;
44
    KoRTree<KoShapeConnection *> connectionTree;
45 46 47
    QSet<KoShape *> aggregate4update;
    QHash<KoShape*, int> shapeIndexesBeforeUpdate;
};
Thomas Zander's avatar
Thomas Zander committed
48

Thomas Zander's avatar
Thomas Zander committed
49
KoShapeManager::KoShapeManager( KoCanvasBase *canvas, const QList<KoShape *> &shapes )
50
: d (new Private(canvas))
Thomas Zander's avatar
Thomas Zander committed
51
{
52 53
    Q_ASSERT(d->canvas); // not optional.
    connect( d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()) );
Thomas Zander's avatar
Thomas Zander committed
54
    setShapes(shapes);
Thomas Zander's avatar
Thomas Zander committed
55 56 57
}

KoShapeManager::KoShapeManager(KoCanvasBase *canvas)
58
: d (new Private(canvas))
Thomas Zander's avatar
Thomas Zander committed
59
{
60 61
    Q_ASSERT(d->canvas); // not optional.
    connect( d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()) );
Thomas Zander's avatar
Thomas Zander committed
62 63 64 65
}

KoShapeManager::~KoShapeManager()
{
66
    foreach(KoShape *shape, d->shapes)
67
        shape->removeShapeManager( this );
68
    delete d->selection;
Thomas Zander's avatar
Thomas Zander committed
69 70 71
}


Thomas Zander's avatar
Thomas Zander committed
72
void KoShapeManager::setShapes( const QList<KoShape *> &shapes )
Thomas Zander's avatar
Thomas Zander committed
73
{
74
    //clear selection
75 76
    d->selection->deselectAll();
    foreach(KoShape *shape, d->shapes)
77
    {
78 79
        d->aggregate4update.remove( shape );
        d->tree.remove( shape );
80 81
        shape->removeShapeManager( this );
    }
82
    d->shapes.clear();
83
    foreach(KoShape *shape, shapes)
84 85 86
    {
        add( shape );
    }
Thomas Zander's avatar
Thomas Zander committed
87 88
}

89 90
void KoShapeManager::add( KoShape *shape )
{
91
    if(d->shapes.contains(shape))
92
        return;
93
    shape->addShapeManager( this );
94
    d->shapes.append(shape);
95 96 97
    if( ! dynamic_cast<KoShapeGroup*>( shape ))
    {
        QRectF br( shape->boundingRect() );
98
        d->tree.insert( br, shape );
99
    }
100
    shape->repaint();
101 102 103 104 105 106 107 108 109 110 111

    // add the children of a KoShapeContainer
    KoShapeContainer* container = dynamic_cast<KoShapeContainer*>(shape);

    if(container)
    {
        foreach(KoShape* containerShape, container->iterator())
        {
            add(containerShape);
        }
    }
Thomas Zander's avatar
Thomas Zander committed
112 113
}

114 115
void KoShapeManager::remove( KoShape *shape )
{
116
    shape->repaint();
117
    shape->removeShapeManager( this );
118 119 120 121
    d->selection->deselect( shape );
    d->aggregate4update.remove( shape );
    d->tree.remove( shape );
    d->shapes.removeAll(shape);
122 123 124 125 126 127 128 129 130 131 132

    // remove the children of a KoShapeContainer
    KoShapeContainer* container = dynamic_cast<KoShapeContainer*>(shape);

    if(container)
    {
        foreach(KoShape* containerShape, container->iterator())
        {
            remove(containerShape);
        }
    }
Thomas Zander's avatar
Thomas Zander committed
133 134
}

135
void KoShapeManager::paint( QPainter &painter, const KoViewConverter &converter, bool forPrint)
Thomas Zander's avatar
Thomas Zander committed
136
{
137
    updateTree();
138 139
    painter.setPen( Qt::NoPen );// painters by default have a black stroke, lets turn that off.
    painter.setBrush( Qt::NoBrush );
140 141

    QList<KoShapeConnection*> sortedConnections;
Jan Hambrecht's avatar
Jan Hambrecht committed
142
    QList<KoShape*> unsortedShapes;
143 144
    if(painter.hasClipping()) {
        QRectF rect = converter.viewToDocument( painter.clipRegion().boundingRect() );
Jan Hambrecht's avatar
Jan Hambrecht committed
145
        unsortedShapes = d->tree.intersects( rect );
146 147 148
        sortedConnections = d->connectionTree.intersects( rect );
    }
    else {
Jan Hambrecht's avatar
Jan Hambrecht committed
149
        unsortedShapes = shapes();
150 151
        kWarning() << "KoShapeManager::paint  Painting with a painter that has no clipping will lead to too much being painted and no connections being painted!\n";
    }
Jan Hambrecht's avatar
Jan Hambrecht committed
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174

    // filter all hidden shapes from the list
    QList<KoShape*> sortedShapes;
    foreach( KoShape * shape, unsortedShapes )
    {
        if( ! shape->isVisible() )
            continue;

        bool hiddenFromParent = false;
        KoShapeContainer * parent = shape->parent();
        while( parent )
        {
            if( ! parent->isVisible() )
            {
                hiddenFromParent = true;
                break;
            }
            parent = parent->parent();
        }
        if( ! hiddenFromParent )
            sortedShapes.append( shape );
    }

Thomas Zander's avatar
Thomas Zander committed
175
    qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
176 177
    qSort(sortedConnections.begin(), sortedConnections.end(), KoShapeConnection::compareConnectionZIndex);
    QList<KoShapeConnection*>::iterator connectionIterator = sortedConnections.begin();
178

179
    const QRegion clipRegion = painter.clipRegion();
Thomas Zander's avatar
Thomas Zander committed
180
    foreach ( KoShape * shape, sortedShapes ) {
Thomas Zander's avatar
Thomas Zander committed
181 182 183
        if(shape->parent() != 0 && shape->parent()->childClipped(shape))
            continue;
        if(painter.hasClipping()) {
Thomas Zander's avatar
Thomas Zander committed
184
            QRectF shapeBox = shape->boundingRect();
185
            shapeBox = converter.documentToView(shapeBox);
Thomas Zander's avatar
Thomas Zander committed
186
            QRegion shapeRegion = QRegion(shapeBox.toRect());
Thomas Zander's avatar
Thomas Zander committed
187

Thomas Zander's avatar
Thomas Zander committed
188
            if(clipRegion.intersect(shapeRegion).isEmpty())
Thomas Zander's avatar
Thomas Zander committed
189 190
                continue;
        }
191 192 193 194 195 196 197

        while(connectionIterator != sortedConnections.end() && (*connectionIterator)->zIndex() < shape->zIndex()) {
            painter.save();
            (*connectionIterator)->paint( painter, converter );
            painter.restore();
            connectionIterator++;
        }
Thomas Zander's avatar
Thomas Zander committed
198 199 200 201 202 203 204 205 206 207 208 209 210 211
        painter.save();
        painter.setMatrix( shape->transformationMatrix(&converter) * painter.matrix() );

        painter.save();
        shape->paint( painter, converter );
        painter.restore();
        if(shape->border()) {
            painter.save();
            shape->border()->paintBorder(shape, painter, converter);
            painter.restore();
        }
        if(! forPrint) {
            painter.save();
            painter.setRenderHint( QPainter::Antialiasing, false );
212
            shape->paintDecorations( painter, converter, d->canvas );
Thomas Zander's avatar
Thomas Zander committed
213 214 215 216 217
            painter.restore();
        }
        painter.restore();  // for the matrix
    }

218
    while(connectionIterator != sortedConnections.end()) { // paint connections that are above the rest.
219
        painter.save();
220
        (*connectionIterator)->paint( painter, converter );
221
        painter.restore();
222
        connectionIterator++;
223 224
    }

225
#ifdef KOFFICE_RTREE_DEBUG
226 227 228 229 230 231
    // paint tree
    double zx = 0;
    double zy = 0;
    converter.zoom( &zx, &zy );
    painter.save();
    painter.scale( zx, zy );
232
    d->tree.paint( painter );
233 234 235
    painter.restore();
#endif

Thomas Zander's avatar
Thomas Zander committed
236
    if(! forPrint)
237
        d->selection->paint( painter, converter );
Thomas Zander's avatar
Thomas Zander committed
238 239
}

Thomas Zander's avatar
Thomas Zander committed
240
KoShape * KoShapeManager::shapeAt( const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes )
Thomas Zander's avatar
Thomas Zander committed
241
{
242
    updateTree();
243
    QList<KoShape*> sortedShapes( d->tree.contains( position ) );
Thomas Zander's avatar
Thomas Zander committed
244
    qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
Thomas Zander's avatar
Thomas Zander committed
245
    KoShape *firstUnselectedShape = 0;
Thomas Zander's avatar
Thomas Zander committed
246 247
    for(int count = sortedShapes.count()-1; count >= 0; count--) {
        KoShape *shape = sortedShapes.at(count);
248
        if ( omitHiddenShapes && ! shape->isVisible() )
249
            continue;
250 251 252 253
        if ( ! shape->hitTest( position ) )
            continue;

        switch ( selection )
Thomas Zander's avatar
Thomas Zander committed
254
        {
Thomas Zander's avatar
Thomas Zander committed
255
            case KoFlake::ShapeOnTop:
256
                return shape;
Thomas Zander's avatar
Thomas Zander committed
257
            case KoFlake::Selected:
258
                if ( d->selection->isSelected( shape ) )
259 260
                    return shape;
                break;
Thomas Zander's avatar
Thomas Zander committed
261
            case KoFlake::Unselected:
262
                if ( ! d->selection->isSelected( shape ) )
263 264
                    return shape;
                break;
Thomas Zander's avatar
Thomas Zander committed
265
            case KoFlake::NextUnselected:
266
                // we want an unselected shape
267
                if ( d->selection->isSelected( shape ) )
268 269
                    continue;
                // memorize the first unselected shape
Thomas Zander's avatar
Thomas Zander committed
270
                if( ! firstUnselectedShape )
271 272
                    firstUnselectedShape = shape;
                // check if the shape above is selected
273
                if( count + 1 < sortedShapes.count() && d->selection->isSelected( sortedShapes.at(count + 1) ) )
274 275
                    return shape;
                break;
Thomas Zander's avatar
Thomas Zander committed
276 277
        }
    }
278 279
    // if we want the next unselected below a selected but there was none selected, 
    // return the first found unselected shape
Thomas Zander's avatar
Thomas Zander committed
280
    if( selection == KoFlake::NextUnselected && firstUnselectedShape )
281 282
        return firstUnselectedShape;

283 284
    if ( d->selection->hitTest( position ) )
        return d->selection;
Thomas Zander's avatar
Thomas Zander committed
285 286 287

    return 0; // missed everything
}
Laurent Montel's avatar
Laurent Montel committed
288

289
QList<KoShape *> KoShapeManager::shapesAt( const QRectF &rect, bool omitHiddenShapes )
290 291
{
    updateTree();
Thomas Zander's avatar
Thomas Zander committed
292 293
    //TODO check if object (outline) is really in the rect and we are not just
    // adding objects by their bounding rect
294
    if( omitHiddenShapes ) {
295
        QList<KoShape*> intersectedShapes( d->tree.intersects( rect ) );
296 297 298 299 300 301 302 303
        for(int count = intersectedShapes.count()-1; count >= 0; count--) {
            KoShape *shape = intersectedShapes.at( count );
            if( ! shape->isVisible() )
                intersectedShapes.removeAt( count );
        }
        return intersectedShapes;
    }
    else
304
        return d->tree.intersects( rect );
305 306 307 308
}

void KoShapeManager::repaint( QRectF &rect, const KoShape *shape, bool selectionHandles )
{
309 310
    d->canvas->updateCanvas( rect );
    if ( selectionHandles && d->selection->isSelected( shape ) )
311
    {
312 313
        if ( d->canvas->toolProxy() )
            d->canvas->toolProxy()->repaintDecorations();
314
    }
315 316 317 318
    if(selectionHandles) {
        foreach(KoShapeConnection *connection, shape->connections())
            d->canvas->updateCanvas( connection->boundingRect() );
    }
319 320
}

Thomas Zander's avatar
Thomas Zander committed
321
void KoShapeManager::notifyShapeChanged( KoShape * shape )
322
{
323 324 325 326 327
    Q_ASSERT(shape);
    if(d->aggregate4update.contains(shape))
        return;
    d->aggregate4update.insert( shape );
    d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex());
328 329 330 331 332 333

    KoShapeContainer *container = dynamic_cast<KoShapeContainer*> (shape);
    if(container) {
        foreach(KoShape *child, container->iterator())
            d->aggregate4update.insert( child );
    }
334 335 336 337
}

void KoShapeManager::updateTree()
{
Thomas Zander's avatar
Thomas Zander committed
338 339 340 341
    // for detecting collisions between shapes.
    class DetectCollision {
      public:
        DetectCollision() {}
342 343
        void detect(KoRTree<KoShape *> &tree, KoShape *s, int prevZIndex) {
            foreach(KoShape *shape, tree.intersects( s->boundingRect() )) {
Thomas Zander's avatar
Thomas Zander committed
344 345
                if(shape == s)
                    continue;
346
                if(s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex())
Thomas Zander's avatar
Thomas Zander committed
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
                    // Moving a shape will only make it collide with shapes below it.
                    continue;
                if(shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape))
                    shapesWithCollisionDetection.append(shape);
            }
        }

        void fireSignals() {
            foreach(KoShape *shape, shapesWithCollisionDetection)
                shape->shapeChanged(KoShape::CollisionDetected);
        }

      private:
        QList<KoShape*> shapesWithCollisionDetection;
    };
    DetectCollision detector;
363 364 365 366
    foreach ( KoShape *shape, d->aggregate4update ) {
        if(d->shapeIndexesBeforeUpdate.contains(shape))
            detector.detect(d->tree, shape, d->shapeIndexesBeforeUpdate[shape]);
    }
Thomas Zander's avatar
Thomas Zander committed
367

368
    foreach ( KoShape * shape, d->aggregate4update )
369
    {
370
        d->tree.remove( shape );
371
        QRectF br( shape->boundingRect() );
372
        d->tree.insert( br, shape );
373 374 375 376 377

        foreach(KoShapeConnection *connection, shape->connections()) {
            d->connectionTree.remove(connection);
            d->connectionTree.insert(connection->boundingRect(), connection);
        }
378
    }
Thomas Zander's avatar
Thomas Zander committed
379 380

    // do it again to see which shapes we intersect with _after_ moving.
381 382 383 384
    foreach ( KoShape *shape, d->aggregate4update )
        detector.detect(d->tree, shape, d->shapeIndexesBeforeUpdate[shape]);
    d->aggregate4update.clear();
    d->shapeIndexesBeforeUpdate.clear();
Thomas Zander's avatar
Thomas Zander committed
385 386

    detector.fireSignals();
387 388
}

389 390 391 392 393 394 395 396
const QList<KoShape *> & KoShapeManager::shapes() const {
    return d->shapes;
}

KoSelection * KoShapeManager::selection() const {
    return d->selection;
}

397
void KoShapeManager::addShapeConnection(KoShapeConnection *connection) {
398
    d->connectionTree.insert(connection->boundingRect(), connection);
399 400
}

Laurent Montel's avatar
Laurent Montel committed
401
#include "KoShapeManager.moc"