1c6bf3e09e
This is a semantic patch using ClangTidyTransformator as in qtbase/df9d882d41b741fef7c5beeddb0abe9d904443d8, but extended to handle typedefs and accesses through pointers, too: const std::string o = "object"; auto hasTypeIgnoringPointer = [](auto type) { return anyOf(hasType(type), hasType(pointsTo(type))); }; auto derivedFromAnyOfClasses = [&](ArrayRef<StringRef> classes) { auto exprOfDeclaredType = [&](auto decl) { return expr(hasTypeIgnoringPointer(hasUnqualifiedDesugaredType(recordType(hasDeclaration(decl))))).bind(o); }; return exprOfDeclaredType(cxxRecordDecl(isSameOrDerivedFrom(hasAnyName(classes)))); }; auto renameMethod = [&] (ArrayRef<StringRef> classes, StringRef from, StringRef to) { return makeRule(cxxMemberCallExpr(on(derivedFromAnyOfClasses(classes)), callee(cxxMethodDecl(hasName(from), parameterCountIs(0)))), changeTo(cat(access(o, cat(to)), "()")), cat("use '", to, "' instead of '", from, "'")); }; renameMethod(<classes>, "count", "size"); renameMethod(<classes>, "length", "size"); except that the on() matcher has been replaced by one that doesn't ignoreParens(). a.k.a qt-port-to-std-compatible-api V5 with config Scope: 'Container'. Added two NOLINTNEXTLINEs in tst_qbitarray and tst_qcontiguouscache, to avoid porting calls that explicitly test count(). Change-Id: Icfb8808c2ff4a30187e9935a51cad26987451c22 Reviewed-by: Ivan Solovev <ivan.solovev@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
459 lines
14 KiB
C++
459 lines
14 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include <QComboBox>
|
|
#include <QDataWidgetMapper>
|
|
#include <QLineEdit>
|
|
#include <QMetaType>
|
|
#include <QStandardItemModel>
|
|
#include <QSignalSpy>
|
|
#include <QTest>
|
|
#include <QTextEdit>
|
|
#include <QVBoxLayout>
|
|
|
|
#include <QtWidgets/private/qapplication_p.h>
|
|
|
|
class tst_QDataWidgetMapper: public QObject
|
|
{
|
|
Q_OBJECT
|
|
private slots:
|
|
void initTestCase();
|
|
|
|
void setModel();
|
|
void navigate();
|
|
void addMapping();
|
|
void currentIndexChanged();
|
|
void changingValues();
|
|
void setData();
|
|
void mappedWidgetAt();
|
|
|
|
void comboBox();
|
|
|
|
void textEditDoesntChangeFocusOnTab_qtbug3305();
|
|
};
|
|
|
|
Q_DECLARE_METATYPE(QAbstractItemDelegate::EndEditHint)
|
|
|
|
static QStandardItemModel *testModel(QObject *parent)
|
|
{
|
|
QStandardItemModel *model = new QStandardItemModel(10, 10, parent);
|
|
|
|
for (int row = 0; row < 10; ++row) {
|
|
const QString prefix = QLatin1String("item ") + QString::number(row)
|
|
+ QLatin1Char(' ');
|
|
for (int col = 0; col < 10; ++col)
|
|
model->setData(model->index(row, col), prefix + QString::number(col));
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::initTestCase()
|
|
{
|
|
qRegisterMetaType<QAbstractItemDelegate::EndEditHint>();
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::setModel()
|
|
{
|
|
QDataWidgetMapper mapper;
|
|
|
|
QCOMPARE(mapper.model(), nullptr);
|
|
|
|
{ // let the model go out of scope firstma
|
|
QStandardItemModel model;
|
|
mapper.setModel(&model);
|
|
QCOMPARE(mapper.model(), &model);
|
|
}
|
|
|
|
QCOMPARE(mapper.model(), nullptr);
|
|
|
|
{ // let the mapper go out of scope first
|
|
QStandardItemModel model2;
|
|
QDataWidgetMapper mapper2;
|
|
mapper2.setModel(&model2);
|
|
}
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::navigate()
|
|
{
|
|
QDataWidgetMapper mapper;
|
|
QAbstractItemModel *model = testModel(&mapper);
|
|
mapper.setModel(model);
|
|
|
|
QLineEdit edit1;
|
|
QLineEdit edit2;
|
|
QLineEdit edit3;
|
|
|
|
mapper.addMapping(&edit1, 0);
|
|
mapper.toFirst();
|
|
mapper.addMapping(&edit2, 1);
|
|
mapper.addMapping(&edit3, 2);
|
|
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
QVERIFY(edit2.text().isEmpty());
|
|
QVERIFY(edit3.text().isEmpty());
|
|
QVERIFY(mapper.submit());
|
|
edit2.setText(QString("item 0 1"));
|
|
edit3.setText(QString("item 0 2"));
|
|
QVERIFY(mapper.submit());
|
|
|
|
mapper.toFirst(); //this will repopulate
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
QCOMPARE(edit2.text(), QString("item 0 1"));
|
|
QCOMPARE(edit3.text(), QString("item 0 2"));
|
|
|
|
|
|
mapper.toFirst();
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
QCOMPARE(edit2.text(), QString("item 0 1"));
|
|
QCOMPARE(edit3.text(), QString("item 0 2"));
|
|
|
|
mapper.toPrevious(); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
QCOMPARE(edit2.text(), QString("item 0 1"));
|
|
QCOMPARE(edit3.text(), QString("item 0 2"));
|
|
|
|
mapper.toNext();
|
|
QCOMPARE(edit1.text(), QString("item 1 0"));
|
|
QCOMPARE(edit2.text(), QString("item 1 1"));
|
|
QCOMPARE(edit3.text(), QString("item 1 2"));
|
|
|
|
mapper.toLast();
|
|
QCOMPARE(edit1.text(), QString("item 9 0"));
|
|
QCOMPARE(edit2.text(), QString("item 9 1"));
|
|
QCOMPARE(edit3.text(), QString("item 9 2"));
|
|
|
|
mapper.toNext(); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 9 0"));
|
|
QCOMPARE(edit2.text(), QString("item 9 1"));
|
|
QCOMPARE(edit3.text(), QString("item 9 2"));
|
|
|
|
mapper.setCurrentIndex(4);
|
|
QCOMPARE(edit1.text(), QString("item 4 0"));
|
|
QCOMPARE(edit2.text(), QString("item 4 1"));
|
|
QCOMPARE(edit3.text(), QString("item 4 2"));
|
|
|
|
mapper.setCurrentIndex(-1); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 4 0"));
|
|
QCOMPARE(edit2.text(), QString("item 4 1"));
|
|
QCOMPARE(edit3.text(), QString("item 4 2"));
|
|
|
|
mapper.setCurrentIndex(10); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 4 0"));
|
|
QCOMPARE(edit2.text(), QString("item 4 1"));
|
|
QCOMPARE(edit3.text(), QString("item 4 2"));
|
|
|
|
mapper.setCurrentModelIndex(QModelIndex()); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 4 0"));
|
|
QCOMPARE(edit2.text(), QString("item 4 1"));
|
|
QCOMPARE(edit3.text(), QString("item 4 2"));
|
|
|
|
mapper.setCurrentModelIndex(model->index(6, 0));
|
|
QCOMPARE(edit1.text(), QString("item 6 0"));
|
|
QCOMPARE(edit2.text(), QString("item 6 1"));
|
|
QCOMPARE(edit3.text(), QString("item 6 2"));
|
|
|
|
/* now try vertical navigation */
|
|
|
|
mapper.setOrientation(Qt::Vertical);
|
|
|
|
mapper.addMapping(&edit1, 0);
|
|
mapper.addMapping(&edit2, 1);
|
|
mapper.addMapping(&edit3, 2);
|
|
|
|
mapper.toFirst();
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
QCOMPARE(edit2.text(), QString("item 1 0"));
|
|
QCOMPARE(edit3.text(), QString("item 2 0"));
|
|
|
|
mapper.toPrevious(); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
QCOMPARE(edit2.text(), QString("item 1 0"));
|
|
QCOMPARE(edit3.text(), QString("item 2 0"));
|
|
|
|
mapper.toNext();
|
|
QCOMPARE(edit1.text(), QString("item 0 1"));
|
|
QCOMPARE(edit2.text(), QString("item 1 1"));
|
|
QCOMPARE(edit3.text(), QString("item 2 1"));
|
|
|
|
mapper.toLast();
|
|
QCOMPARE(edit1.text(), QString("item 0 9"));
|
|
QCOMPARE(edit2.text(), QString("item 1 9"));
|
|
QCOMPARE(edit3.text(), QString("item 2 9"));
|
|
|
|
mapper.toNext(); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 0 9"));
|
|
QCOMPARE(edit2.text(), QString("item 1 9"));
|
|
QCOMPARE(edit3.text(), QString("item 2 9"));
|
|
|
|
mapper.setCurrentIndex(4);
|
|
QCOMPARE(edit1.text(), QString("item 0 4"));
|
|
QCOMPARE(edit2.text(), QString("item 1 4"));
|
|
QCOMPARE(edit3.text(), QString("item 2 4"));
|
|
|
|
mapper.setCurrentIndex(-1); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 0 4"));
|
|
QCOMPARE(edit2.text(), QString("item 1 4"));
|
|
QCOMPARE(edit3.text(), QString("item 2 4"));
|
|
|
|
mapper.setCurrentIndex(10); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 0 4"));
|
|
QCOMPARE(edit2.text(), QString("item 1 4"));
|
|
QCOMPARE(edit3.text(), QString("item 2 4"));
|
|
|
|
mapper.setCurrentModelIndex(QModelIndex()); // should do nothing
|
|
QCOMPARE(edit1.text(), QString("item 0 4"));
|
|
QCOMPARE(edit2.text(), QString("item 1 4"));
|
|
QCOMPARE(edit3.text(), QString("item 2 4"));
|
|
|
|
mapper.setCurrentModelIndex(model->index(0, 6));
|
|
QCOMPARE(edit1.text(), QString("item 0 6"));
|
|
QCOMPARE(edit2.text(), QString("item 1 6"));
|
|
QCOMPARE(edit3.text(), QString("item 2 6"));
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::addMapping()
|
|
{
|
|
QDataWidgetMapper mapper;
|
|
QAbstractItemModel *model = testModel(&mapper);
|
|
mapper.setModel(model);
|
|
|
|
QLineEdit edit1;
|
|
mapper.addMapping(&edit1, 0);
|
|
mapper.toFirst();
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
|
|
mapper.addMapping(&edit1, 1);
|
|
mapper.toFirst();
|
|
QCOMPARE(edit1.text(), QString("item 0 1"));
|
|
|
|
QCOMPARE(mapper.mappedSection(&edit1), 1);
|
|
|
|
edit1.clear();
|
|
mapper.removeMapping(&edit1);
|
|
mapper.toFirst();
|
|
QCOMPARE(edit1.text(), QString());
|
|
|
|
{
|
|
QLineEdit edit2;
|
|
mapper.addMapping(&edit2, 2);
|
|
mapper.toFirst();
|
|
QCOMPARE(edit2.text(), QString("item 0 2"));
|
|
} // let the edit go out of scope
|
|
|
|
QCOMPARE(mapper.mappedWidgetAt(2), nullptr);
|
|
mapper.toLast();
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::currentIndexChanged()
|
|
{
|
|
QDataWidgetMapper mapper;
|
|
QAbstractItemModel *model = testModel(&mapper);
|
|
mapper.setModel(model);
|
|
|
|
QSignalSpy spy(&mapper, &QDataWidgetMapper::currentIndexChanged);
|
|
|
|
mapper.toFirst();
|
|
QCOMPARE(spy.size(), 1);
|
|
QCOMPARE(spy.takeFirst().at(0).toInt(), 0);
|
|
|
|
mapper.toNext();
|
|
QCOMPARE(spy.size(), 1);
|
|
QCOMPARE(spy.takeFirst().at(0).toInt(), 1);
|
|
|
|
mapper.setCurrentIndex(7);
|
|
QCOMPARE(spy.size(), 1);
|
|
QCOMPARE(spy.takeFirst().at(0).toInt(), 7);
|
|
|
|
mapper.setCurrentIndex(-1);
|
|
QCOMPARE(spy.size(), 0);
|
|
|
|
mapper.setCurrentIndex(42);
|
|
QCOMPARE(spy.size(), 0);
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::changingValues()
|
|
{
|
|
QDataWidgetMapper mapper;
|
|
QAbstractItemModel *model = testModel(&mapper);
|
|
mapper.setModel(model);
|
|
|
|
QLineEdit edit1;
|
|
mapper.addMapping(&edit1, 0);
|
|
mapper.toFirst();
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
|
|
QLineEdit edit2;
|
|
mapper.addMapping(&edit2, 0, "text");
|
|
mapper.toFirst();
|
|
QCOMPARE(edit2.text(), QString("item 0 0"));
|
|
|
|
model->setData(model->index(0, 0), QString("changed"));
|
|
QCOMPARE(edit1.text(), QString("changed"));
|
|
QCOMPARE(edit2.text(), QString("changed"));
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::setData()
|
|
{
|
|
QDataWidgetMapper mapper;
|
|
QAbstractItemModel *model = testModel(&mapper);
|
|
mapper.setModel(model);
|
|
|
|
QLineEdit edit1;
|
|
QLineEdit edit2;
|
|
QLineEdit edit3;
|
|
|
|
mapper.addMapping(&edit1, 0);
|
|
mapper.addMapping(&edit2, 1);
|
|
mapper.addMapping(&edit3, 0, "text");
|
|
mapper.toFirst();
|
|
QCOMPARE(edit1.text(), QString("item 0 0"));
|
|
QCOMPARE(edit2.text(), QString("item 0 1"));
|
|
QCOMPARE(edit3.text(), QString("item 0 0"));
|
|
|
|
edit1.setText("new text");
|
|
|
|
mapper.submit();
|
|
QCOMPARE(model->data(model->index(0, 0)).toString(), QString("new text"));
|
|
|
|
edit3.setText("more text");
|
|
|
|
mapper.submit();
|
|
QCOMPARE(model->data(model->index(0, 0)).toString(), QString("more text"));
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::comboBox()
|
|
{
|
|
QDataWidgetMapper mapper;
|
|
QAbstractItemModel *model = testModel(&mapper);
|
|
mapper.setModel(model);
|
|
mapper.setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
|
|
|
|
QComboBox readOnlyBox;
|
|
readOnlyBox.setEditable(false);
|
|
readOnlyBox.addItem("read only item 0");
|
|
readOnlyBox.addItem("read only item 1");
|
|
readOnlyBox.addItem("read only item 2");
|
|
|
|
QComboBox readWriteBox;
|
|
readWriteBox.setEditable(true);
|
|
readWriteBox.addItem("read write item 0");
|
|
readWriteBox.addItem("read write item 1");
|
|
readWriteBox.addItem("read write item 2");
|
|
|
|
// populate the combo boxes with data
|
|
mapper.addMapping(&readOnlyBox, 0, "currentIndex");
|
|
mapper.addMapping(&readWriteBox, 1, "currentText");
|
|
mapper.toFirst();
|
|
|
|
// setCurrentIndex caused the value at index 0 to be displayed
|
|
QCOMPARE(readOnlyBox.currentText(), QString("read only item 0"));
|
|
// setCurrentText set the value in the line edit since the combobox is editable
|
|
QCOMPARE(readWriteBox.currentText(), QString("item 0 1"));
|
|
|
|
// set some new values on the boxes
|
|
readOnlyBox.setCurrentIndex(1);
|
|
readWriteBox.setEditText("read write item y");
|
|
|
|
mapper.submit();
|
|
|
|
// make sure the new values are in the model
|
|
QCOMPARE(model->data(model->index(0, 0)).toInt(), 1);
|
|
QCOMPARE(model->data(model->index(0, 1)).toString(), QString("read write item y"));
|
|
|
|
// now test updating of the widgets
|
|
model->setData(model->index(0, 0), 2, Qt::EditRole);
|
|
model->setData(model->index(0, 1), QString("read write item z"), Qt::EditRole);
|
|
|
|
QCOMPARE(readOnlyBox.currentIndex(), 2);
|
|
QCOMPARE(readWriteBox.currentText(), QString("read write item z"));
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::mappedWidgetAt()
|
|
{
|
|
QDataWidgetMapper mapper;
|
|
QAbstractItemModel *model = testModel(&mapper);
|
|
mapper.setModel(model);
|
|
|
|
QLineEdit lineEdit1;
|
|
QLineEdit lineEdit2;
|
|
|
|
QCOMPARE(mapper.mappedWidgetAt(432312), nullptr);
|
|
|
|
mapper.addMapping(&lineEdit1, 1);
|
|
mapper.addMapping(&lineEdit2, 2);
|
|
|
|
QCOMPARE(mapper.mappedWidgetAt(1), &lineEdit1);
|
|
QCOMPARE(mapper.mappedWidgetAt(2), &lineEdit2);
|
|
|
|
mapper.addMapping(&lineEdit2, 4242);
|
|
|
|
QCOMPARE(mapper.mappedWidgetAt(2), nullptr);
|
|
QCOMPARE(mapper.mappedWidgetAt(4242), &lineEdit2);
|
|
}
|
|
|
|
void tst_QDataWidgetMapper::textEditDoesntChangeFocusOnTab_qtbug3305()
|
|
{
|
|
if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
|
|
QSKIP("Wayland: This fails. Figure out why.");
|
|
|
|
QDataWidgetMapper mapper;
|
|
QAbstractItemModel *model = testModel(&mapper);
|
|
mapper.setModel(model);
|
|
|
|
QSignalSpy closeEditorSpy(mapper.itemDelegate(),
|
|
&QAbstractItemDelegate::closeEditor);
|
|
QVERIFY(closeEditorSpy.isValid());
|
|
|
|
QWidget container;
|
|
container.setLayout(new QVBoxLayout);
|
|
|
|
QLineEdit *lineEdit = new QLineEdit;
|
|
mapper.addMapping(lineEdit, 0);
|
|
container.layout()->addWidget(lineEdit);
|
|
|
|
QTextEdit *textEdit = new QTextEdit;
|
|
mapper.addMapping(textEdit, 1);
|
|
container.layout()->addWidget(textEdit);
|
|
|
|
lineEdit->setFocus();
|
|
|
|
container.show();
|
|
|
|
QApplicationPrivate::setActiveWindow(&container);
|
|
QVERIFY(QTest::qWaitForWindowActive(&container));
|
|
|
|
int closeEditorSpyCount = 0;
|
|
const QString textEditContents = textEdit->toPlainText();
|
|
|
|
QCOMPARE(closeEditorSpy.size(), closeEditorSpyCount);
|
|
QVERIFY(lineEdit->hasFocus());
|
|
QVERIFY(!textEdit->hasFocus());
|
|
|
|
// this will generate a closeEditor for the tab key, and another for the focus out
|
|
QTest::keyClick(QApplication::focusWidget(), Qt::Key_Tab);
|
|
closeEditorSpyCount += 2;
|
|
QTRY_COMPARE(closeEditorSpy.size(), closeEditorSpyCount);
|
|
|
|
QTRY_VERIFY(textEdit->hasFocus());
|
|
QVERIFY(!lineEdit->hasFocus());
|
|
|
|
// now that the text edit is focused, a tab keypress will insert a tab, not change focus
|
|
QTest::keyClick(QApplication::focusWidget(), Qt::Key_Tab);
|
|
QTRY_COMPARE(closeEditorSpy.size(), closeEditorSpyCount);
|
|
|
|
QVERIFY(!lineEdit->hasFocus());
|
|
QVERIFY(textEdit->hasFocus());
|
|
QCOMPARE(textEdit->toPlainText(), QLatin1Char('\t') + textEditContents);
|
|
|
|
// now give focus back to the line edit and check closeEditor gets emitted
|
|
lineEdit->setFocus();
|
|
QTRY_VERIFY(lineEdit->hasFocus());
|
|
QVERIFY(!textEdit->hasFocus());
|
|
++closeEditorSpyCount;
|
|
QCOMPARE(closeEditorSpy.size(), closeEditorSpyCount);
|
|
}
|
|
|
|
QTEST_MAIN(tst_QDataWidgetMapper)
|
|
#include "tst_qdatawidgetmapper.moc"
|