File indexing completed on 2024-05-12 15:56:49

0001 /* This file is part of the KDE project
0002 
0003    SPDX-FileCopyrightText: 2006-2008 Thorsten Zachmann <zachmann@kde.org>
0004    SPDX-FileCopyrightText: 2006-2010 Thomas Zander <zander@kde.org>
0005    SPDX-FileCopyrightText: 2009-2010 Jan Hambrecht <jaham@gmx.net>
0006 
0007    SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "KoShapeManager.h"
0011 #include "KoShapeManager_p.h"
0012 #include "KoSelection.h"
0013 #include "KoToolManager.h"
0014 #include "KoPointerEvent.h"
0015 #include "KoShape.h"
0016 #include "KoShape_p.h"
0017 #include "KoCanvasBase.h"
0018 #include "KoShapeContainer.h"
0019 #include "KoShapeStrokeModel.h"
0020 #include "KoShapeGroup.h"
0021 #include "KoToolProxy.h"
0022 #include "KoShapeShadow.h"
0023 #include "KoShapeLayer.h"
0024 #include "KoFilterEffect.h"
0025 #include "KoFilterEffectStack.h"
0026 #include "KoFilterEffectRenderContext.h"
0027 #include "KoShapeBackground.h"
0028 #include <KoRTree.h>
0029 #include "KoClipPath.h"
0030 #include "KoClipMaskPainter.h"
0031 #include "KoViewConverter.h"
0032 #include "KisQPainterStateSaver.h"
0033 #include "KoSvgTextChunkShape.h"
0034 #include "KoSvgTextShape.h"
0035 #include <QApplication>
0036 
0037 #include <QPainter>
0038 #include <QPainterPath>
0039 #include <QTimer>
0040 #include <FlakeDebug.h>
0041 
0042 #include "kis_painting_tweaks.h"
0043 #include "kis_debug.h"
0044 #include "KisForest.h"
0045 #include <unordered_set>
0046 
0047 
0048 namespace {
0049 
0050 /**
0051  * Returns whether the shape should be added to the RTree for collision and ROI
0052  * detection.
0053  */
0054 inline bool shapeUsedInRenderingTree(KoShape *shape)
0055 {
0056     // FIXME: make more general!
0057 
0058     return !dynamic_cast<KoShapeGroup*>(shape) &&
0059             !dynamic_cast<KoShapeLayer*>(shape) &&
0060             !(dynamic_cast<KoSvgTextChunkShape*>(shape) && !dynamic_cast<KoSvgTextShape*>(shape));
0061 }
0062 
0063 /**
0064  * Returns whether a shape should be added to the rendering tree because of
0065  * its clip mask/path or effects.
0066  */
0067 inline bool shapeHasGroupEffects(KoShape *shape) {
0068     return shape->clipPath() ||
0069         (shape->filterEffectStack() && !shape->filterEffectStack()->isEmpty()) ||
0070         shape->clipMask();
0071 }
0072 
0073 /**
0074  * Returns true if the shape is not fully transparent
0075  */
0076 inline bool shapeIsVisible(KoShape *shape) {
0077     return shape->isVisible(false) && shape->transparency() < 1.0;
0078 }
0079 
0080 
0081 /**
0082  * Populate \p tree with the subtree of shapes pointed by a shape \p parentShape.
0083  * All new shapes are added as children of \p parentIt. Please take it into account
0084  * that \c *parentIt might be not the same as \c parentShape, because \c parentShape
0085  * may be hidden from rendering.
0086  */
0087 void populateRenderSubtree(KoShape *parentShape,
0088                            KisForest<KoShape*>::child_iterator parentIt,
0089                            KisForest<KoShape*> &tree,
0090                            std::function<bool(KoShape*)> shouldIncludeNode,
0091                            std::function<bool(KoShape*)> shouldEnterSubtree)
0092 {
0093     KoShapeContainer *parentContainer = dynamic_cast<KoShapeContainer*>(parentShape);
0094     if (!parentContainer) return;
0095 
0096     QList<KoShape*> children = parentContainer->shapes();
0097     std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex);
0098 
0099     for (auto it = children.constBegin(); it != children.constEnd(); ++it) {
0100         auto newParentIt = parentIt;
0101 
0102         if (shouldIncludeNode(*it)) {
0103             newParentIt = tree.insert(childEnd(parentIt), *it);
0104         }
0105 
0106         if (shouldEnterSubtree(*it)) {
0107             populateRenderSubtree(*it, newParentIt, tree, shouldIncludeNode, shouldEnterSubtree);
0108         }
0109     }
0110 
0111 }
0112 
0113 /**
0114  * Build a rendering tree for **leaf** nodes defined by \p leafNodes
0115  *
0116  * Sometimes we should render only a part of the layer (e.g. when we render
0117  * in patches). So we shouldn't render the whole graph. The problem is that
0118  * some of the shapes may have parents with clip paths/masks and/or effects.
0119  * In such a case, these parents should be also included into the rendering
0120  * process.
0121  *
0122  * \c buildRenderTree() builds a graph for such rendering. It includes the
0123  * leaf shapes themselves, and all parent shapes that have some effects affecting
0124  * these shapes.
0125  */
0126 void buildRenderTree(QList<KoShape*> leafShapes,
0127                      KisForest<KoShape*> &tree)
0128 {
0129     QList<KoShape*> sortedShapes = leafShapes;
0130     std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
0131 
0132     std::unordered_set<KoShape*> includedShapes;
0133 
0134     Q_FOREACH (KoShape *shape, sortedShapes) {
0135         bool shouldSkipShape = !shapeIsVisible(shape);
0136         if (shouldSkipShape) continue;
0137 
0138         bool shapeIsPartOfIncludedSubtree = false;
0139         QVector<KoShape*> hierarchy = {shape};
0140 
0141         while ((shape = shape->parent())) {
0142             if (!shapeIsVisible(shape)) {
0143                 shouldSkipShape = true;
0144                 break;
0145             }
0146 
0147             if (includedShapes.find(shape) != end(includedShapes)) {
0148                 shapeIsPartOfIncludedSubtree = true;
0149                 break;
0150             }
0151 
0152             if (shapeHasGroupEffects(shape)) {
0153                 hierarchy << shape;
0154             }
0155         }
0156 
0157         if (shouldSkipShape) continue;
0158 
0159         if (!shapeIsPartOfIncludedSubtree &&
0160             includedShapes.find(hierarchy.last()) == end(includedShapes)) {
0161 
0162             tree.insert(childEnd(tree), hierarchy.last());
0163         }
0164         std::copy(hierarchy.begin(), hierarchy.end(),
0165                   std::inserter(includedShapes, end(includedShapes)));
0166     }
0167 
0168     auto shouldIncludeShape =
0169         [includedShapes] (KoShape *shape) {
0170             // included shapes are guaranteed to be visible
0171             return includedShapes.find(shape) != end(includedShapes);
0172         };
0173 
0174     for (auto it = childBegin(tree); it != childEnd(tree); ++it) {
0175         populateRenderSubtree(*it, it, tree, shouldIncludeShape, &shapeIsVisible);
0176     }
0177 }
0178 
0179 /**
0180  * Render the prebuilt rendering tree on \p painter
0181  */
0182 void renderShapes(typename KisForest<KoShape*>::child_iterator beginIt,
0183                   typename KisForest<KoShape*>::child_iterator endIt,
0184                   QPainter &painter)
0185 {
0186     for (auto it = beginIt; it != endIt; ++it) {
0187         KoShape *shape = *it;
0188 
0189         KisQPainterStateSaver saver(&painter);
0190 
0191         if (!isEnd(parent(it))) {
0192             painter.setTransform(shape->transformation() * painter.transform());
0193         } else {
0194             painter.setTransform(shape->absoluteTransformation() * painter.transform());
0195         }
0196 
0197         KoClipPath::applyClipping(shape, painter);
0198 
0199         qreal transparency = shape->transparency(true);
0200         if (transparency > 0.0) {
0201             painter.setOpacity(1.0-transparency);
0202         }
0203 
0204         if (shape->shadow()) {
0205             KisQPainterStateSaver saver(&painter);
0206             shape->shadow()->paint(shape, painter);
0207         }
0208 
0209         QScopedPointer<KoClipMaskPainter> clipMaskPainter;
0210         QPainter *shapePainter = &painter;
0211 
0212         KoClipMask *clipMask = shape->clipMask();
0213         if (clipMask) {
0214             const QRectF bounds = painter.transform().mapRect(shape->outlineRect());
0215 
0216             clipMaskPainter.reset(new KoClipMaskPainter(&painter, bounds/*shape->boundingRect())*/));
0217             shapePainter = clipMaskPainter->shapePainter();
0218         }
0219 
0220         /**
0221          * We expect the shape to save/restore the painter's state itself. Such design was not
0222          * not always here, so we need a period of sanity checks to ensure all the shapes are
0223          * ported correctly.
0224          */
0225         const QTransform sanityCheckTransformSaved = shapePainter->transform();
0226 
0227         renderShapes(childBegin(it), childEnd(it), *shapePainter);
0228 
0229         shape->paint(*shapePainter);
0230         shape->paintStroke(*shapePainter);
0231 
0232         KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) {
0233             shapePainter->setTransform(sanityCheckTransformSaved);
0234         }
0235 
0236         if (clipMask) {
0237             clipMaskPainter->maskPainter()->save();
0238 
0239             shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape);
0240             clipMaskPainter->renderOnGlobalPainter();
0241 
0242             clipMaskPainter->maskPainter()->restore();
0243         }
0244     }
0245 }
0246 
0247 }
0248 
0249 void KoShapeManager::Private::updateTree()
0250 {
0251     bool selectionModified = false;
0252     bool anyModified = false;
0253 
0254     {
0255         QMutexLocker l(&this->treeMutex);
0256 
0257         Q_FOREACH (KoShape *shape, aggregate4update) {
0258             selectionModified = selectionModified || selection->isSelected(shape);
0259             anyModified = true;
0260         }
0261 
0262         foreach (KoShape *shape, aggregate4update) {
0263             if (!shapeUsedInRenderingTree(shape)) continue;
0264 
0265             tree.remove(shape);
0266             QRectF br(shape->boundingRect());
0267             tree.insert(br, shape);
0268         }
0269 
0270         aggregate4update.clear();
0271         shapeIndexesBeforeUpdate.clear();
0272     }
0273 
0274     if (selectionModified) {
0275         emit q->selectionContentChanged();
0276     }
0277     if (anyModified) {
0278         emit q->contentChanged();
0279     }
0280 }
0281 
0282 void KoShapeManager::Private::forwardCompressedUdpate()
0283 {
0284     bool shouldUpdateDecorations = false;
0285     QRectF scheduledUpdate;
0286 
0287     {
0288         QMutexLocker l(&shapesMutex);
0289 
0290         if (!compressedUpdate.isEmpty()) {
0291             scheduledUpdate = compressedUpdate;
0292             compressedUpdate = QRect();
0293         }
0294 
0295         Q_FOREACH (const KoShape *shape, compressedUpdatedShapes) {
0296             if (selection->isSelected(shape)) {
0297                 shouldUpdateDecorations = true;
0298                 break;
0299             }
0300         }
0301         compressedUpdatedShapes.clear();
0302     }
0303 
0304     if (shouldUpdateDecorations && canvas->toolProxy()) {
0305         canvas->toolProxy()->repaintDecorations();
0306     }
0307     canvas->updateCanvas(scheduledUpdate);
0308 
0309 }
0310 
0311 KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList<KoShape *> &shapes)
0312     : d(new Private(this, canvas))
0313 {
0314     Q_ASSERT(d->canvas); // not optional.
0315     connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
0316     setShapes(shapes);
0317 
0318     /**
0319      * Shape manager uses signal compressors with timers, therefore
0320      * it might handle queued signals, therefore it should belong
0321      * to the GUI thread.
0322      */
0323     this->moveToThread(qApp->thread());
0324     connect(&d->updateCompressor, SIGNAL(timeout()), this, SLOT(forwardCompressedUdpate()));
0325 }
0326 
0327 KoShapeManager::KoShapeManager(KoCanvasBase *canvas)
0328     : d(new Private(this, canvas))
0329 {
0330     Q_ASSERT(d->canvas); // not optional.
0331     connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
0332 
0333     // see a comment in another constructor
0334     this->moveToThread(qApp->thread());
0335     connect(&d->updateCompressor, SIGNAL(timeout()), this, SLOT(forwardCompressedUdpate()));
0336 }
0337 
0338 void KoShapeManager::Private::unlinkFromShapesRecursively(const QList<KoShape*> &shapes)
0339 {
0340     Q_FOREACH (KoShape *shape, shapes) {
0341         shape->removeShapeManager(q);
0342 
0343         KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
0344         if (container) {
0345             unlinkFromShapesRecursively(container->shapes());
0346         }
0347     }
0348 }
0349 
0350 KoShapeManager::~KoShapeManager()
0351 {
0352     d->unlinkFromShapesRecursively(d->shapes);
0353     d->shapes.clear();
0354 
0355     delete d;
0356 }
0357 
0358 void KoShapeManager::setShapes(const QList<KoShape *> &shapes, Repaint repaint)
0359 {
0360     {
0361         QMutexLocker l1(&d->shapesMutex);
0362         QMutexLocker l2(&d->treeMutex);
0363 
0364         //clear selection
0365         d->selection->deselectAll();
0366         d->unlinkFromShapesRecursively(d->shapes);
0367         d->compressedUpdate = QRect();
0368         d->compressedUpdatedShapes.clear();
0369         d->aggregate4update.clear();
0370         d->shapeIndexesBeforeUpdate.clear();
0371         d->tree.clear();
0372         d->shapes.clear();
0373     }
0374 
0375     Q_FOREACH (KoShape *shape, shapes) {
0376         addShape(shape, repaint);
0377     }
0378 }
0379 
0380 void KoShapeManager::addShape(KoShape *shape, Repaint repaint)
0381 {
0382     {
0383         QMutexLocker l1(&d->shapesMutex);
0384 
0385         if (d->shapes.contains(shape))
0386             return;
0387         shape->addShapeManager(this);
0388         d->shapes.append(shape);
0389 
0390         if (shapeUsedInRenderingTree(shape)) {
0391             QMutexLocker l2(&d->treeMutex);
0392 
0393             QRectF br(shape->boundingRect());
0394             d->tree.insert(br, shape);
0395         }
0396     }
0397 
0398     if (repaint == PaintShapeOnAdd) {
0399         shape->update();
0400     }
0401 
0402     // add the children of a KoShapeContainer
0403     KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
0404 
0405     if (container) {
0406         foreach (KoShape *containerShape, container->shapes()) {
0407             addShape(containerShape, repaint);
0408         }
0409     }
0410 }
0411 
0412 void KoShapeManager::remove(KoShape *shape)
0413 {
0414     QRectF dirtyRect;
0415     {
0416         QMutexLocker l1(&d->shapesMutex);
0417         QMutexLocker l2(&d->treeMutex);
0418 
0419         dirtyRect = shape->boundingRect();
0420 
0421         shape->removeShapeManager(this);
0422         d->selection->deselect(shape);
0423         d->aggregate4update.remove(shape);
0424         d->compressedUpdatedShapes.remove(shape);
0425 
0426         if (shapeUsedInRenderingTree(shape)) {
0427             d->tree.remove(shape);
0428         }
0429         d->shapes.removeAll(shape);
0430     }
0431 
0432     if (!dirtyRect.isEmpty()) {
0433         d->canvas->updateCanvas(dirtyRect);
0434     }
0435 
0436     // remove the children of a KoShapeContainer
0437     KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
0438     if (container) {
0439         foreach (KoShape *containerShape, container->shapes()) {
0440             remove(containerShape);
0441         }
0442     }
0443 }
0444 
0445 KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q)
0446     : q(_q)
0447 {
0448 }
0449 
0450 void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape)
0451 {
0452     QMutexLocker l1(&q->d->shapesMutex);
0453     QMutexLocker l2(&q->d->treeMutex);
0454 
0455     q->d->selection->deselect(shape);
0456     q->d->aggregate4update.remove(shape);
0457     q->d->compressedUpdatedShapes.remove(shape);
0458 
0459     // we cannot access RTTI of the semi-destructed shape, so just
0460     // unlink it lazily
0461     if (q->d->tree.contains(shape)) {
0462         q->d->tree.remove(shape);
0463     }
0464 
0465     q->d->shapes.removeAll(shape);
0466 }
0467 
0468 
0469 KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface()
0470 {
0471     return &d->shapeInterface;
0472 }
0473 
0474 void KoShapeManager::preparePaintJobs(PaintJobsOrder &jobsOrder,
0475                                       KoShape *excludeRoot)
0476 {
0477     d->updateTree();
0478 
0479     QMutexLocker l1(&d->shapesMutex);
0480 
0481     QSet<KoShape*> rootShapesSet;
0482     Q_FOREACH (KoShape *shape, d->shapes) {
0483         while (shape->parent() && shape->parent() != excludeRoot) {
0484             shape = shape->parent();
0485         }
0486 
0487         if (!rootShapesSet.contains(shape) && shape != excludeRoot) {
0488             rootShapesSet.insert(shape);
0489         }
0490     }
0491 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
0492     const QList<KoShape*> rootShapes(rootShapesSet.begin(), rootShapesSet.end());
0493 #else
0494     const QList<KoShape*> rootShapes = QList<KoShape*>::fromSet(rootShapesSet);
0495 #endif
0496     QList<KoShape*> newRootShapes;
0497 
0498     Q_FOREACH (KoShape *srcShape, rootShapes) {
0499         KIS_SAFE_ASSERT_RECOVER(srcShape->parent() == excludeRoot
0500                                 || !srcShape->parent()) {
0501             continue;
0502         }
0503 
0504         KoShape *clonedShape = srcShape->cloneShape();
0505 
0506         KoShapeContainer *parentShape = srcShape->parent();
0507 
0508         if (parentShape && !parentShape->transformation().isIdentity()) {
0509             clonedShape->applyAbsoluteTransformation(parentShape->transformation());
0510         }
0511 
0512         newRootShapes << clonedShape;
0513     }
0514 
0515     PaintJobsOrder result;
0516 
0517     PaintJob::SharedSafeStorage shapesStorage = std::make_shared<PaintJob::ShapesStorage>();
0518     Q_FOREACH (KoShape *shape, newRootShapes) {
0519         shapesStorage->emplace_back(std::unique_ptr<KoShape>(shape));
0520     }
0521 
0522     const QList<KoShape*> originalShapes = KoShape::linearizeSubtreeSorted(rootShapes);
0523     const QList<KoShape*> clonedShapes = KoShape::linearizeSubtreeSorted(newRootShapes);
0524     KIS_SAFE_ASSERT_RECOVER_RETURN(clonedShapes.size() == originalShapes.size());
0525 
0526     QHash<KoShape*, KoShape*> clonedFromOriginal;
0527     for (int i = 0; i < originalShapes.size(); i++) {
0528         clonedFromOriginal[originalShapes[i]] = clonedShapes[i];
0529     }
0530 
0531 
0532     for (auto it = std::begin(jobsOrder.jobs); it != std::end(jobsOrder.jobs); ++it) {
0533         QMutexLocker l(&d->treeMutex);
0534         QList<KoShape*> unsortedOriginalShapes = d->tree.intersects(it->docUpdateRect);
0535 
0536         it->allClonedShapes = shapesStorage;
0537 
0538         Q_FOREACH (KoShape *shape, unsortedOriginalShapes) {
0539             KIS_SAFE_ASSERT_RECOVER(shapeUsedInRenderingTree(shape)) { continue; }
0540             it->shapes << clonedFromOriginal[shape];
0541         }
0542     }
0543 }
0544 
0545 void KoShapeManager::paintJob(QPainter &painter, const KoShapeManager::PaintJob &job)
0546 {
0547     painter.setPen(Qt::NoPen);  // painters by default have a black stroke, lets turn that off.
0548     painter.setBrush(Qt::NoBrush);
0549 
0550     KisForest<KoShape*> renderTree;
0551     buildRenderTree(job.shapes, renderTree);
0552 
0553     renderShapes(childBegin(renderTree), childEnd(renderTree), painter);
0554 }
0555 
0556 void KoShapeManager::paint(QPainter &painter)
0557 {
0558     d->updateTree();
0559 
0560     QMutexLocker l1(&d->shapesMutex);
0561 
0562     painter.setPen(Qt::NoPen);  // painters by default have a black stroke, lets turn that off.
0563     painter.setBrush(Qt::NoBrush);
0564 
0565     QList<KoShape*> unsortedShapes;
0566     if (painter.hasClipping()) {
0567         QMutexLocker l(&d->treeMutex);
0568 
0569         QRectF rect = KisPaintingTweaks::safeClipBoundingRect(painter);
0570         unsortedShapes = d->tree.intersects(rect);
0571     } else {
0572         unsortedShapes = d->shapes;
0573         warnFlake << "KoShapeManager::paint  Painting with a painter that has no clipping will lead to too much being painted!";
0574     }
0575 
0576     KisForest<KoShape*> renderTree;
0577     buildRenderTree(unsortedShapes, renderTree);
0578     renderShapes(childBegin(renderTree), childEnd(renderTree), painter);
0579 }
0580 
0581 void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter)
0582 {
0583     KisForest<KoShape*> renderTree;
0584 
0585     KoViewConverter converter;
0586 
0587     auto root = renderTree.insert(childBegin(renderTree), shape);
0588     populateRenderSubtree(shape, root, renderTree, &shapeIsVisible, &shapeIsVisible);
0589     renderShapes(childBegin(renderTree), childEnd(renderTree), painter);
0590 }
0591 
0592 KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes)
0593 {
0594     d->updateTree();
0595 
0596     QMutexLocker l(&d->shapesMutex);
0597 
0598     QList<KoShape*> sortedShapes;
0599 
0600     {
0601         QMutexLocker l(&d->treeMutex);
0602         sortedShapes = d->tree.contains(position);
0603     }
0604 
0605     std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
0606     KoShape *firstUnselectedShape = 0;
0607     for (int count = sortedShapes.count() - 1; count >= 0; count--) {
0608         KoShape *shape = sortedShapes.at(count);
0609         if (omitHiddenShapes && ! shape->isVisible())
0610             continue;
0611         if (! shape->hitTest(position))
0612             continue;
0613 
0614         switch (selection) {
0615         case KoFlake::ShapeOnTop:
0616             if (shape->isSelectable())
0617                 return shape;
0618             break;
0619         case KoFlake::Selected:
0620             if (d->selection->isSelected(shape))
0621                 return shape;
0622             break;
0623         case KoFlake::Unselected:
0624             if (! d->selection->isSelected(shape))
0625                 return shape;
0626             break;
0627         case KoFlake::NextUnselected:
0628             // we want an unselected shape
0629             if (d->selection->isSelected(shape))
0630                 continue;
0631             // memorize the first unselected shape
0632             if (! firstUnselectedShape)
0633                 firstUnselectedShape = shape;
0634             // check if the shape above is selected
0635             if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1)))
0636                 return shape;
0637             break;
0638         }
0639     }
0640     // if we want the next unselected below a selected but there was none selected,
0641     // return the first found unselected shape
0642     if (selection == KoFlake::NextUnselected && firstUnselectedShape)
0643         return firstUnselectedShape;
0644 
0645     if (d->selection->hitTest(position))
0646         return d->selection;
0647 
0648     return 0; // missed everything
0649 }
0650 
0651 QList<KoShape *> KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode)
0652 {
0653     QMutexLocker l(&d->shapesMutex);
0654 
0655     d->updateTree();
0656     QList<KoShape*> shapes;
0657 
0658     {
0659         QMutexLocker l(&d->treeMutex);
0660         shapes = containedMode ? d->tree.contained(rect) : d->tree.intersects(rect);
0661     }
0662 
0663     for (int count = shapes.count() - 1; count >= 0; count--) {
0664 
0665         KoShape *shape = shapes.at(count);
0666 
0667         if (omitHiddenShapes && !shape->isVisible()) {
0668             shapes.removeAt(count);
0669         } else {
0670             const QPainterPath outline = shape->absoluteTransformation().map(shape->outline());
0671 
0672             if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) {
0673                 shapes.removeAt(count);
0674 
0675             } else if (containedMode) {
0676 
0677                 QPainterPath containingPath;
0678                 containingPath.addRect(rect);
0679 
0680                 if (!containingPath.contains(outline)) {
0681                     shapes.removeAt(count);
0682                 }
0683             }
0684         }
0685     }
0686 
0687     return shapes;
0688 }
0689 
0690 void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles)
0691 {
0692     if (d->updatesBlocked) return;
0693 
0694     {
0695         QMutexLocker l(&d->shapesMutex);
0696 
0697         d->compressedUpdate |= rect;
0698 
0699         if (selectionHandles) {
0700             d->compressedUpdatedShapes.insert(shape);
0701         }
0702     }
0703 
0704     d->updateCompressor.start();
0705 }
0706 
0707 void KoShapeManager::setUpdatesBlocked(bool value)
0708 {
0709     d->updatesBlocked = value;
0710 }
0711 
0712 bool KoShapeManager::updatesBlocked() const
0713 {
0714     return d->updatesBlocked;
0715 }
0716 void KoShapeManager::notifyShapeChanged(KoShape *shape)
0717 {
0718     {
0719         QMutexLocker l(&d->treeMutex);
0720 
0721         Q_ASSERT(shape);
0722         if (d->aggregate4update.contains(shape)) {
0723             return;
0724         }
0725 
0726         d->aggregate4update.insert(shape);
0727         d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex());
0728     }
0729 
0730     KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
0731     if (container) {
0732         Q_FOREACH (KoShape *child, container->shapes())
0733             notifyShapeChanged(child);
0734     }
0735 }
0736 
0737 QList<KoShape*> KoShapeManager::shapes() const
0738 {
0739     QMutexLocker l(&d->shapesMutex);
0740 
0741     return d->shapes;
0742 }
0743 
0744 QList<KoShape*> KoShapeManager::topLevelShapes() const
0745 {
0746     QMutexLocker l(&d->shapesMutex);
0747 
0748     QList<KoShape*> shapes;
0749     // get all toplevel shapes
0750     Q_FOREACH (KoShape *shape, d->shapes) {
0751         if (!shape->parent() || dynamic_cast<KoShapeLayer*>(shape->parent())) {
0752             shapes.append(shape);
0753         }
0754     }
0755     return shapes;
0756 }
0757 
0758 KoSelection *KoShapeManager::selection() const
0759 {
0760     return d->selection;
0761 }
0762 
0763 void KoShapeManager::explicitlyIssueShapeChangedSignals()
0764 {
0765     d->updateTree();
0766 }
0767 
0768 KoCanvasBase *KoShapeManager::canvas()
0769 {
0770     return d->canvas;
0771 }
0772 
0773 //have to include this because of Q_PRIVATE_SLOT
0774 #include "moc_KoShapeManager.cpp"