aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJ-P Nurmi <jpnurmi@qt.io>2016-05-19 13:10:19 +0200
committerJani Heikkinen <jani.heikkinen@qt.io>2016-05-24 04:35:29 +0000
commit5e651178b2d1ac9cea70913de21e0c5b2b7aaa0d (patch)
tree5aa9ba8e552e10172bcf25055e607ba8e822467b
parent607320ca9fb2796a5f6f2578578fd314f7e8b99e (diff)
Fix QQuickItem change listeners
All listeners should get invoked, even if they remove themselves while iterating the listeners. An index-based loop would skip the next listener in the list. This change replaces the QPODVector with a QVector, so we can make a cheap copy before iterating the listeners. Change-Id: I2430b3763184a40ad1c5c3a68d36fecafcadb3ee Task-number: QTBUG-53453 Reviewed-by: Robin Burchell <robin.burchell@viroteck.net>
-rw-r--r--src/quick/items/qquickitem.cpp78
-rw-r--r--src/quick/items/qquickitem_p.h6
-rw-r--r--tests/auto/quick/qquickitem2/tst_qquickitem.cpp198
3 files changed, 243 insertions, 39 deletions
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index 3573788855..a8c9d34110 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -2336,8 +2336,9 @@ QQuickItem::~QQuickItem()
while (!d->childItems.isEmpty())
d->childItems.constFirst()->setParentItem(0);
- for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
- QQuickAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate();
+ const auto listeners = d->changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
+ QQuickAnchorsPrivate *anchor = change.listener->anchorPrivate();
if (anchor)
anchor->clearItem(this);
}
@@ -2346,14 +2347,13 @@ QQuickItem::~QQuickItem()
update item anchors that depended on us unless they are our child (and will also be destroyed),
or our sibling, and our parent is also being destroyed.
*/
- for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
- QQuickAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate();
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
+ QQuickAnchorsPrivate *anchor = change.listener->anchorPrivate();
if (anchor && anchor->item && anchor->item->parentItem() && anchor->item->parentItem() != this)
anchor->update();
}
- for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = d->changeListeners.at(ii);
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Destroyed)
change.listener->itemDestroyed(this);
}
@@ -3595,8 +3595,8 @@ QQuickAnchors *QQuickItemPrivate::anchors() const
void QQuickItemPrivate::siblingOrderChanged()
{
Q_Q(QQuickItem);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::SiblingOrder) {
change.listener->itemSiblingOrderChanged(q);
}
@@ -3704,8 +3704,8 @@ void QQuickItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeo
bool widthChange = (newGeometry.width() != oldGeometry.width());
bool heightChange = (newGeometry.height() != oldGeometry.height());
- for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = d->changeListeners.at(ii);
+ const auto listeners = d->changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Geometry) {
if (change.gTypes == QQuickItemPrivate::GeometryChange) {
change.listener->itemGeometryChanged(this, newGeometry, oldGeometry);
@@ -3841,7 +3841,7 @@ void QQuickItemPrivate::removeItemChangeListener(QQuickItemChangeListener *liste
void QQuickItemPrivate::updateOrAddGeometryChangeListener(QQuickItemChangeListener *listener, GeometryChangeTypes types)
{
ChangeListener change(listener, types);
- int index = changeListeners.find(change);
+ int index = changeListeners.indexOf(change);
if (index > -1)
changeListeners[index].gTypes = change.gTypes; //we may have different GeometryChangeTypes
else
@@ -3855,7 +3855,7 @@ void QQuickItemPrivate::updateOrRemoveGeometryChangeListener(QQuickItemChangeLis
if (types == NoChange) {
changeListeners.removeOne(change);
} else {
- int index = changeListeners.find(change);
+ int index = changeListeners.indexOf(change);
if (index > -1)
changeListeners[index].gTypes = change.gTypes; //we may have different GeometryChangeTypes
}
@@ -4264,8 +4264,8 @@ void QQuickItem::setBaselineOffset(qreal offset)
d->baselineOffset = offset;
- for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = d->changeListeners.at(ii);
+ const auto listeners = d->changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Geometry) {
QQuickAnchorsPrivate *anchor = change.listener->anchorPrivate();
if (anchor)
@@ -5936,66 +5936,72 @@ void QQuickItemPrivate::itemChange(QQuickItem::ItemChange change, const QQuickIt
{
Q_Q(QQuickItem);
switch (change) {
- case QQuickItem::ItemChildAddedChange:
+ case QQuickItem::ItemChildAddedChange: {
q->itemChange(change, data);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Children) {
change.listener->itemChildAdded(q, data.item);
}
}
break;
- case QQuickItem::ItemChildRemovedChange:
+ }
+ case QQuickItem::ItemChildRemovedChange: {
q->itemChange(change, data);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Children) {
change.listener->itemChildRemoved(q, data.item);
}
}
break;
+ }
case QQuickItem::ItemSceneChange:
q->itemChange(change, data);
break;
- case QQuickItem::ItemVisibleHasChanged:
+ case QQuickItem::ItemVisibleHasChanged: {
q->itemChange(change, data);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Visibility) {
change.listener->itemVisibilityChanged(q);
}
}
break;
- case QQuickItem::ItemParentHasChanged:
+ }
+ case QQuickItem::ItemParentHasChanged: {
q->itemChange(change, data);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Parent) {
change.listener->itemParentChanged(q, data.item);
}
}
break;
- case QQuickItem::ItemOpacityHasChanged:
+ }
+ case QQuickItem::ItemOpacityHasChanged: {
q->itemChange(change, data);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Opacity) {
change.listener->itemOpacityChanged(q);
}
}
break;
+ }
case QQuickItem::ItemActiveFocusHasChanged:
q->itemChange(change, data);
break;
- case QQuickItem::ItemRotationHasChanged:
+ case QQuickItem::ItemRotationHasChanged: {
q->itemChange(change, data);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::Rotation) {
change.listener->itemRotationChanged(q);
}
}
break;
+ }
case QQuickItem::ItemAntialiasingHasChanged:
// fall through
case QQuickItem::ItemDevicePixelRatioHasChanged:
@@ -6350,8 +6356,8 @@ void QQuickItem::resetWidth()
void QQuickItemPrivate::implicitWidthChanged()
{
Q_Q(QQuickItem);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::ImplicitWidth) {
change.listener->itemImplicitWidthChanged(q);
}
@@ -6514,8 +6520,8 @@ void QQuickItem::resetHeight()
void QQuickItemPrivate::implicitHeightChanged()
{
Q_Q(QQuickItem);
- for (int ii = 0; ii < changeListeners.count(); ++ii) {
- const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii);
+ const auto listeners = changeListeners;
+ for (const QQuickItemPrivate::ChangeListener &change : listeners) {
if (change.types & QQuickItemPrivate::ImplicitHeight) {
change.listener->itemImplicitHeightChanged(q);
}
diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h
index ad649e5b5f..fed3e88b68 100644
--- a/src/quick/items/qquickitem_p.h
+++ b/src/quick/items/qquickitem_p.h
@@ -62,7 +62,6 @@
#include <QtQuick/qsgnode.h>
#include "qquickclipnode_p.h"
-#include <private/qpodvector_p.h>
#include <QtQuick/private/qquickstate_p.h>
#include <private/qqmlnullablevalue_p.h>
#include <private/qqmlnotifier_p.h>
@@ -332,7 +331,7 @@ public:
Q_DECLARE_FLAGS(GeometryChangeTypes, GeometryChangeType)
struct ChangeListener {
- ChangeListener(QQuickItemChangeListener *l, QQuickItemPrivate::ChangeTypes t) : listener(l), types(t), gTypes(GeometryChange) {}
+ ChangeListener(QQuickItemChangeListener *l = Q_NULLPTR, QQuickItemPrivate::ChangeTypes t = 0) : listener(l), types(t), gTypes(GeometryChange) {}
ChangeListener(QQuickItemChangeListener *l, QQuickItemPrivate::GeometryChangeTypes gt) : listener(l), types(Geometry), gTypes(gt) {}
QQuickItemChangeListener *listener;
QQuickItemPrivate::ChangeTypes types;
@@ -386,7 +385,7 @@ public:
inline Qt::MouseButtons acceptedMouseButtons() const;
- QPODVector<QQuickItemPrivate::ChangeListener,4> changeListeners;
+ QVector<QQuickItemPrivate::ChangeListener> changeListeners;
void addItemChangeListener(QQuickItemChangeListener *listener, ChangeTypes types);
void removeItemChangeListener(QQuickItemChangeListener *, ChangeTypes types);
@@ -932,6 +931,7 @@ QSGNode *QQuickItemPrivate::childContainerNode()
}
Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickItemPrivate::ChangeTypes)
+Q_DECLARE_TYPEINFO(QQuickItemPrivate::ChangeListener, Q_PRIMITIVE_TYPE);
QT_END_NAMESPACE
diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
index 62ff09e698..a087efd6b8 100644
--- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
+++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
@@ -35,6 +35,7 @@
#include <QtGui/private/qinputmethod_p.h>
#include <QtQuick/private/qquickrectangle_p.h>
#include <QtQuick/private/qquicktextinput_p.h>
+#include <QtQuick/private/qquickitemchangelistener_p.h>
#include <QtGui/qstylehints.h>
#include <private/qquickitem_p.h>
#include "../../shared/util.h"
@@ -107,6 +108,7 @@ private slots:
void childrenProperty();
void resourcesProperty();
+ void changeListener();
void transformCrash();
void implicitSize();
void qtbug_16871();
@@ -2694,6 +2696,202 @@ void tst_QQuickItem::childrenRectBottomRightCorner()
delete window;
}
+struct TestListener : public QQuickItemChangeListener
+{
+ TestListener(bool remove = false) : remove(remove) { reset(); }
+
+ void itemGeometryChanged(QQuickItem *, const QRectF &newGeometry, const QRectF &) override { ++itemGeometryChanges; value = newGeometry; }
+ void itemSiblingOrderChanged(QQuickItem *) override { ++itemSiblingOrderChanges; }
+ void itemVisibilityChanged(QQuickItem *) override { ++itemVisibilityChanges; }
+ void itemOpacityChanged(QQuickItem *) override { ++itemOpacityChanges; }
+ void itemRotationChanged(QQuickItem *) override { ++itemRotationChanges; }
+ void itemImplicitWidthChanged(QQuickItem *) override { ++itemImplicitWidthChanges; }
+ void itemImplicitHeightChanged(QQuickItem *) override { ++itemImplicitHeightChanges; }
+
+ void itemDestroyed(QQuickItem *item) override
+ {
+ ++itemDestructions;
+ // QTBUG-53453
+ if (remove)
+ QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed);
+ }
+ void itemChildAdded(QQuickItem *item, QQuickItem *child) override
+ {
+ ++itemChildAdditions;
+ value = QVariant::fromValue(child);
+ // QTBUG-53453
+ if (remove)
+ QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Children);
+ }
+ void itemChildRemoved(QQuickItem *item, QQuickItem *child) override
+ {
+ ++itemChildRemovals;
+ value = QVariant::fromValue(child);
+ // QTBUG-53453
+ if (remove)
+ QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Children);
+ }
+ void itemParentChanged(QQuickItem *item, QQuickItem *parent) override
+ {
+ ++itemParentChanges;
+ value = QVariant::fromValue(parent);
+ // QTBUG-53453
+ if (remove)
+ QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Parent);
+ }
+
+ QQuickAnchorsPrivate *anchorPrivate() override { return nullptr; }
+
+ void reset()
+ {
+ value = QVariant();
+ itemGeometryChanges = 0;
+ itemSiblingOrderChanges = 0;
+ itemVisibilityChanges = 0;
+ itemOpacityChanges = 0;
+ itemDestructions = 0;
+ itemChildAdditions = 0;
+ itemChildRemovals = 0;
+ itemParentChanges = 0;
+ itemRotationChanges = 0;
+ itemImplicitWidthChanges = 0;
+ itemImplicitHeightChanges = 0;
+ }
+
+ bool remove;
+ QVariant value;
+ int itemGeometryChanges;
+ int itemSiblingOrderChanges;
+ int itemVisibilityChanges;
+ int itemOpacityChanges;
+ int itemDestructions;
+ int itemChildAdditions;
+ int itemChildRemovals;
+ int itemParentChanges;
+ int itemRotationChanges;
+ int itemImplicitWidthChanges;
+ int itemImplicitHeightChanges;
+};
+
+void tst_QQuickItem::changeListener()
+{
+ QQuickItem item;
+ TestListener itemListener;
+ QQuickItemPrivate::get(&item)->addItemChangeListener(&itemListener, QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight |
+ QQuickItemPrivate::Opacity | QQuickItemPrivate::Rotation);
+
+ item.setImplicitWidth(50);
+ QCOMPARE(itemListener.itemImplicitWidthChanges, 1);
+ QCOMPARE(itemListener.itemGeometryChanges, 1);
+ QCOMPARE(itemListener.value, QVariant(QRectF(0,0,50,0)));
+
+ item.setImplicitHeight(50);
+ QCOMPARE(itemListener.itemImplicitHeightChanges, 1);
+ QCOMPARE(itemListener.itemGeometryChanges, 2);
+ QCOMPARE(itemListener.value, QVariant(QRectF(0,0,50,50)));
+
+ item.setWidth(100);
+ QCOMPARE(itemListener.itemGeometryChanges, 3);
+ QCOMPARE(itemListener.value, QVariant(QRectF(0,0,100,50)));
+
+ item.setHeight(100);
+ QCOMPARE(itemListener.itemGeometryChanges, 4);
+ QCOMPARE(itemListener.value, QVariant(QRectF(0,0,100,100)));
+
+ item.setOpacity(0.5);
+ QCOMPARE(itemListener.itemOpacityChanges, 1);
+
+ item.setRotation(90);
+ QCOMPARE(itemListener.itemRotationChanges, 1);
+
+ QQuickItem *parent = new QQuickItem;
+ TestListener parentListener;
+ QQuickItemPrivate::get(parent)->addItemChangeListener(&parentListener, QQuickItemPrivate::Children);
+
+ QQuickItem *child1 = new QQuickItem;
+ QQuickItem *child2 = new QQuickItem;
+ TestListener child1Listener;
+ TestListener child2Listener;
+ QQuickItemPrivate::get(child1)->addItemChangeListener(&child1Listener, QQuickItemPrivate::Parent | QQuickItemPrivate::SiblingOrder | QQuickItemPrivate::Destroyed);
+ QQuickItemPrivate::get(child2)->addItemChangeListener(&child2Listener, QQuickItemPrivate::Parent | QQuickItemPrivate::SiblingOrder | QQuickItemPrivate::Destroyed);
+
+ child1->setParentItem(parent);
+ QCOMPARE(parentListener.itemChildAdditions, 1);
+ QCOMPARE(parentListener.value, QVariant::fromValue(child1));
+ QCOMPARE(child1Listener.itemParentChanges, 1);
+ QCOMPARE(child1Listener.value, QVariant::fromValue(parent));
+
+ child2->setParentItem(parent);
+ QCOMPARE(parentListener.itemChildAdditions, 2);
+ QCOMPARE(parentListener.value, QVariant::fromValue(child2));
+ QCOMPARE(child2Listener.itemParentChanges, 1);
+ QCOMPARE(child2Listener.value, QVariant::fromValue(parent));
+
+ child2->stackBefore(child1);
+ QCOMPARE(child1Listener.itemSiblingOrderChanges, 1);
+ QCOMPARE(child2Listener.itemSiblingOrderChanges, 1);
+
+ child1->setParentItem(nullptr);
+ QCOMPARE(parentListener.itemChildRemovals, 1);
+ QCOMPARE(parentListener.value, QVariant::fromValue(child1));
+ QCOMPARE(child1Listener.itemParentChanges, 2);
+ QCOMPARE(child1Listener.value, QVariant::fromValue<QQuickItem *>(nullptr));
+
+ delete child1;
+ QCOMPARE(child1Listener.itemDestructions, 1);
+
+ delete child2;
+ QCOMPARE(parentListener.itemChildRemovals, 2);
+ QCOMPARE(parentListener.value, QVariant::fromValue(child2));
+ QCOMPARE(child2Listener.itemParentChanges, 2);
+ QCOMPARE(child2Listener.value, QVariant::fromValue<QQuickItem *>(nullptr));
+ QCOMPARE(child2Listener.itemDestructions, 1);
+
+ QQuickItemPrivate::get(parent)->removeItemChangeListener(&parentListener, QQuickItemPrivate::Children);
+ QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);
+
+ // QTBUG-53453: all listeners should get invoked even if they remove themselves while iterating the listeners
+ QList<TestListener *> listeners;
+ for (int i = 0; i < 5; ++i)
+ listeners << new TestListener(true);
+
+ // itemChildAdded() x 5
+ foreach (TestListener *listener, listeners)
+ QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Children);
+ QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
+ child1 = new QQuickItem(parent);
+ foreach (TestListener *listener, listeners)
+ QCOMPARE(listener->itemChildAdditions, 1);
+ QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);
+
+ // itemParentChanged() x 5
+ foreach (TestListener *listener, listeners)
+ QQuickItemPrivate::get(child1)->addItemChangeListener(listener, QQuickItemPrivate::Parent);
+ QCOMPARE(QQuickItemPrivate::get(child1)->changeListeners.count(), listeners.count());
+ child1->setParentItem(nullptr);
+ foreach (TestListener *listener, listeners)
+ QCOMPARE(listener->itemParentChanges, 1);
+ QCOMPARE(QQuickItemPrivate::get(child1)->changeListeners.count(), 0);
+
+ // itemChildRemoved() x 5
+ child1->setParentItem(parent);
+ foreach (TestListener *listener, listeners)
+ QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Children);
+ QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
+ delete child1;
+ foreach (TestListener *listener, listeners)
+ QCOMPARE(listener->itemChildRemovals, 1);
+ QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);
+
+ // itemDestroyed() x 5
+ foreach (TestListener *listener, listeners)
+ QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Destroyed);
+ QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
+ delete parent;
+ foreach (TestListener *listener, listeners)
+ QCOMPARE(listener->itemDestructions, 1);
+}
+
// QTBUG-13893
void tst_QQuickItem::transformCrash()
{