Add widget replace function to QLayout

Sometimes it is nice to be able to replace a widget in a layout.

Change-Id: I23a6a65e417e94d53bc48639503db1a142bc3f10
Reviewed-by: J-P Nurmi <jpnurmi@digia.com>
This commit is contained in:
Thorbjørn Lund Martsum 2012-09-03 06:35:39 +02:00 committed by The Qt Project
parent e327ba191d
commit 42d681f9cf
14 changed files with 401 additions and 2 deletions

View File

@ -140,6 +140,7 @@ public:
void calcHfw(int); void calcHfw(int);
void effectiveMargins(int *left, int *top, int *right, int *bottom) const; void effectiveMargins(int *left, int *top, int *right, int *bottom) const;
QLayoutItem* replaceAt(int index, QLayoutItem*) Q_DECL_OVERRIDE;
}; };
QBoxLayoutPrivate::~QBoxLayoutPrivate() QBoxLayoutPrivate::~QBoxLayoutPrivate()
@ -444,6 +445,21 @@ void QBoxLayoutPrivate::calcHfw(int w)
hfwMinHeight = mh; hfwMinHeight = mh;
} }
QLayoutItem* QBoxLayoutPrivate::replaceAt(int index, QLayoutItem *item)
{
Q_Q(QBoxLayout);
if (!item)
return 0;
QBoxLayoutItem *b = list.value(index);
if (!b)
return 0;
QLayoutItem *r = b->item;
b->item = item;
q->invalidate();
return r;
}
/*! /*!
\class QBoxLayout \class QBoxLayout

View File

@ -246,6 +246,7 @@ public:
int hSpacing; int hSpacing;
int vSpacing; int vSpacing;
QLayoutItem* replaceAt(int index, QLayoutItem*) Q_DECL_OVERRIDE;
}; };
QFormLayoutPrivate::QFormLayoutPrivate() QFormLayoutPrivate::QFormLayoutPrivate()
@ -1001,6 +1002,32 @@ QStyle* QFormLayoutPrivate::getStyle() const
return QApplication::style(); return QApplication::style();
} }
QLayoutItem* QFormLayoutPrivate::replaceAt(int index, QLayoutItem *newitem)
{
Q_Q(QFormLayout);
if (!newitem)
return 0;
const int storageIndex = storageIndexFromLayoutItem(m_matrix, m_things.value(index));
if (storageIndex == -1) {
// ### Qt6 - fix warning too when this class becomes public
qWarning("QFormLayoutPrivate::replaceAt: Invalid index %d", index);
return 0;
}
int row, col;
QFormLayoutPrivate::ItemMatrix::storageIndexToPosition(storageIndex, &row, &col);
Q_ASSERT(m_matrix(row, col));
QFormLayoutItem *item = m_matrix(row, col);
Q_ASSERT(item);
QLayoutItem *olditem = item->item;
item->item = newitem;
q->invalidate();
return olditem;
}
/*! /*!
\class QFormLayout \class QFormLayout
\since 4.4 \since 4.4

View File

@ -85,6 +85,7 @@ public:
void setGeometry(const QRect &r) { item_->setGeometry(r); } void setGeometry(const QRect &r) { item_->setGeometry(r); }
Qt::Alignment alignment() const { return item_->alignment(); } Qt::Alignment alignment() const { return item_->alignment(); }
QLayoutItem *item() { return item_; } QLayoutItem *item() { return item_; }
void setItem(QLayoutItem *newitem) { item_ = newitem; }
QLayoutItem *takeItem() { QLayoutItem *i = item_; item_ = 0; return i; } QLayoutItem *takeItem() { QLayoutItem *i = item_; item_ = 0; return i; }
int hStretch() { return item_->widget() ? int hStretch() { return item_->widget() ?
@ -171,6 +172,18 @@ public:
} }
return 0; return 0;
} }
QLayoutItem* replaceAt(int index, QLayoutItem *newitem) Q_DECL_OVERRIDE
{
if (!newitem)
return 0;
QLayoutItem *item = 0;
QGridBox *b = things.value(index);
if (b) {
item = b->takeItem();
b->setItem(newitem);
}
return item;
}
void getItemPosition(int index, int *row, int *column, int *rowSpan, int *columnSpan) const { void getItemPosition(int index, int *row, int *column, int *rowSpan, int *columnSpan) const {
if (index < things.count()) { if (index < things.count()) {

View File

@ -1108,6 +1108,54 @@ bool QLayout::activate()
return true; return true;
} }
/*!
\since 5.2
Searches for widget \a from and replaces it with widget \a to if found.
Returns the layout item that contains the widget \a from on success. Otherwise \c 0 is returned.
If \a recursive is \c true, sub-layouts are searched for doing the replacement. Notice that the returned item therefore might not belong to this layout, but to a sub-layout.
The returned layout item is no longer owned by the layout and should be either deleted or inserted to another layout. The widget \a from is no longer managed by the layout and may need to be deleted or hidden. The parent of widget \a from is left unchanged.
This function works for the built-in Qt layouts, but might not work for custom layouts.
\sa indexOf()
*/
//### Qt 6 make this function virtual
QLayoutItem* QLayout::replaceWidget(QWidget *from, QWidget *to, bool recursive)
{
Q_D(QLayout);
if (!from || !to)
return 0;
int index = -1;
QLayoutItem *item = 0;
for (int u = 0; u < count(); ++u) {
item = itemAt(u);
if (item->widget() == from) {
index = u;
break;
}
if (item && item->layout() && recursive) {
QLayoutItem *r = item->layout()->replaceWidget(from, to, true);
if (r)
return r;
}
}
if (index == -1)
return 0;
QLayoutItem *newitem = new QWidgetItem(to);
newitem->setAlignment(item->alignment());
QLayoutItem *r = d->replaceAt(index, newitem);
if (!r)
delete newitem;
else
addChildWidget(to);
return r;
}
/*! /*!
\fn QLayoutItem *QLayout::itemAt(int index) const \fn QLayoutItem *QLayout::itemAt(int index) const

View File

@ -130,6 +130,7 @@ public:
virtual int count() const = 0; virtual int count() const = 0;
bool isEmpty() const; bool isEmpty() const;
QSizePolicy::ControlTypes controlTypes() const; QSizePolicy::ControlTypes controlTypes() const;
QLayoutItem* replaceWidget(QWidget *from, QWidget *to, bool recursive = true);
int totalHeightForWidth(int w) const; int totalHeightForWidth(int w) const;
QSize totalMinimumSize() const; QSize totalMinimumSize() const;

View File

@ -61,6 +61,7 @@ QT_BEGIN_NAMESPACE
class QWidgetItem; class QWidgetItem;
class QSpacerItem; class QSpacerItem;
class QLayoutItem;
class Q_WIDGETS_EXPORT QLayoutPrivate : public QObjectPrivate class Q_WIDGETS_EXPORT QLayoutPrivate : public QObjectPrivate
{ {
@ -78,6 +79,7 @@ public:
static QWidgetItem *createWidgetItem(const QLayout *layout, QWidget *widget); static QWidgetItem *createWidgetItem(const QLayout *layout, QWidget *widget);
static QSpacerItem *createSpacerItem(const QLayout *layout, int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum, QSizePolicy::Policy vPolicy = QSizePolicy::Minimum); static QSpacerItem *createSpacerItem(const QLayout *layout, int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum, QSizePolicy::Policy vPolicy = QSizePolicy::Minimum);
virtual QLayoutItem* replaceAt(int index, QLayoutItem *newitem) { Q_UNUSED(index); Q_UNUSED(newitem); return 0; }
static QWidgetItemFactoryMethod widgetItemFactoryMethod; static QWidgetItemFactoryMethod widgetItemFactoryMethod;
static QSpacerItemFactoryMethod spacerItemFactoryMethod; static QSpacerItemFactoryMethod spacerItemFactoryMethod;

View File

@ -53,11 +53,29 @@ class QStackedLayoutPrivate : public QLayoutPrivate
Q_DECLARE_PUBLIC(QStackedLayout) Q_DECLARE_PUBLIC(QStackedLayout)
public: public:
QStackedLayoutPrivate() : index(-1), stackingMode(QStackedLayout::StackOne) {} QStackedLayoutPrivate() : index(-1), stackingMode(QStackedLayout::StackOne) {}
QLayoutItem* replaceAt(int index, QLayoutItem *newitem) Q_DECL_OVERRIDE;
QList<QLayoutItem *> list; QList<QLayoutItem *> list;
int index; int index;
QStackedLayout::StackingMode stackingMode; QStackedLayout::StackingMode stackingMode;
}; };
QLayoutItem* QStackedLayoutPrivate::replaceAt(int idx, QLayoutItem *newitem)
{
Q_Q(QStackedLayout);
if (idx < 0 || idx >= list.size() || !newitem)
return 0;
QWidget *wdg = newitem->widget();
if (!wdg) {
qWarning("QStackedLayout::replaceAt: Only widgets can be added");
return 0;
}
QLayoutItem *orgitem = list.at(idx);
list.replace(idx, newitem);
if (idx == index)
q->setCurrentIndex(index);
return orgitem;
}
/*! /*!
\class QStackedLayout \class QStackedLayout

View File

@ -79,6 +79,7 @@ private slots:
void taskQTBUG_7103_minMaxWidthNotRespected(); void taskQTBUG_7103_minMaxWidthNotRespected();
void taskQTBUG_27420_takeAtShouldUnparentLayout(); void taskQTBUG_27420_takeAtShouldUnparentLayout();
void replaceWidget();
}; };
class CustomLayoutStyle : public QProxyStyle class CustomLayoutStyle : public QProxyStyle
@ -328,7 +329,6 @@ void tst_QBoxLayout::taskQTBUG_27420_takeAtShouldUnparentLayout()
QVERIFY(!inner.isNull()); QVERIFY(!inner.isNull());
} }
struct Descr struct Descr
{ {
Descr(int min, int sh, int max = -1, bool exp= false, int _stretch = 0, bool _empty = false) Descr(int min, int sh, int max = -1, bool exp= false, int _stretch = 0, bool _empty = false)
@ -507,5 +507,24 @@ void tst_QBoxLayout::testLayoutEngine()
} }
} }
void tst_QBoxLayout::replaceWidget()
{
QWidget w;
QBoxLayout *boxLayout = new QVBoxLayout(&w);
QLineEdit *replaceFrom = new QLineEdit;
QLineEdit *replaceTo = new QLineEdit;
boxLayout->addWidget(new QLineEdit());
boxLayout->addWidget(replaceFrom);
boxLayout->addWidget(new QLineEdit());
QCOMPARE(boxLayout->indexOf(replaceFrom), 1);
QCOMPARE(boxLayout->indexOf(replaceTo), -1);
boxLayout->replaceWidget(replaceFrom, replaceTo);
QCOMPARE(boxLayout->indexOf(replaceFrom), -1);
QCOMPARE(boxLayout->indexOf(replaceTo), 1);
}
QTEST_MAIN(tst_QBoxLayout) QTEST_MAIN(tst_QBoxLayout)
#include "tst_qboxlayout.moc" #include "tst_qboxlayout.moc"

View File

@ -122,6 +122,7 @@ private slots:
void itemAt(); void itemAt();
void takeAt(); void takeAt();
void layoutAlone(); void layoutAlone();
void replaceWidget();
/* /*
void setGeometry(const QRect &rect); void setGeometry(const QRect &rect);
QSize minimumSize() const; QSize minimumSize() const;
@ -934,6 +935,54 @@ void tst_QFormLayout::taskQTBUG_27420_takeAtShouldUnparentLayout()
QVERIFY(!inner.isNull()); QVERIFY(!inner.isNull());
} }
void tst_QFormLayout::replaceWidget()
{
QWidget w;
QFormLayout *layout = new QFormLayout();
w.setLayout(layout);
QLineEdit *edit1 = new QLineEdit();
QLineEdit *edit2 = new QLineEdit();
QLineEdit *edit3 = new QLineEdit();
QLabel *label1 = new QLabel();
QLabel *label2 = new QLabel();
layout->addRow("Label", edit1);
layout->addRow(label1, edit2);
// Verify controls not in layout
QCOMPARE(layout->indexOf(edit3), -1);
QCOMPARE(layout->indexOf(label2), -1);
// Verify controls in layout
int editIndex = layout->indexOf(edit1);
int labelIndex = layout->indexOf(label1);
QVERIFY(editIndex > 0);
QVERIFY(labelIndex > 0);
int rownum;
QFormLayout::ItemRole role;
// replace editor
layout->replaceWidget(edit1, edit3);
edit1->hide(); // Not strictly needed for the test, but for normal usage it is.
QCOMPARE(layout->indexOf(edit1), -1);
QCOMPARE(layout->indexOf(edit3), editIndex);
QCOMPARE(layout->indexOf(label1), labelIndex);
rownum = -1;
role = QFormLayout::SpanningRole;
layout->getWidgetPosition(edit3, &rownum, &role);
QCOMPARE(rownum, 0);
QCOMPARE(role, QFormLayout::FieldRole);
layout->replaceWidget(label1, label2);
label1->hide();
QCOMPARE(layout->indexOf(label1), -1);
QCOMPARE(layout->indexOf(label2), labelIndex);
layout->getWidgetPosition(label2, &rownum, &role);
QCOMPARE(rownum, 1);
QCOMPARE(role, QFormLayout::LabelRole);
}
QTEST_MAIN(tst_QFormLayout) QTEST_MAIN(tst_QFormLayout)
#include "tst_qformlayout.moc" #include "tst_qformlayout.moc"

View File

@ -101,6 +101,7 @@ private slots:
void distributeMultiCell(); void distributeMultiCell();
void taskQTBUG_27420_takeAtShouldUnparentLayout(); void taskQTBUG_27420_takeAtShouldUnparentLayout();
void replaceWidget();
private: private:
QWidget *testWidget; QWidget *testWidget;
@ -1656,5 +1657,49 @@ void tst_QGridLayout::taskQTBUG_27420_takeAtShouldUnparentLayout()
QVERIFY(!inner.isNull()); QVERIFY(!inner.isNull());
} }
void tst_QGridLayout::replaceWidget()
{
QWidget wdg;
QGridLayout *l = new QGridLayout();
const int itemCount = 9;
QLabel *labels[itemCount];
// setup layout
for (int n = 0; n < itemCount; ++n) {
int x = n % 3;
int y = n / 3;
labels[n] = new QLabel(QString("label %1").arg(n));
Qt::Alignment align = (n % 3 ? Qt::AlignLeft : Qt::AlignRight);
l->addWidget(labels[n], x * 3, y * 3, (n % 2) + 1, (n + 1) % 2 + 1, align);
}
wdg.setLayout(l);
// iterate and replace
for (int n = 0; n < itemCount; n += 2) {
int i = l->indexOf(labels[n]);
int fromRow, fromCol, fromRowSpan, fromColSpan;
l->getItemPosition(i, &fromRow, &fromCol, &fromRowSpan, &fromColSpan);
Qt::Alignment fromAlign = l->itemAt(i)->alignment();
// do replace
QPushButton *pb = new QPushButton("replaced");
QLayoutItem *olditem = l->replaceWidget(labels[n], pb);
// verify
QCOMPARE(i, l->indexOf(pb));
QVERIFY(olditem != 0);
QCOMPARE(l->indexOf(labels[n]), -1);
int toRow, toCol, toRowSpan, toColSpan;
l->getItemPosition(i, &toRow, &toCol, &toRowSpan, &toColSpan);
QCOMPARE(fromRow, toRow);
QCOMPARE(fromCol, toCol);
QCOMPARE(fromRowSpan, toRowSpan);
QCOMPARE(fromColSpan, toColSpan);
Qt::Alignment toAlign = l->itemAt(i)->alignment();
QCOMPARE(fromAlign, toAlign);
// clean up
olditem->widget()->deleteLater();
delete olditem;
}
}
QTEST_MAIN(tst_QGridLayout) QTEST_MAIN(tst_QGridLayout)
#include "tst_qgridlayout.moc" #include "tst_qgridlayout.moc"

View File

@ -70,6 +70,7 @@ private slots:
void removeWidget(); void removeWidget();
void keepFocusAfterSetCurrent(); void keepFocusAfterSetCurrent();
void heigthForWidth(); void heigthForWidth();
void replaceWidget();
private: private:
QWidget *testWidget; QWidget *testWidget;
@ -393,6 +394,27 @@ void tst_QStackedLayout::heigthForWidth()
} }
void tst_QStackedLayout::replaceWidget()
{
QWidget w;
QStackedLayout *stackLayout = new QStackedLayout(&w);
QLineEdit *replaceFrom = new QLineEdit;
QLineEdit *replaceTo = new QLineEdit;
stackLayout->addWidget(new QLineEdit());
stackLayout->addWidget(replaceFrom);
stackLayout->addWidget(new QLineEdit());
stackLayout->setCurrentWidget(replaceFrom);
QCOMPARE(stackLayout->indexOf(replaceFrom), 1);
QCOMPARE(stackLayout->indexOf(replaceTo), -1);
stackLayout->replaceWidget(replaceFrom, replaceTo);
QCOMPARE(stackLayout->indexOf(replaceFrom), -1);
QCOMPARE(stackLayout->indexOf(replaceTo), 1);
QCOMPARE(stackLayout->currentWidget(), replaceTo);
}
QTEST_MAIN(tst_QStackedLayout) QTEST_MAIN(tst_QStackedLayout)
#include "tst_qstackedlayout.moc" #include "tst_qstackedlayout.moc"

View File

@ -1,2 +1,2 @@
TEMPLATE = subdirs TEMPLATE = subdirs
SUBDIRS = qtooltip sizeonhide SUBDIRS = qtooltip sizeonhide layoutreplace

View File

@ -0,0 +1,137 @@
/****************************************************************************
**
** Copyright (C) 2013 Thorbjørn Martsum - tmartsum[at]gmail.com
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QGridLayout>
#include <QFormLayout>
#include <QStackedLayout>
#include <QPushButton>
#include <QLabel>
#include <QApplication>
class ReplaceButton : public QPushButton
{
Q_OBJECT
public:
ReplaceButton(const QString &text = QString("click to replace")) : QPushButton(text)
{
static int v = 0;
++v;
QString txt = QString("click to replace %1").arg(v);
setText(txt);
connect(this, SIGNAL(clicked()), this, SLOT(replace()));
}
protected slots:
void replace()
{
if (!parentWidget())
return;
static int n = 0;
++n;
if (parentWidget()->layout()->replaceWidget(this, new QLabel(QString("replaced(%1)").arg(n))))
deleteLater();
}
};
class StackButtonChange : public QPushButton
{
Q_OBJECT
public:
StackButtonChange(QStackedLayout *l) : QPushButton("stack wdg change")
{
sl = l;
connect(this, SIGNAL(clicked()), this, SLOT(changeWdg()));
}
protected slots:
void changeWdg()
{
int index = sl->indexOf(sl->currentWidget());
++index;
if (index >= sl->count())
index = 0;
sl->setCurrentWidget(sl->itemAt(index)->widget());
sl->parentWidget()->update();
}
protected:
QStackedLayout *sl;
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
QWidget wdg1;
QGridLayout *l1 = new QGridLayout();
l1->addWidget(new ReplaceButton(), 1, 1, 2, 2, Qt::AlignCenter);
l1->addWidget(new ReplaceButton(), 3, 1, 1, 1, Qt::AlignRight);
l1->addWidget(new ReplaceButton(), 1, 3, 1, 1, Qt::AlignLeft);
l1->addWidget(new ReplaceButton(), 2, 3, 1, 1, Qt::AlignLeft);
l1->addWidget(new ReplaceButton(), 3, 2, 1, 1, Qt::AlignRight);
wdg1.setLayout(l1);
wdg1.setWindowTitle("QGridLayout");
wdg1.setGeometry(100, 100, 100, 100);
wdg1.show();
QWidget wdg2;
QFormLayout *l2 = new QFormLayout();
l2->addRow(QString("Label1"), new ReplaceButton());
l2->addRow(QString("Label2"), new ReplaceButton());
l2->addRow(new ReplaceButton(), new ReplaceButton());
wdg2.setLayout(l2);
wdg2.setWindowTitle("QFormLayout");
wdg2.setGeometry(100 + wdg1.sizeHint().width() + 5, 100, 100, 100);
wdg2.show();
QWidget wdg3;
QBoxLayout *l3 = new QVBoxLayout(); // new QHBoxLayout()
QStackedLayout *sl = new QStackedLayout();
sl->addWidget(new ReplaceButton());
sl->addWidget(new ReplaceButton());
sl->addWidget(new ReplaceButton());
l3->addLayout(sl);
l3->addWidget(new StackButtonChange(sl));
l3->addWidget(new ReplaceButton());
l3->addWidget(new ReplaceButton());
wdg3.setLayout(l3);
wdg3.setWindowTitle("QStackedLayout + BoxLayout");
wdg3.setGeometry(100, 100 + wdg1.sizeHint().height() + 30, 100 , 100);
wdg3.show();
app.exec();
}
#include "main.moc"

View File

@ -0,0 +1,2 @@
SOURCES = main.cpp
QT += widgets