3f2d02e2f4
A click on the selected item should deselect all other items before editing starts. Remove the part of the test case that assumes that we can have multiple items selected in ExtendedSelection mode, and click on an item to start editing while maintaining selection. That can never happen. Pick-to: 6.5 6.4 6.2 Fixes: QTBUG-111131 Change-Id: I0312eed4614502cfb77eca26d3f7615427493d7d Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
3409 lines
128 KiB
C++
3409 lines
128 KiB
C++
// 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 <private/qguiapplication_p.h>
|
|
|
|
#include <qpa/qplatformintegration.h>
|
|
|
|
#include <QAbstractItemView>
|
|
#include <QDialog>
|
|
#include <QHeaderView>
|
|
#include <QIdentityProxyModel>
|
|
#include <QItemDelegate>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QListWidget>
|
|
#include <QProxyStyle>
|
|
#include <QPushButton>
|
|
#include <QScrollBar>
|
|
#include <QScroller>
|
|
#include <QSignalSpy>
|
|
#include <QSortFilterProxyModel>
|
|
#include <QSpinBox>
|
|
#include <QStandardItemModel>
|
|
#include <QStringListModel>
|
|
#include <QStyledItemDelegate>
|
|
#include <QTableWidget>
|
|
#include <QTimer>
|
|
#include <QTreeWidget>
|
|
#include <QTest>
|
|
#include <QVBoxLayout>
|
|
#include <QtTest/private/qtesthelpers_p.h>
|
|
#include <private/qabstractitemview_p.h>
|
|
#include <QtWidgets/private/qapplication_p.h>
|
|
|
|
Q_DECLARE_METATYPE(Qt::ItemFlags);
|
|
|
|
using namespace QTestPrivate;
|
|
using IntList = QList<int>;
|
|
|
|
// 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<QDialog *>(editor);
|
|
result = static_cast<QDialog::DialogCode>(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<QByteArray>("viewType");
|
|
|
|
const QList<QByteArray> widgets { "QListView", "QTreeView", "QTableView", "QHeaderView" };
|
|
for (const QByteArray &widget : widgets)
|
|
QTest::newRow(widget) << widget;
|
|
}
|
|
|
|
void tst_QAbstractItemView::emptyModels()
|
|
{
|
|
QFETCH(QByteArray, viewType);
|
|
|
|
QScopedPointer<QAbstractItemView> 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<QAbstractItemView> 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>(), 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<QSpinBox*> list = view.viewport()->findChildren<QSpinBox*>();
|
|
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<IntList>("rowsOrColumnsWithDelegate");
|
|
QTest::addColumn<QPoint>("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<QByteArray>("viewType");
|
|
QTest::addColumn<Qt::ItemFlags>("itemFlags");
|
|
QTest::addColumn<bool>("result");
|
|
|
|
const QList<QByteArray> 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<QAbstractItemView> 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<QStandardItem *> sil({new QStandardItem(QString("Row %1 Item").arg(i)),
|
|
new QStandardItem(QString("2nd column"))});
|
|
model->appendRow(sil);
|
|
}
|
|
return model;
|
|
};
|
|
QTest::addColumn<QStandardItemModel *>("model");
|
|
QTest::addColumn<QList<QModelIndex>>("changedIndexes");
|
|
QTest::addColumn<bool>("isEmpty");
|
|
{
|
|
auto model = createModel(5);
|
|
QTest::newRow("multiple columns")
|
|
<< model << QList<QModelIndex>({ model->index(0, 0), model->index(0, 1) }) << false;
|
|
}
|
|
{
|
|
auto model = createModel(5);
|
|
QTest::newRow("multiple rows")
|
|
<< model
|
|
<< QList<QModelIndex>(
|
|
{ model->index(0, 0), model->index(1, 0), model->index(2, 0) })
|
|
<< false;
|
|
}
|
|
{
|
|
auto model = createModel(5);
|
|
QTest::newRow("hidden rows")
|
|
<< model << QList<QModelIndex>({ model->index(3, 0), model->index(4, 0) }) << true;
|
|
}
|
|
{
|
|
auto model = createModel(500);
|
|
QTest::newRow("rows outside viewport")
|
|
<< model << QList<QModelIndex>({ model->index(498, 0), model->index(499, 0) })
|
|
<< true;
|
|
}
|
|
}
|
|
|
|
void tst_QAbstractItemView::checkIntersectedRect()
|
|
{
|
|
QFETCH(QStandardItemModel *, model);
|
|
QFETCH(const QList<QModelIndex>, changedIndexes);
|
|
QFETCH(bool, isEmpty);
|
|
|
|
class TableView : public QTableView
|
|
{
|
|
public:
|
|
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
|
|
const QList<int> &roles = QList<int>()) override
|
|
{
|
|
QTableView::dataChanged(topLeft, bottomRight, roles);
|
|
// we want to check the base class implementation here!
|
|
QAbstractItemViewPrivate *av = static_cast<QAbstractItemViewPrivate*>(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<QLineEdit *> lineEditors = treeView.viewport()->findChildren<QLineEdit *>();
|
|
QCOMPARE(lineEditors.size(), 1);
|
|
QVERIFY(!lineEditors.constFirst()->size().isEmpty());
|
|
|
|
treeView.edit(model.index(1, 0));
|
|
lineEditors = treeView.viewport()->findChildren<QLineEdit *>();
|
|
QCOMPARE(lineEditors.size(), 1);
|
|
QVERIFY(!lineEditors.constFirst()->size().isEmpty());
|
|
|
|
treeView.edit(model.index(2, 0));
|
|
lineEditors = treeView.viewport()->findChildren<QLineEdit *>();
|
|
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<QAbstractItemDelegate*>("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<QItemSelection>();
|
|
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<QWidget> editor1 = QApplication::focusWidget();
|
|
QVERIFY(editor1);
|
|
QTest::keyClick(editor1, Qt::Key_Tab, Qt::NoModifier);
|
|
|
|
QPointer<QWidget> 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<QTableView> 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<QAbstractItemView::ScrollMode>(
|
|
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<QAbstractItemView::ScrollMode>(
|
|
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>(), 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>(), 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<QByteArray>("viewType");
|
|
QTest::addColumn<Qt::ItemFlags>("itemFlags");
|
|
QTest::addColumn<bool>("result");
|
|
|
|
const QList<QByteArray> 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<QAbstractItemView> 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<QByteArray>("viewType");
|
|
|
|
const QList<QByteArray> 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<QAbstractItemView> 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<QWidget> 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<QString>("viewClass");
|
|
QTest::addColumn<bool>("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 ViewClass>
|
|
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> dragRecorder;
|
|
if (viewClass == "QTreeView")
|
|
dragRecorder.reset(new DragRecorderView<QTreeView>);
|
|
else if (viewClass == "QListView")
|
|
dragRecorder.reset(new DragRecorderView<QListView>);
|
|
|
|
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<QAbstractItemView::SelectionMode>("selectionMode");
|
|
QTest::addColumn<Qt::KeyboardModifier>("keyboardModifier");
|
|
QTest::addColumn<QItemSelectionModel::SelectionFlag>("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<QAbstractItemView::SelectionMode>("selectionMode");
|
|
QTest::addColumn<bool>("dragEnabled");
|
|
QTest::addColumn<QAbstractItemView::EditTrigger>("editTrigger");
|
|
QTest::addColumn<QList<SelectionEvent>>("selectionEvents");
|
|
QTest::addColumn<QList<int>>("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<int>{};
|
|
|
|
// 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<int>{};
|
|
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<int>{};
|
|
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<int>{};
|
|
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<int>{};
|
|
QTest::addRow("Extended:Click,Toggle,dragable,editable") << QAbstractItemView::ExtendedSelection << true
|
|
<< QAbstractItemView::SelectedClicked
|
|
<< QList{SelectionEvent(SelectionEvent::Click, 3),
|
|
SelectionEvent(SelectionEvent::Click, Qt::ControlModifier, 3)}
|
|
<< QList<int>{};
|
|
// 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<int>{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<int>{2};
|
|
}
|
|
|
|
void tst_QAbstractItemView::mouseSelection()
|
|
{
|
|
QFETCH(QAbstractItemView::SelectionMode, selectionMode);
|
|
QFETCH(bool, dragEnabled);
|
|
QFETCH(QAbstractItemView::EditTrigger, editTrigger);
|
|
QFETCH(QList<SelectionEvent>, selectionEvents);
|
|
QFETCH(QList<int>, 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> dragRecorder(new DragRecorderView<QTreeView>);
|
|
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<int> 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<QPoint>("editItem");
|
|
QTest::addColumn<QString>("preedit");
|
|
QTest::addColumn<QString>("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<QLineEdit*>();
|
|
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<Qt::Orientation>("orientation");
|
|
QTest::addColumn<int>("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"
|