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 "../../../shared/highdpi.h"
30
31#include <QtTest/QtTest>
32
33#include <qabstractitemview.h>
34#include <qstandarditemmodel.h>
35#include <qapplication.h>
36#include <qdatetimeedit.h>
37#include <qspinbox.h>
38#include <qlistview.h>
39#include <qtableview.h>
40#include <qtreeview.h>
41#include <qheaderview.h>
42#include <qitemeditorfactory.h>
43#include <qlineedit.h>
44#include <qvalidator.h>
45#include <qtablewidget.h>
46#include <qtreewidget.h>
47
48#include <QItemDelegate>
49#include <QComboBox>
50#include <QAbstractItemDelegate>
51#include <QTextEdit>
52#include <QPlainTextEdit>
53#include <QDialog>
54
55#include <qscreen.h>
56
57#include <QtWidgets/private/qabstractitemdelegate_p.h>
58
59Q_DECLARE_METATYPE(QAbstractItemDelegate::EndEditHint)
60
61#if defined (Q_OS_WIN) && !defined(Q_OS_WINRT)
62#include <windows.h>
63#define Q_CHECK_PAINTEVENTS \
64 if (::SwitchDesktop(::GetThreadDesktop(::GetCurrentThreadId())) == 0) \
65 QSKIP("The widgets don't get the paint events");
66#else
67#define Q_CHECK_PAINTEVENTS
68#endif
69
70//Begin of class definitions
71
72class TestItemDelegate : public QItemDelegate
73{
74public:
75 TestItemDelegate(QObject *parent = 0) : QItemDelegate(parent) {}
76 ~TestItemDelegate() {}
77
78 void drawDisplay(QPainter *painter,
79 const QStyleOptionViewItem &option,
80 const QRect &rect, const QString &text) const
81 {
82 displayText = text;
83 displayFont = option.font;
84 QItemDelegate::drawDisplay(painter, option, rect, text);
85 }
86
87 void drawDecoration(QPainter *painter,
88 const QStyleOptionViewItem &option,
89 const QRect &rect, const QPixmap &pixmap) const
90 {
91 decorationPixmap = pixmap;
92 decorationRect = rect;
93 QItemDelegate::drawDecoration(painter, option, rect, pixmap);
94 }
95
96
97 inline QRect textRectangle(QPainter * painter, const QRect &rect,
98 const QFont &font, const QString &text) const
99 {
100 return QItemDelegate::textRectangle(painter, rect, font, text);
101 }
102
103 inline void doLayout(const QStyleOptionViewItem &option,
104 QRect *checkRect, QRect *pixmapRect,
105 QRect *textRect, bool hint) const
106 {
107 QItemDelegate::doLayout(option, checkRect, iconRect: pixmapRect, textRect, hint);
108 }
109
110 inline QRect rect(const QStyleOptionViewItem &option,
111 const QModelIndex &index, int role) const
112 {
113 return QItemDelegate::rect(option, index, role);
114 }
115
116 inline bool eventFilter(QObject *object, QEvent *event)
117 {
118 return QItemDelegate::eventFilter(object, event);
119 }
120
121 inline bool editorEvent(QEvent *event,
122 QAbstractItemModel *model,
123 const QStyleOptionViewItem &option,
124 const QModelIndex &index)
125 {
126 return QItemDelegate::editorEvent(event, model, option, index);
127 }
128
129 // stored values for testing
130 mutable QString displayText;
131 mutable QFont displayFont;
132 mutable QPixmap decorationPixmap;
133 mutable QRect decorationRect;
134};
135
136class TestItemModel : public QAbstractTableModel
137{
138public:
139
140 enum Roles {
141 PixmapTestRole,
142 ImageTestRole,
143 IconTestRole,
144 ColorTestRole,
145 DoubleTestRole
146 };
147
148 TestItemModel(const QSize &size) : size(size) {}
149
150 ~TestItemModel() {}
151
152 int rowCount(const QModelIndex &parent) const
153 {
154 Q_UNUSED(parent);
155 return 1;
156 }
157
158 int columnCount(const QModelIndex &parent) const
159 {
160 Q_UNUSED(parent);
161 return 1;
162 }
163
164 QVariant data(const QModelIndex& index, int role) const
165 {
166 Q_UNUSED(index);
167 static QPixmap pixmap(size);
168 static QImage image(size, QImage::Format_Mono);
169 static QIcon icon(pixmap);
170 static QColor color(Qt::green);
171
172 switch (role) {
173 case PixmapTestRole: return pixmap;
174 case ImageTestRole: return image;
175 case IconTestRole: return icon;
176 case ColorTestRole: return color;
177 case DoubleTestRole: return 10.00000001;
178 default: break;
179 }
180
181 return QVariant();
182 }
183
184private:
185 QSize size;
186};
187
188class tst_QItemDelegate : public QObject
189{
190 Q_OBJECT
191
192private slots:
193 void getSetCheck();
194 void textRectangle_data();
195 void textRectangle();
196 void sizeHint_data();
197 void sizeHint();
198 void editorKeyPress_data();
199 void editorKeyPress();
200 void doubleEditorNegativeInput();
201 void font_data();
202 void font();
203 void doLayout_data();
204 void doLayout();
205 void rect_data();
206 void rect();
207 void testEventFilter();
208 void dateTimeEditor_data();
209 void dateTimeEditor();
210 void dateAndTimeEditorTest2();
211 void uintEdit();
212 void decoration_data();
213 void decoration();
214 void editorEvent_data();
215 void editorEvent();
216 void enterKey_data();
217 void enterKey();
218 void comboBox();
219 void testLineEditValidation_data();
220 void testLineEditValidation();
221
222 void task257859_finalizeEdit();
223 void QTBUG4435_keepSelectionOnCheck();
224
225 void QTBUG16469_textForRole();
226 void dateTextForRole_data();
227 void dateTextForRole();
228
229private:
230#ifdef QT_BUILD_INTERNAL
231 struct RoleDelegate : public QItemDelegate
232 {
233 QString textForRole(Qt::ItemDataRole role, const QVariant &value, const QLocale &locale)
234 {
235 QAbstractItemDelegatePrivate *d = reinterpret_cast<QAbstractItemDelegatePrivate *>(qGetPtrHelper(ptr&: d_ptr));
236 return d->textForRole(role, value, locale);
237 }
238 };
239#endif
240
241 const int m_fuzz = int(QGuiApplication::primaryScreen()->devicePixelRatio());
242};
243
244
245//End of class definitions
246
247// Testing get/set functions
248void tst_QItemDelegate::getSetCheck()
249{
250 QItemDelegate obj1;
251
252 // QItemEditorFactory * QItemDelegate::itemEditorFactory()
253 // void QItemDelegate::setItemEditorFactory(QItemEditorFactory *)
254 QItemEditorFactory *var1 = new QItemEditorFactory;
255 obj1.setItemEditorFactory(var1);
256 QCOMPARE(var1, obj1.itemEditorFactory());
257 obj1.setItemEditorFactory((QItemEditorFactory *)0);
258 QCOMPARE((QItemEditorFactory *)0, obj1.itemEditorFactory());
259 delete var1;
260
261 QCOMPARE(obj1.hasClipping(), true);
262 obj1.setClipping(false);
263 QCOMPARE(obj1.hasClipping(), false);
264 obj1.setClipping(true);
265 QCOMPARE(obj1.hasClipping(), true);
266}
267
268void tst_QItemDelegate::textRectangle_data()
269{
270 QFont font;
271 QFontMetrics fontMetrics(font);
272 int pm = QApplication::style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin);
273 int margins = 2 * (pm + 1); // margin on each side of the text
274 int height = fontMetrics.height();
275
276 QTest::addColumn<QString>(name: "text");
277 QTest::addColumn<QRect>(name: "rect");
278 QTest::addColumn<QRect>(name: "expected");
279
280 QTest::newRow(dataTag: "empty") << QString()
281 << QRect()
282 << QRect(0, 0, margins, height);
283}
284
285void tst_QItemDelegate::textRectangle()
286{
287 QFETCH(QString, text);
288 QFETCH(QRect, rect);
289 QFETCH(QRect, expected);
290
291 QFont font;
292 TestItemDelegate delegate;
293 QRect result = delegate.textRectangle(painter: 0, rect, font, text);
294 QVERIFY2(HighDpi::fuzzyCompare(result, expected, m_fuzz),
295 HighDpi::msgRectMismatch(result, expected).constData());
296}
297
298void tst_QItemDelegate::sizeHint_data()
299{
300 QTest::addColumn<QSize>(name: "expected");
301
302 QFont font;
303 QFontMetrics fontMetrics(font);
304 //int m = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
305 QTest::newRow(dataTag: "empty")
306 << QSize(0, fontMetrics.height());
307
308}
309
310void tst_QItemDelegate::sizeHint()
311{
312 QFETCH(QSize, expected);
313
314 QModelIndex index;
315 QStyleOptionViewItem option;
316
317 TestItemDelegate delegate;
318 QSize result = delegate.sizeHint(option, index);
319 QCOMPARE(result, expected);
320}
321
322void tst_QItemDelegate::editorKeyPress_data()
323{
324 QTest::addColumn<QString>(name: "initial");
325 QTest::addColumn<QString>(name: "expected");
326
327 QTest::newRow(dataTag: "foo bar")
328 << QString("foo")
329 << QString("bar");
330}
331
332void tst_QItemDelegate::editorKeyPress()
333{
334 QFETCH(QString, initial);
335 QFETCH(QString, expected);
336
337 QStandardItemModel model;
338 model.appendRow(aitem: new QStandardItem(initial));
339
340 QListView view;
341 view.setModel(&model);
342 view.show();
343
344 QModelIndex index = model.index(row: 0, column: 0);
345 view.setCurrentIndex(index); // the editor will only selectAll on the current index
346 view.edit(index);
347
348 QList<QLineEdit*> lineEditors = view.viewport()->findChildren<QLineEdit *>();
349 QCOMPARE(lineEditors.count(), 1);
350
351 QLineEdit *editor = lineEditors.at(i: 0);
352 QCOMPARE(editor->selectedText(), initial);
353
354 QTest::keyClicks(widget: editor, sequence: expected);
355 QTest::keyClick(widget: editor, key: Qt::Key_Enter);
356 QApplication::processEvents();
357
358 QCOMPARE(index.data().toString(), expected);
359}
360
361void tst_QItemDelegate::doubleEditorNegativeInput()
362{
363 QStandardItemModel model;
364
365 QStandardItem *item = new QStandardItem;
366 item->setData(value: 10.0, role: Qt::DisplayRole);
367 model.appendRow(aitem: item);
368
369 QListView view;
370 view.setModel(&model);
371 view.show();
372
373 QModelIndex index = model.index(row: 0, column: 0);
374 view.setCurrentIndex(index); // the editor will only selectAll on the current index
375 view.edit(index);
376
377 QList<QDoubleSpinBox*> editors = view.viewport()->findChildren<QDoubleSpinBox *>();
378 QCOMPARE(editors.count(), 1);
379
380 QDoubleSpinBox *editor = editors.at(i: 0);
381 QCOMPARE(editor->value(), double(10));
382
383 QTest::keyClick(widget: editor, key: Qt::Key_Minus);
384 QTest::keyClick(widget: editor, key: Qt::Key_1);
385 QTest::keyClick(widget: editor, key: Qt::Key_0);
386 QTest::keyClick(widget: editor, key: Qt::Key_Comma); //support both , and . locales
387 QTest::keyClick(widget: editor, key: Qt::Key_Period);
388 QTest::keyClick(widget: editor, key: Qt::Key_0);
389 QTest::keyClick(widget: editor, key: Qt::Key_Enter);
390 QApplication::processEvents();
391
392 QCOMPARE(index.data().toString(), QString("-10"));
393}
394
395void tst_QItemDelegate::font_data()
396{
397 QTest::addColumn<QString>(name: "itemText");
398 QTest::addColumn<QString>(name: "properties");
399 QTest::addColumn<QFont>(name: "itemFont");
400 QTest::addColumn<QFont>(name: "viewFont");
401
402 QFont itemFont;
403 itemFont.setItalic(true);
404 QFont viewFont;
405
406 QTest::newRow(dataTag: "foo italic")
407 << QString("foo")
408 << QString("italic")
409 << itemFont
410 << viewFont;
411
412 itemFont.setItalic(true);
413
414 QTest::newRow(dataTag: "foo bold")
415 << QString("foo")
416 << QString("bold")
417 << itemFont
418 << viewFont;
419
420 itemFont.setFamily(itemFont.defaultFamily());
421
422 QTest::newRow(dataTag: "foo family")
423 << QString("foo")
424 << QString("family")
425 << itemFont
426 << viewFont;
427 }
428
429void tst_QItemDelegate::font()
430{
431 Q_CHECK_PAINTEVENTS
432
433 QFETCH(QString, itemText);
434 QFETCH(QString, properties);
435 QFETCH(QFont, itemFont);
436 QFETCH(QFont, viewFont);
437
438 QTableWidget table(1, 1);
439 table.setFont(viewFont);
440
441 TestItemDelegate *delegate = new TestItemDelegate(&table);
442 table.setItemDelegate(delegate);
443 table.show();
444 QVERIFY(QTest::qWaitForWindowExposed(&table));
445
446 QTableWidgetItem *item = new QTableWidgetItem;
447 item->setText(itemText);
448 item->setFont(itemFont);
449 table.setItem(row: 0, column: 0, item);
450
451 QApplication::processEvents();
452
453 QTRY_COMPARE(delegate->displayText, item->text());
454 if (properties.contains(s: "italic")) {
455 QCOMPARE(delegate->displayFont.italic(), item->font().italic());
456 }
457 if (properties.contains(s: "bold")){
458 QCOMPARE(delegate->displayFont.bold(), item->font().bold());
459 }
460 if (properties.contains(s: "family")){
461 QCOMPARE(delegate->displayFont.family(), item->font().family());
462 }
463}
464
465//Testing the different QRect created by the doLayout function.
466//Tests are made with different values for the QStyleOptionViewItem properties:
467//decorationPosition and position.
468
469void tst_QItemDelegate::doLayout_data()
470{
471 QTest::addColumn<int>(name: "position");
472 QTest::addColumn<int>(name: "direction");
473 QTest::addColumn<bool>(name: "hint");
474 QTest::addColumn<QRect>(name: "itemRect");
475 QTest::addColumn<QRect>(name: "checkRect");
476 QTest::addColumn<QRect>(name: "pixmapRect");
477 QTest::addColumn<QRect>(name: "textRect");
478 QTest::addColumn<QRect>(name: "expectedCheckRect");
479 QTest::addColumn<QRect>(name: "expectedPixmapRect");
480 QTest::addColumn<QRect>(name: "expectedTextRect");
481
482 int m = QApplication::style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin) + 1;
483 //int item = 400;
484 //int check = 50;
485 //int pixmap = 1000;
486 //int text = 400;
487
488 QTest::newRow(dataTag: "top, left to right, hint")
489 << (int)QStyleOptionViewItem::Top
490 << (int)Qt::LeftToRight
491 << true
492 << QRect(0, 0, 400, 400)
493 << QRect(0, 0, 50, 50)
494 << QRect(0, 0, 1000, 1000)
495 << QRect(0, 0, 400, 400)
496 << QRect(0, 0, 50 + 2*m, 1000)
497 << QRect(50 + 2*m, 0, 1000 + 2*m, 1000 + m)
498 << QRect(50 + 2*m, 1000 + m, 1000 + 2*m, 400);
499 /*
500 QTest::newRow("top, left to right, limited")
501 << (int)QStyleOptionViewItem::Top
502 << (int)Qt::LeftToRight
503 << false
504 << QRect(0, 0, 400, 400)
505 << QRect(0, 0, 50, 50)
506 << QRect(0, 0, 1000, 1000)
507 << QRect(0, 0, 400, 400)
508 << QRect(m, (400/2) - (50/2), 50, 50)
509 << QRect(50 + 2*m, 0, 1000, 1000)
510 << QRect(50 + 2*m, 1000 + m, 400 - (50 + 2*m), 400 - 1000 - m);
511 */
512 QTest::newRow(dataTag: "top, right to left, hint")
513 << (int)QStyleOptionViewItem::Top
514 << (int)Qt::RightToLeft
515 << true
516 << QRect(0, 0, 400, 400)
517 << QRect(0, 0, 50, 50)
518 << QRect(0, 0, 1000, 1000)
519 << QRect(0, 0, 400, 400)
520 << QRect(1000 + 2 * m, 0, 50 + 2 * m, 1000)
521 << QRect(0, 0, 1000 + 2 * m, 1000 + m)
522 << QRect(0, 1000 + m, 1000 + 2 * m, 400);
523
524 QTest::newRow(dataTag: "bottom, left to right, hint")
525 << (int)QStyleOptionViewItem::Bottom
526 << (int)Qt::LeftToRight
527 << true
528 << QRect(0, 0, 400, 400)
529 << QRect(0, 0, 50, 50)
530 << QRect(0, 0, 1000, 1000)
531 << QRect(0, 0, 400, 400)
532 << QRect(0, 0, 50 + 2 * m, 1000)
533 << QRect(50 + 2 * m, 400 + m, 1000 + 2 * m, 1000)
534 << QRect(50 + 2 * m, 0, 1000 + 2 * m, 400 + m);
535
536 QTest::newRow(dataTag: "bottom, right to left, hint")
537 << (int)QStyleOptionViewItem::Bottom
538 << (int)Qt::RightToLeft
539 << true
540 << QRect(0, 0, 400, 400)
541 << QRect(0, 0, 50, 50)
542 << QRect(0, 0, 1000, 1000)
543 << QRect(0, 0, 400, 400)
544 << QRect(1000 + 2 * m, 0, 50 + 2 * m, 1000)
545 << QRect(0, 400 + m, 1000 + 2 * m, 1000)
546 << QRect(0, 0, 1000 + 2 * m, 400 + m);
547
548 QTest::newRow(dataTag: "left, left to right, hint")
549 << (int)QStyleOptionViewItem::Left
550 << (int)Qt::LeftToRight
551 << true
552 << QRect(0, 0, 400, 400)
553 << QRect(0, 0, 50, 50)
554 << QRect(0, 0, 1000, 1000)
555 << QRect(0, 0, 400, 400)
556 << QRect(0, 0, 50 + 2 * m, 1000)
557 << QRect(50 + 2 * m, 0, 1000 + 2 * m, 1000)
558 << QRect(1050 + 4 * m, 0, 400 + 2 * m, 1000);
559
560 QTest::newRow(dataTag: "left, right to left, hint")
561 << (int)QStyleOptionViewItem::Left
562 << (int)Qt::RightToLeft
563 << true
564 << QRect(0, 0, 400, 400)
565 << QRect(0, 0, 50, 50)
566 << QRect(0, 0, 1000, 1000)
567 << QRect(0, 0, 400, 400)
568 << QRect(1400 + 4 * m, 0, 50 + 2 * m, 1000)
569 << QRect(400 + 2 * m, 0, 1000 + 2 * m, 1000)
570 << QRect(0, 0, 400 + 2 * m, 1000);
571
572 QTest::newRow(dataTag: "right, left to right, hint")
573 << (int)QStyleOptionViewItem::Right
574 << (int)Qt::LeftToRight
575 << true
576 << QRect(0, 0, 400, 400)
577 << QRect(0, 0, 50, 50)
578 << QRect(0, 0, 1000, 1000)
579 << QRect(0, 0, 400, 400)
580 << QRect(0, 0, 50 + 2 * m, 1000)
581 << QRect(450 + 4 * m, 0, 1000 + 2 * m, 1000)
582 << QRect(50 + 2 * m, 0, 400 + 2 * m, 1000);
583
584 QTest::newRow(dataTag: "right, right to left, hint")
585 << (int)QStyleOptionViewItem::Right
586 << (int)Qt::RightToLeft
587 << true
588 << QRect(0, 0, 400, 400)
589 << QRect(0, 0, 50, 50)
590 << QRect(0, 0, 1000, 1000)
591 << QRect(0, 0, 400, 400)
592 << QRect(1400 + 4 * m, 0, 50 + 2 * m, 1000)
593 << QRect(0, 0, 1000 + 2 * m, 1000)
594 << QRect(1000 + 2 * m, 0, 400 + 2 * m, 1000);
595}
596
597void tst_QItemDelegate::doLayout()
598{
599 QFETCH(int, position);
600 QFETCH(int, direction);
601 QFETCH(bool, hint);
602 QFETCH(QRect, itemRect);
603 QFETCH(QRect, checkRect);
604 QFETCH(QRect, pixmapRect);
605 QFETCH(QRect, textRect);
606 QFETCH(QRect, expectedCheckRect);
607 QFETCH(QRect, expectedPixmapRect);
608 QFETCH(QRect, expectedTextRect);
609
610 TestItemDelegate delegate;
611 QStyleOptionViewItem option;
612
613 option.rect = itemRect;
614 option.decorationPosition = (QStyleOptionViewItem::Position)position;
615 option.direction = (Qt::LayoutDirection)direction;
616
617 delegate.doLayout(option, checkRect: &checkRect, pixmapRect: &pixmapRect, textRect: &textRect, hint);
618
619 QCOMPARE(checkRect, expectedCheckRect);
620 QCOMPARE(pixmapRect, expectedPixmapRect);
621 QCOMPARE(textRect, expectedTextRect);
622}
623
624void tst_QItemDelegate::rect_data()
625{
626 QTest::addColumn<int>(name: "role");
627 QTest::addColumn<QSize>(name: "size");
628 QTest::addColumn<QRect>(name: "expected");
629
630 QTest::newRow(dataTag: "pixmap")
631 << (int)TestItemModel::PixmapTestRole
632 << QSize(200, 300)
633 << QRect(0, 0, 200, 300);
634
635 QTest::newRow(dataTag: "image")
636 << (int)TestItemModel::ImageTestRole
637 << QSize(200, 300)
638 << QRect(0, 0, 200, 300);
639
640 QTest::newRow(dataTag: "icon")
641 << (int)TestItemModel::IconTestRole
642 << QSize(200, 300)
643 << QRect(0, 0, 200, 300);
644
645 QTest::newRow(dataTag: "color")
646 << (int)TestItemModel::ColorTestRole
647 << QSize(200, 300)
648 << QRect(0, 0, 200, 300);
649
650 QTest::newRow(dataTag: "double")
651 << (int)TestItemModel::DoubleTestRole
652 << QSize()
653 << QRect();
654}
655
656void tst_QItemDelegate::rect()
657{
658 QFETCH(int, role);
659 QFETCH(QSize, size);
660 QFETCH(QRect, expected);
661
662 TestItemModel model(size);
663 QStyleOptionViewItem option;
664 TestItemDelegate delegate;
665 option.decorationSize = size;
666
667 if (role == TestItemModel::DoubleTestRole)
668 expected = delegate.textRectangle(painter: 0, rect: QRect(), font: QFont(), text: QLatin1String("10.00000001"));
669
670 QModelIndex index = model.index(row: 0, column: 0);
671 QVERIFY(index.isValid());
672 QRect result = delegate.rect(option, index, role);
673 QCOMPARE(result, expected);
674}
675
676//TODO : Add a test for the keyPress event
677//with Qt::Key_Enter and Qt::Key_Return
678void tst_QItemDelegate::testEventFilter()
679{
680 TestItemDelegate delegate;
681 QWidget widget;
682 QEvent *event;
683
684 qRegisterMetaType<QAbstractItemDelegate::EndEditHint>(typeName: "QAbstractItemDelegate::EndEditHint");
685
686 QSignalSpy commitDataSpy(&delegate, SIGNAL(commitData(QWidget*)));
687 QSignalSpy closeEditorSpy(&delegate,
688 SIGNAL(closeEditor(QWidget *,
689 QAbstractItemDelegate::EndEditHint)));
690
691 //Subtest KeyPress
692 //For each test we send a key event and check if signals were emitted.
693 event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier);
694 QVERIFY(delegate.eventFilter(&widget, event));
695 QCOMPARE(closeEditorSpy.count(), 1);
696 QCOMPARE(commitDataSpy.count(), 1);
697 delete event;
698
699 event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier);
700 QVERIFY(delegate.eventFilter(&widget, event));
701 QCOMPARE(closeEditorSpy.count(), 2);
702 QCOMPARE(commitDataSpy.count(), 2);
703 delete event;
704
705 event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier);
706 QVERIFY(delegate.eventFilter(&widget, event));
707 QCOMPARE(closeEditorSpy.count(), 3);
708 QCOMPARE(commitDataSpy.count(), 2);
709 delete event;
710
711 event = new QKeyEvent(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier);
712 QVERIFY(!delegate.eventFilter(&widget, event));
713 QCOMPARE(closeEditorSpy.count(), 3);
714 QCOMPARE(commitDataSpy.count(), 2);
715 delete event;
716
717 //Subtest focusEvent
718 event = new QFocusEvent(QEvent::FocusOut);
719 QVERIFY(!delegate.eventFilter(&widget, event));
720 QCOMPARE(closeEditorSpy.count(), 4);
721 QCOMPARE(commitDataSpy.count(), 3);
722 delete event;
723}
724
725void tst_QItemDelegate::dateTimeEditor_data()
726{
727 QTest::addColumn<QTime>(name: "time");
728 QTest::addColumn<QDate>(name: "date");
729
730 QTest::newRow(dataTag: "data")
731 << QTime(7, 16, 34)
732 << QDate(2006, 10, 31);
733}
734
735static QDateTimeEdit *findDateTimeEdit(const QWidget *widget)
736{
737 const auto dateTimeEditors = widget->findChildren<QDateTimeEdit *>();
738 for (auto dateTimeEditor : dateTimeEditors) {
739 if (qstrcmp(str1: dateTimeEditor->metaObject()->className(), str2: "QDateTimeEdit") == 0)
740 return dateTimeEditor;
741 }
742 return nullptr;
743}
744
745void tst_QItemDelegate::dateTimeEditor()
746{
747 QFETCH(QTime, time);
748 QFETCH(QDate, date);
749
750 QTableWidgetItem *item1 = new QTableWidgetItem;
751 item1->setData(role: Qt::DisplayRole, value: time);
752
753 QTableWidgetItem *item2 = new QTableWidgetItem;
754 item2->setData(role: Qt::DisplayRole, value: date);
755
756 QTableWidgetItem *item3 = new QTableWidgetItem;
757 item3->setData(role: Qt::DisplayRole, value: QDateTime(date, time));
758
759 QTableWidget widget(1, 3);
760 widget.setWindowTitle(QLatin1String(QTest::currentTestFunction())
761 + QLatin1String("::")
762 + QLatin1String(QTest::currentDataTag()));
763 widget.setItem(row: 0, column: 0, item: item1);
764 widget.setItem(row: 0, column: 1, item: item2);
765 widget.setItem(row: 0, column: 2, item: item3);
766 widget.show();
767 QVERIFY(QTest::qWaitForWindowExposed(&widget));
768 QApplication::setActiveWindow(&widget);
769
770 widget.editItem(item: item1);
771
772 QTestEventLoop::instance().enterLoop(secs: 1);
773
774
775 QTimeEdit *timeEditor = nullptr;
776 auto viewport = widget.viewport();
777 QTRY_VERIFY( (timeEditor = viewport->findChild<QTimeEdit *>()) );
778 QCOMPARE(timeEditor->time(), time);
779 // The data must actually be different in order for the model
780 // to be updated.
781 timeEditor->setTime(time.addSecs(secs: 60));
782
783 widget.clearFocus();
784 qApp->setActiveWindow(&widget);
785 widget.setFocus();
786 widget.editItem(item: item2);
787
788 QDateEdit *dateEditor = nullptr;
789 QTRY_VERIFY( (dateEditor = viewport->findChild<QDateEdit *>()) );
790 QCOMPARE(dateEditor->date(), date);
791 dateEditor->setDate(date.addDays(days: 60));
792
793 widget.clearFocus();
794 widget.setFocus();
795 widget.editItem(item: item3);
796
797 QTestEventLoop::instance().enterLoop(secs: 1);
798
799 QDateTimeEdit *dateTimeEditor = nullptr;
800 QTRY_VERIFY( (dateTimeEditor = findDateTimeEdit(viewport)) );
801 QCOMPARE(dateTimeEditor->date(), date);
802 QCOMPARE(dateTimeEditor->time(), time);
803 dateTimeEditor->setTime(time.addSecs(secs: 600));
804 widget.clearFocus();
805
806 QCOMPARE(item1->data(Qt::EditRole).userType(), int(QMetaType::QTime));
807 QCOMPARE(item2->data(Qt::EditRole).userType(), int(QMetaType::QDate));
808 QCOMPARE(item3->data(Qt::EditRole).userType(), int(QMetaType::QDateTime));
809}
810
811// A delegate where we can either enforce a certain widget or use the standard widget.
812class ChooseEditorDelegate : public QItemDelegate
813{
814public:
815 virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &o, const QModelIndex &i) const
816 {
817 if (m_editor) {
818 m_editor->setParent(parent);
819 return m_editor;
820 }
821 m_editor = QItemDelegate::createEditor(parent, option: o, index: i);
822 return m_editor;
823 }
824
825 virtual void destroyEditor(QWidget *editor, const QModelIndex &i) const
826 { // This is a reimplementation of QAbstractItemDelegate::destroyEditor just set the variable m_editor to 0
827 // The only reason we do this is to avoid the not recommended direct delete of editor (destroyEditor uses deleteLater)
828 QItemDelegate::destroyEditor(editor, index: i); // Allow destroy
829 m_editor = 0; // but clear the variable
830 }
831
832 ChooseEditorDelegate(QObject *parent = 0) : QItemDelegate(parent) { }
833 void setNextOpenEditor(QWidget *w) { m_editor = w; }
834 QWidget* currentEditor() const { return m_editor; }
835private:
836 mutable QPointer<QWidget> m_editor;
837};
838
839// We could (nearly) use a normal QTableView but in order not to add many seconds to the autotest
840// (and save a few lines) we do this
841class FastEditItemView : public QTableView
842{
843public:
844 QWidget* fastEdit(const QModelIndex &i) // Consider this as QAbstractItemView::edit( )
845 {
846 QWidget *v = itemDelegate()->createEditor(parent: viewport(), option: viewOptions(), index: i);
847 if (v)
848 itemDelegate()->setEditorData(editor: v, index: i);
849 return v;
850 }
851 void doCloseEditor(QWidget *editor) // Consider this as QAbstractItemView::closeEditor( )
852 {
853 itemDelegate()->destroyEditor(editor, index: QModelIndex());
854 }
855};
856
857void tst_QItemDelegate::dateAndTimeEditorTest2()
858{
859 // prepare createeditor
860 FastEditItemView w;
861 QStandardItemModel s;
862 s.setRowCount(2);
863 s.setColumnCount(1);
864 w.setModel(&s);
865 ChooseEditorDelegate *d = new ChooseEditorDelegate(&w);
866 w.setItemDelegate(d);
867 const QTime time1(3, 13, 37);
868 const QDate date1(2013, 3, 7);
869
870 QPointer<QTimeEdit> timeEdit;
871 QPointer<QDateEdit> dateEdit;
872 QPointer<QDateTimeEdit> dateTimeEdit;
873
874 // Do some checks
875 // a. Open time editor on empty cell + write QTime data
876 const QModelIndex i1 = s.index(row: 0, column: 0);
877 timeEdit = new QTimeEdit();
878 d->setNextOpenEditor(timeEdit);
879 QCOMPARE(w.fastEdit(i1), timeEdit.data());
880 timeEdit->setTime(time1);
881 d->setModelData(editor: timeEdit, model: &s, index: i1);
882 QCOMPARE(s.data(i1).type(), QVariant::Time); // ensure that we wrote a time variant.
883 QCOMPARE(s.data(i1).toTime(), time1); // ensure that it is the correct time.
884 w.doCloseEditor(editor: timeEdit);
885 QVERIFY(d->currentEditor() == 0); // should happen at doCloseEditor. We only test this once.
886
887 // b. Test that automatic edit of a QTime value is QTimeEdit (and not QDateTimeEdit)
888 QWidget *editor = w.fastEdit(i: i1);
889 timeEdit = qobject_cast<QTimeEdit*>(object: editor);
890 QVERIFY(timeEdit);
891 QCOMPARE(timeEdit->time(), time1);
892 w.doCloseEditor(editor: timeEdit);
893
894 const QTime time2(4, 14, 37);
895 const QDate date2(2014, 4, 7);
896 const QDateTime datetime1(date1, time1);
897 const QDateTime datetime2(date2, time2);
898
899 // c. Test that the automatic open of an QDateTime is QDateTimeEdit + value check + set check
900 s.setData(index: i1, value: datetime2);
901 editor = w.fastEdit(i: i1);
902 timeEdit = qobject_cast<QTimeEdit*>(object: editor);
903 QVERIFY(!timeEdit);
904 dateEdit = qobject_cast<QDateEdit*>(object: editor);
905 QVERIFY(!dateEdit);
906 dateTimeEdit = qobject_cast<QDateTimeEdit*>(object: editor);
907 QVERIFY(dateTimeEdit);
908 QCOMPARE(dateTimeEdit->dateTime(), datetime2);
909 dateTimeEdit->setDateTime(datetime1);
910 d->setModelData(editor: dateTimeEdit, model: &s, index: i1);
911 QCOMPARE(s.data(i1).type(), QVariant::DateTime); // ensure that we wrote a datetime variant.
912 QCOMPARE(s.data(i1).toDateTime(), datetime1);
913 w.doCloseEditor(editor: dateTimeEdit);
914
915 // d. Open date editor on empty cell + write QDate data (similar to a)
916 const QModelIndex i2 = s.index(row: 1, column: 0);
917 dateEdit = new QDateEdit();
918 d->setNextOpenEditor(dateEdit);
919 QCOMPARE(w.fastEdit(i2), dateEdit.data());
920 dateEdit->setDate(date1);
921 d->setModelData(editor: dateEdit, model: &s, index: i2);
922 QCOMPARE(s.data(i2).type(), QVariant::Date); // ensure that we wrote a time variant.
923 QCOMPARE(s.data(i2).toDate(), date1); // ensure that it is the correct date.
924 w.doCloseEditor(editor: dateEdit);
925
926 // e. Test that the default editor editor (QDateEdit) on a QDate (index i2) (similar to b)
927 editor = w.fastEdit(i: i2);
928 dateEdit = qobject_cast<QDateEdit*>(object: editor);
929 QVERIFY(dateEdit);
930 QCOMPARE(dateEdit->date(), date1);
931 w.doCloseEditor(editor: dateEdit);
932}
933
934void tst_QItemDelegate::uintEdit()
935{
936 QListView view;
937 QStandardItemModel model;
938
939 {
940 QStandardItem *data=new QStandardItem;
941 data->setEditable(true);
942 data->setData(value: QVariant((uint)1), role: Qt::DisplayRole);
943 model.setItem(row: 0, column: 0, item: data);
944 }
945 {
946 QStandardItem *data=new QStandardItem;
947 data->setEditable(true);
948 data->setData(value: QVariant((uint)1), role: Qt::DisplayRole);
949 model.setItem(row: 1, column: 0, item: data);
950 }
951
952 view.setModel(&model);
953 view.setEditTriggers(QAbstractItemView::AllEditTriggers);
954
955 const QModelIndex firstCell = model.index(row: 0, column: 0);
956
957 QCOMPARE(firstCell.data(Qt::DisplayRole).userType(), static_cast<int>(QMetaType::UInt));
958
959 view.selectionModel()->setCurrentIndex(index: model.index(row: 0, column: 0), command: QItemSelectionModel::Select);
960 view.edit(index: firstCell);
961
962 QSpinBox *sb = view.findChild<QSpinBox*>();
963 QVERIFY(sb);
964
965 sb->stepUp();
966
967 // Select another index to trigger the end of editing.
968 const QModelIndex secondCell = model.index(row: 1, column: 0);
969 view.selectionModel()->setCurrentIndex(index: secondCell, command: QItemSelectionModel::Select);
970
971 QCOMPARE(firstCell.data(Qt::DisplayRole).userType(), static_cast<int>(QMetaType::UInt));
972 QCOMPARE(firstCell.data(Qt::DisplayRole).toUInt(), static_cast<uint>(2));
973
974
975 view.edit(index: secondCell);
976
977 // The first spinbox is deleted with deleteLater, so it is still there.
978 QList<QSpinBox*> sbList = view.findChildren<QSpinBox*>();
979 QCOMPARE(sbList.size(), 2);
980
981 sb = sbList.at(i: 1);
982
983 sb->stepDown(); // 1 -> 0
984 sb->stepDown(); // 0 (no effect)
985 sb->stepDown(); // 0 (no effect)
986
987 // Select another index to trigger the end of editing.
988 view.selectionModel()->setCurrentIndex(index: firstCell, command: QItemSelectionModel::Select);
989
990 QCOMPARE(secondCell.data(Qt::DisplayRole).userType(), static_cast<int>(QMetaType::UInt));
991 QCOMPARE(secondCell.data(Qt::DisplayRole).toUInt(), static_cast<uint>(0));
992}
993
994void tst_QItemDelegate::decoration_data()
995{
996 QTest::addColumn<int>(name: "type");
997 QTest::addColumn<QSize>(name: "size");
998 QTest::addColumn<QSize>(name: "expected");
999
1000 int pm = QApplication::style()->pixelMetric(metric: QStyle::PM_SmallIconSize);
1001
1002 QTest::newRow(dataTag: "pixmap 30x30")
1003 << (int)QVariant::Pixmap
1004 << QSize(30, 30)
1005 << QSize(30, 30);
1006
1007 QTest::newRow(dataTag: "image 30x30")
1008 << (int)QVariant::Image
1009 << QSize(30, 30)
1010 << QSize(30, 30);
1011
1012//The default engine scales pixmaps down if required, but never up. For WinCE we need bigger IconSize than 30
1013 QTest::newRow(dataTag: "icon 30x30")
1014 << (int)QVariant::Icon
1015 << QSize(60, 60)
1016 << QSize(pm, pm);
1017
1018 QTest::newRow(dataTag: "color 30x30")
1019 << (int)QVariant::Color
1020 << QSize(30, 30)
1021 << QSize(pm, pm);
1022
1023 // This demands too much memory and potentially hangs. Feel free to uncomment
1024 // for your own testing.
1025// QTest::newRow("pixmap 30x30 big")
1026// << (int)QVariant::Pixmap
1027// << QSize(1024, 1024) // Over 1M
1028// << QSize(1024, 1024);
1029}
1030
1031void tst_QItemDelegate::decoration()
1032{
1033 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1034 QSKIP("Wayland: This fails. Figure out why.");
1035
1036 Q_CHECK_PAINTEVENTS
1037
1038 QFETCH(int, type);
1039 QFETCH(QSize, size);
1040 QFETCH(QSize, expected);
1041
1042 QTableWidget table(1, 1);
1043 TestItemDelegate delegate;
1044 table.setItemDelegate(&delegate);
1045 table.show();
1046 QApplication::setActiveWindow(&table);
1047 QVERIFY(QTest::qWaitForWindowActive(&table));
1048
1049 QVariant value;
1050 switch ((QVariant::Type)type) {
1051 case QVariant::Pixmap: {
1052 QPixmap pm(size);
1053 pm.fill(fillColor: Qt::black);
1054 value = pm;
1055 break;
1056 }
1057 case QVariant::Image: {
1058 QImage img(size, QImage::Format_Mono);
1059 memset(s: img.bits(), c: 0, n: img.sizeInBytes());
1060 value = img;
1061 break;
1062 }
1063 case QVariant::Icon: {
1064 QPixmap pm(size);
1065 pm.fill(fillColor: Qt::black);
1066 value = QIcon(pm);
1067 break;
1068 }
1069 case QVariant::Color:
1070 value = QColor(Qt::green);
1071 break;
1072 default:
1073 break;
1074 }
1075
1076 QTableWidgetItem *item = new QTableWidgetItem;
1077 item->setData(role: Qt::DecorationRole, value);
1078 table.setItem(row: 0, column: 0, item);
1079 item->setSelected(true);
1080
1081 QApplication::processEvents();
1082
1083 QTRY_COMPARE(delegate.decorationRect.size(), expected);
1084}
1085
1086void tst_QItemDelegate::editorEvent_data()
1087{
1088 QTest::addColumn<int>(name: "checkState");
1089 QTest::addColumn<int>(name: "flags");
1090 QTest::addColumn<bool>(name: "inCheck");
1091 QTest::addColumn<int>(name: "type");
1092 QTest::addColumn<int>(name: "button");
1093 QTest::addColumn<bool>(name: "edited");
1094 QTest::addColumn<int>(name: "expectedCheckState");
1095
1096 const int defaultFlags = (int)(Qt::ItemIsEditable
1097 |Qt::ItemIsSelectable
1098 |Qt::ItemIsUserCheckable
1099 |Qt::ItemIsEnabled
1100 |Qt::ItemIsDragEnabled
1101 |Qt::ItemIsDropEnabled);
1102
1103 QTest::newRow(dataTag: "unchecked, checkable, release")
1104 << (int)(Qt::Unchecked)
1105 << defaultFlags
1106 << true
1107 << (int)(QEvent::MouseButtonRelease)
1108 << (int)(Qt::LeftButton)
1109 << true
1110 << (int)(Qt::Checked);
1111
1112 QTest::newRow(dataTag: "checked, checkable, release")
1113 << (int)(Qt::Checked)
1114 << defaultFlags
1115 << true
1116 << (int)(QEvent::MouseButtonRelease)
1117 << (int)(Qt::LeftButton)
1118 << true
1119 << (int)(Qt::Unchecked);
1120
1121 QTest::newRow(dataTag: "unchecked, checkable, release")
1122 << (int)(Qt::Unchecked)
1123 << defaultFlags
1124 << true
1125 << (int)(QEvent::MouseButtonRelease)
1126 << (int)(Qt::LeftButton)
1127 << true
1128 << (int)(Qt::Checked);
1129
1130 QTest::newRow(dataTag: "unchecked, checkable, release, right button")
1131 << (int)(Qt::Unchecked)
1132 << defaultFlags
1133 << true
1134 << (int)(QEvent::MouseButtonRelease)
1135 << (int)(Qt::RightButton)
1136 << false
1137 << (int)(Qt::Unchecked);
1138
1139 QTest::newRow(dataTag: "unchecked, checkable, release outside")
1140 << (int)(Qt::Unchecked)
1141 << defaultFlags
1142 << false
1143 << (int)(QEvent::MouseButtonRelease)
1144 << (int)(Qt::LeftButton)
1145 << false
1146 << (int)(Qt::Unchecked);
1147
1148 QTest::newRow(dataTag: "unchecked, checkable, dblclick")
1149 << (int)(Qt::Unchecked)
1150 << defaultFlags
1151 << true
1152 << (int)(QEvent::MouseButtonDblClick)
1153 << (int)(Qt::LeftButton)
1154 << true
1155 << (int)(Qt::Unchecked);
1156
1157 QTest::newRow(dataTag: "unchecked, tristate, release")
1158 << (int)(Qt::Unchecked)
1159 << (int)(defaultFlags | Qt::ItemIsAutoTristate)
1160 << true
1161 << (int)(QEvent::MouseButtonRelease)
1162 << (int)(Qt::LeftButton)
1163 << true
1164 << (int)(Qt::Checked);
1165
1166 QTest::newRow(dataTag: "partially checked, tristate, release")
1167 << (int)(Qt::PartiallyChecked)
1168 << (int)(defaultFlags | Qt::ItemIsAutoTristate)
1169 << true
1170 << (int)(QEvent::MouseButtonRelease)
1171 << (int)(Qt::LeftButton)
1172 << true
1173 << (int)(Qt::Checked);
1174
1175 QTest::newRow(dataTag: "checked, tristate, release")
1176 << (int)(Qt::Checked)
1177 << (int)(defaultFlags | Qt::ItemIsAutoTristate)
1178 << true
1179 << (int)(QEvent::MouseButtonRelease)
1180 << (int)(Qt::LeftButton)
1181 << true
1182 << (int)(Qt::Unchecked);
1183
1184 QTest::newRow(dataTag: "unchecked, user-tristate, release")
1185 << (int)(Qt::Unchecked)
1186 << (int)(defaultFlags | Qt::ItemIsUserTristate)
1187 << true
1188 << (int)(QEvent::MouseButtonRelease)
1189 << (int)(Qt::LeftButton)
1190 << true
1191 << (int)(Qt::PartiallyChecked);
1192
1193 QTest::newRow(dataTag: "partially checked, user-tristate, release")
1194 << (int)(Qt::PartiallyChecked)
1195 << (int)(defaultFlags | Qt::ItemIsUserTristate)
1196 << true
1197 << (int)(QEvent::MouseButtonRelease)
1198 << (int)(Qt::LeftButton)
1199 << true
1200 << (int)(Qt::Checked);
1201
1202 QTest::newRow(dataTag: "checked, user-tristate, release")
1203 << (int)(Qt::Checked)
1204 << (int)(defaultFlags | Qt::ItemIsUserTristate)
1205 << true
1206 << (int)(QEvent::MouseButtonRelease)
1207 << (int)(Qt::LeftButton)
1208 << true
1209 << (int)(Qt::Unchecked);
1210}
1211
1212void tst_QItemDelegate::editorEvent()
1213{
1214 QFETCH(int, checkState);
1215 QFETCH(int, flags);
1216 QFETCH(bool, inCheck);
1217 QFETCH(int, type);
1218 QFETCH(int, button);
1219 QFETCH(bool, edited);
1220 QFETCH(int, expectedCheckState);
1221
1222 QStandardItemModel model(1, 1);
1223 QModelIndex index = model.index(row: 0, column: 0);
1224 QVERIFY(index.isValid());
1225
1226 QStandardItem *item = model.itemFromIndex(index);
1227 item->setText("foo");
1228 item->setCheckState((Qt::CheckState)checkState);
1229 item->setFlags((Qt::ItemFlags)flags);
1230
1231 QStyleOptionViewItem option;
1232 option.rect = QRect(0, 0, 20, 20);
1233 option.state |= QStyle::State_Enabled;
1234 // mimic QStyledItemDelegate::initStyleOption logic
1235 option.features |= QStyleOptionViewItem::HasCheckIndicator | QStyleOptionViewItem::HasDisplay;
1236 option.checkState = Qt::CheckState(checkState);
1237
1238 const int checkMargin = qApp->style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: 0, widget: 0) + 1;
1239 QPoint pos = inCheck ? qApp->style()->subElementRect(subElement: QStyle::SE_ItemViewItemCheckIndicator, option: &option, widget: 0).center() + QPoint(checkMargin, 0) : QPoint(200,200);
1240
1241 QEvent *event = new QMouseEvent((QEvent::Type)type,
1242 pos,
1243 (Qt::MouseButton)button,
1244 (Qt::MouseButton)button,
1245 Qt::NoModifier);
1246 TestItemDelegate delegate;
1247 bool wasEdited = delegate.editorEvent(event, model: &model, option, index);
1248 delete event;
1249
1250 QApplication::processEvents();
1251
1252 QCOMPARE(wasEdited, edited);
1253 QCOMPARE(index.data(Qt::CheckStateRole).toInt(), expectedCheckState);
1254}
1255
1256enum WidgetType
1257{
1258 LineEdit,
1259 TextEdit,
1260 PlainTextEdit
1261};
1262Q_DECLARE_METATYPE(WidgetType);
1263
1264void tst_QItemDelegate::enterKey_data()
1265{
1266 QTest::addColumn<WidgetType>(name: "widget");
1267 QTest::addColumn<int>(name: "key");
1268 QTest::addColumn<bool>(name: "expectedFocus");
1269
1270 QTest::newRow(dataTag: "lineedit enter") << LineEdit << int(Qt::Key_Enter) << false;
1271 QTest::newRow(dataTag: "lineedit return") << LineEdit << int(Qt::Key_Return) << false;
1272 QTest::newRow(dataTag: "lineedit tab") << LineEdit << int(Qt::Key_Tab) << false;
1273 QTest::newRow(dataTag: "lineedit backtab") << LineEdit << int(Qt::Key_Backtab) << false;
1274
1275 QTest::newRow(dataTag: "textedit enter") << TextEdit << int(Qt::Key_Enter) << true;
1276 QTest::newRow(dataTag: "textedit return") << TextEdit << int(Qt::Key_Return) << true;
1277 QTest::newRow(dataTag: "textedit tab") << TextEdit << int(Qt::Key_Tab) << true;
1278 QTest::newRow(dataTag: "textedit backtab") << TextEdit << int(Qt::Key_Backtab) << false;
1279
1280 QTest::newRow(dataTag: "plaintextedit enter") << PlainTextEdit << int(Qt::Key_Enter) << true;
1281 QTest::newRow(dataTag: "plaintextedit return") << PlainTextEdit << int(Qt::Key_Return) << true;
1282 QTest::newRow(dataTag: "plaintextedit tab") << PlainTextEdit << int(Qt::Key_Tab) << true;
1283 QTest::newRow(dataTag: "plaintextedit backtab") << PlainTextEdit << int(Qt::Key_Backtab) << false;
1284}
1285
1286void tst_QItemDelegate::enterKey()
1287{
1288 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1289 QSKIP("Wayland: This fails. Figure out why.");
1290
1291 QFETCH(WidgetType, widget);
1292 QFETCH(int, key);
1293 QFETCH(bool, expectedFocus);
1294
1295 QStandardItemModel model;
1296 model.appendRow(aitem: new QStandardItem());
1297
1298 QListView view;
1299 view.setModel(&model);
1300 view.show();
1301 QApplication::setActiveWindow(&view);
1302 view.setFocus();
1303 QVERIFY(QTest::qWaitForWindowActive(&view));
1304
1305 struct TestDelegate : public QItemDelegate
1306 {
1307 WidgetType widgetType;
1308 virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const
1309 {
1310 QWidget *editor = 0;
1311 switch(widgetType) {
1312 case LineEdit:
1313 editor = new QLineEdit(parent);
1314 break;
1315 case TextEdit:
1316 editor = new QTextEdit(parent);
1317 break;
1318 case PlainTextEdit:
1319 editor = new QPlainTextEdit(parent);
1320 break;
1321 }
1322 editor->setObjectName(QString::fromLatin1(str: "TheEditor"));
1323 return editor;
1324 }
1325 } delegate;
1326
1327 delegate.widgetType = widget;
1328
1329 view.setItemDelegate(&delegate);
1330 QModelIndex index = model.index(row: 0, column: 0);
1331 view.setCurrentIndex(index); // the editor will only selectAll on the current index
1332 view.edit(index);
1333
1334 QList<QWidget*> lineEditors = view.viewport()->findChildren<QWidget *>(aName: QString::fromLatin1(str: "TheEditor"));
1335 QCOMPARE(lineEditors.count(), 1);
1336
1337 QPointer<QWidget> editor = lineEditors.at(i: 0);
1338 QCOMPARE(editor->hasFocus(), true);
1339
1340 QTest::keyClick(widget: editor, key: Qt::Key(key));
1341
1342 if (expectedFocus) {
1343 QVERIFY(!editor.isNull());
1344 QVERIFY(editor->hasFocus());
1345 } else {
1346 QTRY_VERIFY(editor.isNull()); // editor deletion happens via deleteLater
1347 }
1348}
1349
1350void tst_QItemDelegate::task257859_finalizeEdit()
1351{
1352 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1353 QSKIP("Wayland: This fails. Figure out why.");
1354
1355 QStandardItemModel model;
1356 model.appendRow(aitem: new QStandardItem());
1357
1358 QListView view;
1359 view.setModel(&model);
1360 view.show();
1361 QApplication::setActiveWindow(&view);
1362 view.setFocus();
1363 QVERIFY(QTest::qWaitForWindowActive(&view));
1364
1365 QModelIndex index = model.index(row: 0, column: 0);
1366 view.edit(index);
1367
1368 QList<QLineEdit *> lineEditors = view.viewport()->findChildren<QLineEdit *>();
1369 QCOMPARE(lineEditors.count(), 1);
1370
1371 QPointer<QWidget> editor = lineEditors.at(i: 0);
1372 QCOMPARE(editor->hasFocus(), true);
1373
1374 QDialog dialog;
1375 QTimer::singleShot(msec: 500, receiver: &dialog, SLOT(close()));
1376 dialog.exec();
1377 QTRY_VERIFY(!editor);
1378}
1379
1380void tst_QItemDelegate::QTBUG4435_keepSelectionOnCheck()
1381{
1382 QStandardItemModel model(3, 1);
1383 for (int i = 0; i < 3; ++i) {
1384 QStandardItem *item = new QStandardItem(QLatin1String("Item ") + QString::number(i));
1385 item->setCheckable(true);
1386 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
1387 model.setItem(arow: i, aitem: item);
1388 }
1389 QTableView view;
1390 view.setModel(&model);
1391 view.setItemDelegate(new TestItemDelegate(&view));
1392 view.show();
1393 view.selectAll();
1394 QVERIFY(QTest::qWaitForWindowExposed(&view));
1395 QStyleOptionViewItem option;
1396 option.rect = view.visualRect(index: model.index(row: 0, column: 0));
1397 // mimic QStyledItemDelegate::initStyleOption logic
1398 option.features = QStyleOptionViewItem::HasDisplay | QStyleOptionViewItem::HasCheckIndicator;
1399 option.checkState = Qt::CheckState(model.index(row: 0, column: 0).data(arole: Qt::CheckStateRole).toInt());
1400 const int checkMargin = qApp->style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: 0, widget: 0) + 1;
1401 QPoint pos = qApp->style()->subElementRect(subElement: QStyle::SE_ItemViewItemCheckIndicator, option: &option, widget: 0).center()
1402 + QPoint(checkMargin, 0);
1403 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ControlModifier, pos);
1404 QTRY_VERIFY(view.selectionModel()->isColumnSelected(0, QModelIndex()));
1405 QCOMPARE(model.item(0)->checkState(), Qt::Checked);
1406}
1407
1408void tst_QItemDelegate::comboBox()
1409{
1410 QTableWidgetItem *item1 = new QTableWidgetItem;
1411 item1->setData(role: Qt::DisplayRole, value: true);
1412
1413 QTableWidget widget(1, 1);
1414 widget.setItem(row: 0, column: 0, item: item1);
1415 widget.show();
1416 QVERIFY(QTest::qWaitForWindowExposed(&widget));
1417 QApplication::setActiveWindow(&widget);
1418
1419 widget.editItem(item: item1);
1420
1421 QComboBox *boolEditor = nullptr;
1422 QTRY_VERIFY( (boolEditor = widget.viewport()->findChild<QComboBox*>()) );
1423 QCOMPARE(boolEditor->currentIndex(), 1); // True is selected initially.
1424 // The data must actually be different in order for the model
1425 // to be updated.
1426 boolEditor->setCurrentIndex(0);
1427 QCOMPARE(boolEditor->currentIndex(), 0); // Changed to false.
1428
1429 widget.clearFocus();
1430 widget.setFocus();
1431
1432 QVariant data = item1->data(role: Qt::EditRole);
1433 QCOMPARE(data.userType(), (int)QMetaType::Bool);
1434 QCOMPARE(data.toBool(), false);
1435}
1436
1437void tst_QItemDelegate::testLineEditValidation_data()
1438{
1439 QTest::addColumn<int>(name: "key");
1440
1441 QTest::newRow(dataTag: "enter") << int(Qt::Key_Enter);
1442 QTest::newRow(dataTag: "return") << int(Qt::Key_Return);
1443 QTest::newRow(dataTag: "tab") << int(Qt::Key_Tab);
1444 QTest::newRow(dataTag: "backtab") << int(Qt::Key_Backtab);
1445 QTest::newRow(dataTag: "escape") << int(Qt::Key_Escape);
1446}
1447
1448void tst_QItemDelegate::testLineEditValidation()
1449{
1450 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1451 QSKIP("Wayland: This fails. Figure out why.");
1452
1453 QFETCH(int, key);
1454
1455 struct TestDelegate : public QItemDelegate
1456 {
1457 virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
1458 {
1459 Q_UNUSED(option);
1460 Q_UNUSED(index);
1461
1462 QLineEdit *editor = new QLineEdit(parent);
1463 QRegularExpression re("\\w+,\\w+"); // two words separated by a comma
1464 editor->setValidator(new QRegularExpressionValidator(re, editor));
1465 editor->setObjectName(QStringLiteral("TheEditor"));
1466 return editor;
1467 }
1468 } delegate;
1469
1470 QStandardItemModel model;
1471 // need a couple of dummy items to test tab and back tab
1472 model.appendRow(aitem: new QStandardItem(QStringLiteral("dummy")));
1473 QStandardItem *item = new QStandardItem(QStringLiteral("abc,def"));
1474 model.appendRow(aitem: item);
1475 model.appendRow(aitem: new QStandardItem(QStringLiteral("dummy")));
1476
1477 QListView view;
1478 view.setModel(&model);
1479 view.setItemDelegate(&delegate);
1480 view.show();
1481 view.setFocus();
1482 QApplication::setActiveWindow(&view);
1483 QVERIFY(QTest::qWaitForWindowActive(&view));
1484
1485 QPointer<QLineEdit> editor;
1486 QPersistentModelIndex index = model.indexFromItem(item);
1487
1488 view.setCurrentIndex(index);
1489 view.edit(index);
1490
1491 const auto findEditors = [&]() {
1492 return view.findChildren<QLineEdit *>(QStringLiteral("TheEditor"));
1493 };
1494 QCOMPARE(findEditors().count(), 1);
1495 editor = findEditors().at(i: 0);
1496 editor->clear();
1497
1498 // first try to set a valid text
1499 QTest::keyClicks(widget: editor, QStringLiteral("foo,bar"));
1500
1501 // close the editor
1502 QTest::keyClick(widget: editor, key: Qt::Key(key));
1503
1504 QTRY_VERIFY(editor.isNull());
1505 if (key != Qt::Key_Escape)
1506 QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("foo,bar"));
1507 else
1508 QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("abc,def"));
1509
1510 // now an invalid (but partially matching) text
1511 view.setCurrentIndex(index);
1512 view.edit(index);
1513
1514 QTRY_COMPARE(findEditors().count(), 1);
1515 editor = findEditors().at(i: 0);
1516 editor->clear();
1517
1518 // edit
1519 QTest::keyClicks(widget: editor, QStringLiteral("foobar"));
1520
1521 // try to close the editor
1522 QTest::keyClick(widget: editor, key: Qt::Key(key));
1523
1524 if (key != Qt::Key_Escape) {
1525 QVERIFY(!editor.isNull());
1526 QCOMPARE(qApp->focusWidget(), editor.data());
1527 QCOMPARE(editor->text(), QStringLiteral("foobar"));
1528 QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("foo,bar"));
1529 } else {
1530 QTRY_VERIFY(editor.isNull());
1531 QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("abc,def"));
1532 }
1533
1534 // reset the view to forcibly close the editor
1535 view.reset();
1536 QTRY_COMPARE(findEditors().count(), 0);
1537
1538 // set a valid text again
1539 view.setCurrentIndex(index);
1540 view.edit(index);
1541
1542 QTRY_COMPARE(findEditors().count(), 1);
1543 editor = findEditors().at(i: 0);
1544 editor->clear();
1545
1546 // set a valid text
1547 QTest::keyClicks(widget: editor, QStringLiteral("gender,bender"));
1548
1549 // close the editor
1550 QTest::keyClick(widget: editor, key: Qt::Key(key));
1551
1552 QTRY_VERIFY(editor.isNull());
1553 if (key != Qt::Key_Escape)
1554 QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("gender,bender"));
1555 else
1556 QCOMPARE(item->data(Qt::DisplayRole).toString(), QStringLiteral("abc,def"));
1557}
1558
1559void tst_QItemDelegate::QTBUG16469_textForRole()
1560{
1561#ifndef QT_BUILD_INTERNAL
1562 QSKIP("This test requires a developer build");
1563#else
1564 RoleDelegate delegate;
1565 QLocale locale;
1566
1567 const float f = 123.456f;
1568 QCOMPARE(delegate.textForRole(Qt::DisplayRole, f, locale), locale.toString(f));
1569 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, f, locale), locale.toString(f));
1570 const double d = 123.456;
1571 QCOMPARE(delegate.textForRole(Qt::DisplayRole, d, locale), locale.toString(d, 'g', 6));
1572 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, d, locale), locale.toString(d, 'g', 6));
1573 const int i = 1234567;
1574 QCOMPARE(delegate.textForRole(Qt::DisplayRole, i, locale), locale.toString(i));
1575 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, i, locale), locale.toString(i));
1576 const qlonglong ll = 1234567;
1577 QCOMPARE(delegate.textForRole(Qt::DisplayRole, ll, locale), locale.toString(ll));
1578 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, ll, locale), locale.toString(ll));
1579 const uint ui = 1234567;
1580 QCOMPARE(delegate.textForRole(Qt::DisplayRole, ui, locale), locale.toString(ui));
1581 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, ui, locale), locale.toString(ui));
1582 const qulonglong ull = 1234567;
1583 QCOMPARE(delegate.textForRole(Qt::DisplayRole, ull, locale), locale.toString(ull));
1584 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, ull, locale), locale.toString(ull));
1585
1586 const QString text("text");
1587 QCOMPARE(delegate.textForRole(Qt::DisplayRole, text, locale), text);
1588 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, text, locale), text);
1589 const QString multipleLines("multiple\nlines");
1590 QString multipleLines2 = multipleLines;
1591 multipleLines2.replace(before: QLatin1Char('\n'), after: QChar::LineSeparator);
1592 QCOMPARE(delegate.textForRole(Qt::DisplayRole, multipleLines, locale), multipleLines2);
1593 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, multipleLines, locale), multipleLines);
1594#endif
1595}
1596
1597void tst_QItemDelegate::dateTextForRole_data()
1598{
1599#ifdef QT_BUILD_INTERNAL
1600 QTest::addColumn<QDateTime>(name: "when");
1601
1602 QTest::newRow(dataTag: "now") << QDateTime::currentDateTime(); // It's a local time
1603 QDate date(2013, 12, 11);
1604 QTime time(10, 9, 8, 765);
1605 // Ensure we exercise every time-spec variant:
1606 QTest::newRow(dataTag: "local") << QDateTime(date, time, Qt::LocalTime);
1607 QTest::newRow(dataTag: "UTC") << QDateTime(date, time, Qt::UTC);
1608#if QT_CONFIG(timezone)
1609 QTest::newRow(dataTag: "zone") << QDateTime(date, time, QTimeZone("Europe/Dublin"));
1610#endif
1611 QTest::newRow(dataTag: "offset") << QDateTime(date, time, Qt::OffsetFromUTC, 36000);
1612#endif
1613}
1614
1615void tst_QItemDelegate::dateTextForRole()
1616{
1617#ifndef QT_BUILD_INTERNAL
1618 QSKIP("This test requires a developer build");
1619#else
1620 QFETCH(QDateTime, when);
1621 RoleDelegate delegate;
1622 QLocale locale;
1623# define CHECK(value) \
1624 QCOMPARE(delegate.textForRole(Qt::DisplayRole, value, locale), locale.toString(value, QLocale::ShortFormat)); \
1625 QCOMPARE(delegate.textForRole(Qt::ToolTipRole, value, locale), locale.toString(value, QLocale::LongFormat))
1626
1627 CHECK(when);
1628 CHECK(when.date());
1629 CHECK(when.time());
1630# undef CHECK
1631#endif
1632}
1633
1634// ### _not_ covered:
1635
1636// editing with a custom editor factory
1637
1638// painting when editing
1639// painting elided text
1640// painting wrapped text
1641// painting focus
1642// painting icon
1643// painting color
1644// painting check
1645// painting selected
1646
1647// rect for invalid
1648// rect for pixmap
1649// rect for image
1650// rect for icon
1651// rect for check
1652
1653QTEST_MAIN(tst_QItemDelegate)
1654#include "tst_qitemdelegate.moc"
1655

source code of qtbase/tests/auto/widgets/itemviews/qitemdelegate/tst_qitemdelegate.cpp