diff --git a/libs/flake/KoSelection.cpp b/libs/flake/KoSelection.cpp index 706ea4582f..94f96360dd 100644 --- a/libs/flake/KoSelection.cpp +++ b/libs/flake/KoSelection.cpp @@ -1,263 +1,263 @@ /* This file is part of the KDE project Copyright (C) 2006 Boudewijn Rempt Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006 Jan Hambrecht Copyright (C) 2006-2007,2009 Thomas Zander 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 "KoSelection.h" #include "KoSelection_p.h" #include "KoShapeContainer.h" #include "KoShapeGroup.h" #include "KoPointerEvent.h" #include "KoShapePaintingContext.h" #include "kis_algebra_2d.h" #include "krita_container_utils.h" #include #include "kis_debug.h" - -KoSelection::KoSelection() - : KoShape(new KoSelectionPrivate(this)) +KoSelection::KoSelection(QObject *parent) + : QObject(parent), + KoShape(new KoSelectionPrivate(this)) { Q_D(KoSelection); connect(&d->selectionChangedCompressor, SIGNAL(timeout()), SIGNAL(selectionChanged())); } KoSelection::~KoSelection() { } void KoSelection::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintcontext); } void KoSelection::setSize(const QSizeF &size) { Q_UNUSED(size); qWarning() << "WARNING: KoSelection::setSize() should never be used!"; } QSizeF KoSelection::size() const { return outlineRect().size(); } QRectF KoSelection::outlineRect() const { QPolygonF globalPolygon; Q_FOREACH (KoShape *shape, selectedVisibleShapes()) { globalPolygon = globalPolygon.united( shape->absoluteTransformation(0).map(QPolygonF(shape->outlineRect()))); } const QPolygonF localPolygon = transformation().inverted().map(globalPolygon); return localPolygon.boundingRect(); } QRectF KoSelection::boundingRect() const { return KoShape::boundingRect(selectedVisibleShapes()); } void KoSelection::select(KoShape *shape) { Q_D(KoSelection); KIS_SAFE_ASSERT_RECOVER_RETURN(shape != this); KIS_SAFE_ASSERT_RECOVER_RETURN(shape); if (!shape->isSelectable() || !shape->isVisible()) { return; } // check recursively if (isSelected(shape)) { return; } // find the topmost parent to select while (KoShapeGroup *parentGroup = dynamic_cast(shape->parent())) { shape = parentGroup; } d->selectedShapes << shape; shape->addShapeChangeListener(this); if (d->selectedShapes.size() == 1) { setTransformation(shape->absoluteTransformation(0)); } else { setTransformation(QTransform()); } d->selectionChangedCompressor.start(); } void KoSelection::deselect(KoShape *shape) { Q_D(KoSelection); if (!d->selectedShapes.contains(shape)) return; d->selectedShapes.removeAll(shape); shape->removeShapeChangeListener(this); if (d->selectedShapes.size() == 1) { setTransformation(d->selectedShapes.first()->absoluteTransformation(0)); } d->selectionChangedCompressor.start(); } void KoSelection::deselectAll() { Q_D(KoSelection); if (d->selectedShapes.isEmpty()) return; Q_FOREACH (KoShape *shape, d->selectedShapes) { shape->removeShapeChangeListener(this); } // reset the transformation matrix of the selection setTransformation(QTransform()); d->selectedShapes.clear(); d->selectionChangedCompressor.start(); } int KoSelection::count() const { Q_D(const KoSelection); return d->selectedShapes.size(); } bool KoSelection::hitTest(const QPointF &position) const { Q_D(const KoSelection); Q_FOREACH (KoShape *shape, d->selectedShapes) { if (shape->isVisible()) continue; if (shape->hitTest(position)) return true; } return false; } const QList KoSelection::selectedShapes() const { Q_D(const KoSelection); return d->selectedShapes; } const QList KoSelection::selectedVisibleShapes() const { QList shapes = selectedShapes(); KritaUtils::filterContainer (shapes, [](KoShape *shape) { return shape->isVisible(); }); return shapes; } const QList KoSelection::selectedEditableShapes() const { QList shapes = selectedShapes(); KritaUtils::filterContainer (shapes, [](KoShape *shape) { return shape->isShapeEditable(); }); return shapes; } const QList KoSelection::selectedEditableShapesAndDelegates() const { QList shapes; Q_FOREACH (KoShape *shape, selectedShapes()) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes.append(shape); } else { Q_FOREACH (KoShape *delegatedShape, delegates) { shapes.append(delegatedShape); } } } return shapes; } bool KoSelection::isSelected(const KoShape *shape) const { Q_D(const KoSelection); if (shape == this) return true; const KoShape *tmpShape = shape; while (tmpShape && std::find(d->selectedShapes.begin(), d->selectedShapes.end(), tmpShape) == d->selectedShapes.end()) { tmpShape = tmpShape->parent(); } return tmpShape; } KoShape *KoSelection::firstSelectedShape() const { Q_D(const KoSelection); return !d->selectedShapes.isEmpty() ? d->selectedShapes.first() : 0; } void KoSelection::setActiveLayer(KoShapeLayer *layer) { Q_D(KoSelection); d->activeLayer = layer; emit currentLayerChanged(layer); } KoShapeLayer* KoSelection::activeLayer() const { Q_D(const KoSelection); return d->activeLayer; } void KoSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) { Q_UNUSED(shape); if (type == KoShape::Deleted) { deselect(shape); // HACK ALERT: the caller will also remove the listener, which was // removed in deselect(), so re-add it here shape->addShapeChangeListener(this); } } void KoSelection::saveOdf(KoShapeSavingContext &) const { } bool KoSelection::loadOdf(const KoXmlElement &, KoShapeLoadingContext &) { return true; } diff --git a/libs/flake/KoSelection.h b/libs/flake/KoSelection.h index b3e86b1729..b1122477e3 100644 --- a/libs/flake/KoSelection.h +++ b/libs/flake/KoSelection.h @@ -1,165 +1,165 @@ /* This file is part of the KDE project Copyright (C) 2006 Boudewijn Rempt Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2007,2009 Thomas Zander Copyright (C) 2006,2007 Jan Hambrecht 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. */ #ifndef KOSELECTION_H #define KOSELECTION_H #include #include "KoShape.h" #include "KoFlake.h" #include "kritaflake_export.h" class KoViewConverter; class KoShapeLayer; class KoSelectionPrivate; /** * A selection is a shape that contains a number of references * to shapes. That means that a selection can be manipulated in * the same way as a single shape. * * Note that a single shape can be selected in one view, and not in * another, and that in a single view, more than one selection can be * present. So selections should not be seen as singletons, or as * something completely transient. * * A selection, however, should not be selectable. We need to think * a little about the interaction here. */ class KRITAFLAKE_EXPORT KoSelection : public QObject, public KoShape, public KoShape::ShapeChangeListener { Q_OBJECT public: - KoSelection(); + KoSelection(QObject *parent = 0); ~KoSelection() override; void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override; void setSize(const QSizeF &size) override; QSizeF size() const override; QRectF outlineRect() const override; QRectF boundingRect() const override; /** * Adds a shape to the selection. * * If the shape is a KoShapeGroup all of its child shapes are automatically added * to the selection. * If the shape has no parent or is not a KoShapeGroup, only the given shape is * added to the selection. * If the given shape is a child of a KoShapeGroup and recursive selection is enabled * the all parents and their child shapes up to the toplevel KoShapeGroup are added to * the selection. * * @param shape the shape to add to the selection */ void select(KoShape *shape); /** * Removes a selected shape. * * If the shape is a KoShapeGroup all of its child shapes are automatically removed * from the selection. * If the shape has no parent or is not a KoShapeGroup, only the given shape is * removed from the selection. * If the given shape is a child of a KoShapeGroup and recursive selection is enabled * the all parents and their child shape up to the toplevel KoShapeGroup are removed * from the selection. * * @param shape the shape to remove from the selection */ void deselect(KoShape *shape); /// clear the selections list void deselectAll(); /** * Return the list of selected shapes * @return the list of selected shapes */ const QList selectedShapes() const; /** * Same as selectedShapes() but only for shapes in visible state. Used by * the algorithms that draw shapes on the image */ const QList selectedVisibleShapes() const; /** * Same as selectedShapes() but only for editable shapes. Used by * the algorithms that modify the image */ const QList selectedEditableShapes() const; /** * Same as selectedEditableShapes() but also includes shapes delegates. * Used for */ const QList selectedEditableShapesAndDelegates() const; /** * Return the first selected shape, or 0 if there is nothing selected. */ KoShape *firstSelectedShape() const; /// return true if the shape is selected bool isSelected(const KoShape *shape) const; /// return the selection count, i.e. the number of all selected shapes int count() const; bool hitTest(const QPointF &position) const override; /** * Sets the currently active layer. * @param layer the new active layer */ void setActiveLayer(KoShapeLayer *layer); /** * Returns a currently active layer. * * @return the currently active layer, or zero if there is none */ KoShapeLayer *activeLayer() const; void notifyShapeChanged(ChangeType type, KoShape *shape) override; Q_SIGNALS: /// emitted when the selection is changed void selectionChanged(); /// emitted when the current layer is changed void currentLayerChanged(const KoShapeLayer *layer); private: void saveOdf(KoShapeSavingContext &) const override; bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override; Q_DECLARE_PRIVATE_D(KoShape::d_ptr, KoSelection) }; #endif diff --git a/libs/flake/KoSelection_p.h b/libs/flake/KoSelection_p.h index affef2b052..136b75d5a2 100644 --- a/libs/flake/KoSelection_p.h +++ b/libs/flake/KoSelection_p.h @@ -1,44 +1,44 @@ /* This file is part of the KDE project * Copyright (C) 2009 Thomas Zander * * 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. */ #ifndef KOSELECTIONPRIVATE_H #define KOSELECTIONPRIVATE_H #include "KoShape_p.h" -#include "kis_signal_compressor.h" +#include "kis_thread_safe_signal_compressor.h" class KoShapeGroup; class KoSelectionPrivate : public KoShapePrivate { public: explicit KoSelectionPrivate(KoSelection *parent) : KoShapePrivate(parent), activeLayer(0), selectionChangedCompressor(1, KisSignalCompressor::FIRST_INACTIVE) {} QList selectedShapes; KoShapeLayer *activeLayer; - KisSignalCompressor selectionChangedCompressor; + KisThreadSafeSignalCompressor selectionChangedCompressor; Q_DECLARE_PUBLIC(KoSelection) }; #endif diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index 98337cfbe6..d4f3a2e26d 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,623 +1,634 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 Jan Hambrecht 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 "KoShapeManager_p.h" #include "KoSelection.h" #include "KoToolManager.h" #include "KoPointerEvent.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoCanvasBase.h" #include "KoShapeContainer.h" #include "KoShapeStrokeModel.h" #include "KoShapeGroup.h" #include "KoToolProxy.h" #include "KoShapeShadow.h" #include "KoShapeLayer.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectRenderContext.h" #include "KoShapeBackground.h" #include #include "KoClipPath.h" #include "KoClipMaskPainter.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include "KisQPainterStateSaver.h" #include "KoSvgTextChunkShape.h" #include "KoSvgTextShape.h" +#include #include #include #include #include "kis_painting_tweaks.h" bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape) { // FIXME: make more general! return !dynamic_cast(shape) && !dynamic_cast(shape) && !(dynamic_cast(shape) && !dynamic_cast(shape)); } void KoShapeManager::Private::updateTree() { // for detecting collisions between shapes. DetectCollision detector; bool selectionModified = false; bool anyModified = false; Q_FOREACH (KoShape *shape, aggregate4update) { if (shapeIndexesBeforeUpdate.contains(shape)) detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); selectionModified = selectionModified || selection->isSelected(shape); anyModified = true; } foreach (KoShape *shape, aggregate4update) { if (!shapeUsedInRenderingTree(shape)) continue; tree.remove(shape); QRectF br(shape->boundingRect()); tree.insert(br, shape); } // do it again to see which shapes we intersect with _after_ moving. foreach (KoShape *shape, aggregate4update) { detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); } aggregate4update.clear(); shapeIndexesBeforeUpdate.clear(); detector.fireSignals(); if (selectionModified) { emit q->selectionContentChanged(); } if (anyModified) { emit q->contentChanged(); } } void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible(false)) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList &shapes) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); setShapes(shapes); + + /** + * Shape manager uses signal compressors with timers, therefore + * it might handle queued signals, therefore it should belong + * to the GUI thread. + */ + this->moveToThread(qApp->thread()); } KoShapeManager::KoShapeManager(KoCanvasBase *canvas) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); + + // see a comment in another constructor + this->moveToThread(qApp->thread()); } void KoShapeManager::Private::unlinkFromShapesRecursively(const QList &shapes) { Q_FOREACH (KoShape *shape, shapes) { shape->priv()->removeShapeManager(q); KoShapeContainer *container = dynamic_cast(shape); if (container) { unlinkFromShapesRecursively(container->shapes()); } } } KoShapeManager::~KoShapeManager() { d->unlinkFromShapesRecursively(d->shapes); d->shapes.clear(); delete d; } void KoShapeManager::setShapes(const QList &shapes, Repaint repaint) { //clear selection d->selection->deselectAll(); d->unlinkFromShapesRecursively(d->shapes); d->aggregate4update.clear(); d->tree.clear(); d->shapes.clear(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape, repaint); } } void KoShapeManager::addShape(KoShape *shape, Repaint repaint) { if (d->shapes.contains(shape)) return; shape->priv()->addShapeManager(this); d->shapes.append(shape); if (d->shapeUsedInRenderingTree(shape)) { QRectF br(shape->boundingRect()); d->tree.insert(br, shape); } if (repaint == PaintShapeOnAdd) { shape->update(); } // add the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { addShape(containerShape, repaint); } } Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); } void KoShapeManager::remove(KoShape *shape) { Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); shape->update(); shape->priv()->removeShapeManager(this); d->selection->deselect(shape); d->aggregate4update.remove(shape); if (d->shapeUsedInRenderingTree(shape)) { d->tree.remove(shape); } d->shapes.removeAll(shape); // remove the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { remove(containerShape); } } } KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q) : q(_q) { } void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape) { q->d->selection->deselect(shape); q->d->aggregate4update.remove(shape); // we cannot access RTTI of the semi-destructed shape, so just // unlink it lazily if (q->d->tree.contains(shape)) { q->d->tree.remove(shape); } q->d->shapes.removeAll(shape); } KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface() { return &d->shapeInterface; } void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint) { d->updateTree(); painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. painter.setBrush(Qt::NoBrush); QList unsortedShapes; if (painter.hasClipping()) { QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter)); unsortedShapes = d->tree.intersects(rect); } else { unsortedShapes = shapes(); warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!"; } // filter all hidden shapes from the list // also filter shapes with a parent which has filter effects applied QList sortedShapes; foreach (KoShape *shape, unsortedShapes) { if (!shape->isVisible()) continue; bool addShapeToList = true; // check if one of the shapes ancestors have filter effects KoShapeContainer *parent = shape->parent(); while (parent) { // parent must be part of the shape manager to be taken into account if (!d->shapes.contains(parent)) break; if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) { addShapeToList = false; break; } parent = parent->parent(); } if (addShapeToList) { sortedShapes.append(shape); } else if (parent) { sortedShapes.append(parent); } } std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME foreach (KoShape *shape, sortedShapes) { renderSingleShape(shape, painter, converter, paintContext); } #ifdef CALLIGRA_RTREE_DEBUG // paint tree qreal zx = 0; qreal zy = 0; converter.zoom(&zx, &zy); painter.save(); painter.scale(zx, zy); d->tree.paint(painter); painter.restore(); #endif if (! forPrint) { KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->selection->paint(painter, converter, paintContext); } } void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); // apply shape clipping KoClipPath::applyClipping(shape, painter, converter); // apply transformation painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); // paint the shape paintShape(shape, painter, converter, paintContext); } void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { qreal transparency = shape->transparency(true); if (transparency > 0.0) { painter.setOpacity(1.0-transparency); } if (shape->shadow()) { painter.save(); shape->shadow()->paint(shape, painter, converter); painter.restore(); } if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) { QScopedPointer clipMaskPainter; QPainter *shapePainter = &painter; KoClipMask *clipMask = shape->clipMask(); if (clipMask) { clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect())); shapePainter = clipMaskPainter->shapePainter(); } /** * We expect the shape to save/restore the painter's state itself. Such design was not * not always here, so we need a period of sanity checks to ensure all the shapes are * ported correctly. */ const QTransform sanityCheckTransformSaved = shapePainter->transform(); shape->paint(*shapePainter, converter, paintContext); shape->paintStroke(*shapePainter, converter, paintContext); KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) { shapePainter->setTransform(sanityCheckTransformSaved); } if (clipMask) { shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape); clipMaskPainter->renderOnGlobalPainter(); } } else { // TODO: clipping mask is not implemented for this case! // There are filter effects, then we need to prerender the shape on an image, to filter it QRectF shapeBound(QPointF(), shape->size()); // First step, compute the rectangle used for the image QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound); // convert clip region to view coordinates QRectF zoomedClipRegion = converter.documentToView(clipRegion); // determine the offset of the clipping rect from the shapes origin QPointF clippingOffset = zoomedClipRegion.topLeft(); // Initialize the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); QHash imageBuffers; QSet requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs(); if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) { // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.translate(-1.0f*clippingOffset); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Paint the shape on the image KoShapeGroup *group = dynamic_cast(shape); if (group) { // the childrens matrix contains the groups matrix as well // so we have to compensate for that before painting the children imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true); Private::paintGroup(group, imagePainter, converter, paintContext); } else { imagePainter.save(); shape->paint(imagePainter, converter, paintContext); shape->paintStroke(imagePainter, converter, paintContext); imagePainter.restore(); imagePainter.end(); } } if (requiredStdInputs.contains("SourceAlpha")) { QImage sourceAlpha = sourceGraphic; sourceAlpha.fill(qRgba(0,0,0,255)); sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel()); imageBuffers.insert("SourceAlpha", sourceAlpha); } if (requiredStdInputs.contains("FillPaint")) { QImage fillPaint = sourceGraphic; if (shape->background()) { QPainter fillPainter(&fillPaint); QPainterPath fillPath; fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1)); shape->background()->paint(fillPainter, converter, paintContext, fillPath); } else { fillPaint.fill(qRgba(0,0,0,0)); } imageBuffers.insert("FillPaint", fillPaint); } imageBuffers.insert("SourceGraphic", sourceGraphic); imageBuffers.insert(QString(), sourceGraphic); KoFilterEffectRenderContext renderContext(converter); renderContext.setShapeBoundingBox(shapeBound); QImage result; QList filterEffects = shape->filterEffectStack()->filterEffects(); // Filter foreach (KoFilterEffect *filterEffect, filterEffects) { QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound); filterRegion = converter.documentToView(filterRegion); QRect subRegion = filterRegion.translated(-clippingOffset).toRect(); // set current filter region renderContext.setFilterRegion(subRegion & sourceGraphic.rect()); if (filterEffect->maximalInputCount() <= 1) { QList inputs = filterEffect->inputs(); QString input = inputs.count() ? inputs.first() : QString(); // get input image from image buffers and apply the filter effect QImage image = imageBuffers.value(input); if (!image.isNull()) { result = filterEffect->processImage(imageBuffers.value(input), renderContext); } } else { QList inputImages; Q_FOREACH (const QString &input, filterEffect->inputs()) { QImage image = imageBuffers.value(input); if (!image.isNull()) inputImages.append(imageBuffers.value(input)); } // apply the filter effect if (filterEffect->inputs().count() == inputImages.count()) result = filterEffect->processImages(inputImages, renderContext); } // store result of effect imageBuffers.insert(filterEffect->output(), result); } KoFilterEffect *lastEffect = filterEffects.last(); // Paint the result painter.save(); painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output())); painter.restore(); } } KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) { d->updateTree(); QList sortedShapes(d->tree.contains(position)); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShape *firstUnselectedShape = 0; for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible()) continue; if (! shape->hitTest(position)) continue; switch (selection) { case KoFlake::ShapeOnTop: if (shape->isSelectable()) return shape; break; case KoFlake::Selected: if (d->selection->isSelected(shape)) return shape; break; case KoFlake::Unselected: if (! d->selection->isSelected(shape)) return shape; break; case KoFlake::NextUnselected: // we want an unselected shape if (d->selection->isSelected(shape)) continue; // memorize the first unselected shape if (! firstUnselectedShape) firstUnselectedShape = shape; // check if the shape above is selected if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1))) return shape; break; } } // if we want the next unselected below a selected but there was none selected, // return the first found unselected shape if (selection == KoFlake::NextUnselected && firstUnselectedShape) return firstUnselectedShape; if (d->selection->hitTest(position)) return d->selection; return 0; // missed everything } QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode) { d->updateTree(); QList shapes(containedMode ? d->tree.contained(rect) : d->tree.intersects(rect)); for (int count = shapes.count() - 1; count >= 0; count--) { KoShape *shape = shapes.at(count); if (omitHiddenShapes && !shape->isVisible()) { shapes.removeAt(count); } else { const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) { shapes.removeAt(count); } else if (containedMode) { QPainterPath containingPath; containingPath.addRect(rect); if (!containingPath.contains(outline)) { shapes.removeAt(count); } } } } return shapes; } void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles) { d->canvas->updateCanvas(rect); if (selectionHandles && d->selection->isSelected(shape)) { if (d->canvas->toolProxy()) d->canvas->toolProxy()->repaintDecorations(); } } void KoShapeManager::notifyShapeChanged(KoShape *shape) { Q_ASSERT(shape); if (d->aggregate4update.contains(shape)) { return; } const bool wasEmpty = d->aggregate4update.isEmpty(); d->aggregate4update.insert(shape); d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex()); KoShapeContainer *container = dynamic_cast(shape); if (container) { Q_FOREACH (KoShape *child, container->shapes()) notifyShapeChanged(child); } if (wasEmpty && !d->aggregate4update.isEmpty()) { d->updateTreeCompressor.start(); } } QList KoShapeManager::shapes() const { return d->shapes; } QList KoShapeManager::topLevelShapes() const { QList shapes; // get all toplevel shapes Q_FOREACH (KoShape *shape, d->shapes) { if (!shape->parent() || dynamic_cast(shape->parent())) { shapes.append(shape); } } return shapes; } KoSelection *KoShapeManager::selection() const { return d->selection; } KoCanvasBase *KoShapeManager::canvas() { return d->canvas; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoShapeManager.cpp" diff --git a/libs/flake/KoShapeManager_p.h b/libs/flake/KoShapeManager_p.h index 654ace744c..484bf36084 100644 --- a/libs/flake/KoShapeManager_p.h +++ b/libs/flake/KoShapeManager_p.h @@ -1,124 +1,124 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 Jan Hambrecht 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. */ #ifndef KoShapeManager_p_h #define KoShapeManager_p_h #include "KoSelection.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeManager.h" #include #include "kis_thread_safe_signal_compressor.h" class KoCanvasBase; class KoShapeGroup; class KoShapePaintingContext; class QPainter; class Q_DECL_HIDDEN KoShapeManager::Private { public: Private(KoShapeManager *shapeManager, KoCanvasBase *c) - : selection(new KoSelection()), + : selection(new KoSelection(shapeManager)), canvas(c), tree(4, 2), q(shapeManager), shapeInterface(shapeManager), updateTreeCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { connect(&updateTreeCompressor, SIGNAL(timeout()), q, SLOT(updateTree())); } ~Private() { delete selection; } /** * Update the tree when there are shapes in m_aggregate4update. This is done so not all * updates to the tree are done when they are asked for but when they are needed. */ void updateTree(); /** * Returns whether the shape should be added to the RTree for collision and ROI * detection. */ bool shapeUsedInRenderingTree(KoShape *shape); /** * Recursively detach the shapes from this shape manager */ void unlinkFromShapesRecursively(const QList &shapes); /** * Recursively paints the given group shape to the specified painter * This is needed for filter effects on group shapes where the filter effect * applies to all the children of the group shape at once */ static void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); class DetectCollision { public: DetectCollision() {} void detect(KoRTree &tree, KoShape *s, int prevZIndex) { Q_FOREACH (KoShape *shape, tree.intersects(s->boundingRect())) { bool isChild = false; KoShapeContainer *parent = s->parent(); while (parent && !isChild) { if (parent == shape) isChild = true; parent = parent->parent(); } if (isChild) continue; if (s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex()) // Moving a shape will only make it collide with shapes below it. continue; if (shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape)) shapesWithCollisionDetection.append(shape); } } void fireSignals() { Q_FOREACH (KoShape *shape, shapesWithCollisionDetection) shape->priv()->shapeChanged(KoShape::CollisionDetected); } private: QList shapesWithCollisionDetection; }; QList shapes; KoSelection *selection; KoCanvasBase *canvas; KoRTree tree; QSet aggregate4update; QHash shapeIndexesBeforeUpdate; KoShapeManager *q; KoShapeManager::ShapeInterface shapeInterface; KisThreadSafeSignalCompressor updateTreeCompressor; }; #endif