mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2024-11-27 14:38:28 +08:00
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"
|