Fixed applying invalid data from QLineEdit to model

Task-number: QTBUG-2216

Change-Id: I5d1c18aadb380667dedc2bb9f9b7e3dd8178a24c
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Stephen Kelly <stephen.kelly@kdab.com>
Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
Ivan Komissarov 2012-04-14 12:14:22 +04:00 committed by The Qt Project
parent 9953393593
commit 0866e48ae2
3 changed files with 199 additions and 20 deletions

View File

@ -113,6 +113,7 @@ public:
static QString valueToText(const QVariant &value, const QStyleOptionViewItem &option);
bool tryFixup(QWidget *editor);
void _q_commitDataAndCloseEditor(QWidget *editor);
QItemEditorFactory *f;
@ -386,6 +387,24 @@ QString QItemDelegatePrivate::valueToText(const QVariant &value, const QStyleOpt
return text;
}
bool QItemDelegatePrivate::tryFixup(QWidget *editor)
{
#ifndef QT_NO_LINEEDIT
if (QLineEdit *e = qobject_cast<QLineEdit*>(editor)) {
if (!e->hasAcceptableInput()) {
if (const QValidator *validator = e->validator()) {
QString text = e->text();
validator->fixup(text);
e->setText(text);
}
return e->hasAcceptableInput();
}
}
#endif // QT_NO_LINEEDIT
return true;
}
/*!
Renders the delegate using the given \a painter and style \a option for
the item specified by \a index.
@ -1154,18 +1173,24 @@ QRect QItemDelegate::textRectangle(QPainter * /*painter*/, const QRect &rect,
bool QItemDelegate::eventFilter(QObject *object, QEvent *event)
{
Q_D(QItemDelegate);
QWidget *editor = qobject_cast<QWidget*>(object);
if (!editor)
return false;
if (event->type() == QEvent::KeyPress) {
switch (static_cast<QKeyEvent *>(event)->key()) {
case Qt::Key_Tab:
emit commitData(editor);
emit closeEditor(editor, QAbstractItemDelegate::EditNextItem);
if (d->tryFixup(editor)) {
emit commitData(editor);
emit closeEditor(editor, EditNextItem);
}
return true;
case Qt::Key_Backtab:
emit commitData(editor);
emit closeEditor(editor, QAbstractItemDelegate::EditPreviousItem);
if (d->tryFixup(editor)) {
emit commitData(editor);
emit closeEditor(editor, EditPreviousItem);
}
return true;
case Qt::Key_Enter:
case Qt::Key_Return:
@ -1176,11 +1201,9 @@ bool QItemDelegate::eventFilter(QObject *object, QEvent *event)
// before committing the data (e.g. so it can do
// validation/fixup of the input).
#endif // QT_NO_TEXTEDIT
#ifndef QT_NO_LINEEDIT
if (QLineEdit *e = qobject_cast<QLineEdit*>(editor))
if (!e->hasAcceptableInput())
return false;
#endif // QT_NO_LINEEDIT
if (!d->tryFixup(editor))
return true;
QMetaObject::invokeMethod(this, "_q_commitDataAndCloseEditor",
Qt::QueuedConnection, Q_ARG(QWidget*, editor));
return false;
@ -1211,8 +1234,9 @@ bool QItemDelegate::eventFilter(QObject *object, QEvent *event)
return false;
}
#endif
if (d->tryFixup(editor))
emit commitData(editor);
emit commitData(editor);
emit closeEditor(editor, NoHint);
}
} else if (event->type() == QEvent::ShortcutOverride) {

View File

@ -97,6 +97,7 @@ public:
return factory ? factory : QItemEditorFactory::defaultFactory();
}
bool tryFixup(QWidget *editor);
void _q_commitDataAndCloseEditor(QWidget *editor)
{
Q_Q(QStyledItemDelegate);
@ -106,6 +107,24 @@ public:
QItemEditorFactory *factory;
};
bool QStyledItemDelegatePrivate::tryFixup(QWidget *editor)
{
#ifndef QT_NO_LINEEDIT
if (QLineEdit *e = qobject_cast<QLineEdit*>(editor)) {
if (!e->hasAcceptableInput()) {
if (const QValidator *validator = e->validator()) {
QString text = e->text();
validator->fixup(text);
e->setText(text);
}
return e->hasAcceptableInput();
}
}
#endif // QT_NO_LINEEDIT
return true;
}
/*!
\class QStyledItemDelegate
@ -622,18 +641,24 @@ void QStyledItemDelegate::setItemEditorFactory(QItemEditorFactory *factory)
*/
bool QStyledItemDelegate::eventFilter(QObject *object, QEvent *event)
{
Q_D(QStyledItemDelegate);
QWidget *editor = qobject_cast<QWidget*>(object);
if (!editor)
return false;
if (event->type() == QEvent::KeyPress) {
switch (static_cast<QKeyEvent *>(event)->key()) {
case Qt::Key_Tab:
emit commitData(editor);
emit closeEditor(editor, QAbstractItemDelegate::EditNextItem);
if (d->tryFixup(editor)) {
emit commitData(editor);
emit closeEditor(editor, EditNextItem);
}
return true;
case Qt::Key_Backtab:
emit commitData(editor);
emit closeEditor(editor, QAbstractItemDelegate::EditPreviousItem);
if (d->tryFixup(editor)) {
emit commitData(editor);
emit closeEditor(editor, EditPreviousItem);
}
return true;
case Qt::Key_Enter:
case Qt::Key_Return:
@ -644,11 +669,9 @@ bool QStyledItemDelegate::eventFilter(QObject *object, QEvent *event)
// before committing the data (e.g. so it can do
// validation/fixup of the input).
#endif // QT_NO_TEXTEDIT
#ifndef QT_NO_LINEEDIT
if (QLineEdit *e = qobject_cast<QLineEdit*>(editor))
if (!e->hasAcceptableInput())
return false;
#endif // QT_NO_LINEEDIT
if (!d->tryFixup(editor))
return true;
QMetaObject::invokeMethod(this, "_q_commitDataAndCloseEditor",
Qt::QueuedConnection, Q_ARG(QWidget*, editor));
return false;
@ -679,8 +702,9 @@ bool QStyledItemDelegate::eventFilter(QObject *object, QEvent *event)
return false;
}
#endif
if (d->tryFixup(editor))
emit commitData(editor);
emit commitData(editor);
emit closeEditor(editor, NoHint);
}
} else if (event->type() == QEvent::ShortcutOverride) {

View File

@ -232,6 +232,8 @@ private slots:
void enterKey_data();
void enterKey();
void comboBox();
void testLineEditValidation_data();
void testLineEditValidation();
void task257859_finalizeEdit();
void QTBUG4435_keepSelectionOnCheck();
@ -1413,6 +1415,135 @@ void tst_QItemDelegate::comboBox()
QCOMPARE(data.toBool(), false);
}
void tst_QItemDelegate::testLineEditValidation_data()
{
QTest::addColumn<int>("key");
QTest::newRow("enter") << int(Qt::Key_Enter);
QTest::newRow("return") << int(Qt::Key_Return);
QTest::newRow("tab") << int(Qt::Key_Tab);
QTest::newRow("backtab") << int(Qt::Key_Backtab);
QTest::newRow("escape") << int(Qt::Key_Escape);
}
void tst_QItemDelegate::testLineEditValidation()
{
QFETCH(int, key);
struct TestDelegate : public QItemDelegate
{
virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
QLineEdit *editor = new QLineEdit(parent);
QRegularExpression re("\\w+,\\w+"); // two words separated by a comma
editor->setValidator(new QRegularExpressionValidator(re, editor));
editor->setObjectName(QStringLiteral("TheEditor"));
return editor;
}
} delegate;
QStandardItemModel model;
// need a couple of dummy items to test tab and back tab
model.appendRow(new QStandardItem(QStringLiteral("dummy")));
QStandardItem *item = new QStandardItem(QStringLiteral("abc,def"));
model.appendRow(item);
model.appendRow(new QStandardItem(QStringLiteral("dummy")));
QListView view;
view.setModel(&model);
view.setItemDelegate(&delegate);
view.show();
view.setFocus();
QApplication::setActiveWindow(&view);
QVERIFY(QTest::qWaitForWindowActive(&view));
QList<QLineEdit *> lineEditors;
QPointer<QLineEdit> editor;
QPersistentModelIndex index = model.indexFromItem(item);
view.setCurrentIndex(index);
view.edit(index);
QTest::qWait(30);
lineEditors = view.findChildren<QLineEdit *>(QStringLiteral("TheEditor"));
QCOMPARE(lineEditors.count(), 1);
editor = lineEditors.at(0);
editor->clear();
// first try to set a valid text
QTest::keyClicks(editor, QStringLiteral("foo,bar"));
QTest::qWait(30);
// close the editor
QTest::keyClick(editor, Qt::Key(key));
QTest::qWait(30);
QVERIFY(editor.isNull());
if (key != Qt::Key_Escape)
QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("foo,bar"));
else
QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("abc,def"));
// now an invalid (but partially matching) text
view.setCurrentIndex(index);
view.edit(index);
QTest::qWait(30);
lineEditors = view.findChildren<QLineEdit *>(QStringLiteral("TheEditor"));
QCOMPARE(lineEditors.count(), 1);
editor = lineEditors.at(0);
editor->clear();
// edit
QTest::keyClicks(editor, QStringLiteral("foobar"));
QTest::qWait(30);
// try to close the editor
QTest::keyClick(editor, Qt::Key(key));
QTest::qWait(30);
if (key != Qt::Key_Escape) {
QVERIFY(!editor.isNull());
QCOMPARE(qApp->focusWidget(), editor.data());
QCOMPARE(editor->text(), QStringLiteral("foobar"));
QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("foo,bar"));
} else {
QVERIFY(editor.isNull());
QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("abc,def"));
}
// reset the view to forcibly close the editor
view.reset();
QTest::qWait(30);
// set a valid text again
view.setCurrentIndex(index);
view.edit(index);
QTest::qWait(30);
lineEditors = view.findChildren<QLineEdit *>(QStringLiteral("TheEditor"));
QCOMPARE(lineEditors.count(), 1);
editor = lineEditors.at(0);
editor->clear();
// set a valid text
QTest::keyClicks(editor, QStringLiteral("gender,bender"));
QTest::qWait(30);
// close the editor
QTest::keyClick(editor, Qt::Key(key));
QTest::qWait(30);
QVERIFY(editor.isNull());
if (key != Qt::Key_Escape)
QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("gender,bender"));
else
QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("abc,def"));
}
// ### _not_ covered: