libs/flake

KoShapeManager.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002 
00003    Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
00004    Copyright (C) 2006-2009 Thomas Zander <zander@kde.org>
00005    Copyright (C) 2009 Jan Hambrecht <jaham@gmx.net>
00006 
00007    This library is free software; you can redistribute it and/or
00008    modify it under the terms of the GNU Library General Public
00009    License as published by the Free Software Foundation; either
00010    version 2 of the License, or (at your option) any later version.
00011 
00012    This library is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015    Library General Public License for more details.
00016 
00017    You should have received a copy of the GNU Library General Public License
00018    along with this library; see the file COPYING.LIB.  If not, write to
00019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020  * Boston, MA 02110-1301, USA.
00021 */
00022 
00023 #include "KoShapeManager.h"
00024 #include "KoSelection.h"
00025 #include "KoToolManager.h"
00026 #include "KoPointerEvent.h"
00027 #include "KoShape.h"
00028 #include "KoCanvasBase.h"
00029 #include "KoShapeContainer.h"
00030 #include "KoShapeBorderModel.h"
00031 #include "KoShapeGroup.h"
00032 #include "KoToolProxy.h"
00033 #include "KoShapeManagerPaintingStrategy.h"
00034 #include "KoShapeShadow.h"
00035 #include "KoShapeLayer.h"
00036 #include "KoFilterEffect.h"
00037 #include "KoFilterEffectStack.h"
00038 #include "KoFilterEffectRenderContext.h"
00039 
00040 #include <KoRTree.h>
00041 
00042 #include <QPainter>
00043 #include <kdebug.h>
00044 
00045 class KoShapeManager::Private
00046 {
00047 public:
00048     Private(KoShapeManager *shapeManager, KoCanvasBase *c)
00049         : selection(new KoSelection()),
00050           canvas(c),
00051           tree(4, 2),
00052           strategy(new KoShapeManagerPaintingStrategy(shapeManager)),
00053           q(shapeManager)
00054     {
00055     }
00056 
00057     ~Private() {
00058         delete selection;
00059         delete strategy;
00060     }
00061 
00066     void updateTree();
00067 
00068     class DetectCollision
00069     {
00070     public:
00071         DetectCollision() {}
00072         void detect(KoRTree<KoShape *> &tree, KoShape *s, int prevZIndex) {
00073             foreach(KoShape *shape, tree.intersects(s->boundingRect())) {
00074                 bool isChild = false;
00075                 KoShapeContainer *parent = s->parent();
00076                 while (parent && !isChild) {
00077                     if (parent == shape)
00078                         isChild = true;
00079                     parent = parent->parent();
00080                 }
00081                 if (isChild)
00082                     continue;
00083                 if (s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex())
00084                     // Moving a shape will only make it collide with shapes below it.
00085                     continue;
00086                 if (shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape))
00087                     shapesWithCollisionDetection.append(shape);
00088             }
00089         }
00090 
00091         void fireSignals() {
00092             foreach(KoShape *shape, shapesWithCollisionDetection)
00093                 shape->shapeChanged(KoShape::CollisionDetected);
00094         }
00095 
00096     private:
00097         QList<KoShape*> shapesWithCollisionDetection;
00098     };
00099 
00100     QList<KoShape *> shapes;
00101     QList<KoShape *> additionalShapes; // these are shapes that are only handled for updates
00102     KoSelection *selection;
00103     KoCanvasBase *canvas;
00104     KoRTree<KoShape *> tree;
00105     QSet<KoShape *> aggregate4update;
00106     QHash<KoShape*, int> shapeIndexesBeforeUpdate;
00107     KoShapeManagerPaintingStrategy *strategy;
00108     KoShapeManager *q;
00109 };
00110 
00111 void KoShapeManager::Private::updateTree()
00112 {
00113     // for detecting collisions between shapes.
00114     DetectCollision detector;
00115     bool selectionModified = false;
00116     foreach(KoShape *shape, aggregate4update) {
00117         if (shapeIndexesBeforeUpdate.contains(shape))
00118             detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
00119         selectionModified = selectionModified || selection->isSelected(shape);
00120     }
00121 
00122     foreach(KoShape * shape, aggregate4update) {
00123         tree.remove(shape);
00124         QRectF br(shape->boundingRect());
00125         strategy->adapt(shape, br);
00126         tree.insert(br, shape);
00127     }
00128 
00129     // do it again to see which shapes we intersect with _after_ moving.
00130     foreach(KoShape *shape, aggregate4update)
00131         detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
00132     aggregate4update.clear();
00133     shapeIndexesBeforeUpdate.clear();
00134 
00135     detector.fireSignals();
00136     if (selectionModified) {
00137         selection->updateSizeAndPosition();
00138         emit q->selectionContentChanged();
00139     }
00140 }
00141 
00142 KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList<KoShape *> &shapes)
00143         : d(new Private(this, canvas))
00144 {
00145     Q_ASSERT(d->canvas); // not optional.
00146     connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
00147     setShapes(shapes);
00148 }
00149 
00150 KoShapeManager::KoShapeManager(KoCanvasBase *canvas)
00151         : d(new Private(this, canvas))
00152 {
00153     Q_ASSERT(d->canvas); // not optional.
00154     connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
00155 }
00156 
00157 KoShapeManager::~KoShapeManager()
00158 {
00159     foreach(KoShape *shape, d->shapes) {
00160         shape->removeShapeManager(this);
00161     }
00162     foreach(KoShape *shape, d->additionalShapes) {
00163         shape->removeShapeManager(this);
00164     }
00165     delete d;
00166 }
00167 
00168 
00169 void KoShapeManager::setShapes(const QList<KoShape *> &shapes, Repaint repaint)
00170 {
00171     //clear selection
00172     d->selection->deselectAll();
00173     foreach(KoShape *shape, d->shapes) {
00174         shape->removeShapeManager(this);
00175     }
00176     d->aggregate4update.clear();
00177     d->tree.clear();
00178     d->shapes.clear();
00179     foreach(KoShape *shape, shapes) {
00180         add(shape, repaint);
00181     }
00182 }
00183 
00184 void KoShapeManager::add(KoShape *shape, Repaint repaint)
00185 {
00186     if (d->shapes.contains(shape))
00187         return;
00188     shape->addShapeManager(this);
00189     d->shapes.append(shape);
00190     if (! dynamic_cast<KoShapeGroup*>(shape) && ! dynamic_cast<KoShapeLayer*>(shape)) {
00191         QRectF br(shape->boundingRect());
00192         d->tree.insert(br, shape);
00193     }
00194     if (repaint == PaintShapeOnAdd) {
00195         shape->update();
00196     }
00197 
00198     // add the children of a KoShapeContainer
00199     KoShapeContainer* container = dynamic_cast<KoShapeContainer*>(shape);
00200 
00201     if (container) {
00202         foreach(KoShape* containerShape, container->childShapes()) {
00203             add(containerShape, repaint);
00204         }
00205     }
00206 
00207     Private::DetectCollision detector;
00208     detector.detect(d->tree, shape, shape->zIndex());
00209     detector.fireSignals();
00210 }
00211 
00212 void KoShapeManager::addAdditional(KoShape *shape)
00213 {
00214     if ( shape ) {
00215         if (d->additionalShapes.contains(shape)) {
00216             return;
00217         }
00218         shape->addShapeManager(this);
00219         d->additionalShapes.append(shape);
00220     }
00221 }
00222 
00223 void KoShapeManager::remove(KoShape *shape)
00224 {
00225     Private::DetectCollision detector;
00226     detector.detect(d->tree, shape, shape->zIndex());
00227     detector.fireSignals();
00228 
00229     shape->update();
00230     shape->removeShapeManager(this);
00231     d->selection->deselect(shape);
00232     d->aggregate4update.remove(shape);
00233     d->tree.remove(shape);
00234     d->shapes.removeAll(shape);
00235 
00236     // remove the children of a KoShapeContainer
00237     KoShapeContainer* container = dynamic_cast<KoShapeContainer*>(shape);
00238 
00239     if (container) {
00240         foreach(KoShape* containerShape, container->childShapes()) {
00241             remove(containerShape);
00242         }
00243     }
00244 }
00245 
00246 void KoShapeManager::removeAdditional(KoShape *shape)
00247 {
00248     if ( shape ) {
00249         shape->removeShapeManager(this);
00250         d->additionalShapes.removeAll(shape);
00251     }
00252 }
00253 
00254 void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint)
00255 {
00256     d->updateTree();
00257     painter.setPen(Qt::NoPen);  // painters by default have a black stroke, lets turn that off.
00258     painter.setBrush(Qt::NoBrush);
00259 
00260     QList<KoShape*> unsortedShapes;
00261     if (painter.hasClipping()) {
00262         QRectF rect = converter.viewToDocument(painter.clipRegion().boundingRect());
00263         unsortedShapes = d->tree.intersects(rect);
00264     } else {
00265         unsortedShapes = shapes();
00266         kWarning() << "KoShapeManager::paint  Painting with a painter that has no clipping will lead to too much being painted!";
00267     }
00268 
00269     // filter all hidden shapes from the list
00270     QList<KoShape*> sortedShapes;
00271     foreach(KoShape * shape, unsortedShapes) {
00272         if (shape->isVisible(true))
00273             sortedShapes.append(shape);
00274     }
00275 
00276     qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
00277 
00278     foreach(KoShape * shape, sortedShapes) {
00279         if (shape->parent() != 0 && shape->parent()->childClipped(shape))
00280             continue;
00281 
00282         d->strategy->paint(shape, painter, converter, forPrint);
00283     }
00284 
00285 #ifdef KOFFICE_RTREE_DEBUG
00286     // paint tree
00287     qreal zx = 0;
00288     qreal zy = 0;
00289     converter.zoom(&zx, &zy);
00290     painter.save();
00291     painter.scale(zx, zy);
00292     d->tree.paint(painter);
00293     painter.restore();
00294 #endif
00295 
00296     if (! forPrint)
00297         d->selection->paint(painter, converter);
00298 }
00299 
00300 void KoShapeManager::paintShape(KoShape * shape, QPainter &painter, const KoViewConverter &converter, bool forPrint)
00301 {
00302     if (shape->shadow()) {
00303         painter.save();
00304         shape->shadow()->paint(shape, painter, converter);
00305         painter.restore();
00306     }
00307     if(!shape->filterEffectStack() || shape->filterEffectStack()->filterEffects().isEmpty()) {
00308         painter.save();
00309         shape->paint(painter, converter);
00310         painter.restore();
00311         if (shape->border()) {
00312             painter.save();
00313             shape->border()->paintBorder(shape, painter, converter);
00314             painter.restore();
00315         }
00316     } else {
00317         // There are filter effets, then we need to prerender the shape on an image, to filter it
00318         QRectF shapeBound(QPointF(), shape->size());
00319         // First step, compute the rectangle used for the image
00320         QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound);
00321         // convert clip region to view coordinates
00322         QRectF zoomedClipRegion = converter.documentToView(clipRegion);
00323         // determine the offset of the clipping rect from the shapes origin
00324         QPointF clippingOffset = zoomedClipRegion.topLeft();
00325 
00326         // Init the buffer image
00327         QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied);
00328         sourceGraphic.fill(qRgba(0,0,0,0));
00329 
00330         // Init the buffer painter
00331         QPainter imagePainter(&sourceGraphic);
00332         imagePainter.translate(-1.0f*clippingOffset);
00333         imagePainter.setPen(Qt::NoPen);
00334         imagePainter.setBrush(Qt::NoBrush);
00335         imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing));
00336 
00337         // Paint the shape on the image
00338         imagePainter.save();
00339         shape->paint(imagePainter, converter);
00340         imagePainter.restore();
00341         if (shape->border()) {
00342             imagePainter.save();
00343             shape->border()->paintBorder(shape, imagePainter, converter);
00344             imagePainter.restore();
00345         }
00346         imagePainter.end();
00347 
00348         QImage sourceAlpha = sourceGraphic;
00349         sourceAlpha.fill(qRgba(0,0,0,255));
00350         sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel());
00351 
00352         QHash<QString, QImage> imageBuffers;
00353         imageBuffers.insert("SourceGraphic", sourceGraphic);
00354         imageBuffers.insert(QString(), sourceGraphic);
00355         imageBuffers.insert("SourceAlpha", sourceAlpha);
00356 
00357         QMatrix coordTransform = QMatrix().scale(shapeBound.width(), shapeBound.height());
00358         KoFilterEffectRenderContext renderContext(converter);
00359         renderContext.setCoordinateTransformation(coordTransform);
00360         
00361         QImage result;
00362         QList<KoFilterEffect*> filterEffects = shape->filterEffectStack()->filterEffects();
00363         // Filter
00364         foreach(KoFilterEffect* filterEffect, filterEffects) {
00365             QRectF filterRegion = filterEffect->filterRectForBoundingRect(clipRegion);
00366             filterRegion = converter.documentToView(filterRegion);
00367             QRect subRegion = filterRegion.translated(-clippingOffset).toRect();
00368             // set current filter region
00369             renderContext.setFilterRegion(subRegion & sourceGraphic.rect());
00370 
00371             if (filterEffect->maximalInputCount() == 1) {
00372                 QList<QString> inputs = filterEffect->inputs();
00373                 QString input = inputs.count() ? inputs.first() : QString();
00374                 // get input image from image buffers and apply the filter effect
00375                 QImage image = imageBuffers.value(input);
00376                 if (!image.isNull()) {
00377                     result = filterEffect->processImage(imageBuffers.value(input), renderContext);
00378                 }
00379             } else {
00380                 QList<QImage> inputImages;
00381                 foreach(const QString &input, filterEffect->inputs()) {
00382                     QImage image = imageBuffers.value(input);
00383                     if (!image.isNull())
00384                         inputImages.append(imageBuffers.value(input));
00385                 }
00386                 // apply the filter effect
00387                 if (filterEffect->inputs().count() == inputImages.count())
00388                     result = filterEffect->processImages(inputImages, renderContext);
00389             }
00390             // store result of effect
00391             imageBuffers.insert(filterEffect->output(), result);
00392         }
00393 
00394         KoFilterEffect * lastEffect = filterEffects.last();
00395 
00396         // Paint the result
00397         painter.save();
00398         painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output()));
00399         painter.restore();
00400     }
00401     if (! forPrint) {
00402         painter.save();
00403         painter.setRenderHint(QPainter::Antialiasing, false);
00404         shape->paintDecorations(painter, converter, d->canvas);
00405         painter.restore();
00406     }
00407 }
00408 
00409 
00410 KoShape * KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes)
00411 {
00412     d->updateTree();
00413     QList<KoShape*> sortedShapes(d->tree.contains(position));
00414     qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
00415     KoShape *firstUnselectedShape = 0;
00416     for (int count = sortedShapes.count() - 1; count >= 0; count--) {
00417         KoShape *shape = sortedShapes.at(count);
00418         if (omitHiddenShapes && ! shape->isVisible(true))
00419             continue;
00420         if (! shape->hitTest(position))
00421             continue;
00422 
00423         switch (selection) {
00424         case KoFlake::ShapeOnTop:
00425             if (shape->isSelectable())
00426                 return shape;
00427         case KoFlake::Selected:
00428             if (d->selection->isSelected(shape))
00429                 return shape;
00430             break;
00431         case KoFlake::Unselected:
00432             if (! d->selection->isSelected(shape))
00433                 return shape;
00434             break;
00435         case KoFlake::NextUnselected:
00436             // we want an unselected shape
00437             if (d->selection->isSelected(shape))
00438                 continue;
00439             // memorize the first unselected shape
00440             if (! firstUnselectedShape)
00441                 firstUnselectedShape = shape;
00442             // check if the shape above is selected
00443             if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1)))
00444                 return shape;
00445             break;
00446         }
00447     }
00448     // if we want the next unselected below a selected but there was none selected,
00449     // return the first found unselected shape
00450     if (selection == KoFlake::NextUnselected && firstUnselectedShape)
00451         return firstUnselectedShape;
00452 
00453     if (d->selection->hitTest(position))
00454         return d->selection;
00455 
00456     return 0; // missed everything
00457 }
00458 
00459 QList<KoShape *> KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes)
00460 {
00461     d->updateTree();
00462 
00463     QList<KoShape*> intersectedShapes(d->tree.intersects(rect));
00464     for (int count = intersectedShapes.count() - 1; count >= 0; count--) {
00465         KoShape *shape = intersectedShapes.at(count);
00466         if ( omitHiddenShapes && ! shape->isVisible(true)) {
00467             intersectedShapes.removeAt(count);
00468         }
00469         else {
00470             const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline());
00471             if( ! outline.intersects( rect ) && ! outline.contains( rect ) ) {
00472                 intersectedShapes.removeAt(count);
00473             }
00474         }
00475     }
00476     return intersectedShapes;
00477 }
00478 
00479 void KoShapeManager::update(QRectF &rect, const KoShape *shape, bool selectionHandles)
00480 {
00481     d->canvas->updateCanvas(rect);
00482     if (selectionHandles && d->selection->isSelected(shape)) {
00483         if (d->canvas->toolProxy())
00484             d->canvas->toolProxy()->repaintDecorations();
00485     }
00486 }
00487 
00488 void KoShapeManager::notifyShapeChanged(KoShape * shape)
00489 {
00490     Q_ASSERT(shape);
00491     if (d->aggregate4update.contains(shape) || d->additionalShapes.contains(shape)) {
00492         return;
00493     }
00494     d->aggregate4update.insert(shape);
00495     d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex());
00496 
00497     KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
00498     if (container) {
00499         foreach(KoShape *child, container->childShapes())
00500             d->aggregate4update.insert(child);
00501     }
00502 }
00503 
00504 QList<KoShape*> KoShapeManager::shapes() const
00505 {
00506     return d->shapes;
00507 }
00508 
00509 QList<KoShape*> KoShapeManager::topLevelShapes() const
00510 {
00511     QList<KoShape*> shapes;
00512     // get all toplevel shapes
00513     foreach(KoShape *shape, d->shapes) {
00514         if (shape->parent() == 0) {
00515             shapes.append(shape);
00516         }
00517     }
00518     return shapes;
00519 }
00520 
00521 KoSelection * KoShapeManager::selection() const
00522 {
00523     return d->selection;
00524 }
00525 
00526 void KoShapeManager::suggestChangeTool(KoPointerEvent *event)
00527 {
00528     QList<KoShape*> shapes;
00529 
00530     KoShape *clicked = shapeAt(event->point);
00531     if (clicked) {
00532         if (! selection()->isSelected(clicked)) {
00533             selection()->deselectAll();
00534             selection()->select(clicked);
00535         }
00536         shapes.append(clicked);
00537     }
00538     KoToolManager::instance()->switchToolRequested(
00539         KoToolManager::instance()->preferredToolForSelection(shapes));
00540 }
00541 
00542 void KoShapeManager::setPaintingStrategy(KoShapeManagerPaintingStrategy * strategy)
00543 {
00544     delete d->strategy;
00545     d->strategy = strategy;
00546 }
00547 
00548 #include "KoShapeManager.moc"