Added new convenience readonly visibleChildren property to Item
authorMathias Malmqvist <mathias.malmqvist@nokia.com>
Mon, 5 Dec 2011 19:21:33 +0000 (19:21 +0000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 9 Feb 2012 23:43:20 +0000 (00:43 +0100)
Change-Id: I5ec541226fabd72c05ce8ccb8bb7e56f6ec7717a
Task-number: 22724
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
src/quick/items/qquickitem.cpp
src/quick/items/qquickitem.h
src/quick/items/qquickitem_p.h
tests/auto/qtquick2/qquickitem2/data/visiblechildren.qml [new file with mode: 0644]
tests/auto/qtquick2/qquickitem2/tst_qquickitem.cpp

index 22d2dd2..f443b1a 100644 (file)
@@ -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<QQuickItem> *pro
         p->childItems.at(0)->setParentItem(0);
 }
 
+void QQuickItemPrivate::visibleChildren_append(QDeclarativeListProperty<QQuickItem>*, QQuickItem *self)
+{
+    // do nothing
+    qmlInfo(self) << "QQuickItem: visibleChildren property is readonly and cannot be assigned to.";
+}
+
+int QQuickItemPrivate::visibleChildren_count(QDeclarativeListProperty<QQuickItem> *prop)
+{
+    QQuickItemPrivate *p = QQuickItemPrivate::get(static_cast<QQuickItem *>(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<QQuickItem> *prop, int index)
+{
+    QQuickItemPrivate *p = QQuickItemPrivate::get(static_cast<QQuickItem *>(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<QQuickTransform> *prop)
 {
     QQuickItem *that = static_cast<QQuickItem *>(prop->object);
@@ -3321,6 +3360,20 @@ QDeclarativeListProperty<QQuickItem> 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<QQuickItem> QQuickItemPrivate::visibleChildren()
+{
+    return QDeclarativeListProperty<QQuickItem>(q_func(), 0, QQuickItemPrivate::visibleChildren_append,
+                                             QQuickItemPrivate::visibleChildren_count,
+                                             QQuickItemPrivate::visibleChildren_at);
+
+}
+
 QDeclarativeListProperty<QDeclarativeState> 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
index dd7d2c3..91771c8 100644 (file)
@@ -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<QQuickItem> visibleChildren READ visibleChildren NOTIFY visibleChildrenChanged DESIGNABLE false)
 
     Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QDeclarativeListProperty<QDeclarativeState> states READ states DESIGNABLE false)
     Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QDeclarativeListProperty<QDeclarativeTransition> transitions READ transitions DESIGNABLE false)
@@ -341,6 +342,7 @@ Q_SIGNALS:
     void opacityChanged();
     void enabledChanged();
     void visibleChanged();
+    void visibleChildrenChanged();
     void rotationChanged();
     void scaleChanged();
 
index 790c925..c88219f 100644 (file)
@@ -246,6 +246,7 @@ public:
     QDeclarativeListProperty<QObject> data();
     QDeclarativeListProperty<QObject> resources();
     QDeclarativeListProperty<QQuickItem> children();
+    QDeclarativeListProperty<QQuickItem> visibleChildren();
 
     QDeclarativeListProperty<QDeclarativeState> states();
     QDeclarativeListProperty<QDeclarativeTransition> transitions();
@@ -281,6 +282,11 @@ public:
     static QQuickItem *children_at(QDeclarativeListProperty<QQuickItem> *, int);
     static void children_clear(QDeclarativeListProperty<QQuickItem> *);
 
+    // visibleChildren property
+    static void visibleChildren_append(QDeclarativeListProperty<QQuickItem> *prop, QQuickItem *o);
+    static int visibleChildren_count(QDeclarativeListProperty<QQuickItem> *prop);
+    static QQuickItem *visibleChildren_at(QDeclarativeListProperty<QQuickItem> *prop, int index);
+
     // transform property
     static int transform_count(QDeclarativeListProperty<QQuickTransform> *list);
     static void transform_append(QDeclarativeListProperty<QQuickTransform> *list, QQuickTransform *);
@@ -471,7 +477,7 @@ public:
     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 (file)
index 0000000..e51eb35
--- /dev/null
@@ -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
+
+}
index f6a7b0c..c26fc94 100644 (file)
@@ -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<QQuickItem*>(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"