diff options
-rw-r--r-- | src/widgets/widgets/qsplitter.cpp | 71 | ||||
-rw-r--r-- | src/widgets/widgets/qsplitter.h | 1 | ||||
-rw-r--r-- | src/widgets/widgets/qsplitter_p.h | 1 | ||||
-rw-r--r-- | tests/auto/widgets/widgets/qsplitter/tst_qsplitter.cpp | 172 |
4 files changed, 242 insertions, 3 deletions
diff --git a/src/widgets/widgets/qsplitter.cpp b/src/widgets/widgets/qsplitter.cpp index 910904e96e..77a793e951 100644 --- a/src/widgets/widgets/qsplitter.cpp +++ b/src/widgets/widgets/qsplitter.cpp @@ -731,6 +731,12 @@ void QSplitterPrivate::setSizes_helper(const QList<int> &sizes, bool clampNegati doResize(); } +bool QSplitterPrivate::shouldShowWidget(const QWidget *w) const +{ + Q_Q(const QSplitter); + return q->isVisible() && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide)); +} + void QSplitterPrivate::setGeo(QSplitterLayoutStruct *sls, int p, int s, bool allowCollapse) { Q_Q(QSplitter); @@ -827,8 +833,7 @@ void QSplitterPrivate::insertWidget_helper(int index, QWidget *widget, bool show { Q_Q(QSplitter); QBoolBlocker b(blockChildAdd); - bool needShow = show && q->isVisible() && - !(widget->isHidden() && widget->testAttribute(Qt::WA_WState_ExplicitShowHide)); + const bool needShow = show && shouldShowWidget(widget); if (widget->parentWidget() != q) widget->setParent(q); if (needShow) @@ -1125,6 +1130,66 @@ void QSplitter::insertWidget(int index, QWidget *widget) } /*! + \since 5.9 + + Replaces the widget in the splitter's layout at the given \a index by \a widget. + + Returns the widget that has just been replaced if \a index is valid and \a widget + is not already a child of the splitter. Otherwise, it returns null and no replacement + or addition is made. + + The geometry of the newly inserted widget will be the same as the widget it replaces. + Its visible and collapsed states are also inherited. + + \note The splitter takes ownership of \a widget and sets the parent of the + replaced widget to null. + + \sa insertWidget(), indexOf() +*/ +QWidget *QSplitter::replaceWidget(int index, QWidget *widget) +{ + Q_D(QSplitter); + if (!widget) { + qWarning("QSplitter::replaceWidget: Widget can't be null"); + return nullptr; + } + + if (index < 0 || index >= d->list.count()) { + qWarning("QSplitter::replaceWidget: Index %d out of range", index); + return nullptr; + } + + QSplitterLayoutStruct *s = d->list.at(index); + QWidget *current = s->widget; + if (current == widget) { + qWarning("QSplitter::replaceWidget: Trying to replace a widget with itself"); + return nullptr; + } + + if (widget->parentWidget() == this) { + qWarning("QSplitter::replaceWidget: Trying to replace a widget with one of its siblings"); + return nullptr; + } + + QBoolBlocker b(d->blockChildAdd); + + const QRect geom = current->geometry(); + const bool shouldShow = d->shouldShowWidget(current); + + s->widget = widget; + current->setParent(nullptr); + widget->setParent(this); + + // The splitter layout struct's geometry is already set and + // should not change. Only set the geometry on the new widget + widget->setGeometry(geom); + widget->lower(); + widget->setVisible(shouldShow); + + return current; +} + +/*! \fn int QSplitter::indexOf(QWidget *widget) const Returns the index in the splitter's layout of the specified \a widget. This @@ -1232,7 +1297,7 @@ void QSplitter::childEvent(QChildEvent *c) if (c->added() && !d->blockChildAdd && !d->findWidget(w)) { d->insertWidget_helper(d->list.count(), w, false); } else if (c->polished() && !d->blockChildAdd) { - if (isVisible() && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide))) + if (d->shouldShowWidget(w)) w->show(); } else if (c->type() == QEvent::ChildRemoved) { for (int i = 0; i < d->list.size(); ++i) { diff --git a/src/widgets/widgets/qsplitter.h b/src/widgets/widgets/qsplitter.h index 9cfde9fed3..e9ef3c3f2d 100644 --- a/src/widgets/widgets/qsplitter.h +++ b/src/widgets/widgets/qsplitter.h @@ -71,6 +71,7 @@ public: void addWidget(QWidget *widget); void insertWidget(int index, QWidget *widget); + QWidget *replaceWidget(int index, QWidget *widget); void setOrientation(Qt::Orientation); Qt::Orientation orientation() const; diff --git a/src/widgets/widgets/qsplitter_p.h b/src/widgets/widgets/qsplitter_p.h index 4422d9a8a4..07b43e56b8 100644 --- a/src/widgets/widgets/qsplitter_p.h +++ b/src/widgets/widgets/qsplitter_p.h @@ -125,6 +125,7 @@ public: int findWidgetJustBeforeOrJustAfter(int index, int delta, int &collapsibleSize) const; void updateHandles(); void setSizes_helper(const QList<int> &sizes, bool clampNegativeSize = false); + bool shouldShowWidget(const QWidget *w) const; }; diff --git a/tests/auto/widgets/widgets/qsplitter/tst_qsplitter.cpp b/tests/auto/widgets/widgets/qsplitter/tst_qsplitter.cpp index f490446c8a..c4cd771f92 100644 --- a/tests/auto/widgets/widgets/qsplitter/tst_qsplitter.cpp +++ b/tests/auto/widgets/widgets/qsplitter/tst_qsplitter.cpp @@ -77,6 +77,10 @@ private slots: void rubberBandNotInSplitter(); void saveAndRestoreStateOfNotYetShownSplitter(); void saveAndRestoreHandleWidth(); + void replaceWidget_data(); + void replaceWidget(); + void replaceWidgetWithSplitterChild_data(); + void replaceWidgetWithSplitterChild(); // task-specific tests below me: void task187373_addAbstractScrollAreas(); @@ -645,9 +649,177 @@ public: MyFriendlySplitter(QWidget *parent = 0) : QSplitter(parent) {} void setRubberBand(int pos) { QSplitter::setRubberBand(pos); } + void moveSplitter(int pos, int index) { QSplitter::moveSplitter(pos, index); } + friend class tst_QSplitter; }; +class EventCounterSpy : public QObject +{ +public: + EventCounterSpy(QWidget *parentWidget) : QObject(parentWidget) + { } + + bool eventFilter(QObject *watched, QEvent *event) override + { + // Watch for events in the parent widget and all its children + if (watched == parent() || watched->parent() == parent()) { + if (event->type() == QEvent::Resize) + resizeCount++; + else if (event->type() == QEvent::Paint) + paintCount++; + } + + return QObject::eventFilter(watched, event); + } + + int resizeCount = 0; + int paintCount = 0; +}; + +void tst_QSplitter::replaceWidget_data() +{ + QTest::addColumn<int>("index"); + QTest::addColumn<bool>("visible"); + QTest::addColumn<bool>("collapsed"); + + QTest::newRow("negative index") << -1 << true << false; + QTest::newRow("index too large") << 80 << true << false; + QTest::newRow("visible, not collapsed") << 3 << true << false; + QTest::newRow("visible, collapsed") << 3 << true << true; + QTest::newRow("not visible, not collapsed") << 3 << false << false; + QTest::newRow("not visible, collapsed") << 3 << false << true; +} + +void tst_QSplitter::replaceWidget() +{ + QFETCH(int, index); + QFETCH(bool, visible); + QFETCH(bool, collapsed); + + // Setup + MyFriendlySplitter sp; + const int count = 7; + for (int i = 0; i < count; i++) { + // We use labels instead of plain widgets to + // make it easier to fix eventual regressions. + QLabel *w = new QLabel(QString::asprintf("WIDGET #%d", i)); + sp.addWidget(w); + } + sp.setWindowTitle(QString::asprintf("index %d, visible %d, collapsed %d", index, visible, collapsed)); + sp.show(); + QVERIFY(QTest::qWaitForWindowExposed(&sp)); + + // Configure splitter + QWidget *oldWidget = sp.widget(index); + const QRect oldGeom = oldWidget ? oldWidget->geometry() : QRect(); + if (oldWidget) { + // Collapse first, then hide, if necessary + if (collapsed) { + sp.setCollapsible(index, true); + sp.moveSplitter(oldWidget->x() + 1, index + 1); + } + if (!visible) + oldWidget->hide(); + } + + // Replace widget + QTest::qWait(100); // Flush event queue + const QList<int> sizes = sp.sizes(); + // Shorter label: The important thing is to ensure we can set + // the same size on the new widget. Because of QLabel's sizing + // constraints (they can expand but not shrink) the easiest is + // to set a shorter label. + QLabel *newWidget = new QLabel(QLatin1String("<b>NEW</b>")); + + EventCounterSpy *ef = new EventCounterSpy(&sp); + qApp->installEventFilter(ef); + const QWidget *res = sp.replaceWidget(index, newWidget); + QTest::qWait(100); // Give visibility and resizing some time + qApp->removeEventFilter(ef); + + // Check + if (index < 0 || index >= count) { + QVERIFY(!res); + QVERIFY(!newWidget->parentWidget()); + QCOMPARE(ef->resizeCount, 0); + QCOMPARE(ef->paintCount, 0); + } else { + QCOMPARE(res, oldWidget); + QVERIFY(!res->parentWidget()); + QVERIFY(!res->isVisible()); + QCOMPARE(newWidget->parentWidget(), &sp); + QCOMPARE(newWidget->isVisible(), visible); + if (visible && !collapsed) + QCOMPARE(newWidget->geometry(), oldGeom); + QCOMPARE(newWidget->size().isEmpty(), !visible || collapsed); + const int expectedResizeCount = visible ? 1 : 0; // new widget only + const int expectedPaintCount = visible && !collapsed ? 2 : 0; // splitter and new widget + QCOMPARE(ef->resizeCount, expectedResizeCount); + QCOMPARE(ef->paintCount, expectedPaintCount); + delete res; + } + QCOMPARE(sp.count(), count); + QCOMPARE(sp.sizes(), sizes); +} + +void tst_QSplitter::replaceWidgetWithSplitterChild_data() +{ + QTest::addColumn<int>("srcIndex"); + QTest::addColumn<int>("dstIndex"); + + QTest::newRow("replace with null widget") << -2 << 3; + QTest::newRow("replace with itself") << 3 << 3; + QTest::newRow("replace with sibling, after recalc") << 1 << 4; + QTest::newRow("replace with sibling, before recalc") << -1 << 4; +} + +void tst_QSplitter::replaceWidgetWithSplitterChild() +{ + QFETCH(int, srcIndex); + QFETCH(int, dstIndex); + + // Setup + MyFriendlySplitter sp; + const int count = 7; + for (int i = 0; i < count; i++) { + // We use labels instead of plain widgets to + // make it easier to fix eventual regressions. + QLabel *w = new QLabel(QString::asprintf("WIDGET #%d", i)); + sp.addWidget(w); + } + sp.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ') + QLatin1String(QTest::currentDataTag())); + sp.show(); + QVERIFY(QTest::qWaitForWindowExposed(&sp)); + + QTest::qWait(100); // Flush event queue before new widget creation + const QList<int> sizes = sp.sizes(); + QWidget *sibling = srcIndex == -1 ? (new QLabel("<b>NEW</b>", &sp)) : sp.widget(srcIndex); + + EventCounterSpy *ef = new EventCounterSpy(&sp); + qApp->installEventFilter(ef); + const QWidget *res = sp.replaceWidget(dstIndex, sibling); + QTest::qWait(100); // Give visibility and resizing some time + qApp->removeEventFilter(ef); + + QVERIFY(!res); + if (srcIndex == -1) { + // Create and replace before recalc. The sibling is scheduled to be + // added after replaceWidget(), when QSplitter receives a child event. + QVERIFY(ef->resizeCount > 0); + QVERIFY(ef->paintCount > 0); + QCOMPARE(sp.count(), count + 1); + QCOMPARE(sp.sizes().mid(0, count), sizes); + QCOMPARE(sp.sizes().last(), sibling->width()); + } else { + // No-op for the rest + QCOMPARE(ef->resizeCount, 0); + QCOMPARE(ef->paintCount, 0); + QCOMPARE(sp.count(), count); + QCOMPARE(sp.sizes(), sizes); + } +} + void tst_QSplitter::rubberBandNotInSplitter() { MyFriendlySplitter split; |