From 9f1aa866bda7678261f2f441d4cfd5bb524c2411 Mon Sep 17 00:00:00 2001 From: Jo Asplin Date: Thu, 20 Oct 2011 13:17:26 +0200 Subject: Moved tests into integrationtests/ and widgets/ Task-number: QTBUG-19013 Change-Id: Ibb776f5967c0645ce6d22ef7afdc40657c575461 Reviewed-by: Holger Ihrig --- .../widgets/itemviews/qtreeview/tst_qtreeview.cpp | 3942 ++++++++++++++++++++ 1 file changed, 3942 insertions(+) create mode 100644 tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp (limited to 'tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp') diff --git a/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp b/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp new file mode 100644 index 0000000000..d6cdf00218 --- /dev/null +++ b/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp @@ -0,0 +1,3942 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +//TESTED_CLASS= +//TESTED_FILES= + +Q_DECLARE_METATYPE(QModelIndex) +#ifndef QT_NO_DRAGANDDROP +Q_DECLARE_METATYPE(QAbstractItemView::DragDropMode) +#endif +Q_DECLARE_METATYPE(QAbstractItemView::EditTriggers) +Q_DECLARE_METATYPE(QAbstractItemView::EditTrigger) + +static void initStandardTreeModel(QStandardItemModel *model) +{ + QStandardItem *item; + item = new QStandardItem(QLatin1String("Row 1 Item")); + model->insertRow(0, item); + + item = new QStandardItem(QLatin1String("Row 2 Item")); + item->setCheckable(true); + model->insertRow(1, item); + + QStandardItem *childItem = new QStandardItem(QLatin1String("Row 2 Child Item")); + item->setChild(0, childItem); + + item = new QStandardItem(QLatin1String("Row 3 Item")); + item->setIcon(QIcon()); + model->insertRow(2, item); +} + +struct PublicView : public QTreeView +{ + inline void executeDelayedItemsLayout() + { QTreeView::executeDelayedItemsLayout(); } + + enum PublicCursorAction { + MoveUp = QAbstractItemView::MoveUp, + MoveDown = QAbstractItemView::MoveDown, + MoveLeft = QAbstractItemView::MoveLeft, + MoveRight = QAbstractItemView::MoveRight, + MoveHome = QAbstractItemView::MoveHome, + MoveEnd = QAbstractItemView::MoveEnd, + MovePageUp = QAbstractItemView::MovePageUp, + MovePageDown = QAbstractItemView::MovePageDown, + MoveNext = QAbstractItemView::MoveNext, + MovePrevious = QAbstractItemView::MovePrevious + }; + + inline QModelIndex moveCursor(PublicCursorAction ca, Qt::KeyboardModifiers kbm) + { return QTreeView::moveCursor((CursorAction)ca, kbm); } + + inline void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) + { + QTreeView::setSelection(rect, command); + } + inline int state() + { + return QTreeView::state(); + } + + inline int rowHeight(QModelIndex idx) { return QTreeView::rowHeight(idx); } + inline int indexRowSizeHint(const QModelIndex &index) const { return QTreeView::indexRowSizeHint(index); } + + inline QModelIndexList selectedIndexes() const { return QTreeView::selectedIndexes(); } + + inline QStyleOptionViewItem viewOptions() const { return QTreeView::viewOptions(); } + inline int sizeHintForColumn(int column) const { return QTreeView::sizeHintForColumn(column); } +}; + +class tst_QTreeView : public QObject +{ + Q_OBJECT + +public: + tst_QTreeView(); + virtual ~tst_QTreeView(); + + +public slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void selectionOrderTest(); + +private slots: + void getSetCheck(); + + // one test per QTreeView property + void construction(); + void alternatingRowColors(); + void currentIndex_data(); + void currentIndex(); +#ifndef QT_NO_DRAGANDDROP + void dragDropMode_data(); + void dragDropMode(); + void dragDropModeFromDragEnabledAndAcceptDrops_data(); + void dragDropModeFromDragEnabledAndAcceptDrops(); + void dragDropOverwriteMode(); +#endif + void editTriggers_data(); + void editTriggers(); + void hasAutoScroll(); + void horizontalScrollMode(); + void iconSize(); + void indexAt(); + void indexWidget(); + void itemDelegate(); + void itemDelegateForColumnOrRow(); + void keyboardSearch(); + void setModel(); + void openPersistentEditor(); + void rootIndex(); + + // specialized tests below + void setHeader(); + void columnHidden(); + void rowHidden(); + void noDelegate(); + void noModel(); + void emptyModel(); + void removeRows(); + void removeCols(); + void expandAndCollapse_data(); + void expandAndCollapse(); + void expandAndCollapseAll(); + void expandWithNoChildren(); + void keyboardNavigation(); + void headerSections(); + void moveCursor_data(); + void moveCursor(); + void setSelection_data(); + void setSelection(); + void extendedSelection_data(); + void extendedSelection(); + void indexAbove(); + void indexBelow(); + void clicked(); + void mouseDoubleClick(); + void rowsAboutToBeRemoved(); + void headerSections_unhideSection(); + void columnAt(); + void scrollTo(); + void rowsAboutToBeRemoved_move(); + void resizeColumnToContents(); + void insertAfterSelect(); + void removeAfterSelect(); + void hiddenItems(); + void spanningItems(); + void rowSizeHint(); + void setSortingEnabled(); + void headerHidden(); + + void selection(); + void removeAndInsertExpandedCol0(); + void selectionWithHiddenItems(); + void selectAll(); + + void disabledButCheckable(); + void sortByColumn_data(); + void sortByColumn(); + + void evilModel_data(); + void evilModel(); + + void indexRowSizeHint(); + void addRowsWhileSectionsAreHidden(); + void filterProxyModelCrash(); + void styleOptionViewItem(); + void keyboardNavigationWithDisabled(); + + // task-specific tests: + void task174627_moveLeftToRoot(); + void task171902_expandWith1stColHidden(); + void task203696_hidingColumnsAndRowsn(); + void task211293_removeRootIndex(); + void task216717_updateChildren(); + void task220298_selectColumns(); + void task224091_appendColumns(); + void task225539_deleteModel(); + void task230123_setItemsExpandable(); + void task202039_closePersistentEditor(); + void task238873_avoidAutoReopening(); + void task244304_clickOnDecoration(); + void task246536_scrollbarsNotWorking(); + void task250683_wrongSectionSize(); + void task239271_addRowsWithFirstColumnHidden(); + void task254234_proxySort(); + void task248022_changeSelection(); + void task245654_changeModelAndExpandAll(); + void doubleClickedWithSpans(); + void taskQTBUG_6450_selectAllWith1stColumnHidden(); + void taskQTBUG_9216_setSizeAndUniformRowHeightsWrongRepaint(); + void taskQTBUG_11466_keyboardNavigationRegression(); + void taskQTBUG_13567_removeLastItemRegression(); +}; + +class QtTestModel: public QAbstractItemModel +{ +public: + QtTestModel(QObject *parent = 0): QAbstractItemModel(parent), + fetched(false), rows(0), cols(0), levels(INT_MAX), wrongIndex(false) { init(); } + + QtTestModel(int _rows, int _cols, QObject *parent = 0): QAbstractItemModel(parent), + fetched(false), rows(_rows), cols(_cols), levels(INT_MAX), wrongIndex(false) { init(); } + + void init() { + decorationsEnabled = false; + } + + inline qint32 level(const QModelIndex &index) const { + return index.isValid() ? qint32(index.internalId()) : qint32(-1); + } + + bool canFetchMore(const QModelIndex &) const { + return !fetched; + } + + void fetchMore(const QModelIndex &) { + fetched = true; + } + + bool hasChildren(const QModelIndex &parent = QModelIndex()) const { + bool hasFetched = fetched; + fetched = true; + bool r = QAbstractItemModel::hasChildren(parent); + fetched = hasFetched; + return r; + } + + int rowCount(const QModelIndex& parent = QModelIndex()) const { + if (!fetched) + qFatal("%s: rowCount should not be called before fetching", Q_FUNC_INFO); + if ((parent.column() > 0) || (level(parent) > levels)) + return 0; + return rows; + } + int columnCount(const QModelIndex& parent = QModelIndex()) const { + if ((parent.column() > 0) || (level(parent) > levels)) + return 0; + return cols; + } + + bool isEditable(const QModelIndex &index) const { + if (index.isValid()) + return true; + return false; + } + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const + { + if (row < 0 || column < 0 || (level(parent) > levels) || column >= cols || row >= rows) { + return QModelIndex(); + } + QModelIndex i = createIndex(row, column, level(parent) + 1); + parentHash[i] = parent; + return i; + } + + QModelIndex parent(const QModelIndex &index) const + { + if (!parentHash.contains(index)) + return QModelIndex(); + return parentHash[index]; + } + + QVariant data(const QModelIndex &idx, int role) const + { + if (!idx.isValid()) + return QVariant(); + + if (role == Qt::DisplayRole) { + if (idx.row() < 0 || idx.column() < 0 || idx.column() >= cols || idx.row() >= rows) { + wrongIndex = true; + qWarning("Invalid modelIndex [%d,%d,%p]", idx.row(), idx.column(), + idx.internalPointer()); + } + if (idx.row() & 1) + return QString("[%1,%2,%3] - this item is extra wide").arg(idx.row()).arg(idx.column()).arg(level(idx)); + return QString("[%1,%2,%3]").arg(idx.row()).arg(idx.column()).arg(level(idx)); + } + if (decorationsEnabled && role == Qt::DecorationRole) { + QPixmap pm(16,16); + pm.fill(QColor::fromHsv((idx.column() % 16)*8 + 64, 254, (idx.row() % 16)*8 + 32)); + return pm; + } + return QVariant(); + } + + void removeLastRow() + { + beginRemoveRows(QModelIndex(), rows - 1, rows - 1); + --rows; + endRemoveRows(); + } + + void removeAllRows() + { + beginRemoveRows(QModelIndex(), 0, rows - 1); + rows = 0; + endRemoveRows(); + } + + void removeLastColumn() + { + beginRemoveColumns(QModelIndex(), cols - 1, cols - 1); + --cols; + endRemoveColumns(); + } + + void removeAllColumns() + { + beginRemoveColumns(QModelIndex(), 0, cols - 1); + cols = 0; + endRemoveColumns(); + } + + void insertNewRow() + { + beginInsertRows(QModelIndex(), rows - 1, rows - 1); + ++rows; + endInsertRows(); + } + + void setDecorationsEnabled(bool enable) + { + decorationsEnabled = enable; + } + + mutable bool fetched; + bool decorationsEnabled; + int rows, cols; + int levels; + mutable bool wrongIndex; + mutable QMap parentHash; +}; + +tst_QTreeView::tst_QTreeView() +{ +} + +tst_QTreeView::~tst_QTreeView() +{ +} + +void tst_QTreeView::initTestCase() +{ +#ifdef Q_OS_WINCE //disable magic for WindowsCE + qApp->setAutoMaximizeThreshold(-1); +#endif + qRegisterMetaType("QModelIndex"); +} + +void tst_QTreeView::cleanupTestCase() +{ +} + +void tst_QTreeView::init() +{ +} + +void tst_QTreeView::cleanup() +{ +} + +// Testing get/set functions +void tst_QTreeView::getSetCheck() +{ + QTreeView obj1; + + // int QTreeView::indentation() + // void QTreeView::setIndentation(int) + QCOMPARE(obj1.indentation(), 20); + obj1.setIndentation(0); + QCOMPARE(obj1.indentation(), 0); + obj1.setIndentation(INT_MIN); + QCOMPARE(obj1.indentation(), INT_MIN); + obj1.setIndentation(INT_MAX); + QCOMPARE(obj1.indentation(), INT_MAX); + + // bool QTreeView::rootIsDecorated() + // void QTreeView::setRootIsDecorated(bool) + QCOMPARE(obj1.rootIsDecorated(), true); + obj1.setRootIsDecorated(false); + QCOMPARE(obj1.rootIsDecorated(), false); + obj1.setRootIsDecorated(true); + QCOMPARE(obj1.rootIsDecorated(), true); + + // bool QTreeView::uniformRowHeights() + // void QTreeView::setUniformRowHeights(bool) + QCOMPARE(obj1.uniformRowHeights(), false); + obj1.setUniformRowHeights(false); + QCOMPARE(obj1.uniformRowHeights(), false); + obj1.setUniformRowHeights(true); + QCOMPARE(obj1.uniformRowHeights(), true); + + // bool QTreeView::itemsExpandable() + // void QTreeView::setItemsExpandable(bool) + QCOMPARE(obj1.itemsExpandable(), true); + obj1.setItemsExpandable(false); + QCOMPARE(obj1.itemsExpandable(), false); + obj1.setItemsExpandable(true); + QCOMPARE(obj1.itemsExpandable(), true); + + // bool QTreeView::allColumnsShowFocus + // void QTreeView::setAllColumnsShowFocus + QCOMPARE(obj1.allColumnsShowFocus(), false); + obj1.setAllColumnsShowFocus(false); + QCOMPARE(obj1.allColumnsShowFocus(), false); + obj1.setAllColumnsShowFocus(true); + QCOMPARE(obj1.allColumnsShowFocus(), true); + + // bool QTreeView::isAnimated + // void QTreeView::setAnimated + QCOMPARE(obj1.isAnimated(), false); + obj1.setAnimated(false); + QCOMPARE(obj1.isAnimated(), false); + obj1.setAnimated(true); + QCOMPARE(obj1.isAnimated(), true); + + // bool QTreeView::setSortingEnabled + // void QTreeView::isSortingEnabled + QCOMPARE(obj1.isSortingEnabled(), false); + obj1.setSortingEnabled(false); + QCOMPARE(obj1.isSortingEnabled(), false); + obj1.setSortingEnabled(true); + QCOMPARE(obj1.isSortingEnabled(), true); +} + +void tst_QTreeView::construction() +{ + QTreeView view; + + // QAbstractItemView properties + QVERIFY(!view.alternatingRowColors()); + QCOMPARE(view.currentIndex(), QModelIndex()); +#ifndef QT_NO_DRAGANDDROP + QCOMPARE(view.dragDropMode(), QAbstractItemView::NoDragDrop); + QVERIFY(!view.dragDropOverwriteMode()); + QVERIFY(!view.dragEnabled()); +#endif + QCOMPARE(view.editTriggers(), QAbstractItemView::EditKeyPressed | QAbstractItemView::DoubleClicked); + QVERIFY(view.hasAutoScroll()); + QCOMPARE(view.horizontalScrollMode(), QAbstractItemView::ScrollPerPixel); + QCOMPARE(view.iconSize(), QSize()); + QCOMPARE(view.indexAt(QPoint()), QModelIndex()); + QVERIFY(!view.indexWidget(QModelIndex())); + QVERIFY(qobject_cast(view.itemDelegate())); + QVERIFY(!view.itemDelegateForColumn(-1)); + QVERIFY(!view.itemDelegateForColumn(0)); + QVERIFY(!view.itemDelegateForColumn(1)); + QVERIFY(!view.itemDelegateForRow(-1)); + QVERIFY(!view.itemDelegateForRow(0)); + QVERIFY(!view.itemDelegateForRow(1)); + QVERIFY(!view.model()); + QCOMPARE(view.rootIndex(), QModelIndex()); + QCOMPARE(view.selectionBehavior(), QAbstractItemView::SelectRows); + QCOMPARE(view.selectionMode(), QAbstractItemView::SingleSelection); + QVERIFY(!view.selectionModel()); +#ifndef QT_NO_DRAGANDDROP + QVERIFY(view.showDropIndicator()); +#endif + QCOMPARE(view.QAbstractItemView::sizeHintForColumn(-1), -1); // <- protected in QTreeView + QCOMPARE(view.QAbstractItemView::sizeHintForColumn(0), -1); // <- protected in QTreeView + QCOMPARE(view.QAbstractItemView::sizeHintForColumn(1), -1); // <- protected in QTreeView + QCOMPARE(view.sizeHintForIndex(QModelIndex()), QSize()); + QCOMPARE(view.sizeHintForRow(-1), -1); + QCOMPARE(view.sizeHintForRow(0), -1); + QCOMPARE(view.sizeHintForRow(1), -1); + QVERIFY(!view.tabKeyNavigation()); + QCOMPARE(view.textElideMode(), Qt::ElideRight); + QCOMPARE(view.verticalScrollMode(), QAbstractItemView::ScrollPerItem); + QCOMPARE(view.visualRect(QModelIndex()), QRect()); + + // QTreeView properties + QVERIFY(!view.allColumnsShowFocus()); + QCOMPARE(view.autoExpandDelay(), -1); + QCOMPARE(view.columnAt(-1), -1); + QCOMPARE(view.columnAt(0), -1); + QCOMPARE(view.columnAt(1), -1); + QCOMPARE(view.columnViewportPosition(-1), -1); + QCOMPARE(view.columnViewportPosition(0), -1); + QCOMPARE(view.columnViewportPosition(1), -1); + QCOMPARE(view.columnWidth(-1), 0); + QCOMPARE(view.columnWidth(0), 0); + QCOMPARE(view.columnWidth(1), 0); + QVERIFY(view.header()); + QCOMPARE(view.indentation(), 20); + QCOMPARE(view.indexAbove(QModelIndex()), QModelIndex()); + QCOMPARE(view.indexBelow(QModelIndex()), QModelIndex()); + QVERIFY(!view.isAnimated()); + QVERIFY(!view.isColumnHidden(-1)); + QVERIFY(!view.isColumnHidden(0)); + QVERIFY(!view.isColumnHidden(1)); + QVERIFY(!view.isExpanded(QModelIndex())); + QVERIFY(!view.isRowHidden(-1, QModelIndex())); + QVERIFY(!view.isRowHidden(0, QModelIndex())); + QVERIFY(!view.isRowHidden(1, QModelIndex())); + QVERIFY(!view.isFirstColumnSpanned(-1, QModelIndex())); + QVERIFY(!view.isFirstColumnSpanned(0, QModelIndex())); + QVERIFY(!view.isFirstColumnSpanned(1, QModelIndex())); + QVERIFY(!view.isSortingEnabled()); + QVERIFY(view.itemsExpandable()); + QVERIFY(view.rootIsDecorated()); + QVERIFY(!view.uniformRowHeights()); + QCOMPARE(view.visualRect(QModelIndex()), QRect()); + QVERIFY(!view.wordWrap()); +} + +void tst_QTreeView::alternatingRowColors() +{ + QTreeView view; + QVERIFY(!view.alternatingRowColors()); + view.setAlternatingRowColors(true); + QVERIFY(view.alternatingRowColors()); + view.setAlternatingRowColors(false); + QVERIFY(!view.alternatingRowColors()); + + // ### Test visual effect. +} + +void tst_QTreeView::currentIndex_data() +{ + QTest::addColumn("row"); + QTest::addColumn("column"); + QTest::addColumn("indexRow"); + QTest::addColumn("indexColumn"); + QTest::addColumn("parentIndexRow"); + QTest::addColumn("parentIndexColumn"); + + QTest::newRow("-1, -1") << -1 << -1 << -1 << -1 << -1 << -1; + QTest::newRow("-1, 0") << -1 << 0 << -1 << -1 << -1 << -1; + QTest::newRow("0, -1") << 0 << -1 << -1 << -1 << -1 << -1; + QTest::newRow("0, 0") << 0 << 0 << 0 << 0 << -1 << -1; + QTest::newRow("0, 1") << 0 << 0 << 0 << 0 << -1 << -1; + QTest::newRow("1, 0") << 1 << 0 << 1 << 0 << -1 << -1; + QTest::newRow("1, 1") << 1 << 1 << -1 << -1 << -1 << -1; + QTest::newRow("2, 0") << 2 << 0 << 2 << 0 << -1 << -1; + QTest::newRow("2, 1") << 2 << 1 << -1 << -1 << -1 << -1; + QTest::newRow("3, -1") << 3 << -1 << -1 << -1 << -1 << -1; + QTest::newRow("3, 0") << 3 << 0 << -1 << -1 << -1 << -1; + QTest::newRow("3, 1") << 3 << 1 << -1 << -1 << -1 << -1; +} + +void tst_QTreeView::currentIndex() +{ + QFETCH(int, row); + QFETCH(int, column); + QFETCH(int, indexRow); + QFETCH(int, indexColumn); + QFETCH(int, parentIndexRow); + QFETCH(int, parentIndexColumn); + + QTreeView view; + QStandardItemModel treeModel; + initStandardTreeModel(&treeModel); + view.setModel(&treeModel); + + QCOMPARE(view.currentIndex(), QModelIndex()); + view.setCurrentIndex(view.model()->index(row, column)); + QCOMPARE(view.currentIndex().row(), indexRow); + QCOMPARE(view.currentIndex().column(), indexColumn); + QCOMPARE(view.currentIndex().parent().row(), parentIndexRow); + QCOMPARE(view.currentIndex().parent().column(), parentIndexColumn); + + // ### Test child and grandChild indexes. +} + +#ifndef QT_NO_DRAGANDDROP + +void tst_QTreeView::dragDropMode_data() +{ + QTest::addColumn("dragDropMode"); + QTest::addColumn("acceptDrops"); + QTest::addColumn("dragEnabled"); + QTest::newRow("NoDragDrop") << QAbstractItemView::NoDragDrop << false << false; + QTest::newRow("DragOnly") << QAbstractItemView::DragOnly << false << true; + QTest::newRow("DropOnly") << QAbstractItemView::DropOnly << true << false; + QTest::newRow("DragDrop") << QAbstractItemView::DragDrop << true << true; + QTest::newRow("InternalMove") << QAbstractItemView::InternalMove << true << true; +} + +void tst_QTreeView::dragDropMode() +{ + QFETCH(QAbstractItemView::DragDropMode, dragDropMode); + QFETCH(bool, acceptDrops); + QFETCH(bool, dragEnabled); + + QTreeView view; + QCOMPARE(view.dragDropMode(), QAbstractItemView::NoDragDrop); + QVERIFY(!view.acceptDrops()); + QVERIFY(!view.dragEnabled()); + + view.setDragDropMode(dragDropMode); + QCOMPARE(view.dragDropMode(), dragDropMode); + QCOMPARE(view.acceptDrops(), acceptDrops); + QCOMPARE(view.dragEnabled(), dragEnabled); + + // ### Test effects of this mode +} + +void tst_QTreeView::dragDropModeFromDragEnabledAndAcceptDrops_data() +{ + QTest::addColumn("dragEnabled"); + QTest::addColumn("acceptDrops"); + QTest::addColumn("dragDropMode"); + QTest::addColumn("setBehavior"); + + QTest::newRow("NoDragDrop -1") << false << false << QAbstractItemView::NoDragDrop << QAbstractItemView::DragDropMode(-1); + QTest::newRow("NoDragDrop 0") << false << false << QAbstractItemView::NoDragDrop << QAbstractItemView::NoDragDrop; + QTest::newRow("NoDragDrop 1") << false << false << QAbstractItemView::NoDragDrop << QAbstractItemView::DragOnly; + QTest::newRow("NoDragDrop 2") << false << false << QAbstractItemView::NoDragDrop << QAbstractItemView::DropOnly; + QTest::newRow("NoDragDrop 3") << false << false << QAbstractItemView::NoDragDrop << QAbstractItemView::DragDrop; + QTest::newRow("NoDragDrop 4") << false << false << QAbstractItemView::NoDragDrop << QAbstractItemView::InternalMove; + QTest::newRow("DragOnly -1") << true << false << QAbstractItemView::DragOnly << QAbstractItemView::DragDropMode(-1); + QTest::newRow("DragOnly 0") << true << false << QAbstractItemView::DragOnly << QAbstractItemView::NoDragDrop; + QTest::newRow("DragOnly 1") << true << false << QAbstractItemView::DragOnly << QAbstractItemView::DragOnly; + QTest::newRow("DragOnly 2") << true << false << QAbstractItemView::DragOnly << QAbstractItemView::DropOnly; + QTest::newRow("DragOnly 3") << true << false << QAbstractItemView::DragOnly << QAbstractItemView::DragDrop; + QTest::newRow("DragOnly 4") << true << false << QAbstractItemView::DragOnly << QAbstractItemView::InternalMove; + QTest::newRow("DropOnly -1") << false << true << QAbstractItemView::DropOnly << QAbstractItemView::DragDropMode(-1); + QTest::newRow("DropOnly 0") << false << true << QAbstractItemView::DropOnly << QAbstractItemView::NoDragDrop; + QTest::newRow("DropOnly 1") << false << true << QAbstractItemView::DropOnly << QAbstractItemView::DragOnly; + QTest::newRow("DropOnly 2") << false << true << QAbstractItemView::DropOnly << QAbstractItemView::DropOnly; + QTest::newRow("DropOnly 3") << false << true << QAbstractItemView::DropOnly << QAbstractItemView::DragDrop; + QTest::newRow("DropOnly 4") << false << true << QAbstractItemView::DropOnly << QAbstractItemView::InternalMove; + QTest::newRow("DragDrop -1") << true << true << QAbstractItemView::DragDrop << QAbstractItemView::DragDropMode(-1); + QTest::newRow("DragDrop 0") << true << true << QAbstractItemView::DragDrop << QAbstractItemView::DragDropMode(-1); + QTest::newRow("DragDrop 1") << true << true << QAbstractItemView::DragDrop << QAbstractItemView::NoDragDrop; + QTest::newRow("DragDrop 2") << true << true << QAbstractItemView::DragDrop << QAbstractItemView::DragOnly; + QTest::newRow("DragDrop 3") << true << true << QAbstractItemView::DragDrop << QAbstractItemView::DropOnly; + QTest::newRow("DragDrop 4") << true << true << QAbstractItemView::DragDrop << QAbstractItemView::DragDrop; + QTest::newRow("DragDrop 5") << true << true << QAbstractItemView::InternalMove << QAbstractItemView::InternalMove; +} + +void tst_QTreeView::dragDropModeFromDragEnabledAndAcceptDrops() +{ + QFETCH(bool, acceptDrops); + QFETCH(bool, dragEnabled); + QFETCH(QAbstractItemView::DragDropMode, dragDropMode); + QFETCH(QAbstractItemView::DragDropMode, setBehavior); + + QTreeView view; + QCOMPARE(view.dragDropMode(), QAbstractItemView::NoDragDrop); + + if (setBehavior != QAbstractItemView::DragDropMode(-1)) + view.setDragDropMode(setBehavior); + + view.setAcceptDrops(acceptDrops); + view.setDragEnabled(dragEnabled); + QCOMPARE(view.dragDropMode(), dragDropMode); + + // ### Test effects of this mode +} + +void tst_QTreeView::dragDropOverwriteMode() +{ + QTreeView view; + QVERIFY(!view.dragDropOverwriteMode()); + view.setDragDropOverwriteMode(true); + QVERIFY(view.dragDropOverwriteMode()); + view.setDragDropOverwriteMode(false); + QVERIFY(!view.dragDropOverwriteMode()); + + // ### This property changes the behavior of dropIndicatorPosition(), + // which is protected and called only from within QListWidget and + // QTableWidget, from their reimplementations of dropMimeData(). Hard to + // test. +} +#endif + +void tst_QTreeView::editTriggers_data() +{ + QTest::addColumn("editTriggers"); + QTest::addColumn("triggeredTrigger"); + QTest::addColumn("editorOpened"); + + // NoEditTriggers + QTest::newRow("NoEditTriggers 0") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers) + << QAbstractItemView::NoEditTriggers << false; + QTest::newRow("NoEditTriggers 1") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers) + << QAbstractItemView::CurrentChanged << false; + QTest::newRow("NoEditTriggers 2") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers) + << QAbstractItemView::DoubleClicked << false; + QTest::newRow("NoEditTriggers 3") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers) + << QAbstractItemView::SelectedClicked << false; + QTest::newRow("NoEditTriggers 4") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers) + << QAbstractItemView::EditKeyPressed << false; + + // CurrentChanged + QTest::newRow("CurrentChanged 0") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged) + << QAbstractItemView::NoEditTriggers << false; + QTest::newRow("CurrentChanged 1") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged) + << QAbstractItemView::CurrentChanged << true; + QTest::newRow("CurrentChanged 2") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged) + << QAbstractItemView::DoubleClicked << false; + QTest::newRow("CurrentChanged 3") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged) + << QAbstractItemView::SelectedClicked << false; + QTest::newRow("CurrentChanged 4") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged) + << QAbstractItemView::EditKeyPressed << false; + + // DoubleClicked + QTest::newRow("DoubleClicked 0") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked) + << QAbstractItemView::NoEditTriggers << false; + QTest::newRow("DoubleClicked 1") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked) + << QAbstractItemView::CurrentChanged << false; + QTest::newRow("DoubleClicked 2") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked) + << QAbstractItemView::DoubleClicked << true; + QTest::newRow("DoubleClicked 3") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked) + << QAbstractItemView::SelectedClicked << false; + QTest::newRow("DoubleClicked 4") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked) + << QAbstractItemView::EditKeyPressed << false; + + // SelectedClicked + QTest::newRow("SelectedClicked 0") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked) + << QAbstractItemView::NoEditTriggers << false; + QTest::newRow("SelectedClicked 1") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked) + << QAbstractItemView::CurrentChanged << false; + QTest::newRow("SelectedClicked 2") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked) + << QAbstractItemView::DoubleClicked << false; + QTest::newRow("SelectedClicked 3") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked) + << QAbstractItemView::SelectedClicked << true; + QTest::newRow("SelectedClicked 4") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked) + << QAbstractItemView::EditKeyPressed << false; + + // EditKeyPressed + QTest::newRow("EditKeyPressed 0") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed) + << QAbstractItemView::NoEditTriggers << false; + QTest::newRow("EditKeyPressed 1") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed) + << QAbstractItemView::CurrentChanged << false; + QTest::newRow("EditKeyPressed 2") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed) + << QAbstractItemView::DoubleClicked << false; + QTest::newRow("EditKeyPressed 3") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed) + << QAbstractItemView::SelectedClicked << false; + QTest::newRow("EditKeyPressed 4") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed) + << QAbstractItemView::EditKeyPressed << true; +} + +void tst_QTreeView::editTriggers() +{ + QFETCH(QAbstractItemView::EditTriggers, editTriggers); + QFETCH(QAbstractItemView::EditTrigger, triggeredTrigger); + QFETCH(bool, editorOpened); + + QTreeView view; + QStandardItemModel treeModel; + initStandardTreeModel(&treeModel); + view.setModel(&treeModel); + view.show(); + + QCOMPARE(view.editTriggers(), QAbstractItemView::EditKeyPressed | QAbstractItemView::DoubleClicked); + + // Initialize the first index + view.setCurrentIndex(view.model()->index(0, 0)); + + // Verify that we don't have any editor initially + QVERIFY(!qFindChild(&view, QString())); + + // Set the triggers + view.setEditTriggers(editTriggers); + + // Interact with the view + switch (triggeredTrigger) { + case QAbstractItemView::NoEditTriggers: + // Do nothing, the editor shouldn't be there + break; + case QAbstractItemView::CurrentChanged: + // Change the index to open an editor + view.setCurrentIndex(view.model()->index(1, 0)); + break; + case QAbstractItemView::DoubleClicked: + // Doubleclick the center of the current cell + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, + view.visualRect(view.model()->index(0, 0)).center()); + QTest::mouseDClick(view.viewport(), Qt::LeftButton, 0, + view.visualRect(view.model()->index(0, 0)).center()); + break; + case QAbstractItemView::SelectedClicked: + // Click the center of the current cell + view.selectionModel()->select(view.model()->index(0, 0), QItemSelectionModel::Select); + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, + view.visualRect(view.model()->index(0, 0)).center()); + QTest::qWait(int(QApplication::doubleClickInterval() * 1.5)); + break; + case QAbstractItemView::EditKeyPressed: + view.setFocus(); +#ifdef Q_OS_MAC + // Mac OS X uses Enter for editing + QTest::keyPress(&view, Qt::Key_Enter); +#else + // All other platforms use F2 + QTest::keyPress(&view, Qt::Key_F2); +#endif + break; + default: + break; + } + + // Check if we got an editor + QTRY_COMPARE(qFindChild(&view, QString()) != 0, editorOpened); +} + +void tst_QTreeView::hasAutoScroll() +{ + QTreeView view; + QVERIFY(view.hasAutoScroll()); + view.setAutoScroll(false); + QVERIFY(!view.hasAutoScroll()); + view.setAutoScroll(true); + QVERIFY(view.hasAutoScroll()); +} + +void tst_QTreeView::horizontalScrollMode() +{ + QStandardItemModel model; + for (int i = 0; i < 100; ++i) { + model.appendRow(QList() + << new QStandardItem("An item that has very long text and should" + " cause the horizontal scroll bar to appear.") + << new QStandardItem("An item that has very long text and should" + " cause the horizontal scroll bar to appear.")); + } + + QTreeView view; + view.setModel(&model); + view.setFixedSize(100, 100); + view.header()->resizeSection(0, 200); + view.show(); + + QCOMPARE(view.horizontalScrollMode(), QAbstractItemView::ScrollPerPixel); + QCOMPARE(view.horizontalScrollBar()->minimum(), 0); + QVERIFY(view.horizontalScrollBar()->maximum() > 2); + + view.setHorizontalScrollMode(QAbstractItemView::ScrollPerItem); + QCOMPARE(view.horizontalScrollMode(), QAbstractItemView::ScrollPerItem); + QCOMPARE(view.horizontalScrollBar()->minimum(), 0); + QCOMPARE(view.horizontalScrollBar()->maximum(), 1); + + view.setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + QCOMPARE(view.horizontalScrollMode(), QAbstractItemView::ScrollPerPixel); + QCOMPARE(view.horizontalScrollBar()->minimum(), 0); + QVERIFY(view.horizontalScrollBar()->maximum() > 2); +} + +class RepaintTreeView : public QTreeView +{ +public: + RepaintTreeView() : repainted(false) { } + bool repainted; + +protected: + void paintEvent(QPaintEvent *event) + { repainted = true; QTreeView::paintEvent(event); } +}; + +void tst_QTreeView::iconSize() +{ + RepaintTreeView view; + QCOMPARE(view.iconSize(), QSize()); + + QStandardItemModel treeModel; + initStandardTreeModel(&treeModel); + view.setModel(&treeModel); + QCOMPARE(view.iconSize(), QSize()); + QVERIFY(!view.repainted); + + view.show(); + view.update(); + QTest::qWaitForWindowShown(&view); + QTRY_VERIFY(view.repainted); + QCOMPARE(view.iconSize(), QSize()); + + view.repainted = false; + view.setIconSize(QSize()); + QTRY_VERIFY(!view.repainted); + QCOMPARE(view.iconSize(), QSize()); + + view.setIconSize(QSize(10, 10)); + QTRY_VERIFY(view.repainted); + QCOMPARE(view.iconSize(), QSize(10, 10)); + + view.repainted = false; + view.setIconSize(QSize(10000, 10000)); + QTRY_VERIFY(view.repainted); + QCOMPARE(view.iconSize(), QSize(10000, 10000)); +} + +void tst_QTreeView::indexAt() +{ + QtTestModel model; + model.rows = model.cols = 5; + + QTreeView view; + QCOMPARE(view.indexAt(QPoint()), QModelIndex()); + view.setModel(&model); + QVERIFY(view.indexAt(QPoint()) != QModelIndex()); + + QSize itemSize = view.visualRect(model.index(0, 0)).size(); + for (int i = 0; i < model.rowCount(); ++i) { + QPoint pos(itemSize.width() / 2, (i * itemSize.height()) + (itemSize.height() / 2)); + QModelIndex index = view.indexAt(pos); + QCOMPARE(index, model.index(i, 0)); + } + + /* + // ### this is wrong; the widget _will_ affect the item size + for (int j = 0; j < model.rowCount(); ++j) + view.setIndexWidget(model.index(j, 0), new QPushButton); + */ + + for (int k = 0; k < model.rowCount(); ++k) { + QPoint pos(itemSize.width() / 2, (k * itemSize.height()) + (itemSize.height() / 2)); + QModelIndex index = view.indexAt(pos); + QCOMPARE(index, model.index(k, 0)); + } + + view.show(); + view.resize(600, 600); + view.header()->setStretchLastSection(false); + QCOMPARE(view.indexAt(QPoint(550, 20)), QModelIndex()); +} + +void tst_QTreeView::indexWidget() +{ + QTreeView view; + QStandardItemModel treeModel; + initStandardTreeModel(&treeModel); + view.setModel(&treeModel); + + QModelIndex index = view.model()->index(0, 0); + + QVERIFY(!view.indexWidget(QModelIndex())); + QVERIFY(!view.indexWidget(index)); + + QString text = QLatin1String("TestLabel"); + + QWidget *label = new QLabel(text); + view.setIndexWidget(QModelIndex(), label); + QVERIFY(!view.indexWidget(QModelIndex())); + QVERIFY(!label->parent()); + view.setIndexWidget(index, label); + QCOMPARE(view.indexWidget(index), label); + QCOMPARE(label->parentWidget(), view.viewport()); + + + QTextEdit *widget = new QTextEdit(text); + widget->setFixedSize(200,100); + view.setIndexWidget(index, widget); + QCOMPARE(view.indexWidget(index), static_cast(widget)); + + QCOMPARE(widget->parentWidget(), view.viewport()); + QCOMPARE(widget->geometry(), view.visualRect(index).intersected(widget->geometry())); + QCOMPARE(widget->toPlainText(), text); + + //now let's try to do that later when the widget is already shown + view.show(); + index = view.model()->index(1, 0); + QVERIFY(!view.indexWidget(index)); + + widget = new QTextEdit(text); + widget->setFixedSize(200,100); + view.setIndexWidget(index, widget); + QCOMPARE(view.indexWidget(index), static_cast(widget)); + + QCOMPARE(widget->parentWidget(), view.viewport()); + QCOMPARE(widget->geometry(), view.visualRect(index).intersect(widget->geometry())); + QCOMPARE(widget->toPlainText(), text); +} + +void tst_QTreeView::itemDelegate() +{ + QPointer oldDelegate; + QPointer otherItemDelegate; + + { + QTreeView view; + QVERIFY(qobject_cast(view.itemDelegate())); + QPointer oldDelegate = view.itemDelegate(); + + otherItemDelegate = new QItemDelegate; + view.setItemDelegate(otherItemDelegate); + QVERIFY(!otherItemDelegate->parent()); + QVERIFY(oldDelegate); + + QCOMPARE(view.itemDelegate(), (QAbstractItemDelegate *)otherItemDelegate); + + view.setItemDelegate(0); + QVERIFY(!view.itemDelegate()); // <- view does its own drawing? + QVERIFY(otherItemDelegate); + } + + // This is strange. Why doesn't setItemDelegate() reparent the delegate? + QVERIFY(!oldDelegate); + QVERIFY(otherItemDelegate); + + delete otherItemDelegate; +} + +void tst_QTreeView::itemDelegateForColumnOrRow() +{ + QTreeView view; + QAbstractItemDelegate *defaultDelegate = view.itemDelegate(); + QVERIFY(defaultDelegate); + + QVERIFY(!view.itemDelegateForRow(0)); + QVERIFY(!view.itemDelegateForColumn(0)); + QCOMPARE(view.itemDelegate(QModelIndex()), defaultDelegate); + + QStandardItemModel model; + for (int i = 0; i < 100; ++i) { + model.appendRow(QList() + << new QStandardItem("An item that has very long text and should" + " cause the horizontal scroll bar to appear.") + << new QStandardItem("An item that has very long text and should" + " cause the horizontal scroll bar to appear.") + << new QStandardItem("An item that has very long text and should" + " cause the horizontal scroll bar to appear.")); + } + view.setModel(&model); + + QVERIFY(!view.itemDelegateForRow(0)); + QVERIFY(!view.itemDelegateForColumn(0)); + QCOMPARE(view.itemDelegate(QModelIndex()), defaultDelegate); + QCOMPARE(view.itemDelegate(view.model()->index(0, 0)), defaultDelegate); + + QPointer rowDelegate = new QItemDelegate; + view.setItemDelegateForRow(0, rowDelegate); + QVERIFY(!rowDelegate->parent()); + QCOMPARE(view.itemDelegateForRow(0), (QAbstractItemDelegate *)rowDelegate); + QCOMPARE(view.itemDelegate(view.model()->index(0, 0)), (QAbstractItemDelegate *)rowDelegate); + QCOMPARE(view.itemDelegate(view.model()->index(0, 1)), (QAbstractItemDelegate *)rowDelegate); + QCOMPARE(view.itemDelegate(view.model()->index(1, 0)), defaultDelegate); + QCOMPARE(view.itemDelegate(view.model()->index(1, 1)), defaultDelegate); + + QPointer columnDelegate = new QItemDelegate; + view.setItemDelegateForColumn(1, columnDelegate); + QVERIFY(!columnDelegate->parent()); + QCOMPARE(view.itemDelegateForColumn(1), (QAbstractItemDelegate *)columnDelegate); + QCOMPARE(view.itemDelegate(view.model()->index(0, 0)), (QAbstractItemDelegate *)rowDelegate); + QCOMPARE(view.itemDelegate(view.model()->index(0, 1)), (QAbstractItemDelegate *)rowDelegate); // row wins + QCOMPARE(view.itemDelegate(view.model()->index(1, 0)), defaultDelegate); + QCOMPARE(view.itemDelegate(view.model()->index(1, 1)), (QAbstractItemDelegate *)columnDelegate); + + view.setItemDelegateForRow(0, 0); + QVERIFY(!view.itemDelegateForRow(0)); + QVERIFY(rowDelegate); // <- wasn't deleted + + view.setItemDelegateForColumn(1, 0); + QVERIFY(!view.itemDelegateForColumn(1)); + QVERIFY(columnDelegate); // <- wasn't deleted + + delete rowDelegate; + delete columnDelegate; +} + +void tst_QTreeView::keyboardSearch() +{ + QTreeView view; + QStandardItemModel model; + model.appendRow(new QStandardItem("Andreas")); + model.appendRow(new QStandardItem("Baldrian")); + model.appendRow(new QStandardItem("Cecilie")); + view.setModel(&model); + view.show(); + + // Nothing is selected + QVERIFY(!view.selectionModel()->hasSelection()); + QVERIFY(!view.selectionModel()->isSelected(model.index(0, 0))); + + // First item is selected + view.keyboardSearch(QLatin1String("A")); + QTRY_VERIFY(view.selectionModel()->isSelected(model.index(0, 0))); + + // First item is still selected + view.keyboardSearch(QLatin1String("n")); + QVERIFY(view.selectionModel()->isSelected(model.index(0, 0))); + + // No "AnB" item - keep the same selection. + view.keyboardSearch(QLatin1String("B")); + QVERIFY(view.selectionModel()->isSelected(model.index(0, 0))); + + // Wait a bit. + QTest::qWait(QApplication::keyboardInputInterval() * 2); + + // The item that starts with B is selected. + view.keyboardSearch(QLatin1String("B")); + QVERIFY(view.selectionModel()->isSelected(model.index(1, 0))); +} + +void tst_QTreeView::setModel() +{ + QTreeView view; + view.show(); + QCOMPARE(view.model(), (QAbstractItemModel*)0); + QCOMPARE(view.selectionModel(), (QItemSelectionModel*)0); + QCOMPARE(view.header()->model(), (QAbstractItemModel*)0); + QCOMPARE(view.header()->selectionModel(), (QItemSelectionModel*)0); + + for (int x = 0; x < 2; ++x) { + QtTestModel *model = new QtTestModel(10, 8); + QAbstractItemModel *oldModel = view.model(); + QSignalSpy modelDestroyedSpy(oldModel ? oldModel : model, SIGNAL(destroyed())); + // set the same model twice + for (int i = 0; i < 2; ++i) { + QItemSelectionModel *oldSelectionModel = view.selectionModel(); + QItemSelectionModel *dummy = new QItemSelectionModel(model); + QSignalSpy selectionModelDestroyedSpy( + oldSelectionModel ? oldSelectionModel : dummy, SIGNAL(destroyed())); + view.setModel(model); +// QCOMPARE(selectionModelDestroyedSpy.count(), (x == 0 || i == 1) ? 0 : 1); + QCOMPARE(view.model(), (QAbstractItemModel *)model); + QCOMPARE(view.header()->model(), (QAbstractItemModel *)model); + QCOMPARE(view.selectionModel() != oldSelectionModel, (i == 0)); + } + QTRY_COMPARE(modelDestroyedSpy.count(), 0); + + view.setModel(0); + QCOMPARE(view.model(), (QAbstractItemModel*)0); + // ### shouldn't selectionModel also be 0 now? +// QCOMPARE(view.selectionModel(), (QItemSelectionModel*)0); + delete model; + } +} + +void tst_QTreeView::openPersistentEditor() +{ + QTreeView view; + QStandardItemModel treeModel; + initStandardTreeModel(&treeModel); + view.setModel(&treeModel); + view.show(); + + QVERIFY(!qFindChild(view.viewport())); + view.openPersistentEditor(view.model()->index(0, 0)); + QVERIFY(qFindChild(view.viewport())); + + view.closePersistentEditor(view.model()->index(0, 0)); + QVERIFY(!qFindChild(view.viewport())->isVisible()); + + qApp->sendPostedEvents(0, QEvent::DeferredDelete); + QVERIFY(!qFindChild(view.viewport())); +} + +void tst_QTreeView::rootIndex() +{ + QTreeView view; + QCOMPARE(view.rootIndex(), QModelIndex()); + QStandardItemModel treeModel; + initStandardTreeModel(&treeModel); + view.setModel(&treeModel); + QCOMPARE(view.rootIndex(), QModelIndex()); + + view.setRootIndex(view.model()->index(1, 0)); + + QCOMPARE(view.model()->data(view.model()->index(0, view.header()->visualIndex(0), view.rootIndex()), Qt::DisplayRole) + .toString(), QString("Row 2 Child Item")); +} + +void tst_QTreeView::setHeader() +{ + QTreeView view; + QVERIFY(view.header() != 0); + QCOMPARE(view.header()->orientation(), Qt::Horizontal); + QCOMPARE(view.header()->parent(), (QObject *)&view); + for (int x = 0; x < 2; ++x) { + QSignalSpy destroyedSpy(view.header(), SIGNAL(destroyed())); + Qt::Orientation orient = x ? Qt::Vertical : Qt::Horizontal; + QHeaderView *head = new QHeaderView(orient); + view.setHeader(head); + QCOMPARE(destroyedSpy.count(), 1); + QCOMPARE(head->parent(), (QObject *)&view); + QCOMPARE(view.header(), head); + view.setHeader(head); + QCOMPARE(view.header(), head); + // Itemviews in Qt < 4.2 have asserts for this. Qt >= 4.2 should handle this gracefully + view.setHeader((QHeaderView *)0); + QCOMPARE(view.header(), head); + } +} + +void tst_QTreeView::columnHidden() +{ + QTreeView view; + QtTestModel model(10, 8); + view.setModel(&model); + view.show(); + for (int c = 0; c < model.columnCount(); ++c) + QCOMPARE(view.isColumnHidden(c), false); + // hide even columns + for (int c = 0; c < model.columnCount(); c+=2) + view.setColumnHidden(c, true); + for (int c = 0; c < model.columnCount(); ++c) + QCOMPARE(view.isColumnHidden(c), (c & 1) == 0); + view.update(); + // hide odd columns too + for (int c = 1; c < model.columnCount(); c+=2) + view.setColumnHidden(c, true); + for (int c = 0; c < model.columnCount(); ++c) + QCOMPARE(view.isColumnHidden(c), true); + view.update(); +} + +void tst_QTreeView::rowHidden() +{ + QtTestModel model(4, 6); + model.levels = 3; + QTreeView view; + view.resize(500,500); + view.setModel(&model); + view.show(); + + QCOMPARE(view.isRowHidden(-1, QModelIndex()), false); + QCOMPARE(view.isRowHidden(999999, QModelIndex()), false); + view.setRowHidden(-1, QModelIndex(), true); + view.setRowHidden(999999, QModelIndex(), true); + QCOMPARE(view.isRowHidden(-1, QModelIndex()), false); + QCOMPARE(view.isRowHidden(999999, QModelIndex()), false); + + view.setRowHidden(0, QModelIndex(), true); + QCOMPARE(view.isRowHidden(0, QModelIndex()), true); + view.setRowHidden(0, QModelIndex(), false); + QCOMPARE(view.isRowHidden(0, QModelIndex()), false); + + QStack parents; + parents.push(QModelIndex()); + while (!parents.isEmpty()) { + QModelIndex p = parents.pop(); + if (model.canFetchMore(p)) + model.fetchMore(p); + int rows = model.rowCount(p); + // hide all + for (int r = 0; r < rows; ++r) { + view.setRowHidden(r, p, true); + QCOMPARE(view.isRowHidden(r, p), true); + } + // hide none + for (int r = 0; r < rows; ++r) { + view.setRowHidden(r, p, false); + QCOMPARE(view.isRowHidden(r, p), false); + } + // hide only even rows + for (int r = 0; r < rows; ++r) { + bool hide = (r & 1) == 0; + view.setRowHidden(r, p, hide); + QCOMPARE(view.isRowHidden(r, p), hide); + } + for (int r = 0; r < rows; ++r) + parents.push(model.index(r, 0, p)); + } + + parents.push(QModelIndex()); + while (!parents.isEmpty()) { + QModelIndex p = parents.pop(); + // all even rows should still be hidden + for (int r = 0; r < model.rowCount(p); ++r) + QCOMPARE(view.isRowHidden(r, p), (r & 1) == 0); + if (model.rowCount(p) > 0) { + for (int r = 0; r < model.rowCount(p); ++r) + parents.push(model.index(r, 0, p)); + } + } +} + +void tst_QTreeView::noDelegate() +{ + QtTestModel model(10, 7); + QTreeView view; + view.setModel(&model); + view.setItemDelegate(0); + QCOMPARE(view.itemDelegate(), (QAbstractItemDelegate *)0); +} + +void tst_QTreeView::noModel() +{ + QTreeView view; + view.show(); + view.setRowHidden(0, QModelIndex(), true); +} + +void tst_QTreeView::emptyModel() +{ + QtTestModel model; + QTreeView view; + view.setModel(&model); + view.show(); + QVERIFY(!model.wrongIndex); +} + +void tst_QTreeView::removeRows() +{ + QtTestModel model(7, 10); + + QTreeView view; + + view.setModel(&model); + view.show(); + + model.removeLastRow(); + QVERIFY(!model.wrongIndex); + + model.removeAllRows(); + QVERIFY(!model.wrongIndex); +} + +void tst_QTreeView::removeCols() +{ + QtTestModel model(5, 8); + + QTreeView view; + view.setModel(&model); + view.show(); + model.fetched = true; + model.removeLastColumn(); + QVERIFY(!model.wrongIndex); + QCOMPARE(view.header()->count(), model.cols); + + model.removeAllColumns(); + QVERIFY(!model.wrongIndex); + QCOMPARE(view.header()->count(), model.cols); +} + +void tst_QTreeView::expandAndCollapse_data() +{ + QTest::addColumn("animationEnabled"); + QTest::newRow("normal") << false; + QTest::newRow("animated") << true; + +} + +void tst_QTreeView::expandAndCollapse() +{ + QFETCH(bool, animationEnabled); + + QtTestModel model(10, 9); + + QTreeView view; + view.setUniformRowHeights(true); + view.setModel(&model); + view.setAnimated(animationEnabled); + view.show(); + + QModelIndex a = model.index(0, 0, QModelIndex()); + QModelIndex b = model.index(0, 0, a); + + QSignalSpy expandedSpy(&view, SIGNAL(expanded(const QModelIndex&))); + QSignalSpy collapsedSpy(&view, SIGNAL(collapsed(const QModelIndex&))); + QVariantList args; + + for (int y = 0; y < 2; ++y) { + view.setVisible(y == 0); + for (int x = 0; x < 2; ++x) { + view.setItemsExpandable(x == 0); + + // Test bad args + view.expand(QModelIndex()); + QCOMPARE(view.isExpanded(QModelIndex()), false); + view.collapse(QModelIndex()); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(collapsedSpy.count(), 0); + + // expand a first level item + QVERIFY(!view.isExpanded(a)); + view.expand(a); + QVERIFY(view.isExpanded(a)); + QCOMPARE(expandedSpy.count(), 1); + QCOMPARE(collapsedSpy.count(), 0); + args = expandedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), a); + + view.expand(a); + QVERIFY(view.isExpanded(a)); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(collapsedSpy.count(), 0); + + // expand a second level item + QVERIFY(!view.isExpanded(b)); + view.expand(b); + QVERIFY(view.isExpanded(a)); + QVERIFY(view.isExpanded(b)); + QCOMPARE(expandedSpy.count(), 1); + QCOMPARE(collapsedSpy.count(), 0); + args = expandedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), b); + + view.expand(b); + QVERIFY(view.isExpanded(b)); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(collapsedSpy.count(), 0); + + // collapse the first level item + view.collapse(a); + QVERIFY(!view.isExpanded(a)); + QVERIFY(view.isExpanded(b)); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(collapsedSpy.count(), 1); + args = collapsedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), a); + + view.collapse(a); + QVERIFY(!view.isExpanded(a)); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(collapsedSpy.count(), 0); + + // expand the first level item again + view.expand(a); + QVERIFY(view.isExpanded(a)); + QVERIFY(view.isExpanded(b)); + QCOMPARE(expandedSpy.count(), 1); + QCOMPARE(collapsedSpy.count(), 0); + args = expandedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), a); + + // collapse the second level item + view.collapse(b); + QVERIFY(view.isExpanded(a)); + QVERIFY(!view.isExpanded(b)); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(collapsedSpy.count(), 1); + args = collapsedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), b); + + // collapse the first level item + view.collapse(a); + QVERIFY(!view.isExpanded(a)); + QVERIFY(!view.isExpanded(b)); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(collapsedSpy.count(), 1); + args = collapsedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), a); + + // expand and remove row + QPersistentModelIndex c = model.index(9, 0, b); + view.expand(a); + view.expand(b); + model.removeLastRow(); // remove c + QVERIFY(view.isExpanded(a)); + QVERIFY(view.isExpanded(b)); + QVERIFY(!view.isExpanded(c)); + QCOMPARE(expandedSpy.count(), 2); + QCOMPARE(collapsedSpy.count(), 0); + args = expandedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), a); + args = expandedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), b); + + view.collapse(a); + view.collapse(b); + QVERIFY(!view.isExpanded(a)); + QVERIFY(!view.isExpanded(b)); + QVERIFY(!view.isExpanded(c)); + QCOMPARE(expandedSpy.count(), 0); + QCOMPARE(collapsedSpy.count(), 2); + args = collapsedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), a); + args = collapsedSpy.takeFirst(); + QCOMPARE(qvariant_cast(args.at(0)), b); + } + } +} + +void tst_QTreeView::expandAndCollapseAll() +{ + QtTestModel model(3, 2); + model.levels = 2; + QTreeView view; + view.setUniformRowHeights(true); + view.setModel(&model); + + QSignalSpy expandedSpy(&view, SIGNAL(expanded(const QModelIndex&))); + QSignalSpy collapsedSpy(&view, SIGNAL(collapsed(const QModelIndex&))); + + view.expandAll(); + view.show(); + + QCOMPARE(collapsedSpy.count(), 0); + + QStack parents; + parents.push(QModelIndex()); + int count = 0; + while (!parents.isEmpty()) { + QModelIndex p = parents.pop(); + int rows = model.rowCount(p); + for (int r = 0; r < rows; ++r) + QVERIFY(view.isExpanded(model.index(r, 0, p))); + count += rows; + for (int r = 0; r < rows; ++r) + parents.push(model.index(r, 0, p)); + } +// ### why is expanded() signal not emitted? +// QCOMPARE(expandedSpy.count(), count); + + view.collapseAll(); + + QCOMPARE(expandedSpy.count(), 0); + + parents.push(QModelIndex()); + count = 0; + while (!parents.isEmpty()) { + QModelIndex p = parents.pop(); + int rows = model.rowCount(p); + for (int r = 0; r < rows; ++r) + QVERIFY(!view.isExpanded(model.index(r, 0, p))); + count += rows; + for (int r = 0; r < rows; ++r) + parents.push(model.index(r, 0, p)); + } +// ### why is collapsed() signal not emitted? +// QCOMPARE(collapsedSpy.count(), count); +} + +void tst_QTreeView::expandWithNoChildren() +{ + QTreeView tree; + QStandardItemModel model(1,1); + tree.setModel(&model); + tree.setAnimated(true); + tree.doItemsLayout(); + //this test should not output warnings + tree.expand(model.index(0,0)); +} + + + +void tst_QTreeView::keyboardNavigation() +{ + const int rows = 10; + const int columns = 7; + + QtTestModel model(rows, columns); + + QTreeView view; + view.setModel(&model); + view.show(); + + QVector keymoves; + keymoves << Qt::Key_Down << Qt::Key_Right << Qt::Key_Right << Qt::Key_Right + << Qt::Key_Down << Qt::Key_Down << Qt::Key_Down << Qt::Key_Down + << Qt::Key_Right << Qt::Key_Right << Qt::Key_Right + << Qt::Key_Left << Qt::Key_Up << Qt::Key_Left << Qt::Key_Left + << Qt::Key_Up << Qt::Key_Down << Qt::Key_Up << Qt::Key_Up + << Qt::Key_Up << Qt::Key_Up << Qt::Key_Up << Qt::Key_Up + << Qt::Key_Left << Qt::Key_Left << Qt::Key_Up << Qt::Key_Down; + + int row = 0; + int column = 0; + QModelIndex index = model.index(row, column, QModelIndex()); + view.setCurrentIndex(index); + QCOMPARE(view.currentIndex(), index); + + for (int i = 0; i < keymoves.size(); ++i) { + Qt::Key key = keymoves.at(i); + QTest::keyClick(&view, key); + + switch (key) { + case Qt::Key_Up: + if (row > 0) { + index = index.sibling(row - 1, column); + row -= 1; + } else if (index.parent() != QModelIndex()) { + index = index.parent(); + row = index.row(); + } + break; + case Qt::Key_Down: + if (view.isExpanded(index)) { + row = 0; + index = index.child(row, column); + } else { + row = qMin(rows - 1, row + 1); + index = index.sibling(row, column); + } + break; + case Qt::Key_Left: { + QScrollBar *b = view.horizontalScrollBar(); + if (b->value() == b->minimum()) + QVERIFY(!view.isExpanded(index)); + // windows style right will walk to the parent + if (view.currentIndex() != index) { + QCOMPARE(view.currentIndex(), index.parent()); + index = view.currentIndex(); + row = index.row(); + column = index.column(); + } + break; + } + case Qt::Key_Right: + QVERIFY(view.isExpanded(index)); + // windows style right will walk to the first child + if (view.currentIndex() != index) { + QCOMPARE(view.currentIndex().parent(), index); + row = view.currentIndex().row(); + column = view.currentIndex().column(); + index = view.currentIndex(); + } + break; + default: + QVERIFY(false); + } + + QCOMPARE(view.currentIndex().row(), row); + QCOMPARE(view.currentIndex().column(), column); + QCOMPARE(view.currentIndex(), index); + } +} + +class Dmodel : public QtTestModel +{ +public: + Dmodel() : QtTestModel(10, 10){} + + int columnCount(const QModelIndex &parent) const + { + if (parent.row() == 5) + return 1; + return QtTestModel::columnCount(parent); + } +}; + +void tst_QTreeView::headerSections() +{ + Dmodel model; + + QTreeView view; + QHeaderView *header = view.header(); + + view.setModel(&model); + QModelIndex index = model.index(5, 0); + view.setRootIndex(index); + QCOMPARE(model.columnCount(QModelIndex()), 10); + QCOMPARE(model.columnCount(index), 1); + QCOMPARE(header->count(), model.columnCount(index)); +} + +void tst_QTreeView::moveCursor_data() +{ + QTest::addColumn("uniformRowHeights"); + QTest::addColumn("scrollPerPixel"); + QTest::newRow("uniformRowHeights = false, scrollPerPixel = false") + << false << false; + QTest::newRow("uniformRowHeights = true, scrollPerPixel = false") + << true << false; + QTest::newRow("uniformRowHeights = false, scrollPerPixel = true") + << false << true; + QTest::newRow("uniformRowHeights = true, scrollPerPixel = true") + << true << true; +} + +void tst_QTreeView::moveCursor() +{ + QFETCH(bool, uniformRowHeights); + QFETCH(bool, scrollPerPixel); + QtTestModel model(8, 6); + + PublicView view; + view.setUniformRowHeights(uniformRowHeights); + view.setModel(&model); + view.setRowHidden(0, QModelIndex(), true); + view.setVerticalScrollMode(scrollPerPixel ? + QAbstractItemView::ScrollPerPixel : + QAbstractItemView::ScrollPerItem); + QVERIFY(view.isRowHidden(0, QModelIndex())); + view.setColumnHidden(0, true); + QVERIFY(view.isColumnHidden(0)); + view.show(); + qApp->setActiveWindow(&view); + + //here the first visible index should be selected + //because the view got the focus + QModelIndex expected = model.index(1, 1, QModelIndex()); + QCOMPARE(view.currentIndex(), expected); + + //then pressing down should go to the next line + QModelIndex actual = view.moveCursor(PublicView::MoveDown, Qt::NoModifier); + expected = model.index(2, 1, QModelIndex()); + QCOMPARE(actual, expected); + + view.setRowHidden(0, QModelIndex(), false); + view.setColumnHidden(0, false); + + // PageUp was broken with uniform row heights turned on + view.setCurrentIndex(model.index(1, 0)); + actual = view.moveCursor(PublicView::MovePageUp, Qt::NoModifier); + expected = model.index(0, 0, QModelIndex()); + QCOMPARE(actual, expected); + + //let's try another column + view.setCurrentIndex(model.index(1, 1)); + view.setSelectionBehavior(QAbstractItemView::SelectItems); + QTest::keyClick(view.viewport(), Qt::Key_Up); + expected = model.index(0, 1, QModelIndex()); + QCOMPARE(view.currentIndex(), expected); + QTest::keyClick(view.viewport(), Qt::Key_Down); + expected = model.index(1, 1, QModelIndex()); + QCOMPARE(view.currentIndex(), expected); + QTest::keyClick(view.viewport(), Qt::Key_Up); + expected = model.index(0, 1, QModelIndex()); + QCOMPARE(view.currentIndex(), expected); + view.setColumnHidden(0, true); + QTest::keyClick(view.viewport(), Qt::Key_Left); + expected = model.index(0, 1, QModelIndex()); //it shouldn't have changed + QCOMPARE(view.currentIndex(), expected); + view.setColumnHidden(0, false); + QTest::keyClick(view.viewport(), Qt::Key_Left); + expected = model.index(0, 0, QModelIndex()); + QCOMPARE(view.currentIndex(), expected); +} + +class TestDelegate : public QItemDelegate +{ +public: + TestDelegate(QObject *parent) : QItemDelegate(parent) {} + QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const { return QSize(200, 50); } +}; + +typedef QList PointList; +Q_DECLARE_METATYPE(PointList) + +void tst_QTreeView::setSelection_data() +{ + QTest::addColumn("selectionRect"); + QTest::addColumn("selectionMode"); + QTest::addColumn("selectionCommand"); + QTest::addColumn("expectedItems"); + QTest::addColumn("verticalOffset"); + + QTest::newRow("(0,0,50,20),rows") << QRect(0,0,50,20) + << int(QAbstractItemView::SingleSelection) + << int(QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows) + << (PointList() + << QPoint(0,0) << QPoint(1,0) << QPoint(2,0) << QPoint(3,0) << QPoint(4,0) + ) + << 0; + + QTest::newRow("(0,0,50,90),rows") << QRect(0,0,50,90) + << int(QAbstractItemView::ExtendedSelection) + << int(QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows) + << (PointList() + << QPoint(0,0) << QPoint(1,0) << QPoint(2,0) << QPoint(3,0) << QPoint(4,0) + << QPoint(0,1) << QPoint(1,1) << QPoint(2,1) << QPoint(3,1) << QPoint(4,1) + ) + << 0; + + QTest::newRow("(50,0,0,90),rows,invalid rect") << QRect(QPoint(50, 0), QPoint(0, 90)) + << int(QAbstractItemView::ExtendedSelection) + << int(QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows) + << (PointList() + << QPoint(0,0) << QPoint(1,0) << QPoint(2,0) << QPoint(3,0) << QPoint(4,0) + << QPoint(0,1) << QPoint(1,1) << QPoint(2,1) << QPoint(3,1) << QPoint(4,1) + ) + << 0; + + QTest::newRow("(0,-20,20,50),rows") << QRect(0,-20,20,50) + << int(QAbstractItemView::ExtendedSelection) + << int(QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows) + << (PointList() + << QPoint(0,0) << QPoint(1,0) << QPoint(2,0) << QPoint(3,0) << QPoint(4,0) + << QPoint(0,1) << QPoint(1,1) << QPoint(2,1) << QPoint(3,1) << QPoint(4,1) + ) + << 1; + QTest::newRow("(0,-50,20,90),rows") << QRect(0,-50,20,90) + << int(QAbstractItemView::ExtendedSelection) + << int(QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows) + << (PointList() + << QPoint(0,0) << QPoint(1,0) << QPoint(2,0) << QPoint(3,0) << QPoint(4,0) + << QPoint(0,1) << QPoint(1,1) << QPoint(2,1) << QPoint(3,1) << QPoint(4,1) + ) + << 1; + +} + +void tst_QTreeView::setSelection() +{ + QFETCH(QRect, selectionRect); + QFETCH(int, selectionMode); + QFETCH(int, selectionCommand); + QFETCH(PointList, expectedItems); + QFETCH(int, verticalOffset); + + QtTestModel model(10, 5); + model.levels = 1; + model.setDecorationsEnabled(true); + PublicView view; + view.resize(400, 300); + view.show(); + view.setRootIsDecorated(false); + view.setItemDelegate(new TestDelegate(&view)); + view.setSelectionMode(QAbstractItemView::SelectionMode(selectionMode)); + view.setModel(&model); + view.setUniformRowHeights(true); + view.setVerticalScrollMode(QAbstractItemView::ScrollPerItem); + view.scrollTo(model.index(verticalOffset, 0), QAbstractItemView::PositionAtTop); + view.setSelection(selectionRect, QItemSelectionModel::SelectionFlags(selectionCommand)); + QItemSelectionModel *selectionModel = view.selectionModel(); + QVERIFY(selectionModel); + + QModelIndexList selectedIndexes = selectionModel->selectedIndexes(); + QCOMPARE(selectedIndexes.count(), expectedItems.count()); + for (int i = 0; i < selectedIndexes.count(); ++i) { + QModelIndex idx = selectedIndexes.at(i); + QVERIFY(expectedItems.contains(QPoint(idx.column(), idx.row()))); + } +} + +void tst_QTreeView::indexAbove() +{ + QtTestModel model(6, 7); + model.levels = 2; + QTreeView view; + + QCOMPARE(view.indexAbove(QModelIndex()), QModelIndex()); + view.setModel(&model); + QCOMPARE(view.indexAbove(QModelIndex()), QModelIndex()); + + QStack parents; + parents.push(QModelIndex()); + while (!parents.isEmpty()) { + QModelIndex p = parents.pop(); + if (model.canFetchMore(p)) + model.fetchMore(p); + int rows = model.rowCount(p); + for (int r = rows - 1; r > 0; --r) { + QModelIndex idx = model.index(r, 0, p); + QModelIndex expected = model.index(r - 1, 0, p); + QCOMPARE(view.indexAbove(idx), expected); + } + // hide even rows + for (int r = 0; r < rows; r+=2) + view.setRowHidden(r, p, true); + for (int r = rows - 1; r > 0; r-=2) { + QModelIndex idx = model.index(r, 0, p); + QModelIndex expected = model.index(r - 2, 0, p); + QCOMPARE(view.indexAbove(idx), expected); + } +// for (int r = 0; r < rows; ++r) +// parents.push(model.index(r, 0, p)); + } +} + +void tst_QTreeView::indexBelow() +{ + QtTestModel model(2, 1); + + QTreeView view; + view.setModel(&model); + view.show(); + + QModelIndex i = model.index(0, 0, view.rootIndex()); + QVERIFY(i.isValid()); + QCOMPARE(i.row(), 0); + + i = view.indexBelow(i); + QVERIFY(i.isValid()); + QCOMPARE(i.row(), 1); + i = view.indexBelow(i); + QVERIFY(!i.isValid()); +} + +void tst_QTreeView::clicked() +{ + QtTestModel model(10, 2); + + QTreeView view; + view.setModel(&model); + view.show(); + + QModelIndex firstIndex = model.index(0, 0, QModelIndex()); + QVERIFY(firstIndex.isValid()); + int itemHeight = view.visualRect(firstIndex).height(); + int itemOffset = view.visualRect(firstIndex).width() / 2; + view.resize(200, itemHeight * (model.rows + 2)); + + for (int i = 0; i < model.rowCount(); ++i) { + QPoint p(itemOffset, 1 + itemHeight * i); + QModelIndex index = view.indexAt(p); + if (!index.isValid()) + continue; + QSignalSpy spy(&view, SIGNAL(clicked(const QModelIndex&))); + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, p); + QTRY_COMPARE(spy.count(), 1); + } +} + +void tst_QTreeView::mouseDoubleClick() +{ + // Test double clicks outside the viewport. + // (Should be a no-op and should not expand any item.) + + QStandardItemModel model(20, 2); + for (int i = 0; i < model.rowCount(); i++) { + QModelIndex index = model.index(i, 0, QModelIndex()); + model.insertRows(0, 20, index); + model.insertColumns(0,2,index); + for (int i1 = 0; i1 < model.rowCount(index); i1++) { + QModelIndex index2 = model.index(i1, 0, index); + } + } + + QTreeView view; + view.setModel(&model); + + // make sure the viewport height is smaller than the contents height. + view.resize(200,200); + view.move(0,0); + view.show(); + QModelIndex index = model.index(0, 0, QModelIndex()); + view.setCurrentIndex(index); + + view.setExpanded(model.index(0,0, QModelIndex()), true); + view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + // Make sure all items are collapsed + for (int i = 0; i < model.rowCount(QModelIndex()); i++) { + view.setExpanded(model.index(i,0, QModelIndex()), false); + } + + int maximum = view.verticalScrollBar()->maximum(); + + // Doubleclick in the bottom right corner, in the unused area between the vertical and horizontal scrollbar. + int vsw = view.verticalScrollBar()->width(); + int hsh = view.horizontalScrollBar()->height(); + QTest::mouseDClick(&view, Qt::LeftButton, Qt::NoModifier, QPoint(view.width() - vsw + 1, view.height() - hsh + 1)); + // Should not have expanded, thus maximum() should have the same value. + QCOMPARE(maximum, view.verticalScrollBar()->maximum()); + + view.setExpandsOnDoubleClick(false); + QTest::mouseDClick(&view, Qt::LeftButton, Qt::NoModifier, view.visualRect(index).center()); + QVERIFY(!view.isExpanded(index)); +} + +void tst_QTreeView::rowsAboutToBeRemoved() +{ + QStandardItemModel model(3, 1); + for (int i = 0; i < model.rowCount(); i++) { + QModelIndex index = model.index(i, 0, QModelIndex()); + model.setData(index, QString("%1").arg(i)); + model.insertRows(0, 4, index); + model.insertColumns(0,1,index); + for (int i1 = 0; i1 < model.rowCount(index); i1++) { + QModelIndex index2 = model.index(i1, 0, index); + model.setData(index2, QString("%1%2").arg(i).arg(i1)); + } + } + + PublicView view; + view.setModel(&model); + view.show(); + QModelIndex index = model.index(0,0, QModelIndex()); + view.setCurrentIndex(index); + view.setExpanded(model.index(0,0, QModelIndex()), true); + + for (int i = 0; i < model.rowCount(QModelIndex()); i++) { + view.setExpanded(model.index(i,0, QModelIndex()), true); + } + + QSignalSpy spy1(&model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + + model.removeRows(1,1); + QCOMPARE(view.state(), 0); + // Should not be 5 (or any other number for that sake :) + QCOMPARE(spy1.count(), 1); + +} + +void tst_QTreeView::headerSections_unhideSection() +{ + QtTestModel model(10, 7); + + QTreeView view; + + view.setModel(&model); + view.show(); + int size = view.header()->sectionSize(0); + view.setColumnHidden(0, true); + + // should go back to old size + view.setColumnHidden(0, false); + QCOMPARE(size, view.header()->sectionSize(0)); +} + +void tst_QTreeView::columnAt() +{ + QtTestModel model; + model.rows = model.cols = 10; + QTreeView view; + view.resize(500,500); + view.setModel(&model); + + QCOMPARE(view.columnAt(0), 0); + // really this is testing the header... so not much more should be needed if the header is working... +} + +void tst_QTreeView::scrollTo() +{ +#define CHECK_VISIBLE(ROW,COL) QVERIFY(QRect(QPoint(),view.viewport()->size()).contains(\ + view.visualRect(model.index((ROW),(COL),QModelIndex())))) + + QtTestModel model; + model.rows = model.cols = 100; + QTreeView view; + view.setUniformRowHeights(true); + view.scrollTo(QModelIndex(), QTreeView::PositionAtTop); + view.setModel(&model); + + // ### check the scrollbar values an make sure it actually scrolls to the item + // ### check for bot item based and pixel based scrolling + // ### create a data function for this test + + view.scrollTo(QModelIndex()); + view.scrollTo(model.index(0,0,QModelIndex())); + view.scrollTo(model.index(0,0,QModelIndex()), QTreeView::PositionAtTop); + view.scrollTo(model.index(0,0,QModelIndex()), QTreeView::PositionAtBottom); + + // + + view.show(); + view.setVerticalScrollMode(QAbstractItemView::ScrollPerItem); //some styles change that in Polish + + view.resize(300, 200); + //view.verticalScrollBar()->setValue(0); + + view.scrollTo(model.index(0,0,QModelIndex())); + CHECK_VISIBLE(0,0); + QCOMPARE(view.verticalScrollBar()->value(), 0); + + view.header()->resizeSection(0, 5); // now we only see the branches + view.scrollTo(model.index(5, 0, QModelIndex()), QTreeView::PositionAtTop); + QCOMPARE(view.verticalScrollBar()->value(), 5); + + view.scrollTo(model.index(60, 60, QModelIndex())); + + CHECK_VISIBLE(60,60); + view.scrollTo(model.index(60, 30, QModelIndex())); + CHECK_VISIBLE(60,30); + view.scrollTo(model.index(30, 30, QModelIndex())); + CHECK_VISIBLE(30,30); + + // TODO force it to move to the left and then the right +} + +void tst_QTreeView::rowsAboutToBeRemoved_move() +{ + QStandardItemModel model(3,1); + QTreeView view; + view.setModel(&model); + QModelIndex indexThatWantsToLiveButWillDieDieITellYou; + QModelIndex parent = model.index(2, 0 ); + view.expand(parent); + for (int i = 0; i < 6; ++i) { + model.insertRows(0, 1, parent); + model.insertColumns(0, 1, parent); + QModelIndex index = model.index(0, 0, parent); + view.expand(index); + if ( i == 3 ) + indexThatWantsToLiveButWillDieDieITellYou = index; + model.setData(index, i); + parent = index; + } + view.resize(600,800); + view.show(); + view.doItemsLayout(); + static_cast(&view)->executeDelayedItemsLayout(); + parent = indexThatWantsToLiveButWillDieDieITellYou.parent(); + QCOMPARE(view.isExpanded(indexThatWantsToLiveButWillDieDieITellYou), true); + QCOMPARE(parent.isValid(), true); + QCOMPARE(parent.parent().isValid(), true); + view.expand(parent); + QCOMPARE(view.isExpanded(parent), true); + QCOMPARE(view.isExpanded(indexThatWantsToLiveButWillDieDieITellYou), true); + model.removeRow(0, indexThatWantsToLiveButWillDieDieITellYou); + QCOMPARE(view.isExpanded(parent), true); + QCOMPARE(view.isExpanded(indexThatWantsToLiveButWillDieDieITellYou), true); +} + +void tst_QTreeView::resizeColumnToContents() +{ + QStandardItemModel model(50,2); + for (int r = 0; r < model.rowCount(); ++r) { + for (int c = 0; c < model.columnCount(); ++c) { + QModelIndex idx = model.index(r, c); + model.setData(idx, QString::fromAscii("%1,%2").arg(r).arg(c) ); + model.insertColumns(0, 2, idx); + model.insertRows(0, 6, idx); + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 2 ; ++j) { + model.setData(model.index(i, j, idx), QString::fromAscii("child%1%2").arg(i).arg(j)); + } + } + } + } + QTreeView view; + view.setModel(&model); + view.show(); + qApp->processEvents(); //must have this, or else it will not scroll + view.scrollToBottom(); + view.resizeColumnToContents(0); + int oldColumnSize = view.header()->sectionSize(0); + view.setRootIndex(model.index(0, 0)); + view.resizeColumnToContents(0); //Earlier, this gave an assert + QVERIFY(view.header()->sectionSize(0) > oldColumnSize); +} + +void tst_QTreeView::insertAfterSelect() +{ + QtTestModel model; + model.rows = model.cols = 10; + QTreeView view; + view.setModel(&model); + view.show(); + QModelIndex firstIndex = model.index(0, 0, QModelIndex()); + QVERIFY(firstIndex.isValid()); + int itemOffset = view.visualRect(firstIndex).width() / 2; + QPoint p(itemOffset, 1); + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, p); + QVERIFY(view.selectionModel()->isSelected(firstIndex)); + model.insertNewRow(); + QVERIFY(view.selectionModel()->isSelected(firstIndex)); // Should still be selected +} + +void tst_QTreeView::removeAfterSelect() +{ + QtTestModel model; + model.rows = model.cols = 10; + QTreeView view; + view.setModel(&model); + view.show(); + QModelIndex firstIndex = model.index(0, 0, QModelIndex()); + QVERIFY(firstIndex.isValid()); + int itemOffset = view.visualRect(firstIndex).width() / 2; + QPoint p(itemOffset, 1); + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, p); + QVERIFY(view.selectionModel()->isSelected(firstIndex)); + model.removeLastRow(); + QVERIFY(view.selectionModel()->isSelected(firstIndex)); // Should still be selected +} + +void tst_QTreeView::hiddenItems() +{ + QtTestModel model; + model.rows = model.cols = 10; + QTreeView view; + view.setModel(&model); + view.show(); + + QModelIndex firstIndex = model.index(1, 0, QModelIndex()); + QVERIFY(firstIndex.isValid()); + if (model.canFetchMore(firstIndex)) + model.fetchMore(firstIndex); + for (int i=0; i < model.rowCount(firstIndex); i++) + view.setRowHidden(i , firstIndex, true ); + + int itemOffset = view.visualRect(firstIndex).width() / 2; + int itemHeight = view.visualRect(firstIndex).height(); + QPoint p(itemOffset, itemHeight + 1); + view.setExpanded(firstIndex, false); + QTest::mouseDClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, p); + QCOMPARE(view.isExpanded(firstIndex), false); + + p.setX( 5 ); + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, p); + QCOMPARE(view.isExpanded(firstIndex), false); +} + +void tst_QTreeView::spanningItems() +{ + QtTestModel model; + model.rows = model.cols = 10; + PublicView view; + view.setModel(&model); + view.show(); + + int itemWidth = view.header()->sectionSize(0); + int itemHeight = view.visualRect(model.index(0, 0, QModelIndex())).height(); + + // every second row is spanning + for (int i = 1; i < model.rowCount(QModelIndex()); i += 2) + view.setFirstColumnSpanned(i , QModelIndex(), true); + + // non-spanning item + QPoint p(itemWidth / 2, itemHeight / 2); // column 0, row 0 + view.setCurrentIndex(QModelIndex()); + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, p); + QCOMPARE(view.currentIndex(), model.index(0, 0, QModelIndex())); + QCOMPARE(view.header()->sectionSize(0) - view.indentation(), + view.visualRect(model.index(0, 0, QModelIndex())).width()); + + // spanning item + p.setX(itemWidth + (itemWidth / 2)); // column 1 + p.setY(itemHeight + (itemHeight / 2)); // row 1 + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, p); + QCOMPARE(view.currentIndex(), model.index(1, 0, QModelIndex())); + QCOMPARE(view.header()->length() - view.indentation(), + view.visualRect(model.index(1, 0, QModelIndex())).width()); + + // size hint + // every second row is un-spanned + QStyleOptionViewItem option = view.viewOptions(); + int w = view.header()->sectionSizeHint(0); + for (int i = 0; i < model.rowCount(QModelIndex()); ++i) { + if (!view.isFirstColumnSpanned(i, QModelIndex())) { + QModelIndex index = model.index(i, 0, QModelIndex()); + w = qMax(w, view.itemDelegate(index)->sizeHint(option, index).width() + view.indentation()); + } + } + QCOMPARE(view.sizeHintForColumn(0), w); +} + +void tst_QTreeView::selectionOrderTest() +{ + QVERIFY(((QItemSelectionModel*)sender())->currentIndex().row() != -1); +} + +void tst_QTreeView::selection() +{ + QTreeView treeView; + QStandardItemModel m(10, 2); + for (int i = 0;i < 10; ++i) + m.setData(m.index(i, 0), i); + treeView.setModel(&m); + + treeView.setSelectionBehavior(QAbstractItemView::SelectRows); + treeView.setSelectionMode(QAbstractItemView::ExtendedSelection); + + connect(treeView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(selectionOrderTest())); + + treeView.show(); + + QTest::mousePress(treeView.viewport(), Qt::LeftButton, 0, treeView.visualRect(m.index(1, 0)).center()); + QTest::keyPress(treeView.viewport(), Qt::Key_Down); +} + +//From task 151686 QTreeView ExtendedSelection selects hidden rows +void tst_QTreeView::selectionWithHiddenItems() +{ + QStandardItemModel model; + for (int i = 0; i < model.rowCount(); ++i) + model.setData(model.index(i,0), QString("row %1").arg(i)); + + QStandardItem item0("row 0"); + QStandardItem item1("row 1"); + QStandardItem item2("row 2"); + QStandardItem item3("row 3"); + model.appendColumn( QList() << &item0 << &item1 << &item2 << &item3); + + QStandardItem child("child"); + item1.appendRow( &child); + + QTreeView view; + view.setModel(&model); + view.setSelectionMode(QAbstractItemView::ExtendedSelection); + view.show(); + qApp->processEvents(); + + //child should not be selected as it is hidden (its parent is not expanded) + view.selectAll(); + QCOMPARE(view.selectionModel()->selection().count(), 1); //one range + QCOMPARE(view.selectionModel()->selectedRows().count(), 4); + view.expandAll(); + QVERIFY(view.isExpanded(item1.index())); + QCOMPARE(view.selectionModel()->selection().count(), 1); + QCOMPARE(view.selectionModel()->selectedRows().count(), 4); + QVERIFY( !view.selectionModel()->isSelected(model.indexFromItem(&child))); + view.clearSelection(); + QVERIFY(view.isExpanded(item1.index())); + + //child should be selected as it is visible (its parent is expanded) + view.selectAll(); + QCOMPARE(view.selectionModel()->selection().count(), 2); + QCOMPARE(view.selectionModel()->selectedRows().count(), 5); //everything is selected + view.clearSelection(); + + //we hide the node with a child (there should then be 3 items selected in 2 ranges) + view.setRowHidden(1, QModelIndex(), true); + QVERIFY(view.isExpanded(item1.index())); + qApp->processEvents(); + view.selectAll(); + QCOMPARE(view.selectionModel()->selection().count(), 2); + QCOMPARE(view.selectionModel()->selectedRows().count(), 3); + QVERIFY( !view.selectionModel()->isSelected(model.indexFromItem(&item1))); + QVERIFY( !view.selectionModel()->isSelected(model.indexFromItem(&child))); + + view.setRowHidden(1, QModelIndex(), false); + QVERIFY(view.isExpanded(item1.index())); + view.clearSelection(); + + //we hide a node without children (there should then be 4 items selected in 3 ranges) + view.setRowHidden(2, QModelIndex(), true); + qApp->processEvents(); + QVERIFY(view.isExpanded(item1.index())); + view.selectAll(); + QVERIFY(view.isExpanded(item1.index())); + QCOMPARE(view.selectionModel()->selection().count(), 3); + QCOMPARE(view.selectionModel()->selectedRows().count(), 4); + QVERIFY( !view.selectionModel()->isSelected(model.indexFromItem(&item2))); + view.setRowHidden(2, QModelIndex(), false); + QVERIFY(view.isExpanded(item1.index())); + view.clearSelection(); +} + +void tst_QTreeView::selectAll() +{ + QStandardItemModel model(4,4); + PublicView view2; + view2.setModel(&model); + view2.setSelectionMode(QAbstractItemView::ExtendedSelection); + view2.selectAll(); // Should work with an empty model + //everything should be selected since we are in ExtendedSelection mode + QCOMPARE(view2.selectedIndexes().count(), model.rowCount() * model.columnCount()); + + for (int i = 0; i < model.rowCount(); ++i) + model.setData(model.index(i,0), QString("row %1").arg(i)); + PublicView view; + view.setModel(&model); + int selectedCount = view.selectedIndexes().count(); + view.selectAll(); + QCOMPARE(view.selectedIndexes().count(), selectedCount); +} + +void tst_QTreeView::extendedSelection_data() +{ + QTest::addColumn("mousePressPos"); + QTest::addColumn("selectedCount"); + + QTest::newRow("select") << QPoint(10, 10) << 2; + QTest::newRow("unselect") << QPoint(10, 150) << 0; +} + +void tst_QTreeView::extendedSelection() +{ + QFETCH(QPoint, mousePressPos); + QFETCH(int, selectedCount); + + QStandardItemModel model(5, 2); + QWidget topLevel; + QTreeView view(&topLevel); + view.resize(qMax(mousePressPos.x() * 2, 200), qMax(mousePressPos.y() * 2, 200)); + view.setModel(&model); + view.setSelectionMode(QAbstractItemView::ExtendedSelection); + topLevel.show(); + QTest::mousePress(view.viewport(), Qt::LeftButton, 0, mousePressPos); + QCOMPARE(view.selectionModel()->selectedIndexes().count(), selectedCount); +} + +void tst_QTreeView::rowSizeHint() +{ + //tests whether the correct visible columns are taken into account when + //calculating the height of a line + QStandardItemModel model(1,3); + model.setData( model.index(0,0), QSize(20,40), Qt::SizeHintRole); + model.setData( model.index(0,1), QSize(20,10), Qt::SizeHintRole); + model.setData( model.index(0,2), QSize(20,10), Qt::SizeHintRole); + QTreeView view; + view.setModel(&model); + + view.header()->moveSection(1, 0); //the 2nd column goes to the 1st place + + view.show(); + + //it must be 40 since the tallest item that defines the height of a line + QCOMPARE( view.visualRect(model.index(0,0)).height(), 40); + QCOMPARE( view.visualRect(model.index(0,1)).height(), 40); + QCOMPARE( view.visualRect(model.index(0,2)).height(), 40); +} + + +//From task 155449 (QTreeWidget has a large width for the first section when sorting +//is turned on before items are added) +void tst_QTreeView::setSortingEnabled() +{ + //1st the treeview is a top-level + { + QTreeView view; + QStandardItemModel model(1,1); + view.setModel(&model); + const int size = view.header()->sectionSize(0); + view.setSortingEnabled(true); + model.setColumnCount(3); + //we test that changing the column count doesn't change the 1st column size + QCOMPARE(view.header()->sectionSize(0), size); + } + + //then it is no more a top-level + { + QMainWindow win; + QTreeView view; + QStandardItemModel model(1,1); + view.setModel(&model); + win.setCentralWidget(&view); + const int size = view.header()->sectionSize(0); + view.setSortingEnabled(true); + model.setColumnCount(3); + //we test that changing the column count doesn't change the 1st column size + QCOMPARE(view.header()->sectionSize(0), size); + } +} + +void tst_QTreeView::headerHidden() +{ + QTreeView view; + QStandardItemModel model; + view.setModel(&model); + QCOMPARE(view.isHeaderHidden(), false); + QCOMPARE(view.header()->isHidden(), false); + view.setHeaderHidden(true); + QCOMPARE(view.isHeaderHidden(), true); + QCOMPARE(view.header()->isHidden(), true); +} + +// From Task 145199 (crash when column 0 having at least one expanded item is removed and then +// inserted). The test passes simply if it doesn't crash, hence there are no calls +// to QCOMPARE() or QVERIFY(). +void tst_QTreeView::removeAndInsertExpandedCol0() +{ + QTreeView view; + QStandardItemModel model; + view.setModel(&model); + + model.setColumnCount(1); + + QStandardItem *item0 = new QStandardItem(QString("item 0")); + model.invisibleRootItem()->appendRow(item0); + view.expand(item0->index()); + QStandardItem *item1 = new QStandardItem(QString("item 1")); + item0->appendRow(item1); + + model.removeColumns(0, 1); + model.insertColumns(0, 1); + + view.show(); + qApp->processEvents(); +} + +void tst_QTreeView::disabledButCheckable() +{ + QTreeView view; + QStandardItemModel model; + QStandardItem *item; + item = new QStandardItem(QLatin1String("Row 1 Item")); + model.insertRow(0, item); + + item = new QStandardItem(QLatin1String("Row 2 Item")); + item->setCheckable(true); + item->setEnabled(false); + model.insertRow(1, item); + + view.setModel(&model); + view.setCurrentIndex(model.index(1,0)); + QCOMPARE(item->checkState(), Qt::Unchecked); + view.show(); + + QTest::keyClick(&view, Qt::Key_Space); + QCOMPARE(item->checkState(), Qt::Unchecked); +} + +void tst_QTreeView::sortByColumn_data() +{ + QTest::addColumn("sortingEnabled"); + QTest::newRow("sorting enabled") << true; + QTest::newRow("sorting disabled") << false; +} + +// Checks sorting and that sortByColumn also sets the sortIndicator +void tst_QTreeView::sortByColumn() +{ + QFETCH(bool, sortingEnabled); + QTreeView view; + QStandardItemModel model(4,2); + model.setItem(0,0,new QStandardItem("b")); + model.setItem(1,0,new QStandardItem("d")); + model.setItem(2,0,new QStandardItem("c")); + model.setItem(3,0,new QStandardItem("a")); + model.setItem(0,1,new QStandardItem("e")); + model.setItem(1,1,new QStandardItem("g")); + model.setItem(2,1,new QStandardItem("h")); + model.setItem(3,1,new QStandardItem("f")); + + view.setSortingEnabled(sortingEnabled); + view.setModel(&model); + view.sortByColumn(1); + QCOMPARE(view.header()->sortIndicatorSection(), 1); + QCOMPARE(view.model()->data(view.model()->index(0,1)).toString(), QString::fromLatin1("h")); + QCOMPARE(view.model()->data(view.model()->index(1,1)).toString(), QString::fromLatin1("g")); + view.sortByColumn(0, Qt::AscendingOrder); + QCOMPARE(view.header()->sortIndicatorSection(), 0); + QCOMPARE(view.model()->data(view.model()->index(0,0)).toString(), QString::fromLatin1("a")); + QCOMPARE(view.model()->data(view.model()->index(1,0)).toString(), QString::fromLatin1("b")); +} + +/* + This is a model that every time kill() is called it will completely change + all of its nodes for new nodes. It then qFatal's if you later use a dead node. + */ +class EvilModel: public QAbstractItemModel +{ + +public: + class Node { + public: + Node(Node *p = 0, int level = 0) : parent(p), isDead(false) { + populate(level); + } + ~Node() + { + qDeleteAll(children.begin(), children.end()); + qDeleteAll(deadChildren.begin(), deadChildren.end()); + } + + void populate(int level = 0) { + if (level < 4) + for (int i = 0; i < 5; ++i) + children.append(new Node(this, level + 1)); + } + void kill() { + for (int i = children.count() -1; i >= 0; --i) { + children.at(i)->kill(); + if (parent == 0) { + deadChildren.append(children.at(i)); + children.removeAt(i); + } + } + if (parent == 0) { + if (!children.isEmpty()) + qFatal("%s: children should be empty when parent is null", Q_FUNC_INFO); + populate(); + } else { + isDead = true; + } + } + + QList children; + QList deadChildren; + Node *parent; + bool isDead; + }; + + Node *root; + + EvilModel(QObject *parent = 0): QAbstractItemModel(parent), root(new Node) + { + } + ~EvilModel() + { + delete root; + } + + void change() + { + emit layoutAboutToBeChanged(); + QModelIndexList oldList = persistentIndexList(); + QList > oldListPath; + for (int i = 0; i < oldList.count(); ++i) { + QModelIndex idx = oldList.at(i); + QStack path; + while (idx.isValid()) { + path.push(idx.row()); + idx = idx.parent(); + } + oldListPath.append(path); + } + root->kill(); + + QModelIndexList newList; + for (int i = 0; i < oldListPath.count(); ++i) { + QStack path = oldListPath[i]; + QModelIndex idx; + while(!path.isEmpty()) { + idx = index(path.pop(), 0, idx); + } + newList.append(idx); + } + + changePersistentIndexList(oldList, newList); + emit layoutChanged(); + } + + int rowCount(const QModelIndex& parent = QModelIndex()) const { + Node *parentNode = root; + if (parent.isValid()) { + parentNode = static_cast(parent.internalPointer()); + if (parentNode->isDead) + qFatal("%s: parentNode is dead!", Q_FUNC_INFO); + } + return parentNode->children.count(); + } + int columnCount(const QModelIndex& parent = QModelIndex()) const { + if (parent.column() > 0) + return 0; + return 1; + } + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const + { + Node *grandparentNode = static_cast(parent.internalPointer()); + Node *parentNode = root; + if (parent.isValid()) { + if (grandparentNode->isDead) + qFatal("%s: grandparentNode is dead!", Q_FUNC_INFO); + parentNode = grandparentNode->children[parent.row()]; + if (parentNode->isDead) + qFatal("%s: grandparentNode is dead!", Q_FUNC_INFO); + } + return createIndex(row, column, parentNode); + } + + QModelIndex parent(const QModelIndex &index) const + { + Node *parent = static_cast(index.internalPointer()); + Node *grandparent = parent->parent; + if (!grandparent) + return QModelIndex(); + return createIndex(grandparent->children.indexOf(parent), 0, grandparent); + } + + QVariant data(const QModelIndex &idx, int role) const + { + if (idx.isValid() && role == Qt::DisplayRole) { + Node *parentNode = root; + if (idx.isValid()) { + parentNode = static_cast(idx.internalPointer()); + if (parentNode->isDead) + qFatal("%s: grandparentNode is dead!", Q_FUNC_INFO); + } + return QString("[%1,%2,%3]").arg(idx.row()).arg(idx.column()) + .arg(parentNode->isDead ? "dead" : "alive"); + } + return QVariant(); + } +}; + +void tst_QTreeView::evilModel_data() +{ + QTest::addColumn("visible"); + QTest::newRow("visible") << false; +} + +void tst_QTreeView::evilModel() +{ + QFETCH(bool, visible); + // init + PublicView view; + EvilModel model; + view.setModel(&model); + view.setVisible(visible); + QPersistentModelIndex firstLevel = model.index(0, 0); + QPersistentModelIndex secondLevel = model.index(1, 0, firstLevel); + QPersistentModelIndex thirdLevel = model.index(2, 0, secondLevel); + view.setExpanded(firstLevel, true); + view.setExpanded(secondLevel, true); + view.setExpanded(thirdLevel, true); + model.change(); + + // tests + view.setRowHidden(0, firstLevel, true); + model.change(); + + return; + view.setFirstColumnSpanned(1, QModelIndex(), true); + model.change(); + + view.expand(secondLevel); + model.change(); + + view.collapse(secondLevel); + model.change(); + + view.isExpanded(secondLevel); + view.setCurrentIndex(firstLevel); + model.change(); + + view.keyboardSearch("foo"); + model.change(); + + view.visualRect(secondLevel); + model.change(); + + view.scrollTo(thirdLevel); + model.change(); + + view.repaint(); + model.change(); + + QTest::mouseDClick(view.viewport(), Qt::LeftButton); + model.change(); + + view.indexAt(QPoint(10, 10)); + model.change(); + + view.indexAbove(model.index(2, 0)); + model.change(); + + view.indexBelow(model.index(1, 0)); + model.change(); + + QRect rect(0, 0, 10, 10); + view.setSelection(rect, QItemSelectionModel::Select); + model.change(); + + view.moveCursor(PublicView::MoveDown, Qt::NoModifier); + model.change(); + + view.resizeColumnToContents(1); + model.change(); + + view.QAbstractItemView::sizeHintForColumn(1); + model.change(); + + view.rowHeight(secondLevel); + model.change(); + + view.setRootIsDecorated(true); + model.change(); + + view.setItemsExpandable(false); + model.change(); + + view.columnViewportPosition(0); + model.change(); + + view.columnWidth(0); + model.change(); + + view.setColumnWidth(0, 30); + model.change(); + + view.columnAt(15); + model.change(); + + view.isColumnHidden(1); + model.change(); + + view.setColumnHidden(2, true); + model.change(); + + view.isHeaderHidden(); + model.change(); + + view.setHeaderHidden(true); + model.change(); + + view.isRowHidden(2, secondLevel); + model.change(); + + view.setRowHidden(3, secondLevel, true); + model.change(); + + view.isFirstColumnSpanned(3, thirdLevel); + model.change(); + + view.setSortingEnabled(true); + model.change(); + + view.isSortingEnabled(); + model.change(); + + view.setAnimated(true); + model.change(); + + view.isAnimated(); + model.change(); + + view.setAllColumnsShowFocus(true); + model.change(); + + view.allColumnsShowFocus(); + model.change(); + + view.doItemsLayout(); + model.change(); + + view.reset(); + model.change(); + + view.sortByColumn(1, Qt::AscendingOrder); + model.change(); + + view.dataChanged(secondLevel, secondLevel); + model.change(); + + view.hideColumn(1); + model.change(); + + view.showColumn(1); + model.change(); + + view.resizeColumnToContents(1); + model.change(); + + view.sortByColumn(1); + model.change(); + + view.selectAll(); + model.change(); + + view.expandAll(); + model.change(); + + view.collapseAll(); + model.change(); + + view.expandToDepth(3); + model.change(); + + view.setRootIndex(secondLevel); +} + +void tst_QTreeView::indexRowSizeHint() +{ + QStandardItemModel model(10, 1); + PublicView view; + + view.setModel(&model); + + QModelIndex index = model.index(5, 0); + QPushButton *w = new QPushButton("Test"); + view.setIndexWidget(index, w); + + view.show(); + + QCOMPARE(view.indexRowSizeHint(index), w->sizeHint().height()); +} + +void tst_QTreeView::filterProxyModelCrash() +{ + QStandardItemModel model; + QList items; + for (int i = 0; i < 100; i++) + items << new QStandardItem(QString::fromLatin1("item %1").arg(i)); + model.appendColumn(items); + + QSortFilterProxyModel proxy; + proxy.setSourceModel(&model); + + QTreeView view; + view.setModel(&proxy); + view.show(); + QTest::qWait(30); + proxy.invalidate(); + view.verticalScrollBar()->setValue(15); + QTest::qWait(20); + + proxy.invalidate(); + view.repaint(); //used to crash +} + +void tst_QTreeView::styleOptionViewItem() +{ + class MyDelegate : public QStyledItemDelegate + { + public: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const + { + QVERIFY(qstyleoption_cast(&option)); + QStyleOptionViewItemV4 opt(option); + initStyleOption(&opt, index); + + QVERIFY(!opt.text.isEmpty()); + QCOMPARE(opt.index, index); + //qDebug() << index << opt.text; + + if (allCollapsed) + QCOMPARE(!(opt.features & QStyleOptionViewItemV2::Alternate), !(index.row() % 2)); + QCOMPARE(!(opt.features & QStyleOptionViewItemV2::HasCheckIndicator), !opt.text.contains("Checkable")); + + if (opt.text.contains("Beginning")) + QCOMPARE(opt.viewItemPosition, QStyleOptionViewItemV4::Beginning); + + if (opt.text.contains("Middle")) + QCOMPARE(opt.viewItemPosition, QStyleOptionViewItemV4::Middle); + + if (opt.text.contains("End")) + QCOMPARE(opt.viewItemPosition, QStyleOptionViewItemV4::End); + + if (opt.text.contains("OnlyOne")) + QCOMPARE(opt.viewItemPosition, QStyleOptionViewItemV4::OnlyOne); + + if (opt.text.contains("Checked")) + QCOMPARE(opt.checkState, Qt::Checked); + else + QCOMPARE(opt.checkState, Qt::Unchecked); + + QCOMPARE(!(opt.state & QStyle::State_Children) , !opt.text.contains("HasChildren")); + QCOMPARE(!!(opt.state & QStyle::State_Sibling) , !opt.text.contains("Last")); + + QVERIFY(!opt.text.contains("Assert")); + + QStyledItemDelegate::paint(painter, option, index); + count++; + } + mutable int count; + bool allCollapsed; + }; + + QTreeView view; + QStandardItemModel model; + view.setModel(&model); + MyDelegate delegate; + view.setItemDelegate(&delegate); + model.appendRow(QList() + << new QStandardItem("Beginning") << new QStandardItem("Middle") << new QStandardItem("Middle") << new QStandardItem("End") ); + QStandardItem *par1 = new QStandardItem("Beginning HasChildren"); + model.appendRow(QList() + << par1 << new QStandardItem("Middle HasChildren") << new QStandardItem("Middle HasChildren") << new QStandardItem("End HasChildren") ); + model.appendRow(QList() + << new QStandardItem("OnlyOne") << new QStandardItem("Assert") << new QStandardItem("Assert") << new QStandardItem("Assert") ); + QStandardItem *checkable = new QStandardItem("Checkable"); + checkable->setCheckable(true); + QStandardItem *checked = new QStandardItem("Checkable Checked"); + checkable->setCheckable(true); + checked->setCheckState(Qt::Checked); + model.appendRow(QList() + << new QStandardItem("Beginning") << checkable << checked << new QStandardItem("End") ); + model.appendRow(QList() + << new QStandardItem("Beginning Last") << new QStandardItem("Middle Last") << new QStandardItem("Middle Last") << new QStandardItem("End Last") ); + + par1->appendRow(QList() + << new QStandardItem("Beginning") << new QStandardItem("Middle") << new QStandardItem("Middle") << new QStandardItem("End") ); + QStandardItem *par2 = new QStandardItem("Beginning HasChildren"); + par1->appendRow(QList() + << par2 << new QStandardItem("Middle HasChildren") << new QStandardItem("Middle HasChildren") << new QStandardItem("End HasChildren") ); + par2->appendRow(QList() + << new QStandardItem("Beginning Last") << new QStandardItem("Middle Last") << new QStandardItem("Middle Last") << new QStandardItem("End Last") ); + + QStandardItem *par3 = new QStandardItem("Beginning Last"); + par1->appendRow(QList() + << par3 << new QStandardItem("Middle Last") << new QStandardItem("Middle Last") << new QStandardItem("End Last") ); + par3->appendRow(QList() + << new QStandardItem("Assert") << new QStandardItem("Assert") << new QStandardItem("Assert") << new QStandardItem("Asser") ); + view.setRowHidden(0, par3->index(), true); + par1->appendRow(QList() + << new QStandardItem("Assert") << new QStandardItem("Assert") << new QStandardItem("Assert") << new QStandardItem("Asser") ); + view.setRowHidden(3, par1->index(), true); + + + view.setFirstColumnSpanned(2, QModelIndex(), true); + view.setAlternatingRowColors(true); + + delegate.count = 0; + delegate.allCollapsed = true; + view.showMaximized(); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 13); + delegate.count = 0; + delegate.allCollapsed = false; + view.expandAll(); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 13); + delegate.count = 0; + view.collapse(par2->index()); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 4); + + + //test dynamic models + { + delegate.count = 0; + QStandardItemModel model2; + QStandardItem *item0 = new QStandardItem("OnlyOne Last"); + model2.appendRow(QList() << item0); + view.setModel(&model2); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 1); + QApplication::processEvents(); + + QStandardItem *item00 = new QStandardItem("OnlyOne Last"); + item0->appendRow(QList() << item00); + item0->setText("OnlyOne Last HasChildren"); + QApplication::processEvents(); + delegate.count = 0; + view.expandAll(); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 2); + QApplication::processEvents(); + + QStandardItem *item1 = new QStandardItem("OnlyOne Last"); + delegate.count = 0; + item0->setText("OnlyOne HasChildren"); + model2.appendRow(QList() << item1); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 3); + QApplication::processEvents(); + + QStandardItem *item01 = new QStandardItem("OnlyOne Last"); + delegate.count = 0; + item00->setText("OnlyOne"); + item0->appendRow(QList() << item01); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 4); + QApplication::processEvents(); + + QStandardItem *item000 = new QStandardItem("OnlyOne Last"); + delegate.count = 0; + item00->setText("OnlyOne HasChildren"); + item00->appendRow(QList() << item000); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 5); + QApplication::processEvents(); + + delegate.count = 0; + item0->removeRow(0); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 3); + QApplication::processEvents(); + + item00 = new QStandardItem("OnlyOne"); + item0->insertRow(0, QList() << item00); + QApplication::processEvents(); + delegate.count = 0; + view.expandAll(); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 4); + QApplication::processEvents(); + + delegate.count = 0; + item0->removeRow(1); + item00->setText("OnlyOne Last"); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 3); + QApplication::processEvents(); + + delegate.count = 0; + item0->removeRow(0); + item0->setText("OnlyOne"); + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 2); + QApplication::processEvents(); + + //with hidden items + item0->setText("OnlyOne HasChildren"); + item00 = new QStandardItem("OnlyOne"); + item0->appendRow(QList() << item00); + item01 = new QStandardItem("Assert"); + item0->appendRow(QList() << item01); + view.setRowHidden(1, item0->index(), true); + view.expandAll(); + QStandardItem *item02 = new QStandardItem("OnlyOne Last"); + item0->appendRow(QList() << item02); + delegate.count = 0; + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 4); + QApplication::processEvents(); + + item0->removeRow(2); + item00->setText("OnlyOne Last"); + delegate.count = 0; + QApplication::processEvents(); + QTRY_VERIFY(delegate.count >= 3); + QApplication::processEvents(); + + item00->setText("OnlyOne"); + item0->insertRow(2, new QStandardItem("OnlyOne Last")); + view.collapse(item0->index()); + item0->removeRow(0); + delegate.count = 0; + QTRY_VERIFY(delegate.count >= 2); + QApplication::processEvents(); + + item0->removeRow(1); + item0->setText("OnlyOne"); + delegate.count = 0; + QTRY_VERIFY(delegate.count >= 2); + QApplication::processEvents(); + } +} + +class task174627_TreeView : public QTreeView +{ + Q_OBJECT +protected slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &) + { emit currentChanged(current); } +signals: + void currentChanged(const QModelIndex &); +}; + +void tst_QTreeView::task174627_moveLeftToRoot() +{ + QStandardItemModel model; + QStandardItem *item1 = new QStandardItem(QString("item 1")); + model.invisibleRootItem()->appendRow(item1); + QStandardItem *item2 = new QStandardItem(QString("item 2")); + item1->appendRow(item2); + + task174627_TreeView view; + view.setModel(&model); + view.setRootIndex(item1->index()); + view.setCurrentIndex(item2->index()); + + QSignalSpy spy(&view, SIGNAL(currentChanged(QModelIndex))); + QTest::keyClick(&view, Qt::Key_Left); + QCOMPARE(spy.count(), 0); +} + +void tst_QTreeView::task171902_expandWith1stColHidden() +{ + //the task was: if the first column of a treeview is hidden, the expanded state is not correctly restored + QStandardItemModel model; + QStandardItem root("root"), root2("root"), + subitem("subitem"), subitem2("subitem"), + subsubitem("subsubitem"), subsubitem2("subsubitem"); + + model.appendRow( QList() << &root << &root2); + root.appendRow( QList() << &subitem << &subitem2); + subitem.appendRow( QList() << &subsubitem << &subsubitem2); + + QTreeView view; + view.setModel(&model); + + view.setColumnHidden(0, true); + view.expandAll(); + view.collapse(root.index()); + view.expand(root.index()); + + QCOMPARE(view.isExpanded(root.index()), true); + QCOMPARE(view.isExpanded(subitem.index()), true); + +} + +void tst_QTreeView::task203696_hidingColumnsAndRowsn() +{ + QTreeView view; + QStandardItemModel *model = new QStandardItemModel(0, 3, &view); + for (int i = 0; i < 3; ++i) + { + model->insertRow(model->rowCount()); + for (int j = 0; j < model->columnCount(); ++j) + model->setData(model->index(i, j), QString("row %1 col %2").arg(i).arg(j)); + } + view.setModel(model); + view.show(); + view.setColumnHidden(0, true); + view.setRowHidden(0, QModelIndex(), true); + QCOMPARE(view.indexAt(QPoint(0, 0)), model->index(1, 1)); +} + + +void tst_QTreeView::addRowsWhileSectionsAreHidden() +{ + QTreeView view; + for (int pass = 1; pass <= 2; ++pass) { + QStandardItemModel *model = new QStandardItemModel(6, pass, &view); + view.setModel(model); + view.show(); + + int i; + for (i = 0; i < 3; ++i) + { + model->insertRow(model->rowCount()); + for (int j = 0; j < model->columnCount(); ++j) { + model->setData(model->index(i, j), QString("row %1 col %2").arg(i).arg(j)); + } + } + int col; + for (col = 0; col < pass; ++col) + view.setColumnHidden(col, true); + for (i = 3; i < 6; ++i) + { + model->insertRow(model->rowCount()); + for (int j = 0; j < model->columnCount(); ++j) { + model->setData(model->index(i, j), QString("row %1 col %2").arg(i).arg(j)); + } + } + for (col = 0; col < pass; ++col) + view.setColumnHidden(col, false); + QTest::qWait(250); + + for (i = 0; i < 6; ++i) { + QRect rect = view.visualRect(model->index(i, 0)); + QCOMPARE(rect.isValid(), true); + } + + delete model; + } +} + +void tst_QTreeView::task216717_updateChildren() +{ + class Tree : public QTreeWidget { + protected: + void paintEvent(QPaintEvent *e) + { + QTreeWidget::paintEvent(e); + refreshed=true; + } + public: + bool refreshed; + } tree; + tree.show(); + QTest::qWaitForWindowShown(&tree); + tree.refreshed = false; + QTreeWidgetItem *parent = new QTreeWidgetItem(QStringList() << "parent"); + tree.addTopLevelItem(parent); + QTest::qWait(10); + QTRY_VERIFY(tree.refreshed); + tree.refreshed = false; + parent->addChild(new QTreeWidgetItem(QStringList() << "child")); + QTest::qWait(10); + QTRY_VERIFY(tree.refreshed); + +} + +void tst_QTreeView::task220298_selectColumns() +{ + //this is a very simple 3x3 model where the internalId of the index are different for each cell + class Model : public QAbstractTableModel + { public: + virtual int columnCount ( const QModelIndex & parent = QModelIndex() ) const + { return parent.isValid() ? 0 : 3; } + virtual int rowCount ( const QModelIndex & parent = QModelIndex() ) const + { return parent.isValid() ? 0 : 3; } + + virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const + { + if(role == Qt::DisplayRole) + return QVariant(QString("%1-%2").arg(index.column()).arg(index.row())); + return QVariant(); + } + + virtual QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const + { + return hasIndex(row, column, parent) ? createIndex(row, column, column*10+row) : QModelIndex(); + } + }; + + class TreeView : public QTreeView { public: QModelIndexList selectedIndexes () const { return QTreeView::selectedIndexes(); } } view; + Model model; + view.setModel(&model); + view.show(); + QTest::qWait(50); + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, + view.visualRect(view.model()->index(1, 1)).center()); + QTest::qWait(50); + QVERIFY(view.selectedIndexes().contains(view.model()->index(1, 2))); + QVERIFY(view.selectedIndexes().contains(view.model()->index(1, 1))); + QVERIFY(view.selectedIndexes().contains(view.model()->index(1, 0))); +} + + +void tst_QTreeView::task224091_appendColumns() +{ + QStandardItemModel *model = new QStandardItemModel(); + QWidget* topLevel= new QWidget; + QTreeView *treeView = new QTreeView(topLevel); + treeView->setModel(model); + topLevel->show(); + treeView->resize(50,50); + + QTest::qWaitForWindowShown(treeView); + qApp->processEvents(); + + QList projlist; + for (int k = 0; k < 10; ++k) + projlist.append(new QStandardItem(QString("Top Level %0").arg(k))); + model->appendColumn(projlist); + model->invisibleRootItem()->appendRow(new QStandardItem("end")); + + QTest::qWait(50); + qApp->processEvents(); + + QTRY_VERIFY(treeView->verticalScrollBar()->isVisible()); + + delete topLevel; + delete model; +} + +void tst_QTreeView::task211293_removeRootIndex() +{ + QTreeView view; + QStandardItemModel model; + QStandardItem *A1 = new QStandardItem("A1"); + QStandardItem *B11 = new QStandardItem("B1.1"); + QStandardItem *C111 = new QStandardItem("C1.1.1"); + QStandardItem *C112 = new QStandardItem("C1.1.2"); + QStandardItem *C113 = new QStandardItem("C1.1.3"); + QStandardItem *D1131 = new QStandardItem("D1.1.3.1"); + QStandardItem *E11311 = new QStandardItem("E1.1.3.1.1"); + QStandardItem *E11312 = new QStandardItem("E1.1.3.1.2"); + QStandardItem *E11313 = new QStandardItem("E1.1.3.1.3"); + QStandardItem *E11314 = new QStandardItem("E1.1.3.1.4"); + QStandardItem *D1132 = new QStandardItem("D1.1.3.2"); + QStandardItem *E11321 = new QStandardItem("E1.1.3.2.1"); + D1132->appendRow(E11321); + D1131->appendRow(E11311); + D1131->appendRow(E11312); + D1131->appendRow(E11313); + D1131->appendRow(E11314); + C113->appendRow(D1131); + C113->appendRow(D1132); + B11->appendRow(C111); + B11->appendRow(C112); + B11->appendRow(C113); + A1->appendRow(B11); + model.appendRow(A1); + view.setModel(&model); + view.setRootIndex(model.indexFromItem(B11)); + view.setExpanded(model.indexFromItem(B11), true); + view.setCurrentIndex(model.indexFromItem(E11314)); + view.setExpanded(model.indexFromItem(E11314), true); + view.show(); + qApp->processEvents(); + model.removeRows(0, 1); + qApp->processEvents(); +} + +void tst_QTreeView::task225539_deleteModel() +{ + QTreeView treeView; + treeView.show(); + QStandardItemModel *model = new QStandardItemModel(&treeView); + + QStandardItem* parentItem = model->invisibleRootItem(); + QStandardItem* item = new QStandardItem(QString("item")); + parentItem->appendRow(item); + + treeView.setModel(model); + + QCOMPARE(item->index(), treeView.indexAt(QPoint())); + + delete model; + + QVERIFY(!treeView.indexAt(QPoint()).isValid()); +} + +void tst_QTreeView::task230123_setItemsExpandable() +{ + //let's check that we prevent the expansion inside a treeview + //when the property is set. + QTreeWidget tree; + + QTreeWidgetItem root; + QTreeWidgetItem child; + root.addChild(&child); + tree.addTopLevelItem(&root); + + tree.setCurrentItem(&root); + + tree.setItemsExpandable(false); + + QTest::keyClick(&tree, Qt::Key_Plus); + QVERIFY(!root.isExpanded()); + + QTest::keyClick(&tree, Qt::Key_Right); + QVERIFY(!root.isExpanded()); + + tree.setItemsExpandable(true); + + QTest::keyClick(&tree, Qt::Key_Plus); + QVERIFY(root.isExpanded()); + + QTest::keyClick(&tree, Qt::Key_Minus); + QVERIFY(!root.isExpanded()); + + QTest::keyClick(&tree, Qt::Key_Right); + QVERIFY(root.isExpanded()); + + QTest::keyClick(&tree, Qt::Key_Left); + QVERIFY(!root.isExpanded()); + + QTest::keyClick(&tree, Qt::Key_Right); + QVERIFY(root.isExpanded()); + + const bool navToChild = tree.style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, &tree); + QTest::keyClick(&tree, Qt::Key_Right); + QCOMPARE(tree.currentItem(), navToChild ? &child : &root); + + QTest::keyClick(&tree, Qt::Key_Right); + //it should not be expanded: it has no leaf + QCOMPARE(child.isExpanded(), false); + + QTest::keyClick(&tree, Qt::Key_Left); + QCOMPARE(tree.currentItem(), &root); + + QTest::keyClick(&tree, Qt::Key_Left); + QVERIFY(!root.isExpanded()); + + +} + +void tst_QTreeView::task202039_closePersistentEditor() +{ + QStandardItemModel model(1,1); + QTreeView view; + view.setModel(&model); + + QModelIndex current = model.index(0,0); + QTest::mousePress(view.viewport(), Qt::LeftButton, 0, view.visualRect(current).center()); + QTest::mouseDClick(view.viewport(), Qt::LeftButton, 0, view.visualRect(current).center()); + QCOMPARE(view.currentIndex(), current); + QVERIFY(view.indexWidget(current)); + + view.closePersistentEditor(current); + QVERIFY(view.indexWidget(current) == 0); + + //here was the bug: closing the persistent editor would not reset the state + //and it was impossible to go into editinon again + QTest::mousePress(view.viewport(), Qt::LeftButton, 0, view.visualRect(current).center()); + QTest::mouseDClick(view.viewport(), Qt::LeftButton, 0, view.visualRect(current).center()); + QCOMPARE(view.currentIndex(), current); + QVERIFY(view.indexWidget(current)); +} + +void tst_QTreeView::task238873_avoidAutoReopening() +{ + QStandardItemModel model; + + QStandardItem item0("row 0"); + QStandardItem item1("row 1"); + QStandardItem item2("row 2"); + QStandardItem item3("row 3"); + model.appendColumn( QList() << &item0 << &item1 << &item2 << &item3); + + QStandardItem child("child"); + item1.appendRow( &child); + + QTreeView view; + view.setModel(&model); + view.show(); + view.expandAll(); + QTest::qWait(100); + + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, view.visualRect(child.index()).center()); + QTest::qWait(20); + QCOMPARE(view.currentIndex(), child.index()); + + view.setExpanded(item1.index(), false); + + QTest::qWait(500); //enough to trigger the delayedAutoScroll timer + QVERIFY(!view.isExpanded(item1.index())); +} + +void tst_QTreeView::task244304_clickOnDecoration() +{ + QTreeView view; + QStandardItemModel model; + QStandardItem item0("row 0"); + QStandardItem item00("row 0"); + item0.appendRow(&item00); + QStandardItem item1("row 1"); + model.appendColumn(QList() << &item0 << &item1); + view.setModel(&model); + + QVERIFY(!view.currentIndex().isValid()); + QRect rect = view.visualRect(item0.index()); + //we click on the decoration + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, rect.topLeft()+QPoint(-rect.left()/2,rect.height()/2)); + QVERIFY(!view.currentIndex().isValid()); + QVERIFY(view.isExpanded(item0.index())); + + rect = view.visualRect(item1.index()); + //the item has no decoration, it should get selected + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, rect.topLeft()+QPoint(-rect.left()/2,rect.height()/2)); + QCOMPARE(view.currentIndex(), item1.index()); +} + +void tst_QTreeView::task246536_scrollbarsNotWorking() +{ + struct MyObject : public QObject + { + MyObject() : count(0) + { + } + + bool eventFilter(QObject*, QEvent *e) + { + if (e->type() == QEvent::Paint) + count++; + + return false; + } + + int count; + }; + QTreeView tree; + MyObject o; + tree.viewport()->installEventFilter(&o); + QStandardItemModel model; + tree.setModel(&model); + tree.show(); + QTest::qWaitForWindowShown(&tree); + QList items; + for(int i=0; i<100; ++i){ + items << new QStandardItem(QString::fromLatin1("item %1").arg(i)); + } + model.invisibleRootItem()->appendColumn(items); + QTest::qWait(100); + o.count = 0; + tree.verticalScrollBar()->setValue(50); + QTest::qWait(100); + QTRY_VERIFY(o.count > 0); +} + +void tst_QTreeView::task250683_wrongSectionSize() +{ + QDirModel model; + QTreeView treeView; + treeView.header()->setResizeMode(QHeaderView::ResizeToContents); + treeView.setModel(&model); + treeView.setColumnHidden(2, true); + treeView.setColumnHidden(3, true); + + treeView.show(); + QTest::qWait(100); + + QCOMPARE(treeView.header()->sectionSize(0) + treeView.header()->sectionSize(1), treeView.viewport()->width()); +} + +void tst_QTreeView::task239271_addRowsWithFirstColumnHidden() +{ + class MyDelegate : public QStyledItemDelegate + { + public: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const + { + paintedIndexes << index; + QStyledItemDelegate::paint(painter, option, index); + } + + mutable QSet paintedIndexes; + }; + + QTreeView view; + QStandardItemModel model; + view.setModel(&model); + MyDelegate delegate; + view.setItemDelegate(&delegate); + QStandardItem root0("root0"), root1("root1"); + model.invisibleRootItem()->appendRow(QList() << &root0 << &root1); + QStandardItem sub0("sub0"), sub00("sub00"); + root0.appendRow(QList() << &sub0 << &sub00); + view.expand(root0.index()); + + view.hideColumn(0); + view.show(); + QTest::qWaitForWindowShown(&view); + delegate.paintedIndexes.clear(); + QStandardItem sub1("sub1"), sub11("sub11"); + root0.appendRow(QList() << &sub1 << &sub11); + + QTest::qWait(20); + //items in the 2nd column should have been painted + QTRY_VERIFY(!delegate.paintedIndexes.isEmpty()); + QVERIFY(delegate.paintedIndexes.contains(sub00.index())); + QVERIFY(delegate.paintedIndexes.contains(sub11.index())); +} + +void tst_QTreeView::task254234_proxySort() +{ + //based on tst_QTreeView::sortByColumn + // it used not to work when setting the source of a proxy after enabling sorting + QTreeView view; + QStandardItemModel model(4,2); + model.setItem(0,0,new QStandardItem("b")); + model.setItem(1,0,new QStandardItem("d")); + model.setItem(2,0,new QStandardItem("c")); + model.setItem(3,0,new QStandardItem("a")); + model.setItem(0,1,new QStandardItem("e")); + model.setItem(1,1,new QStandardItem("g")); + model.setItem(2,1,new QStandardItem("h")); + model.setItem(3,1,new QStandardItem("f")); + + view.sortByColumn(1); + view.setSortingEnabled(true); + + QSortFilterProxyModel proxy; + proxy.setDynamicSortFilter(true); + view.setModel(&proxy); + proxy.setSourceModel(&model); + QCOMPARE(view.header()->sortIndicatorSection(), 1); + QCOMPARE(view.model()->data(view.model()->index(0,1)).toString(), QString::fromLatin1("h")); + QCOMPARE(view.model()->data(view.model()->index(1,1)).toString(), QString::fromLatin1("g")); +} + +class TreeView : public QTreeView +{ + Q_OBJECT +public slots: + void handleSelectionChanged() + { + //let's select the last item + QModelIndex idx = model()->index(0, 0); + selectionModel()->select(QItemSelection(idx, idx), QItemSelectionModel::Select); + disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(handleSelectionChanged())); + } +}; + +void tst_QTreeView::task248022_changeSelection() +{ + //we check that changing the selection between the mouse press and the mouse release + //works correctly + TreeView view; + QStringList list = QStringList() << "1" << "2"; + QStringListModel model(list); + view.setSelectionMode(QAbstractItemView::ExtendedSelection); + view.setModel(&model); + view.connect(view.selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(handleSelectionChanged())); + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, view.visualRect(model.index(1)).center()); + QCOMPARE(view.selectionModel()->selectedIndexes().count(), list.count()); +} + +void tst_QTreeView::task245654_changeModelAndExpandAll() +{ + QTreeView view; + QStandardItemModel *model = new QStandardItemModel; + QStandardItem *top = new QStandardItem("top"); + QStandardItem *sub = new QStandardItem("sub"); + top->appendRow(sub); + model->appendRow(top); + view.setModel(model); + view.expandAll(); + QApplication::processEvents(); + QVERIFY(view.isExpanded(top->index())); + + //now let's try to delete the model + //then repopulate and expand again + delete model; + model = new QStandardItemModel; + top = new QStandardItem("top"); + sub = new QStandardItem("sub"); + top->appendRow(sub); + model->appendRow(top); + view.setModel(model); + view.expandAll(); + QApplication::processEvents(); + QVERIFY(view.isExpanded(top->index())); + +} + +void tst_QTreeView::doubleClickedWithSpans() +{ + QTreeView view; + QStandardItemModel model(1, 2); + view.setModel(&model); + view.setFirstColumnSpanned(0, QModelIndex(), true); + view.show(); + QApplication::setActiveWindow(&view); + QTest::qWaitForWindowShown(&view); + QTRY_VERIFY(view.isActiveWindow()); + + QPoint p(10, 10); + QCOMPARE(view.indexAt(p), model.index(0, 0)); + QSignalSpy spy(&view, SIGNAL(doubleClicked(QModelIndex))); + QTest::mousePress(view.viewport(), Qt::LeftButton, 0, p); + QTest::mouseDClick(view.viewport(), Qt::LeftButton, 0, p); + QTest::mouseRelease(view.viewport(), Qt::LeftButton, 0, p); + QCOMPARE(spy.count(), 1); + + //let's click on the 2nd column + p.setX(p.x() + view.header()->sectionSize(0)); + QCOMPARE(view.indexAt(p), model.index(0, 0)); + + //end the previous edition + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, p); + QTest::qWait(150); + QTest::mousePress(view.viewport(), Qt::LeftButton, 0, p); + QTest::mouseDClick(view.viewport(), Qt::LeftButton, 0, p); + QTest::mouseRelease(view.viewport(), Qt::LeftButton, 0, p); + QTRY_COMPARE(spy.count(), 2); +} + +void tst_QTreeView::taskQTBUG_6450_selectAllWith1stColumnHidden() +{ + QTreeWidget tree; + tree.setSelectionMode(QAbstractItemView::MultiSelection); + tree.setColumnCount(2); + QList items; + const int nrRows = 10; + for (int i = 0; i < nrRows; ++i) { + items.append(new QTreeWidgetItem((QTreeWidget*)0, QStringList(QString("item: %1").arg(i)))); + items.last()->setText(1, QString("is an item")); + } + tree.insertTopLevelItems(0, items); + + tree.hideColumn(0); + tree.selectAll(); + + QVERIFY(tree.selectionModel()->hasSelection()); + for (int i = 0; i < nrRows; ++i) + QVERIFY(tree.selectionModel()->isRowSelected(i, QModelIndex())); +} + +class TreeViewQTBUG_9216 : public QTreeView +{ + Q_OBJECT +public: + void paintEvent(QPaintEvent *event) + { + if (doCompare) + QCOMPARE(event->rect(), viewport()->rect()); + QTreeView::paintEvent(event); + painted++; + } + int painted; + bool doCompare; +}; + +void tst_QTreeView::taskQTBUG_9216_setSizeAndUniformRowHeightsWrongRepaint() +{ + QStandardItemModel model(10, 10, this); + for (int row = 0; row < 10; row++) + for (int col = 0; col < 10; col++) + model.setItem(row, col, new QStandardItem(QString("row %0, col %1").arg(row).arg(col))); + TreeViewQTBUG_9216 view; + view.setUniformRowHeights(true); + view.setModel(&model); + view.painted = 0; + view.doCompare = false; + view.show(); + QTest::qWaitForWindowShown(&view); + QTRY_VERIFY(view.painted > 0); + + QTest::qWait(100); // This one is needed to make the test fail before the patch. + view.painted = 0; + view.doCompare = true; + model.setData(model.index(0, 0), QVariant(QSize(50, 50)), Qt::SizeHintRole); + QTest::qWait(100); + QTRY_VERIFY(view.painted > 0); +} + +void tst_QTreeView::keyboardNavigationWithDisabled() +{ + QWidget topLevel; + QTreeView view(&topLevel); + QStandardItemModel model(90, 0); + for (int i = 0; i < 90; i ++) { + model.setItem(i, new QStandardItem(QString::number(i))); + model.item(i)->setEnabled(i%6 == 0); + } + view.setModel(&model); + + view.resize(200, view.visualRect(model.index(0,0)).height()*10); + topLevel.show(); + QApplication::setActiveWindow(&topLevel); + QTest::qWaitForWindowShown(&topLevel); + QTRY_VERIFY(topLevel.isActiveWindow()); + + view.setCurrentIndex(model.index(1, 0)); + QTest::keyClick(view.viewport(), Qt::Key_Up); + QCOMPARE(view.currentIndex(), model.index(0, 0)); + QTest::keyClick(view.viewport(), Qt::Key_Down); + QCOMPARE(view.currentIndex(), model.index(6, 0)); + QTest::keyClick(view.viewport(), Qt::Key_PageDown); + QCOMPARE(view.currentIndex(), model.index(18, 0)); + QTest::keyClick(view.viewport(), Qt::Key_Down); + QCOMPARE(view.currentIndex(), model.index(24, 0)); + QTest::keyClick(view.viewport(), Qt::Key_PageUp); + QCOMPARE(view.currentIndex(), model.index(12, 0)); + QTest::keyClick(view.viewport(), Qt::Key_Up); + QCOMPARE(view.currentIndex(), model.index(6, 0)); +} + +class Model_11466 : public QAbstractItemModel +{ + Q_OBJECT +public: + Model_11466(QObject * /* parent */) : + m_block(false) + { + // set up the model to have two top level items and a few others + m_selectionModel = new QItemSelectionModel(this, this); // owned by this + + connect(m_selectionModel, SIGNAL(currentChanged(const QModelIndex &,const QModelIndex &)), + this, SLOT(slotCurrentChanged(const QModelIndex &,const QModelIndex &))); + }; + + int rowCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return (parent.internalId() == 0) ? 4 : 0; + return 2; // two top level items + } + + int columnCount(const QModelIndex & /* parent */) const + { + return 2; + } + + QVariant data(const QModelIndex &index, int role) const + { + if (role == Qt::DisplayRole && index.isValid()) { + qint64 parentRowPlusOne = index.internalId(); + QString str; + QTextStream stream(&str); + if (parentRowPlusOne > 0) + stream << parentRowPlusOne << " -> " << index.row() << " : " << index.column(); + else + stream << index.row() << " : " << index.column(); + return QVariant(str); + } + return QVariant(); + } + + QModelIndex parent(const QModelIndex &index) const + { + if (index.isValid()) { + qint64 parentRowPlusOne = index.internalId(); + if (parentRowPlusOne > 0) { + int row = static_cast(parentRowPlusOne - 1); + return createIndex(row, 0, (quint32)0); + } + } + return QModelIndex(); + } + + void bindView(QTreeView *view) + { + // sets the view to this model with a shared selection model + QItemSelectionModel *oldModel = view->selectionModel(); + if (oldModel != m_selectionModel) + delete oldModel; + view->setModel(this); // this creates a new selection model for the view, but we dont want it either ... + oldModel = view->selectionModel(); + view->setSelectionModel(m_selectionModel); + delete oldModel; + } + + QModelIndex index(int row, int column, const QModelIndex &parent) const + { + return createIndex(row, column, parent.isValid() ? (quint32)(parent.row() + 1) : (quint32)0); + } + +public slots: + void slotCurrentChanged(const QModelIndex ¤t,const QModelIndex &) + { + if (m_block) + return; + + if (current.isValid()) { + int selectedRow = current.row(); + quint32 parentRowPlusOne = static_cast(current.internalId()); + + for (int i = 0; i < 2; ++i) { + // announce the removal of all non top level items + beginRemoveRows(createIndex(i, 0, 0), 0, 3); + // nothing to actually do for the removal + endRemoveRows(); + + // put them back in again + beginInsertRows(createIndex(i, 0, 0), 0, 3); + // nothing to actually do for the insertion + endInsertRows(); + } + // reselect the current item ... + QModelIndex selectedIndex = createIndex(selectedRow, 0, parentRowPlusOne); + + m_block = true; // recursion block + m_selectionModel->select(selectedIndex, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current|QItemSelectionModel::Rows); + m_selectionModel->setCurrentIndex(selectedIndex, QItemSelectionModel::NoUpdate); + m_block = false; + } else { + m_selectionModel->clear(); + } + } + +private: + bool m_block; + QItemSelectionModel *m_selectionModel; +}; + +void tst_QTreeView::taskQTBUG_11466_keyboardNavigationRegression() +{ + QTreeView treeView; + treeView.setSelectionBehavior(QAbstractItemView::SelectRows); + treeView.setSelectionMode(QAbstractItemView::SingleSelection); + Model_11466 model(&treeView); + model.bindView(&treeView); + treeView.expandAll(); + treeView.show(); + QTest::qWaitForWindowShown(&treeView); + + QTest::keyPress(treeView.viewport(), Qt::Key_Down); + QTest::qWait(10); + QTRY_COMPARE(treeView.currentIndex(), treeView.selectionModel()->selection().indexes().first()); +} + +void tst_QTreeView::taskQTBUG_13567_removeLastItemRegression() +{ + QtTestModel model(200, 1); + + QTreeView view; + view.setSelectionMode(QAbstractItemView::ExtendedSelection); + view.setModel(&model); + view.show(); + QTest::qWaitForWindowShown(&view); + + view.scrollToBottom(); + QTest::qWait(10); + CHECK_VISIBLE(199, 0); + + view.setCurrentIndex(model.index(199, 0)); + model.removeLastRow(); + QTest::qWait(10); + QCOMPARE(view.currentIndex(), model.index(198, 0)); + CHECK_VISIBLE(198, 0); +} + +QTEST_MAIN(tst_QTreeView) +#include "tst_qtreeview.moc" -- cgit v1.2.3