diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 22d2dd2c39..f443b1acd0 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -1904,7 +1904,11 @@ void QQuickItem::setParentItem(QQuickItem *parentItem) QQuickCanvasPrivate::get(d->canvas)->clearFocusInScope(scopeItem, scopeFocusedItem, QQuickCanvasPrivate::DontChangeFocusProperty); + const bool wasVisible = isVisible(); op->removeChild(this); + if (wasVisible) { + emit oldParentItem->visibleChildrenChanged(); + } } else if (d->canvas) { QQuickCanvasPrivate::get(d->canvas)->parentlessItems.remove(this); } @@ -1951,6 +1955,8 @@ void QQuickItem::setParentItem(QQuickItem *parentItem) } emit parentChanged(d->parentItem); + if (isVisible() && d->parentItem) + emit d->parentItem->visibleChildrenChanged(); } void QQuickItem::stackBefore(const QQuickItem *sibling) @@ -2488,6 +2494,39 @@ void QQuickItemPrivate::children_clear(QDeclarativeListProperty *pro p->childItems.at(0)->setParentItem(0); } +void QQuickItemPrivate::visibleChildren_append(QDeclarativeListProperty*, QQuickItem *self) +{ + // do nothing + qmlInfo(self) << "QQuickItem: visibleChildren property is readonly and cannot be assigned to."; +} + +int QQuickItemPrivate::visibleChildren_count(QDeclarativeListProperty *prop) +{ + QQuickItemPrivate *p = QQuickItemPrivate::get(static_cast(prop->object)); + int visibleCount = 0; + int c = p->childItems.count(); + while (c--) { + if (p->childItems.at(c)->isVisible()) visibleCount++; + } + + return visibleCount; +} + +QQuickItem *QQuickItemPrivate::visibleChildren_at(QDeclarativeListProperty *prop, int index) +{ + QQuickItemPrivate *p = QQuickItemPrivate::get(static_cast(prop->object)); + const int childCount = p->childItems.count(); + if (index >= childCount || index < 0) + return 0; + + int visibleCount = -1; + for (int i = 0; i < childCount; i++) { + if (p->childItems.at(i)->isVisible()) visibleCount++; + if (visibleCount == index) return p->childItems.at(i); + } + return 0; +} + int QQuickItemPrivate::transform_count(QDeclarativeListProperty *prop) { QQuickItem *that = static_cast(prop->object); @@ -3321,6 +3360,20 @@ QDeclarativeListProperty QQuickItemPrivate::children() } +/*! + \qmlproperty real QtQuick2::Item::visibleChildren + This read-only property lists all of the item's children that are currently visible. + Note that a child's visibility may have changed explicitly, or because the visibility + of this (it's parent) item or another grandparent changed. +*/ +QDeclarativeListProperty QQuickItemPrivate::visibleChildren() +{ + return QDeclarativeListProperty(q_func(), 0, QQuickItemPrivate::visibleChildren_append, + QQuickItemPrivate::visibleChildren_count, + QQuickItemPrivate::visibleChildren_at); + +} + QDeclarativeListProperty QQuickItemPrivate::states() { return _states()->statesProperty(); @@ -3894,7 +3947,9 @@ void QQuickItem::setVisible(bool v) d->explicitVisible = v; - d->setEffectiveVisibleRecur(d->calcEffectiveVisible()); + const bool childVisibilityChanged = d->setEffectiveVisibleRecur(d->calcEffectiveVisible()); + if (childVisibilityChanged && d->parentItem) + emit d->parentItem->visibleChildrenChanged(); // signal the parent, not this! } bool QQuickItem::isEnabled() const @@ -3926,18 +3981,18 @@ bool QQuickItemPrivate::calcEffectiveVisible() const return explicitVisible && (!parentItem || QQuickItemPrivate::get(parentItem)->effectiveVisible); } -void QQuickItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible) +bool QQuickItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible) { Q_Q(QQuickItem); if (newEffectiveVisible && !explicitVisible) { // This item locally overrides visibility - return; + return false; // effective visibility didn't change } if (newEffectiveVisible == effectiveVisible) { // No change necessary - return; + return false; // effective visibility didn't change } effectiveVisible = newEffectiveVisible; @@ -3950,8 +4005,9 @@ void QQuickItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible) q->ungrabMouse(); } + bool childVisibilityChanged = false; for (int ii = 0; ii < childItems.count(); ++ii) - QQuickItemPrivate::get(childItems.at(ii))->setEffectiveVisibleRecur(newEffectiveVisible); + childVisibilityChanged |= QQuickItemPrivate::get(childItems.at(ii))->setEffectiveVisibleRecur(newEffectiveVisible); for (int ii = 0; ii < changeListeners.count(); ++ii) { const QQuickItemPrivate::ChangeListener &change = changeListeners.at(ii); @@ -3963,6 +4019,10 @@ void QQuickItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible) QAccessible::updateAccessibility(QAccessibleEvent(effectiveVisible ? QAccessible::ObjectShow : QAccessible::ObjectHide, q, 0)); emit q->visibleChanged(); + if (childVisibilityChanged) + emit q->visibleChildrenChanged(); + + return true; // effective visibility DID change } bool QQuickItemPrivate::calcEffectiveEnable() const diff --git a/src/quick/items/qquickitem.h b/src/quick/items/qquickitem.h index dd7d2c3a1f..91771c809b 100644 --- a/src/quick/items/qquickitem.h +++ b/src/quick/items/qquickitem.h @@ -116,6 +116,7 @@ class Q_QUICK_EXPORT QQuickItem : public QObject, public QDeclarativeParserStatu Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged FINAL) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged FINAL) + Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QDeclarativeListProperty visibleChildren READ visibleChildren NOTIFY visibleChildrenChanged DESIGNABLE false) Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QDeclarativeListProperty states READ states DESIGNABLE false) Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QDeclarativeListProperty transitions READ transitions DESIGNABLE false) @@ -341,6 +342,7 @@ public Q_SLOTS: void opacityChanged(); void enabledChanged(); void visibleChanged(); + void visibleChildrenChanged(); void rotationChanged(); void scaleChanged(); diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index 790c925ed0..c88219f732 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -246,6 +246,7 @@ class Q_QUICK_EXPORT QQuickItemPrivate : public QObjectPrivate QDeclarativeListProperty data(); QDeclarativeListProperty resources(); QDeclarativeListProperty children(); + QDeclarativeListProperty visibleChildren(); QDeclarativeListProperty states(); QDeclarativeListProperty transitions(); @@ -281,6 +282,11 @@ class Q_QUICK_EXPORT QQuickItemPrivate : public QObjectPrivate static QQuickItem *children_at(QDeclarativeListProperty *, int); static void children_clear(QDeclarativeListProperty *); + // visibleChildren property + static void visibleChildren_append(QDeclarativeListProperty *prop, QQuickItem *o); + static int visibleChildren_count(QDeclarativeListProperty *prop); + static QQuickItem *visibleChildren_at(QDeclarativeListProperty *prop, int index); + // transform property static int transform_count(QDeclarativeListProperty *list); static void transform_append(QDeclarativeListProperty *list, QQuickTransform *); @@ -471,7 +477,7 @@ class Q_QUICK_EXPORT QQuickItemPrivate : public QObjectPrivate void deliverDragEvent(QEvent *); bool calcEffectiveVisible() const; - void setEffectiveVisibleRecur(bool); + bool setEffectiveVisibleRecur(bool); bool calcEffectiveEnable() const; void setEffectiveEnableRecur(QQuickItem *scope, bool); diff --git a/tests/auto/qtquick2/qquickitem2/data/visiblechildren.qml b/tests/auto/qtquick2/qquickitem2/data/visiblechildren.qml new file mode 100644 index 0000000000..e51eb3551b --- /dev/null +++ b/tests/auto/qtquick2/qquickitem2/data/visiblechildren.qml @@ -0,0 +1,143 @@ +import QtQuick 2.0 + +Item { + id: root + width: 400 + height: 300 + + Row { + id: row + Item { id: item1 + Item { id: item1_1; visible: true } + Item { id: item1_2; visible: true } + } + Item { id: item2 } + Item { id: item3 + Item { id: item3_1; visible: false } + Item { id: item3_2; visible: false } + } + Item { id: item4; visible: false + Item { id: item4_1 // implicitly invisible + Item { id: item4_1_1 } // implicitly invisible + Item { id: item4_1_2 } // implicitly invisible + } + } + } + + property int row_changeEventCalls: 0 + property int item1_changeEventCalls: 0 + property int item2_changeEventCalls: 0 + property int item3_changeEventCalls: 0 + property int item4_1_changeEventCalls: 0 + property int item4_1_1_changeEventCalls: 0 + Connections { target: row; onVisibleChildrenChanged: row_changeEventCalls++ } + Connections { target: item1; onVisibleChildrenChanged: item1_changeEventCalls++ } + Connections { target: item2; onVisibleChildrenChanged: item2_changeEventCalls++ } + Connections { target: item3; onVisibleChildrenChanged: item3_changeEventCalls++ } + Connections { target: item4_1; onVisibleChildrenChanged: item4_1_changeEventCalls++ } + Connections { target: item4_1_1; onVisibleChildrenChanged: item4_1_1_changeEventCalls++ } + + // Make sure there are three visible children and no signals fired yet + property bool test1_1: row.visibleChildren.length == 3 + property bool test1_2: row.visibleChildren[0] == item1 && row.visibleChildren[1] == item2 && row.visibleChildren[2] == item3 + property bool test1_3: row_changeEventCalls == 0 + property bool test1_4: item1_changeEventCalls == 0 && item2_changeEventCalls == 0 && item3_changeEventCalls == 0 + + // Next test + function hideFirstAndLastRowChild() { + item1.visible = false; + item3.visible = false; + } + + // Make sure row is signaled twice and item1 only once, and item3 not at all, and that item2 is the visible child + property bool test2_1: row.visibleChildren.length == 1 + property bool test2_2: row.visibleChildren[0] == item2 + property bool test2_3: row_changeEventCalls == 2 + property bool test2_4: item1_changeEventCalls == 1 && item2_changeEventCalls == 0 && item3_changeEventCalls == 0 + + // Next test + function showLastRowChildsLastChild() { + item3_2.visible = true; + } + + // Make sure item3_changeEventCalls is not signaled + property bool test3_1: row.visibleChildren.length == 1 + property bool test3_2: row.visibleChildren[0] == item2 + property bool test3_3: row_changeEventCalls == 2 + property bool test3_4: item1_changeEventCalls == 1 && item2_changeEventCalls == 0 && item3_changeEventCalls == 0 + + // Next test + function showLastRowChild() { + item3.visible = true; + } + + // Make sure row and item3 are signaled + property bool test4_1: row.visibleChildren.length == 2 + property bool test4_2: row.visibleChildren[0] == item2 && row.visibleChildren[1] == item3 + property bool test4_3: row_changeEventCalls == 3 + property bool test4_4: item1_changeEventCalls == 1 && item2_changeEventCalls == 0 && item3_changeEventCalls == 1 + + // Next test + function tryWriteToReadonlyVisibleChildren() { + var foo = fooComponent.createObject(root); + if (Qt.isQtObject(foo) && foo.children.length == 3 && foo.visibleChildren.length == 3) { + test5_1 = true; + } + + foo.visibleChildren.length = 10; // make sure this has no effect + test5_1 = (foo.visibleChildren.length == 3); + delete foo; + } + + Component { + id: fooComponent + Item { + children: [ Item {},Item {},Item {} ] + visibleChildren: [ Item {} ] + } + } + + // Make sure visibleChildren.length is 3 and stays that way + property bool test5_1: false + + // Next test + function reparentVisibleItem3() { + item3.parent = hiddenItem; // item3 has one visible children + } + + Item { id: hiddenItem; visible: false } + + property bool test6_1: row.visibleChildren.length == 1 && row_changeEventCalls == 4 + property bool test6_2: item3_changeEventCalls == 2 + property bool test6_3: item3.visible == false + + // Next test + + property bool test6_4: item4_1.visible == false && item4_1_changeEventCalls == 0 + + function reparentImlicitlyInvisibleItem4_1() { + item4_1.parent = visibleItem; + } + + Item { id: visibleItem; visible: true } + property int visibleItem_changeEventCalls: 0 + Connections { target: visibleItem; onVisibleChildrenChanged: visibleItem_changeEventCalls++ } + + + // Make sure that an item with implictly invisible children will be signaled when reparented to a visible parent + property bool test7_1: row.visibleChildren.length == 1 && row_changeEventCalls == 4 + property bool test7_2: item4_1.visible == true + property bool test7_3: item4_1_changeEventCalls == 1 + property bool test7_4: visibleItem_changeEventCalls == 1 + + + + // FINALLY make sure nothing has changes while we weren't paying attention + + property bool test8_1: row.visibleChildren.length == 1 && row.visibleChildren[0] == item2 && row_changeEventCalls == 4 + property bool test8_2: item1_changeEventCalls == 1 && item1.visible == false + property bool test8_3: item2_changeEventCalls == 0 && item2.visible == true + property bool test8_4: item3_changeEventCalls == 2 && item3.visible == false + property bool test8_5: item4_1_1_changeEventCalls == 0 && item4_1_1.visible == true + +} diff --git a/tests/auto/qtquick2/qquickitem2/tst_qquickitem.cpp b/tests/auto/qtquick2/qquickitem2/tst_qquickitem.cpp index f6a7b0c9c3..c26fc94178 100644 --- a/tests/auto/qtquick2/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/qtquick2/qquickitem2/tst_qquickitem.cpp @@ -85,6 +85,7 @@ private slots: void transformCrash(); void implicitSize(); void qtbug_16871(); + void visibleChildren(); private: QDeclarativeEngine engine; }; @@ -1246,6 +1247,66 @@ void tst_QQuickItem::qtbug_16871() delete o; } + +void tst_QQuickItem::visibleChildren() +{ + QQuickView *canvas = new QQuickView(0); + canvas->setSource(testFileUrl("visiblechildren.qml")); + canvas->show(); + + QQuickItem *root = qobject_cast(canvas->rootObject()); + QVERIFY(root); + + QCOMPARE(root->property("test1_1").toBool(), true); + QCOMPARE(root->property("test1_2").toBool(), true); + QCOMPARE(root->property("test1_3").toBool(), true); + QCOMPARE(root->property("test1_4").toBool(), true); + + QMetaObject::invokeMethod(root, "hideFirstAndLastRowChild"); + QCOMPARE(root->property("test2_1").toBool(), true); + QCOMPARE(root->property("test2_2").toBool(), true); + QCOMPARE(root->property("test2_3").toBool(), true); + QCOMPARE(root->property("test2_4").toBool(), true); + + QMetaObject::invokeMethod(root, "showLastRowChildsLastChild"); + QCOMPARE(root->property("test3_1").toBool(), true); + QCOMPARE(root->property("test3_2").toBool(), true); + QCOMPARE(root->property("test3_3").toBool(), true); + QCOMPARE(root->property("test3_4").toBool(), true); + + QMetaObject::invokeMethod(root, "showLastRowChild"); + QCOMPARE(root->property("test4_1").toBool(), true); + QCOMPARE(root->property("test4_2").toBool(), true); + QCOMPARE(root->property("test4_3").toBool(), true); + QCOMPARE(root->property("test4_4").toBool(), true); + + QString warning1 = testFileUrl("visiblechildren.qml").toString() + ":96:32: QML Item: QQuickItem: visibleChildren property is readonly and cannot be assigned to."; + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1)); + QMetaObject::invokeMethod(root, "tryWriteToReadonlyVisibleChildren"); + QCOMPARE(root->property("test5_1").toBool(), true); + + QMetaObject::invokeMethod(root, "reparentVisibleItem3"); + QCOMPARE(root->property("test6_1").toBool(), true); + QCOMPARE(root->property("test6_2").toBool(), true); + QCOMPARE(root->property("test6_3").toBool(), true); + QCOMPARE(root->property("test6_4").toBool(), true); + + QMetaObject::invokeMethod(root, "reparentImlicitlyInvisibleItem4_1"); + QCOMPARE(root->property("test7_1").toBool(), true); + QCOMPARE(root->property("test7_2").toBool(), true); + QCOMPARE(root->property("test7_3").toBool(), true); + QCOMPARE(root->property("test7_4").toBool(), true); + + // FINALLY TEST THAT EVERYTHING IS AS EXPECTED + QCOMPARE(root->property("test8_1").toBool(), true); + QCOMPARE(root->property("test8_2").toBool(), true); + QCOMPARE(root->property("test8_3").toBool(), true); + QCOMPARE(root->property("test8_4").toBool(), true); + QCOMPARE(root->property("test8_5").toBool(), true); + + delete canvas; +} + QTEST_MAIN(tst_QQuickItem) #include "tst_qquickitem.moc"