// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(Qt::ItemFlags); using namespace QTestPrivate; using IntList = QList; // Move cursor out of widget area to avoid undesired interaction on Mac. static inline void moveCursorAway(const QWidget *topLevel) { #ifndef QT_NO_CURSOR QCursor::setPos(topLevel->geometry().topRight() + QPoint(100, 0)); #else Q_UNUSED(topLevel); #endif } class GeometriesTestView : public QTableView { Q_OBJECT public: using QTableView::QTableView; using QTableView::selectedIndexes; bool updateGeometriesCalled = false; protected slots: void updateGeometries() override { updateGeometriesCalled = true; QTableView::updateGeometries(); } }; class tst_QAbstractItemView : public QObject { Q_OBJECT public: void basic_tests(QAbstractItemView *view); private slots: void cleanup(); void getSetCheck(); void emptyModels_data(); void emptyModels(); void setModel_data(); void setModel(); void noModel(); void dragSelect(); void rowDelegate(); void columnDelegate(); void selectAll(); void ctrlA(); void persistentEditorFocus(); void pressClosesReleaseDoesntOpenEditor(); void setItemDelegate(); void setItemDelegate_data(); // The dragAndDrop() test doesn't work, and is thus disabled on Mac and Windows // for the following reasons: // Mac: use of GetCurrentEventButtonState() in QDragManager::drag() // Win: unknown reason #if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) #if 0 void dragAndDrop(); void dragAndDropOnChild(); #endif #endif void noFallbackToRoot(); void setCurrentIndex_data(); void setCurrentIndex(); void checkIntersectedRect_data(); void checkIntersectedRect(); void task221955_selectedEditor(); void task250754_fontChange(); void task200665_itemEntered(); void task257481_emptyEditor(); void shiftArrowSelectionAfterScrolling(); void shiftSelectionAfterRubberbandSelection(); void ctrlRubberbandSelection(); void QTBUG6407_extendedSelection(); void QTBUG6753_selectOnSelection(); void testDelegateDestroyEditor(); void testClickedSignal(); void testChangeEditorState(); void deselectInSingleSelection(); void testNoActivateOnDisabledItem(); void testFocusPolicy_data(); void testFocusPolicy(); void QTBUG31411_noSelection(); void QTBUG39324_settingSameInstanceOfIndexWidget(); void sizeHintChangeTriggersLayout(); void shiftSelectionAfterChangingModelContents(); void QTBUG48968_reentrant_updateEditorGeometries(); void QTBUG50102_SH_ItemView_ScrollMode(); void QTBUG50535_update_on_new_selection_model(); void testSelectionModelInSyncWithView(); void testClickToSelect(); void testDialogAsEditor(); void QTBUG46785_mouseout_hover_state(); void testClearModelInClickedSignal(); void inputMethodEnabled_data(); void inputMethodEnabled(); void currentFollowsIndexWidget_data(); void currentFollowsIndexWidget(); void checkFocusAfterActivationChanges_data(); void checkFocusAfterActivationChanges(); void dragSelectAfterNewPress(); void dragWithSecondClick_data(); void dragWithSecondClick(); void clickAfterDoubleClick(); void selectionCommand_data(); void selectionCommand(); void mouseSelection_data(); void mouseSelection(); void keepSingleSelectionOnEmptyAreaClick(); void scrollerSmoothScroll(); void inputMethodOpensEditor_data(); void inputMethodOpensEditor(); void selectionAutoScrolling_data(); void selectionAutoScrolling(); private: static QAbstractItemView *viewFromString(const QByteArray &viewType, QWidget *parent = nullptr) { if (viewType == "QListView") return new QListView(parent); if (viewType == "QTableView") return new QTableView(parent); if (viewType == "QTreeView") return new QTreeView(parent); if (viewType == "QHeaderView") return new QHeaderView(Qt::Vertical, parent); Q_ASSERT(false); return nullptr; } }; class MyAbstractItemDelegate : public QAbstractItemDelegate { public: using QAbstractItemDelegate::QAbstractItemDelegate; void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override {} QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override { return size; } QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const override { openedEditor = new QWidget(parent); return openedEditor; } void destroyEditor(QWidget *editor, const QModelIndex &) const override { calledVirtualDtor = true; editor->deleteLater(); } void changeSize() { size = QSize(50, 50); emit sizeHintChanged(QModelIndex()); } mutable QWidget *openedEditor = nullptr; QSize size; mutable bool calledVirtualDtor = false; }; class DialogItemDelegate : public QStyledItemDelegate { public: using QStyledItemDelegate::QStyledItemDelegate; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const override { openedEditor = new QDialog(parent); return openedEditor; } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { Q_UNUSED(model); Q_UNUSED(index); QDialog *dialog = qobject_cast(editor); result = static_cast(dialog->result()); } mutable QDialog *openedEditor = nullptr; mutable QDialog::DialogCode result = QDialog::Rejected; }; // Testing get/set functions void tst_QAbstractItemView::getSetCheck() { QListView view; QAbstractItemView *obj1 = &view; // QAbstractItemDelegate * QAbstractItemView::itemDelegate() // void QAbstractItemView::setItemDelegate(QAbstractItemDelegate *) MyAbstractItemDelegate *var1 = new MyAbstractItemDelegate; obj1->setItemDelegate(var1); QCOMPARE(var1, obj1->itemDelegate()); obj1->setItemDelegate(nullptr); QCOMPARE(obj1->itemDelegate(), nullptr); delete var1; // EditTriggers ) // void QAbstractItemView::setEditTriggers(EditTriggers) obj1->setEditTriggers(QAbstractItemView::NoEditTriggers); QCOMPARE(obj1->editTriggers(), QAbstractItemView::NoEditTriggers); obj1->setEditTriggers(QAbstractItemView::CurrentChanged); QCOMPARE(obj1->editTriggers(), QAbstractItemView::CurrentChanged); obj1->setEditTriggers(QAbstractItemView::DoubleClicked); QCOMPARE(obj1->editTriggers(), QAbstractItemView::DoubleClicked); obj1->setEditTriggers(QAbstractItemView::SelectedClicked); QCOMPARE(obj1->editTriggers(), QAbstractItemView::SelectedClicked); obj1->setEditTriggers(QAbstractItemView::EditKeyPressed); QCOMPARE(obj1->editTriggers(), QAbstractItemView::EditKeyPressed); obj1->setEditTriggers(QAbstractItemView::AnyKeyPressed); QCOMPARE(obj1->editTriggers(), QAbstractItemView::AnyKeyPressed); obj1->setEditTriggers(QAbstractItemView::AllEditTriggers); QCOMPARE(obj1->editTriggers(), QAbstractItemView::AllEditTriggers); // bool QAbstractItemView::tabKeyNavigation() // void QAbstractItemView::setTabKeyNavigation(bool) obj1->setTabKeyNavigation(false); QCOMPARE(false, obj1->tabKeyNavigation()); obj1->setTabKeyNavigation(true); QCOMPARE(true, obj1->tabKeyNavigation()); // bool QAbstractItemView::dragEnabled() // void QAbstractItemView::setDragEnabled(bool) #if QT_CONFIG(draganddrop) obj1->setDragEnabled(false); QCOMPARE(false, obj1->dragEnabled()); obj1->setDragEnabled(true); QCOMPARE(true, obj1->dragEnabled()); #endif // bool QAbstractItemView::alternatingRowColors() // void QAbstractItemView::setAlternatingRowColors(bool) obj1->setAlternatingRowColors(false); QCOMPARE(false, obj1->alternatingRowColors()); obj1->setAlternatingRowColors(true); QCOMPARE(true, obj1->alternatingRowColors()); // State QAbstractItemView::state() // void QAbstractItemView::setState(State) obj1->setState(QAbstractItemView::NoState); QCOMPARE(QAbstractItemView::NoState, obj1->state()); obj1->setState(QAbstractItemView::DraggingState); QCOMPARE(QAbstractItemView::DraggingState, obj1->state()); obj1->setState(QAbstractItemView::DragSelectingState); QCOMPARE(QAbstractItemView::DragSelectingState, obj1->state()); obj1->setState(QAbstractItemView::EditingState); QCOMPARE(QAbstractItemView::EditingState, obj1->state()); obj1->setState(QAbstractItemView::ExpandingState); QCOMPARE(QAbstractItemView::ExpandingState, obj1->state()); obj1->setState(QAbstractItemView::CollapsingState); QCOMPARE(QAbstractItemView::CollapsingState, obj1->state()); // QWidget QAbstractScrollArea::viewport() // void setViewport(QWidget*) QWidget *vp = new QWidget; obj1->setViewport(vp); QCOMPARE(vp, obj1->viewport()); QCOMPARE(16, obj1->autoScrollMargin()); obj1->setAutoScrollMargin(20); QCOMPARE(20, obj1->autoScrollMargin()); obj1->setAutoScrollMargin(16); QCOMPARE(16, obj1->autoScrollMargin()); } void tst_QAbstractItemView::cleanup() { QVERIFY(QApplication::topLevelWidgets().isEmpty()); } void tst_QAbstractItemView::emptyModels_data() { QTest::addColumn("viewType"); const QList widgets { "QListView", "QTreeView", "QTableView", "QHeaderView" }; for (const QByteArray &widget : widgets) QTest::newRow(widget) << widget; } void tst_QAbstractItemView::emptyModels() { QFETCH(QByteArray, viewType); QScopedPointer view(viewFromString(viewType)); centerOnScreen(view.data()); moveCursorAway(view.data()); view->show(); QVERIFY(QTest::qWaitForWindowExposed(view.data())); QVERIFY(!view->model()); QVERIFY(!view->selectionModel()); //QVERIFY(view->itemDelegate() != 0); basic_tests(view.data()); } void tst_QAbstractItemView::setModel_data() { emptyModels_data(); } void tst_QAbstractItemView::setModel() { QFETCH(QByteArray, viewType); QScopedPointer view(viewFromString(viewType)); centerOnScreen(view.data()); moveCursorAway(view.data()); view->show(); QVERIFY(QTest::qWaitForWindowExposed(view.data())); QStandardItemModel model(20,20); view->setModel(nullptr); view->setModel(&model); basic_tests(view.data()); } void tst_QAbstractItemView::basic_tests(QAbstractItemView *view) { // setSelectionModel // Will assert as it should //view->setSelectionModel(0); // setItemDelegate //view->setItemDelegate(0); // Will asswert as it should // setSelectionMode view->setSelectionMode(QAbstractItemView::SingleSelection); QCOMPARE(view->selectionMode(), QAbstractItemView::SingleSelection); view->setSelectionMode(QAbstractItemView::ContiguousSelection); QCOMPARE(view->selectionMode(), QAbstractItemView::ContiguousSelection); view->setSelectionMode(QAbstractItemView::ExtendedSelection); QCOMPARE(view->selectionMode(), QAbstractItemView::ExtendedSelection); view->setSelectionMode(QAbstractItemView::MultiSelection); QCOMPARE(view->selectionMode(), QAbstractItemView::MultiSelection); view->setSelectionMode(QAbstractItemView::NoSelection); QCOMPARE(view->selectionMode(), QAbstractItemView::NoSelection); // setSelectionBehavior view->setSelectionBehavior(QAbstractItemView::SelectItems); QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectItems); view->setSelectionBehavior(QAbstractItemView::SelectRows); QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectRows); view->setSelectionBehavior(QAbstractItemView::SelectColumns); QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectColumns); // setEditTriggers view->setEditTriggers(QAbstractItemView::EditKeyPressed); QCOMPARE(view->editTriggers(), QAbstractItemView::EditKeyPressed); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QCOMPARE(view->editTriggers(), QAbstractItemView::NoEditTriggers); view->setEditTriggers(QAbstractItemView::CurrentChanged); QCOMPARE(view->editTriggers(), QAbstractItemView::CurrentChanged); view->setEditTriggers(QAbstractItemView::DoubleClicked); QCOMPARE(view->editTriggers(), QAbstractItemView::DoubleClicked); view->setEditTriggers(QAbstractItemView::SelectedClicked); QCOMPARE(view->editTriggers(), QAbstractItemView::SelectedClicked); view->setEditTriggers(QAbstractItemView::AnyKeyPressed); QCOMPARE(view->editTriggers(), QAbstractItemView::AnyKeyPressed); view->setEditTriggers(QAbstractItemView::AllEditTriggers); QCOMPARE(view->editTriggers(), QAbstractItemView::AllEditTriggers); // setAutoScroll view->setAutoScroll(false); QCOMPARE(view->hasAutoScroll(), false); view->setAutoScroll(true); QCOMPARE(view->hasAutoScroll(), true); // setTabKeyNavigation view->setTabKeyNavigation(false); QCOMPARE(view->tabKeyNavigation(), false); view->setTabKeyNavigation(true); QCOMPARE(view->tabKeyNavigation(), true); #if QT_CONFIG(draganddrop) // setDropIndicatorShown view->setDropIndicatorShown(false); QCOMPARE(view->showDropIndicator(), false); view->setDropIndicatorShown(true); QCOMPARE(view->showDropIndicator(), true); // setDragEnabled view->setDragEnabled(false); QCOMPARE(view->dragEnabled(), false); view->setDragEnabled(true); QCOMPARE(view->dragEnabled(), true); #endif // setAlternatingRowColors view->setAlternatingRowColors(false); QCOMPARE(view->alternatingRowColors(), false); view->setAlternatingRowColors(true); QCOMPARE(view->alternatingRowColors(), true); // setIconSize view->setIconSize(QSize(16, 16)); QCOMPARE(view->iconSize(), QSize(16, 16)); QSignalSpy spy(view, &QAbstractItemView::iconSizeChanged); QVERIFY(spy.isValid()); view->setIconSize(QSize(32, 32)); QCOMPARE(view->iconSize(), QSize(32, 32)); QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).at(0).value(), QSize(32, 32)); // Should this happen? view->setIconSize(QSize(-1, -1)); QCOMPARE(view->iconSize(), QSize(-1, -1)); QCOMPARE(spy.size(), 2); QCOMPARE(view->currentIndex(), QModelIndex()); QCOMPARE(view->rootIndex(), QModelIndex()); view->keyboardSearch(""); view->keyboardSearch("foo"); view->keyboardSearch("1"); QCOMPARE(view->visualRect(QModelIndex()), QRect()); view->scrollTo(QModelIndex()); QCOMPARE(view->sizeHintForIndex(QModelIndex()), QSize()); QCOMPARE(view->indexAt(QPoint(-1, -1)), QModelIndex()); if (!view->model()) { QCOMPARE(view->indexAt(QPoint(10, 10)), QModelIndex()); QCOMPARE(view->sizeHintForRow(0), -1); QCOMPARE(view->sizeHintForColumn(0), -1); } else if (view->itemDelegate()) { view->sizeHintForRow(0); view->sizeHintForColumn(0); } view->openPersistentEditor(QModelIndex()); view->closePersistentEditor(QModelIndex()); view->reset(); view->setRootIndex(QModelIndex()); view->doItemsLayout(); view->selectAll(); view->edit(QModelIndex()); view->clearSelection(); view->setCurrentIndex(QModelIndex()); // protected methods // will assert because an invalid index is passed //view->dataChanged(QModelIndex(), QModelIndex()); view->rowsInserted(QModelIndex(), -1, -1); view->rowsAboutToBeRemoved(QModelIndex(), -1, -1); view->selectionChanged(QItemSelection(), QItemSelection()); if (view->model()) { view->currentChanged(QModelIndex(), QModelIndex()); view->currentChanged(QModelIndex(), view->model()->index(0,0)); } view->updateEditorData(); view->updateEditorGeometries(); view->updateGeometries(); view->verticalScrollbarAction(QAbstractSlider::SliderSingleStepAdd); view->horizontalScrollbarAction(QAbstractSlider::SliderSingleStepAdd); view->verticalScrollbarValueChanged(10); view->horizontalScrollbarValueChanged(10); view->closeEditor(nullptr, QAbstractItemDelegate::NoHint); view->commitData(nullptr); view->editorDestroyed(nullptr); // Will assert as it should // view->setIndexWidget(QModelIndex(), 0); view->moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier); view->horizontalOffset(); view->verticalOffset(); // view->isIndexHidden(QModelIndex()); // will (correctly) assert if (view->model()) view->isIndexHidden(view->model()->index(0,0)); view->setSelection(QRect(0, 0, 10, 10), QItemSelectionModel::ClearAndSelect); view->setSelection(QRect(-1, -1, -1, -1), QItemSelectionModel::ClearAndSelect); view->visualRegionForSelection(QItemSelection()); view->selectedIndexes(); view->edit(QModelIndex(), QAbstractItemView::NoEditTriggers, nullptr); view->selectionCommand(QModelIndex(), nullptr); #if QT_CONFIG(draganddrop) if (!view->model()) view->startDrag(Qt::CopyAction); QStyleOptionViewItem option; view->initViewItemOption(&option); view->setState(QAbstractItemView::NoState); QCOMPARE(view->state(), QAbstractItemView::NoState); view->setState(QAbstractItemView::DraggingState); QCOMPARE(view->state(), QAbstractItemView::DraggingState); view->setState(QAbstractItemView::DragSelectingState); QCOMPARE(view->state(), QAbstractItemView::DragSelectingState); view->setState(QAbstractItemView::EditingState); QCOMPARE(view->state(), QAbstractItemView::EditingState); view->setState(QAbstractItemView::ExpandingState); QCOMPARE(view->state(), QAbstractItemView::ExpandingState); view->setState(QAbstractItemView::CollapsingState); QCOMPARE(view->state(), QAbstractItemView::CollapsingState); #endif view->startAutoScroll(); view->stopAutoScroll(); view->doAutoScroll(); // testing mouseFoo and key functions // QTest::mousePress(view, Qt::LeftButton, Qt::NoModifier, QPoint(0,0)); // mouseMove(view, Qt::LeftButton, Qt::NoModifier, QPoint(10,10)); // QTest::mouseRelease(view, Qt::LeftButton, Qt::NoModifier, QPoint(10,10)); // QTest::mouseClick(view, Qt::LeftButton, Qt::NoModifier, QPoint(10,10)); // mouseDClick(view, Qt::LeftButton, Qt::NoModifier, QPoint(10,10)); // QTest::keyClick(view, Qt::Key_A); } void tst_QAbstractItemView::noModel() { // From task #85415 QStandardItemModel model(20,20); QTreeView view; setFrameless(&view); view.setModel(&model); // Make the viewport smaller than the contents, so that we can scroll view.resize(100,100); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); // make sure that the scrollbars are not at value 0 view.scrollTo(view.model()->index(10,10)); QApplication::processEvents(); view.setModel(nullptr); // Due to the model is removed, this will generate a valueChanged signal on both scrollbars. (value to 0) QApplication::processEvents(); QCOMPARE(view.model(), nullptr); } void tst_QAbstractItemView::dragSelect() { // From task #86108 QStandardItemModel model(64, 64); QTableView view; view.setModel(&model); centerOnScreen(&view); moveCursorAway(&view); view.setVisible(true); QVERIFY(QTest::qWaitForWindowExposed(&view)); const int delay = 2; for (int i = 0; i < 2; ++i) { bool tracking = (i == 1); view.setMouseTracking(false); QTest::mouseMove(&view, QPoint(0, 0), delay); view.setMouseTracking(tracking); QTest::mouseMove(&view, QPoint(50, 50), delay); QVERIFY(view.selectionModel()->selectedIndexes().isEmpty()); } } void tst_QAbstractItemView::rowDelegate() { QStandardItemModel model(4, 4); MyAbstractItemDelegate delegate; QTableView view; view.setModel(&model); view.setItemDelegateForRow(3, &delegate); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QModelIndex index = model.index(3, 0); QVERIFY(!view.isPersistentEditorOpen(index)); view.openPersistentEditor(index); QVERIFY(view.isPersistentEditorOpen(index)); QWidget *w = view.indexWidget(index); QVERIFY(w); QCOMPARE(w->metaObject()->className(), "QWidget"); } void tst_QAbstractItemView::columnDelegate() { QStandardItemModel model(4, 4); MyAbstractItemDelegate delegate; QTableView view; view.setModel(&model); view.setItemDelegateForColumn(3, &delegate); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QModelIndex index = model.index(0, 3); QVERIFY(!view.isPersistentEditorOpen(index)); view.openPersistentEditor(index); QVERIFY(view.isPersistentEditorOpen(index)); QWidget *w = view.indexWidget(index); QVERIFY(w); QCOMPARE(w->metaObject()->className(), "QWidget"); } void tst_QAbstractItemView::sizeHintChangeTriggersLayout() { QStandardItemModel model(4, 4); MyAbstractItemDelegate delegate; MyAbstractItemDelegate rowDelegate; MyAbstractItemDelegate columnDelegate; GeometriesTestView view; view.setModel(&model); view.setItemDelegate(&delegate); view.setItemDelegateForRow(1, &rowDelegate); view.setItemDelegateForColumn(2, &columnDelegate); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); view.updateGeometriesCalled = false; delegate.changeSize(); QTRY_VERIFY(view.updateGeometriesCalled); view.updateGeometriesCalled = false; rowDelegate.changeSize(); QTRY_VERIFY(view.updateGeometriesCalled); view.updateGeometriesCalled = false; columnDelegate.changeSize(); QTRY_VERIFY(view.updateGeometriesCalled); } void tst_QAbstractItemView::selectAll() { QStandardItemModel model(4, 4); GeometriesTestView view; view.setModel(&model); QCOMPARE(view.selectedIndexes().size(), 0); view.selectAll(); QCOMPARE(view.selectedIndexes().size(), 4 * 4); } void tst_QAbstractItemView::ctrlA() { QStandardItemModel model(4, 4); GeometriesTestView view; view.setModel(&model); QCOMPARE(view.selectedIndexes().size(), 0); QTest::keyClick(&view, Qt::Key_A, Qt::ControlModifier); QCOMPARE(view.selectedIndexes().size(), 4 * 4); } void tst_QAbstractItemView::persistentEditorFocus() { // one row, three columns QStandardItemModel model(1, 3); for(int i = 0; i < model.columnCount(); ++i) model.setData(model.index(0, i), i); QTableView view; view.setModel(&model); view.openPersistentEditor(model.index(0, 1)); view.openPersistentEditor(model.index(0, 2)); //these are spinboxes because we put numbers inside const QList list = view.viewport()->findChildren(); QCOMPARE(list.size(), 2); //these should be the 2 editors view.setCurrentIndex(model.index(0, 0)); QCOMPARE(view.currentIndex(), model.index(0, 0)); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); const QPoint p(5, 5); for (QSpinBox *sb : list) { QTRY_VERIFY(sb->isVisible()); QMouseEvent mouseEvent(QEvent::MouseButtonPress, p, sb->mapToGlobal(p), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(sb, &mouseEvent); if (!QApplication::focusWidget()) QSKIP("Some window managers don't handle focus that well"); QTRY_COMPARE(QApplication::focusWidget(), sb); } } /*! A press into the selection area of an item being edited, but outside the editor, closes the editor by transferring focus to the view. The corresponding release should then not re-open the editor. QTBUG-20456. */ void tst_QAbstractItemView::pressClosesReleaseDoesntOpenEditor() { QStandardItemModel model(0, 1); auto *parent = new QStandardItem("parent"); for (const auto &childText : {"child1", "child2"}) { auto *child = new QStandardItem(childText); child->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsEditable | Qt::ItemIsSelectable); parent->appendRow(child); } model.appendRow(parent); QTreeView view; view.setModel(&model); view.setExpanded(model.indexFromItem(parent), true); view.setSelectionMode(QAbstractItemView::SingleSelection); view.setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); const QRect childRect = view.visualRect(model.indexFromItem(parent->child(0))); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, childRect.center()); // select QVERIFY(view.selectionModel()->selectedIndexes().contains(model.indexFromItem(parent->child(0)))); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, childRect.center()); // edit QTRY_COMPARE(view.state(), QAbstractItemView::EditingState); QPoint inChildOutsideEditor = QPoint(view.indentation() / 2, childRect.center().y()); QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, inChildOutsideEditor); // focus itemview, editor closes QCOMPARE(view.state(), QAbstractItemView::NoState); QTest::qWait(10); // process some events, let the internal timer time out QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, inChildOutsideEditor); // should not reopen editor QTest::qWait(QApplication::doubleClickInterval() * 2); QCOMPARE(view.state(), QAbstractItemView::NoState); } #if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) #if 0 static void sendMouseMove(QWidget *widget, QPoint pos = QPoint()) { if (pos.isNull()) pos = widget->rect().center(); QMouseEvent event(QEvent::MouseMove, pos, widget->mapToGlobal(pos), Qt::NoButton, 0, 0); QCursor::setPos(widget->mapToGlobal(pos)); qApp->processEvents(); QVERIFY(QTest::qWaitForWindowExposed(widget)); QApplication::sendEvent(widget, &event); } static void sendMousePress( QWidget *widget, QPoint pos = QPoint(), Qt::MouseButton button = Qt::LeftButton) { if (pos.isNull()) pos = widget->rect().center(); QMouseEvent event(QEvent::MouseButtonPress, pos, widget->mapToGlobal(pos), button, 0, 0); QApplication::sendEvent(widget, &event); } static void sendMouseRelease( QWidget *widget, QPoint pos = QPoint(), Qt::MouseButton button = Qt::LeftButton) { if (pos.isNull()) pos = widget->rect().center(); QMouseEvent event(QEvent::MouseButtonRelease, pos, widget->mapToGlobal(pos), button, 0, 0); QApplication::sendEvent(widget, &event); } class DnDTestModel : public QStandardItemModel { Q_OBJECT bool dropMimeData(const QMimeData *md, Qt::DropAction action, int r, int c, const QModelIndex &p) { dropAction_result = action; QStandardItemModel::dropMimeData(md, action, r, c, p); return true; } Qt::DropActions supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropAction dropAction_result; public: DnDTestModel() : QStandardItemModel(20, 20), dropAction_result(Qt::IgnoreAction) { for (int i = 0; i < rowCount(); ++i) setData(index(i, 0), QString::number(i)); } Qt::DropAction dropAction() const { return dropAction_result; } }; class DnDTestView : public QTreeView { Q_OBJECT QPoint dropPoint; Qt::DropAction dropAction; void dragEnterEvent(QDragEnterEvent *event) { QAbstractItemView::dragEnterEvent(event); } void dropEvent(QDropEvent *event) { event->setDropAction(dropAction); QTreeView::dropEvent(event); } void timerEvent(QTimerEvent *event) { killTimer(event->timerId()); sendMouseMove(this, dropPoint); sendMouseRelease(this); } void mousePressEvent(QMouseEvent *e) { QTreeView::mousePressEvent(e); startTimer(0); setState(DraggingState); startDrag(dropAction); } public: DnDTestView(Qt::DropAction dropAction, QAbstractItemModel *model) : dropAction(dropAction) { header()->hide(); setModel(model); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDragEnabled(true); } void dragAndDrop(QPoint drag, QPoint drop) { dropPoint = drop; setCurrentIndex(indexAt(drag)); sendMousePress(viewport(), drag); } }; class DnDTestWidget : public QWidget { Q_OBJECT Qt::DropAction dropAction_request; Qt::DropAction dropAction_result; QWidget *dropTarget; void timerEvent(QTimerEvent *event) { killTimer(event->timerId()); sendMouseMove(dropTarget); sendMouseRelease(dropTarget); } void mousePressEvent(QMouseEvent *) { QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; mimeData->setData("application/x-qabstractitemmodeldatalist", QByteArray("")); drag->setMimeData(mimeData); startTimer(0); dropAction_result = drag->start(dropAction_request); } public: Qt::DropAction dropAction() const { return dropAction_result; } void dragAndDrop(QWidget *dropTarget, Qt::DropAction dropAction) { this->dropTarget = dropTarget; dropAction_request = dropAction; sendMousePress(this); } }; void tst_QAbstractItemView::dragAndDrop() { // From Task 137729 const int attempts = 10; int successes = 0; for (int i = 0; i < attempts; ++i) { Qt::DropAction dropAction = Qt::MoveAction; DnDTestModel model; DnDTestView view(dropAction, &model); DnDTestWidget widget; const int size = 200; widget.setFixedSize(size, size); view.setFixedSize(size, size); widget.move(0, 0); view.move(int(size * 1.5), int(size * 1.5)); widget.show(); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); QVERIFY(QTest::qWaitForWindowExposed(&view)); widget.dragAndDrop(&view, dropAction); if (model.dropAction() == dropAction && widget.dropAction() == dropAction) ++successes; } if (successes < attempts) { QString msg = QString("# successes (%1) < # attempts (%2)").arg(successes).arg(attempts); qWarning() << qPrintable(msg); } QVERIFY(successes > 0); // allow for some "event unstability" (i.e. unless // successes == 0, QAbstractItemView is probably ok!) } void tst_QAbstractItemView::dragAndDropOnChild() { const int attempts = 10; int successes = 0; for (int i = 0; i < attempts; ++i) { Qt::DropAction dropAction = Qt::MoveAction; DnDTestModel model; QModelIndex parent = model.index(0, 0); model.insertRow(0, parent); model.insertColumn(0, parent); QModelIndex child = model.index(0, 0, parent); model.setData(child, "child"); QCOMPARE(model.rowCount(parent), 1); DnDTestView view(dropAction, &model); view.setExpanded(parent, true); view.setDragDropMode(QAbstractItemView::InternalMove); const int size = 200; view.setFixedSize(size, size); view.move(int(size * 1.5), int(size * 1.5)); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); view.dragAndDrop(view.visualRect(parent).center(), view.visualRect(child).center()); if (model.dropAction() == dropAction) ++successes; } QCOMPARE(successes, 0); } #endif // 0 #endif // !Q_OS_MAC && !Q_OS_WIN class TestModel : public QStandardItemModel { Q_OBJECT public: using QStandardItemModel::QStandardItemModel; bool setData(const QModelIndex &, const QVariant &, int) override { ++setData_count; return true; } void emitDataChanged() { emit dataChanged(index(0, 0), index(0, 1)); } int setData_count = 0; }; void tst_QAbstractItemView::setItemDelegate_data() { // default is rows, a -1 will switch to columns QTest::addColumn("rowsOrColumnsWithDelegate"); QTest::addColumn("cellToEdit"); QTest::newRow("4 columndelegates") << (IntList{ -1, 0, 1, 2, 3 }) << QPoint(0, 0); QTest::newRow("2 identical rowdelegates on the same row") << (IntList{ 0, 0 }) << QPoint(0, 0); QTest::newRow("2 identical columndelegates on the same column") << (IntList{ -1, 2, 2 }) << QPoint(2, 0); QTest::newRow("2 duplicate delegates, 1 row and 1 column") << (IntList{ 0, -1, 2 }) << QPoint(2, 0); QTest::newRow("4 duplicate delegates, 2 row and 2 column") << (IntList{ 0, 0, -1, 2, 2 }) << QPoint(2, 0); } void tst_QAbstractItemView::setItemDelegate() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QFETCH(const IntList, rowsOrColumnsWithDelegate); QFETCH(QPoint, cellToEdit); QTableView v; QStyledItemDelegate *delegate = new QStyledItemDelegate(&v); TestModel model(5, 5); v.setModel(&model); bool row = true; for (int rc : rowsOrColumnsWithDelegate) { if (rc == -1) { row = !row; } else { if (row) v.setItemDelegateForRow(rc, delegate); else v.setItemDelegateForColumn(rc, delegate); } } centerOnScreen(&v); moveCursorAway(&v); v.show(); QApplicationPrivate::setActiveWindow(&v); QVERIFY(QTest::qWaitForWindowActive(&v)); QModelIndex index = model.index(cellToEdit.y(), cellToEdit.x()); v.edit(index); // This will close the editor QTRY_VERIFY(QApplication::focusWidget()); QWidget *editor = QApplication::focusWidget(); QVERIFY(editor); editor->hide(); delete editor; QCOMPARE(model.setData_count, 1); delete delegate; } void tst_QAbstractItemView::noFallbackToRoot() { QStandardItemModel model(0, 1); for (int i = 0; i < 5; ++i) model.appendRow(new QStandardItem("top" + QString::number(i))); QStandardItem *par1 = model.item(1); for (int j = 0; j < 15; ++j) par1->appendRow(new QStandardItem("sub" + QString::number(j))); QStandardItem *par2 = par1->child(2); for (int k = 0; k < 10; ++k) par2->appendRow(new QStandardItem("bot" + QString::number(k))); QStandardItem *it1 = par2->child(5); QModelIndex parent1 = model.indexFromItem(par1); QModelIndex parent2 = model.indexFromItem(par2); QModelIndex item1 = model.indexFromItem(it1); QTreeView v; v.setModel(&model); v.setRootIndex(parent1); v.setCurrentIndex(item1); QCOMPARE(v.currentIndex(), item1); QVERIFY(model.removeRows(0, 10, parent2)); QCOMPARE(v.currentIndex(), parent2); QVERIFY(model.removeRows(0, 15, parent1)); QCOMPARE(v.currentIndex(), QModelIndex()); } void tst_QAbstractItemView::setCurrentIndex_data() { QTest::addColumn("viewType"); QTest::addColumn("itemFlags"); QTest::addColumn("result"); const QList widgets { "QListView", "QTreeView", "QTableView", "QHeaderView" }; for (const QByteArray &widget : widgets) { QTest::newRow(widget + ": no flags") << widget << Qt::ItemFlags(Qt::NoItemFlags) << false; QTest::newRow(widget + ": checkable") << widget << Qt::ItemFlags(Qt::ItemIsUserCheckable) << false; QTest::newRow(widget + ": selectable") << widget << Qt::ItemFlags(Qt::ItemIsSelectable) << false; QTest::newRow(widget + ": enabled") << widget << Qt::ItemFlags(Qt::ItemIsEnabled) << true; QTest::newRow(widget + ": enabled|selectable") << widget << (Qt::ItemIsSelectable|Qt::ItemIsEnabled) << true; } } void tst_QAbstractItemView::setCurrentIndex() { QFETCH(QByteArray, viewType); QFETCH(Qt::ItemFlags, itemFlags); QFETCH(bool, result); QScopedPointer view(viewFromString(viewType)); centerOnScreen(view.data()); moveCursorAway(view.data()); view->show(); QVERIFY(QTest::qWaitForWindowExposed(view.data())); QStandardItemModel *model = new QStandardItemModel(view.data()); QStandardItem *item = new QStandardItem("first item"); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); model->appendRow(item); item = new QStandardItem("test item"); item->setFlags(itemFlags); model->appendRow(item); view->setModel(model); view->setCurrentIndex(model->index(0, 0)); QCOMPARE(view->currentIndex(), model->index(0, 0)); view->setCurrentIndex(model->index(1, 0)); QVERIFY(view->currentIndex() == model->index(result ? 1 : 0, 0)); } void tst_QAbstractItemView::checkIntersectedRect_data() { auto createModel = [](int rowCount) -> QStandardItemModel* { QStandardItemModel *model = new QStandardItemModel; for (int i = 0; i < rowCount; ++i) { const QList sil({new QStandardItem(QString("Row %1 Item").arg(i)), new QStandardItem(QString("2nd column"))}); model->appendRow(sil); } return model; }; QTest::addColumn("model"); QTest::addColumn>("changedIndexes"); QTest::addColumn("isEmpty"); { auto model = createModel(5); QTest::newRow("multiple columns") << model << QList({ model->index(0, 0), model->index(0, 1) }) << false; } { auto model = createModel(5); QTest::newRow("multiple rows") << model << QList( { model->index(0, 0), model->index(1, 0), model->index(2, 0) }) << false; } { auto model = createModel(5); QTest::newRow("hidden rows") << model << QList({ model->index(3, 0), model->index(4, 0) }) << true; } { auto model = createModel(500); QTest::newRow("rows outside viewport") << model << QList({ model->index(498, 0), model->index(499, 0) }) << true; } } void tst_QAbstractItemView::checkIntersectedRect() { QFETCH(QStandardItemModel *, model); QFETCH(const QList, changedIndexes); QFETCH(bool, isEmpty); class TableView : public QTableView { public: void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles = QList()) override { QTableView::dataChanged(topLeft, bottomRight, roles); // we want to check the base class implementation here! QAbstractItemViewPrivate *av = static_cast(qt_widget_private(this)); m_intersectecRect = av->intersectedRect(av->viewport->rect(), topLeft, bottomRight); } mutable QRect m_intersectecRect; }; TableView view; model->setParent(&view); view.setModel(model); view.resize(400, 400); view.show(); view.setRowHidden(3, true); view.setRowHidden(4, true); QVERIFY(QTest::qWaitForWindowExposed(&view)); view.m_intersectecRect = QRect(); emit view.model()->dataChanged(changedIndexes.first(), changedIndexes.last()); if (isEmpty) { QVERIFY(view.m_intersectecRect.isEmpty()); } else { const auto parent = changedIndexes.first().parent(); const int rCount = view.model()->rowCount(parent); const int cCount = view.model()->columnCount(parent); for (int r = 0; r < rCount; ++r) { for (int c = 0; c < cCount; ++c) { const QModelIndex &idx = view.model()->index(r, c, parent); const auto rect = view.visualRect(idx); if (changedIndexes.contains(idx)) QVERIFY(view.m_intersectecRect.contains(rect)); else QVERIFY(!view.m_intersectecRect.contains(rect)); } } } } void tst_QAbstractItemView::task221955_selectedEditor() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QTreeWidget tree; tree.setColumnCount(2); tree.addTopLevelItem(new QTreeWidgetItem({"Foo", "1"})); tree.addTopLevelItem(new QTreeWidgetItem({"Bar", "2"})); tree.addTopLevelItem(new QTreeWidgetItem({"Baz", "3"})); QTreeWidgetItem *dummy = new QTreeWidgetItem(); tree.addTopLevelItem(dummy); QPushButton *button = new QPushButton("More..."); tree.setItemWidget(dummy, 0, button); button->setAutoFillBackground(true); // as recommended in doc centerOnScreen(&tree); moveCursorAway(&tree); tree.show(); tree.setFocus(); tree.setCurrentIndex(tree.model()->index(1,0)); QApplicationPrivate::setActiveWindow(&tree); QVERIFY(QTest::qWaitForWindowActive(&tree)); QVERIFY(! tree.selectionModel()->selectedIndexes().contains(tree.model()->index(3,0))); //We set the focus to the button, the index need to be selected button->setFocus(); QTRY_VERIFY(tree.selectionModel()->selectedIndexes().contains(tree.model()->index(3,0))); tree.setCurrentIndex(tree.model()->index(1,0)); QVERIFY(! tree.selectionModel()->selectedIndexes().contains(tree.model()->index(3,0))); //Same thing but with the flag NoSelection, nothing can be selected. tree.setFocus(); tree.setSelectionMode(QAbstractItemView::NoSelection); tree.clearSelection(); QVERIFY(tree.selectionModel()->selectedIndexes().isEmpty()); button->setFocus(); QTest::qWait(50); QVERIFY(tree.selectionModel()->selectedIndexes().isEmpty()); } void tst_QAbstractItemView::task250754_fontChange() { QString app_css = qApp->styleSheet(); qApp->setStyleSheet("/* */"); QWidget w; QTreeView tree(&w); QVBoxLayout *vLayout = new QVBoxLayout(&w); vLayout->addWidget(&tree); QStandardItemModel *m = new QStandardItemModel(&w); for (int i = 0; i < 20; ++i) { QStandardItem *item = new QStandardItem(QStringLiteral("Item number ") + QString::number(i)); for (int j = 0; j < 5; ++j) { QStandardItem *child = new QStandardItem(QStringLiteral("Child Item number ") + QString::number(j)); item->setChild(j, 0, child); } m->setItem(i, 0, item); } tree.setModel(m); tree.setHeaderHidden(true); // The header is (in certain styles) dpi dependent w.resize(160, 350); // Minimum width for windows with frame on Windows 8 centerOnScreen(&w); moveCursorAway(&w); w.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&w)); QFont font = tree.font(); font.setPixelSize(10); tree.setFont(font); QTRY_VERIFY(!tree.verticalScrollBar()->isVisible()); font.setPixelSize(60); tree.setFont(font); //now with the huge items, the scrollbar must be visible QTRY_VERIFY(tree.verticalScrollBar()->isVisible()); qApp->setStyleSheet(app_css); } void tst_QAbstractItemView::task200665_itemEntered() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); { // skip if we can't move mouse const QPoint cursorPos = QCursor::pos() + QPoint(10, 10); QCursor::setPos(cursorPos); if (!QTest::qWaitFor([cursorPos] { return QCursor::pos() == cursorPos; }, 500)) QSKIP("Can't move mouse"); } //we test that view will emit entered //when the scrollbar move but not the mouse itself QStandardItemModel model(1000, 1); QListView view; view.setModel(&model); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QCursor::setPos(view.geometry().center()); QTRY_COMPARE(QCursor::pos(), view.geometry().center()); QSignalSpy spy(&view, &QAbstractItemView::entered); view.verticalScrollBar()->setValue(view.verticalScrollBar()->maximum()); QTRY_COMPARE(spy.size(), 1); } void tst_QAbstractItemView::task257481_emptyEditor() { QIcon icon = QApplication::style()->standardIcon(QStyle::SP_ComputerIcon); QStandardItemModel model; model.appendRow(new QStandardItem(icon, QString())); model.appendRow(new QStandardItem(icon, "Editor works")); model.appendRow(new QStandardItem(QString())); QTreeView treeView; treeView.setRootIsDecorated(false); treeView.setModel(&model); centerOnScreen(&treeView); moveCursorAway(&treeView); treeView.show(); QVERIFY(QTest::qWaitForWindowExposed(&treeView)); treeView.edit(model.index(0, 0)); QList lineEditors = treeView.viewport()->findChildren(); QCOMPARE(lineEditors.size(), 1); QVERIFY(!lineEditors.constFirst()->size().isEmpty()); treeView.edit(model.index(1, 0)); lineEditors = treeView.viewport()->findChildren(); QCOMPARE(lineEditors.size(), 1); QVERIFY(!lineEditors.constFirst()->size().isEmpty()); treeView.edit(model.index(2, 0)); lineEditors = treeView.viewport()->findChildren(); QCOMPARE(lineEditors.size(), 1); QVERIFY(!lineEditors.constFirst()->size().isEmpty()); } void tst_QAbstractItemView::shiftArrowSelectionAfterScrolling() { QStandardItemModel model; for (int i = 0; i < 10; ++i) model.setItem(i, 0, new QStandardItem(QString::number(i))); QListView view; view.setFixedSize(160, 250); // Minimum width for windows with frame on Windows 8 view.setFlow(QListView::LeftToRight); view.setGridSize(QSize(100, 100)); view.setSelectionMode(QListView::ExtendedSelection); view.setViewMode(QListView::IconMode); view.setModel(&model); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QModelIndex index0 = model.index(0, 0); QModelIndex index1 = model.index(1, 0); QModelIndex index9 = model.index(9, 0); view.selectionModel()->setCurrentIndex(index0, QItemSelectionModel::NoUpdate); QCOMPARE(view.currentIndex(), index0); view.scrollTo(index9); QTest::keyClick(&view, Qt::Key_Down, Qt::ShiftModifier); QCOMPARE(view.currentIndex(), index1); QModelIndexList selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 2); QVERIFY(selected.contains(index0)); QVERIFY(selected.contains(index1)); } void tst_QAbstractItemView::shiftSelectionAfterRubberbandSelection() { QStandardItemModel model; for (int i = 0; i < 3; ++i) model.setItem(i, 0, new QStandardItem(QString::number(i))); QListView view; view.setFixedSize(160, 450); // Minimum width for windows with frame on Windows 8 view.setFlow(QListView::LeftToRight); view.setGridSize(QSize(100, 100)); view.setSelectionMode(QListView::ExtendedSelection); view.setViewMode(QListView::IconMode); view.setModel(&model); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QModelIndex index0 = model.index(0, 0); QModelIndex index1 = model.index(1, 0); QModelIndex index2 = model.index(2, 0); view.setCurrentIndex(index0); QCOMPARE(view.currentIndex(), index0); // Determine the points where the rubberband selection starts and ends QPoint pressPos = view.visualRect(index1).bottomRight() + QPoint(1, 1); QPoint releasePos = view.visualRect(index1).center(); QVERIFY(!view.indexAt(pressPos).isValid()); QCOMPARE(view.indexAt(releasePos), index1); // Select item 1 using a rubberband selection // The mouse move event has to be created manually because the QTest framework does not // contain a function for mouse moves with buttons pressed QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, pressPos); QMouseEvent moveEvent(QEvent::MouseMove, releasePos, view.viewport()->mapToGlobal(releasePos), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent); QVERIFY(moveEventReceived); QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, releasePos); QCOMPARE(view.currentIndex(), index1); // Shift-click item 2 QPoint item2Pos = view.visualRect(index2).center(); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, item2Pos); QCOMPARE(view.currentIndex(), index2); // Verify that the selection worked OK QModelIndexList selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 2); QVERIFY(selected.contains(index1)); QVERIFY(selected.contains(index2)); // Select item 0 to revert the selection view.setCurrentIndex(index0); QCOMPARE(view.currentIndex(), index0); // Repeat the same steps as above, but with a Shift-Arrow selection QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, pressPos); QMouseEvent moveEvent2(QEvent::MouseMove, releasePos, view.viewport()->mapToGlobal(releasePos), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); moveEventReceived = qApp->notify(view.viewport(), &moveEvent2); QVERIFY(moveEventReceived); QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, releasePos); QCOMPARE(view.currentIndex(), index1); // Press Shift-Down QTest::keyClick(&view, Qt::Key_Down, Qt::ShiftModifier); QCOMPARE(view.currentIndex(), index2); // Verify that the selection worked OK selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 2); QVERIFY(selected.contains(index1)); QVERIFY(selected.contains(index2)); } void tst_QAbstractItemView::ctrlRubberbandSelection() { QStandardItemModel model; for (int i = 0; i < 3; ++i) model.setItem(i, 0, new QStandardItem(QString::number(i))); QListView view; view.setFixedSize(160, 450); // Minimum width for windows with frame on Windows 8 view.setFlow(QListView::LeftToRight); view.setGridSize(QSize(100, 100)); view.setSelectionMode(QListView::ExtendedSelection); view.setViewMode(QListView::IconMode); view.setModel(&model); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QModelIndex index1 = model.index(1, 0); QModelIndex index2 = model.index(2, 0); // Select item 1 view.setCurrentIndex(index1); QModelIndexList selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 1); QVERIFY(selected.contains(index1)); // Now press control and draw a rubberband around items 1 and 2. // The mouse move event has to be created manually because the QTest framework does not // contain a function for mouse moves with buttons pressed. QPoint pressPos = view.visualRect(index1).topLeft() - QPoint(1, 1); QPoint releasePos = view.visualRect(index2).bottomRight() + QPoint(1, 1); QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::ControlModifier, pressPos); QMouseEvent moveEvent(QEvent::MouseMove, releasePos, view.viewport()->mapToGlobal(releasePos), Qt::NoButton, Qt::LeftButton, Qt::ControlModifier); bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent); QVERIFY(moveEventReceived); QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::ControlModifier, releasePos); // Verify that item 2 is selected now selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 1); QVERIFY(selected.contains(index2)); } void tst_QAbstractItemView::QTBUG6407_extendedSelection() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QListWidget view; view.setSelectionMode(QAbstractItemView::ExtendedSelection); for (int i = 0; i < 50; ++i) view.addItem(QString::number(i)); QFont font = view.font(); font.setPixelSize(10); view.setFont(font); view.resize(200,240); centerOnScreen(&view); moveCursorAway(&view); view.show(); QApplicationPrivate::setActiveWindow(&view); QVERIFY(QTest::qWaitForWindowActive(&view)); QCOMPARE(&view, QApplication::activeWindow()); view.verticalScrollBar()->setValue(view.verticalScrollBar()->maximum()); QModelIndex index49 = view.model()->index(49,0); QPoint p = view.visualRect(index49).center(); QVERIFY(view.viewport()->rect().contains(p)); QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, p); QCOMPARE(view.currentIndex(), index49); QCOMPARE(view.selectedItems().size(), 1); QModelIndex index47 = view.model()->index(47,0); p = view.visualRect(index47).center(); QVERIFY(view.viewport()->rect().contains(p)); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, p); QCOMPARE(view.currentIndex(), index47); QCOMPARE(view.selectedItems().size(), 3); //49, 48, 47; QModelIndex index44 = view.model()->index(44,0); p = view.visualRect(index44).center(); QVERIFY(view.viewport()->rect().contains(p)); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, p); QCOMPARE(view.currentIndex(), index44); QCOMPARE(view.selectedItems().size(), 6); //49 .. 44; } void tst_QAbstractItemView::QTBUG6753_selectOnSelection() { QTableWidget table(5, 5); for (int i = 0; i < table.rowCount(); ++i) { for (int j = 0; j < table.columnCount(); ++j) table.setItem(i, j, new QTableWidgetItem("choo-be-doo-wah")); } centerOnScreen(&table); moveCursorAway(&table); table.show(); QVERIFY(QTest::qWaitForWindowExposed(&table)); table.setSelectionMode(QAbstractItemView::ExtendedSelection); table.selectAll(); QVERIFY(QTest::qWaitForWindowExposed(&table)); QModelIndex item = table.model()->index(1,1); QRect itemRect = table.visualRect(item); QTest::mouseMove(table.viewport(), itemRect.center()); QTest::mouseClick(table.viewport(), Qt::LeftButton, Qt::NoModifier, itemRect.center()); QCOMPARE(table.selectedItems().size(), 1); QCOMPARE(table.selectedItems().first(), table.item(item.row(), item.column())); } void tst_QAbstractItemView::testDelegateDestroyEditor() { QTableWidget table(5, 5); MyAbstractItemDelegate delegate; table.setItemDelegate(&delegate); table.edit(table.model()->index(1, 1)); QAbstractItemView *tv = &table; QVERIFY(!delegate.calledVirtualDtor); tv->closeEditor(delegate.openedEditor, QAbstractItemDelegate::NoHint); QVERIFY(delegate.calledVirtualDtor); } void tst_QAbstractItemView::testClickedSignal() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QTableWidget view(5, 5); centerOnScreen(&view); moveCursorAway(&view); view.showNormal(); QApplicationPrivate::setActiveWindow(&view); QVERIFY(QTest::qWaitForWindowActive(&view)); QCOMPARE(&view, QApplication::activeWindow()); QModelIndex index49 = view.model()->index(49,0); QPoint p = view.visualRect(index49).center(); QVERIFY(view.viewport()->rect().contains(p)); QSignalSpy clickedSpy(&view, &QTableWidget::clicked); QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, p); QCOMPARE(clickedSpy.size(), 1); QTest::mouseClick(view.viewport(), Qt::RightButton, {}, p); // We expect that right-clicks do not cause the clicked() signal to // be emitted. QCOMPARE(clickedSpy.size(), 1); } class StateChangeDelegate : public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; void setEditorData(QWidget *editor, const QModelIndex &index) const override { Q_UNUSED(index); static bool w = true; editor->setEnabled(w); w = !w; } }; void tst_QAbstractItemView::testChangeEditorState() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); // Test for QTBUG-25370 TestModel model; model.setItem(0, 0, new QStandardItem("a")); model.setItem(0, 1, new QStandardItem("b")); QTableView view; view.setEditTriggers(QAbstractItemView::CurrentChanged); view.setItemDelegate(new StateChangeDelegate(&view)); view.setModel(&model); centerOnScreen(&view); moveCursorAway(&view); view.show(); QApplicationPrivate::setActiveWindow(&view); QVERIFY(QTest::qWaitForWindowActive(&view)); QCOMPARE(&view, QApplication::activeWindow()); model.emitDataChanged(); // No segfault - the test passes. } void tst_QAbstractItemView::deselectInSingleSelection() { QTableView view; QStandardItemModel s; s.setRowCount(10); s.setColumnCount(10); view.setModel(&s); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); view.setSelectionMode(QAbstractItemView::SingleSelection); view.setEditTriggers(QAbstractItemView::NoEditTriggers); QApplicationPrivate::setActiveWindow(&view); QVERIFY(QTest::qWaitForWindowExposed(&view)); // mouse QModelIndex index22 = s.index(2, 2); QRect rect22 = view.visualRect(index22); QPoint clickpos = rect22.center(); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, clickpos); QCOMPARE(view.currentIndex(), index22); QCOMPARE(view.selectionModel()->selectedIndexes().size(), 1); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, clickpos); QCOMPARE(view.currentIndex(), index22); QCOMPARE(view.selectionModel()->selectedIndexes().size(), 0); // second click with modifier however does select QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, clickpos); QCOMPARE(view.currentIndex(), index22); QCOMPARE(view.selectionModel()->selectedIndexes().size(), 1); // keyboard QTest::keyClick(&view, Qt::Key_Space, Qt::NoModifier); QCOMPARE(view.currentIndex(), index22); QCOMPARE(view.selectionModel()->selectedIndexes().size(), 1); QTest::keyClick(&view, Qt::Key_Space, Qt::ControlModifier); QCOMPARE(view.currentIndex(), index22); QCOMPARE(view.selectionModel()->selectedIndexes().size(), 0); // second keypress with modifier however does select QTest::keyClick(&view, Qt::Key_Space, Qt::ControlModifier); QCOMPARE(view.currentIndex(), index22); QCOMPARE(view.selectionModel()->selectedIndexes().size(), 1); } void tst_QAbstractItemView::testNoActivateOnDisabledItem() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QTreeView treeView; QStandardItemModel model(1, 1); QStandardItem *item = new QStandardItem("item"); model.setItem(0, 0, item); item->setFlags(Qt::NoItemFlags); treeView.setModel(&model); centerOnScreen(&treeView); moveCursorAway(&treeView); treeView.show(); QApplicationPrivate::setActiveWindow(&treeView); QVERIFY(QTest::qWaitForWindowActive(&treeView)); QSignalSpy activatedSpy(&treeView, &QAbstractItemView::activated); // Ensure clicking on a disabled item doesn't emit itemActivated. QModelIndex itemIndex = treeView.model()->index(0, 0); QPoint clickPos = treeView.visualRect(itemIndex).center(); QTest::mouseClick(treeView.viewport(), Qt::LeftButton, {}, clickPos); QCOMPARE(activatedSpy.size(), 0); } void tst_QAbstractItemView::testFocusPolicy_data() { QTest::addColumn("delegate"); QAbstractItemDelegate *styledItemDelegate = new QStyledItemDelegate(this); QAbstractItemDelegate *itemDelegate = new QItemDelegate(this); QTest::newRow("QStyledItemDelegate") << styledItemDelegate; QTest::newRow("QItemDelegate") << itemDelegate; } void tst_QAbstractItemView::testFocusPolicy() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QFETCH(QAbstractItemDelegate*, delegate); QWidget window; QTableView *table = new QTableView(&window); table->setItemDelegate(delegate); QVBoxLayout *layout = new QVBoxLayout(&window); layout->addWidget(table); QStandardItemModel model; model.setRowCount(10); model.setColumnCount(10); table->setModel(&model); table->setCurrentIndex(model.index(1, 1)); centerOnScreen(&window); moveCursorAway(&window); window.show(); QApplicationPrivate::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); // itemview accepts focus => editor is closed => return focus to the itemview QPoint clickpos = table->visualRect(model.index(1, 1)).center(); QTest::mouseDClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, clickpos); QWidget *editor = QApplication::focusWidget(); QVERIFY(editor); QTest::keyClick(editor, Qt::Key_Escape, Qt::NoModifier); QCOMPARE(QApplication::focusWidget(), table); // itemview doesn't accept focus => editor is closed => clear the focus table->setFocusPolicy(Qt::NoFocus); QTest::mouseDClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, clickpos); editor = QApplication::focusWidget(); QVERIFY(editor); QTest::keyClick(editor, Qt::Key_Escape, Qt::NoModifier); QVERIFY(!QApplication::focusWidget()); } void tst_QAbstractItemView::QTBUG31411_noSelection() { QWidget window; QTableView *table = new QTableView(&window); table->setSelectionMode(QAbstractItemView::NoSelection); QVBoxLayout *layout = new QVBoxLayout(&window); layout->addWidget(table); QStandardItemModel model; model.setRowCount(10); model.setColumnCount(10); table->setModel(&model); table->setCurrentIndex(model.index(1, 1)); centerOnScreen(&window); moveCursorAway(&window); window.show(); QApplicationPrivate::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); qRegisterMetaType(); QSignalSpy selectionChangeSpy(table->selectionModel(), &QItemSelectionModel::selectionChanged); QVERIFY(selectionChangeSpy.isValid()); QPoint clickpos = table->visualRect(model.index(1, 1)).center(); QTest::mouseClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, clickpos); QTest::mouseDClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, clickpos); QPointer editor1 = QApplication::focusWidget(); QVERIFY(editor1); QTest::keyClick(editor1, Qt::Key_Tab, Qt::NoModifier); QPointer editor2 = QApplication::focusWidget(); QVERIFY(editor2); QTest::keyClick(editor2, Qt::Key_Escape, Qt::NoModifier); QCOMPARE(selectionChangeSpy.size(), 0); } void tst_QAbstractItemView::QTBUG39324_settingSameInstanceOfIndexWidget() { QStringListModel model({ "FOO", "bar" }); QScopedPointer table(new QTableView()); table->setModel(&model); QModelIndex index = model.index(0,0); QLineEdit *lineEdit = new QLineEdit(); table->setIndexWidget(index, lineEdit); table->setIndexWidget(index, lineEdit); QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); table->show(); } void tst_QAbstractItemView::shiftSelectionAfterChangingModelContents() { QStringListModel model({ "A", "B", "C", "D", "E", "F" }); QSortFilterProxyModel proxyModel; proxyModel.setSourceModel(&model); proxyModel.sort(0, Qt::AscendingOrder); proxyModel.setDynamicSortFilter(true); QPersistentModelIndex indexA(proxyModel.index(0, 0)); QPersistentModelIndex indexB(proxyModel.index(1, 0)); QPersistentModelIndex indexC(proxyModel.index(2, 0)); QPersistentModelIndex indexD(proxyModel.index(3, 0)); QPersistentModelIndex indexE(proxyModel.index(4, 0)); QPersistentModelIndex indexF(proxyModel.index(5, 0)); QListView view; view.setModel(&proxyModel); view.setSelectionMode(QAbstractItemView::ExtendedSelection); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); // Click "C" QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(indexC).center()); QModelIndexList selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 1); QVERIFY(selected.contains(indexC)); // Insert new item "B1" model.insertRow(0); model.setData(model.index(0, 0), "B1"); // Shift-click "D" -> we expect that "C" and "D" are selected QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, view.visualRect(indexD).center()); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 2); QVERIFY(selected.contains(indexC)); QVERIFY(selected.contains(indexD)); // Click "D" QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(indexD).center()); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 1); QVERIFY(selected.contains(indexD)); // Remove items "B" and "C" model.removeRows(proxyModel.mapToSource(indexB).row(), 1); model.removeRows(proxyModel.mapToSource(indexC).row(), 1); QVERIFY(!indexB.isValid()); QVERIFY(!indexC.isValid()); // Shift-click "F" -> we expect that "D", "E", and "F" are selected QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, view.visualRect(indexF).center()); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 3); QVERIFY(selected.contains(indexD)); QVERIFY(selected.contains(indexE)); QVERIFY(selected.contains(indexF)); // Move to "A" by pressing "Up" repeatedly while (view.currentIndex() != indexA) QTest::keyClick(&view, Qt::Key_Up); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 1); QVERIFY(selected.contains(indexA)); // Change the sort order proxyModel.sort(0, Qt::DescendingOrder); // Shift-click "F" -> All items should be selected QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, view.visualRect(indexF).center()); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), model.rowCount()); // Restore the old sort order proxyModel.sort(0, Qt::AscendingOrder); // Click "D" QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(indexD).center()); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 1); QVERIFY(selected.contains(indexD)); // Insert new item "B2" model.insertRow(0); model.setData(model.index(0, 0), "B2"); // Press Shift+Down -> "D" and "E" should be selected. QTest::keyClick(&view, Qt::Key_Down, Qt::ShiftModifier); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 2); QVERIFY(selected.contains(indexD)); QVERIFY(selected.contains(indexE)); // Click "A" to reset the current selection starting point; //then select "D" via QAbstractItemView::setCurrentIndex(const QModelIndex&). QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(indexA).center()); view.setCurrentIndex(indexD); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 1); QVERIFY(selected.contains(indexD)); // Insert new item "B3" model.insertRow(0); model.setData(model.index(0, 0), "B3"); // Press Shift+Down -> "D" and "E" should be selected. QTest::keyClick(&view, Qt::Key_Down, Qt::ShiftModifier); selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 2); QVERIFY(selected.contains(indexD)); QVERIFY(selected.contains(indexE)); } void tst_QAbstractItemView::QTBUG48968_reentrant_updateEditorGeometries() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); QTreeView tree; QStandardItemModel *m = new QStandardItemModel(&tree); for (int i = 0; i < 10; ++i) { QStandardItem *item = new QStandardItem(QString("Item number %1").arg(i)); item->setEditable(true); for (int j = 0; j < 5; ++j) { QStandardItem *child = new QStandardItem(QString("Child Item number %1").arg(j)); item->setChild(j, 0, child); } m->setItem(i, 0, item); } tree.setModel(m); tree.setRootIsDecorated(false); connect(&tree, &QTreeView::doubleClicked, &tree, &QTreeView::setRootIndex); tree.show(); QVERIFY(QTest::qWaitForWindowActive(&tree)); // Trigger editing idx QModelIndex idx = m->index(1, 0); const QPoint pos = tree.visualRect(idx).center(); QTest::mouseClick(tree.viewport(), Qt::LeftButton, Qt::NoModifier, pos); QTest::mouseDClick(tree.viewport(), Qt::LeftButton, Qt::NoModifier, pos); // Add more children to idx QStandardItem *item = m->itemFromIndex(idx); for (int j = 5; j < 10; ++j) { QStandardItem *child = new QStandardItem(QString("Child Item number %1").arg(j)); item->setChild(j, 0, child); } // No crash, all fine. } class ScrollModeProxyStyle: public QProxyStyle { Q_OBJECT public: ScrollModeProxyStyle(QAbstractItemView::ScrollMode sm, QStyle *style = nullptr) : QProxyStyle(style) , scrollMode(sm == QAbstractItemView::ScrollPerItem ? QAbstractItemView::ScrollPerPixel : QAbstractItemView::ScrollPerItem) { } int styleHint(QStyle::StyleHint hint, const QStyleOption *opt, const QWidget *w, QStyleHintReturn *returnData) const override { if (hint == SH_ItemView_ScrollMode) return scrollMode; return baseStyle()->styleHint(hint, opt, w, returnData); } QAbstractItemView::ScrollMode scrollMode; }; void tst_QAbstractItemView::QTBUG50102_SH_ItemView_ScrollMode() { QListView view; // Default comes from the style auto styleScrollMode = static_cast( view.style()->styleHint(QStyle::SH_ItemView_ScrollMode, nullptr, &view, nullptr)); QCOMPARE(view.verticalScrollMode(), styleScrollMode); QCOMPARE(view.horizontalScrollMode(), styleScrollMode); // Change style, get new value ScrollModeProxyStyle proxyStyle1(styleScrollMode); view.setStyle(&proxyStyle1); auto proxyScrollMode = static_cast( view.style()->styleHint(QStyle::SH_ItemView_ScrollMode, nullptr, &view, nullptr)); QVERIFY(styleScrollMode != proxyScrollMode); QCOMPARE(view.verticalScrollMode(), proxyScrollMode); QCOMPARE(view.horizontalScrollMode(), proxyScrollMode); // Explicitly set vertical, same value view.setVerticalScrollMode(proxyScrollMode); QCOMPARE(view.verticalScrollMode(), proxyScrollMode); QCOMPARE(view.horizontalScrollMode(), proxyScrollMode); // Change style, won't change value for vertical, will change for horizontal ScrollModeProxyStyle proxyStyle2(proxyScrollMode); view.setStyle(&proxyStyle2); QCOMPARE(view.verticalScrollMode(), proxyScrollMode); QCOMPARE(view.horizontalScrollMode(), styleScrollMode); } void tst_QAbstractItemView::QTBUG50535_update_on_new_selection_model() { QStandardItemModel model; for (int i = 0; i < 10; ++i) model.appendRow(new QStandardItem(QStringLiteral("%1").arg(i))); class ListView : public QListView { public: using QListView::QListView; void setSelectionModel(QItemSelectionModel *model) override { m_deselectedMustBeEmpty = !selectionModel() || !model || selectionModel()->model() != model->model(); QListView::setSelectionModel(model); m_deselectedMustBeEmpty = false; } int m_paintEventsCount = 0; bool selectionChangedOk() const { return m_selectionChangedOk; } protected: bool viewportEvent(QEvent *event) override { if (event->type() == QEvent::Paint) ++m_paintEventsCount; return QListView::viewportEvent(event); } void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override { if (m_deselectedMustBeEmpty && !deselected.isEmpty()) m_selectionChangedOk = false; // Make sure both selections belong to the same model const auto selIndexes = selected.indexes(); const auto deselIndexes = deselected.indexes(); for (const QModelIndex &nmi : selIndexes) { for (const QModelIndex &omi : deselIndexes) m_selectionChangedOk = m_selectionChangedOk && (nmi.model() == omi.model()); } QListView::selectionChanged(selected, deselected); } private: bool m_deselectedMustBeEmpty = false; bool m_selectionChangedOk = true; }; // keep the current/selected row in the "low range", i.e. be sure it's visible, otherwise we // don't get updates and the test fails. ListView view; view.setModel(&model); view.selectionModel()->setCurrentIndex(model.index(1, 0), QItemSelectionModel::SelectCurrent); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QVERIFY(view.selectionChangedOk()); QItemSelectionModel selectionModel(&model); selectionModel.setCurrentIndex(model.index(2, 0), QItemSelectionModel::Current); int oldPaintEventsCount = view.m_paintEventsCount; view.setSelectionModel(&selectionModel); QTRY_VERIFY(view.m_paintEventsCount > oldPaintEventsCount); QVERIFY(view.selectionChangedOk()); QItemSelectionModel selectionModel2(&model); selectionModel2.select(model.index(0, 0), QItemSelectionModel::ClearAndSelect); selectionModel2.setCurrentIndex(model.index(1, 0), QItemSelectionModel::Current); oldPaintEventsCount = view.m_paintEventsCount; view.setSelectionModel(&selectionModel2); QTRY_VERIFY(view.m_paintEventsCount > oldPaintEventsCount); QVERIFY(view.selectionChangedOk()); // Tests QAbstractItemView::selectionChanged QStandardItemModel model1; for (int i = 0; i < 10; ++i) model1.appendRow(new QStandardItem(QString::number(i))); view.setModel(&model1); QItemSelectionModel selectionModel1(&model1); selectionModel1.select(model1.index(0, 0), QItemSelectionModel::ClearAndSelect); selectionModel1.setCurrentIndex(model1.index(1, 0), QItemSelectionModel::Current); view.setSelectionModel(&selectionModel1); QVERIFY(view.selectionChangedOk()); } void tst_QAbstractItemView::testSelectionModelInSyncWithView() { QStandardItemModel model; for (int i = 0; i < 10; ++i) model.appendRow(new QStandardItem(QString::number(i))); class ListView : public QListView { public: using QListView::selectedIndexes; }; ListView view; QVERIFY(!view.selectionModel()); view.setModel(&model); QVERIFY(view.selectionModel()); QVERIFY(view.selectedIndexes().isEmpty()); QVERIFY(view.selectionModel()->selection().isEmpty()); view.setCurrentIndex(model.index(0, 0)); QCOMPARE(view.currentIndex(), model.index(0, 0)); QCOMPARE(view.selectionModel()->currentIndex(), model.index(0, 0)); view.selectionModel()->setCurrentIndex(model.index(1, 0), QItemSelectionModel::SelectCurrent); QCOMPARE(view.currentIndex(), model.index(1, 0)); QCOMPARE(view.selectedIndexes(), QModelIndexList() << model.index(1, 0)); QCOMPARE(view.selectionModel()->currentIndex(), model.index(1, 0)); QCOMPARE(view.selectionModel()->selection().indexes(), QModelIndexList() << model.index(1, 0)); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QItemSelectionModel selectionModel(&model); selectionModel.setCurrentIndex(model.index(2, 0), QItemSelectionModel::Current); view.setSelectionModel(&selectionModel); QCOMPARE(view.currentIndex(), model.index(2, 0)); QCOMPARE(view.selectedIndexes(), QModelIndexList()); QCOMPARE(view.selectionModel()->currentIndex(), model.index(2, 0)); QCOMPARE(view.selectionModel()->selection().indexes(), QModelIndexList()); QItemSelectionModel selectionModel2(&model); selectionModel2.select(model.index(0, 0), QItemSelectionModel::ClearAndSelect); selectionModel2.setCurrentIndex(model.index(1, 0), QItemSelectionModel::Current); view.setSelectionModel(&selectionModel2); QCOMPARE(view.currentIndex(), model.index(1, 0)); QCOMPARE(view.selectedIndexes(), QModelIndexList() << model.index(0, 0)); QCOMPARE(view.selectionModel()->currentIndex(), model.index(1, 0)); QCOMPARE(view.selectionModel()->selection().indexes(), QModelIndexList() << model.index(0, 0)); } class SetSelectionTestView : public QListView { Q_OBJECT public: using QListView::QListView; signals: void setSelectionCalled(const QRect &rect); protected: void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override { emit setSelectionCalled(rect); QListView::setSelection(rect, flags); } }; void tst_QAbstractItemView::testClickToSelect() { // This test verifies that the QRect that is passed from QAbstractItemView::mousePressEvent // to the virtual method QAbstractItemView::setSelection(const QRect &, SelectionFlags) // is the 1x1 rect which conains exactly the clicked pixel if no modifiers are pressed. QStringListModel model({ "A", "B", "C" }); SetSelectionTestView view; view.setModel(&model); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QSignalSpy spy(&view, &SetSelectionTestView::setSelectionCalled); const QModelIndex indexA(model.index(0, 0)); const QRect visualRectA = view.visualRect(indexA); const QPoint centerA = visualRectA.center(); // Click the center of the visualRect of item "A" QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, centerA); QCOMPARE(spy.size(), 1); QCOMPARE(spy.back().front().value(), QRect(centerA, QSize(1, 1))); // Click a point slightly away from the center const QPoint nearCenterA = centerA + QPoint(1, 1); QVERIFY(visualRectA.contains(nearCenterA)); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, nearCenterA); QCOMPARE(spy.size(), 2); QCOMPARE(spy.back().front().value(), QRect(nearCenterA, QSize(1, 1))); } void tst_QAbstractItemView::testDialogAsEditor() { DialogItemDelegate delegate; QStandardItemModel model; model.appendRow(new QStandardItem(QStringLiteral("editme"))); QListView view; view.setItemDelegate(&delegate); view.setModel(&model); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); view.edit(model.index(0,0)); QVERIFY(QTest::qWaitForWindowExposed(delegate.openedEditor)); delegate.openedEditor->reject(); QApplication::processEvents(); QCOMPARE(delegate.result, QDialog::Rejected); view.edit(model.index(0,0)); QVERIFY(QTest::qWaitForWindowExposed(delegate.openedEditor)); delegate.openedEditor->accept(); QApplication::processEvents(); QCOMPARE(delegate.result, QDialog::Accepted); } class HoverItemDelegate : public QStyledItemDelegate { public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override { Q_UNUSED(painter); if (!(opt.state & QStyle::State_MouseOver)) { // We don't want to set m_paintedWithoutHover for any item so check for the item at 0,0 if (index.row() == 0 && index.column() == 0) m_paintedWithoutHover = true; } } mutable bool m_paintedWithoutHover = false; }; void tst_QAbstractItemView::QTBUG46785_mouseout_hover_state() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); HoverItemDelegate delegate; QTableWidget table(5, 5); table.verticalHeader()->hide(); table.horizontalHeader()->hide(); table.setMouseTracking(true); table.setItemDelegate(&delegate); centerOnScreen(&table); table.show(); QVERIFY(QTest::qWaitForWindowActive(&table)); QModelIndex item = table.model()->index(0, 0); QRect itemRect = table.visualRect(item); // Move the mouse into the center of the item at 0,0 to cause a paint event to occur QTest::mouseMove(table.viewport(), itemRect.center()); QTest::mouseClick(table.viewport(), Qt::LeftButton, {}, itemRect.center()); delegate.m_paintedWithoutHover = false; QTest::mouseMove(table.viewport(), QPoint(-50, 0)); QTRY_VERIFY(delegate.m_paintedWithoutHover); } void tst_QAbstractItemView::testClearModelInClickedSignal() { QStringListModel model({"A", "B"}); QListView view; view.setModel(&model); view.show(); connect(&view, &QListView::clicked, [&view](const QModelIndex &index) { view.setModel(nullptr); QCOMPARE(index.data().toString(), QStringLiteral("B")); }); QModelIndex index = view.model()->index(1, 0); QVERIFY(index.isValid()); QPoint p = view.visualRect(index).center(); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, p); QCOMPARE(view.model(), nullptr); } void tst_QAbstractItemView::inputMethodEnabled_data() { QTest::addColumn("viewType"); QTest::addColumn("itemFlags"); QTest::addColumn("result"); const QList widgets { "QListView", "QTreeView", "QTableView" }; for (const QByteArray &widget : widgets) { QTest::newRow(widget + ": no flags") << widget << Qt::ItemFlags(Qt::NoItemFlags) << false; QTest::newRow(widget + ": checkable") << widget << Qt::ItemFlags(Qt::ItemIsUserCheckable) << false; QTest::newRow(widget + ": selectable") << widget << Qt::ItemFlags(Qt::ItemIsSelectable) << false; QTest::newRow(widget + ": enabled") << widget << Qt::ItemFlags(Qt::ItemIsEnabled) << false; QTest::newRow(widget + ": selectable|enabled") << widget << Qt::ItemFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled) << false; QTest::newRow(widget + ": editable|enabled") << widget << Qt::ItemFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled) << true; QTest::newRow(widget + ": editable|enabled|selectable") << widget << Qt::ItemFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable) << true; } } void tst_QAbstractItemView::inputMethodEnabled() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QFETCH(QByteArray, viewType); QFETCH(Qt::ItemFlags, itemFlags); QFETCH(bool, result); QScopedPointer view(viewFromString(viewType)); centerOnScreen(view.data()); view->show(); QVERIFY(QTest::qWaitForWindowExposed(view.data())); QStandardItemModel *model = new QStandardItemModel(view.data()); QStandardItem *item = new QStandardItem("first item"); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); model->appendRow(item); QStandardItem *secondItem = new QStandardItem("test item"); secondItem->setFlags(Qt::ItemFlags(itemFlags)); model->appendRow(secondItem); view->setModel(model); // Check current changed view->setCurrentIndex(model->index(0, 0)); QVERIFY(!view->testAttribute(Qt::WA_InputMethodEnabled)); view->setCurrentIndex(model->index(1, 0)); QCOMPARE(view->testAttribute(Qt::WA_InputMethodEnabled), result); view->setCurrentIndex(model->index(0, 0)); QVERIFY(!view->testAttribute(Qt::WA_InputMethodEnabled)); // Check focus by switching the activation of the window to force a focus in view->setCurrentIndex(model->index(1, 0)); QApplicationPrivate::setActiveWindow(nullptr); QApplicationPrivate::setActiveWindow(view.data()); QVERIFY(QTest::qWaitForWindowActive(view.data())); QCOMPARE(view->testAttribute(Qt::WA_InputMethodEnabled), result); view->setCurrentIndex(QModelIndex()); QVERIFY(!view->testAttribute(Qt::WA_InputMethodEnabled)); QApplicationPrivate::setActiveWindow(nullptr); QApplicationPrivate::setActiveWindow(view.data()); QVERIFY(QTest::qWaitForWindowActive(view.data())); QModelIndex index = model->index(1, 0); QPoint p = view->visualRect(index).center(); QTest::mouseClick(view->viewport(), Qt::LeftButton, Qt::NoModifier, p); if (itemFlags & Qt::ItemIsEnabled) QCOMPARE(view->currentIndex(), index); QCOMPARE(view->testAttribute(Qt::WA_InputMethodEnabled), result); index = model->index(0, 0); QApplicationPrivate::setActiveWindow(nullptr); QApplicationPrivate::setActiveWindow(view.data()); QVERIFY(QTest::qWaitForWindowActive(view.data())); p = view->visualRect(index).center(); QTest::mouseClick(view->viewport(), Qt::LeftButton, Qt::NoModifier, p); QCOMPARE(view->currentIndex(), index); QVERIFY(!view->testAttribute(Qt::WA_InputMethodEnabled)); // There is a case when it goes to the first visible item so we // make the flags of the first item match the ones we are testing // to check the attribute correctly QApplicationPrivate::setActiveWindow(nullptr); view->setCurrentIndex(QModelIndex()); view->reset(); item->setFlags(Qt::ItemFlags(itemFlags)); QApplicationPrivate::setActiveWindow(view.data()); QVERIFY(QTest::qWaitForWindowActive(view.data())); QCOMPARE(view->testAttribute(Qt::WA_InputMethodEnabled), result); } void tst_QAbstractItemView::currentFollowsIndexWidget_data() { QTest::addColumn("viewType"); const QList widgets { "QListView", "QTreeView", "QTableView" }; for (const QByteArray &widget : widgets) QTest::newRow(widget) << widget; } void tst_QAbstractItemView::currentFollowsIndexWidget() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QFETCH(QByteArray, viewType); QScopedPointer view(viewFromString(viewType)); centerOnScreen(view.data()); view->show(); QVERIFY(QTest::qWaitForWindowExposed(view.data())); QStandardItemModel *model = new QStandardItemModel(view.data()); QStandardItem *item1 = new QStandardItem("first item"); item1->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); model->appendRow(item1); QStandardItem *item2 = new QStandardItem("test item"); item2->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); model->appendRow(item2); view->setModel(model); QLineEdit *lineEdit1 = new QLineEdit; QLineEdit *lineEdit2 = new QLineEdit; view->setIndexWidget(item1->index(), lineEdit1); view->setIndexWidget(item2->index(), lineEdit2); lineEdit2->setFocus(); QTRY_VERIFY(lineEdit2->hasFocus()); QCOMPARE(view->currentIndex(), item2->index()); lineEdit1->setFocus(); QTRY_VERIFY(lineEdit1->hasFocus()); QCOMPARE(view->currentIndex(), item1->index()); } class EditorItemDelegate : public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const override { openedEditor = new QLineEdit(parent); return openedEditor; } mutable QPointer openedEditor = nullptr; }; // Testing the case reported in QTBUG-62253. // When an itemview with an editor that has focus loses focus // due to a change in the active window then we need to check // that the itemview gets focus once the activation is back // on the original window. void tst_QAbstractItemView::checkFocusAfterActivationChanges_data() { currentFollowsIndexWidget_data(); } void tst_QAbstractItemView::checkFocusAfterActivationChanges() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("Window activation is not supported"); QFETCH(QByteArray, viewType); const QRect availableGeo = QGuiApplication::primaryScreen()->availableGeometry(); const int halfWidth = availableGeo.width() / 2; QWidget otherTopLevel; otherTopLevel.setGeometry(availableGeo.x(), availableGeo.y(), halfWidth, availableGeo.height()); otherTopLevel.show(); QWidget w; w.setGeometry(availableGeo.x() + halfWidth, availableGeo.y(), halfWidth, availableGeo.height()); QLineEdit *le = new QLineEdit(&w); QAbstractItemView *view = viewFromString(viewType, &w); QStandardItemModel model(5, 5); view->setModel(&model); view->move(0, 50); EditorItemDelegate delegate; view->setItemDelegate(&delegate); w.show(); QVERIFY(QTest::qWaitForWindowActive(&w)); QVERIFY(le->hasFocus()); view->setFocus(); QVERIFY(view->hasFocus()); view->edit(model.index(0,0)); QVERIFY(QTest::qWaitForWindowExposed(delegate.openedEditor)); QVERIFY(delegate.openedEditor->hasFocus()); QApplicationPrivate::setActiveWindow(&otherTopLevel); otherTopLevel.setFocus(); QTRY_VERIFY(!delegate.openedEditor); QApplicationPrivate::setActiveWindow(&w); QVERIFY(QTest::qWaitForWindowActive(&w)); QVERIFY(view->hasFocus()); } void tst_QAbstractItemView::dragSelectAfterNewPress() { QStandardItemModel model; for (int i = 0; i < 10; ++i) { QStandardItem *item = new QStandardItem(QString::number(i)); model.setItem(i, 0, item); } QListView view; view.setFixedSize(160, 650); // Minimum width for windows with frame on Windows 8 view.setSelectionMode(QListView::ExtendedSelection); view.setModel(&model); centerOnScreen(&view); moveCursorAway(&view); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QModelIndex index0 = model.index(0, 0); QModelIndex index2 = model.index(2, 0); view.setCurrentIndex(index0); QCOMPARE(view.currentIndex(), index0); // Select item 0 using a single click QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index0).center()); QCOMPARE(view.currentIndex(), index0); // Press to select item 2 QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, view.visualRect(index2).center()); QCOMPARE(view.currentIndex(), index2); // Verify that the selection worked OK QModelIndexList selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 3); for (int i = 0; i < 2; ++i) QVERIFY(selected.contains(model.index(i, 0))); QModelIndex index5 = model.index(5, 0); const QPoint releasePos = view.visualRect(index5).center(); // The mouse move event has to be created manually because the QTest framework does not // contain a function for mouse moves with buttons pressed QMouseEvent moveEvent2(QEvent::MouseMove, releasePos, view.viewport()->mapToGlobal(releasePos), Qt::NoButton, Qt::LeftButton, Qt::ShiftModifier); const bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent2); QVERIFY(moveEventReceived); QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, releasePos); QCOMPARE(view.currentIndex(), index5); // Verify that the selection worked OK selected = view.selectionModel()->selectedIndexes(); QCOMPARE(selected.size(), 6); for (int i = 0; i < 5; ++i) QVERIFY(selected.contains(model.index(i, 0))); } void tst_QAbstractItemView::dragWithSecondClick_data() { QTest::addColumn("viewClass"); QTest::addColumn("doubleClick"); for (QString viewClass : {"QListView", "QTreeView"}) { QTest::addRow("DoubleClick") << viewClass << true; QTest::addRow("Two Single Clicks") << viewClass << false; } } // inject the ability to record which indexes get dragged into any QAbstractItemView class struct DragRecorder { virtual ~DragRecorder() = default; bool dragStarted = false; QModelIndexList draggedIndexes; QAbstractItemView *view; }; template class DragRecorderView : public ViewClass, public DragRecorder { public: DragRecorderView() { view = this; } protected: void startDrag(Qt::DropActions) override { draggedIndexes = ViewClass::selectedIndexes(); dragStarted = true; } }; void tst_QAbstractItemView::dragWithSecondClick() { QFETCH(QString, viewClass); QFETCH(bool, doubleClick); QStandardItemModel model; QStandardItem *parentItem = model.invisibleRootItem(); for (int i = 0; i < 10; ++i) { QStandardItem *item = new QStandardItem(QString("item %0").arg(i)); item->setDragEnabled(true); item->setEditable(false); parentItem->appendRow(item); } std::unique_ptr dragRecorder; if (viewClass == "QTreeView") dragRecorder.reset(new DragRecorderView); else if (viewClass == "QListView") dragRecorder.reset(new DragRecorderView); QAbstractItemView *view = dragRecorder->view; view->setModel(&model); view->setFixedSize(160, 650); // Minimum width for windows with frame on Windows 8 view->setSelectionMode(QAbstractItemView::MultiSelection); view->setDragDropMode(QAbstractItemView::InternalMove); centerOnScreen(view); moveCursorAway(view); view->show(); QVERIFY(QTest::qWaitForWindowExposed(view)); QModelIndex index0 = model.index(0, 0); QModelIndex index1 = model.index(1, 0); // Select item 0 using a single click QTest::mouseClick(view->viewport(), Qt::LeftButton, Qt::NoModifier, view->visualRect(index0).center()); QCOMPARE(view->currentIndex(), index0); if (doubleClick) { // press on same item within the double click interval QTest::mouseDClick(view->viewport(), Qt::LeftButton, Qt::NoModifier, view->visualRect(index0).center()); } else { // or on different item with a slow second press QTest::mousePress(view->viewport(), Qt::LeftButton, Qt::NoModifier, view->visualRect(index1).center()); } // then drag far enough with left button held const QPoint dragTo = view->visualRect(index1).center() + QPoint(2 * QApplication::startDragDistance(), 2 * QApplication::startDragDistance()); QMouseEvent mouseMoveEvent(QEvent::MouseMove, dragTo, view->viewport()->mapToGlobal(dragTo), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); QVERIFY(QApplication::sendEvent(view->viewport(), &mouseMoveEvent)); // twice since the view will first enter dragging state, then start the drag // (not necessary to actually move the mouse) QVERIFY(QApplication::sendEvent(view->viewport(), &mouseMoveEvent)); QVERIFY(dragRecorder->dragStarted); QTest::mouseRelease(view->viewport(), Qt::LeftButton, Qt::NoModifier, dragTo); } void tst_QAbstractItemView::clickAfterDoubleClick() { QTableWidget view(5, 5); view.horizontalHeader()->hide(); view.verticalHeader()->hide(); view.setEditTriggers(QAbstractItemView::NoEditTriggers); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); const QModelIndex index = view.model()->index(1, 1); QVERIFY(index.isValid()); const QPoint clickPoint = view.visualRect(index).center(); // must use the QWindow overloads so that modality is respected QWindow *window = view.window()->windowHandle(); int clickCount = 0; connect(&view, &QAbstractItemView::doubleClicked, [&]{ QDialog dialog(&view); dialog.setModal(true); QTimer::singleShot(0, [&]{ dialog.close(); }); dialog.exec(); }); connect(&view, &QAbstractItemView::clicked, [&]{ ++clickCount; }); QTest::mouseClick(window, Qt::LeftButton, {}, clickPoint); QCOMPARE(clickCount, 1); // generates a click followed by a double click; double click opens // dialog that eats second release QTest::mouseDClick(window, Qt::LeftButton, {}, clickPoint); QCOMPARE(clickCount, 2); QTest::mouseClick(window, Qt::LeftButton, {}, clickPoint); QCOMPARE(clickCount, 3); } void tst_QAbstractItemView::selectionCommand_data() { QTest::addColumn("selectionMode"); QTest::addColumn("keyboardModifier"); QTest::addColumn("selectionFlag"); QTest::newRow("NoSelection - NoModifier") << QAbstractItemView::NoSelection << Qt::NoModifier << QItemSelectionModel::NoUpdate; QTest::newRow("NoSelection - ShiftModifier") << QAbstractItemView::NoSelection << Qt::ShiftModifier << QItemSelectionModel::NoUpdate; QTest::newRow("NoSelection - ControlModifier") << QAbstractItemView::NoSelection << Qt::ControlModifier << QItemSelectionModel::NoUpdate; QTest::newRow("NoSelection - AltModifier") << QAbstractItemView::NoSelection << Qt::AltModifier << QItemSelectionModel::NoUpdate; QTest::newRow("SingleSelection - NoModifier") << QAbstractItemView::SingleSelection << Qt::NoModifier << QItemSelectionModel::ClearAndSelect; QTest::newRow("SingleSelection - ShiftModifier") << QAbstractItemView::SingleSelection << Qt::ShiftModifier << QItemSelectionModel::ClearAndSelect; QTest::newRow("SingleSelection - ControlModifier") << QAbstractItemView::SingleSelection << Qt::ControlModifier << QItemSelectionModel::ClearAndSelect; QTest::newRow("SingleSelection - AltModifier") << QAbstractItemView::SingleSelection << Qt::AltModifier << QItemSelectionModel::ClearAndSelect; QTest::newRow("MultiSelection - NoModifier") << QAbstractItemView::MultiSelection << Qt::NoModifier << QItemSelectionModel::Toggle; QTest::newRow("MultiSelection - ShiftModifier") << QAbstractItemView::MultiSelection << Qt::ShiftModifier << QItemSelectionModel::Toggle; QTest::newRow("MultiSelection - ControlModifier") << QAbstractItemView::MultiSelection << Qt::ControlModifier << QItemSelectionModel::Toggle; QTest::newRow("MultiSelection - AltModifier") << QAbstractItemView::MultiSelection << Qt::AltModifier << QItemSelectionModel::Toggle; QTest::newRow("ExtendedSelection - NoModifier") << QAbstractItemView::ExtendedSelection << Qt::NoModifier << QItemSelectionModel::ClearAndSelect; QTest::newRow("ExtendedSelection - ShiftModifier") << QAbstractItemView::ExtendedSelection << Qt::ShiftModifier << QItemSelectionModel::SelectCurrent; QTest::newRow("ExtendedSelection - ControlModifier") << QAbstractItemView::ExtendedSelection << Qt::ControlModifier << QItemSelectionModel::Toggle; QTest::newRow("ExtendedSelection - AltModifier") << QAbstractItemView::ExtendedSelection << Qt::AltModifier << QItemSelectionModel::ClearAndSelect; QTest::newRow("ContiguousSelection - NoModifier") << QAbstractItemView::ContiguousSelection << Qt::NoModifier << QItemSelectionModel::ClearAndSelect; QTest::newRow("ContiguousSelection - ShiftModifier") << QAbstractItemView::ContiguousSelection << Qt::ShiftModifier << QItemSelectionModel::SelectCurrent; QTest::newRow("ContiguousSelection - ControlModifier") << QAbstractItemView::ContiguousSelection << Qt::ControlModifier << QItemSelectionModel::SelectCurrent; QTest::newRow("ContiguousSelection - AltModifier") << QAbstractItemView::ContiguousSelection << Qt::AltModifier << QItemSelectionModel::ClearAndSelect; } void tst_QAbstractItemView::selectionCommand() { QFETCH(QAbstractItemView::SelectionMode, selectionMode); QFETCH(Qt::KeyboardModifier, keyboardModifier); QFETCH(QItemSelectionModel::SelectionFlag, selectionFlag); QTableView view; view.setSelectionMode(selectionMode); QTest::keyPress(&view, Qt::Key_A, keyboardModifier); QCOMPARE(selectionFlag, view.selectionCommand(QModelIndex(), nullptr)); } struct SelectionEvent { enum MouseEvent { Press, Release, Click, Move }; constexpr SelectionEvent(MouseEvent type, int r = -1) noexcept : eventType(type), row(r) {} constexpr SelectionEvent(MouseEvent type, Qt::KeyboardModifiers mod, int r = -1) noexcept : eventType(type), keyboardModifiers(mod), row(r) {} MouseEvent eventType = Press; Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier; int row = -1; }; void tst_QAbstractItemView::mouseSelection_data() { QTest::addColumn("selectionMode"); QTest::addColumn("dragEnabled"); QTest::addColumn("editTrigger"); QTest::addColumn>("selectionEvents"); QTest::addColumn>("selectedRows"); // single selection mode - always one row selected, modifiers ignored QTest::addRow("Single:Press") << QAbstractItemView::SingleSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1)} << QList{1}; QTest::addRow("Single:Click") << QAbstractItemView::SingleSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent{SelectionEvent::Click, 1}} << QList{1}; QTest::addRow("Single:Press+Drag") << QAbstractItemView::SingleSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1), SelectionEvent{SelectionEvent::Move, 2}, SelectionEvent{SelectionEvent::Release}} << QList{2}; QTest::addRow("Single:Shift+Click") << QAbstractItemView::SingleSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent{SelectionEvent::Click, Qt::ShiftModifier, 2}} << QList{2}; QTest::addRow("Single:Press;Ctrl+Press") << QAbstractItemView::SingleSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent{SelectionEvent::Press, 3}, SelectionEvent{SelectionEvent::Press, Qt::ControlModifier, 3}} << QList{3}; QTest::addRow("Single:Ctrl+Click") << QAbstractItemView::SingleSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent{SelectionEvent::Click, Qt::ControlModifier, 3}} << QList{3}; QTest::addRow("Single:Click;Ctrl+Click") << QAbstractItemView::SingleSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent{SelectionEvent::Click, 3}, SelectionEvent{SelectionEvent::Click, Qt::ControlModifier, 3}} << QList{}; // multi selection mode - selection toggles on press, selection can be drag-extended // modifiers ignored QTest::addRow("Multi:Press") << QAbstractItemView::MultiSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1)} << QList{1}; QTest::addRow("Multi:Press twice") << QAbstractItemView::MultiSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1), SelectionEvent(SelectionEvent::Press, 1)} << QList{}; QTest::addRow("Multi:Press twice with drag enabled") << QAbstractItemView::MultiSelection << true << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Click, 1), SelectionEvent(SelectionEvent::Press, 1)} << QList{1}; QTest::addRow("Multi:Press and click with drag enabled") << QAbstractItemView::MultiSelection << true << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1), SelectionEvent(SelectionEvent::Click, 1)} << QList{}; QTest::addRow("Multi:Press,Press") << QAbstractItemView::MultiSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 2), SelectionEvent(SelectionEvent::Press, 3)} << QList{2, 3}; QTest::addRow("Multi:Press,Drag") << QAbstractItemView::MultiSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1), SelectionEvent(SelectionEvent::Move, 5), SelectionEvent(SelectionEvent::Release)} << QList{1, 2, 3, 4, 5}; QTest::addRow("Multi:Press,Drag,Deselect") << QAbstractItemView::MultiSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1), SelectionEvent(SelectionEvent::Move, 5), SelectionEvent(SelectionEvent::Release), SelectionEvent(SelectionEvent::Press, 3)} << QList{1, 2, 4, 5}; // drag-select a few indices; then drag-select a larger area that includes the first QTest::addRow("Multi:Press,Drag;Surround") << QAbstractItemView::MultiSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 3), SelectionEvent(SelectionEvent::Move, 5), SelectionEvent(SelectionEvent::Release), SelectionEvent(SelectionEvent::Press, 1), SelectionEvent(SelectionEvent::Move, 8), SelectionEvent(SelectionEvent::Release)} << QList{1, 2, 3, 4, 5, 6, 7, 8}; // drag-select a few indices; then try to select more starting with the last -> not working QTest::addRow("Multi:Press,Drag;Expand") << QAbstractItemView::MultiSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 3), SelectionEvent(SelectionEvent::Move, 5), SelectionEvent(SelectionEvent::Release), SelectionEvent(SelectionEvent::Press, 5), // this will deselect #5 and not select 6/7/8 SelectionEvent(SelectionEvent::Move, 8), SelectionEvent(SelectionEvent::Release)} << QList{3, 4}; // Multi: Press-dragging a selection should not deselect #QTBUG-59888 QTest::addRow("Multi:Press-Drag selection") << QAbstractItemView::MultiSelection << true // with drag'n'drop enabled, we cannot drag a selection << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Click, 2), SelectionEvent(SelectionEvent::Click, 3), SelectionEvent(SelectionEvent::Click, 4), SelectionEvent(SelectionEvent::Click, 5), SelectionEvent(SelectionEvent::Press, 3), // two moves needed because of distance and state logic in QAbstractItemView SelectionEvent(SelectionEvent::Move, 5), SelectionEvent(SelectionEvent::Move, 6)} << QList{2, 3, 4, 5}; // Extended selection: Press selects a single item QTest::addRow("Extended:Press") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 3)} << QList{3}; QTest::addRow("Extended:Press twice") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 3), SelectionEvent(SelectionEvent::Press, 3)} << QList{3}; QTest::addRow("Extended:Press,Press") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 2), SelectionEvent(SelectionEvent::Press, 3)} << QList{3}; // Extended selection: press with Ctrl toggles item QTest::addRow("Extended:Press,Toggle") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 3), SelectionEvent(SelectionEvent::Click, Qt::ControlModifier, 3)} << QList{}; QTest::addRow("Extended:Press,Add") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1), SelectionEvent(SelectionEvent::Click, Qt::ControlModifier, 3)} << QList{1, 3}; // Extended selection: Shift creates a range between first and last pressed QTest::addRow("Extended:Press,Range") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1), SelectionEvent(SelectionEvent::Press, Qt::ShiftModifier, 5)} << QList{1, 2, 3, 4, 5}; QTest::addRow("Extended:Press,Range,Fix Range") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 1), SelectionEvent(SelectionEvent::Press, Qt::ShiftModifier, 5), SelectionEvent(SelectionEvent::Press, Qt::ShiftModifier, 3)} << QList{1, 2, 3}; // Extended: dragging extends the selection QTest::addRow("Extended:Press,Drag") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 2), SelectionEvent(SelectionEvent::Move, 5)} << QList{2, 3, 4, 5}; // Extended: Ctrl+Press-dragging extends the selection QTest::addRow("Extended:Press,Drag;Ctrl-Press,Drag") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Press, 2), SelectionEvent(SelectionEvent::Move, 5), SelectionEvent(SelectionEvent::Release), SelectionEvent(SelectionEvent::Press, Qt::ControlModifier, 6), SelectionEvent(SelectionEvent::Move, Qt::ControlModifier, 8), SelectionEvent(SelectionEvent::Release, Qt::ControlModifier, 8)} << QList{2, 3, 4, 5, 6, 7, 8}; // Extended: Ctrl+Press-dragging in a selection should not deselect #QTBUG-59888 QTest::addRow("Extended:Ctrl-Drag selection") << QAbstractItemView::ExtendedSelection << true << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Click, 2), SelectionEvent(SelectionEvent::Click, Qt::ShiftModifier, 5), SelectionEvent(SelectionEvent::Press, Qt::ControlModifier, 3), SelectionEvent(SelectionEvent::Move, Qt::ControlModifier, 5), // two moves needed because of distance and state logic in QAbstractItemView SelectionEvent(SelectionEvent::Move, Qt::ControlModifier, 6)} << QList{2, 3, 4, 5}; // Extended: Ctrl+Press-dragging with a selection extends, then drags #QTBUG-59888 QTest::addRow("Extended:Ctrl-Drag selection") << QAbstractItemView::ExtendedSelection << true << QAbstractItemView::NoEditTriggers << QList{SelectionEvent(SelectionEvent::Click, 2), SelectionEvent(SelectionEvent::Click, Qt::ShiftModifier, 5), SelectionEvent(SelectionEvent::Press, Qt::ControlModifier, 6), SelectionEvent(SelectionEvent::Move, Qt::ControlModifier, 7), // two moves needed because of distance and state logic in 7QAbstractItemView SelectionEvent(SelectionEvent::Move, Qt::ControlModifier, 8)} << QList{2, 3, 4, 5, 6}; // Extended: when drag is enabled, click with Ctrl toggles item instead of editing # QTBUG-111131 QTest::addRow("Extended:Click,Toggle,editable") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::SelectedClicked << QList{SelectionEvent(SelectionEvent::Click, 3), SelectionEvent(SelectionEvent::Click, Qt::ControlModifier, 3)} << QList{}; QTest::addRow("Extended:Click,Toggle,dragable,editable") << QAbstractItemView::ExtendedSelection << true << QAbstractItemView::SelectedClicked << QList{SelectionEvent(SelectionEvent::Click, 3), SelectionEvent(SelectionEvent::Click, Qt::ControlModifier, 3)} << QList{}; // Extended: when drag is enabled, click on selected without Ctrl clears before editing QTest::addRow("Extended:Range,Click,editable") << QAbstractItemView::ExtendedSelection << false << QAbstractItemView::SelectedClicked << QList{SelectionEvent(SelectionEvent::Click, 1), SelectionEvent(SelectionEvent::Click, Qt::ShiftModifier, 3), SelectionEvent(SelectionEvent::Click, 2)} << QList{2}; QTest::addRow("Extended:Range,Click,dragable,editable") << QAbstractItemView::ExtendedSelection << true << QAbstractItemView::SelectedClicked << QList{SelectionEvent(SelectionEvent::Click, 1), SelectionEvent(SelectionEvent::Click, Qt::ShiftModifier, 3), SelectionEvent(SelectionEvent::Click, 2)} << QList{2}; } void tst_QAbstractItemView::mouseSelection() { QFETCH(QAbstractItemView::SelectionMode, selectionMode); QFETCH(bool, dragEnabled); QFETCH(QAbstractItemView::EditTrigger, editTrigger); QFETCH(QList, selectionEvents); QFETCH(QList, selectedRows); QStandardItemModel model; QStandardItem *parentItem = model.invisibleRootItem(); for (int i = 0; i < 10; ++i) { QStandardItem *item = new QStandardItem(QString("item %0").arg(i)); item->setDragEnabled(dragEnabled); item->setEditable(editTrigger != QAbstractItemView::NoEditTriggers); parentItem->appendRow(item); } std::unique_ptr dragRecorder(new DragRecorderView); QAbstractItemView *view = dragRecorder->view; QVERIFY(view); view->setModel(&model); view->setDragEnabled(dragEnabled); view->setSelectionMode(selectionMode); view->setEditTriggers(editTrigger); view->show(); QVERIFY(QTest::qWaitForWindowActive(view)); Qt::MouseButton buttonDown = Qt::NoButton; int targetRow = -1; QModelIndex pressedIndex; for (const auto &event : std::as_const(selectionEvents)) { if (event.row != -1) targetRow = event.row; const QModelIndex targetIndex = model.index(targetRow, 0); const QPoint targetPoint = view->visualRect(targetIndex).center(); switch (event.eventType) { case SelectionEvent::Press: if (buttonDown != Qt::NoButton) { QTest::mouseRelease(view->viewport(), buttonDown, event.keyboardModifiers, view->visualRect(pressedIndex).center()); } buttonDown = Qt::LeftButton; pressedIndex = model.index(targetRow, 0); QTest::mousePress(view->viewport(), buttonDown, event.keyboardModifiers, targetPoint); break; case SelectionEvent::Release: QTest::mouseRelease(view->viewport(), buttonDown, event.keyboardModifiers, targetPoint); buttonDown = Qt::NoButton; pressedIndex = QModelIndex(); break; case SelectionEvent::Click: QTest::mouseClick(view->viewport(), Qt::LeftButton, event.keyboardModifiers, targetPoint); buttonDown = Qt::NoButton; break; case SelectionEvent::Move: { QMouseEvent mouseMoveEvent(QEvent::MouseMove, targetPoint, view->viewport()->mapToGlobal(targetPoint), Qt::NoButton, buttonDown, event.keyboardModifiers); QApplication::sendEvent(view->viewport(), &mouseMoveEvent); } break; } } QList actualSelected; const auto selectedIndexes = dragRecorder->dragStarted ? dragRecorder->draggedIndexes : view->selectionModel()->selectedIndexes(); for (auto index : selectedIndexes) actualSelected << index.row(); QCOMPARE(actualSelected, selectedRows); } /*! Make sure that when clicking on empty space in the view, we don't unselect the current row. QTBUG-105870 */ void tst_QAbstractItemView::keepSingleSelectionOnEmptyAreaClick() { QListWidget view; view.setSelectionMode(QAbstractItemView::SingleSelection); QListWidgetItem *lastItem; for (int i = 0; i < 5; i++) lastItem = new QListWidgetItem("item " + QString::number(i), &view); // Make widget large enough so that there is empty area below the last item view.setFixedSize(300, 500); view.show(); QVERIFY(QTest::qWaitForWindowActive(&view)); // Select third row view.setCurrentRow(2); // Click below the last row QPoint targetPoint = view.visualItemRect(lastItem).bottomLeft(); targetPoint += QPoint(10, 10); QTest::mouseClick(view.viewport(), Qt::MouseButton::LeftButton, Qt::NoModifier, targetPoint); QCOMPARE(view.currentRow(), 2); QVERIFY(view.currentItem()->isSelected()); } /*! Verify that scrolling an autoScroll enabled itemview with a QScroller produces a continuous, smooth scroll without any jumping around due to the currentItem negotiation between QAbstractItemView and QScroller. QTBUG-64543. */ void tst_QAbstractItemView::scrollerSmoothScroll() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("QWindow::requestActivate() is not supported."); QListWidget view; view.setAutoScroll(true); view.setVerticalScrollMode(QListView::ScrollPerPixel); QScroller::grabGesture(view.viewport(), QScroller::TouchGesture); QScroller::grabGesture(view.viewport(), QScroller::LeftMouseButtonGesture); for (int i = 0; i < 50; i++) { QListWidgetItem* item = new QListWidgetItem("item " + QString::number(i), &view); // gives items a touch friendly size so that only a few fit into the viewport item->setSizeHint(QSize(100,50)); } // make sure we have space for only a few items view.setFixedSize(120, 200); view.show(); QVERIFY(QTest::qWaitForWindowActive(&view)); // we flick up, so we should never scroll back int lastScrollPosition = 0; bool scrollBack = false; connect(view.verticalScrollBar(), &QScrollBar::valueChanged, [&](int value){ scrollBack |= (value < lastScrollPosition); lastScrollPosition = value; }); // start in the middle view.scrollToItem(view.item(25)); QCOMPARE(view.currentItem(), view.item(0)); QListWidgetItem *pressItem = view.item(23); QPoint dragPosition = view.visualRect(view.indexFromItem(pressItem)).center(); // the mouse press changes the current item temporarily, but the press is delayed // by the gesture machinery. this is not what we are testing here, so skip the test // if this fails within a reasonable amount of time. QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, dragPosition); if (!(QTest::qWaitFor([&]{ return view.currentItem() == pressItem; }))) QSKIP("Current item didn't change on press, skipping test"); // QAIV will reset the current item when the scroller changes state to Dragging for (int y = 0; y < QApplication::startDragDistance() * 2; ++y) { // gesture recognizer needs some throttling QTest::qWait(10); dragPosition -= QPoint(0, 10); const QPoint globalPos = view.viewport()->mapToGlobal(dragPosition); QMouseEvent mouseMoveEvent(QEvent::MouseMove, dragPosition, dragPosition, globalPos, Qt::NoButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(view.viewport(), &mouseMoveEvent); QVERIFY(!scrollBack); } QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, dragPosition); } /*! Verify that starting the editing of an item with a key press while a composing input method is active doesn't break the input method. See QTBUG-54848. */ void tst_QAbstractItemView::inputMethodOpensEditor_data() { QTest::addColumn("editItem"); QTest::addColumn("preedit"); QTest::addColumn("commit"); QTest::addRow("IM accepted") << QPoint(1, 1) << "chang" << QString("长"); QTest::addRow("IM cancelled") << QPoint(25, 25) << "chang" << QString(); } void tst_QAbstractItemView::inputMethodOpensEditor() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QSKIP("QWindow::requestActivate() is not supported."); QTableWidget tableWidget(50, 50); tableWidget.setEditTriggers(QAbstractItemView::AnyKeyPressed); for (int r = 0; r < 50; ++r) { for (int c = 0; c < 50; ++c ) tableWidget.setItem(r, c, new QTableWidgetItem(QString("Item %1:%2").arg(r).arg(c))); } tableWidget.show(); QVERIFY(QTest::qWaitForWindowActive(&tableWidget)); const auto sendInputMethodEvent = [](const QString &preeditText, const QString &commitString = {}){ QInputMethodEvent imEvent(preeditText, {}); imEvent.setCommitString(commitString); QApplication::sendEvent(QApplication::focusWidget(), &imEvent); }; QCOMPARE(QApplication::focusWidget(), &tableWidget); QFETCH(QPoint, editItem); QFETCH(QString, preedit); QFETCH(QString, commit); tableWidget.setCurrentCell(editItem.y(), editItem.x()); const QString orgText = tableWidget.currentItem()->text(); const QModelIndex currentIndex = tableWidget.currentIndex(); QCOMPARE(tableWidget.inputMethodQuery(Qt::ImCursorRectangle), tableWidget.visualRect(currentIndex)); // simulate the start of input via a composing input method sendInputMethodEvent(preedit.left(1)); QCOMPARE(tableWidget.state(), QAbstractItemView::EditingState); QLineEdit *editor = tableWidget.findChild(); QVERIFY(editor); QCOMPARE(editor->text(), QString()); // the focus must remain with the tableWidget, as otherwise the compositing is interrupted QCOMPARE(QApplication::focusWidget(), &tableWidget); // the item view delegates input method queries to the editor const QRect cursorRect = tableWidget.inputMethodQuery(Qt::ImCursorRectangle).toRect(); QVERIFY(cursorRect.isValid()); QVERIFY(tableWidget.visualRect(currentIndex).intersects(cursorRect)); // finish preediting, then commit or cancel the input sendInputMethodEvent(preedit); sendInputMethodEvent(QString(), commit); // editing continues, the editor now has focus QCOMPARE(tableWidget.state(), QAbstractItemView::EditingState); QVERIFY(editor->hasFocus()); // finish editing QTest::keyClick(editor, Qt::Key_Return); if (commit.isEmpty()) { // if composition was cancelled, then the item's value is unchanged QCOMPARE(tableWidget.currentItem()->text(), orgText); } else { // otherwise, the item's value is now the commit string QTRY_COMPARE(tableWidget.currentItem()->text(), commit); } } void tst_QAbstractItemView::selectionAutoScrolling_data() { QTest::addColumn("orientation"); QTest::addColumn("direction"); // negative or positive QTest::addRow("scroll up") << Qt::Vertical << -1; QTest::addRow("scroll left") << Qt::Horizontal << -1; QTest::addRow("scroll down") << Qt::Vertical << +1; QTest::addRow("scroll right") << Qt::Horizontal << +1; } void tst_QAbstractItemView::selectionAutoScrolling() { QFETCH(Qt::Orientation, orientation); QFETCH(int, direction); QListView listview; listview.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); listview.setResizeMode(QListView::Fixed); listview.setAutoScroll(true); listview.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listview.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listview.setSpacing(10); listview.setGeometry(0, 0, 500, 500); listview.setFrameShape(QFrame::Shape::NoFrame); listview.setEditTriggers(QListView::NoEditTriggers); QStandardItemModel *listModel = new QStandardItemModel(&listview); listview.setModel(listModel); listview.setViewMode(QListView::IconMode); listview.setSelectionMode(QListView::ExtendedSelection); QPixmap pm(50, 50); pm.fill(Qt::red); for (int i = 0; i < 80; i++) { QStandardItem *item = new QStandardItem(pm, QString::number(i)); listModel->appendRow(item); } listview.show(); QVERIFY(QTest::qWaitForWindowExposed(&listview)); listview.resize(200, 200); // scroll to the middle listview.verticalScrollBar()->setValue(listview.verticalScrollBar()->maximum() / 2); listview.horizontalScrollBar()->setValue(listview.horizontalScrollBar()->maximum() / 2); // remove all visible items so that we don't select any items at the edges, as that // would scroll the view already for (int x = 0; x < listview.viewport()->width(); x += 5) { for (int y = 0; y < listview.viewport()->height(); y += 5) { const QModelIndex index = listview.indexAt(QPoint(x, y)); if (index.isValid()) delete listModel->itemFromIndex(index); } } // remove all items around the edges of the model QRect topLeftRect = listview.visualRect(listModel->index(0, 0)); const QPoint topLeftCenter(topLeftRect.center()); QPoint bottomRightCenter; for (int x = 0; x < listview.horizontalScrollBar()->maximum() + listview.viewport()->width(); x += 5) { const QModelIndex index = listview.indexAt(topLeftCenter + QPoint(x, 0)); if (index.isValid()) { delete listModel->itemFromIndex(index); bottomRightCenter.rx() = x; } } for (int y = 0; y < listview.verticalScrollBar()->maximum() + listview.viewport()->height(); y += 5) { const QModelIndex index = listview.indexAt(topLeftCenter + QPoint(0, y)); if (index.isValid()) { delete listModel->itemFromIndex(index); bottomRightCenter.ry() = y; } } for (int x = 0; x < bottomRightCenter.x(); x += 5) { const QModelIndex index = listview.indexAt(topLeftCenter + QPoint(x, bottomRightCenter.y())); if (index.isValid()) delete listModel->itemFromIndex(index); } for (int y = 0; y < bottomRightCenter.y(); y += 5) { const QModelIndex index = listview.indexAt(topLeftCenter + QPoint(bottomRightCenter.x(), y)); if (index.isValid()) delete listModel->itemFromIndex(index); } // Simulate multiple select behavior; start in the middle, drag to the edge const QPoint pressPoint(listview.viewport()->width() / 2, listview.viewport()->height() / 2); QPoint dragPoint = pressPoint; if (orientation == Qt::Vertical) { dragPoint.rx() += 50; dragPoint.ry() = direction > 0 ? listview.viewport()->height() : 0; } else { dragPoint.rx() = direction > 0 ? listview.viewport()->width() : 0; dragPoint.ry() += 50; } QTest::mousePress(listview.viewport(), Qt::LeftButton, Qt::NoModifier, pressPoint); QMouseEvent mmEvent(QEvent::MouseMove, dragPoint, listview.viewport()->mapToGlobal(dragPoint), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(listview.viewport(), &mmEvent); // QTest::mouseMove is useless // check that we scrolled to the end QScrollBar *scrollBar = orientation == Qt::Vertical ? listview.verticalScrollBar() : listview.horizontalScrollBar(); if (direction < 0) QTRY_COMPARE(scrollBar->value(), 0); else QTRY_COMPARE(scrollBar->value(), scrollBar->maximum()); QVERIFY(listview.selectionModel()->selectedIndexes().size() > 0); QTest::mouseRelease(listview.viewport(), Qt::LeftButton, Qt::NoModifier, dragPoint); } QTEST_MAIN(tst_QAbstractItemView) #include "tst_qabstractitemview.moc"