From d84a7a812818065f97e015e50f66f6dad8c9a978 Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Mon, 1 Oct 2018 16:24:44 +0200 Subject: QTreeView: add expandRecursively() to expand all items below an index QTreeView only had functions to either expand all items or until a given depth. What was missing is to expand all subitems for an index programmatically which is added with this patch. [ChangeLog][QtWidgets][QTreeView] Added expandRecursively() to expand all items below a given index Fixes: QTBUG-10482 Change-Id: I8fc4d50b0b7e90245840c99a0188f13c0670253a Reviewed-by: Samuel Gaist Reviewed-by: Richard Moe Gustavsen Reviewed-by: Shawn Rutledge --- src/widgets/itemviews/qtreeview.cpp | 62 +++++++++--- src/widgets/itemviews/qtreeview.h | 1 + .../widgets/itemviews/qtreeview/tst_qtreeview.cpp | 112 +++++++++++++++------ 3 files changed, 126 insertions(+), 49 deletions(-) diff --git a/src/widgets/itemviews/qtreeview.cpp b/src/widgets/itemviews/qtreeview.cpp index 60da0eb6c3..743832b214 100644 --- a/src/widgets/itemviews/qtreeview.cpp +++ b/src/widgets/itemviews/qtreeview.cpp @@ -1985,21 +1985,7 @@ void QTreeView::keyPressEvent(QKeyEvent *event) if (d->isIndexValid(current) && d->model && d->itemsExpandable) { switch (event->key()) { case Qt::Key_Asterisk: { - // do layouting only once after expanding is done - d->doDelayedItemsLayout(); - QStack parents; - parents.push(current); - while (!parents.isEmpty()) { - QModelIndex parent = parents.pop(); - for (int row = 0; row < d->model->rowCount(parent); ++row) { - QModelIndex child = d->model->index(row, 0, parent); - if (!d->isIndexValid(child)) - break; - parents.push(child); - expand(child); - } - } - expand(current); + expandRecursively(current); break; } case Qt::Key_Plus: expand(current); @@ -2694,7 +2680,7 @@ QSize QTreeView::viewportSizeHint() const \since 4.2 Expands all expandable items. - Warning: if the model contains a large number of items, + \warning: if the model contains a large number of items, this function will take some time to execute. \sa collapseAll(), expand(), collapse(), setExpanded() @@ -2709,6 +2695,50 @@ void QTreeView::expandAll() d->viewport->update(); } +/*! + \since 5.13 + Expands the item at the given \a index and all its children to the + given \a depth. The \a depth is relative to the given \a index. + A \a depth of -1 will expand all children, a \a depth of 0 will + only expand the given \a index. + + \warning: if the model contains a large number of items, + this function will take some time to execute. + + \sa expandAll() +*/ +void QTreeView::expandRecursively(const QModelIndex &index, int depth) +{ + Q_D(QTreeView); + + if (depth < -1) + return; + // do layouting only once after expanding is done + d->doDelayedItemsLayout(); + expand(index); + if (depth == 0) + return; + QStack> parents; + parents.push({index, 0}); + while (!parents.isEmpty()) { + const QPair elem = parents.pop(); + const QModelIndex &parent = elem.first; + const int curDepth = elem.second; + const int rowCount = d->model->rowCount(parent); + for (int row = 0; row < rowCount; ++row) { + const QModelIndex child = d->model->index(row, 0, parent); + if (!d->isIndexValid(child)) + break; + if (depth == -1 || curDepth + 1 < depth) + parents.push({child, curDepth + 1}); + if (d->isIndexExpanded(child)) + continue; + if (d->storeExpanded(child)) + emit expanded(child); + } + } +} + /*! \since 4.2 diff --git a/src/widgets/itemviews/qtreeview.h b/src/widgets/itemviews/qtreeview.h index 33dbf1c1ce..ee65cf4265 100644 --- a/src/widgets/itemviews/qtreeview.h +++ b/src/widgets/itemviews/qtreeview.h @@ -160,6 +160,7 @@ public Q_SLOTS: void resizeColumnToContents(int column); void sortByColumn(int column); void expandAll(); + void expandRecursively(const QModelIndex &index, int depth = -1); void collapseAll(); void expandToDepth(int depth); diff --git a/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp b/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp index f4a73b8b4a..b8c36f9b21 100644 --- a/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp +++ b/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp @@ -1647,50 +1647,96 @@ void tst_QTreeView::expandAndCollapse() } } +static void checkExpandState(const QAbstractItemModel &model, const QTreeView &view, + const QModelIndex &startIdx, bool bIsExpanded, int *count) +{ + *count = 0; + QStack parents; + parents.push(startIdx); + if (startIdx.isValid()) { + QCOMPARE(view.isExpanded(startIdx), bIsExpanded); + *count += 1; + } + while (!parents.isEmpty()) { + const QModelIndex p = parents.pop(); + const int rows = model.rowCount(p); + for (int r = 0; r < rows; ++r) { + const QModelIndex c = model.index(r, 0, p); + QCOMPARE(view.isExpanded(c), bIsExpanded); + parents.push(c); + } + *count += rows; + } +} + void tst_QTreeView::expandAndCollapseAll() { - QtTestModel model(3, 2); - model.levels = 2; + QStandardItemModel model; + // QtTestModel has a broken parent/child handling which will break the test + for (int i1 = 0; i1 < 3; ++i1) { + QStandardItem *s1 = new QStandardItem; + s1->setText(QString::number(i1)); + model.appendRow(s1); + for (int i2 = 0; i2 < 3; ++i2) { + QStandardItem *s2 = new QStandardItem; + s2->setText(QStringLiteral("%1 - %2").arg(i1).arg(i2)); + s1->appendRow(s2); + for (int i3 = 0; i3 < 3; ++i3) { + QStandardItem *s3 = new QStandardItem; + s3->setText(QStringLiteral("%1 - %2 - %3").arg(i1).arg(i2).arg(i3)); + s2->appendRow(s3); + } + } + } QTreeView view; view.setUniformRowHeights(true); view.setModel(&model); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); - QSignalSpy expandedSpy(&view, SIGNAL(expanded(QModelIndex))); - QSignalSpy collapsedSpy(&view, SIGNAL(collapsed(QModelIndex))); + QSignalSpy expandedSpy(&view, &QTreeView::expanded); + QSignalSpy collapsedSpy(&view, &QTreeView::collapsed); + int count; view.expandAll(); - view.show(); - + checkExpandState(model, view, QModelIndex(), true, &count); QCOMPARE(collapsedSpy.count(), 0); + QCOMPARE(expandedSpy.count(), 39); // == 3 (first) + 9 (second) + 27 (third level) + QCOMPARE(count, 39); - QStack parents; - parents.push(QModelIndex()); - int count = 0; - while (!parents.isEmpty()) { - QModelIndex p = parents.pop(); - int rows = model.rowCount(p); - for (int r = 0; r < rows; ++r) - QVERIFY(view.isExpanded(model.index(r, 0, p))); - count += rows; - for (int r = 0; r < rows; ++r) - parents.push(model.index(r, 0, p)); - } - QCOMPARE(expandedSpy.count(), 12); // == (3+1)*(2+1) from QtTestModel model(3, 2); - + collapsedSpy.clear(); + expandedSpy.clear(); view.collapseAll(); - - parents.push(QModelIndex()); - count = 0; - while (!parents.isEmpty()) { - QModelIndex p = parents.pop(); - int rows = model.rowCount(p); - for (int r = 0; r < rows; ++r) - QVERIFY(!view.isExpanded(model.index(r, 0, p))); - count += rows; - for (int r = 0; r < rows; ++r) - parents.push(model.index(r, 0, p)); - } - QCOMPARE(collapsedSpy.count(), 12); + checkExpandState(model, view, QModelIndex(), false, &count); + QCOMPARE(collapsedSpy.count(), 39); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(count, 39); + + collapsedSpy.clear(); + expandedSpy.clear(); + view.expandRecursively(model.index(0, 0)); + QCOMPARE(expandedSpy.count(), 13); // 1 + 3 + 9 + + checkExpandState(model, view, model.index(0, 0), true, &count); + QCOMPARE(count, 13); + checkExpandState(model, view, model.index(1, 0), false, &count); + QCOMPARE(count, 13); + checkExpandState(model, view, model.index(2, 0), false, &count); + QCOMPARE(count, 13); + + expandedSpy.clear(); + view.collapseAll(); + view.expandRecursively(model.index(0, 0), 1); + QCOMPARE(expandedSpy.count(), 4); // 1 + 3 + view.expandRecursively(model.index(0, 0), 2); + QCOMPARE(expandedSpy.count(), 13); // (1 + 3) + 9 + + checkExpandState(model, view, model.index(0, 0), true, &count); + QCOMPARE(count, 13); + checkExpandState(model, view, model.index(1, 0), false, &count); + QCOMPARE(count, 13); + checkExpandState(model, view, model.index(2, 0), false, &count); + QCOMPARE(count, 13); } void tst_QTreeView::expandWithNoChildren() -- cgit v1.2.3