34#include <QApplication>
37#include <QPainterPath>
39#include <QMutexLocker>
45#include <unordered_set>
54inline bool shapeUsedInRenderingTree(
KoShape *shape)
66inline bool shapeHasGroupEffects(
KoShape *shape) {
75inline bool shapeIsVisible(
KoShape *shape) {
86void populateRenderSubtree(
KoShape *parentShape,
89 std::function<
bool(
KoShape*)> shouldIncludeNode,
90 std::function<
bool(
KoShape*)> shouldEnterSubtree)
93 if (!parentContainer)
return;
98 for (
auto it = children.constBegin(); it != children.constEnd(); ++it) {
99 auto newParentIt = parentIt;
101 if (shouldIncludeNode(*it)) {
105 if (shouldEnterSubtree(*it)) {
106 populateRenderSubtree(*it, newParentIt, tree, shouldIncludeNode, shouldEnterSubtree);
131 std::unordered_set<KoShape*> includedShapes;
133 Q_FOREACH (
KoShape *shape, sortedShapes) {
134 bool shouldSkipShape = !shapeIsVisible(shape);
135 if (shouldSkipShape)
continue;
137 bool shapeIsPartOfIncludedSubtree =
false;
140 while ((shape = shape->
parent())) {
141 if (!shapeIsVisible(shape)) {
142 shouldSkipShape =
true;
146 if (includedShapes.find(shape) != end(includedShapes)) {
147 shapeIsPartOfIncludedSubtree =
true;
151 if (shapeHasGroupEffects(shape)) {
156 if (shouldSkipShape)
continue;
158 if (!shapeIsPartOfIncludedSubtree &&
159 includedShapes.find(hierarchy.last()) == end(includedShapes)) {
163 std::copy(hierarchy.begin(), hierarchy.end(),
164 std::inserter(includedShapes, end(includedShapes)));
167 auto shouldIncludeShape =
168 [includedShapes] (
KoShape *shape) {
170 return includedShapes.find(shape) != end(includedShapes);
174 populateRenderSubtree(*it, it, tree, shouldIncludeShape, &shapeIsVisible);
185 for (
auto it = beginIt; it != endIt; ++it) {
191 painter.setTransform(shape->
transformation() * painter.transform());
199 if (transparency > 0.0) {
200 painter.setOpacity(1.0-transparency);
208 QScopedPointer<KoClipMaskPainter> clipMaskPainter;
209 QPainter *shapePainter = &painter;
217 const QRectF
bounds = painter.transform().mapRect(shape->
outlineRect() & painter.clipBoundingRect());
220 shapePainter = clipMaskPainter->shapePainter();
228 const QTransform sanityCheckTransformSaved = shapePainter->transform();
234 shape->
paint(*shapePainter);
243 shapePainter->setTransform(sanityCheckTransformSaved);
247 clipMaskPainter->maskPainter()->save();
250 clipMaskPainter->renderOnGlobalPainter();
252 clipMaskPainter->maskPainter()->restore();
259void KoShapeManager::Private::updateTree()
261 bool selectionModified =
false;
262 bool anyModified =
false;
265 QMutexLocker l(&this->treeMutex);
267 Q_FOREACH (
KoShape *shape, aggregate4update) {
268 selectionModified = selectionModified || selection->isSelected(shape);
272 foreach (
KoShape *shape, aggregate4update) {
273 if (!shapeUsedInRenderingTree(shape))
continue;
280 aggregate4update.clear();
281 shapeIndexesBeforeUpdate.clear();
284 if (selectionModified) {
285 Q_EMIT q->selectionContentChanged();
288 Q_EMIT q->contentChanged();
292void KoShapeManager::Private::forwardCompressedUpdate()
294 bool shouldUpdateDecorations =
false;
295 QRectF scheduledUpdate;
298 QMutexLocker l(&shapesMutex);
300 if (!compressedUpdate.isEmpty()) {
301 scheduledUpdate = compressedUpdate;
302 compressedUpdate = QRect();
305 Q_FOREACH (
const KoShape *shape, compressedUpdatedShapes) {
306 if (selection->isSelected(shape)) {
307 shouldUpdateDecorations =
true;
311 compressedUpdatedShapes.clear();
314 if (shouldUpdateDecorations && canvas->toolProxy()) {
315 canvas->toolProxy()->repaintDecorations();
317 canvas->updateCanvas(scheduledUpdate);
332 this->moveToThread(qApp->thread());
343 this->moveToThread(qApp->thread());
347void KoShapeManager::Private::unlinkFromShapesRecursively(
const QList<KoShape*> &shapes)
349 Q_FOREACH (
KoShape *shape, shapes) {
354 unlinkFromShapesRecursively(container->
shapes());
361 d->unlinkFromShapesRecursively(
d->shapes);
370 QMutexLocker l1(&
d->shapesMutex);
371 QMutexLocker l2(&
d->treeMutex);
374 d->selection->deselectAll();
375 d->unlinkFromShapesRecursively(
d->shapes);
376 d->compressedUpdate = QRect();
377 d->compressedUpdatedShapes.clear();
378 d->aggregate4update.clear();
379 d->shapeIndexesBeforeUpdate.clear();
392 QMutexLocker l1(&
d->shapesMutex);
394 if (
d->shapes.contains(shape))
397 d->shapes.append(shape);
399 if (shapeUsedInRenderingTree(shape)) {
400 QMutexLocker l2(&
d->treeMutex);
403 d->tree.insert(br, shape);
425 QMutexLocker l1(&
d->shapesMutex);
426 QMutexLocker l2(&
d->treeMutex);
431 d->selection->deselect(shape);
432 d->aggregate4update.remove(shape);
433 d->compressedUpdatedShapes.remove(shape);
435 if (shapeUsedInRenderingTree(shape)) {
436 d->tree.remove(shape);
438 d->shapes.removeAll(shape);
441 if (!dirtyRect.isEmpty()) {
461 QMutexLocker l1(&
q->
d->shapesMutex);
462 QMutexLocker l2(&
q->
d->treeMutex);
464 q->
d->selection->deselect(shape);
465 q->
d->aggregate4update.remove(shape);
466 q->
d->compressedUpdatedShapes.remove(shape);
470 if (
q->
d->tree.contains(shape)) {
471 q->
d->tree.remove(shape);
474 q->
d->shapes.removeAll(shape);
480 return &
d->shapeInterface;
488 QMutexLocker l1(&
d->shapesMutex);
490 QSet<KoShape*> rootShapesSet;
491 Q_FOREACH (
KoShape *shape,
d->shapes) {
492 while (shape->
parent() && shape->
parent() != excludeRoot) {
496 if (!rootShapesSet.contains(shape) && shape != excludeRoot) {
497 rootShapesSet.insert(shape);
500 const QList<KoShape*> rootShapes(rootShapesSet.begin(), rootShapesSet.end());
503 Q_FOREACH (
KoShape *srcShape, rootShapes) {
517 newRootShapes << clonedShape;
523 Q_FOREACH (
KoShape *shape, newRootShapes) {
524 shapesStorage->emplace_back(std::unique_ptr<KoShape>(shape));
531 QHash<KoShape*, KoShape*> clonedFromOriginal;
532 for (
int i = 0; i < originalShapes.size(); i++) {
533 clonedFromOriginal[originalShapes[i]] = clonedShapes[i];
537 for (
auto it = std::begin(jobsOrder.
jobs); it != std::end(jobsOrder.
jobs); ++it) {
538 QMutexLocker l(&
d->treeMutex);
539 QList<KoShape*> unsortedOriginalShapes =
d->tree.intersects(it->docUpdateRect);
541 it->allClonedShapes = shapesStorage;
543 Q_FOREACH (
KoShape *shape, unsortedOriginalShapes) {
545 it->shapes << clonedFromOriginal[shape];
552 painter.setPen(Qt::NoPen);
553 painter.setBrush(Qt::NoBrush);
556 buildRenderTree(job.
shapes, renderTree);
558 renderShapes(childBegin(renderTree), childEnd(renderTree), painter);
565 QMutexLocker l1(&
d->shapesMutex);
567 painter.setPen(Qt::NoPen);
568 painter.setBrush(Qt::NoBrush);
571 if (painter.hasClipping()) {
572 QMutexLocker l(&
d->treeMutex);
575 unsortedShapes =
d->tree.intersects(
rect);
577 unsortedShapes =
d->shapes;
578 warnFlake <<
"KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!";
582 buildRenderTree(unsortedShapes, renderTree);
583 renderShapes(childBegin(renderTree), childEnd(renderTree), painter);
592 auto root = renderTree.
insert(childBegin(renderTree), shape);
593 populateRenderSubtree(shape, root, renderTree, &shapeIsVisible, &shapeIsVisible);
594 renderShapes(childBegin(renderTree), childEnd(renderTree), painter);
601 QMutexLocker l(&
d->shapesMutex);
606 QMutexLocker l(&
d->treeMutex);
607 sortedShapes =
d->tree.contains(position);
611 KoShape *firstUnselectedShape = 0;
612 for (
int count = sortedShapes.count() - 1; count >= 0; count--) {
613 KoShape *shape = sortedShapes.at(count);
614 if (omitHiddenShapes && ! shape->
isVisible())
616 if (! shape->
hitTest(position))
625 if (
d->selection->isSelected(shape))
629 if (!
d->selection->isSelected(shape))
634 if (
d->selection->isSelected(shape))
637 if (! firstUnselectedShape)
638 firstUnselectedShape = shape;
640 if (count + 1 < sortedShapes.count() &&
d->selection->isSelected(sortedShapes.at(count + 1)))
648 return firstUnselectedShape;
650 if (
d->selection->hitTest(position))
658 QMutexLocker l(&
d->shapesMutex);
664 QMutexLocker l(&
d->treeMutex);
665 shapes = containedMode ?
d->tree.contained(
rect) :
d->tree.intersects(
rect);
668 for (
int count =
shapes.count() - 1; count >= 0; count--) {
672 if (omitHiddenShapes && !shape->
isVisible()) {
677 if (!containedMode && !outline.intersects(
rect) && !outline.contains(
rect)) {
680 }
else if (containedMode) {
682 QPainterPath containingPath;
683 containingPath.addRect(
rect);
685 if (!containingPath.contains(outline)) {
697 if (
d->updatesBlocked)
return;
700 QMutexLocker l(&
d->shapesMutex);
702 d->compressedUpdate |=
rect;
704 if (selectionHandles) {
705 d->compressedUpdatedShapes.insert(shape);
714 d->updatesBlocked =
value;
719 return d->updatesBlocked;
724 QMutexLocker l(&
d->treeMutex);
727 if (
d->aggregate4update.contains(shape)) {
731 d->aggregate4update.insert(shape);
732 d->shapeIndexesBeforeUpdate.insert(shape, shape->
zIndex());
744 QMutexLocker l(&
d->shapesMutex);
751 QMutexLocker l(&
d->shapesMutex);
755 Q_FOREACH (
KoShape *shape,
d->shapes) {
779#include "moc_KoShapeManager.cpp"
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
void updateCanvas(const QRectF &rc) override
child_iterator insert(child_iterator pos, X &&value)
Inserts element value into position pos. value becomes the child of the same parent as pos and is pla...
static void applyClipping(KoShape *clippedShape, QPainter &painter)
Applies the clipping to the given painter.
QList< KoShape * > shapes() const
@ PaintShapeOnAdd
Causes each shapes 'update()' to be called after being added to the shapeManager.
QList< KoShape * > topLevelShapes() const
KoShapeManager(KoCanvasBase *canvas)
~KoShapeManager() override
QList< KoShape * > shapes
QList< KoShape * > shapesAt(const QRectF &rect, bool omitHiddenShapes=true, bool containedMode=false)
KoShape * shapeAt(const QPointF &position, KoFlake::ShapeSelection selection=KoFlake::ShapeOnTop, bool omitHiddenShapes=true)
KoShapeManager::ShapeInterface shapeInterface
void forwardCompressedUpdate()
void paintJob(QPainter &painter, const KoShapeManager::PaintJob &job)
void setShapes(const QList< KoShape * > &shapes, Repaint repaint=PaintShapeOnAdd)
void selectionChanged()
emitted when the selection is changed
void update(const QRectF &rect, const KoShape *shape=0, bool selectionHandles=false)
void setUpdatesBlocked(bool value)
void paint(QPainter &painter)
void explicitlyIssueShapeChangedSignals()
void notifyShapeChanged(KoShape *shape)
static void renderSingleShape(KoShape *shape, QPainter &painter)
renderSingleShape renders a shape on painter. This method includes all the needed steps for painting ...
void forwardUpdate()
Emitted upon update and used to update the canvas.
void remove(KoShape *shape)
void addShape(KoShape *shape, KoShapeManager::Repaint repaint=PaintShapeOnAdd)
void preparePaintJobs(PaintJobsOrder &jobsOrder, KoShape *excludeRoot)
void paint(KoShape *shape, QPainter &painter)
virtual void paintStroke(QPainter &painter) const
paintStroke paints the shape's stroked outline
virtual QRectF outlineRect() const
virtual QPainterPath outline() const
virtual QVector< PaintOrder > paintOrder() const
paintOrder
bool isSelectable() const
void addShapeManager(KoShapeManager *manager)
void applyAbsoluteTransformation(const QTransform &matrix)
static bool compareShapeZIndex(KoShape *s1, KoShape *s2)
virtual QRectF boundingRect() const
Get the bounding box of the shape.
KoClipPath * clipPath() const
Returns the currently set clip path or 0 if there is no clip path set.
virtual void update() const
KoShapeContainer * parent() const
void removeShapeManager(KoShapeManager *manager)
static QList< KoShape * > linearizeSubtreeSorted(const QList< KoShape * > &shapes)
QTransform absoluteTransformation() const
virtual void paintMarkers(QPainter &painter) const
paintStroke paints the shape's markers
KoShapeShadow * shadow() const
Returns the currently set shadow or 0 if there is no shadow set.
KoClipMask * clipMask() const
Returns the currently set clip mask or 0 if there is no clip mask set.
virtual void paint(QPainter &painter) const =0
Paint the shape fill The class extending this one is responsible for painting itself....
virtual KoShape * cloneShape() const
creates a deep copy of the shape or shape's subtree
QTransform transformation() const
Returns the shapes local transformation matrix.
KoFilterEffectStack * filterEffectStack() const
virtual bool hitTest(const QPointF &position) const
Check if the shape is hit on position.
bool isVisible(bool recursive=true) const
qreal transparency(bool recursive=false) const
#define KIS_SAFE_ASSERT_RECOVER(cond)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
bool isEnd(const ChildIterator< T, is_const > &it)
ChildIterator< value_type, is_const > childBegin(const ChildIterator< value_type, is_const > &it)
ChildIterator< value_type, is_const > parent(const ChildIterator< value_type, is_const > &it)
ChildIterator< value_type, is_const > childEnd(const ChildIterator< value_type, is_const > &it)
QRect safeClipBoundingRect(const QPainter &painter)
@ Unselected
return the first unselected on top.
@ Selected
return the first selected with the highest z-ordering (i.e. on top).
@ ShapeOnTop
return the shape highest z-ordering, regardless of selection.
@ NextUnselected
return the first unselected directly under a selected shape, or the top most one if nothing is select...
void drawMask(QPainter *painter, KoShape *shape)
std::shared_ptr< ShapesStorage > SharedSafeStorage
QList< KoShape * > shapes
ShapeInterface(KoShapeManager *_q)
void notifyShapeDestructed(KoShape *shape)