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 <private/qguiapplication_p.h>
30
31#include <qpa/qplatformintegration.h>
32
33#include <QAbstractItemView>
34#include <QDialog>
35#include <QHeaderView>
36#include <QIdentityProxyModel>
37#include <QItemDelegate>
38#include <QLineEdit>
39#include <QListWidget>
40#include <QProxyStyle>
41#include <QPushButton>
42#include <QScrollBar>
43#include <QSignalSpy>
44#include <QSortFilterProxyModel>
45#include <QSpinBox>
46#include <QStandardItemModel>
47#include <QStringListModel>
48#include <QStyledItemDelegate>
49#include <QTableWidget>
50#include <QTimer>
51#include <QTreeWidget>
52#include <QTest>
53#include <QVBoxLayout>
54#include <QtTest/private/qtesthelpers_p.h>
55
56Q_DECLARE_METATYPE(Qt::ItemFlags);
57
58using namespace QTestPrivate;
59using IntList = QVector<int>;
60
61// Move cursor out of widget area to avoid undesired interaction on Mac.
62static inline void moveCursorAway(const QWidget *topLevel)
63{
64#ifndef QT_NO_CURSOR
65 QCursor::setPos(topLevel->geometry().topRight() + QPoint(100, 0));
66#else
67 Q_UNUSED(topLevel)
68#endif
69}
70
71class GeometriesTestView : public QTableView
72{
73 Q_OBJECT
74public:
75 using QTableView::QTableView;
76 using QTableView::selectedIndexes;
77 bool updateGeometriesCalled = false;
78protected slots:
79 void updateGeometries() override { updateGeometriesCalled = true; QTableView::updateGeometries(); }
80};
81
82class tst_QAbstractItemView : public QObject
83{
84 Q_OBJECT
85
86public:
87 void basic_tests(QAbstractItemView *view);
88
89private slots:
90 void cleanup();
91 void getSetCheck();
92 void emptyModels_data();
93 void emptyModels();
94 void setModel_data();
95 void setModel();
96 void noModel();
97 void dragSelect();
98 void rowDelegate();
99 void columnDelegate();
100 void selectAll();
101 void ctrlA();
102 void persistentEditorFocus();
103 void setItemDelegate();
104 void setItemDelegate_data();
105 // The dragAndDrop() test doesn't work, and is thus disabled on Mac and Windows
106 // for the following reasons:
107 // Mac: use of GetCurrentEventButtonState() in QDragManager::drag()
108 // Win: unknown reason
109#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
110#if 0
111 void dragAndDrop();
112 void dragAndDropOnChild();
113#endif
114#endif
115 void noFallbackToRoot();
116 void setCurrentIndex_data();
117 void setCurrentIndex();
118
119 void task221955_selectedEditor();
120 void task250754_fontChange();
121 void task200665_itemEntered();
122 void task257481_emptyEditor();
123 void shiftArrowSelectionAfterScrolling();
124 void shiftSelectionAfterRubberbandSelection();
125 void ctrlRubberbandSelection();
126 void QTBUG6407_extendedSelection();
127 void QTBUG6753_selectOnSelection();
128 void testDelegateDestroyEditor();
129 void testClickedSignal();
130 void testChangeEditorState();
131 void deselectInSingleSelection();
132 void testNoActivateOnDisabledItem();
133 void testFocusPolicy_data();
134 void testFocusPolicy();
135 void QTBUG31411_noSelection();
136 void QTBUG39324_settingSameInstanceOfIndexWidget();
137 void sizeHintChangeTriggersLayout();
138 void shiftSelectionAfterChangingModelContents();
139 void QTBUG48968_reentrant_updateEditorGeometries();
140 void QTBUG50102_SH_ItemView_ScrollMode();
141 void QTBUG50535_update_on_new_selection_model();
142 void testSelectionModelInSyncWithView();
143 void testClickToSelect();
144 void testDialogAsEditor();
145 void QTBUG46785_mouseout_hover_state();
146 void testClearModelInClickedSignal();
147 void inputMethodEnabled_data();
148 void inputMethodEnabled();
149 void currentFollowsIndexWidget_data();
150 void currentFollowsIndexWidget();
151 void checkFocusAfterActivationChanges_data();
152 void checkFocusAfterActivationChanges();
153 void dragSelectAfterNewPress();
154 void dragWithSecondClick_data();
155 void dragWithSecondClick();
156 void clickAfterDoubleClick();
157
158private:
159 static QAbstractItemView *viewFromString(const QByteArray &viewType, QWidget *parent = nullptr)
160 {
161 if (viewType == "QListView")
162 return new QListView(parent);
163 if (viewType == "QTableView")
164 return new QTableView(parent);
165 if (viewType == "QTreeView")
166 return new QTreeView(parent);
167 if (viewType == "QHeaderView")
168 return new QHeaderView(Qt::Vertical, parent);
169 Q_ASSERT(false);
170 return nullptr;
171 }
172};
173
174class MyAbstractItemDelegate : public QAbstractItemDelegate
175{
176public:
177 using QAbstractItemDelegate::QAbstractItemDelegate;
178 void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override {}
179 QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override { return size; }
180 QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const override
181 {
182 openedEditor = new QWidget(parent);
183 return openedEditor;
184 }
185 void destroyEditor(QWidget *editor, const QModelIndex &) const override
186 {
187 calledVirtualDtor = true;
188 editor->deleteLater();
189 }
190 void changeSize() { size = QSize(50, 50); emit sizeHintChanged(QModelIndex()); }
191 mutable QWidget *openedEditor = nullptr;
192 QSize size;
193 mutable bool calledVirtualDtor = false;
194};
195
196class DialogItemDelegate : public QStyledItemDelegate
197{
198public:
199 using QStyledItemDelegate::QStyledItemDelegate;
200 QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const
201 {
202 openedEditor = new QDialog(parent);
203 return openedEditor;
204 }
205
206 void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
207 {
208 Q_UNUSED(model)
209 Q_UNUSED(index)
210
211 QDialog *dialog = qobject_cast<QDialog *>(object: editor);
212 result = static_cast<QDialog::DialogCode>(dialog->result());
213 }
214
215 mutable QDialog *openedEditor = nullptr;
216 mutable QDialog::DialogCode result = QDialog::Rejected;
217};
218
219// Testing get/set functions
220void tst_QAbstractItemView::getSetCheck()
221{
222 QListView view;
223 QAbstractItemView *obj1 = &view;
224 // QAbstractItemDelegate * QAbstractItemView::itemDelegate()
225 // void QAbstractItemView::setItemDelegate(QAbstractItemDelegate *)
226 MyAbstractItemDelegate *var1 = new MyAbstractItemDelegate;
227 obj1->setItemDelegate(var1);
228 QCOMPARE(var1, obj1->itemDelegate());
229 obj1->setItemDelegate(nullptr);
230 QCOMPARE(obj1->itemDelegate(), nullptr);
231 delete var1;
232
233 // EditTriggers )
234 // void QAbstractItemView::setEditTriggers(EditTriggers)
235 obj1->setEditTriggers(QAbstractItemView::NoEditTriggers);
236 QCOMPARE(obj1->editTriggers(), QAbstractItemView::NoEditTriggers);
237 obj1->setEditTriggers(QAbstractItemView::CurrentChanged);
238 QCOMPARE(obj1->editTriggers(), QAbstractItemView::CurrentChanged);
239 obj1->setEditTriggers(QAbstractItemView::DoubleClicked);
240 QCOMPARE(obj1->editTriggers(), QAbstractItemView::DoubleClicked);
241 obj1->setEditTriggers(QAbstractItemView::SelectedClicked);
242 QCOMPARE(obj1->editTriggers(), QAbstractItemView::SelectedClicked);
243 obj1->setEditTriggers(QAbstractItemView::EditKeyPressed);
244 QCOMPARE(obj1->editTriggers(), QAbstractItemView::EditKeyPressed);
245 obj1->setEditTriggers(QAbstractItemView::AnyKeyPressed);
246 QCOMPARE(obj1->editTriggers(), QAbstractItemView::AnyKeyPressed);
247 obj1->setEditTriggers(QAbstractItemView::AllEditTriggers);
248 QCOMPARE(obj1->editTriggers(), QAbstractItemView::AllEditTriggers);
249
250 // bool QAbstractItemView::tabKeyNavigation()
251 // void QAbstractItemView::setTabKeyNavigation(bool)
252 obj1->setTabKeyNavigation(false);
253 QCOMPARE(false, obj1->tabKeyNavigation());
254 obj1->setTabKeyNavigation(true);
255 QCOMPARE(true, obj1->tabKeyNavigation());
256
257 // bool QAbstractItemView::dragEnabled()
258 // void QAbstractItemView::setDragEnabled(bool)
259#if QT_CONFIG(draganddrop)
260 obj1->setDragEnabled(false);
261 QCOMPARE(false, obj1->dragEnabled());
262 obj1->setDragEnabled(true);
263 QCOMPARE(true, obj1->dragEnabled());
264#endif
265 // bool QAbstractItemView::alternatingRowColors()
266 // void QAbstractItemView::setAlternatingRowColors(bool)
267 obj1->setAlternatingRowColors(false);
268 QCOMPARE(false, obj1->alternatingRowColors());
269 obj1->setAlternatingRowColors(true);
270 QCOMPARE(true, obj1->alternatingRowColors());
271
272 // State QAbstractItemView::state()
273 // void QAbstractItemView::setState(State)
274 obj1->setState(QAbstractItemView::NoState);
275 QCOMPARE(QAbstractItemView::NoState, obj1->state());
276 obj1->setState(QAbstractItemView::DraggingState);
277 QCOMPARE(QAbstractItemView::DraggingState, obj1->state());
278 obj1->setState(QAbstractItemView::DragSelectingState);
279 QCOMPARE(QAbstractItemView::DragSelectingState, obj1->state());
280 obj1->setState(QAbstractItemView::EditingState);
281 QCOMPARE(QAbstractItemView::EditingState, obj1->state());
282 obj1->setState(QAbstractItemView::ExpandingState);
283 QCOMPARE(QAbstractItemView::ExpandingState, obj1->state());
284 obj1->setState(QAbstractItemView::CollapsingState);
285 QCOMPARE(QAbstractItemView::CollapsingState, obj1->state());
286
287 // QWidget QAbstractScrollArea::viewport()
288 // void setViewport(QWidget*)
289 QWidget *vp = new QWidget;
290 obj1->setViewport(vp);
291 QCOMPARE(vp, obj1->viewport());
292
293 QCOMPARE(16, obj1->autoScrollMargin());
294 obj1->setAutoScrollMargin(20);
295 QCOMPARE(20, obj1->autoScrollMargin());
296 obj1->setAutoScrollMargin(16);
297 QCOMPARE(16, obj1->autoScrollMargin());
298}
299
300void tst_QAbstractItemView::cleanup()
301{
302 QVERIFY(QApplication::topLevelWidgets().isEmpty());
303}
304
305void tst_QAbstractItemView::emptyModels_data()
306{
307 QTest::addColumn<QByteArray>(name: "viewType");
308
309 const QVector<QByteArray> widgets{ "QListView", "QTreeView", "QTableView", "QHeaderView" };
310 for (const QByteArray &widget : widgets)
311 QTest::newRow(dataTag: widget) << widget;
312}
313
314void tst_QAbstractItemView::emptyModels()
315{
316 QFETCH(QByteArray, viewType);
317
318 QScopedPointer<QAbstractItemView> view(viewFromString(viewType));
319 centerOnScreen(w: view.data());
320 moveCursorAway(topLevel: view.data());
321 view->show();
322 QVERIFY(QTest::qWaitForWindowExposed(view.data()));
323
324 QVERIFY(!view->model());
325 QVERIFY(!view->selectionModel());
326 //QVERIFY(view->itemDelegate() != 0);
327
328 basic_tests(view: view.data());
329}
330
331void tst_QAbstractItemView::setModel_data()
332{
333 emptyModels_data();
334}
335
336void tst_QAbstractItemView::setModel()
337{
338 QFETCH(QByteArray, viewType);
339
340 QScopedPointer<QAbstractItemView> view(viewFromString(viewType));
341 centerOnScreen(w: view.data());
342 moveCursorAway(topLevel: view.data());
343 view->show();
344 QVERIFY(QTest::qWaitForWindowExposed(view.data()));
345
346 QStandardItemModel model(20,20);
347 view->setModel(nullptr);
348 view->setModel(&model);
349 basic_tests(view: view.data());
350}
351
352void tst_QAbstractItemView::basic_tests(QAbstractItemView *view)
353{
354 // setSelectionModel
355 // Will assert as it should
356 //view->setSelectionModel(0);
357 // setItemDelegate
358 //view->setItemDelegate(0);
359 // Will asswert as it should
360
361 // setSelectionMode
362 view->setSelectionMode(QAbstractItemView::SingleSelection);
363 QCOMPARE(view->selectionMode(), QAbstractItemView::SingleSelection);
364 view->setSelectionMode(QAbstractItemView::ContiguousSelection);
365 QCOMPARE(view->selectionMode(), QAbstractItemView::ContiguousSelection);
366 view->setSelectionMode(QAbstractItemView::ExtendedSelection);
367 QCOMPARE(view->selectionMode(), QAbstractItemView::ExtendedSelection);
368 view->setSelectionMode(QAbstractItemView::MultiSelection);
369 QCOMPARE(view->selectionMode(), QAbstractItemView::MultiSelection);
370 view->setSelectionMode(QAbstractItemView::NoSelection);
371 QCOMPARE(view->selectionMode(), QAbstractItemView::NoSelection);
372
373 // setSelectionBehavior
374 view->setSelectionBehavior(QAbstractItemView::SelectItems);
375 QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectItems);
376 view->setSelectionBehavior(QAbstractItemView::SelectRows);
377 QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectRows);
378 view->setSelectionBehavior(QAbstractItemView::SelectColumns);
379 QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectColumns);
380
381 // setEditTriggers
382 view->setEditTriggers(QAbstractItemView::EditKeyPressed);
383 QCOMPARE(view->editTriggers(), QAbstractItemView::EditKeyPressed);
384 view->setEditTriggers(QAbstractItemView::NoEditTriggers);
385 QCOMPARE(view->editTriggers(), QAbstractItemView::NoEditTriggers);
386 view->setEditTriggers(QAbstractItemView::CurrentChanged);
387 QCOMPARE(view->editTriggers(), QAbstractItemView::CurrentChanged);
388 view->setEditTriggers(QAbstractItemView::DoubleClicked);
389 QCOMPARE(view->editTriggers(), QAbstractItemView::DoubleClicked);
390 view->setEditTriggers(QAbstractItemView::SelectedClicked);
391 QCOMPARE(view->editTriggers(), QAbstractItemView::SelectedClicked);
392 view->setEditTriggers(QAbstractItemView::AnyKeyPressed);
393 QCOMPARE(view->editTriggers(), QAbstractItemView::AnyKeyPressed);
394 view->setEditTriggers(QAbstractItemView::AllEditTriggers);
395 QCOMPARE(view->editTriggers(), QAbstractItemView::AllEditTriggers);
396
397 // setAutoScroll
398 view->setAutoScroll(false);
399 QCOMPARE(view->hasAutoScroll(), false);
400 view->setAutoScroll(true);
401 QCOMPARE(view->hasAutoScroll(), true);
402
403 // setTabKeyNavigation
404 view->setTabKeyNavigation(false);
405 QCOMPARE(view->tabKeyNavigation(), false);
406 view->setTabKeyNavigation(true);
407 QCOMPARE(view->tabKeyNavigation(), true);
408
409#if QT_CONFIG(draganddrop)
410 // setDropIndicatorShown
411 view->setDropIndicatorShown(false);
412 QCOMPARE(view->showDropIndicator(), false);
413 view->setDropIndicatorShown(true);
414 QCOMPARE(view->showDropIndicator(), true);
415
416 // setDragEnabled
417 view->setDragEnabled(false);
418 QCOMPARE(view->dragEnabled(), false);
419 view->setDragEnabled(true);
420 QCOMPARE(view->dragEnabled(), true);
421#endif
422
423 // setAlternatingRowColors
424 view->setAlternatingRowColors(false);
425 QCOMPARE(view->alternatingRowColors(), false);
426 view->setAlternatingRowColors(true);
427 QCOMPARE(view->alternatingRowColors(), true);
428
429 // setIconSize
430 view->setIconSize(QSize(16, 16));
431 QCOMPARE(view->iconSize(), QSize(16, 16));
432 QSignalSpy spy(view, &QAbstractItemView::iconSizeChanged);
433 QVERIFY(spy.isValid());
434 view->setIconSize(QSize(32, 32));
435 QCOMPARE(view->iconSize(), QSize(32, 32));
436 QCOMPARE(spy.count(), 1);
437 QCOMPARE(spy.at(0).at(0).value<QSize>(), QSize(32, 32));
438 // Should this happen?
439 view->setIconSize(QSize(-1, -1));
440 QCOMPARE(view->iconSize(), QSize(-1, -1));
441 QCOMPARE(spy.count(), 2);
442
443 QCOMPARE(view->currentIndex(), QModelIndex());
444 QCOMPARE(view->rootIndex(), QModelIndex());
445
446 view->keyboardSearch(search: "");
447 view->keyboardSearch(search: "foo");
448 view->keyboardSearch(search: "1");
449
450 QCOMPARE(view->visualRect(QModelIndex()), QRect());
451
452 view->scrollTo(index: QModelIndex());
453
454 QCOMPARE(view->sizeHintForIndex(QModelIndex()), QSize());
455 QCOMPARE(view->indexAt(QPoint(-1, -1)), QModelIndex());
456
457 if (!view->model()) {
458 QCOMPARE(view->indexAt(QPoint(10, 10)), QModelIndex());
459 QCOMPARE(view->sizeHintForRow(0), -1);
460 QCOMPARE(view->sizeHintForColumn(0), -1);
461 } else if (view->itemDelegate()) {
462 view->sizeHintForRow(row: 0);
463 view->sizeHintForColumn(column: 0);
464 }
465 view->openPersistentEditor(index: QModelIndex());
466 view->closePersistentEditor(index: QModelIndex());
467
468 view->reset();
469 view->setRootIndex(QModelIndex());
470 view->doItemsLayout();
471 view->selectAll();
472 view->edit(index: QModelIndex());
473 view->clearSelection();
474 view->setCurrentIndex(QModelIndex());
475
476 // protected methods
477 view->dataChanged(topLeft: QModelIndex(), bottomRight: QModelIndex());
478 view->rowsInserted(parent: QModelIndex(), start: -1, end: -1);
479 view->rowsAboutToBeRemoved(parent: QModelIndex(), start: -1, end: -1);
480 view->selectionChanged(selected: QItemSelection(), deselected: QItemSelection());
481 if (view->model()) {
482 view->currentChanged(current: QModelIndex(), previous: QModelIndex());
483 view->currentChanged(current: QModelIndex(), previous: view->model()->index(row: 0,column: 0));
484 }
485 view->updateEditorData();
486 view->updateEditorGeometries();
487 view->updateGeometries();
488 view->verticalScrollbarAction(action: QAbstractSlider::SliderSingleStepAdd);
489 view->horizontalScrollbarAction(action: QAbstractSlider::SliderSingleStepAdd);
490 view->verticalScrollbarValueChanged(value: 10);
491 view->horizontalScrollbarValueChanged(value: 10);
492 view->closeEditor(editor: nullptr, hint: QAbstractItemDelegate::NoHint);
493 view->commitData(editor: nullptr);
494 view->editorDestroyed(editor: nullptr);
495
496 // Will assert as it should
497 // view->setIndexWidget(QModelIndex(), 0);
498
499 view->moveCursor(cursorAction: QAbstractItemView::MoveUp, modifiers: Qt::NoModifier);
500 view->horizontalOffset();
501 view->verticalOffset();
502
503// view->isIndexHidden(QModelIndex()); // will (correctly) assert
504 if (view->model())
505 view->isIndexHidden(index: view->model()->index(row: 0,column: 0));
506
507 view->setSelection(rect: QRect(0, 0, 10, 10), command: QItemSelectionModel::ClearAndSelect);
508 view->setSelection(rect: QRect(-1, -1, -1, -1), command: QItemSelectionModel::ClearAndSelect);
509 view->visualRegionForSelection(selection: QItemSelection());
510 view->selectedIndexes();
511
512 view->edit(index: QModelIndex(), trigger: QAbstractItemView::NoEditTriggers, event: nullptr);
513
514 view->selectionCommand(index: QModelIndex(), event: nullptr);
515
516#if QT_CONFIG(draganddrop)
517 if (!view->model())
518 view->startDrag(supportedActions: Qt::CopyAction);
519
520 view->viewOptions();
521
522 view->setState(QAbstractItemView::NoState);
523 QCOMPARE(view->state(), QAbstractItemView::NoState);
524 view->setState(QAbstractItemView::DraggingState);
525 QCOMPARE(view->state(), QAbstractItemView::DraggingState);
526 view->setState(QAbstractItemView::DragSelectingState);
527 QCOMPARE(view->state(), QAbstractItemView::DragSelectingState);
528 view->setState(QAbstractItemView::EditingState);
529 QCOMPARE(view->state(), QAbstractItemView::EditingState);
530 view->setState(QAbstractItemView::ExpandingState);
531 QCOMPARE(view->state(), QAbstractItemView::ExpandingState);
532 view->setState(QAbstractItemView::CollapsingState);
533 QCOMPARE(view->state(), QAbstractItemView::CollapsingState);
534#endif
535
536 view->startAutoScroll();
537 view->stopAutoScroll();
538 view->doAutoScroll();
539
540 // testing mouseFoo and key functions
541// QTest::mousePress(view, Qt::LeftButton, Qt::NoModifier, QPoint(0,0));
542// mouseMove(view, Qt::LeftButton, Qt::NoModifier, QPoint(10,10));
543// QTest::mouseRelease(view, Qt::LeftButton, Qt::NoModifier, QPoint(10,10));
544// QTest::mouseClick(view, Qt::LeftButton, Qt::NoModifier, QPoint(10,10));
545// mouseDClick(view, Qt::LeftButton, Qt::NoModifier, QPoint(10,10));
546// QTest::keyClick(view, Qt::Key_A);
547}
548
549void tst_QAbstractItemView::noModel()
550{
551 // From task #85415
552
553 QStandardItemModel model(20,20);
554 QTreeView view;
555 setFrameless(&view);
556
557 view.setModel(&model);
558 // Make the viewport smaller than the contents, so that we can scroll
559 view.resize(w: 100,h: 100);
560 centerOnScreen(w: &view);
561 moveCursorAway(topLevel: &view);
562 view.show();
563 QVERIFY(QTest::qWaitForWindowExposed(&view));
564
565 // make sure that the scrollbars are not at value 0
566 view.scrollTo(index: view.model()->index(row: 10,column: 10));
567 QApplication::processEvents();
568
569 view.setModel(nullptr);
570 // Due to the model is removed, this will generate a valueChanged signal on both scrollbars. (value to 0)
571 QApplication::processEvents();
572 QCOMPARE(view.model(), nullptr);
573}
574
575void tst_QAbstractItemView::dragSelect()
576{
577 // From task #86108
578
579 QStandardItemModel model(64, 64);
580
581 QTableView view;
582 view.setModel(&model);
583 centerOnScreen(w: &view);
584 moveCursorAway(topLevel: &view);
585 view.setVisible(true);
586 QVERIFY(QTest::qWaitForWindowExposed(&view));
587
588 const int delay = 2;
589 for (int i = 0; i < 2; ++i) {
590 bool tracking = (i == 1);
591 view.setMouseTracking(false);
592 QTest::mouseMove(widget: &view, pos: QPoint(0, 0), delay);
593 view.setMouseTracking(tracking);
594 QTest::mouseMove(widget: &view, pos: QPoint(50, 50), delay);
595 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
596 }
597}
598
599void tst_QAbstractItemView::rowDelegate()
600{
601 QStandardItemModel model(4, 4);
602 MyAbstractItemDelegate delegate;
603
604 QTableView view;
605 view.setModel(&model);
606 view.setItemDelegateForRow(row: 3, delegate: &delegate);
607 centerOnScreen(w: &view);
608 moveCursorAway(topLevel: &view);
609 view.show();
610 QVERIFY(QTest::qWaitForWindowExposed(&view));
611
612 QModelIndex index = model.index(row: 3, column: 0);
613 QVERIFY(!view.isPersistentEditorOpen(index));
614 view.openPersistentEditor(index);
615 QVERIFY(view.isPersistentEditorOpen(index));
616 QWidget *w = view.indexWidget(index);
617 QVERIFY(w);
618 QCOMPARE(w->metaObject()->className(), "QWidget");
619}
620
621void tst_QAbstractItemView::columnDelegate()
622{
623 QStandardItemModel model(4, 4);
624 MyAbstractItemDelegate delegate;
625
626 QTableView view;
627 view.setModel(&model);
628 view.setItemDelegateForColumn(column: 3, delegate: &delegate);
629 centerOnScreen(w: &view);
630 moveCursorAway(topLevel: &view);
631 view.show();
632 QVERIFY(QTest::qWaitForWindowExposed(&view));
633
634 QModelIndex index = model.index(row: 0, column: 3);
635 QVERIFY(!view.isPersistentEditorOpen(index));
636 view.openPersistentEditor(index);
637 QVERIFY(view.isPersistentEditorOpen(index));
638 QWidget *w = view.indexWidget(index);
639 QVERIFY(w);
640 QCOMPARE(w->metaObject()->className(), "QWidget");
641}
642
643void tst_QAbstractItemView::sizeHintChangeTriggersLayout()
644{
645 QStandardItemModel model(4, 4);
646 MyAbstractItemDelegate delegate;
647 MyAbstractItemDelegate rowDelegate;
648 MyAbstractItemDelegate columnDelegate;
649
650 GeometriesTestView view;
651 view.setModel(&model);
652 view.setItemDelegate(&delegate);
653 view.setItemDelegateForRow(row: 1, delegate: &rowDelegate);
654 view.setItemDelegateForColumn(column: 2, delegate: &columnDelegate);
655 view.show();
656 QVERIFY(QTest::qWaitForWindowExposed(&view));
657 view.updateGeometriesCalled = false;
658 delegate.changeSize();
659 QTRY_VERIFY(view.updateGeometriesCalled);
660 view.updateGeometriesCalled = false;
661 rowDelegate.changeSize();
662 QTRY_VERIFY(view.updateGeometriesCalled);
663 view.updateGeometriesCalled = false;
664 columnDelegate.changeSize();
665 QTRY_VERIFY(view.updateGeometriesCalled);
666}
667
668void tst_QAbstractItemView::selectAll()
669{
670 QStandardItemModel model(4, 4);
671 GeometriesTestView view;
672 view.setModel(&model);
673
674 QCOMPARE(view.selectedIndexes().count(), 0);
675 view.selectAll();
676 QCOMPARE(view.selectedIndexes().count(), 4 * 4);
677}
678
679void tst_QAbstractItemView::ctrlA()
680{
681 QStandardItemModel model(4, 4);
682 GeometriesTestView view;
683 view.setModel(&model);
684
685 QCOMPARE(view.selectedIndexes().count(), 0);
686 QTest::keyClick(widget: &view, key: Qt::Key_A, modifier: Qt::ControlModifier);
687 QCOMPARE(view.selectedIndexes().count(), 4 * 4);
688}
689
690void tst_QAbstractItemView::persistentEditorFocus()
691{
692 // one row, three columns
693 QStandardItemModel model(1, 3);
694 for(int i = 0; i < model.columnCount(); ++i)
695 model.setData(index: model.index(row: 0, column: i), value: i);
696 QTableView view;
697 view.setModel(&model);
698
699 view.openPersistentEditor(index: model.index(row: 0, column: 1));
700 view.openPersistentEditor(index: model.index(row: 0, column: 2));
701
702 //these are spinboxes because we put numbers inside
703 const QList<QSpinBox*> list = view.viewport()->findChildren<QSpinBox*>();
704 QCOMPARE(list.count(), 2); //these should be the 2 editors
705
706 view.setCurrentIndex(model.index(row: 0, column: 0));
707 QCOMPARE(view.currentIndex(), model.index(0, 0));
708 centerOnScreen(w: &view);
709 moveCursorAway(topLevel: &view);
710 view.show();
711 QVERIFY(QTest::qWaitForWindowExposed(&view));
712
713 const QPoint p(5, 5);
714 for (QSpinBox *sb : list) {
715 QTRY_VERIFY(sb->isVisible());
716 QMouseEvent mouseEvent(QEvent::MouseButtonPress, p, Qt::LeftButton,
717 Qt::LeftButton, Qt::NoModifier);
718 QCoreApplication::sendEvent(receiver: sb, event: &mouseEvent);
719 if (!QApplication::focusWidget())
720 QSKIP("Some window managers don't handle focus that well");
721 QTRY_COMPARE(QApplication::focusWidget(), sb);
722 }
723}
724
725
726#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
727
728#if 0
729
730static void sendMouseMove(QWidget *widget, QPoint pos = QPoint())
731{
732 if (pos.isNull())
733 pos = widget->rect().center();
734 QMouseEvent event(QEvent::MouseMove, pos, widget->mapToGlobal(pos), Qt::NoButton, 0, 0);
735 QCursor::setPos(widget->mapToGlobal(pos));
736 qApp->processEvents();
737 QVERIFY(QTest::qWaitForWindowExposed(widget));
738 QApplication::sendEvent(widget, &event);
739}
740
741static void sendMousePress(
742 QWidget *widget, QPoint pos = QPoint(), Qt::MouseButton button = Qt::LeftButton)
743{
744 if (pos.isNull())
745 pos = widget->rect().center();
746 QMouseEvent event(QEvent::MouseButtonPress, pos, widget->mapToGlobal(pos), button, 0, 0);
747 QApplication::sendEvent(widget, &event);
748}
749
750static void sendMouseRelease(
751 QWidget *widget, QPoint pos = QPoint(), Qt::MouseButton button = Qt::LeftButton)
752{
753 if (pos.isNull())
754 pos = widget->rect().center();
755 QMouseEvent event(QEvent::MouseButtonRelease, pos, widget->mapToGlobal(pos), button, 0, 0);
756 QApplication::sendEvent(widget, &event);
757}
758
759class DnDTestModel : public QStandardItemModel
760{
761 Q_OBJECT
762 bool dropMimeData(const QMimeData *md, Qt::DropAction action, int r, int c, const QModelIndex &p)
763 {
764 dropAction_result = action;
765 QStandardItemModel::dropMimeData(md, action, r, c, p);
766 return true;
767 }
768 Qt::DropActions supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; }
769
770 Qt::DropAction dropAction_result;
771public:
772 DnDTestModel() : QStandardItemModel(20, 20), dropAction_result(Qt::IgnoreAction) {
773 for (int i = 0; i < rowCount(); ++i)
774 setData(index(i, 0), QString::number(i));
775 }
776 Qt::DropAction dropAction() const { return dropAction_result; }
777};
778
779class DnDTestView : public QTreeView
780{
781 Q_OBJECT
782
783 QPoint dropPoint;
784 Qt::DropAction dropAction;
785
786 void dragEnterEvent(QDragEnterEvent *event)
787 {
788 QAbstractItemView::dragEnterEvent(event);
789 }
790
791 void dropEvent(QDropEvent *event)
792 {
793 event->setDropAction(dropAction);
794 QTreeView::dropEvent(event);
795 }
796
797 void timerEvent(QTimerEvent *event)
798 {
799 killTimer(event->timerId());
800 sendMouseMove(this, dropPoint);
801 sendMouseRelease(this);
802 }
803
804 void mousePressEvent(QMouseEvent *e)
805 {
806 QTreeView::mousePressEvent(e);
807
808 startTimer(0);
809 setState(DraggingState);
810 startDrag(dropAction);
811 }
812
813public:
814 DnDTestView(Qt::DropAction dropAction, QAbstractItemModel *model)
815 : dropAction(dropAction)
816 {
817 header()->hide();
818 setModel(model);
819 setDragDropMode(QAbstractItemView::DragDrop);
820 setAcceptDrops(true);
821 setDragEnabled(true);
822 }
823
824 void dragAndDrop(QPoint drag, QPoint drop)
825 {
826 dropPoint = drop;
827 setCurrentIndex(indexAt(drag));
828 sendMousePress(viewport(), drag);
829 }
830};
831
832class DnDTestWidget : public QWidget
833{
834 Q_OBJECT
835
836 Qt::DropAction dropAction_request;
837 Qt::DropAction dropAction_result;
838 QWidget *dropTarget;
839
840 void timerEvent(QTimerEvent *event)
841 {
842 killTimer(event->timerId());
843 sendMouseMove(dropTarget);
844 sendMouseRelease(dropTarget);
845 }
846
847 void mousePressEvent(QMouseEvent *)
848 {
849 QDrag *drag = new QDrag(this);
850 QMimeData *mimeData = new QMimeData;
851 mimeData->setData("application/x-qabstractitemmodeldatalist", QByteArray(""));
852 drag->setMimeData(mimeData);
853 startTimer(0);
854 dropAction_result = drag->start(dropAction_request);
855 }
856
857public:
858 Qt::DropAction dropAction() const { return dropAction_result; }
859
860 void dragAndDrop(QWidget *dropTarget, Qt::DropAction dropAction)
861 {
862 this->dropTarget = dropTarget;
863 dropAction_request = dropAction;
864 sendMousePress(this);
865 }
866};
867
868void tst_QAbstractItemView::dragAndDrop()
869{
870 // From Task 137729
871
872
873 const int attempts = 10;
874 int successes = 0;
875 for (int i = 0; i < attempts; ++i) {
876 Qt::DropAction dropAction = Qt::MoveAction;
877
878 DnDTestModel model;
879 DnDTestView view(dropAction, &model);
880 DnDTestWidget widget;
881
882 const int size = 200;
883 widget.setFixedSize(size, size);
884 view.setFixedSize(size, size);
885
886 widget.move(0, 0);
887 view.move(int(size * 1.5), int(size * 1.5));
888
889 widget.show();
890 view.show();
891 QVERIFY(QTest::qWaitForWindowExposed(&widget));
892 QVERIFY(QTest::qWaitForWindowExposed(&view));
893
894 widget.dragAndDrop(&view, dropAction);
895 if (model.dropAction() == dropAction
896 && widget.dropAction() == dropAction)
897 ++successes;
898 }
899
900 if (successes < attempts) {
901 QString msg = QString("# successes (%1) < # attempts (%2)").arg(successes).arg(attempts);
902 QWARN(msg.toLatin1());
903 }
904 QVERIFY(successes > 0); // allow for some "event unstability" (i.e. unless
905 // successes == 0, QAbstractItemView is probably ok!)
906}
907
908void tst_QAbstractItemView::dragAndDropOnChild()
909{
910
911 const int attempts = 10;
912 int successes = 0;
913 for (int i = 0; i < attempts; ++i) {
914 Qt::DropAction dropAction = Qt::MoveAction;
915
916 DnDTestModel model;
917 QModelIndex parent = model.index(0, 0);
918 model.insertRow(0, parent);
919 model.insertColumn(0, parent);
920 QModelIndex child = model.index(0, 0, parent);
921 model.setData(child, "child");
922 QCOMPARE(model.rowCount(parent), 1);
923 DnDTestView view(dropAction, &model);
924 view.setExpanded(parent, true);
925 view.setDragDropMode(QAbstractItemView::InternalMove);
926
927 const int size = 200;
928 view.setFixedSize(size, size);
929 view.move(int(size * 1.5), int(size * 1.5));
930 view.show();
931 QVERIFY(QTest::qWaitForWindowExposed(&view));
932
933 view.dragAndDrop(view.visualRect(parent).center(),
934 view.visualRect(child).center());
935 if (model.dropAction() == dropAction)
936 ++successes;
937 }
938
939 QCOMPARE(successes, 0);
940}
941
942#endif // 0
943#endif // !Q_OS_MAC && !Q_OS_WIN
944
945class TestModel : public QStandardItemModel
946{
947 Q_OBJECT
948public:
949 using QStandardItemModel::QStandardItemModel;
950 bool setData(const QModelIndex &, const QVariant &, int) override
951 {
952 ++setData_count;
953 return true;
954 }
955 void emitDataChanged()
956 {
957 emit dataChanged(topLeft: index(row: 0, column: 0), bottomRight: index(row: 0, column: 1));
958 }
959
960 int setData_count = 0;
961};
962
963void tst_QAbstractItemView::setItemDelegate_data()
964{
965 // default is rows, a -1 will switch to columns
966 QTest::addColumn<IntList>(name: "rowsOrColumnsWithDelegate");
967 QTest::addColumn<QPoint>(name: "cellToEdit");
968 QTest::newRow(dataTag: "4 columndelegates")
969 << (IntList{ -1, 0, 1, 2, 3 })
970 << QPoint(0, 0);
971 QTest::newRow(dataTag: "2 identical rowdelegates on the same row")
972 << (IntList{ 0, 0 })
973 << QPoint(0, 0);
974 QTest::newRow(dataTag: "2 identical columndelegates on the same column")
975 << (IntList{ -1, 2, 2 })
976 << QPoint(2, 0);
977 QTest::newRow(dataTag: "2 duplicate delegates, 1 row and 1 column")
978 << (IntList{ 0, -1, 2 })
979 << QPoint(2, 0);
980 QTest::newRow(dataTag: "4 duplicate delegates, 2 row and 2 column")
981 << (IntList{ 0, 0, -1, 2, 2 })
982 << QPoint(2, 0);
983
984}
985
986void tst_QAbstractItemView::setItemDelegate()
987{
988 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
989 QSKIP("Window activation is not supported");
990
991 QFETCH(const IntList, rowsOrColumnsWithDelegate);
992 QFETCH(QPoint, cellToEdit);
993 QTableView v;
994 QStyledItemDelegate *delegate = new QStyledItemDelegate(&v);
995 TestModel model(5, 5);
996 v.setModel(&model);
997
998 bool row = true;
999 for (int rc : rowsOrColumnsWithDelegate) {
1000 if (rc == -1) {
1001 row = !row;
1002 } else {
1003 if (row)
1004 v.setItemDelegateForRow(row: rc, delegate);
1005 else
1006 v.setItemDelegateForColumn(column: rc, delegate);
1007 }
1008 }
1009 centerOnScreen(w: &v);
1010 moveCursorAway(topLevel: &v);
1011 v.show();
1012 QApplication::setActiveWindow(&v);
1013 QVERIFY(QTest::qWaitForWindowActive(&v));
1014
1015 QModelIndex index = model.index(row: cellToEdit.y(), column: cellToEdit.x());
1016 v.edit(index);
1017
1018 // This will close the editor
1019 QTRY_VERIFY(QApplication::focusWidget());
1020 QWidget *editor = QApplication::focusWidget();
1021 QVERIFY(editor);
1022 editor->hide();
1023 delete editor;
1024 QCOMPARE(model.setData_count, 1);
1025 delete delegate;
1026}
1027
1028void tst_QAbstractItemView::noFallbackToRoot()
1029{
1030 QStandardItemModel model(0, 1);
1031 for (int i = 0; i < 5; ++i)
1032 model.appendRow(aitem: new QStandardItem("top" + QString::number(i)));
1033 QStandardItem *par1 = model.item(row: 1);
1034 for (int j = 0; j < 15; ++j)
1035 par1->appendRow(aitem: new QStandardItem("sub" + QString::number(j)));
1036 QStandardItem *par2 = par1->child(row: 2);
1037 for (int k = 0; k < 10; ++k)
1038 par2->appendRow(aitem: new QStandardItem("bot" + QString::number(k)));
1039 QStandardItem *it1 = par2->child(row: 5);
1040
1041 QModelIndex parent1 = model.indexFromItem(item: par1);
1042 QModelIndex parent2 = model.indexFromItem(item: par2);
1043 QModelIndex item1 = model.indexFromItem(item: it1);
1044
1045 QTreeView v;
1046 v.setModel(&model);
1047 v.setRootIndex(parent1);
1048 v.setCurrentIndex(item1);
1049 QCOMPARE(v.currentIndex(), item1);
1050 QVERIFY(model.removeRows(0, 10, parent2));
1051 QCOMPARE(v.currentIndex(), parent2);
1052 QVERIFY(model.removeRows(0, 15, parent1));
1053 QCOMPARE(v.currentIndex(), QModelIndex());
1054}
1055
1056void tst_QAbstractItemView::setCurrentIndex_data()
1057{
1058 QTest::addColumn<QByteArray>(name: "viewType");
1059 QTest::addColumn<Qt::ItemFlags>(name: "itemFlags");
1060 QTest::addColumn<bool>(name: "result");
1061
1062 const QVector<QByteArray> widgets{ "QListView", "QTreeView", "QTableView", "QHeaderView" };
1063 for (const QByteArray &widget : widgets) {
1064 QTest::newRow(dataTag: widget + ": no flags")
1065 << widget << Qt::ItemFlags(Qt::NoItemFlags) << false;
1066 QTest::newRow(dataTag: widget + ": checkable")
1067 << widget << Qt::ItemFlags(Qt::ItemIsUserCheckable) << false;
1068 QTest::newRow(dataTag: widget + ": selectable")
1069 << widget << Qt::ItemFlags(Qt::ItemIsSelectable) << false;
1070 QTest::newRow(dataTag: widget + ": enabled")
1071 << widget << Qt::ItemFlags(Qt::ItemIsEnabled) << true;
1072 QTest::newRow(dataTag: widget + ": enabled|selectable")
1073 << widget << (Qt::ItemIsSelectable|Qt::ItemIsEnabled) << true;
1074 }
1075}
1076
1077void tst_QAbstractItemView::setCurrentIndex()
1078{
1079 QFETCH(QByteArray, viewType);
1080 QFETCH(Qt::ItemFlags, itemFlags);
1081 QFETCH(bool, result);
1082
1083 QScopedPointer<QAbstractItemView> view(viewFromString(viewType));
1084
1085 centerOnScreen(w: view.data());
1086 moveCursorAway(topLevel: view.data());
1087 view->show();
1088 QVERIFY(QTest::qWaitForWindowExposed(view.data()));
1089
1090 QStandardItemModel *model = new QStandardItemModel(view.data());
1091 QStandardItem *item = new QStandardItem("first item");
1092 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1093 model->appendRow(aitem: item);
1094
1095 item = new QStandardItem("test item");
1096 item->setFlags(itemFlags);
1097 model->appendRow(aitem: item);
1098
1099 view->setModel(model);
1100
1101 view->setCurrentIndex(model->index(row: 0, column: 0));
1102 QCOMPARE(view->currentIndex(), model->index(0, 0));
1103 view->setCurrentIndex(model->index(row: 1, column: 0));
1104 QVERIFY(view->currentIndex() == model->index(result ? 1 : 0, 0));
1105}
1106
1107void tst_QAbstractItemView::task221955_selectedEditor()
1108{
1109 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1110 QSKIP("Window activation is not supported");
1111
1112 QTreeWidget tree;
1113 tree.setColumnCount(2);
1114
1115 tree.addTopLevelItem(item: new QTreeWidgetItem({"Foo", "1"}));
1116 tree.addTopLevelItem(item: new QTreeWidgetItem({"Bar", "2"}));
1117 tree.addTopLevelItem(item: new QTreeWidgetItem({"Baz", "3"}));
1118
1119 QTreeWidgetItem *dummy = new QTreeWidgetItem();
1120 tree.addTopLevelItem(item: dummy);
1121 QPushButton *button = new QPushButton("More...");
1122 tree.setItemWidget(item: dummy, column: 0, widget: button);
1123 button->setAutoFillBackground(true); // as recommended in doc
1124
1125 centerOnScreen(w: &tree);
1126 moveCursorAway(topLevel: &tree);
1127 tree.show();
1128 tree.setFocus();
1129 tree.setCurrentIndex(tree.model()->index(row: 1,column: 0));
1130 QApplication::setActiveWindow(&tree);
1131 QVERIFY(QTest::qWaitForWindowActive(&tree));
1132
1133 QVERIFY(! tree.selectionModel()->selectedIndexes().contains(tree.model()->index(3,0)));
1134
1135 //We set the focus to the button, the index need to be selected
1136 button->setFocus();
1137 QTRY_VERIFY(tree.selectionModel()->selectedIndexes().contains(tree.model()->index(3,0)));
1138
1139 tree.setCurrentIndex(tree.model()->index(row: 1,column: 0));
1140 QVERIFY(! tree.selectionModel()->selectedIndexes().contains(tree.model()->index(3,0)));
1141
1142 //Same thing but with the flag NoSelection, nothing can be selected.
1143 tree.setFocus();
1144 tree.setSelectionMode(QAbstractItemView::NoSelection);
1145 tree.clearSelection();
1146 QVERIFY(tree.selectionModel()->selectedIndexes().isEmpty());
1147 button->setFocus();
1148 QTest::qWait(ms: 50);
1149 QVERIFY(tree.selectionModel()->selectedIndexes().isEmpty());
1150}
1151
1152void tst_QAbstractItemView::task250754_fontChange()
1153{
1154 QString app_css = qApp->styleSheet();
1155 qApp->setStyleSheet("/* */");
1156
1157 QWidget w;
1158 QTreeView tree(&w);
1159 QVBoxLayout *vLayout = new QVBoxLayout(&w);
1160 vLayout->addWidget(&tree);
1161
1162 QStandardItemModel *m = new QStandardItemModel(&w);
1163 for (int i = 0; i < 20; ++i) {
1164 QStandardItem *item = new QStandardItem(QStringLiteral("Item number ") + QString::number(i));
1165 for (int j = 0; j < 5; ++j) {
1166 QStandardItem *child = new QStandardItem(QStringLiteral("Child Item number ") + QString::number(j));
1167 item->setChild(row: j, column: 0, item: child);
1168 }
1169 m->setItem(row: i, column: 0, item);
1170 }
1171 tree.setModel(m);
1172
1173 tree.setHeaderHidden(true); // The header is (in certain styles) dpi dependent
1174 w.resize(w: 160, h: 350); // Minimum width for windows with frame on Windows 8
1175 centerOnScreen(w: &w);
1176 moveCursorAway(topLevel: &w);
1177 w.showNormal();
1178 QVERIFY(QTest::qWaitForWindowExposed(&w));
1179 QFont font = tree.font();
1180 font.setPixelSize(10);
1181 tree.setFont(font);
1182 QTRY_VERIFY(!tree.verticalScrollBar()->isVisible());
1183
1184 font.setPixelSize(60);
1185 tree.setFont(font);
1186#ifdef Q_OS_WINRT
1187 QSKIP("Resizing the widget does not work as expected for WinRT, so the scroll bar might not be visible");
1188#endif
1189 //now with the huge items, the scrollbar must be visible
1190 QTRY_VERIFY(tree.verticalScrollBar()->isVisible());
1191
1192 qApp->setStyleSheet(app_css);
1193}
1194
1195void tst_QAbstractItemView::task200665_itemEntered()
1196{
1197 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1198 QSKIP("Wayland: This fails. Figure out why.");
1199
1200 //we test that view will emit entered
1201 //when the scrollbar move but not the mouse itself
1202 QStandardItemModel model(1000, 1);
1203 QListView view;
1204 view.setModel(&model);
1205 centerOnScreen(w: &view);
1206 moveCursorAway(topLevel: &view);
1207 view.show();
1208 QVERIFY(QTest::qWaitForWindowExposed(&view));
1209 QCursor::setPos(view.geometry().center());
1210 QTRY_COMPARE(QCursor::pos(), view.geometry().center());
1211 QSignalSpy spy(&view, &QAbstractItemView::entered);
1212 view.verticalScrollBar()->setValue(view.verticalScrollBar()->maximum());
1213
1214 QTRY_COMPARE(spy.count(), 1);
1215}
1216
1217void tst_QAbstractItemView::task257481_emptyEditor()
1218{
1219 QIcon icon = QApplication::style()->standardIcon(standardIcon: QStyle::SP_ComputerIcon);
1220
1221 QStandardItemModel model;
1222
1223 model.appendRow(aitem: new QStandardItem(icon, QString()));
1224 model.appendRow(aitem: new QStandardItem(icon, "Editor works"));
1225 model.appendRow(aitem: new QStandardItem(QString()));
1226
1227 QTreeView treeView;
1228 treeView.setRootIsDecorated(false);
1229 treeView.setModel(&model);
1230 centerOnScreen(w: &treeView);
1231 moveCursorAway(topLevel: &treeView);
1232 treeView.show();
1233 QVERIFY(QTest::qWaitForWindowExposed(&treeView));
1234
1235 treeView.edit(index: model.index(row: 0, column: 0));
1236 QList<QLineEdit *> lineEditors = treeView.viewport()->findChildren<QLineEdit *>();
1237 QCOMPARE(lineEditors.count(), 1);
1238 QVERIFY(!lineEditors.constFirst()->size().isEmpty());
1239
1240 treeView.edit(index: model.index(row: 1, column: 0));
1241 lineEditors = treeView.viewport()->findChildren<QLineEdit *>();
1242 QCOMPARE(lineEditors.count(), 1);
1243 QVERIFY(!lineEditors.constFirst()->size().isEmpty());
1244
1245 treeView.edit(index: model.index(row: 2, column: 0));
1246 lineEditors = treeView.viewport()->findChildren<QLineEdit *>();
1247 QCOMPARE(lineEditors.count(), 1);
1248 QVERIFY(!lineEditors.constFirst()->size().isEmpty());
1249}
1250
1251void tst_QAbstractItemView::shiftArrowSelectionAfterScrolling()
1252{
1253 QStandardItemModel model;
1254 for (int i = 0; i < 10; ++i)
1255 model.setItem(row: i, column: 0, item: new QStandardItem(QString::number(i)));
1256
1257 QListView view;
1258 view.setFixedSize(w: 160, h: 250); // Minimum width for windows with frame on Windows 8
1259 view.setFlow(QListView::LeftToRight);
1260 view.setGridSize(QSize(100, 100));
1261 view.setSelectionMode(QListView::ExtendedSelection);
1262 view.setViewMode(QListView::IconMode);
1263 view.setModel(&model);
1264 centerOnScreen(w: &view);
1265 moveCursorAway(topLevel: &view);
1266 view.show();
1267 QVERIFY(QTest::qWaitForWindowExposed(&view));
1268
1269 QModelIndex index0 = model.index(row: 0, column: 0);
1270 QModelIndex index1 = model.index(row: 1, column: 0);
1271 QModelIndex index9 = model.index(row: 9, column: 0);
1272
1273 view.selectionModel()->setCurrentIndex(index: index0, command: QItemSelectionModel::NoUpdate);
1274 QCOMPARE(view.currentIndex(), index0);
1275
1276 view.scrollTo(index: index9);
1277 QTest::keyClick(widget: &view, key: Qt::Key_Down, modifier: Qt::ShiftModifier);
1278
1279 QCOMPARE(view.currentIndex(), index1);
1280 QModelIndexList selected = view.selectionModel()->selectedIndexes();
1281 QCOMPARE(selected.count(), 2);
1282 QVERIFY(selected.contains(index0));
1283 QVERIFY(selected.contains(index1));
1284}
1285
1286void tst_QAbstractItemView::shiftSelectionAfterRubberbandSelection()
1287{
1288 QStandardItemModel model;
1289 for (int i = 0; i < 3; ++i)
1290 model.setItem(row: i, column: 0, item: new QStandardItem(QString::number(i)));
1291
1292 QListView view;
1293 view.setFixedSize(w: 160, h: 450); // Minimum width for windows with frame on Windows 8
1294 view.setFlow(QListView::LeftToRight);
1295 view.setGridSize(QSize(100, 100));
1296 view.setSelectionMode(QListView::ExtendedSelection);
1297 view.setViewMode(QListView::IconMode);
1298 view.setModel(&model);
1299 centerOnScreen(w: &view);
1300 moveCursorAway(topLevel: &view);
1301 view.show();
1302 QVERIFY(QTest::qWaitForWindowExposed(&view));
1303
1304 QModelIndex index0 = model.index(row: 0, column: 0);
1305 QModelIndex index1 = model.index(row: 1, column: 0);
1306 QModelIndex index2 = model.index(row: 2, column: 0);
1307
1308 view.setCurrentIndex(index0);
1309 QCOMPARE(view.currentIndex(), index0);
1310
1311 // Determine the points where the rubberband selection starts and ends
1312 QPoint pressPos = view.visualRect(index: index1).bottomRight() + QPoint(1, 1);
1313 QPoint releasePos = view.visualRect(index: index1).center();
1314 QVERIFY(!view.indexAt(pressPos).isValid());
1315 QCOMPARE(view.indexAt(releasePos), index1);
1316
1317 // Select item 1 using a rubberband selection
1318 // The mouse move event has to be created manually because the QTest framework does not
1319 // contain a function for mouse moves with buttons pressed
1320 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: pressPos);
1321 QMouseEvent moveEvent(QEvent::MouseMove, releasePos, Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
1322 bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent);
1323 QVERIFY(moveEventReceived);
1324 QTest::mouseRelease(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: releasePos);
1325 QCOMPARE(view.currentIndex(), index1);
1326
1327 // Shift-click item 2
1328 QPoint item2Pos = view.visualRect(index: index2).center();
1329 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier, pos: item2Pos);
1330 QCOMPARE(view.currentIndex(), index2);
1331
1332 // Verify that the selection worked OK
1333 QModelIndexList selected = view.selectionModel()->selectedIndexes();
1334 QCOMPARE(selected.count(), 2);
1335 QVERIFY(selected.contains(index1));
1336 QVERIFY(selected.contains(index2));
1337
1338 // Select item 0 to revert the selection
1339 view.setCurrentIndex(index0);
1340 QCOMPARE(view.currentIndex(), index0);
1341
1342 // Repeat the same steps as above, but with a Shift-Arrow selection
1343 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: pressPos);
1344 QMouseEvent moveEvent2(QEvent::MouseMove, releasePos, Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
1345 moveEventReceived = qApp->notify(view.viewport(), &moveEvent2);
1346 QVERIFY(moveEventReceived);
1347 QTest::mouseRelease(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: releasePos);
1348 QCOMPARE(view.currentIndex(), index1);
1349
1350 // Press Shift-Down
1351 QTest::keyClick(widget: &view, key: Qt::Key_Down, modifier: Qt::ShiftModifier);
1352 QCOMPARE(view.currentIndex(), index2);
1353
1354 // Verify that the selection worked OK
1355 selected = view.selectionModel()->selectedIndexes();
1356 QCOMPARE(selected.count(), 2);
1357 QVERIFY(selected.contains(index1));
1358 QVERIFY(selected.contains(index2));
1359}
1360
1361void tst_QAbstractItemView::ctrlRubberbandSelection()
1362{
1363 QStandardItemModel model;
1364 for (int i = 0; i < 3; ++i)
1365 model.setItem(row: i, column: 0, item: new QStandardItem(QString::number(i)));
1366
1367 QListView view;
1368 view.setFixedSize(w: 160, h: 450); // Minimum width for windows with frame on Windows 8
1369 view.setFlow(QListView::LeftToRight);
1370 view.setGridSize(QSize(100, 100));
1371 view.setSelectionMode(QListView::ExtendedSelection);
1372 view.setViewMode(QListView::IconMode);
1373 view.setModel(&model);
1374 centerOnScreen(w: &view);
1375 moveCursorAway(topLevel: &view);
1376 view.show();
1377 QVERIFY(QTest::qWaitForWindowExposed(&view));
1378
1379 QModelIndex index1 = model.index(row: 1, column: 0);
1380 QModelIndex index2 = model.index(row: 2, column: 0);
1381
1382 // Select item 1
1383 view.setCurrentIndex(index1);
1384 QModelIndexList selected = view.selectionModel()->selectedIndexes();
1385 QCOMPARE(selected.count(), 1);
1386 QVERIFY(selected.contains(index1));
1387
1388 // Now press control and draw a rubberband around items 1 and 2.
1389 // The mouse move event has to be created manually because the QTest framework does not
1390 // contain a function for mouse moves with buttons pressed.
1391 QPoint pressPos = view.visualRect(index: index1).topLeft() - QPoint(1, 1);
1392 QPoint releasePos = view.visualRect(index: index2).bottomRight() + QPoint(1, 1);
1393 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ControlModifier, pos: pressPos);
1394 QMouseEvent moveEvent(QEvent::MouseMove, releasePos, Qt::NoButton, Qt::LeftButton, Qt::ControlModifier);
1395 bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent);
1396 QVERIFY(moveEventReceived);
1397 QTest::mouseRelease(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ControlModifier, pos: releasePos);
1398
1399 // Verify that item 2 is selected now
1400 selected = view.selectionModel()->selectedIndexes();
1401 QCOMPARE(selected.count(), 1);
1402 QVERIFY(selected.contains(index2));
1403}
1404
1405void tst_QAbstractItemView::QTBUG6407_extendedSelection()
1406{
1407 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1408 QSKIP("Window activation is not supported");
1409
1410 QListWidget view;
1411 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
1412 for (int i = 0; i < 50; ++i)
1413 view.addItem(label: QString::number(i));
1414
1415 QFont font = view.font();
1416 font.setPixelSize(10);
1417 view.setFont(font);
1418 view.resize(w: 200,h: 240);
1419 centerOnScreen(w: &view);
1420 moveCursorAway(topLevel: &view);
1421
1422 view.show();
1423 QApplication::setActiveWindow(&view);
1424 QVERIFY(QTest::qWaitForWindowActive(&view));
1425 QCOMPARE(&view, QApplication::activeWindow());
1426
1427 view.verticalScrollBar()->setValue(view.verticalScrollBar()->maximum());
1428
1429 QModelIndex index49 = view.model()->index(row: 49,column: 0);
1430 QPoint p = view.visualRect(index: index49).center();
1431 QVERIFY(view.viewport()->rect().contains(p));
1432 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
1433 QCOMPARE(view.currentIndex(), index49);
1434 QCOMPARE(view.selectedItems().count(), 1);
1435
1436 QModelIndex index47 = view.model()->index(row: 47,column: 0);
1437 p = view.visualRect(index: index47).center();
1438 QVERIFY(view.viewport()->rect().contains(p));
1439 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier, pos: p);
1440 QCOMPARE(view.currentIndex(), index47);
1441 QCOMPARE(view.selectedItems().count(), 3); //49, 48, 47;
1442
1443 QModelIndex index44 = view.model()->index(row: 44,column: 0);
1444 p = view.visualRect(index: index44).center();
1445 QVERIFY(view.viewport()->rect().contains(p));
1446 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier, pos: p);
1447 QCOMPARE(view.currentIndex(), index44);
1448 QCOMPARE(view.selectedItems().count(), 6); //49 .. 44;
1449
1450}
1451
1452void tst_QAbstractItemView::QTBUG6753_selectOnSelection()
1453{
1454 QTableWidget table(5, 5);
1455 for (int i = 0; i < table.rowCount(); ++i) {
1456 for (int j = 0; j < table.columnCount(); ++j)
1457 table.setItem(row: i, column: j, item: new QTableWidgetItem("choo-be-doo-wah"));
1458 }
1459
1460 centerOnScreen(w: &table);
1461 moveCursorAway(topLevel: &table);
1462 table.show();
1463 QVERIFY(QTest::qWaitForWindowExposed(&table));
1464
1465 table.setSelectionMode(QAbstractItemView::ExtendedSelection);
1466 table.selectAll();
1467 QVERIFY(QTest::qWaitForWindowExposed(&table));
1468 QModelIndex item = table.model()->index(row: 1,column: 1);
1469 QRect itemRect = table.visualRect(index: item);
1470 QTest::mouseMove(widget: table.viewport(), pos: itemRect.center());
1471 QTest::mouseClick(widget: table.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: itemRect.center());
1472
1473 QCOMPARE(table.selectedItems().count(), 1);
1474 QCOMPARE(table.selectedItems().first(), table.item(item.row(), item.column()));
1475}
1476
1477void tst_QAbstractItemView::testDelegateDestroyEditor()
1478{
1479 QTableWidget table(5, 5);
1480 MyAbstractItemDelegate delegate;
1481 table.setItemDelegate(&delegate);
1482 table.edit(index: table.model()->index(row: 1, column: 1));
1483 QAbstractItemView *tv = &table;
1484 QVERIFY(!delegate.calledVirtualDtor);
1485 tv->closeEditor(editor: delegate.openedEditor, hint: QAbstractItemDelegate::NoHint);
1486 QVERIFY(delegate.calledVirtualDtor);
1487}
1488
1489void tst_QAbstractItemView::testClickedSignal()
1490{
1491 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1492 QSKIP("Window activation is not supported");
1493
1494 QTableWidget view(5, 5);
1495
1496 centerOnScreen(w: &view);
1497 moveCursorAway(topLevel: &view);
1498 view.showNormal();
1499 QApplication::setActiveWindow(&view);
1500 QVERIFY(QTest::qWaitForWindowActive(&view));
1501 QCOMPARE(&view, QApplication::activeWindow());
1502
1503 QModelIndex index49 = view.model()->index(row: 49,column: 0);
1504 QPoint p = view.visualRect(index: index49).center();
1505 QVERIFY(view.viewport()->rect().contains(p));
1506
1507 QSignalSpy clickedSpy(&view, &QTableWidget::clicked);
1508
1509 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
1510#ifdef Q_OS_WINRT
1511 QEXPECT_FAIL("", "Fails on WinRT - QTBUG-68297", Abort);
1512#endif
1513 QCOMPARE(clickedSpy.count(), 1);
1514
1515 QTest::mouseClick(widget: view.viewport(), button: Qt::RightButton, stateKey: {}, pos: p);
1516 // We expect that right-clicks do not cause the clicked() signal to
1517 // be emitted.
1518 QCOMPARE(clickedSpy.count(), 1);
1519
1520}
1521
1522class StateChangeDelegate : public QStyledItemDelegate
1523{
1524 Q_OBJECT
1525public:
1526 using QStyledItemDelegate::QStyledItemDelegate;
1527 void setEditorData(QWidget *editor, const QModelIndex &index) const override
1528 {
1529 Q_UNUSED(index);
1530 static bool w = true;
1531 editor->setEnabled(w);
1532 w = !w;
1533 }
1534};
1535
1536void tst_QAbstractItemView::testChangeEditorState()
1537{
1538 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1539 QSKIP("Window activation is not supported");
1540
1541 // Test for QTBUG-25370
1542 TestModel model;
1543 model.setItem(row: 0, column: 0, item: new QStandardItem("a"));
1544 model.setItem(row: 0, column: 1, item: new QStandardItem("b"));
1545
1546 QTableView view;
1547 view.setEditTriggers(QAbstractItemView::CurrentChanged);
1548 view.setItemDelegate(new StateChangeDelegate(&view));
1549 view.setModel(&model);
1550 centerOnScreen(w: &view);
1551 moveCursorAway(topLevel: &view);
1552 view.show();
1553 QApplication::setActiveWindow(&view);
1554 QVERIFY(QTest::qWaitForWindowActive(&view));
1555 QCOMPARE(&view, QApplication::activeWindow());
1556
1557 model.emitDataChanged();
1558 // No segfault - the test passes.
1559}
1560
1561void tst_QAbstractItemView::deselectInSingleSelection()
1562{
1563 QTableView view;
1564 QStandardItemModel s;
1565 s.setRowCount(10);
1566 s.setColumnCount(10);
1567 view.setModel(&s);
1568 centerOnScreen(w: &view);
1569 moveCursorAway(topLevel: &view);
1570 view.show();
1571 QVERIFY(QTest::qWaitForWindowExposed(&view));
1572 view.setSelectionMode(QAbstractItemView::SingleSelection);
1573 view.setEditTriggers(QAbstractItemView::NoEditTriggers);
1574 QApplication::setActiveWindow(&view);
1575 QVERIFY(QTest::qWaitForWindowExposed(&view));
1576 // mouse
1577 QModelIndex index22 = s.index(row: 2, column: 2);
1578 QRect rect22 = view.visualRect(index: index22);
1579 QPoint clickpos = rect22.center();
1580 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: clickpos);
1581 QCOMPARE(view.currentIndex(), index22);
1582 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 1);
1583 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ControlModifier, pos: clickpos);
1584 QCOMPARE(view.currentIndex(), index22);
1585 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 0);
1586
1587 // second click with modifier however does select
1588 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ControlModifier, pos: clickpos);
1589 QCOMPARE(view.currentIndex(), index22);
1590 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 1);
1591
1592 // keyboard
1593 QTest::keyClick(widget: &view, key: Qt::Key_Space, modifier: Qt::NoModifier);
1594 QCOMPARE(view.currentIndex(), index22);
1595 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 1);
1596 QTest::keyClick(widget: &view, key: Qt::Key_Space, modifier: Qt::ControlModifier);
1597 QCOMPARE(view.currentIndex(), index22);
1598 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 0);
1599
1600 // second keypress with modifier however does select
1601 QTest::keyClick(widget: &view, key: Qt::Key_Space, modifier: Qt::ControlModifier);
1602 QCOMPARE(view.currentIndex(), index22);
1603 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 1);
1604}
1605
1606void tst_QAbstractItemView::testNoActivateOnDisabledItem()
1607{
1608 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1609 QSKIP("Window activation is not supported");
1610
1611 QTreeView treeView;
1612 QStandardItemModel model(1, 1);
1613 QStandardItem *item = new QStandardItem("item");
1614 model.setItem(row: 0, column: 0, item);
1615 item->setFlags(Qt::NoItemFlags);
1616 treeView.setModel(&model);
1617 centerOnScreen(w: &treeView);
1618 moveCursorAway(topLevel: &treeView);
1619 treeView.show();
1620
1621 QApplication::setActiveWindow(&treeView);
1622 QVERIFY(QTest::qWaitForWindowActive(&treeView));
1623
1624 QSignalSpy activatedSpy(&treeView, &QAbstractItemView::activated);
1625
1626 // Ensure clicking on a disabled item doesn't emit itemActivated.
1627 QModelIndex itemIndex = treeView.model()->index(row: 0, column: 0);
1628 QPoint clickPos = treeView.visualRect(index: itemIndex).center();
1629 QTest::mouseClick(widget: treeView.viewport(), button: Qt::LeftButton, stateKey: {}, pos: clickPos);
1630
1631 QCOMPARE(activatedSpy.count(), 0);
1632}
1633
1634void tst_QAbstractItemView::testFocusPolicy_data()
1635{
1636 QTest::addColumn<QAbstractItemDelegate*>(name: "delegate");
1637
1638 QAbstractItemDelegate *styledItemDelegate = new QStyledItemDelegate(this);
1639 QAbstractItemDelegate *itemDelegate = new QItemDelegate(this);
1640
1641 QTest::newRow(dataTag: "QStyledItemDelegate") << styledItemDelegate;
1642 QTest::newRow(dataTag: "QItemDelegate") << itemDelegate;
1643}
1644
1645void tst_QAbstractItemView::testFocusPolicy()
1646{
1647 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1648 QSKIP("Window activation is not supported");
1649
1650 QFETCH(QAbstractItemDelegate*, delegate);
1651
1652 QWidget window;
1653 QTableView *table = new QTableView(&window);
1654 table->setItemDelegate(delegate);
1655 QVBoxLayout *layout = new QVBoxLayout(&window);
1656 layout->addWidget(table);
1657
1658 QStandardItemModel model;
1659 model.setRowCount(10);
1660 model.setColumnCount(10);
1661 table->setModel(&model);
1662 table->setCurrentIndex(model.index(row: 1, column: 1));
1663
1664 centerOnScreen(w: &window);
1665 moveCursorAway(topLevel: &window);
1666
1667 window.show();
1668 QApplication::setActiveWindow(&window);
1669 QVERIFY(QTest::qWaitForWindowActive(&window));
1670
1671 // itemview accepts focus => editor is closed => return focus to the itemview
1672 QPoint clickpos = table->visualRect(index: model.index(row: 1, column: 1)).center();
1673 QTest::mouseDClick(widget: table->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: clickpos);
1674 QWidget *editor = QApplication::focusWidget();
1675 QVERIFY(editor);
1676 QTest::keyClick(widget: editor, key: Qt::Key_Escape, modifier: Qt::NoModifier);
1677 QCOMPARE(QApplication::focusWidget(), table);
1678
1679 // itemview doesn't accept focus => editor is closed => clear the focus
1680 table->setFocusPolicy(Qt::NoFocus);
1681 QTest::mouseDClick(widget: table->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: clickpos);
1682 editor = QApplication::focusWidget();
1683 QVERIFY(editor);
1684 QTest::keyClick(widget: editor, key: Qt::Key_Escape, modifier: Qt::NoModifier);
1685 QVERIFY(!QApplication::focusWidget());
1686}
1687
1688void tst_QAbstractItemView::QTBUG31411_noSelection()
1689{
1690 QWidget window;
1691 QTableView *table = new QTableView(&window);
1692 table->setSelectionMode(QAbstractItemView::NoSelection);
1693 QVBoxLayout *layout = new QVBoxLayout(&window);
1694 layout->addWidget(table);
1695
1696 QStandardItemModel model;
1697 model.setRowCount(10);
1698 model.setColumnCount(10);
1699 table->setModel(&model);
1700 table->setCurrentIndex(model.index(row: 1, column: 1));
1701
1702 centerOnScreen(w: &window);
1703 moveCursorAway(topLevel: &window);
1704
1705 window.show();
1706 QApplication::setActiveWindow(&window);
1707 QVERIFY(QTest::qWaitForWindowActive(&window));
1708
1709 qRegisterMetaType<QItemSelection>();
1710 QSignalSpy selectionChangeSpy(table->selectionModel(), &QItemSelectionModel::selectionChanged);
1711 QVERIFY(selectionChangeSpy.isValid());
1712
1713 QPoint clickpos = table->visualRect(index: model.index(row: 1, column: 1)).center();
1714 QTest::mouseClick(widget: table->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: clickpos);
1715 QTest::mouseDClick(widget: table->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: clickpos);
1716
1717 QPointer<QWidget> editor1 = QApplication::focusWidget();
1718 QVERIFY(editor1);
1719 QTest::keyClick(widget: editor1, key: Qt::Key_Tab, modifier: Qt::NoModifier);
1720
1721 QPointer<QWidget> editor2 = QApplication::focusWidget();
1722 QVERIFY(editor2);
1723 QTest::keyClick(widget: editor2, key: Qt::Key_Escape, modifier: Qt::NoModifier);
1724
1725 QCOMPARE(selectionChangeSpy.count(), 0);
1726}
1727
1728void tst_QAbstractItemView::QTBUG39324_settingSameInstanceOfIndexWidget()
1729{
1730 QStringListModel model({ "FOO", "bar" });
1731
1732 QScopedPointer<QTableView> table(new QTableView());
1733 table->setModel(&model);
1734
1735 QModelIndex index = model.index(row: 0,column: 0);
1736 QLineEdit *lineEdit = new QLineEdit();
1737 table->setIndexWidget(index, widget: lineEdit);
1738 table->setIndexWidget(index, widget: lineEdit);
1739 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
1740 table->show();
1741}
1742
1743void tst_QAbstractItemView::shiftSelectionAfterChangingModelContents()
1744{
1745 QStringListModel model({ "A", "B", "C", "D", "E", "F" });
1746 QSortFilterProxyModel proxyModel;
1747 proxyModel.setSourceModel(&model);
1748 proxyModel.sort(column: 0, order: Qt::AscendingOrder);
1749 proxyModel.setDynamicSortFilter(true);
1750
1751 QPersistentModelIndex indexA(proxyModel.index(row: 0, column: 0));
1752 QPersistentModelIndex indexB(proxyModel.index(row: 1, column: 0));
1753 QPersistentModelIndex indexC(proxyModel.index(row: 2, column: 0));
1754 QPersistentModelIndex indexD(proxyModel.index(row: 3, column: 0));
1755 QPersistentModelIndex indexE(proxyModel.index(row: 4, column: 0));
1756 QPersistentModelIndex indexF(proxyModel.index(row: 5, column: 0));
1757
1758 QListView view;
1759 view.setModel(&proxyModel);
1760 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
1761 view.show();
1762 QVERIFY(QTest::qWaitForWindowExposed(&view));
1763
1764 // Click "C"
1765 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: view.visualRect(index: indexC).center());
1766 QModelIndexList selected = view.selectionModel()->selectedIndexes();
1767 QCOMPARE(selected.count(), 1);
1768 QVERIFY(selected.contains(indexC));
1769
1770 // Insert new item "B1"
1771 model.insertRow(arow: 0);
1772 model.setData(index: model.index(row: 0, column: 0), value: "B1");
1773
1774 // Shift-click "D" -> we expect that "C" and "D" are selected
1775 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier, pos: view.visualRect(index: indexD).center());
1776 selected = view.selectionModel()->selectedIndexes();
1777 QCOMPARE(selected.count(), 2);
1778 QVERIFY(selected.contains(indexC));
1779 QVERIFY(selected.contains(indexD));
1780
1781 // Click "D"
1782 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: view.visualRect(index: indexD).center());
1783 selected = view.selectionModel()->selectedIndexes();
1784 QCOMPARE(selected.count(), 1);
1785 QVERIFY(selected.contains(indexD));
1786
1787 // Remove items "B" and "C"
1788 model.removeRows(row: proxyModel.mapToSource(proxyIndex: indexB).row(), count: 1);
1789 model.removeRows(row: proxyModel.mapToSource(proxyIndex: indexC).row(), count: 1);
1790 QVERIFY(!indexB.isValid());
1791 QVERIFY(!indexC.isValid());
1792
1793 // Shift-click "F" -> we expect that "D", "E", and "F" are selected
1794 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier, pos: view.visualRect(index: indexF).center());
1795 selected = view.selectionModel()->selectedIndexes();
1796 QCOMPARE(selected.count(), 3);
1797 QVERIFY(selected.contains(indexD));
1798 QVERIFY(selected.contains(indexE));
1799 QVERIFY(selected.contains(indexF));
1800
1801 // Move to "A" by pressing "Up" repeatedly
1802 while (view.currentIndex() != indexA)
1803 QTest::keyClick(widget: &view, key: Qt::Key_Up);
1804 selected = view.selectionModel()->selectedIndexes();
1805 QCOMPARE(selected.count(), 1);
1806 QVERIFY(selected.contains(indexA));
1807
1808 // Change the sort order
1809 proxyModel.sort(column: 0, order: Qt::DescendingOrder);
1810
1811 // Shift-click "F" -> All items should be selected
1812 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier, pos: view.visualRect(index: indexF).center());
1813 selected = view.selectionModel()->selectedIndexes();
1814 QCOMPARE(selected.count(), model.rowCount());
1815
1816 // Restore the old sort order
1817 proxyModel.sort(column: 0, order: Qt::AscendingOrder);
1818
1819 // Click "D"
1820 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: view.visualRect(index: indexD).center());
1821 selected = view.selectionModel()->selectedIndexes();
1822 QCOMPARE(selected.count(), 1);
1823 QVERIFY(selected.contains(indexD));
1824
1825 // Insert new item "B2"
1826 model.insertRow(arow: 0);
1827 model.setData(index: model.index(row: 0, column: 0), value: "B2");
1828
1829 // Press Shift+Down -> "D" and "E" should be selected.
1830 QTest::keyClick(widget: &view, key: Qt::Key_Down, modifier: Qt::ShiftModifier);
1831 selected = view.selectionModel()->selectedIndexes();
1832 QCOMPARE(selected.count(), 2);
1833 QVERIFY(selected.contains(indexD));
1834 QVERIFY(selected.contains(indexE));
1835
1836 // Click "A" to reset the current selection starting point;
1837 //then select "D" via QAbstractItemView::setCurrentIndex(const QModelIndex&).
1838 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: view.visualRect(index: indexA).center());
1839 view.setCurrentIndex(indexD);
1840 selected = view.selectionModel()->selectedIndexes();
1841 QCOMPARE(selected.count(), 1);
1842 QVERIFY(selected.contains(indexD));
1843
1844 // Insert new item "B3"
1845 model.insertRow(arow: 0);
1846 model.setData(index: model.index(row: 0, column: 0), value: "B3");
1847
1848 // Press Shift+Down -> "D" and "E" should be selected.
1849 QTest::keyClick(widget: &view, key: Qt::Key_Down, modifier: Qt::ShiftModifier);
1850 selected = view.selectionModel()->selectedIndexes();
1851 QCOMPARE(selected.count(), 2);
1852 QVERIFY(selected.contains(indexD));
1853 QVERIFY(selected.contains(indexE));
1854}
1855
1856void tst_QAbstractItemView::QTBUG48968_reentrant_updateEditorGeometries()
1857{
1858 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1859 QSKIP("Wayland: This fails. Figure out why.");
1860
1861 QTreeView tree;
1862 QStandardItemModel *m = new QStandardItemModel(&tree);
1863 for (int i = 0; i < 10; ++i) {
1864 QStandardItem *item = new QStandardItem(QString("Item number %1").arg(a: i));
1865 item->setEditable(true);
1866 for (int j = 0; j < 5; ++j) {
1867 QStandardItem *child = new QStandardItem(QString("Child Item number %1").arg(a: j));
1868 item->setChild(row: j, column: 0, item: child);
1869 }
1870 m->setItem(row: i, column: 0, item);
1871 }
1872
1873 tree.setModel(m);
1874 tree.setRootIsDecorated(false);
1875 connect(sender: &tree, signal: &QTreeView::doubleClicked,
1876 receiver: &tree, slot: &QTreeView::setRootIndex);
1877 tree.show();
1878 QVERIFY(QTest::qWaitForWindowActive(&tree));
1879
1880 // Trigger editing idx
1881 QModelIndex idx = m->index(row: 1, column: 0);
1882 const QPoint pos = tree.visualRect(index: idx).center();
1883 QTest::mouseClick(widget: tree.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos);
1884 QTest::mouseDClick(widget: tree.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos);
1885
1886 // Add more children to idx
1887 QStandardItem *item = m->itemFromIndex(index: idx);
1888 for (int j = 5; j < 10; ++j) {
1889 QStandardItem *child = new QStandardItem(QString("Child Item number %1").arg(a: j));
1890 item->setChild(row: j, column: 0, item: child);
1891 }
1892
1893 // No crash, all fine.
1894}
1895
1896class ScrollModeProxyStyle: public QProxyStyle
1897{
1898 Q_OBJECT
1899public:
1900 ScrollModeProxyStyle(QAbstractItemView::ScrollMode sm, QStyle *style = nullptr)
1901 : QProxyStyle(style)
1902 , scrollMode(sm == QAbstractItemView::ScrollPerItem ?
1903 QAbstractItemView::ScrollPerPixel : QAbstractItemView::ScrollPerItem)
1904 { }
1905
1906 int styleHint(QStyle::StyleHint hint, const QStyleOption *opt,
1907 const QWidget *w, QStyleHintReturn *returnData) const override
1908 {
1909 if (hint == SH_ItemView_ScrollMode)
1910 return scrollMode;
1911
1912 return baseStyle()->styleHint(stylehint: hint, opt, widget: w, returnData);
1913 }
1914
1915 QAbstractItemView::ScrollMode scrollMode;
1916};
1917
1918void tst_QAbstractItemView::QTBUG50102_SH_ItemView_ScrollMode()
1919{
1920 QListView view;
1921
1922 // Default comes from the style
1923 auto styleScrollMode = static_cast<QAbstractItemView::ScrollMode>(
1924 view.style()->styleHint(stylehint: QStyle::SH_ItemView_ScrollMode, opt: nullptr, widget: &view, returnData: nullptr));
1925 QCOMPARE(view.verticalScrollMode(), styleScrollMode);
1926 QCOMPARE(view.horizontalScrollMode(), styleScrollMode);
1927
1928 // Change style, get new value
1929 ScrollModeProxyStyle proxyStyle1(styleScrollMode);
1930 view.setStyle(&proxyStyle1);
1931 auto proxyScrollMode = static_cast<QAbstractItemView::ScrollMode>(
1932 view.style()->styleHint(stylehint: QStyle::SH_ItemView_ScrollMode, opt: nullptr, widget: &view, returnData: nullptr));
1933 QVERIFY(styleScrollMode != proxyScrollMode);
1934 QCOMPARE(view.verticalScrollMode(), proxyScrollMode);
1935 QCOMPARE(view.horizontalScrollMode(), proxyScrollMode);
1936
1937 // Explicitly set vertical, same value
1938 view.setVerticalScrollMode(proxyScrollMode);
1939 QCOMPARE(view.verticalScrollMode(), proxyScrollMode);
1940 QCOMPARE(view.horizontalScrollMode(), proxyScrollMode);
1941
1942 // Change style, won't change value for vertical, will change for horizontal
1943 ScrollModeProxyStyle proxyStyle2(proxyScrollMode);
1944 view.setStyle(&proxyStyle2);
1945 QCOMPARE(view.verticalScrollMode(), proxyScrollMode);
1946 QCOMPARE(view.horizontalScrollMode(), styleScrollMode);
1947}
1948
1949void tst_QAbstractItemView::QTBUG50535_update_on_new_selection_model()
1950{
1951 QStandardItemModel model;
1952 for (int i = 0; i < 10; ++i)
1953 model.appendRow(aitem: new QStandardItem(QStringLiteral("%1").arg(a: i)));
1954
1955 class ListView : public QListView
1956 {
1957 public:
1958 using QListView::QListView;
1959
1960 void setSelectionModel(QItemSelectionModel *model) override
1961 {
1962 m_deselectedMustBeEmpty = !selectionModel() || !model || selectionModel()->model() != model->model();
1963 QListView::setSelectionModel(model);
1964 m_deselectedMustBeEmpty = false;
1965 }
1966 int m_paintEventsCount = 0;
1967 bool selectionChangedOk() const { return m_selectionChangedOk; }
1968
1969 protected:
1970 bool viewportEvent(QEvent *event) override
1971 {
1972 if (event->type() == QEvent::Paint)
1973 ++m_paintEventsCount;
1974 return QListView::viewportEvent(event);
1975 }
1976
1977 void selectionChanged(const QItemSelection &selected,
1978 const QItemSelection &deselected) override
1979 {
1980 if (m_deselectedMustBeEmpty && !deselected.isEmpty())
1981 m_selectionChangedOk = false;
1982
1983 // Make sure both selections belong to the same model
1984 const auto selIndexes = selected.indexes();
1985 const auto deselIndexes = deselected.indexes();
1986 for (const QModelIndex &nmi : selIndexes) {
1987 for (const QModelIndex &omi : deselIndexes)
1988 m_selectionChangedOk = m_selectionChangedOk && (nmi.model() == omi.model());
1989 }
1990 QListView::selectionChanged(selected, deselected);
1991 }
1992 private:
1993 bool m_deselectedMustBeEmpty = false;
1994 bool m_selectionChangedOk = true;
1995 };
1996
1997 // keep the current/selected row in the "low range", i.e. be sure it's visible, otherwise we
1998 // don't get updates and the test fails.
1999
2000 ListView view;
2001 view.setModel(&model);
2002 view.selectionModel()->setCurrentIndex(index: model.index(row: 1, column: 0), command: QItemSelectionModel::SelectCurrent);
2003 view.show();
2004 QVERIFY(QTest::qWaitForWindowExposed(&view));
2005 QVERIFY(view.selectionChangedOk());
2006
2007 QItemSelectionModel selectionModel(&model);
2008 selectionModel.setCurrentIndex(index: model.index(row: 2, column: 0), command: QItemSelectionModel::Current);
2009
2010 int oldPaintEventsCount = view.m_paintEventsCount;
2011 view.setSelectionModel(&selectionModel);
2012 QTRY_VERIFY(view.m_paintEventsCount > oldPaintEventsCount);
2013 QVERIFY(view.selectionChangedOk());
2014
2015
2016 QItemSelectionModel selectionModel2(&model);
2017 selectionModel2.select(index: model.index(row: 0, column: 0), command: QItemSelectionModel::ClearAndSelect);
2018 selectionModel2.setCurrentIndex(index: model.index(row: 1, column: 0), command: QItemSelectionModel::Current);
2019
2020 oldPaintEventsCount = view.m_paintEventsCount;
2021 view.setSelectionModel(&selectionModel2);
2022 QTRY_VERIFY(view.m_paintEventsCount > oldPaintEventsCount);
2023 QVERIFY(view.selectionChangedOk());
2024
2025 // Tests QAbstractItemView::selectionChanged
2026 QStandardItemModel model1;
2027 for (int i = 0; i < 10; ++i)
2028 model1.appendRow(aitem: new QStandardItem(QString::number(i)));
2029 view.setModel(&model1);
2030
2031 QItemSelectionModel selectionModel1(&model1);
2032 selectionModel1.select(index: model1.index(row: 0, column: 0), command: QItemSelectionModel::ClearAndSelect);
2033 selectionModel1.setCurrentIndex(index: model1.index(row: 1, column: 0), command: QItemSelectionModel::Current);
2034 view.setSelectionModel(&selectionModel1);
2035 QVERIFY(view.selectionChangedOk());
2036}
2037
2038void tst_QAbstractItemView::testSelectionModelInSyncWithView()
2039{
2040 QStandardItemModel model;
2041 for (int i = 0; i < 10; ++i)
2042 model.appendRow(aitem: new QStandardItem(QString::number(i)));
2043
2044 class ListView : public QListView
2045 {
2046 public:
2047 using QListView::selectedIndexes;
2048 };
2049
2050 ListView view;
2051 QVERIFY(!view.selectionModel());
2052
2053 view.setModel(&model);
2054 QVERIFY(view.selectionModel());
2055 QVERIFY(view.selectedIndexes().isEmpty());
2056 QVERIFY(view.selectionModel()->selection().isEmpty());
2057
2058 view.setCurrentIndex(model.index(row: 0, column: 0));
2059 QCOMPARE(view.currentIndex(), model.index(0, 0));
2060 QCOMPARE(view.selectionModel()->currentIndex(), model.index(0, 0));
2061
2062 view.selectionModel()->setCurrentIndex(index: model.index(row: 1, column: 0), command: QItemSelectionModel::SelectCurrent);
2063 QCOMPARE(view.currentIndex(), model.index(1, 0));
2064 QCOMPARE(view.selectedIndexes(), QModelIndexList() << model.index(1, 0));
2065 QCOMPARE(view.selectionModel()->currentIndex(), model.index(1, 0));
2066 QCOMPARE(view.selectionModel()->selection().indexes(), QModelIndexList() << model.index(1, 0));
2067
2068 view.show();
2069 QVERIFY(QTest::qWaitForWindowExposed(&view));
2070
2071 QItemSelectionModel selectionModel(&model);
2072 selectionModel.setCurrentIndex(index: model.index(row: 2, column: 0), command: QItemSelectionModel::Current);
2073
2074 view.setSelectionModel(&selectionModel);
2075 QCOMPARE(view.currentIndex(), model.index(2, 0));
2076 QCOMPARE(view.selectedIndexes(), QModelIndexList());
2077 QCOMPARE(view.selectionModel()->currentIndex(), model.index(2, 0));
2078 QCOMPARE(view.selectionModel()->selection().indexes(), QModelIndexList());
2079
2080
2081 QItemSelectionModel selectionModel2(&model);
2082 selectionModel2.select(index: model.index(row: 0, column: 0), command: QItemSelectionModel::ClearAndSelect);
2083 selectionModel2.setCurrentIndex(index: model.index(row: 1, column: 0), command: QItemSelectionModel::Current);
2084
2085 view.setSelectionModel(&selectionModel2);
2086 QCOMPARE(view.currentIndex(), model.index(1, 0));
2087 QCOMPARE(view.selectedIndexes(), QModelIndexList() << model.index(0, 0));
2088 QCOMPARE(view.selectionModel()->currentIndex(), model.index(1, 0));
2089 QCOMPARE(view.selectionModel()->selection().indexes(), QModelIndexList() << model.index(0, 0));
2090}
2091
2092class SetSelectionTestView : public QListView
2093{
2094 Q_OBJECT
2095public:
2096 using QListView::QListView;
2097signals:
2098 void setSelectionCalled(const QRect &rect);
2099
2100protected:
2101 void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override
2102 {
2103 emit setSelectionCalled(rect);
2104 QListView::setSelection(rect, command: flags);
2105 }
2106};
2107
2108void tst_QAbstractItemView::testClickToSelect()
2109{
2110 // This test verifies that the QRect that is passed from QAbstractItemView::mousePressEvent
2111 // to the virtual method QAbstractItemView::setSelection(const QRect &, SelectionFlags)
2112 // is the 1x1 rect which conains exactly the clicked pixel if no modifiers are pressed.
2113
2114 QStringListModel model({ "A", "B", "C" });
2115
2116 SetSelectionTestView view;
2117 view.setModel(&model);
2118 view.show();
2119 QVERIFY(QTest::qWaitForWindowExposed(&view));
2120
2121 QSignalSpy spy(&view, &SetSelectionTestView::setSelectionCalled);
2122
2123 const QModelIndex indexA(model.index(row: 0, column: 0));
2124 const QRect visualRectA = view.visualRect(index: indexA);
2125 const QPoint centerA = visualRectA.center();
2126
2127 // Click the center of the visualRect of item "A"
2128 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: centerA);
2129 QCOMPARE(spy.count(), 1);
2130 QCOMPARE(spy.back().front().value<QRect>(), QRect(centerA, QSize(1, 1)));
2131
2132 // Click a point slightly away from the center
2133 const QPoint nearCenterA = centerA + QPoint(1, 1);
2134 QVERIFY(visualRectA.contains(nearCenterA));
2135 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: nearCenterA);
2136 QCOMPARE(spy.count(), 2);
2137 QCOMPARE(spy.back().front().value<QRect>(), QRect(nearCenterA, QSize(1, 1)));
2138}
2139
2140void tst_QAbstractItemView::testDialogAsEditor()
2141{
2142 DialogItemDelegate delegate;
2143
2144 QStandardItemModel model;
2145 model.appendRow(aitem: new QStandardItem(QStringLiteral("editme")));
2146
2147 QListView view;
2148 view.setItemDelegate(&delegate);
2149 view.setModel(&model);
2150 view.show();
2151 QVERIFY(QTest::qWaitForWindowExposed(&view));
2152
2153 view.edit(index: model.index(row: 0,column: 0));
2154
2155 QVERIFY(QTest::qWaitForWindowExposed(delegate.openedEditor));
2156
2157 delegate.openedEditor->reject();
2158 QApplication::processEvents();
2159
2160 QCOMPARE(delegate.result, QDialog::Rejected);
2161
2162 view.edit(index: model.index(row: 0,column: 0));
2163
2164 QVERIFY(QTest::qWaitForWindowExposed(delegate.openedEditor));
2165
2166 delegate.openedEditor->accept();
2167 QApplication::processEvents();
2168
2169 QCOMPARE(delegate.result, QDialog::Accepted);
2170}
2171
2172class HoverItemDelegate : public QStyledItemDelegate
2173{
2174public:
2175 using QStyledItemDelegate::QStyledItemDelegate;
2176 void paint(QPainter *painter, const QStyleOptionViewItem &opt,
2177 const QModelIndex &index) const override
2178 {
2179 Q_UNUSED(painter);
2180
2181 if (!(opt.state & QStyle::State_MouseOver)) {
2182
2183 // We don't want to set m_paintedWithoutHover for any item so check for the item at 0,0
2184 if (index.row() == 0 && index.column() == 0)
2185 m_paintedWithoutHover = true;
2186 }
2187 }
2188
2189 mutable bool m_paintedWithoutHover = false;
2190};
2191
2192void tst_QAbstractItemView::QTBUG46785_mouseout_hover_state()
2193{
2194 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
2195 QSKIP("Wayland: This fails. Figure out why.");
2196
2197 HoverItemDelegate delegate;
2198
2199 QTableWidget table(5, 5);
2200 table.verticalHeader()->hide();
2201 table.horizontalHeader()->hide();
2202 table.setMouseTracking(true);
2203 table.setItemDelegate(&delegate);
2204 centerOnScreen(w: &table);
2205 table.show();
2206 QVERIFY(QTest::qWaitForWindowActive(&table));
2207
2208 QModelIndex item = table.model()->index(row: 0, column: 0);
2209 QRect itemRect = table.visualRect(index: item);
2210
2211 // Move the mouse into the center of the item at 0,0 to cause a paint event to occur
2212 QTest::mouseMove(widget: table.viewport(), pos: itemRect.center());
2213 QTest::mouseClick(widget: table.viewport(), button: Qt::LeftButton, stateKey: {}, pos: itemRect.center());
2214
2215 delegate.m_paintedWithoutHover = false;
2216
2217 QTest::mouseMove(widget: table.viewport(), pos: QPoint(-50, 0));
2218
2219#ifdef Q_OS_WINRT
2220 QEXPECT_FAIL("", "QTest::mouseMove does not work on WinRT", Abort);
2221#endif
2222 QTRY_VERIFY(delegate.m_paintedWithoutHover);
2223}
2224
2225void tst_QAbstractItemView::testClearModelInClickedSignal()
2226{
2227 QStringListModel model({"A", "B"});
2228
2229 QListView view;
2230 view.setModel(&model);
2231 view.show();
2232
2233 connect(sender: &view, signal: &QListView::clicked, slot: [&view](const QModelIndex &index)
2234 {
2235 view.setModel(nullptr);
2236 QCOMPARE(index.data().toString(), QStringLiteral("B"));
2237 });
2238
2239 QModelIndex index = view.model()->index(row: 1, column: 0);
2240 QVERIFY(index.isValid());
2241 QPoint p = view.visualRect(index).center();
2242
2243 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2244
2245 QCOMPARE(view.model(), nullptr);
2246}
2247
2248void tst_QAbstractItemView::inputMethodEnabled_data()
2249{
2250 QTest::addColumn<QByteArray>(name: "viewType");
2251 QTest::addColumn<Qt::ItemFlags>(name: "itemFlags");
2252 QTest::addColumn<bool>(name: "result");
2253
2254 const QVector<QByteArray> widgets{ "QListView", "QTreeView", "QTableView" };
2255 for (const QByteArray &widget : widgets) {
2256 QTest::newRow(dataTag: widget + ": no flags")
2257 << widget << Qt::ItemFlags(Qt::NoItemFlags) << false;
2258 QTest::newRow(dataTag: widget + ": checkable")
2259 << widget << Qt::ItemFlags(Qt::ItemIsUserCheckable) << false;
2260 QTest::newRow(dataTag: widget + ": selectable")
2261 << widget << Qt::ItemFlags(Qt::ItemIsSelectable) << false;
2262 QTest::newRow(dataTag: widget + ": enabled")
2263 << widget << Qt::ItemFlags(Qt::ItemIsEnabled) << false;
2264 QTest::newRow(dataTag: widget + ": selectable|enabled")
2265 << widget << Qt::ItemFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled) << false;
2266 QTest::newRow(dataTag: widget + ": editable|enabled")
2267 << widget << Qt::ItemFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled) << true;
2268 QTest::newRow(dataTag: widget + ": editable|enabled|selectable")
2269 << widget << Qt::ItemFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable) << true;
2270 }
2271}
2272
2273void tst_QAbstractItemView::inputMethodEnabled()
2274{
2275 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
2276 QSKIP("Window activation is not supported");
2277
2278 QFETCH(QByteArray, viewType);
2279 QFETCH(Qt::ItemFlags, itemFlags);
2280 QFETCH(bool, result);
2281
2282 QScopedPointer<QAbstractItemView> view(viewFromString(viewType));
2283
2284 centerOnScreen(w: view.data());
2285 view->show();
2286 QVERIFY(QTest::qWaitForWindowExposed(view.data()));
2287
2288 QStandardItemModel *model = new QStandardItemModel(view.data());
2289 QStandardItem *item = new QStandardItem("first item");
2290 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2291 model->appendRow(aitem: item);
2292
2293 QStandardItem *secondItem = new QStandardItem("test item");
2294 secondItem->setFlags(Qt::ItemFlags(itemFlags));
2295 model->appendRow(aitem: secondItem);
2296
2297 view->setModel(model);
2298
2299 // Check current changed
2300 view->setCurrentIndex(model->index(row: 0, column: 0));
2301 QVERIFY(!view->testAttribute(Qt::WA_InputMethodEnabled));
2302 view->setCurrentIndex(model->index(row: 1, column: 0));
2303 QCOMPARE(view->testAttribute(Qt::WA_InputMethodEnabled), result);
2304 view->setCurrentIndex(model->index(row: 0, column: 0));
2305 QVERIFY(!view->testAttribute(Qt::WA_InputMethodEnabled));
2306
2307 // Check focus by switching the activation of the window to force a focus in
2308 view->setCurrentIndex(model->index(row: 1, column: 0));
2309 QApplication::setActiveWindow(nullptr);
2310 QApplication::setActiveWindow(view.data());
2311 QVERIFY(QTest::qWaitForWindowActive(view.data()));
2312 QCOMPARE(view->testAttribute(Qt::WA_InputMethodEnabled), result);
2313
2314 view->setCurrentIndex(QModelIndex());
2315 QVERIFY(!view->testAttribute(Qt::WA_InputMethodEnabled));
2316 QApplication::setActiveWindow(nullptr);
2317 QApplication::setActiveWindow(view.data());
2318 QVERIFY(QTest::qWaitForWindowActive(view.data()));
2319 QModelIndex index = model->index(row: 1, column: 0);
2320 QPoint p = view->visualRect(index).center();
2321 QTest::mouseClick(widget: view->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2322 if (itemFlags & Qt::ItemIsEnabled)
2323 QCOMPARE(view->currentIndex(), index);
2324 QCOMPARE(view->testAttribute(Qt::WA_InputMethodEnabled), result);
2325
2326 index = model->index(row: 0, column: 0);
2327 QApplication::setActiveWindow(nullptr);
2328 QApplication::setActiveWindow(view.data());
2329 QVERIFY(QTest::qWaitForWindowActive(view.data()));
2330 p = view->visualRect(index).center();
2331 QTest::mouseClick(widget: view->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2332 QCOMPARE(view->currentIndex(), index);
2333 QVERIFY(!view->testAttribute(Qt::WA_InputMethodEnabled));
2334
2335 // There is a case when it goes to the first visible item so we
2336 // make the flags of the first item match the ones we are testing
2337 // to check the attribute correctly
2338 QApplication::setActiveWindow(nullptr);
2339 view->setCurrentIndex(QModelIndex());
2340 view->reset();
2341 item->setFlags(Qt::ItemFlags(itemFlags));
2342 QApplication::setActiveWindow(view.data());
2343 QVERIFY(QTest::qWaitForWindowActive(view.data()));
2344 QCOMPARE(view->testAttribute(Qt::WA_InputMethodEnabled), result);
2345}
2346
2347void tst_QAbstractItemView::currentFollowsIndexWidget_data()
2348{
2349 QTest::addColumn<QByteArray>(name: "viewType");
2350
2351 const QVector<QByteArray> widgets{ "QListView", "QTreeView", "QTableView" };
2352 for (const QByteArray &widget : widgets)
2353 QTest::newRow(dataTag: widget) << widget;
2354}
2355
2356void tst_QAbstractItemView::currentFollowsIndexWidget()
2357{
2358 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
2359 QSKIP("Window activation is not supported");
2360
2361 QFETCH(QByteArray, viewType);
2362
2363 QScopedPointer<QAbstractItemView> view(viewFromString(viewType));
2364
2365 centerOnScreen(w: view.data());
2366 view->show();
2367 QVERIFY(QTest::qWaitForWindowExposed(view.data()));
2368
2369 QStandardItemModel *model = new QStandardItemModel(view.data());
2370 QStandardItem *item1 = new QStandardItem("first item");
2371 item1->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2372 model->appendRow(aitem: item1);
2373
2374 QStandardItem *item2 = new QStandardItem("test item");
2375 item2->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2376 model->appendRow(aitem: item2);
2377
2378 view->setModel(model);
2379 QLineEdit *lineEdit1 = new QLineEdit;
2380 QLineEdit *lineEdit2 = new QLineEdit;
2381 view->setIndexWidget(index: item1->index(), widget: lineEdit1);
2382 view->setIndexWidget(index: item2->index(), widget: lineEdit2);
2383
2384 lineEdit2->setFocus();
2385 QTRY_VERIFY(lineEdit2->hasFocus());
2386 QCOMPARE(view->currentIndex(), item2->index());
2387 lineEdit1->setFocus();
2388 QTRY_VERIFY(lineEdit1->hasFocus());
2389 QCOMPARE(view->currentIndex(), item1->index());
2390}
2391
2392class EditorItemDelegate : public QStyledItemDelegate
2393{
2394 Q_OBJECT
2395public:
2396 using QStyledItemDelegate::QStyledItemDelegate;
2397 QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
2398 const QModelIndex &) const override
2399 {
2400 openedEditor = new QLineEdit(parent);
2401 return openedEditor;
2402 }
2403 mutable QPointer<QWidget> openedEditor = nullptr;
2404};
2405
2406// Testing the case reported in QTBUG-62253.
2407// When an itemview with an editor that has focus loses focus
2408// due to a change in the active window then we need to check
2409// that the itemview gets focus once the activation is back
2410// on the original window.
2411void tst_QAbstractItemView::checkFocusAfterActivationChanges_data()
2412{
2413 currentFollowsIndexWidget_data();
2414}
2415
2416void tst_QAbstractItemView::checkFocusAfterActivationChanges()
2417{
2418 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
2419 QSKIP("Window activation is not supported");
2420
2421 QFETCH(QByteArray, viewType);
2422
2423 const QRect availableGeo = QGuiApplication::primaryScreen()->availableGeometry();
2424 const int halfWidth = availableGeo.width() / 2;
2425 QWidget otherTopLevel;
2426 otherTopLevel.setGeometry(ax: availableGeo.x(), ay: availableGeo.y(),
2427 aw: halfWidth, ah: availableGeo.height());
2428 otherTopLevel.show();
2429
2430 QWidget w;
2431 w.setGeometry(ax: availableGeo.x() + halfWidth, ay: availableGeo.y(),
2432 aw: halfWidth, ah: availableGeo.height());
2433 QLineEdit *le = new QLineEdit(&w);
2434 QAbstractItemView *view = viewFromString(viewType, parent: &w);
2435
2436 QStandardItemModel model(5, 5);
2437 view->setModel(&model);
2438 view->move(ax: 0, ay: 50);
2439 EditorItemDelegate delegate;
2440 view->setItemDelegate(&delegate);
2441 w.show();
2442
2443 QVERIFY(QTest::qWaitForWindowActive(&w));
2444 QVERIFY(le->hasFocus());
2445
2446 view->setFocus();
2447 QVERIFY(view->hasFocus());
2448
2449 view->edit(index: model.index(row: 0,column: 0));
2450 QVERIFY(QTest::qWaitForWindowExposed(delegate.openedEditor));
2451 QVERIFY(delegate.openedEditor->hasFocus());
2452
2453 QApplication::setActiveWindow(&otherTopLevel);
2454 otherTopLevel.setFocus();
2455 QTRY_VERIFY(!delegate.openedEditor);
2456
2457 QApplication::setActiveWindow(&w);
2458 QVERIFY(QTest::qWaitForWindowActive(&w));
2459 QVERIFY(view->hasFocus());
2460}
2461
2462void tst_QAbstractItemView::dragSelectAfterNewPress()
2463{
2464 QStandardItemModel model;
2465 for (int i = 0; i < 10; ++i) {
2466 QStandardItem *item = new QStandardItem(QString::number(i));
2467 model.setItem(row: i, column: 0, item);
2468 }
2469
2470 QListView view;
2471 view.setFixedSize(w: 160, h: 650); // Minimum width for windows with frame on Windows 8
2472 view.setSelectionMode(QListView::ExtendedSelection);
2473 view.setModel(&model);
2474 centerOnScreen(w: &view);
2475 moveCursorAway(topLevel: &view);
2476 view.show();
2477 QVERIFY(QTest::qWaitForWindowExposed(&view));
2478
2479 QModelIndex index0 = model.index(row: 0, column: 0);
2480 QModelIndex index2 = model.index(row: 2, column: 0);
2481
2482 view.setCurrentIndex(index0);
2483 QCOMPARE(view.currentIndex(), index0);
2484
2485 // Select item 0 using a single click
2486 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier,
2487 pos: view.visualRect(index: index0).center());
2488 QCOMPARE(view.currentIndex(), index0);
2489
2490 // Press to select item 2
2491 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier,
2492 pos: view.visualRect(index: index2).center());
2493 QCOMPARE(view.currentIndex(), index2);
2494
2495 // Verify that the selection worked OK
2496 QModelIndexList selected = view.selectionModel()->selectedIndexes();
2497 QCOMPARE(selected.count(), 3);
2498 for (int i = 0; i < 2; ++i)
2499 QVERIFY(selected.contains(model.index(i, 0)));
2500
2501 QModelIndex index5 = model.index(row: 5, column: 0);
2502 const QPoint releasePos = view.visualRect(index: index5).center();
2503 // The mouse move event has to be created manually because the QTest framework does not
2504 // contain a function for mouse moves with buttons pressed
2505 QMouseEvent moveEvent2(QEvent::MouseMove, releasePos, Qt::NoButton, Qt::LeftButton,
2506 Qt::ShiftModifier);
2507 const bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent2);
2508 QVERIFY(moveEventReceived);
2509 QTest::mouseRelease(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier, pos: releasePos);
2510 QCOMPARE(view.currentIndex(), index5);
2511
2512 // Verify that the selection worked OK
2513 selected = view.selectionModel()->selectedIndexes();
2514 QCOMPARE(selected.count(), 6);
2515 for (int i = 0; i < 5; ++i)
2516 QVERIFY(selected.contains(model.index(i, 0)));
2517}
2518
2519void tst_QAbstractItemView::dragWithSecondClick_data()
2520{
2521 QTest::addColumn<QString>(name: "viewClass");
2522 QTest::addColumn<bool>(name: "doubleClick");
2523 for (QString viewClass : {"QListView", "QTreeView"}) {
2524 QTest::addRow(format: "DoubleClick") << viewClass << true;
2525 QTest::addRow(format: "Two Single Clicks") << viewClass << false;
2526 }
2527}
2528
2529// inject the ability to record which indexes get dragged into any QAbstractItemView class
2530struct DragRecorder
2531{
2532 virtual ~DragRecorder() = default;
2533 bool dragStarted = false;
2534 QModelIndexList draggedIndexes;
2535 QAbstractItemView *view;
2536};
2537
2538template<class ViewClass>
2539class DragRecorderView : public ViewClass, public DragRecorder
2540{
2541public:
2542 DragRecorderView()
2543 { view = this; }
2544protected:
2545 void startDrag(Qt::DropActions) override
2546 {
2547 draggedIndexes = ViewClass::selectedIndexes();
2548 dragStarted = true;
2549 }
2550};
2551
2552void tst_QAbstractItemView::dragWithSecondClick()
2553{
2554 QFETCH(QString, viewClass);
2555 QFETCH(bool, doubleClick);
2556
2557 QStandardItemModel model;
2558 QStandardItem *parentItem = model.invisibleRootItem();
2559 for (int i = 0; i < 10; ++i) {
2560 QStandardItem *item = new QStandardItem(QString("item %0").arg(a: i));
2561 item->setDragEnabled(true);
2562 item->setEditable(false);
2563 parentItem->appendRow(aitem: item);
2564 }
2565
2566 std::unique_ptr<DragRecorder> dragRecorder;
2567 if (viewClass == "QTreeView")
2568 dragRecorder.reset(p: new DragRecorderView<QTreeView>);
2569 else if (viewClass == "QListView")
2570 dragRecorder.reset(p: new DragRecorderView<QListView>);
2571
2572 QAbstractItemView *view = dragRecorder->view;
2573 view->setModel(&model);
2574 view->setFixedSize(w: 160, h: 650); // Minimum width for windows with frame on Windows 8
2575 view->setSelectionMode(QAbstractItemView::MultiSelection);
2576 view->setDragDropMode(QAbstractItemView::InternalMove);
2577 centerOnScreen(w: view);
2578 moveCursorAway(topLevel: view);
2579 view->show();
2580 QVERIFY(QTest::qWaitForWindowExposed(view));
2581
2582 QModelIndex index0 = model.index(row: 0, column: 0);
2583 QModelIndex index1 = model.index(row: 1, column: 0);
2584 // Select item 0 using a single click
2585 QTest::mouseClick(widget: view->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier,
2586 pos: view->visualRect(index: index0).center());
2587 QCOMPARE(view->currentIndex(), index0);
2588
2589 if (doubleClick) {
2590 // press on same item within the double click interval
2591 QTest::mouseDClick(widget: view->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier,
2592 pos: view->visualRect(index: index0).center());
2593 } else {
2594 // or on different item with a slow second press
2595 QTest::mousePress(widget: view->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier,
2596 pos: view->visualRect(index: index1).center());
2597 }
2598 // then drag far enough with left button held
2599 const QPoint dragTo = view->visualRect(index: index1).center()
2600 + QPoint(2 * QApplication::startDragDistance(),
2601 2 * QApplication::startDragDistance());
2602 QMouseEvent mouseMoveEvent(QEvent::MouseMove, dragTo,
2603 Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
2604 QVERIFY(QApplication::sendEvent(view->viewport(), &mouseMoveEvent));
2605 // twice since the view will first enter dragging state, then start the drag
2606 // (not necessary to actually move the mouse)
2607 QVERIFY(QApplication::sendEvent(view->viewport(), &mouseMoveEvent));
2608 QVERIFY(dragRecorder->dragStarted);
2609 QTest::mouseRelease(widget: view->viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: dragTo);
2610}
2611
2612void tst_QAbstractItemView::clickAfterDoubleClick()
2613{
2614 QTableWidget view(5, 5);
2615 view.horizontalHeader()->hide();
2616 view.verticalHeader()->hide();
2617 view.setEditTriggers(QAbstractItemView::NoEditTriggers);
2618 view.show();
2619 QVERIFY(QTest::qWaitForWindowExposed(&view));
2620 const QModelIndex index = view.model()->index(row: 1, column: 1);
2621 QVERIFY(index.isValid());
2622 const QPoint clickPoint = view.visualRect(index).center();
2623
2624 // must use the QWindow overloads so that modality is respected
2625 QWindow *window = view.window()->windowHandle();
2626 int clickCount = 0;
2627
2628 connect(sender: &view, signal: &QAbstractItemView::doubleClicked, slot: [&]{
2629 QDialog dialog(&view);
2630 dialog.setModal(true);
2631 QTimer::singleShot(interval: 0, slot: [&]{ dialog.close(); });
2632 dialog.exec();
2633 });
2634 connect(sender: &view, signal: &QAbstractItemView::clicked, slot: [&]{
2635 ++clickCount;
2636 });
2637
2638 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: clickPoint);
2639 QCOMPARE(clickCount, 1);
2640 // generates a click followed by a double click; double click opens
2641 // dialog that eats second release
2642 QTest::mouseDClick(window, button: Qt::LeftButton, stateKey: {}, pos: clickPoint);
2643 QCOMPARE(clickCount, 2);
2644 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: clickPoint);
2645 QCOMPARE(clickCount, 3);
2646}
2647
2648QTEST_MAIN(tst_QAbstractItemView)
2649#include "tst_qabstractitemview.moc"
2650

source code of qtbase/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp