diff --git a/src/widgets/graphicsview/qgraphicsitem.cpp b/src/widgets/graphicsview/qgraphicsitem.cpp index d4edc63403..605e96802b 100644 --- a/src/widgets/graphicsview/qgraphicsitem.cpp +++ b/src/widgets/graphicsview/qgraphicsitem.cpp @@ -332,6 +332,8 @@ this flag is disabled; children can draw anywhere. This behavior is enforced by QGraphicsView::drawItems() or QGraphicsScene::drawItems(). This flag was introduced in Qt 4.3. + \note This flag is similar to ItemContainsChildrenInShape but in addition + enforces the containment by clipping the children. \value ItemIgnoresTransformations The item ignores inherited transformations (i.e., its position is still anchored to its parent, but @@ -423,6 +425,19 @@ ItemStopsClickFocusPropagation, but also suppresses focus-out. This flag allows you to completely take over focus handling. This flag was introduced in Qt 4.7. \endomit + + \value ItemContainsChildrenInShape This flag indicates that all of the + item's direct or indirect children only draw within the item's shape. + Unlike ItemClipsChildrenToShape, this restriction is not enforced. Set + ItemContainsChildrenInShape when you manually assure that drawing + is bound to the item's shape and want to avoid the cost associated with + enforcing the clip. Setting this flag enables more efficient drawing and + collision detection. The flag is disabled by default. + \note If both this flag and ItemClipsChildrenToShape are set, the clip + will be enforced. This is equivalent to just setting + ItemClipsChildrenToShape. + . + This flag was introduced in Qt 5.4. */ /*! @@ -836,6 +851,10 @@ void QGraphicsItemPrivate::updateAncestorFlag(QGraphicsItem::GraphicsItemFlag ch flag = AncestorIgnoresTransformations; enabled = flags & QGraphicsItem::ItemIgnoresTransformations; break; + case QGraphicsItem::ItemContainsChildrenInShape: + flag = AncestorContainsChildren; + enabled = flags & QGraphicsItem::ItemContainsChildrenInShape; + break; default: return; } @@ -895,6 +914,8 @@ void QGraphicsItemPrivate::updateAncestorFlags() flags |= AncestorClipsChildren; if (pd->flags & QGraphicsItem::ItemIgnoresTransformations) flags |= AncestorIgnoresTransformations; + if (pd->flags & QGraphicsItem::ItemContainsChildrenInShape) + flags |= AncestorContainsChildren; } if (ancestorFlags == flags) @@ -1831,6 +1852,11 @@ void QGraphicsItem::setFlags(GraphicsItemFlags flags) d_ptr->markParentDirty(true); } + if ((flags & ItemContainsChildrenInShape) != (oldFlags & ItemContainsChildrenInShape)) { + // Item children containtment changes. Propagate the ancestor flag to all children. + d_ptr->updateAncestorFlag(ItemContainsChildrenInShape); + } + if ((flags & ItemIgnoresTransformations) != (oldFlags & ItemIgnoresTransformations)) { // Item children clipping changes. Propagate the ancestor flag to // all children. @@ -2322,7 +2348,8 @@ void QGraphicsItemPrivate::setVisibleHelper(bool newVisible, bool explicitly, } // Update children with explicitly = false. - const bool updateChildren = update && !((flags & QGraphicsItem::ItemClipsChildrenToShape) + const bool updateChildren = update && !((flags & QGraphicsItem::ItemClipsChildrenToShape + || flags & QGraphicsItem::ItemContainsChildrenInShape) && !(flags & QGraphicsItem::ItemHasNoContents)); foreach (QGraphicsItem *child, children) { if (!newVisible || !child->d_ptr->explicitlyHidden) @@ -2835,7 +2862,9 @@ QRectF QGraphicsItemPrivate::effectiveBoundingRect(QGraphicsItem *topMostEffectI #ifndef QT_NO_GRAPHICSEFFECT Q_Q(const QGraphicsItem); QRectF brect = effectiveBoundingRect(q_ptr->boundingRect()); - if (ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren || topMostEffectItem == q) + if (ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren + || topMostEffectItem == q) return brect; const QGraphicsItem *effectParent = parent; @@ -2847,6 +2876,7 @@ QRectF QGraphicsItemPrivate::effectiveBoundingRect(QGraphicsItem *topMostEffectI brect = effectParent->mapRectToItem(q, effectRectInParentSpace); } if (effectParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || effectParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren || topMostEffectItem == effectParent) { return brect; } @@ -7442,7 +7472,8 @@ QVariant QGraphicsItem::extension(const QVariant &variant) const */ void QGraphicsItem::addToIndex() { - if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) { + if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren) { // ### add to child index only if applicable return; } @@ -7459,7 +7490,8 @@ void QGraphicsItem::addToIndex() */ void QGraphicsItem::removeFromIndex() { - if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) { + if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren) { // ### remove from child index only if applicable return; } @@ -11451,6 +11483,9 @@ QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlag flag) case QGraphicsItem::ItemStopsFocusHandling: str = "ItemStopsFocusHandling"; break; + case QGraphicsItem::ItemContainsChildrenInShape: + str = "ItemContainsChildrenInShape"; + break; } debug << str; return debug; diff --git a/src/widgets/graphicsview/qgraphicsitem.h b/src/widgets/graphicsview/qgraphicsitem.h index 4283deb5b8..dfc06fdaea 100644 --- a/src/widgets/graphicsview/qgraphicsitem.h +++ b/src/widgets/graphicsview/qgraphicsitem.h @@ -105,7 +105,8 @@ public: ItemIsFocusScope = 0x8000, // internal ItemSendsScenePositionChanges = 0x10000, ItemStopsClickFocusPropagation = 0x20000, - ItemStopsFocusHandling = 0x40000 + ItemStopsFocusHandling = 0x40000, + ItemContainsChildrenInShape = 0x80000 // NB! Don't forget to increase the d_ptr->flags bit field by 1 when adding a new flag. }; Q_DECLARE_FLAGS(GraphicsItemFlags, GraphicsItemFlag) diff --git a/src/widgets/graphicsview/qgraphicsitem_p.h b/src/widgets/graphicsview/qgraphicsitem_p.h index 3968d89a13..4d1835f178 100644 --- a/src/widgets/graphicsview/qgraphicsitem_p.h +++ b/src/widgets/graphicsview/qgraphicsitem_p.h @@ -172,7 +172,8 @@ public: AncestorHandlesChildEvents = 0x1, AncestorClipsChildren = 0x2, AncestorIgnoresTransformations = 0x4, - AncestorFiltersChildEvents = 0x8 + AncestorFiltersChildEvents = 0x8, + AncestorContainsChildren = 0x10 }; inline QGraphicsItemPrivate() @@ -213,7 +214,6 @@ public: needSortChildren(0), allChildrenDirty(0), fullUpdatePending(0), - dirtyChildrenBoundingRect(1), flags(0), paintedViewBoundingRectsNeedRepaint(0), dirtySceneTransform(1), @@ -239,6 +239,7 @@ public: mayHaveChildWithGraphicsEffect(0), isDeclarativeItem(0), sendParentChangeNotification(0), + dirtyChildrenBoundingRect(1), globalStackingOrder(-1), q_ptr(0) { @@ -544,7 +545,7 @@ public: quint32 handlesChildEvents : 1; quint32 itemDiscovered : 1; quint32 hasCursor : 1; - quint32 ancestorFlags : 4; + quint32 ancestorFlags : 5; quint32 cacheMode : 2; quint32 hasBoundingRegionGranularity : 1; quint32 isWidget : 1; @@ -555,10 +556,9 @@ public: quint32 needSortChildren : 1; quint32 allChildrenDirty : 1; quint32 fullUpdatePending : 1; - quint32 dirtyChildrenBoundingRect : 1; // Packed 32 bits - quint32 flags : 19; + quint32 flags : 20; quint32 paintedViewBoundingRectsNeedRepaint : 1; quint32 dirtySceneTransform : 1; quint32 geometryChanged : 1; @@ -571,9 +571,9 @@ public: quint32 filtersDescendantEvents : 1; quint32 sceneTransformTranslateOnly : 1; quint32 notifyBoundingRectChanged : 1; - quint32 notifyInvalidated : 1; // New 32 bits + quint32 notifyInvalidated : 1; quint32 mouseSetsFocus : 1; quint32 explicitActivate : 1; quint32 wantsActive : 1; @@ -585,7 +585,8 @@ public: quint32 mayHaveChildWithGraphicsEffect : 1; quint32 isDeclarativeItem : 1; quint32 sendParentChangeNotification : 1; - quint32 padding : 21; + quint32 dirtyChildrenBoundingRect : 1; + quint32 padding : 19; // Optional stacking order int globalStackingOrder; diff --git a/src/widgets/graphicsview/qgraphicsscene.cpp b/src/widgets/graphicsview/qgraphicsscene.cpp index a7584ef198..1cd162e6bb 100644 --- a/src/widgets/graphicsview/qgraphicsscene.cpp +++ b/src/widgets/graphicsview/qgraphicsscene.cpp @@ -4717,7 +4717,8 @@ void QGraphicsScenePrivate::drawSubtreeRecursive(QGraphicsItem *item, QPainter * wasDirtyParentSceneTransform = true; } - const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape + || item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape); bool drawItem = itemHasContents && !itemIsFullyTransparent; if (drawItem || minimumRenderSize > 0.0) { const QRectF brect = adjustedItemEffectiveBoundingRect(item); @@ -5221,7 +5222,8 @@ void QGraphicsScenePrivate::processDirtyItemsRecursive(QGraphicsItem *item, bool // Process children. if (itemHasChildren && item->d_ptr->dirtyChildren) { - const bool itemClipsChildrenToShape = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape; + const bool itemClipsChildrenToShape = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape + || item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape; // Items with no content are threated as 'dummy' items which means they are never drawn and // 'processed', so the painted view bounding rect is never up-to-date. This means that whenever // such an item changes geometry, its children have to take care of the update regardless diff --git a/src/widgets/graphicsview/qgraphicsscenebsptreeindex.cpp b/src/widgets/graphicsview/qgraphicsscenebsptreeindex.cpp index 7598163f2d..50f17ab73f 100644 --- a/src/widgets/graphicsview/qgraphicsscenebsptreeindex.cpp +++ b/src/widgets/graphicsview/qgraphicsscenebsptreeindex.cpp @@ -169,7 +169,8 @@ void QGraphicsSceneBspTreeIndexPrivate::_q_updateIndex() untransformableItems << item; continue; } - if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) + if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren) continue; bsp.insertItem(item, item->d_ptr->sceneEffectiveBoundingRect()); @@ -351,7 +352,8 @@ void QGraphicsSceneBspTreeIndexPrivate::removeItem(QGraphicsItem *item, bool rec // Avoid virtual function calls from the destructor. purgePending = true; removedItems << item; - } else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) { + } else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)) { bsp.removeItem(item, item->d_ptr->sceneEffectiveBoundingRect()); } } else { @@ -510,7 +512,8 @@ void QGraphicsSceneBspTreeIndex::prepareBoundingRectChange(const QGraphicsItem * return; if (item->d_ptr->index == -1 || item->d_ptr->itemIsUntransformable() - || (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) { + || (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)) { return; // Item is not in BSP tree; nothing to do. } @@ -641,8 +644,10 @@ void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphics QGraphicsItem::GraphicsItemFlags newFlags = *static_cast(value); bool ignoredTransform = item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations; bool willIgnoreTransform = newFlags & QGraphicsItem::ItemIgnoresTransformations; - bool clipsChildren = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape; - bool willClipChildren = newFlags & QGraphicsItem::ItemClipsChildrenToShape; + bool clipsChildren = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape + || item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape; + bool willClipChildren = newFlags & QGraphicsItem::ItemClipsChildrenToShape + || newFlags & QGraphicsItem::ItemContainsChildrenInShape; if ((ignoredTransform != willIgnoreTransform) || (clipsChildren != willClipChildren)) { QGraphicsItem *thatItem = const_cast(item); // Remove item and its descendants from the index and append @@ -663,10 +668,13 @@ void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphics bool ignoredTransform = item->d_ptr->itemIsUntransformable(); bool willIgnoreTransform = (item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations) || (newParent && newParent->d_ptr->itemIsUntransformable()); - bool ancestorClippedChildren = item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren; + bool ancestorClippedChildren = item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren; bool ancestorWillClipChildren = newParent - && ((newParent->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape) - || (newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)); + && ((newParent->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape + || newParent->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape) + || (newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)); if ((ignoredTransform != willIgnoreTransform) || (ancestorClippedChildren != ancestorWillClipChildren)) { QGraphicsItem *thatItem = const_cast(item); // Remove item and its descendants from the index and append diff --git a/src/widgets/graphicsview/qgraphicssceneindex.cpp b/src/widgets/graphicsview/qgraphicssceneindex.cpp index 40f63937f4..d06c5523ca 100644 --- a/src/widgets/graphicsview/qgraphicssceneindex.cpp +++ b/src/widgets/graphicsview/qgraphicssceneindex.cpp @@ -278,7 +278,8 @@ void QGraphicsSceneIndexPrivate::recursive_items_helper(QGraphicsItem *item, QRe Q_ASSERT(!item->d_ptr->dirtySceneTransform); } - const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape + || item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape); bool processItem = !itemIsFullyTransparent; if (processItem) { processItem = intersect(item, exposeRect, mode, viewTransform, intersectData); diff --git a/tests/auto/widgets/graphicsview/qgraphicsitem/tst_qgraphicsitem.cpp b/tests/auto/widgets/graphicsview/qgraphicsitem/tst_qgraphicsitem.cpp index 84466b92d1..f686b5854c 100644 --- a/tests/auto/widgets/graphicsview/qgraphicsitem/tst_qgraphicsitem.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicsitem/tst_qgraphicsitem.cpp @@ -377,6 +377,8 @@ private slots: void itemClipsChildrenToShape5(); void itemClipsTextChildToShape(); void itemClippingDiscovery(); + void itemContainsChildrenInShape(); + void itemContainsChildrenInShape2(); void ancestorFlags(); void untransformable(); void contextMenuEventPropagation(); @@ -5845,6 +5847,102 @@ void tst_QGraphicsItem::itemClippingDiscovery() QCOMPARE(scene.itemAt(90, 90), (QGraphicsItem *)0); } +class ItemCountsBoundingRectCalls : public QGraphicsRectItem +{ +public: + ItemCountsBoundingRectCalls(const QRectF & rect, QGraphicsItem *parent = 0) + : QGraphicsRectItem(rect, parent), boundingRectCalls(0) {} + QRectF boundingRect () const { + ++boundingRectCalls; + return QGraphicsRectItem::boundingRect(); + } + mutable int boundingRectCalls; +}; + +void tst_QGraphicsItem::itemContainsChildrenInShape() +{ + ItemCountsBoundingRectCalls *parent = new ItemCountsBoundingRectCalls(QRectF(0,0, 10, 10)); + ItemCountsBoundingRectCalls *childOutsideShape = new ItemCountsBoundingRectCalls(QRectF(0,0, 10, 10), parent); + childOutsideShape->setPos(20,0); + + QGraphicsScene scene; + scene.setItemIndexMethod(QGraphicsScene::NoIndex); + scene.addItem(parent); + + QVERIFY(parent->boundingRectCalls == childOutsideShape->boundingRectCalls); + + int oldParentBoundingRectCalls = parent->boundingRectCalls; + int oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls; + + // First test that both items are searched if no optimization flags are set + QGraphicsItem* item = scene.itemAt(25,5); + + QVERIFY(item == childOutsideShape); + QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls); + QVERIFY(childOutsideShape->boundingRectCalls > oldChildBoundingRectCalls); + QVERIFY(parent->boundingRectCalls == childOutsideShape->boundingRectCalls); + + oldParentBoundingRectCalls = parent->boundingRectCalls; + oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls; + + // Repeat the test to make sure that no caching/indexing is in effect + item = scene.itemAt(25,5); + + QVERIFY(item == childOutsideShape); + QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls); + QVERIFY(childOutsideShape->boundingRectCalls > oldChildBoundingRectCalls); + QVERIFY(parent->boundingRectCalls == childOutsideShape->boundingRectCalls); + + oldParentBoundingRectCalls = parent->boundingRectCalls; + oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls; + + // Set the optimization flag and make sure that the child is not returned + // and that the child's boundingRect() method is never called. + parent->setFlag(QGraphicsItem::ItemContainsChildrenInShape); + item = scene.itemAt(25,5); + + QVERIFY(!(item)); + QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls); + QVERIFY(childOutsideShape->boundingRectCalls == oldChildBoundingRectCalls); + QVERIFY(parent->boundingRectCalls > childOutsideShape->boundingRectCalls); +} + +void tst_QGraphicsItem::itemContainsChildrenInShape2() +{ + //The tested flag behaves almost identically to ItemClipsChildrenToShape + //in terms of optimizations but does not enforce the clip. + //This test makes sure there is no clip. + QGraphicsScene scene; + QGraphicsItem *rect = scene.addRect(0, 0, 50, 50, QPen(Qt::NoPen), QBrush(Qt::yellow)); + + QGraphicsItem *ellipse = scene.addEllipse(0, 0, 100, 100, QPen(Qt::NoPen), QBrush(Qt::green)); + ellipse->setParentItem(rect); + + QGraphicsItem *clippedEllipse = scene.addEllipse(0, 0, 50, 50, QPen(Qt::NoPen), QBrush(Qt::blue)); + clippedEllipse->setParentItem(ellipse); + + QGraphicsItem *clippedEllipse2 = scene.addEllipse(0, 0, 25, 25, QPen(Qt::NoPen), QBrush(Qt::red)); + clippedEllipse2->setParentItem(clippedEllipse); + + QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemClipsChildrenToShape)); + QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemContainsChildrenInShape)); + ellipse->setFlags(QGraphicsItem::ItemContainsChildrenInShape); + QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemClipsChildrenToShape)); + QVERIFY((ellipse->flags() & QGraphicsItem::ItemContainsChildrenInShape)); + + QImage image(100, 100, QImage::Format_ARGB32_Premultiplied); + image.fill(0); + QPainter painter(&image); + painter.setRenderHint(QPainter::Antialiasing); + scene.render(&painter); + painter.end(); + + QCOMPARE(image.pixel(2, 2), QColor(Qt::yellow).rgba()); + QCOMPARE(image.pixel(12, 12), QColor(Qt::red).rgba()); + QCOMPARE(image.pixel(2, 25), QColor(Qt::blue).rgba()); + QCOMPARE(image.pixel(2, 50), QColor(Qt::green).rgba()); +} + void tst_QGraphicsItem::ancestorFlags() { QGraphicsItem *level1 = new QGraphicsRectItem; @@ -5965,11 +6063,27 @@ void tst_QGraphicsItem::ancestorFlags() // Nobody handles child events level21->setHandlesChildEvents(false); - for (int i = 0; i < 2; ++i) { - QGraphicsItem::GraphicsItemFlag flag = !i ? QGraphicsItem::ItemClipsChildrenToShape - : QGraphicsItem::ItemIgnoresTransformations; - int ancestorFlag = !i ? QGraphicsItemPrivate::AncestorClipsChildren - : QGraphicsItemPrivate::AncestorIgnoresTransformations; + for (int i = 0; i < 3; ++i) { + QGraphicsItem::GraphicsItemFlag flag; + int ancestorFlag; + + switch (i) { + case(0): + flag = QGraphicsItem::ItemClipsChildrenToShape; + ancestorFlag = QGraphicsItemPrivate::AncestorClipsChildren; + break; + case(1): + flag = QGraphicsItem::ItemIgnoresTransformations; + ancestorFlag = QGraphicsItemPrivate::AncestorIgnoresTransformations; + break; + case(2): + flag = QGraphicsItem::ItemContainsChildrenInShape; + ancestorFlag = QGraphicsItemPrivate::AncestorContainsChildren; + break; + default: + qFatal("Unknown ancestor flag, please fix!"); + break; + } QCOMPARE(int(level1->d_ptr->ancestorFlags), 0); QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);