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 | |
56 | Q_DECLARE_METATYPE(Qt::ItemFlags); |
57 | |
58 | using namespace QTestPrivate; |
59 | using IntList = QVector<int>; |
60 | |
61 | // Move cursor out of widget area to avoid undesired interaction on Mac. |
62 | static 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 | |
71 | class GeometriesTestView : public QTableView |
72 | { |
73 | Q_OBJECT |
74 | public: |
75 | using QTableView::QTableView; |
76 | using QTableView::selectedIndexes; |
77 | bool updateGeometriesCalled = false; |
78 | protected slots: |
79 | void updateGeometries() override { updateGeometriesCalled = true; QTableView::updateGeometries(); } |
80 | }; |
81 | |
82 | class tst_QAbstractItemView : public QObject |
83 | { |
84 | Q_OBJECT |
85 | |
86 | public: |
87 | void basic_tests(QAbstractItemView *view); |
88 | |
89 | private 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 | |
158 | private: |
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 | |
174 | class MyAbstractItemDelegate : public QAbstractItemDelegate |
175 | { |
176 | public: |
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 | |
196 | class DialogItemDelegate : public QStyledItemDelegate |
197 | { |
198 | public: |
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 |
220 | void 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 | |
300 | void tst_QAbstractItemView::cleanup() |
301 | { |
302 | QVERIFY(QApplication::topLevelWidgets().isEmpty()); |
303 | } |
304 | |
305 | void 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 | |
314 | void 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 | |
331 | void tst_QAbstractItemView::setModel_data() |
332 | { |
333 | emptyModels_data(); |
334 | } |
335 | |
336 | void 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 | |
352 | void 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 | |
549 | void 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 | |
575 | void 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 | |
599 | void 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 | |
621 | void 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 | |
643 | void 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 | |
668 | void 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 | |
679 | void 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 | |
690 | void 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 | |
730 | static 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 | |
741 | static 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 | |
750 | static 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 | |
759 | class 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; |
771 | public: |
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 | |
779 | class 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 | |
813 | public: |
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 | |
832 | class 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 | |
857 | public: |
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 | |
868 | void 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 | |
908 | void 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 | |
945 | class TestModel : public QStandardItemModel |
946 | { |
947 | Q_OBJECT |
948 | public: |
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 | |
963 | void 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 | |
986 | void 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 | |
1028 | void 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 | |
1056 | void 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 | |
1077 | void 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 | |
1107 | void 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 | |
1152 | void 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 | |
1195 | void 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 | |
1217 | void 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 | |
1251 | void 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 | |
1286 | void 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 | |
1361 | void 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 | |
1405 | void 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 | |
1452 | void 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 | |
1477 | void 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 | |
1489 | void 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 | |
1522 | class StateChangeDelegate : public QStyledItemDelegate |
1523 | { |
1524 | Q_OBJECT |
1525 | public: |
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 | |
1536 | void 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 | |
1561 | void 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 | |
1606 | void 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 | |
1634 | void 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 | |
1645 | void 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 | |
1688 | void 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 | |
1728 | void 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 | |
1743 | void 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 | |
1856 | void 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 | |
1896 | class ScrollModeProxyStyle: public QProxyStyle |
1897 | { |
1898 | Q_OBJECT |
1899 | public: |
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 | |
1918 | void 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 | |
1949 | void 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 | |
2038 | void 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 | |
2092 | class SetSelectionTestView : public QListView |
2093 | { |
2094 | Q_OBJECT |
2095 | public: |
2096 | using QListView::QListView; |
2097 | signals: |
2098 | void setSelectionCalled(const QRect &rect); |
2099 | |
2100 | protected: |
2101 | void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override |
2102 | { |
2103 | emit setSelectionCalled(rect); |
2104 | QListView::setSelection(rect, command: flags); |
2105 | } |
2106 | }; |
2107 | |
2108 | void 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 | |
2140 | void 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 | |
2172 | class HoverItemDelegate : public QStyledItemDelegate |
2173 | { |
2174 | public: |
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 | |
2192 | void 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 | |
2225 | void 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 | |
2248 | void 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 | |
2273 | void 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 | |
2347 | void 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 | |
2356 | void 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 | |
2392 | class EditorItemDelegate : public QStyledItemDelegate |
2393 | { |
2394 | Q_OBJECT |
2395 | public: |
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. |
2411 | void tst_QAbstractItemView::checkFocusAfterActivationChanges_data() |
2412 | { |
2413 | currentFollowsIndexWidget_data(); |
2414 | } |
2415 | |
2416 | void 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 | |
2462 | void 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 | |
2519 | void 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 |
2530 | struct DragRecorder |
2531 | { |
2532 | virtual ~DragRecorder() = default; |
2533 | bool dragStarted = false; |
2534 | QModelIndexList draggedIndexes; |
2535 | QAbstractItemView *view; |
2536 | }; |
2537 | |
2538 | template<class ViewClass> |
2539 | class DragRecorderView : public ViewClass, public DragRecorder |
2540 | { |
2541 | public: |
2542 | DragRecorderView() |
2543 | { view = this; } |
2544 | protected: |
2545 | void startDrag(Qt::DropActions) override |
2546 | { |
2547 | draggedIndexes = ViewClass::selectedIndexes(); |
2548 | dragStarted = true; |
2549 | } |
2550 | }; |
2551 | |
2552 | void 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 | |
2612 | void 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 | |
2648 | QTEST_MAIN(tst_QAbstractItemView) |
2649 | #include "tst_qabstractitemview.moc" |
2650 | |