Warning, file /office/calligra/libs/flake/KoShapeManager.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* This file is part of the KDE project 0002 0003 Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org> 0004 Copyright (C) 2006-2010 Thomas Zander <zander@kde.org> 0005 Copyright (C) 2009-2010 Jan Hambrecht <jaham@gmx.net> 0006 0007 This library is free software; you can redistribute it and/or 0008 modify it under the terms of the GNU Library General Public 0009 License as published by the Free Software Foundation; either 0010 version 2 of the License, or (at your option) any later version. 0011 0012 This library is distributed in the hope that it will be useful, 0013 but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 Library General Public License for more details. 0016 0017 You should have received a copy of the GNU Library General Public License 0018 along with this library; see the file COPYING.LIB. If not, write to 0019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0020 * Boston, MA 02110-1301, USA. 0021 */ 0022 0023 #include "KoShapeManager.h" 0024 #include "KoShapeManager_p.h" 0025 #include "KoSelection.h" 0026 #include "KoToolManager.h" 0027 #include "KoPointerEvent.h" 0028 #include "KoShape.h" 0029 #include "KoShape_p.h" 0030 #include "KoCanvasBase.h" 0031 #include "KoShapeContainer.h" 0032 #include "KoShapeStrokeModel.h" 0033 #include "KoShapeGroup.h" 0034 #include "KoToolProxy.h" 0035 #include "KoShapeManagerPaintingStrategy.h" 0036 #include "KoShapeShadow.h" 0037 #include "KoShapeLayer.h" 0038 #include "KoFilterEffect.h" 0039 #include "KoFilterEffectStack.h" 0040 #include "KoFilterEffectRenderContext.h" 0041 #include "KoShapeBackground.h" 0042 #include <KoRTree.h> 0043 #include "KoClipPath.h" 0044 #include "KoShapePaintingContext.h" 0045 0046 #include <QPainter> 0047 #include <QPainterPath> 0048 #include <QTimer> 0049 #include <FlakeDebug.h> 0050 0051 #include <algorithm> 0052 0053 void KoShapeManager::Private::updateTree() 0054 { 0055 // for detecting collisions between shapes. 0056 DetectCollision detector; 0057 bool selectionModified = false; 0058 bool anyModified = false; 0059 foreach(KoShape *shape, aggregate4update) { 0060 if (shapeIndexesBeforeUpdate.contains(shape)) 0061 detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); 0062 selectionModified = selectionModified || selection->isSelected(shape); 0063 anyModified = true; 0064 } 0065 0066 foreach (KoShape *shape, aggregate4update) { 0067 tree.remove(shape); 0068 QRectF br(shape->boundingRect()); 0069 strategy->adapt(shape, br); 0070 tree.insert(br, shape); 0071 } 0072 0073 // do it again to see which shapes we intersect with _after_ moving. 0074 foreach (KoShape *shape, aggregate4update) 0075 detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); 0076 aggregate4update.clear(); 0077 shapeIndexesBeforeUpdate.clear(); 0078 0079 detector.fireSignals(); 0080 if (selectionModified) { 0081 selection->updateSizeAndPosition(); 0082 emit q->selectionContentChanged(); 0083 } 0084 if (anyModified) { 0085 emit q->contentChanged(); 0086 } 0087 } 0088 0089 void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) 0090 { 0091 QList<KoShape*> shapes = group->shapes(); 0092 std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); 0093 foreach(KoShape *child, shapes) { 0094 // we paint recursively here, so we do not have to check recursively for visibility 0095 if (!child->isVisible()) 0096 continue; 0097 KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(child); 0098 if (childGroup) { 0099 paintGroup(childGroup, painter, converter, paintContext); 0100 } else { 0101 painter.save(); 0102 strategy->paint(child, painter, converter, paintContext); 0103 painter.restore(); 0104 } 0105 } 0106 } 0107 0108 KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList<KoShape *> &shapes) 0109 : d(new Private(this, canvas)) 0110 { 0111 Q_ASSERT(d->canvas); // not optional. 0112 connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); 0113 setShapes(shapes); 0114 } 0115 0116 KoShapeManager::KoShapeManager(KoCanvasBase *canvas) 0117 : d(new Private(this, canvas)) 0118 { 0119 Q_ASSERT(d->canvas); // not optional. 0120 connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); 0121 } 0122 0123 KoShapeManager::~KoShapeManager() 0124 { 0125 foreach(KoShape *shape, d->shapes) { 0126 shape->priv()->removeShapeManager(this); 0127 } 0128 foreach(KoShape *shape, d->additionalShapes) { 0129 shape->priv()->removeShapeManager(this); 0130 } 0131 delete d; 0132 } 0133 0134 0135 void KoShapeManager::setShapes(const QList<KoShape *> &shapes, Repaint repaint) 0136 { 0137 //clear selection 0138 d->selection->deselectAll(); 0139 foreach(KoShape *shape, d->shapes) { 0140 shape->priv()->removeShapeManager(this); 0141 } 0142 d->aggregate4update.clear(); 0143 d->tree.clear(); 0144 d->shapes.clear(); 0145 foreach(KoShape *shape, shapes) { 0146 addShape(shape, repaint); 0147 } 0148 } 0149 0150 void KoShapeManager::addShape(KoShape *shape, Repaint repaint) 0151 { 0152 if (d->shapes.contains(shape)) 0153 return; 0154 shape->priv()->addShapeManager(this); 0155 d->shapes.append(shape); 0156 if (! dynamic_cast<KoShapeGroup*>(shape) && ! dynamic_cast<KoShapeLayer*>(shape)) { 0157 QRectF br(shape->boundingRect()); 0158 d->tree.insert(br, shape); 0159 } 0160 if (repaint == PaintShapeOnAdd) { 0161 shape->update(); 0162 } 0163 0164 // add the children of a KoShapeContainer 0165 KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape); 0166 0167 if (container) { 0168 foreach (KoShape *containerShape, container->shapes()) { 0169 addShape(containerShape, repaint); 0170 } 0171 } 0172 0173 Private::DetectCollision detector; 0174 detector.detect(d->tree, shape, shape->zIndex()); 0175 detector.fireSignals(); 0176 } 0177 0178 void KoShapeManager::addAdditional(KoShape *shape) 0179 { 0180 if (shape) { 0181 if (d->additionalShapes.contains(shape)) { 0182 return; 0183 } 0184 shape->priv()->addShapeManager(this); 0185 d->additionalShapes.append(shape); 0186 } 0187 } 0188 0189 void KoShapeManager::remove(KoShape *shape) 0190 { 0191 Private::DetectCollision detector; 0192 detector.detect(d->tree, shape, shape->zIndex()); 0193 detector.fireSignals(); 0194 0195 shape->update(); 0196 shape->priv()->removeShapeManager(this); 0197 d->selection->deselect(shape); 0198 d->aggregate4update.remove(shape); 0199 d->tree.remove(shape); 0200 d->shapes.removeAll(shape); 0201 0202 // remove the children of a KoShapeContainer 0203 KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape); 0204 if (container) { 0205 foreach (KoShape *containerShape, container->shapes()) { 0206 remove(containerShape); 0207 } 0208 } 0209 0210 // This signal is used in the annotation shape. 0211 // FIXME: Is this really what we want? (and shouldn't it be called shapeDeleted()?) 0212 emit shapeRemoved(shape); 0213 } 0214 0215 void KoShapeManager::removeAdditional(KoShape *shape) 0216 { 0217 if (shape) { 0218 shape->priv()->removeShapeManager(this); 0219 d->additionalShapes.removeAll(shape); 0220 } 0221 } 0222 0223 void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint) 0224 { 0225 d->updateTree(); 0226 painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. 0227 painter.setBrush(Qt::NoBrush); 0228 0229 QList<KoShape*> unsortedShapes; 0230 if (painter.hasClipping()) { 0231 QRectF rect = converter.viewToDocument(painter.clipRegion().boundingRect()); 0232 unsortedShapes = d->tree.intersects(rect); 0233 } else { 0234 unsortedShapes = shapes(); 0235 warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!"; 0236 } 0237 0238 // filter all hidden shapes from the list 0239 // also filter shapes with a parent which has filter effects applied 0240 QList<KoShape*> sortedShapes; 0241 foreach (KoShape *shape, unsortedShapes) { 0242 if (!shape->isVisible(true)) 0243 continue; 0244 bool addShapeToList = true; 0245 // check if one of the shapes ancestors have filter effects 0246 KoShapeContainer *parent = shape->parent(); 0247 while (parent) { 0248 // parent must be part of the shape manager to be taken into account 0249 if (!d->shapes.contains(parent)) 0250 break; 0251 if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) { 0252 addShapeToList = false; 0253 break; 0254 } 0255 parent = parent->parent(); 0256 } 0257 if (addShapeToList) { 0258 sortedShapes.append(shape); 0259 } else if (parent) { 0260 sortedShapes.append(parent); 0261 } 0262 } 0263 0264 std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); 0265 0266 foreach (KoShape *shape, sortedShapes) { 0267 if (shape->parent() != 0 && shape->parent()->isClipped(shape)) 0268 continue; 0269 0270 painter.save(); 0271 0272 // apply shape clipping 0273 KoClipPath::applyClipping(shape, painter, converter); 0274 0275 // let the painting strategy paint the shape 0276 KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME 0277 d->strategy->paint(shape, painter, converter, paintContext); 0278 0279 painter.restore(); 0280 } 0281 0282 #ifdef CALLIGRA_RTREE_DEBUG 0283 // paint tree 0284 qreal zx = 0; 0285 qreal zy = 0; 0286 converter.zoom(&zx, &zy); 0287 painter.save(); 0288 painter.scale(zx, zy); 0289 d->tree.paint(painter); 0290 painter.restore(); 0291 #endif 0292 0293 if (! forPrint) { 0294 KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME 0295 d->selection->paint(painter, converter, paintContext); 0296 } 0297 } 0298 0299 void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) 0300 { 0301 qreal transparency = shape->transparency(true); 0302 if (transparency > 0.0) { 0303 painter.setOpacity(1.0-transparency); 0304 } 0305 0306 if (shape->shadow()) { 0307 painter.save(); 0308 shape->shadow()->paint(shape, painter, converter); 0309 painter.restore(); 0310 } 0311 if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) { 0312 painter.save(); 0313 shape->paint(painter, converter, paintContext); 0314 painter.restore(); 0315 if (shape->stroke()) { 0316 painter.save(); 0317 shape->stroke()->paint(shape, painter, converter); 0318 painter.restore(); 0319 } 0320 } else { 0321 // There are filter effects, then we need to prerender the shape on an image, to filter it 0322 QRectF shapeBound(QPointF(), shape->size()); 0323 // First step, compute the rectangle used for the image 0324 QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound); 0325 // convert clip region to view coordinates 0326 QRectF zoomedClipRegion = converter.documentToView(clipRegion); 0327 // determine the offset of the clipping rect from the shapes origin 0328 QPointF clippingOffset = zoomedClipRegion.topLeft(); 0329 0330 // Initialize the buffer image 0331 QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); 0332 sourceGraphic.fill(qRgba(0,0,0,0)); 0333 0334 QHash<QString, QImage> imageBuffers; 0335 0336 QSet<QString> requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs(); 0337 0338 if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) { 0339 // Init the buffer painter 0340 QPainter imagePainter(&sourceGraphic); 0341 imagePainter.translate(-1.0f*clippingOffset); 0342 imagePainter.setPen(Qt::NoPen); 0343 imagePainter.setBrush(Qt::NoBrush); 0344 imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); 0345 0346 // Paint the shape on the image 0347 KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape); 0348 if (group) { 0349 // the childrens matrix contains the groups matrix as well 0350 // so we have to compensate for that before painting the children 0351 imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true); 0352 d->paintGroup(group, imagePainter, converter, paintContext); 0353 } else { 0354 imagePainter.save(); 0355 shape->paint(imagePainter, converter, paintContext); 0356 imagePainter.restore(); 0357 if (shape->stroke()) { 0358 imagePainter.save(); 0359 shape->stroke()->paint(shape, imagePainter, converter); 0360 imagePainter.restore(); 0361 } 0362 imagePainter.end(); 0363 } 0364 } 0365 if (requiredStdInputs.contains("SourceAlpha")) { 0366 QImage sourceAlpha = sourceGraphic; 0367 sourceAlpha.fill(qRgba(0,0,0,255)); 0368 sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel()); 0369 imageBuffers.insert("SourceAlpha", sourceAlpha); 0370 } 0371 if (requiredStdInputs.contains("FillPaint")) { 0372 QImage fillPaint = sourceGraphic; 0373 if (shape->background()) { 0374 QPainter fillPainter(&fillPaint); 0375 QPainterPath fillPath; 0376 fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1)); 0377 shape->background()->paint(fillPainter, converter, paintContext, fillPath); 0378 } else { 0379 fillPaint.fill(qRgba(0,0,0,0)); 0380 } 0381 imageBuffers.insert("FillPaint", fillPaint); 0382 } 0383 0384 imageBuffers.insert("SourceGraphic", sourceGraphic); 0385 imageBuffers.insert(QString(), sourceGraphic); 0386 0387 KoFilterEffectRenderContext renderContext(converter); 0388 renderContext.setShapeBoundingBox(shapeBound); 0389 0390 QImage result; 0391 QList<KoFilterEffect*> filterEffects = shape->filterEffectStack()->filterEffects(); 0392 // Filter 0393 foreach (KoFilterEffect *filterEffect, filterEffects) { 0394 QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound); 0395 filterRegion = converter.documentToView(filterRegion); 0396 QRect subRegion = filterRegion.translated(-clippingOffset).toRect(); 0397 // set current filter region 0398 renderContext.setFilterRegion(subRegion & sourceGraphic.rect()); 0399 0400 if (filterEffect->maximalInputCount() <= 1) { 0401 QList<QString> inputs = filterEffect->inputs(); 0402 QString input = inputs.count() ? inputs.first() : QString(); 0403 // get input image from image buffers and apply the filter effect 0404 QImage image = imageBuffers.value(input); 0405 if (!image.isNull()) { 0406 result = filterEffect->processImage(imageBuffers.value(input), renderContext); 0407 } 0408 } else { 0409 QVector<QImage> inputImages; 0410 foreach(const QString &input, filterEffect->inputs()) { 0411 QImage image = imageBuffers.value(input); 0412 if (!image.isNull()) 0413 inputImages.append(imageBuffers.value(input)); 0414 } 0415 // apply the filter effect 0416 if (filterEffect->inputs().count() == inputImages.count()) 0417 result = filterEffect->processImages(inputImages, renderContext); 0418 } 0419 // store result of effect 0420 imageBuffers.insert(filterEffect->output(), result); 0421 } 0422 0423 KoFilterEffect *lastEffect = filterEffects.last(); 0424 0425 // Paint the result 0426 painter.save(); 0427 painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output())); 0428 painter.restore(); 0429 } 0430 } 0431 0432 KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) 0433 { 0434 d->updateTree(); 0435 QList<KoShape*> sortedShapes(d->tree.contains(position)); 0436 std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); 0437 KoShape *firstUnselectedShape = 0; 0438 for (int count = sortedShapes.count() - 1; count >= 0; count--) { 0439 KoShape *shape = sortedShapes.at(count); 0440 if (omitHiddenShapes && ! shape->isVisible(true)) 0441 continue; 0442 if (! shape->hitTest(position)) 0443 continue; 0444 0445 switch (selection) { 0446 case KoFlake::ShapeOnTop: 0447 if (shape->isSelectable()) 0448 return shape; 0449 case KoFlake::Selected: 0450 if (d->selection->isSelected(shape)) 0451 return shape; 0452 break; 0453 case KoFlake::Unselected: 0454 if (! d->selection->isSelected(shape)) 0455 return shape; 0456 break; 0457 case KoFlake::NextUnselected: 0458 // we want an unselected shape 0459 if (d->selection->isSelected(shape)) 0460 continue; 0461 // memorize the first unselected shape 0462 if (! firstUnselectedShape) 0463 firstUnselectedShape = shape; 0464 // check if the shape above is selected 0465 if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1))) 0466 return shape; 0467 break; 0468 } 0469 } 0470 // if we want the next unselected below a selected but there was none selected, 0471 // return the first found unselected shape 0472 if (selection == KoFlake::NextUnselected && firstUnselectedShape) 0473 return firstUnselectedShape; 0474 0475 if (d->selection->hitTest(position)) 0476 return d->selection; 0477 0478 return 0; // missed everything 0479 } 0480 0481 QList<KoShape *> KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes) 0482 { 0483 d->updateTree(); 0484 QList<KoShape*> intersectedShapes(d->tree.intersects(rect)); 0485 0486 for (int count = intersectedShapes.count() - 1; count >= 0; count--) { 0487 0488 KoShape *shape = intersectedShapes.at(count); 0489 0490 if (omitHiddenShapes && ! shape->isVisible(true)) { 0491 intersectedShapes.removeAt(count); 0492 } else { 0493 const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); 0494 if (! outline.intersects(rect) && ! outline.contains(rect)) { 0495 intersectedShapes.removeAt(count); 0496 } 0497 } 0498 } 0499 return intersectedShapes; 0500 } 0501 0502 void KoShapeManager::update(QRectF &rect, const KoShape *shape, bool selectionHandles) 0503 { 0504 d->canvas->updateCanvas(rect); 0505 if (selectionHandles && d->selection->isSelected(shape)) { 0506 if (d->canvas->toolProxy()) 0507 d->canvas->toolProxy()->repaintDecorations(); 0508 } 0509 } 0510 0511 void KoShapeManager::notifyShapeChanged(KoShape *shape) 0512 { 0513 Q_ASSERT(shape); 0514 if (d->aggregate4update.contains(shape) || d->additionalShapes.contains(shape)) { 0515 return; 0516 } 0517 const bool wasEmpty = d->aggregate4update.isEmpty(); 0518 d->aggregate4update.insert(shape); 0519 d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex()); 0520 0521 KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape); 0522 if (container) { 0523 foreach(KoShape *child, container->shapes()) 0524 notifyShapeChanged(child); 0525 } 0526 if (wasEmpty && !d->aggregate4update.isEmpty()) 0527 QTimer::singleShot(100, this, SLOT(updateTree())); 0528 emit shapeChanged(shape); 0529 } 0530 0531 QList<KoShape*> KoShapeManager::shapes() const 0532 { 0533 return d->shapes; 0534 } 0535 0536 QList<KoShape*> KoShapeManager::topLevelShapes() const 0537 { 0538 QList<KoShape*> shapes; 0539 // get all toplevel shapes 0540 foreach(KoShape *shape, d->shapes) { 0541 if (shape->parent() == 0) { 0542 shapes.append(shape); 0543 } 0544 } 0545 return shapes; 0546 } 0547 0548 KoSelection *KoShapeManager::selection() const 0549 { 0550 return d->selection; 0551 } 0552 0553 void KoShapeManager::suggestChangeTool(KoPointerEvent *event) 0554 { 0555 QList<KoShape*> shapes; 0556 0557 KoShape *clicked = shapeAt(event->point); 0558 if (clicked) { 0559 if (! selection()->isSelected(clicked)) { 0560 selection()->deselectAll(); 0561 selection()->select(clicked); 0562 } 0563 shapes.append(clicked); 0564 } 0565 0566 QList<KoShape*> shapes2; 0567 foreach (KoShape *shape, shapes) { 0568 QSet<KoShape*> delegates = shape->toolDelegates(); 0569 if (delegates.isEmpty()) { 0570 shapes2.append(shape); 0571 } else { 0572 foreach (KoShape *delegatedShape, delegates) { 0573 shapes2.append(delegatedShape); 0574 } 0575 } 0576 } 0577 KoToolManager::instance()->switchToolRequested( 0578 KoToolManager::instance()->preferredToolForSelection(shapes2)); 0579 } 0580 0581 void KoShapeManager::setPaintingStrategy(KoShapeManagerPaintingStrategy *strategy) 0582 { 0583 delete d->strategy; 0584 d->strategy = strategy; 0585 } 0586 0587 KoCanvasBase *KoShapeManager::canvas() 0588 { 0589 return d->canvas; 0590 } 0591 0592 //have to include this because of Q_PRIVATE_SLOT 0593 #include "moc_KoShapeManager.cpp"