1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QComboBox>
30#include <QDataWidgetMapper>
31#include <QLineEdit>
32#include <QMetaType>
33#include <QStandardItemModel>
34#include <QSignalSpy>
35#include <QTest>
36#include <QTextEdit>
37#include <QVBoxLayout>
38
39class tst_QDataWidgetMapper: public QObject
40{
41 Q_OBJECT
42private slots:
43 void initTestCase();
44
45 void setModel();
46 void navigate();
47 void addMapping();
48 void currentIndexChanged();
49 void changingValues();
50 void setData();
51 void mappedWidgetAt();
52
53 void comboBox();
54
55 void textEditDoesntChangeFocusOnTab_qtbug3305();
56};
57
58Q_DECLARE_METATYPE(QAbstractItemDelegate::EndEditHint)
59
60static QStandardItemModel *testModel(QObject *parent)
61{
62 QStandardItemModel *model = new QStandardItemModel(10, 10, parent);
63
64 for (int row = 0; row < 10; ++row) {
65 const QString prefix = QLatin1String("item ") + QString::number(row)
66 + QLatin1Char(' ');
67 for (int col = 0; col < 10; ++col)
68 model->setData(index: model->index(row, column: col), value: prefix + QString::number(col));
69 }
70
71 return model;
72}
73
74void tst_QDataWidgetMapper::initTestCase()
75{
76 qRegisterMetaType<QAbstractItemDelegate::EndEditHint>();
77}
78
79void tst_QDataWidgetMapper::setModel()
80{
81 QDataWidgetMapper mapper;
82
83 QCOMPARE(mapper.model(), nullptr);
84
85 { // let the model go out of scope firstma
86 QStandardItemModel model;
87 mapper.setModel(&model);
88 QCOMPARE(mapper.model(), &model);
89 }
90
91 QCOMPARE(mapper.model(), nullptr);
92
93 { // let the mapper go out of scope first
94 QStandardItemModel model2;
95 QDataWidgetMapper mapper2;
96 mapper2.setModel(&model2);
97 }
98}
99
100void tst_QDataWidgetMapper::navigate()
101{
102 QDataWidgetMapper mapper;
103 QAbstractItemModel *model = testModel(parent: &mapper);
104 mapper.setModel(model);
105
106 QLineEdit edit1;
107 QLineEdit edit2;
108 QLineEdit edit3;
109
110 mapper.addMapping(widget: &edit1, section: 0);
111 mapper.toFirst();
112 mapper.addMapping(widget: &edit2, section: 1);
113 mapper.addMapping(widget: &edit3, section: 2);
114
115 QCOMPARE(edit1.text(), QString("item 0 0"));
116 QVERIFY(edit2.text().isEmpty());
117 QVERIFY(edit3.text().isEmpty());
118 QVERIFY(mapper.submit());
119 edit2.setText(QString("item 0 1"));
120 edit3.setText(QString("item 0 2"));
121 QVERIFY(mapper.submit());
122
123 mapper.toFirst(); //this will repopulate
124 QCOMPARE(edit1.text(), QString("item 0 0"));
125 QCOMPARE(edit2.text(), QString("item 0 1"));
126 QCOMPARE(edit3.text(), QString("item 0 2"));
127
128
129 mapper.toFirst();
130 QCOMPARE(edit1.text(), QString("item 0 0"));
131 QCOMPARE(edit2.text(), QString("item 0 1"));
132 QCOMPARE(edit3.text(), QString("item 0 2"));
133
134 mapper.toPrevious(); // should do nothing
135 QCOMPARE(edit1.text(), QString("item 0 0"));
136 QCOMPARE(edit2.text(), QString("item 0 1"));
137 QCOMPARE(edit3.text(), QString("item 0 2"));
138
139 mapper.toNext();
140 QCOMPARE(edit1.text(), QString("item 1 0"));
141 QCOMPARE(edit2.text(), QString("item 1 1"));
142 QCOMPARE(edit3.text(), QString("item 1 2"));
143
144 mapper.toLast();
145 QCOMPARE(edit1.text(), QString("item 9 0"));
146 QCOMPARE(edit2.text(), QString("item 9 1"));
147 QCOMPARE(edit3.text(), QString("item 9 2"));
148
149 mapper.toNext(); // should do nothing
150 QCOMPARE(edit1.text(), QString("item 9 0"));
151 QCOMPARE(edit2.text(), QString("item 9 1"));
152 QCOMPARE(edit3.text(), QString("item 9 2"));
153
154 mapper.setCurrentIndex(4);
155 QCOMPARE(edit1.text(), QString("item 4 0"));
156 QCOMPARE(edit2.text(), QString("item 4 1"));
157 QCOMPARE(edit3.text(), QString("item 4 2"));
158
159 mapper.setCurrentIndex(-1); // should do nothing
160 QCOMPARE(edit1.text(), QString("item 4 0"));
161 QCOMPARE(edit2.text(), QString("item 4 1"));
162 QCOMPARE(edit3.text(), QString("item 4 2"));
163
164 mapper.setCurrentIndex(10); // should do nothing
165 QCOMPARE(edit1.text(), QString("item 4 0"));
166 QCOMPARE(edit2.text(), QString("item 4 1"));
167 QCOMPARE(edit3.text(), QString("item 4 2"));
168
169 mapper.setCurrentModelIndex(QModelIndex()); // should do nothing
170 QCOMPARE(edit1.text(), QString("item 4 0"));
171 QCOMPARE(edit2.text(), QString("item 4 1"));
172 QCOMPARE(edit3.text(), QString("item 4 2"));
173
174 mapper.setCurrentModelIndex(model->index(row: 6, column: 0));
175 QCOMPARE(edit1.text(), QString("item 6 0"));
176 QCOMPARE(edit2.text(), QString("item 6 1"));
177 QCOMPARE(edit3.text(), QString("item 6 2"));
178
179 /* now try vertical navigation */
180
181 mapper.setOrientation(Qt::Vertical);
182
183 mapper.addMapping(widget: &edit1, section: 0);
184 mapper.addMapping(widget: &edit2, section: 1);
185 mapper.addMapping(widget: &edit3, section: 2);
186
187 mapper.toFirst();
188 QCOMPARE(edit1.text(), QString("item 0 0"));
189 QCOMPARE(edit2.text(), QString("item 1 0"));
190 QCOMPARE(edit3.text(), QString("item 2 0"));
191
192 mapper.toPrevious(); // should do nothing
193 QCOMPARE(edit1.text(), QString("item 0 0"));
194 QCOMPARE(edit2.text(), QString("item 1 0"));
195 QCOMPARE(edit3.text(), QString("item 2 0"));
196
197 mapper.toNext();
198 QCOMPARE(edit1.text(), QString("item 0 1"));
199 QCOMPARE(edit2.text(), QString("item 1 1"));
200 QCOMPARE(edit3.text(), QString("item 2 1"));
201
202 mapper.toLast();
203 QCOMPARE(edit1.text(), QString("item 0 9"));
204 QCOMPARE(edit2.text(), QString("item 1 9"));
205 QCOMPARE(edit3.text(), QString("item 2 9"));
206
207 mapper.toNext(); // should do nothing
208 QCOMPARE(edit1.text(), QString("item 0 9"));
209 QCOMPARE(edit2.text(), QString("item 1 9"));
210 QCOMPARE(edit3.text(), QString("item 2 9"));
211
212 mapper.setCurrentIndex(4);
213 QCOMPARE(edit1.text(), QString("item 0 4"));
214 QCOMPARE(edit2.text(), QString("item 1 4"));
215 QCOMPARE(edit3.text(), QString("item 2 4"));
216
217 mapper.setCurrentIndex(-1); // should do nothing
218 QCOMPARE(edit1.text(), QString("item 0 4"));
219 QCOMPARE(edit2.text(), QString("item 1 4"));
220 QCOMPARE(edit3.text(), QString("item 2 4"));
221
222 mapper.setCurrentIndex(10); // should do nothing
223 QCOMPARE(edit1.text(), QString("item 0 4"));
224 QCOMPARE(edit2.text(), QString("item 1 4"));
225 QCOMPARE(edit3.text(), QString("item 2 4"));
226
227 mapper.setCurrentModelIndex(QModelIndex()); // should do nothing
228 QCOMPARE(edit1.text(), QString("item 0 4"));
229 QCOMPARE(edit2.text(), QString("item 1 4"));
230 QCOMPARE(edit3.text(), QString("item 2 4"));
231
232 mapper.setCurrentModelIndex(model->index(row: 0, column: 6));
233 QCOMPARE(edit1.text(), QString("item 0 6"));
234 QCOMPARE(edit2.text(), QString("item 1 6"));
235 QCOMPARE(edit3.text(), QString("item 2 6"));
236}
237
238void tst_QDataWidgetMapper::addMapping()
239{
240 QDataWidgetMapper mapper;
241 QAbstractItemModel *model = testModel(parent: &mapper);
242 mapper.setModel(model);
243
244 QLineEdit edit1;
245 mapper.addMapping(widget: &edit1, section: 0);
246 mapper.toFirst();
247 QCOMPARE(edit1.text(), QString("item 0 0"));
248
249 mapper.addMapping(widget: &edit1, section: 1);
250 mapper.toFirst();
251 QCOMPARE(edit1.text(), QString("item 0 1"));
252
253 QCOMPARE(mapper.mappedSection(&edit1), 1);
254
255 edit1.clear();
256 mapper.removeMapping(widget: &edit1);
257 mapper.toFirst();
258 QCOMPARE(edit1.text(), QString());
259
260 {
261 QLineEdit edit2;
262 mapper.addMapping(widget: &edit2, section: 2);
263 mapper.toFirst();
264 QCOMPARE(edit2.text(), QString("item 0 2"));
265 } // let the edit go out of scope
266
267 QCOMPARE(mapper.mappedWidgetAt(2), nullptr);
268 mapper.toLast();
269}
270
271void tst_QDataWidgetMapper::currentIndexChanged()
272{
273 QDataWidgetMapper mapper;
274 QAbstractItemModel *model = testModel(parent: &mapper);
275 mapper.setModel(model);
276
277 QSignalSpy spy(&mapper, &QDataWidgetMapper::currentIndexChanged);
278
279 mapper.toFirst();
280 QCOMPARE(spy.count(), 1);
281 QCOMPARE(spy.takeFirst().at(0).toInt(), 0);
282
283 mapper.toNext();
284 QCOMPARE(spy.count(), 1);
285 QCOMPARE(spy.takeFirst().at(0).toInt(), 1);
286
287 mapper.setCurrentIndex(7);
288 QCOMPARE(spy.count(), 1);
289 QCOMPARE(spy.takeFirst().at(0).toInt(), 7);
290
291 mapper.setCurrentIndex(-1);
292 QCOMPARE(spy.count(), 0);
293
294 mapper.setCurrentIndex(42);
295 QCOMPARE(spy.count(), 0);
296}
297
298void tst_QDataWidgetMapper::changingValues()
299{
300 QDataWidgetMapper mapper;
301 QAbstractItemModel *model = testModel(parent: &mapper);
302 mapper.setModel(model);
303
304 QLineEdit edit1;
305 mapper.addMapping(widget: &edit1, section: 0);
306 mapper.toFirst();
307 QCOMPARE(edit1.text(), QString("item 0 0"));
308
309 QLineEdit edit2;
310 mapper.addMapping(widget: &edit2, section: 0, propertyName: "text");
311 mapper.toFirst();
312 QCOMPARE(edit2.text(), QString("item 0 0"));
313
314 model->setData(index: model->index(row: 0, column: 0), value: QString("changed"));
315 QCOMPARE(edit1.text(), QString("changed"));
316 QCOMPARE(edit2.text(), QString("changed"));
317}
318
319void tst_QDataWidgetMapper::setData()
320{
321 QDataWidgetMapper mapper;
322 QAbstractItemModel *model = testModel(parent: &mapper);
323 mapper.setModel(model);
324
325 QLineEdit edit1;
326 QLineEdit edit2;
327 QLineEdit edit3;
328
329 mapper.addMapping(widget: &edit1, section: 0);
330 mapper.addMapping(widget: &edit2, section: 1);
331 mapper.addMapping(widget: &edit3, section: 0, propertyName: "text");
332 mapper.toFirst();
333 QCOMPARE(edit1.text(), QString("item 0 0"));
334 QCOMPARE(edit2.text(), QString("item 0 1"));
335 QCOMPARE(edit3.text(), QString("item 0 0"));
336
337 edit1.setText("new text");
338
339 mapper.submit();
340 QCOMPARE(model->data(model->index(0, 0)).toString(), QString("new text"));
341
342 edit3.setText("more text");
343
344 mapper.submit();
345 QCOMPARE(model->data(model->index(0, 0)).toString(), QString("more text"));
346}
347
348void tst_QDataWidgetMapper::comboBox()
349{
350 QDataWidgetMapper mapper;
351 QAbstractItemModel *model = testModel(parent: &mapper);
352 mapper.setModel(model);
353 mapper.setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
354
355 QComboBox readOnlyBox;
356 readOnlyBox.setEditable(false);
357 readOnlyBox.addItem(atext: "read only item 0");
358 readOnlyBox.addItem(atext: "read only item 1");
359 readOnlyBox.addItem(atext: "read only item 2");
360
361 QComboBox readWriteBox;
362 readWriteBox.setEditable(true);
363 readWriteBox.addItem(atext: "read write item 0");
364 readWriteBox.addItem(atext: "read write item 1");
365 readWriteBox.addItem(atext: "read write item 2");
366
367 // populate the combo boxes with data
368 mapper.addMapping(widget: &readOnlyBox, section: 0, propertyName: "currentIndex");
369 mapper.addMapping(widget: &readWriteBox, section: 1, propertyName: "currentText");
370 mapper.toFirst();
371
372 // setCurrentIndex caused the value at index 0 to be displayed
373 QCOMPARE(readOnlyBox.currentText(), QString("read only item 0"));
374 // setCurrentText set the value in the line edit since the combobox is editable
375 QCOMPARE(readWriteBox.currentText(), QString("item 0 1"));
376
377 // set some new values on the boxes
378 readOnlyBox.setCurrentIndex(1);
379 readWriteBox.setEditText("read write item y");
380
381 mapper.submit();
382
383 // make sure the new values are in the model
384 QCOMPARE(model->data(model->index(0, 0)).toInt(), 1);
385 QCOMPARE(model->data(model->index(0, 1)).toString(), QString("read write item y"));
386
387 // now test updating of the widgets
388 model->setData(index: model->index(row: 0, column: 0), value: 2, role: Qt::EditRole);
389 model->setData(index: model->index(row: 0, column: 1), value: QString("read write item z"), role: Qt::EditRole);
390
391 QCOMPARE(readOnlyBox.currentIndex(), 2);
392 QCOMPARE(readWriteBox.currentText(), QString("read write item z"));
393}
394
395void tst_QDataWidgetMapper::mappedWidgetAt()
396{
397 QDataWidgetMapper mapper;
398 QAbstractItemModel *model = testModel(parent: &mapper);
399 mapper.setModel(model);
400
401 QLineEdit lineEdit1;
402 QLineEdit lineEdit2;
403
404 QCOMPARE(mapper.mappedWidgetAt(432312), nullptr);
405
406 mapper.addMapping(widget: &lineEdit1, section: 1);
407 mapper.addMapping(widget: &lineEdit2, section: 2);
408
409 QCOMPARE(mapper.mappedWidgetAt(1), &lineEdit1);
410 QCOMPARE(mapper.mappedWidgetAt(2), &lineEdit2);
411
412 mapper.addMapping(widget: &lineEdit2, section: 4242);
413
414 QCOMPARE(mapper.mappedWidgetAt(2), nullptr);
415 QCOMPARE(mapper.mappedWidgetAt(4242), &lineEdit2);
416}
417
418void tst_QDataWidgetMapper::textEditDoesntChangeFocusOnTab_qtbug3305()
419{
420 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
421 QSKIP("Wayland: This fails. Figure out why.");
422
423 QDataWidgetMapper mapper;
424 QAbstractItemModel *model = testModel(parent: &mapper);
425 mapper.setModel(model);
426
427 QSignalSpy closeEditorSpy(mapper.itemDelegate(),
428 &QAbstractItemDelegate::closeEditor);
429 QVERIFY(closeEditorSpy.isValid());
430
431 QWidget container;
432 container.setLayout(new QVBoxLayout);
433
434 QLineEdit *lineEdit = new QLineEdit;
435 mapper.addMapping(widget: lineEdit, section: 0);
436 container.layout()->addWidget(w: lineEdit);
437
438 QTextEdit *textEdit = new QTextEdit;
439 mapper.addMapping(widget: textEdit, section: 1);
440 container.layout()->addWidget(w: textEdit);
441
442 lineEdit->setFocus();
443
444 container.show();
445
446 QApplication::setActiveWindow(&container);
447 QVERIFY(QTest::qWaitForWindowActive(&container));
448
449 int closeEditorSpyCount = 0;
450 const QString textEditContents = textEdit->toPlainText();
451
452 QCOMPARE(closeEditorSpy.count(), closeEditorSpyCount);
453 QVERIFY(lineEdit->hasFocus());
454 QVERIFY(!textEdit->hasFocus());
455
456 // this will generate a closeEditor for the tab key, and another for the focus out
457 QTest::keyClick(widget: QApplication::focusWidget(), key: Qt::Key_Tab);
458 closeEditorSpyCount += 2;
459 QTRY_COMPARE(closeEditorSpy.count(), closeEditorSpyCount);
460
461 QTRY_VERIFY(textEdit->hasFocus());
462 QVERIFY(!lineEdit->hasFocus());
463
464 // now that the text edit is focused, a tab keypress will insert a tab, not change focus
465 QTest::keyClick(widget: QApplication::focusWidget(), key: Qt::Key_Tab);
466 QTRY_COMPARE(closeEditorSpy.count(), closeEditorSpyCount);
467
468 QVERIFY(!lineEdit->hasFocus());
469 QVERIFY(textEdit->hasFocus());
470 QCOMPARE(textEdit->toPlainText(), QLatin1Char('\t') + textEditContents);
471
472 // now give focus back to the line edit and check closeEditor gets emitted
473 lineEdit->setFocus();
474 QTRY_VERIFY(lineEdit->hasFocus());
475 QVERIFY(!textEdit->hasFocus());
476 ++closeEditorSpyCount;
477 QCOMPARE(closeEditorSpy.count(), closeEditorSpyCount);
478}
479
480QTEST_MAIN(tst_QDataWidgetMapper)
481#include "tst_qdatawidgetmapper.moc"
482

source code of qtbase/tests/auto/widgets/itemviews/qdatawidgetmapper/tst_qdatawidgetmapper.cpp