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>
This commit is contained in:
parent
9f2f3cb90b
commit
2c634a1326
@ -731,6 +731,12 @@ void QSplitterPrivate::setSizes_helper(const QList<int> &sizes, bool clampNegati
|
|||||||
doResize();
|
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)
|
void QSplitterPrivate::setGeo(QSplitterLayoutStruct *sls, int p, int s, bool allowCollapse)
|
||||||
{
|
{
|
||||||
Q_Q(QSplitter);
|
Q_Q(QSplitter);
|
||||||
@ -827,8 +833,7 @@ void QSplitterPrivate::insertWidget_helper(int index, QWidget *widget, bool show
|
|||||||
{
|
{
|
||||||
Q_Q(QSplitter);
|
Q_Q(QSplitter);
|
||||||
QBoolBlocker b(blockChildAdd);
|
QBoolBlocker b(blockChildAdd);
|
||||||
bool needShow = show && q->isVisible() &&
|
const bool needShow = show && shouldShowWidget(widget);
|
||||||
!(widget->isHidden() && widget->testAttribute(Qt::WA_WState_ExplicitShowHide));
|
|
||||||
if (widget->parentWidget() != q)
|
if (widget->parentWidget() != q)
|
||||||
widget->setParent(q);
|
widget->setParent(q);
|
||||||
if (needShow)
|
if (needShow)
|
||||||
@ -1124,6 +1129,66 @@ void QSplitter::insertWidget(int index, QWidget *widget)
|
|||||||
d->insertWidget_helper(index, widget, true);
|
d->insertWidget_helper(index, widget, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\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
|
\fn int QSplitter::indexOf(QWidget *widget) const
|
||||||
|
|
||||||
@ -1232,7 +1297,7 @@ void QSplitter::childEvent(QChildEvent *c)
|
|||||||
if (c->added() && !d->blockChildAdd && !d->findWidget(w)) {
|
if (c->added() && !d->blockChildAdd && !d->findWidget(w)) {
|
||||||
d->insertWidget_helper(d->list.count(), w, false);
|
d->insertWidget_helper(d->list.count(), w, false);
|
||||||
} else if (c->polished() && !d->blockChildAdd) {
|
} else if (c->polished() && !d->blockChildAdd) {
|
||||||
if (isVisible() && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide)))
|
if (d->shouldShowWidget(w))
|
||||||
w->show();
|
w->show();
|
||||||
} else if (c->type() == QEvent::ChildRemoved) {
|
} else if (c->type() == QEvent::ChildRemoved) {
|
||||||
for (int i = 0; i < d->list.size(); ++i) {
|
for (int i = 0; i < d->list.size(); ++i) {
|
||||||
|
@ -71,6 +71,7 @@ public:
|
|||||||
|
|
||||||
void addWidget(QWidget *widget);
|
void addWidget(QWidget *widget);
|
||||||
void insertWidget(int index, QWidget *widget);
|
void insertWidget(int index, QWidget *widget);
|
||||||
|
QWidget *replaceWidget(int index, QWidget *widget);
|
||||||
|
|
||||||
void setOrientation(Qt::Orientation);
|
void setOrientation(Qt::Orientation);
|
||||||
Qt::Orientation orientation() const;
|
Qt::Orientation orientation() const;
|
||||||
|
@ -125,6 +125,7 @@ public:
|
|||||||
int findWidgetJustBeforeOrJustAfter(int index, int delta, int &collapsibleSize) const;
|
int findWidgetJustBeforeOrJustAfter(int index, int delta, int &collapsibleSize) const;
|
||||||
void updateHandles();
|
void updateHandles();
|
||||||
void setSizes_helper(const QList<int> &sizes, bool clampNegativeSize = false);
|
void setSizes_helper(const QList<int> &sizes, bool clampNegativeSize = false);
|
||||||
|
bool shouldShowWidget(const QWidget *w) const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,6 +77,10 @@ private slots:
|
|||||||
void rubberBandNotInSplitter();
|
void rubberBandNotInSplitter();
|
||||||
void saveAndRestoreStateOfNotYetShownSplitter();
|
void saveAndRestoreStateOfNotYetShownSplitter();
|
||||||
void saveAndRestoreHandleWidth();
|
void saveAndRestoreHandleWidth();
|
||||||
|
void replaceWidget_data();
|
||||||
|
void replaceWidget();
|
||||||
|
void replaceWidgetWithSplitterChild_data();
|
||||||
|
void replaceWidgetWithSplitterChild();
|
||||||
|
|
||||||
// task-specific tests below me:
|
// task-specific tests below me:
|
||||||
void task187373_addAbstractScrollAreas();
|
void task187373_addAbstractScrollAreas();
|
||||||
@ -645,9 +649,177 @@ public:
|
|||||||
MyFriendlySplitter(QWidget *parent = 0) : QSplitter(parent) {}
|
MyFriendlySplitter(QWidget *parent = 0) : QSplitter(parent) {}
|
||||||
void setRubberBand(int pos) { QSplitter::setRubberBand(pos); }
|
void setRubberBand(int pos) { QSplitter::setRubberBand(pos); }
|
||||||
|
|
||||||
|
void moveSplitter(int pos, int index) { QSplitter::moveSplitter(pos, index); }
|
||||||
|
|
||||||
friend class tst_QSplitter;
|
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()
|
void tst_QSplitter::rubberBandNotInSplitter()
|
||||||
{
|
{
|
||||||
MyFriendlySplitter split;
|
MyFriendlySplitter split;
|
||||||
|
Loading…
Reference in New Issue
Block a user