| 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 | |