summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGabriel de Dietrich <gabriel.dedietrich@qt.io>2016-11-16 17:21:32 -0800
committerGabriel de Dietrich <gabriel.dedietrich@qt.io>2016-12-02 16:15:58 +0000
commit2c634a132696376a0a0d7641bf05ae875c50190e (patch)
tree828b10cad212ca4f320c4b6a012ddc660a023e7a
parent9f2f3cb90b211b273139e12640d36f6ce845dc98 (diff)
Introducing QSplitter::replaceWidget()
This new API addresses the use case where we want to replace a widget by another one inside the splitter. Up to now, the way of doing would include removing one widget and add the new one at the same place. However, this triggers a series of resize and paint events because of the successive changes in the splitter's children leading to a relayout of the remaining children. The new widget inherits the same properties as in the previous slot: geometry, visibility, and collapsed states. The previous widget, returned by the function, loses its parent and is hidden. Change-Id: I3dddf6b582d5ce2db8cff3c40bc46084263123ac Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io> Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
-rw-r--r--src/widgets/widgets/qsplitter.cpp71
-rw-r--r--src/widgets/widgets/qsplitter.h1
-rw-r--r--src/widgets/widgets/qsplitter_p.h1
-rw-r--r--tests/auto/widgets/widgets/qsplitter/tst_qsplitter.cpp172
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;