// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2012 Thorbjørn Lund Martsum - tmartsum[at]gmail.com // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include #include #include #include #include #include #include using BoolList = QList; using IntList = QList; using ResizeVec = QList; class TestStyle : public QProxyStyle { Q_OBJECT public: void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override { if (element == CE_HeaderSection) { if (const QStyleOptionHeader *header = qstyleoption_cast(option)) lastPosition = header->position; } QProxyStyle::drawControl(element, option, painter, widget); } mutable QStyleOptionHeader::SectionPosition lastPosition = QStyleOptionHeader::Beginning; }; class protected_QHeaderView : public QHeaderView { Q_OBJECT public: protected_QHeaderView(Qt::Orientation orientation) : QHeaderView(orientation) { resizeSections(); } void testEvent(); void testhorizontalOffset(); void testverticalOffset(); void testVisualRegionForSelection(); friend class tst_QHeaderView; }; class XResetModel : public QStandardItemModel { Q_OBJECT public: bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override { blockSignals(true); bool r = QStandardItemModel::removeRows(row, count, parent); blockSignals(false); beginResetModel(); endResetModel(); return r; } bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override { blockSignals(true); bool r = QStandardItemModel::insertRows(row, count, parent); blockSignals(false); beginResetModel(); endResetModel(); return r; } }; class tst_QHeaderView : public QObject { Q_OBJECT public: tst_QHeaderView(); private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void getSetCheck(); void visualIndex(); void visualIndexAt_data(); void visualIndexAt(); void noModel(); void emptyModel(); void removeRows(); void removeCols(); void clickable(); void movable(); void hidden(); void stretch(); void sectionSize_data(); void sectionSize(); void length(); void offset(); void sectionSizeHint(); void logicalIndex(); void logicalIndexAt(); void swapSections(); void moveSection_data(); void moveSection(); void resizeMode(); void resizeSection_data(); void resizeSection(); void resizeAndMoveSection_data(); void resizeAndMoveSection(); void resizeHiddenSection_data(); void resizeHiddenSection(); void resizeAndInsertSection_data(); void resizeAndInsertSection(); void resizeWithResizeModes_data(); void resizeWithResizeModes(); void moveAndInsertSection_data(); void moveAndInsertSection(); void highlightSections(); void showSortIndicator(); void clearSectionSorting(); void sortIndicatorTracking(); void removeAndInsertRow(); void unhideSection(); void testEvent(); void headerDataChanged(); void currentChanged(); void horizontalOffset(); void verticalOffset(); void stretchSectionCount(); void hiddenSectionCount(); void focusPolicy(); void moveSectionAndReset(); void moveSectionAndRemove(); void saveRestore(); void QTBUG99487_saveRestoreQt5Compat(); void restoreToMoreColumns(); void restoreToMoreColumnsNoMovedColumns(); void restoreBeforeSetModel(); void defaultSectionSizeTest(); void defaultSectionSizeTestStyles(); void defaultAlignment_data(); void defaultAlignment(); void globalResizeMode_data(); void globalResizeMode(); void sectionPressedSignal_data(); void sectionPressedSignal(); void sectionClickedSignal_data() { sectionPressedSignal_data(); } void sectionClickedSignal(); void defaultSectionSize_data(); void defaultSectionSize(); void oneSectionSize(); void hideAndInsert_data(); void hideAndInsert(); void removeSection(); void preserveHiddenSectionWidth(); void invisibleStretchLastSection(); void noSectionsWithNegativeSize(); void emptySectionSpan(); void task236450_hidden_data(); void task236450_hidden(); void task248050_hideRow(); void QTBUG6058_reset(); void QTBUG7833_sectionClicked(); void checkLayoutChangeEmptyModel(); void QTBUG8650_crashOnInsertSections(); void QTBUG12268_hiddenMovedSectionSorting(); void QTBUG14242_hideSectionAutoSize(); void QTBUG50171_visualRegionForSwappedItems(); void QTBUG53221_assertShiftHiddenRow(); void QTBUG75615_sizeHintWithStylesheet(); void ensureNoIndexAtLength(); void offsetConsistent(); void sectionsDontSortWhenNotClickingInThem(); void initialSortOrderRole(); void logicalIndexAtTest_data() { setupTestData(); } void visualIndexAtTest_data() { setupTestData(); } void hideShowTest_data() { setupTestData(); } void swapSectionsTest_data() { setupTestData(); } void moveSectionTest_data() { setupTestData(); } void defaultSizeTest_data() { setupTestData(); } void removeTest_data() { setupTestData(true); } void insertTest_data() { setupTestData(true); } void mixedTests_data() { setupTestData(true); } void resizeToContentTest_data() { setupTestData(); } void logicalIndexAtTest(); void visualIndexAtTest(); void hideShowTest(); void swapSectionsTest(); void moveSectionTest(); void defaultSizeTest(); void removeTest(); void insertTest(); void mixedTests(); void resizeToContentTest(); void testStreamWithHide(); void testStylePosition(); void stretchAndRestoreLastSection(); void testMinMaxSectionSize_data(); void testMinMaxSectionSize(); void sizeHintCrash(); void testResetCachedSizeHint(); void statusTips(); void testRemovingColumnsViaLayoutChanged(); void testModelMovingColumns(); protected: void setupTestData(bool use_reset_model = false); void additionalInit(); void calculateAndCheck(int cppline, const int precalced_comparedata[]); void testMinMaxSectionSize(bool stretchLastSection); QWidget *topLevel = nullptr; QHeaderView *view = nullptr; QStandardItemModel *model = nullptr; QTableView *m_tableview = nullptr; bool m_using_reset_model = false; bool m_special_prepare = false; QElapsedTimer timer; }; class QtTestModel: public QAbstractTableModel { Q_OBJECT public: QtTestModel(int rc, int cc, QObject *parent = nullptr) : QAbstractTableModel(parent), rows(rc), cols(cc) {} int rowCount(const QModelIndex &) const override { return rows; } int columnCount(const QModelIndex &) const override { return cols; } bool isEditable(const QModelIndex &) const { return true; } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { if (section < 0 || (role != Qt::DisplayRole && role != Qt::StatusTipRole)) return QVariant(); const int row = (orientation == Qt::Vertical ? section : 0); const int col = (orientation == Qt::Horizontal ? section : 0); if (orientation == Qt::Vertical && row >= rows) return QVariant(); if (orientation == Qt::Horizontal && col >= cols) return QVariant(); if (m_bMultiLine) return QString("%1\n%1").arg(section); return QLatin1Char('[') + QString::number(row) + QLatin1Char(',') + QString::number(col) + QLatin1String(",0] -- Header"); } QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override { if (role != Qt::DisplayRole) return QVariant(); 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()); } return QLatin1Char('[') + QString::number(idx.row()) + QLatin1Char(',') + QString::number(idx.column()) + QLatin1String(",0]"); } void insertOneColumn(int col) { beginInsertColumns(QModelIndex(), col, col); --cols; endInsertColumns(); } void removeFirstRow() { beginRemoveRows(QModelIndex(), 0, 0); --rows; endRemoveRows(); } void removeLastRow() { beginRemoveRows(QModelIndex(), rows - 1, rows - 1); --rows; endRemoveRows(); } void removeAllRows() { beginRemoveRows(QModelIndex(), 0, rows - 1); rows = 0; endRemoveRows(); } void removeOneColumn(int col) { beginRemoveColumns(QModelIndex(), col, col); --cols; endRemoveColumns(); } void removeLastColumn() { beginRemoveColumns(QModelIndex(), cols - 1, cols - 1); --cols; endRemoveColumns(); } void removeAllColumns() { beginRemoveColumns(QModelIndex(), 0, cols - 1); cols = 0; endRemoveColumns(); } void moveColumn(int from, int to) { beginMoveColumns(QModelIndex(), from, from, QModelIndex(), to); endMoveColumns(); } void cleanup() { emit layoutAboutToBeChanged(); cols = 3; rows = 3; emit layoutChanged(); } void emitLayoutChanged() { emit layoutAboutToBeChanged(); emit layoutChanged(); } void emitLayoutChangedWithRemoveFirstRow() { emit layoutAboutToBeChanged(); QModelIndexList milNew; const auto milOld = persistentIndexList(); milNew.reserve(milOld.size()); for (int i = 0; i < milOld.size(); ++i) milNew += QModelIndex(); changePersistentIndexList(milOld, milNew); emit layoutChanged(); } void setMultiLineHeader(bool bEnable) { beginResetModel(); m_bMultiLine = bEnable; endResetModel(); } int rows = 0; int cols = 0; mutable bool wrongIndex = false; bool m_bMultiLine = false; }; // Testing get/set functions void tst_QHeaderView::getSetCheck() { protected_QHeaderView obj1(Qt::Horizontal); // bool QHeaderView::highlightSections() // void QHeaderView::setHighlightSections(bool) obj1.setHighlightSections(false); QCOMPARE(false, obj1.highlightSections()); obj1.setHighlightSections(true); QCOMPARE(true, obj1.highlightSections()); // bool QHeaderView::stretchLastSection() // void QHeaderView::setStretchLastSection(bool) obj1.setStretchLastSection(false); QCOMPARE(false, obj1.stretchLastSection()); obj1.setStretchLastSection(true); QCOMPARE(true, obj1.stretchLastSection()); // int QHeaderView::defaultSectionSize() // void QHeaderView::setDefaultSectionSize(int) obj1.setMinimumSectionSize(0); obj1.setDefaultSectionSize(-1); QVERIFY(obj1.defaultSectionSize() >= 0); obj1.setDefaultSectionSize(0); QCOMPARE(0, obj1.defaultSectionSize()); obj1.setDefaultSectionSize(99999); QCOMPARE(99999, obj1.defaultSectionSize()); // int QHeaderView::minimumSectionSize() // void QHeaderView::setMinimumSectionSize(int) obj1.setMinimumSectionSize(-1); QVERIFY(obj1.minimumSectionSize() >= 0); obj1.setMinimumSectionSize(0); QCOMPARE(0, obj1.minimumSectionSize()); obj1.setMinimumSectionSize(99999); QCOMPARE(99999, obj1.minimumSectionSize()); obj1.setMinimumSectionSize(-1); QVERIFY(obj1.minimumSectionSize() < 100); // int QHeaderView::offset() // void QHeaderView::setOffset(int) obj1.setOffset(0); QCOMPARE(0, obj1.offset()); obj1.setOffset(std::numeric_limits::min()); QCOMPARE(std::numeric_limits::min(), obj1.offset()); obj1.setOffset(std::numeric_limits::max()); QCOMPARE(std::numeric_limits::max(), obj1.offset()); } tst_QHeaderView::tst_QHeaderView() { qRegisterMetaType("Qt::SortOrder"); } void tst_QHeaderView::initTestCase() { m_tableview = new QTableView; qDebug().noquote().nospace() << "default min section size is " << QString::number(m_tableview->verticalHeader()->minimumSectionSize()) << QLatin1Char('/') << m_tableview->horizontalHeader()->minimumSectionSize() << " (v/h)"; } void tst_QHeaderView::cleanupTestCase() { delete m_tableview; } void tst_QHeaderView::init() { topLevel = new QWidget; view = new QHeaderView(Qt::Vertical, topLevel); // Some initial value tests before a model is added QCOMPARE(view->length(), 0); QCOMPARE(view->sizeHint(), QSize(0,0)); QCOMPARE(view->sectionSizeHint(0), -1); view->setMinimumSectionSize(0); // system default min size can be to large /* model = new QStandardItemModel(1, 1); view->setModel(model); //qDebug() << view->count(); view->sizeHint(); */ int rows = 4; int columns = 4; model = new QStandardItemModel(rows, columns); /* for (int row = 0; row < rows; ++row) { for (int column = 0; column < columns; ++column) { QModelIndex index = model->index(row, column, QModelIndex()); model->setData(index, QVariant((row+1) * (column+1))); } } */ QSignalSpy spy(view, &QHeaderView::sectionCountChanged); view->setModel(model); QCOMPARE(spy.size(), 1); view->resize(200,200); } void tst_QHeaderView::cleanup() { m_tableview->setUpdatesEnabled(true); if (view && view->parent() != m_tableview) delete view; view = nullptr; delete model; model = nullptr; delete topLevel; topLevel = nullptr; } void tst_QHeaderView::noModel() { QHeaderView emptyView(Qt::Vertical); QCOMPARE(emptyView.count(), 0); } void tst_QHeaderView::emptyModel() { QtTestModel testmodel(0, 0); view->setModel(&testmodel); QVERIFY(!testmodel.wrongIndex); QCOMPARE(view->count(), testmodel.rows); view->setModel(model); } void tst_QHeaderView::removeRows() { QtTestModel model(10, 10); QHeaderView vertical(Qt::Vertical); QHeaderView horizontal(Qt::Horizontal); vertical.setModel(&model); horizontal.setModel(&model); vertical.show(); horizontal.show(); QCOMPARE(vertical.count(), model.rows); QCOMPARE(horizontal.count(), model.cols); model.removeLastRow(); QVERIFY(!model.wrongIndex); QCOMPARE(vertical.count(), model.rows); QCOMPARE(horizontal.count(), model.cols); model.removeAllRows(); QVERIFY(!model.wrongIndex); QCOMPARE(vertical.count(), model.rows); QCOMPARE(horizontal.count(), model.cols); } void tst_QHeaderView::removeCols() { QtTestModel model(10, 10); QHeaderView vertical(Qt::Vertical); QHeaderView horizontal(Qt::Horizontal); vertical.setModel(&model); horizontal.setModel(&model); vertical.show(); horizontal.show(); QCOMPARE(vertical.count(), model.rows); QCOMPARE(horizontal.count(), model.cols); model.removeLastColumn(); QVERIFY(!model.wrongIndex); QCOMPARE(vertical.count(), model.rows); QCOMPARE(horizontal.count(), model.cols); model.removeAllColumns(); QVERIFY(!model.wrongIndex); QCOMPARE(vertical.count(), model.rows); QCOMPARE(horizontal.count(), model.cols); } void tst_QHeaderView::movable() { QCOMPARE(view->sectionsMovable(), false); view->setSectionsMovable(false); QCOMPARE(view->sectionsMovable(), false); view->setSectionsMovable(true); QCOMPARE(view->sectionsMovable(), true); QCOMPARE(view->isFirstSectionMovable(), true); view->setFirstSectionMovable(false); QCOMPARE(view->isFirstSectionMovable(), false); view->setFirstSectionMovable(true); QCOMPARE(view->isFirstSectionMovable(), true); } void tst_QHeaderView::clickable() { QCOMPARE(view->sectionsClickable(), false); view->setSectionsClickable(false); QCOMPARE(view->sectionsClickable(), false); view->setSectionsClickable(true); QCOMPARE(view->sectionsClickable(), true); } void tst_QHeaderView::hidden() { //hideSection() & showSection call setSectionHidden // Test bad arguments QCOMPARE(view->isSectionHidden(-1), false); QCOMPARE(view->isSectionHidden(view->count()), false); QCOMPARE(view->isSectionHidden(999999), false); view->setSectionHidden(-1, true); view->setSectionHidden(view->count(), true); view->setSectionHidden(999999, true); view->setSectionHidden(-1, false); view->setSectionHidden(view->count(), false); view->setSectionHidden(999999, false); // Hidden sections shouldn't have visual properties (except position) int pos = view->defaultSectionSize(); view->setSectionHidden(1, true); QCOMPARE(view->sectionSize(1), 0); QCOMPARE(view->sectionPosition(1), pos); view->resizeSection(1, 100); QCOMPARE(view->sectionViewportPosition(1), pos); QCOMPARE(view->sectionSize(1), 0); view->setSectionHidden(1, false); QCOMPARE(view->isSectionHidden(0), false); QCOMPARE(view->sectionSize(0), view->defaultSectionSize()); // d->hiddenSectionSize could go out of sync when a new model // was set which has fewer sections than before and some of them // were hidden QStandardItemModel model2(model->rowCount() - 1, model->columnCount()); for (int i = 0; i < model->rowCount(); ++i) view->setSectionHidden(i, true); view->setModel(&model2); QVERIFY(view->sectionsHidden()); for (int i = 0; i < model2.rowCount(); ++i) QVERIFY(view->isSectionHidden(i)); view->setModel(model); for (int i = 0; i < model2.rowCount(); ++i) QVERIFY(view->isSectionHidden(i)); QCOMPARE(view->isSectionHidden(model->rowCount() - 1), false); for (int i = 0; i < model->rowCount(); ++i) view->setSectionHidden(i, false); } void tst_QHeaderView::stretch() { // Show before resize and setStretchLastSection QSize viewSize(500, 500); view->resize(viewSize); view->setStretchLastSection(true); QCOMPARE(view->stretchLastSection(), true); topLevel->show(); QVERIFY(QTest::qWaitForWindowExposed(topLevel)); QCOMPARE(view->width(), viewSize.width()); QCOMPARE(view->visualIndexAt(view->viewport()->height() - 5), 3); view->setSectionHidden(3, true); QCOMPARE(view->visualIndexAt(view->viewport()->height() - 5), 2); view->setStretchLastSection(false); QCOMPARE(view->stretchLastSection(), false); } void tst_QHeaderView::oneSectionSize() { //this ensures that if there is only one section, it gets a correct width (more than 0) QHeaderView view (Qt::Vertical); QtTestModel model(1, 1); view.setSectionResizeMode(QHeaderView::Interactive); view.setModel(&model); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QVERIFY(view.sectionSize(0) > 0); } void tst_QHeaderView::sectionSize_data() { QTest::addColumn("boundsCheck"); QTest::addColumn("defaultSizes"); QTest::addColumn("initialDefaultSize"); QTest::addColumn("lastVisibleSectionSize"); QTest::addColumn("persistentSectionSize"); QTest::newRow("data set one") << (IntList{ -1, 0, 4, 9999 }) << (IntList{ 10, 30, 30 }) << 30 << 300 << 20; } void tst_QHeaderView::sectionSize() { #if defined Q_OS_QNX QSKIP("The section size is dpi dependent on QNX"); #endif QFETCH(const IntList, boundsCheck); QFETCH(const IntList, defaultSizes); QFETCH(int, initialDefaultSize); QFETCH(int, lastVisibleSectionSize); QFETCH(int, persistentSectionSize); // bounds check for (int val : boundsCheck) view->sectionSize(val); // default size QCOMPARE(view->defaultSectionSize(), initialDefaultSize); for (int def : defaultSizes) { view->setDefaultSectionSize(def); QCOMPARE(view->defaultSectionSize(), def); } view->setDefaultSectionSize(initialDefaultSize); for (int s = 0; s < view->count(); ++s) QCOMPARE(view->sectionSize(s), initialDefaultSize); view->doItemsLayout(); // stretch last section view->setStretchLastSection(true); int lastSection = view->count() - 1; //test that when hiding the last column, //resizing the new last visible columns still works view->hideSection(lastSection); view->resizeSection(lastSection - 1, lastVisibleSectionSize); QCOMPARE(view->sectionSize(lastSection - 1), lastVisibleSectionSize); view->showSection(lastSection); // turn off stretching view->setStretchLastSection(false); QCOMPARE(view->sectionSize(lastSection), initialDefaultSize); // test persistence int sectionCount = view->count(); for (int i = 0; i < sectionCount; ++i) view->resizeSection(i, persistentSectionSize); QtTestModel model(sectionCount * 2, sectionCount * 2); view->setModel(&model); for (int j = 0; j < sectionCount; ++j) QCOMPARE(view->sectionSize(j), persistentSectionSize); for (int k = sectionCount; k < view->count(); ++k) QCOMPARE(view->sectionSize(k), initialDefaultSize); } void tst_QHeaderView::visualIndex() { // Test bad arguments QCOMPARE(view->visualIndex(999999), -1); QCOMPARE(view->visualIndex(-1), -1); QCOMPARE(view->visualIndex(1), 1); view->setSectionHidden(1, true); QCOMPARE(view->visualIndex(1), 1); QCOMPARE(view->visualIndex(2), 2); view->setSectionHidden(1, false); QCOMPARE(view->visualIndex(1), 1); QCOMPARE(view->visualIndex(2), 2); } void tst_QHeaderView::visualIndexAt_data() { QTest::addColumn("hidden"); QTest::addColumn("from"); QTest::addColumn("to"); QTest::addColumn("coordinate"); QTest::addColumn("visual"); const IntList coordinateList{ -1, 0, 31, 91, 99999 }; QTest::newRow("no hidden, no moved sections") << IntList() << IntList() << IntList() << coordinateList << (IntList{ -1, 0, 1, 3, -1 }); QTest::newRow("no hidden, moved sections") << IntList() << (IntList{ 0 }) << (IntList{ 1 }) << coordinateList << (IntList{ -1, 0, 1, 3, -1 }); QTest::newRow("hidden, no moved sections") << (IntList{ 0 }) << IntList() << IntList() << coordinateList << (IntList{ -1, 1, 2, 3, -1 }); } void tst_QHeaderView::visualIndexAt() { #if defined Q_OS_QNX QSKIP("The section size is dpi dependent on QNX"); #endif QFETCH(const IntList, hidden); QFETCH(const IntList, from); QFETCH(const IntList, to); QFETCH(const IntList, coordinate); QFETCH(const IntList, visual); view->setStretchLastSection(true); topLevel->show(); QVERIFY(QTest::qWaitForWindowExposed(topLevel)); for (int i : hidden) view->setSectionHidden(i, true); for (int j = 0; j < from.size(); ++j) view->moveSection(from.at(j), to.at(j)); for (int k = 0; k < coordinate.size(); ++k) QTRY_COMPARE(view->visualIndexAt(coordinate.at(k)), visual.at(k)); } void tst_QHeaderView::length() { view->setStretchLastSection(true); topLevel->show(); QVERIFY(QTest::qWaitForWindowExposed(topLevel)); //minimumSectionSize should be the size of the last section of the widget is not tall enough int length = view->minimumSectionSize(); for (int i = 0; i < view->count() - 1; i++) length += view->sectionSize(i); length = qMax(length, view->viewport()->height()); QCOMPARE(length, view->length()); view->setStretchLastSection(false); topLevel->show(); QVERIFY(QTest::qWaitForWindowExposed(topLevel)); QVERIFY(length != view->length()); // layoutChanged might mean rows have been removed QtTestModel model(10, 10); view->setModel(&model); int oldLength = view->length(); model.cleanup(); QCOMPARE(model.rows, view->count()); QVERIFY(oldLength != view->length()); } void tst_QHeaderView::offset() { QCOMPARE(view->offset(), 0); view->setOffset(10); QCOMPARE(view->offset(), 10); view->setOffset(0); QCOMPARE(view->offset(), 0); // Test odd arguments view->setOffset(-1); } void tst_QHeaderView::sectionSizeHint() { QCOMPARE(view->sectionSizeHint(-1), -1); QCOMPARE(view->sectionSizeHint(99999), -1); QVERIFY(view->sectionSizeHint(0) >= 0); } void tst_QHeaderView::logicalIndex() { // Test bad arguments QCOMPARE(view->logicalIndex(-1), -1); QCOMPARE(view->logicalIndex(99999), -1); } void tst_QHeaderView::logicalIndexAt() { // Test bad arguments view->logicalIndexAt(-1); view->logicalIndexAt(99999); QCOMPARE(view->logicalIndexAt(0), 0); QCOMPARE(view->logicalIndexAt(1), 0); topLevel->show(); QVERIFY(QTest::qWaitForWindowExposed(topLevel)); view->setStretchLastSection(true); // First item QCOMPARE(view->logicalIndexAt(0), 0); QCOMPARE(view->logicalIndexAt(view->sectionSize(0)-1), 0); QCOMPARE(view->logicalIndexAt(view->sectionSize(0)+1), 1); // Last item int last = view->length() - 1;//view->viewport()->height() - 10; QCOMPARE(view->logicalIndexAt(last), 3); // Not in widget int outofbounds = view->length() + 1;//view->viewport()->height() + 1; QCOMPARE(view->logicalIndexAt(outofbounds), -1); view->moveSection(0,1); // First item QCOMPARE(view->logicalIndexAt(0), 1); QCOMPARE(view->logicalIndexAt(view->sectionSize(0)-1), 1); QCOMPARE(view->logicalIndexAt(view->sectionSize(0)+1), 0); // Last item QCOMPARE(view->logicalIndexAt(last), 3); view->moveSection(1,0); } void tst_QHeaderView::swapSections() { view->swapSections(-1, 1); view->swapSections(99999, 1); view->swapSections(1, -1); view->swapSections(1, 99999); IntList logical{ 0, 1, 2, 3 }; QSignalSpy spy1(view, &QHeaderView::sectionMoved); QCOMPARE(view->sectionsMoved(), false); view->swapSections(1, 1); QCOMPARE(view->sectionsMoved(), false); view->swapSections(1, 2); QCOMPARE(view->sectionsMoved(), true); view->swapSections(2, 1); QCOMPARE(view->sectionsMoved(), true); for (int i = 0; i < view->count(); ++i) QCOMPARE(view->logicalIndex(i), logical.at(i)); QCOMPARE(spy1.size(), 4); logical = { 3, 1, 2, 0 }; view->swapSections(3, 0); QCOMPARE(view->sectionsMoved(), true); for (int j = 0; j < view->count(); ++j) QCOMPARE(view->logicalIndex(j), logical.at(j)); QCOMPARE(spy1.size(), 6); } void tst_QHeaderView::moveSection_data() { QTest::addColumn("hidden"); QTest::addColumn("from"); QTest::addColumn("to"); QTest::addColumn("moved"); QTest::addColumn("logical"); QTest::addColumn("count"); QTest::newRow("bad args, no hidden") << IntList() << (IntList{ -1, 1, 99999, 1 }) << (IntList{ 1, -1, 1, 99999 }) << (BoolList{ false, false, false, false }) << (IntList{ 0, 1, 2, 3 }) << 0; QTest::newRow("good args, no hidden") << IntList() << (IntList{ 1, 1, 2, 1 }) << (IntList{ 1, 2, 1, 2 }) << (BoolList{ false, true, true, true }) << (IntList{ 0, 2, 1, 3 }) << 3; QTest::newRow("hidden sections") << (IntList{ 0, 3 }) << (IntList{ 1, 1, 2, 1 }) << (IntList{ 1, 2, 1, 2 }) << (BoolList{ false, true, true, true }) << (IntList{ 0, 2, 1, 3 }) << 3; } void tst_QHeaderView::moveSection() { QFETCH(const IntList, hidden); QFETCH(const IntList, from); QFETCH(const IntList, to); QFETCH(const BoolList, moved); QFETCH(const IntList, logical); QFETCH(int, count); QCOMPARE(from.size(), to.size()); QCOMPARE(from.size(), moved.size()); QCOMPARE(view->count(), logical.size()); QSignalSpy spy1(view, &QHeaderView::sectionMoved); QCOMPARE(view->sectionsMoved(), false); for (int h : hidden) view->setSectionHidden(h, true); for (int i = 0; i < from.size(); ++i) { view->moveSection(from.at(i), to.at(i)); QCOMPARE(view->sectionsMoved(), moved.at(i)); } for (int j = 0; j < view->count(); ++j) QCOMPARE(view->logicalIndex(j), logical.at(j)); QCOMPARE(spy1.size(), count); } void tst_QHeaderView::resizeAndMoveSection_data() { QTest::addColumn("logicalIndexes"); QTest::addColumn("sizes"); QTest::addColumn("logicalFrom"); QTest::addColumn("logicalTo"); QTest::newRow("resizeAndMove-1") << (IntList{ 0, 1 }) << (IntList{ 20, 40 }) << 0 << 1; QTest::newRow("resizeAndMove-2") << (IntList{ 0, 1, 2, 3 }) << (IntList{ 20, 60, 10, 80 }) << 0 << 2; QTest::newRow("resizeAndMove-3") << (IntList{ 0, 1, 2, 3 }) << (IntList{ 100, 60, 40, 10 }) << 0 << 3; QTest::newRow("resizeAndMove-4") << (IntList{ 0, 1, 2, 3 }) << (IntList{ 10, 40, 80, 30 }) << 1 << 2; QTest::newRow("resizeAndMove-5") << (IntList{ 2, 3 }) << (IntList{ 100, 200}) << 3 << 2; } void tst_QHeaderView::resizeAndMoveSection() { QFETCH(const IntList, logicalIndexes); QFETCH(const IntList, sizes); QFETCH(int, logicalFrom); QFETCH(int, logicalTo); // Save old visual indexes and sizes IntList oldVisualIndexes; IntList oldSizes; for (int logical : logicalIndexes) { oldVisualIndexes.append(view->visualIndex(logical)); oldSizes.append(view->sectionSize(logical)); } // Resize sections for (int i = 0; i < logicalIndexes.size(); ++i) { int logical = logicalIndexes.at(i); view->resizeSection(logical, sizes.at(i)); } // Move sections int visualFrom = view->visualIndex(logicalFrom); int visualTo = view->visualIndex(logicalTo); view->moveSection(visualFrom, visualTo); QCOMPARE(view->visualIndex(logicalFrom), visualTo); // Check that sizes are still correct for (int i = 0; i < logicalIndexes.size(); ++i) { int logical = logicalIndexes.at(i); QCOMPARE(view->sectionSize(logical), sizes.at(i)); } // Move sections back view->moveSection(visualTo, visualFrom); // Check that sizes are still correct for (int i = 0; i < logicalIndexes.size(); ++i) { int logical = logicalIndexes.at(i); QCOMPARE(view->sectionSize(logical), sizes.at(i)); } // Put everything back as it was for (int i = 0; i < logicalIndexes.size(); ++i) { int logical = logicalIndexes.at(i); view->resizeSection(logical, oldSizes.at(i)); QCOMPARE(view->visualIndex(logical), oldVisualIndexes.at(i)); } } void tst_QHeaderView::resizeHiddenSection_data() { QTest::addColumn("section"); QTest::addColumn("initialSize"); QTest::addColumn("finalSize"); QTest::newRow("section 0 resize 50 to 20") << 0 << 50 << 20; QTest::newRow("section 1 resize 50 to 20") << 1 << 50 << 20; QTest::newRow("section 2 resize 50 to 20") << 2 << 50 << 20; QTest::newRow("section 3 resize 50 to 20") << 3 << 50 << 20; } void tst_QHeaderView::resizeHiddenSection() { QFETCH(int, section); QFETCH(int, initialSize); QFETCH(int, finalSize); view->resizeSection(section, initialSize); view->setSectionHidden(section, true); QCOMPARE(view->sectionSize(section), 0); view->resizeSection(section, finalSize); QCOMPARE(view->sectionSize(section), 0); view->setSectionHidden(section, false); QCOMPARE(view->sectionSize(section), finalSize); } void tst_QHeaderView::resizeAndInsertSection_data() { QTest::addColumn("section"); QTest::addColumn("size"); QTest::addColumn("insert"); QTest::addColumn("compare"); QTest::addColumn("expected"); QTest::newRow("section 0 size 50 insert 0") << 0 << 50 << 0 << 1 << 50; QTest::newRow("section 1 size 50 insert 1") << 0 << 50 << 1 << 0 << 50; QTest::newRow("section 1 size 50 insert 0") << 1 << 50 << 0 << 2 << 50; } void tst_QHeaderView::resizeAndInsertSection() { QFETCH(int, section); QFETCH(int, size); QFETCH(int, insert); QFETCH(int, compare); QFETCH(int, expected); view->setStretchLastSection(false); view->resizeSection(section, size); QCOMPARE(view->sectionSize(section), size); model->insertRow(insert); QCOMPARE(view->sectionSize(compare), expected); } void tst_QHeaderView::resizeWithResizeModes_data() { QTest::addColumn("size"); QTest::addColumn("sections"); QTest::addColumn("modes"); QTest::addColumn("expected"); QTest::newRow("stretch first section") << 600 << (IntList{ 100, 100, 100, 100 }) << (ResizeVec { QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }) << (IntList{ 300, 100, 100, 100 }); } void tst_QHeaderView::resizeWithResizeModes() { QFETCH(int, size); QFETCH(const IntList, sections); QFETCH(const ResizeVec, modes); QFETCH(const IntList, expected); view->setStretchLastSection(false); for (int i = 0; i < sections.size(); ++i) { view->resizeSection(i, sections.at(i)); view->setSectionResizeMode(i, modes.at(i)); } topLevel->show(); QVERIFY(QTest::qWaitForWindowExposed(topLevel)); view->resize(size, size); for (int j = 0; j < expected.size(); ++j) QCOMPARE(view->sectionSize(j), expected.at(j)); } void tst_QHeaderView::moveAndInsertSection_data() { QTest::addColumn("from"); QTest::addColumn("to"); QTest::addColumn("insert"); QTest::addColumn("mapping"); QTest::newRow("move from 1 to 3, insert 0") << 1 << 3 << 0 <<(IntList{ 0, 1, 3, 4, 2 }); } void tst_QHeaderView::moveAndInsertSection() { QFETCH(int, from); QFETCH(int, to); QFETCH(int, insert); QFETCH(IntList, mapping); view->setStretchLastSection(false); view->moveSection(from, to); model->insertRow(insert); for (int i = 0; i < mapping.size(); ++i) QCOMPARE(view->logicalIndex(i), mapping.at(i)); } void tst_QHeaderView::resizeMode() { // resizeMode must not be called with an invalid index int last = view->count() - 1; view->setSectionResizeMode(QHeaderView::Interactive); QCOMPARE(view->sectionResizeMode(last), QHeaderView::Interactive); QCOMPARE(view->sectionResizeMode(1), QHeaderView::Interactive); view->setSectionResizeMode(QHeaderView::Stretch); QCOMPARE(view->sectionResizeMode(last), QHeaderView::Stretch); QCOMPARE(view->sectionResizeMode(1), QHeaderView::Stretch); view->setSectionResizeMode(QHeaderView::Custom); QCOMPARE(view->sectionResizeMode(last), QHeaderView::Custom); QCOMPARE(view->sectionResizeMode(1), QHeaderView::Custom); // test when sections have been moved view->setStretchLastSection(false); for (int i = 0; i < (view->count() - 1); ++i) view->setSectionResizeMode(i, QHeaderView::Interactive); int logicalIndex = view->count() / 2; view->setSectionResizeMode(logicalIndex, QHeaderView::Stretch); view->moveSection(view->visualIndex(logicalIndex), 0); for (int i = 0; i < (view->count() - 1); ++i) { if (i == logicalIndex) QCOMPARE(view->sectionResizeMode(i), QHeaderView::Stretch); else QCOMPARE(view->sectionResizeMode(i), QHeaderView::Interactive); } } void tst_QHeaderView::resizeSection_data() { QTest::addColumn("initial"); QTest::addColumn("logical"); QTest::addColumn("size"); QTest::addColumn("mode"); QTest::addColumn("resized"); QTest::addColumn("expected"); QTest::newRow("bad args") << 100 << (IntList{ -1, -1, 99999, 99999, 4 }) << (IntList{ -1, 0, 99999, -1, -1 }) << (ResizeVec{ QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }) << 0 << (IntList{ 0, 0, 0, 0, 0 }); } void tst_QHeaderView::resizeSection() { QFETCH(int, initial); QFETCH(const IntList, logical); QFETCH(const IntList, size); QFETCH(const ResizeVec, mode); QFETCH(int, resized); QFETCH(const IntList, expected); view->resize(400, 400); topLevel->show(); QVERIFY(QTest::qWaitForWindowExposed(topLevel)); view->setSectionsMovable(true); view->setStretchLastSection(false); for (int i = 0; i < logical.size(); ++i) if (logical.at(i) > -1 && logical.at(i) < view->count()) // for now view->setSectionResizeMode(logical.at(i), mode.at(i)); for (int j = 0; j < logical.size(); ++j) view->resizeSection(logical.at(j), initial); QSignalSpy spy(view, &QHeaderView::sectionResized); for (int k = 0; k < logical.size(); ++k) view->resizeSection(logical.at(k), size.at(k)); QCOMPARE(spy.size(), resized); for (int l = 0; l < logical.size(); ++l) QCOMPARE(view->sectionSize(logical.at(l)), expected.at(l)); } void tst_QHeaderView::highlightSections() { view->setHighlightSections(true); QCOMPARE(view->highlightSections(), true); view->setHighlightSections(false); QCOMPARE(view->highlightSections(), false); } void tst_QHeaderView::showSortIndicator() { view->setSortIndicatorShown(true); QCOMPARE(view->isSortIndicatorShown(), true); QCOMPARE(view->sortIndicatorOrder(), Qt::DescendingOrder); view->setSortIndicator(1, Qt::AscendingOrder); QCOMPARE(view->sortIndicatorOrder(), Qt::AscendingOrder); view->setSortIndicator(1, Qt::DescendingOrder); QCOMPARE(view->sortIndicatorOrder(), Qt::DescendingOrder); view->setSortIndicatorShown(false); QCOMPARE(view->isSortIndicatorShown(), false); view->setSortIndicator(999999, Qt::DescendingOrder); // Don't segfault baby :) view->setSortIndicatorShown(true); view->setSortIndicator(0, Qt::DescendingOrder); // Don't assert baby :) } void tst_QHeaderView::clearSectionSorting() { QStandardItemModel m(4, 4); QHeaderView h(Qt::Horizontal); QCOMPARE(h.sortIndicatorSection(), 0); QCOMPARE(h.sortIndicatorOrder(), Qt::DescendingOrder); h.setModel(&m); h.setSectionsClickable(true); h.setSortIndicatorShown(true); h.setSortIndicator(-1, Qt::DescendingOrder); h.show(); QVERIFY(QTest::qWaitForWindowExposed(&h)); QCOMPARE(h.sortIndicatorSection(), -1); QCOMPARE(h.sortIndicatorOrder(), Qt::DescendingOrder); QSignalSpy sectionClickedSpy(&h, &QHeaderView::sectionClicked); QVERIFY(sectionClickedSpy.isValid()); QCOMPARE(sectionClickedSpy.size(), 0); QSignalSpy sortIndicatorChangedSpy(&h, &QHeaderView::sortIndicatorChanged); QVERIFY(sortIndicatorChangedSpy.isValid()); QCOMPARE(sortIndicatorChangedSpy.size(), 0); enum { Count = 30 }; // normal behavior: clicking multiple times will just toggle the sort indicator for (int i = 0; i < Count; ++i) { QTest::mouseClick(h.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); QCOMPARE(sectionClickedSpy.size(), i + 1); QCOMPARE(sortIndicatorChangedSpy.size(), i + 1); QCOMPARE(h.sortIndicatorSection(), 0); const auto expectedOrder = (i % 2) == 0 ? Qt::AscendingOrder : Qt::DescendingOrder; QCOMPARE(h.sortIndicatorOrder(), expectedOrder); } h.setSortIndicator(-1, Qt::DescendingOrder); h.setSortIndicatorClearable(true); QCOMPARE(h.sortIndicatorSection(), -1); QCOMPARE(h.sortIndicatorOrder(), Qt::DescendingOrder); sectionClickedSpy.clear(); sortIndicatorChangedSpy.clear(); // clearing behavior: clicking multiple times will be tristate (asc, desc, nothing) for (int i = 0; i < Count; ++i) { QTest::mouseClick(h.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); QCOMPARE(sectionClickedSpy.size(), i + 1); QCOMPARE(sortIndicatorChangedSpy.size(), i + 1); switch (i % 3) { case 0: QCOMPARE(h.sortIndicatorSection(), 0); QCOMPARE(h.sortIndicatorOrder(), Qt::AscendingOrder); break; case 1: QCOMPARE(h.sortIndicatorSection(), 0); QCOMPARE(h.sortIndicatorOrder(), Qt::DescendingOrder); break; case 2: QCOMPARE(h.sortIndicatorSection(), -1); QCOMPARE(h.sortIndicatorOrder(), Qt::AscendingOrder); break; } } } void tst_QHeaderView::sortIndicatorTracking() { QtTestModel model(10, 10); QHeaderView hv(Qt::Horizontal); hv.setModel(&model); hv.show(); hv.setSortIndicatorShown(true); hv.setSortIndicator(1, Qt::DescendingOrder); model.removeOneColumn(8); QCOMPARE(hv.sortIndicatorSection(), 1); model.removeOneColumn(2); QCOMPARE(hv.sortIndicatorSection(), 1); model.insertOneColumn(2); QCOMPARE(hv.sortIndicatorSection(), 1); model.insertOneColumn(1); QCOMPARE(hv.sortIndicatorSection(), 2); model.removeOneColumn(0); QCOMPARE(hv.sortIndicatorSection(), 1); model.removeOneColumn(1); QCOMPARE(hv.sortIndicatorSection(), -1); } void tst_QHeaderView::removeAndInsertRow() { // Check if logicalIndex returns the correct value after we have removed a row // we might as well te for (int i = 0; i < model->rowCount(); ++i) QCOMPARE(i, view->logicalIndex(i)); while (model->removeRow(0)) { for (int i = 0; i < model->rowCount(); ++i) QCOMPARE(i, view->logicalIndex(i)); } for (int pass = 0; pass < 5; pass++) { for (int i = 0; i < model->rowCount(); ++i) QCOMPARE(i, view->logicalIndex(i)); model->insertRow(0); } while (model->removeRows(0, 2)) { for (int i = 0; i < model->rowCount(); ++i) QCOMPARE(i, view->logicalIndex(i)); } for (int pass = 0; pass < 3; pass++) { model->insertRows(0, 2); for (int i = 0; i < model->rowCount(); ++i) { QCOMPARE(i, view->logicalIndex(i)); } } for (int pass = 0; pass < 3; pass++) { model->insertRows(3, 2); for (int i = 0; i < model->rowCount(); ++i) QCOMPARE(i, view->logicalIndex(i)); } // Insert at end for (int pass = 0; pass < 3; pass++) { int rowCount = model->rowCount(); model->insertRows(rowCount, 1); for (int i = 0; i < rowCount; ++i) QCOMPARE(i, view->logicalIndex(i)); } } void tst_QHeaderView::unhideSection() { // You should not necessarily expect the same size back again, so the best test we can do is to test if it is larger than 0 after a unhide. QCOMPARE(view->sectionsHidden(), false); view->setSectionHidden(0, true); QCOMPARE(view->sectionsHidden(), true); QCOMPARE(view->sectionSize(0), 0); view->setSectionResizeMode(QHeaderView::Interactive); view->setSectionHidden(0, false); QVERIFY(view->sectionSize(0) > 0); view->setSectionHidden(0, true); QCOMPARE(view->sectionSize(0), 0); view->setSectionHidden(0, true); QCOMPARE(view->sectionSize(0), 0); view->setSectionResizeMode(QHeaderView::Stretch); view->setSectionHidden(0, false); QVERIFY(view->sectionSize(0) > 0); } void tst_QHeaderView::testEvent() { protected_QHeaderView x(Qt::Vertical); x.testEvent(); protected_QHeaderView y(Qt::Horizontal); y.testEvent(); } void protected_QHeaderView::testEvent() { // No crashy please QHoverEvent enterEvent(QEvent::HoverEnter, QPoint(), QPoint(), QPoint()); event(&enterEvent); QHoverEvent eventLeave(QEvent::HoverLeave, QPoint(), QPoint(), QPoint()); event(&eventLeave); QHoverEvent eventMove(QEvent::HoverMove, QPoint(), QPoint(), QPoint()); event(&eventMove); } void tst_QHeaderView::headerDataChanged() { // This shouldn't assert because view is Vertical view->headerDataChanged(Qt::Horizontal, -1, -1); #if 0 // This will assert view->headerDataChanged(Qt::Vertical, -1, -1); #endif // No crashing please view->headerDataChanged(Qt::Horizontal, 0, 1); view->headerDataChanged(Qt::Vertical, 0, 1); } void tst_QHeaderView::currentChanged() { view->setCurrentIndex(QModelIndex()); } void tst_QHeaderView::horizontalOffset() { protected_QHeaderView x(Qt::Vertical); x.testhorizontalOffset(); protected_QHeaderView y(Qt::Horizontal); y.testhorizontalOffset(); } void tst_QHeaderView::verticalOffset() { protected_QHeaderView x(Qt::Vertical); x.testverticalOffset(); protected_QHeaderView y(Qt::Horizontal); y.testverticalOffset(); } void protected_QHeaderView::testhorizontalOffset() { if (orientation() == Qt::Horizontal) { QCOMPARE(horizontalOffset(), 0); setOffset(10); QCOMPARE(horizontalOffset(), 10); } else QCOMPARE(horizontalOffset(), 0); } void protected_QHeaderView::testverticalOffset() { if (orientation() == Qt::Vertical) { QCOMPARE(verticalOffset(), 0); setOffset(10); QCOMPARE(verticalOffset(), 10); } else QCOMPARE(verticalOffset(), 0); } void tst_QHeaderView::stretchSectionCount() { view->setStretchLastSection(false); QCOMPARE(view->stretchSectionCount(), 0); view->setStretchLastSection(true); QCOMPARE(view->stretchSectionCount(), 0); view->setSectionResizeMode(0, QHeaderView::Stretch); QCOMPARE(view->stretchSectionCount(), 1); } void tst_QHeaderView::hiddenSectionCount() { model->clear(); model->insertRows(0, 10); // Hide every other one for (int i = 0; i < 10; i++) view->setSectionHidden(i, (i & 1) == 0); QCOMPARE(view->hiddenSectionCount(), 5); view->setSectionResizeMode(QHeaderView::Stretch); QCOMPARE(view->hiddenSectionCount(), 5); // Remove some rows and make sure they are now still counted model->removeRow(9); model->removeRow(8); model->removeRow(7); model->removeRow(6); QCOMPARE(view->count(), 6); QCOMPARE(view->hiddenSectionCount(), 3); model->removeRows(0, 5); QCOMPARE(view->count(), 1); QCOMPARE(view->hiddenSectionCount(), 0); QVERIFY(view->count() >= view->hiddenSectionCount()); } void tst_QHeaderView::focusPolicy() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); QHeaderView view(Qt::Horizontal); QCOMPARE(view.focusPolicy(), Qt::NoFocus); QTreeWidget widget; QCOMPARE(widget.header()->focusPolicy(), Qt::NoFocus); QVERIFY(!widget.focusProxy()); QVERIFY(!widget.hasFocus()); QVERIFY(!widget.header()->focusProxy()); QVERIFY(!widget.header()->hasFocus()); widget.show(); widget.setFocus(Qt::OtherFocusReason); QApplicationPrivate::setActiveWindow(&widget); widget.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&widget)); QVERIFY(widget.hasFocus()); QVERIFY(!widget.header()->hasFocus()); widget.setFocusPolicy(Qt::NoFocus); widget.clearFocus(); QTRY_VERIFY(!widget.hasFocus()); QVERIFY(!widget.header()->hasFocus()); QTest::keyPress(&widget, Qt::Key_Tab); QCoreApplication::processEvents(); QCoreApplication::processEvents(); QVERIFY(!widget.hasFocus()); QVERIFY(!widget.header()->hasFocus()); } class SimpleModel : public QAbstractItemModel { Q_OBJECT public: using QAbstractItemModel::QAbstractItemModel; QModelIndex parent(const QModelIndex &/*child*/) const override { return QModelIndex(); } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { return hasIndex(row, column, parent) ? createIndex(row, column) : QModelIndex(); } int rowCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return 8; } int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override { return m_col_count; } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) return QVariant(); if (role == Qt::DisplayRole) return QString::number(index.row()) + QLatin1Char(',') + QString::number(index.column()); return QVariant(); } void setColumnCount(int c) { m_col_count = c; } private: int m_col_count = 3; }; void tst_QHeaderView::moveSectionAndReset() { SimpleModel m; QHeaderView v(Qt::Horizontal); v.setModel(&m); int cc = 2; for (cc = 2; cc < 4; ++cc) { m.setColumnCount(cc); int movefrom = 0; int moveto; for (moveto = 1; moveto < cc; ++moveto) { v.moveSection(movefrom, moveto); m.setColumnCount(cc - 1); v.reset(); for (int i = 0; i < cc - 1; ++i) QCOMPARE(v.logicalIndex(v.visualIndex(i)), i); } } } void tst_QHeaderView::moveSectionAndRemove() { QStandardItemModel m; QHeaderView v(Qt::Horizontal); v.setModel(&m); v.model()->insertColumns(0, 3); v.moveSection(0, 1); QCOMPARE(v.count(), 3); v.model()->removeColumns(0, v.model()->columnCount()); QCOMPARE(v.count(), 0); } static QByteArray savedState() { QStandardItemModel m(4, 4); QHeaderView h1(Qt::Horizontal); h1.setModel(&m); h1.setMinimumSectionSize(0); // system default min size can be to large h1.swapSections(0, 2); h1.resizeSection(1, 10); h1.setSortIndicatorShown(true); h1.setSortIndicator(2, Qt::DescendingOrder); h1.setSectionHidden(3, true); return h1.saveState(); } // As generated by savedState() static const QByteArray qt5SavedSate = QByteArrayLiteral("\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\b\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00""d\x00\x00\x00\xD2\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00""d\x00\x00\x00\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00""d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00""d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x00\x00\x00\x00\x00\x00"); enum class SaveRestoreOption { CheckGeneratedState, DoNotCheckGeneratedState, }; static void saveRestoreImpl(const QByteArray &state, SaveRestoreOption option) { QStandardItemModel m(4, 4); QHeaderView h2(Qt::Vertical); QSignalSpy spy(&h2, &QHeaderView::sortIndicatorChanged); h2.setModel(&m); QVERIFY(h2.restoreState(state)); QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).at(0).toInt(), 2); QCOMPARE(h2.logicalIndex(0), 2); QCOMPARE(h2.logicalIndex(2), 0); QCOMPARE(h2.sectionSize(1), 10); QCOMPARE(h2.sortIndicatorSection(), 2); QCOMPARE(h2.sortIndicatorOrder(), Qt::DescendingOrder); QCOMPARE(h2.isSortIndicatorShown(), true); QVERIFY(!h2.isSectionHidden(2)); QVERIFY(h2.isSectionHidden(3)); QCOMPARE(h2.hiddenSectionCount(), 1); switch (option) { case SaveRestoreOption::CheckGeneratedState: { QByteArray s2 = h2.saveState(); QCOMPARE(state, s2); break; } case SaveRestoreOption::DoNotCheckGeneratedState: break; }; QVERIFY(!h2.restoreState(QByteArrayLiteral("Garbage"))); } void tst_QHeaderView::saveRestore() { saveRestoreImpl(savedState(), SaveRestoreOption::CheckGeneratedState); } void tst_QHeaderView::QTBUG99487_saveRestoreQt5Compat() { saveRestoreImpl(qt5SavedSate, SaveRestoreOption::DoNotCheckGeneratedState); } void tst_QHeaderView::restoreToMoreColumns() { // Restore state onto a model with more columns const QByteArray s1 = savedState(); QHeaderView h4(Qt::Horizontal); QStandardItemModel fiveColumnsModel(1, 5); h4.setModel(&fiveColumnsModel); QCOMPARE(fiveColumnsModel.columnCount(), 5); QCOMPARE(h4.count(), 5); QVERIFY(h4.restoreState(s1)); QCOMPARE(fiveColumnsModel.columnCount(), 5); QCOMPARE(h4.count(), 5); QCOMPARE(h4.sectionSize(1), 10); for (int i = 0; i < h4.count(); ++i) QVERIFY(h4.sectionSize(i) > 0 || h4.isSectionHidden(i)); QVERIFY(!h4.isSectionHidden(2)); QVERIFY(h4.isSectionHidden(3)); QCOMPARE(h4.hiddenSectionCount(), 1); QCOMPARE(h4.sortIndicatorSection(), 2); QCOMPARE(h4.sortIndicatorOrder(), Qt::DescendingOrder); QCOMPARE(h4.logicalIndex(0), 2); QCOMPARE(h4.logicalIndex(1), 1); QCOMPARE(h4.logicalIndex(2), 0); QCOMPARE(h4.visualIndex(0), 2); QCOMPARE(h4.visualIndex(1), 1); QCOMPARE(h4.visualIndex(2), 0); // Repainting shouldn't crash h4.show(); QVERIFY(QTest::qWaitForWindowExposed(&h4)); } void tst_QHeaderView::restoreToMoreColumnsNoMovedColumns() { // Given a model with 2 columns, for saving state QHeaderView h1(Qt::Horizontal); QStandardItemModel model1(1, 2); h1.setModel(&model1); QCOMPARE(h1.visualIndex(0), 0); QCOMPARE(h1.visualIndex(1), 1); QCOMPARE(h1.logicalIndex(0), 0); QCOMPARE(h1.logicalIndex(1), 1); const QByteArray savedState = h1.saveState(); // And a model with 3 columns, to apply that state upon QHeaderView h2(Qt::Horizontal); QStandardItemModel model2(1, 3); h2.setModel(&model2); QCOMPARE(h2.visualIndex(0), 0); QCOMPARE(h2.visualIndex(1), 1); QCOMPARE(h2.visualIndex(2), 2); QCOMPARE(h2.logicalIndex(0), 0); QCOMPARE(h2.logicalIndex(1), 1); QCOMPARE(h2.logicalIndex(2), 2); // When calling restoreState() QVERIFY(h2.restoreState(savedState)); // Then the index mapping should still be as default QCOMPARE(h2.visualIndex(0), 0); QCOMPARE(h2.visualIndex(1), 1); QCOMPARE(h2.visualIndex(2), 2); QCOMPARE(h2.logicalIndex(0), 0); QCOMPARE(h2.logicalIndex(1), 1); QCOMPARE(h2.logicalIndex(2), 2); // And repainting shouldn't crash h2.show(); QVERIFY(QTest::qWaitForWindowExposed(&h2)); } void tst_QHeaderView::restoreBeforeSetModel() { QHeaderView h2(Qt::Horizontal); const QByteArray s1 = savedState(); // First restore QVERIFY(h2.restoreState(s1)); // Then setModel QStandardItemModel model(4, 4); h2.setModel(&model); // Check the result QCOMPARE(h2.logicalIndex(0), 2); QCOMPARE(h2.logicalIndex(2), 0); QCOMPARE(h2.sectionSize(1), 10); QCOMPARE(h2.sortIndicatorSection(), 2); QCOMPARE(h2.sortIndicatorOrder(), Qt::DescendingOrder); QCOMPARE(h2.isSortIndicatorShown(), true); QVERIFY(!h2.isSectionHidden(2)); QVERIFY(h2.isSectionHidden(3)); QCOMPARE(h2.hiddenSectionCount(), 1); } void tst_QHeaderView::defaultSectionSizeTest() { // Setup QTableView qtv; QHeaderView *hv = qtv.verticalHeader(); hv->setMinimumSectionSize(10); hv->setDefaultSectionSize(99); // Set it to a value different from defaultSize. QStandardItemModel amodel(4, 4); qtv.setModel(&amodel); QCOMPARE(hv->sectionSize(0), 99); QCOMPARE(hv->visualIndexAt(50), 0); // <= also make sure that indexes are calculated hv->setDefaultSectionSize(40); // Set it to a value different from defaultSize. QCOMPARE(hv->visualIndexAt(50), 1); const int defaultSize = 26; hv->setDefaultSectionSize(defaultSize + 1); // Set it to a value different from defaultSize. // no hidden Sections hv->resizeSection(1, 0); hv->setDefaultSectionSize(defaultSize); QCOMPARE(hv->sectionSize(1), defaultSize); // with hidden sections hv->resizeSection(1, 0); hv->hideSection(2); hv->setDefaultSectionSize(defaultSize); QVERIFY(hv->sectionSize(0) == defaultSize); // trivial case. QVERIFY(hv->sectionSize(1) == defaultSize); // just sized 0. Now it should be 10 QVERIFY(hv->sectionSize(2) == 0); // section is hidden. It should not be resized. } class TestHeaderViewStyle : public QProxyStyle { Q_OBJECT public: using QProxyStyle::QProxyStyle; int pixelMetric(PixelMetric metric, const QStyleOption *option = nullptr, const QWidget *widget = nullptr) const override { if (metric == QStyle::PM_HeaderDefaultSectionSizeHorizontal) return horizontalSectionSize; else return QProxyStyle::pixelMetric(metric, option, widget); } int horizontalSectionSize = 100; }; void tst_QHeaderView::defaultSectionSizeTestStyles() { TestHeaderViewStyle style1; TestHeaderViewStyle style2; style1.horizontalSectionSize = 100; style2.horizontalSectionSize = 200; QHeaderView hv(Qt::Horizontal); hv.setStyle(&style1); QCOMPARE(hv.defaultSectionSize(), style1.horizontalSectionSize); hv.setStyle(&style2); QCOMPARE(hv.defaultSectionSize(), style2.horizontalSectionSize); hv.setDefaultSectionSize(70); QCOMPARE(hv.defaultSectionSize(), 70); hv.setStyle(&style1); QCOMPARE(hv.defaultSectionSize(), 70); hv.resetDefaultSectionSize(); QCOMPARE(hv.defaultSectionSize(), style1.horizontalSectionSize); hv.setStyle(&style2); QCOMPARE(hv.defaultSectionSize(), style2.horizontalSectionSize); } void tst_QHeaderView::defaultAlignment_data() { QTest::addColumn("direction"); QTest::addColumn("initial"); QTest::addColumn("alignment"); QTest::newRow("horizontal right aligned") << Qt::Horizontal << Qt::Alignment(Qt::AlignCenter) << Qt::Alignment(Qt::AlignRight); QTest::newRow("horizontal left aligned") << Qt::Horizontal << Qt::Alignment(Qt::AlignCenter) << Qt::Alignment(Qt::AlignLeft); QTest::newRow("vertical right aligned") << Qt::Vertical << Qt::Alignment(Qt::AlignLeft|Qt::AlignVCenter) << Qt::Alignment(Qt::AlignRight); QTest::newRow("vertical left aligned") << Qt::Vertical << Qt::Alignment(Qt::AlignLeft|Qt::AlignVCenter) << Qt::Alignment(Qt::AlignLeft); } void tst_QHeaderView::defaultAlignment() { QFETCH(Qt::Orientation, direction); QFETCH(Qt::Alignment, initial); QFETCH(Qt::Alignment, alignment); SimpleModel m; QHeaderView header(direction); header.setModel(&m); QCOMPARE(header.defaultAlignment(), initial); header.setDefaultAlignment(alignment); QCOMPARE(header.defaultAlignment(), alignment); } void tst_QHeaderView::globalResizeMode_data() { QTest::addColumn("direction"); QTest::addColumn("mode"); QTest::addColumn("insert"); QTest::newRow("horizontal ResizeToContents 0") << Qt::Horizontal << QHeaderView::ResizeToContents << 0; } void tst_QHeaderView::globalResizeMode() { QFETCH(Qt::Orientation, direction); QFETCH(QHeaderView::ResizeMode, mode); QFETCH(int, insert); QStandardItemModel m(4, 4); QHeaderView h(direction); h.setModel(&m); h.setSectionResizeMode(mode); m.insertRow(insert); for (int i = 0; i < h.count(); ++i) QCOMPARE(h.sectionResizeMode(i), mode); } void tst_QHeaderView::sectionPressedSignal_data() { QTest::addColumn("direction"); QTest::addColumn("clickable"); QTest::addColumn("count"); QTest::newRow("horizontal unclickable 0") << Qt::Horizontal << false << 0; QTest::newRow("horizontal clickable 1") << Qt::Horizontal << true << 1; } void tst_QHeaderView::sectionPressedSignal() { QFETCH(Qt::Orientation, direction); QFETCH(bool, clickable); QFETCH(int, count); QStandardItemModel m(4, 4); QHeaderView h(direction); h.setModel(&m); h.show(); h.setSectionsClickable(clickable); QSignalSpy spy(&h, &QHeaderView::sectionPressed); QCOMPARE(spy.size(), 0); QTest::mousePress(h.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); QCOMPARE(spy.size(), count); } void tst_QHeaderView::sectionClickedSignal() { QFETCH(Qt::Orientation, direction); QFETCH(bool, clickable); QFETCH(int, count); QStandardItemModel m(4, 4); QHeaderView h(direction); h.setModel(&m); h.show(); h.setSectionsClickable(clickable); h.setSortIndicatorShown(true); QSignalSpy spy(&h, &QHeaderView::sectionClicked); QSignalSpy spy2(&h, &QHeaderView::sortIndicatorChanged); QCOMPARE(spy.size(), 0); QCOMPARE(spy2.size(), 0); QTest::mouseClick(h.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); QCOMPARE(spy.size(), count); QCOMPARE(spy2.size(), count); //now let's try with the sort indicator hidden (the result should be the same spy.clear(); spy2.clear(); h.setSortIndicatorShown(false); QTest::mouseClick(h.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); QCOMPARE(spy.size(), count); QCOMPARE(spy2.size(), count); } void tst_QHeaderView::defaultSectionSize_data() { QTest::addColumn("direction"); QTest::addColumn("oldDefaultSize"); QTest::addColumn("newDefaultSize"); //QTest::newRow("horizontal,-5") << int(Qt::Horizontal) << 100 << -5; QTest::newRow("horizontal, 0") << Qt::Horizontal << 100 << 0; QTest::newRow("horizontal, 5") << Qt::Horizontal << 100 << 5; QTest::newRow("horizontal,25") << Qt::Horizontal << 100 << 5; } void tst_QHeaderView::defaultSectionSize() { QFETCH(Qt::Orientation, direction); QFETCH(int, oldDefaultSize); QFETCH(int, newDefaultSize); QStandardItemModel m(4, 4); QHeaderView h(direction); h.setModel(&m); h.setMinimumSectionSize(0); QCOMPARE(h.defaultSectionSize(), oldDefaultSize); h.setDefaultSectionSize(newDefaultSize); QCOMPARE(h.defaultSectionSize(), newDefaultSize); h.reset(); for (int i = 0; i < h.count(); ++i) QCOMPARE(h.sectionSize(i), newDefaultSize); } void tst_QHeaderView::hideAndInsert_data() { QTest::addColumn("direction"); QTest::addColumn("hide"); QTest::addColumn("insert"); QTest::addColumn("hidden"); QTest::newRow("horizontal, 0, 0") << Qt::Horizontal << 0 << 0 << 1; } void tst_QHeaderView::hideAndInsert() { QFETCH(Qt::Orientation, direction); QFETCH(int, hide); QFETCH(int, insert); QFETCH(int, hidden); QStandardItemModel m(4, 4); QHeaderView h(direction); h.setModel(&m); h.setSectionHidden(hide, true); if (direction == Qt::Vertical) m.insertRow(insert); else m.insertColumn(insert); for (int i = 0; i < h.count(); ++i) QCOMPARE(h.isSectionHidden(i), i == hidden); } void tst_QHeaderView::removeSection() { const int hidden = 3; //section that will be hidden QStringListModel model({ "0", "1", "2", "3", "4", "5", "6" }); QHeaderView view(Qt::Vertical); view.setModel(&model); view.hideSection(hidden); view.hideSection(1); model.removeRow(1); view.show(); for(int i = 0; i < view.count(); i++) { if (i == (hidden - 1)) { //-1 because we removed a row in the meantime QCOMPARE(view.sectionSize(i), 0); QVERIFY(view.isSectionHidden(i)); } else { QCOMPARE(view.sectionSize(i), view.defaultSectionSize() ); QVERIFY(!view.isSectionHidden(i)); } } } void tst_QHeaderView::preserveHiddenSectionWidth() { QStringListModel model({ "0", "1", "2", "3" }); QHeaderView view(Qt::Vertical); view.setModel(&model); view.resizeSection(0, 100); view.resizeSection(1, 10); view.resizeSection(2, 50); view.setSectionResizeMode(3, QHeaderView::Stretch); view.show(); view.hideSection(2); model.removeRow(1); view.showSection(1); QCOMPARE(view.sectionSize(0), 100); QCOMPARE(view.sectionSize(1), 50); view.hideSection(1); model.insertRow(1); view.showSection(2); QCOMPARE(view.sectionSize(0), 100); QCOMPARE(view.sectionSize(1), view.defaultSectionSize()); QCOMPARE(view.sectionSize(2), 50); } void tst_QHeaderView::invisibleStretchLastSection() { int count = 6; QStandardItemModel model(1, count); QHeaderView view(Qt::Horizontal); view.setModel(&model); int height = view.height(); view.resize(view.defaultSectionSize() * (count / 2), height); // don't show all sections view.show(); view.setStretchLastSection(true); // stretch section is not visible; it should not be stretched for (int i = 0; i < count; ++i) QCOMPARE(view.sectionSize(i), view.defaultSectionSize()); view.resize(view.defaultSectionSize() * (count + 1), height); // give room to stretch // stretch section is visible; it should be stretched for (int i = 0; i < count - 1; ++i) QCOMPARE(view.sectionSize(i), view.defaultSectionSize()); QCOMPARE(view.sectionSize(count - 1), view.defaultSectionSize() * 2); } void tst_QHeaderView::noSectionsWithNegativeSize() { QStandardItemModel m(4, 4); QHeaderView h(Qt::Horizontal); h.setModel(&m); h.resizeSection(1, -5); QVERIFY(h.sectionSize(1) >= 0); // Sections with negative sizes not well defined. } void tst_QHeaderView::emptySectionSpan() { QHeaderViewPrivate::SectionItem section; QCOMPARE(section.sectionSize(), 0); } void tst_QHeaderView::task236450_hidden_data() { QTest::addColumn("hide1"); QTest::addColumn("hide2"); QTest::newRow("set 1") << (IntList{ 1, 3 }) << (IntList{ 1, 5 }); QTest::newRow("set 2") << (IntList{ 2, 3 }) << (IntList{ 1, 5 }); QTest::newRow("set 3") << (IntList{ 0, 2, 4 }) << (IntList{ 2, 3, 5 }); } void tst_QHeaderView::task236450_hidden() { QFETCH(const IntList, hide1); QFETCH(const IntList, hide2); QStringListModel model({ "0", "1", "2", "3", "4", "5" }); protected_QHeaderView view(Qt::Vertical); view.setModel(&model); view.show(); for (int i : hide1) view.hideSection(i); QCOMPARE(view.hiddenSectionCount(), hide1.size()); for (int i = 0; i < 6; i++) QCOMPARE(!view.isSectionHidden(i), !hide1.contains(i)); view.setDefaultSectionSize(2); view.scheduleDelayedItemsLayout(); view.executeDelayedItemsLayout(); //force to do a relayout QCOMPARE(view.hiddenSectionCount(), hide1.size()); for (int i = 0; i < 6; i++) { QCOMPARE(!view.isSectionHidden(i), !hide1.contains(i)); view.setSectionHidden(i, hide2.contains(i)); } QCOMPARE(view.hiddenSectionCount(), hide2.size()); for (int i = 0; i < 6; i++) QCOMPARE(!view.isSectionHidden(i), !hide2.contains(i)); } void tst_QHeaderView::task248050_hideRow() { //this is the sequence of events that make the task fail protected_QHeaderView header(Qt::Vertical); QStandardItemModel model(0, 1); header.setMinimumSectionSize(0); // system default min size can be to large header.setStretchLastSection(false); header.setDefaultSectionSize(17); header.setModel(&model); header.doItemsLayout(); model.setRowCount(3); QCOMPARE(header.sectionPosition(2), 17*2); header.hideSection(1); QCOMPARE(header.sectionPosition(2), 17); QTest::qWait(100); //the size of the section shouldn't have changed QCOMPARE(header.sectionPosition(2), 17); } //returns 0 if everything is fine. static int checkHeaderViewOrder(const QHeaderView *view, const IntList &expected) { if (view->count() != expected.size()) return 1; for (int i = 0; i < expected.size(); i++) { if (view->logicalIndex(i) != expected.at(i)) return i + 10; if (view->visualIndex(expected.at(i)) != i) return i + 100; } return 0; } void tst_QHeaderView::QTBUG6058_reset() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); QStringListModel model1({ "0", "1", "2", "3", "4", "5" }); QStringListModel model2({ "a", "b", "c" }); QSortFilterProxyModel proxy; QHeaderView view(Qt::Vertical); view.setModel(&proxy); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QVERIFY(QTest::qWaitForWindowActive(&view)); proxy.setSourceModel(&model1); view.swapSections(0, 2); view.swapSections(1, 4); IntList expectedOrder{2, 4, 0, 3, 1, 5}; QTRY_COMPARE(checkHeaderViewOrder(&view, expectedOrder) , 0); proxy.setSourceModel(&model2); expectedOrder = {2, 0, 1}; QTRY_COMPARE(checkHeaderViewOrder(&view, expectedOrder) , 0); proxy.setSourceModel(&model1); expectedOrder = {2, 0, 1, 3, 4, 5}; QTRY_COMPARE(checkHeaderViewOrder(&view, expectedOrder) , 0); } void tst_QHeaderView::QTBUG7833_sectionClicked() { QTableView tv; QStandardItemModel *sim = new QStandardItemModel(&tv); QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(&tv); proxyModel->setSourceModel(sim); proxyModel->setDynamicSortFilter(true); proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); QList row; for (char i = 0; i < 12; i++) row.append(new QStandardItem(QString(QLatin1Char('A' + i)))); sim->appendRow(row); row.clear(); for (char i = 12; i > 0; i--) row.append(new QStandardItem(QString(QLatin1Char('A' + i)))); sim->appendRow(row); tv.setSortingEnabled(true); tv.horizontalHeader()->setSortIndicatorShown(true); tv.horizontalHeader()->setSectionsClickable(true); tv.horizontalHeader()->setStretchLastSection(true); tv.horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); tv.setModel(proxyModel); const int section4Size = tv.horizontalHeader()->sectionSize(4) + 1; const int section5Size = section4Size + 1; tv.horizontalHeader()->resizeSection(4, section4Size); tv.horizontalHeader()->resizeSection(5, section5Size); tv.setColumnHidden(5, true); tv.setColumnHidden(6, true); tv.horizontalHeader()->swapSections(8, 10); tv.sortByColumn(1, Qt::AscendingOrder); QCOMPARE(tv.isColumnHidden(5), true); QCOMPARE(tv.isColumnHidden(6), true); QCOMPARE(tv.horizontalHeader()->sectionsMoved(), true); QCOMPARE(tv.horizontalHeader()->logicalIndex(8), 10); QCOMPARE(tv.horizontalHeader()->logicalIndex(10), 8); QCOMPARE(tv.horizontalHeader()->sectionSize(4), section4Size); tv.setColumnHidden(5, false); // unhide, section size must be properly restored QCOMPARE(tv.horizontalHeader()->sectionSize(5), section5Size); tv.setColumnHidden(5, true); QSignalSpy clickedSpy(tv.horizontalHeader(), &QHeaderView::sectionClicked); QSignalSpy pressedSpy(tv.horizontalHeader(), &QHeaderView::sectionPressed); QTest::mouseClick(tv.horizontalHeader()->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(tv.horizontalHeader()->sectionViewportPosition(11) + tv.horizontalHeader()->sectionSize(11) / 2, 5)); QCOMPARE(clickedSpy.size(), 1); QCOMPARE(pressedSpy.size(), 1); QCOMPARE(clickedSpy.at(0).at(0).toInt(), 11); QCOMPARE(pressedSpy.at(0).at(0).toInt(), 11); QTest::mouseClick(tv.horizontalHeader()->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(tv.horizontalHeader()->sectionViewportPosition(8) + tv.horizontalHeader()->sectionSize(0) / 2, 5)); QCOMPARE(clickedSpy.size(), 2); QCOMPARE(pressedSpy.size(), 2); QCOMPARE(clickedSpy.at(1).at(0).toInt(), 8); QCOMPARE(pressedSpy.at(1).at(0).toInt(), 8); QTest::mouseClick(tv.horizontalHeader()->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(tv.horizontalHeader()->sectionViewportPosition(0) + tv.horizontalHeader()->sectionSize(0) / 2, 5)); QCOMPARE(clickedSpy.size(), 3); QCOMPARE(pressedSpy.size(), 3); QCOMPARE(clickedSpy.at(2).at(0).toInt(), 0); QCOMPARE(pressedSpy.at(2).at(0).toInt(), 0); } void tst_QHeaderView::checkLayoutChangeEmptyModel() { QtTestModel tm(0, 11); QTableView tv; tv.verticalHeader()->setStretchLastSection(true); tv.setModel(&tm); const int section4Size = tv.horizontalHeader()->sectionSize(4) + 1; const int section5Size = section4Size + 1; tv.horizontalHeader()->resizeSection(4, section4Size); tv.horizontalHeader()->resizeSection(5, section5Size); tv.setColumnHidden(5, true); tv.setColumnHidden(6, true); tv.horizontalHeader()->swapSections(8, 10); tv.sortByColumn(1, Qt::AscendingOrder); tm.emitLayoutChanged(); QCOMPARE(tv.isColumnHidden(5), true); QCOMPARE(tv.isColumnHidden(6), true); QCOMPARE(tv.horizontalHeader()->sectionsMoved(), true); QCOMPARE(tv.horizontalHeader()->logicalIndex(8), 10); QCOMPARE(tv.horizontalHeader()->logicalIndex(10), 8); QCOMPARE(tv.horizontalHeader()->sectionSize(4), section4Size); tv.setColumnHidden(5, false); // unhide, section size must be properly restored QCOMPARE(tv.horizontalHeader()->sectionSize(5), section5Size); tv.setColumnHidden(5, true); // adjust tm.rows = 3; tm.emitLayoutChanged(); // remove the row used for QPersistenModelIndexes tm.emitLayoutChangedWithRemoveFirstRow(); QCOMPARE(tv.isColumnHidden(5), true); QCOMPARE(tv.isColumnHidden(6), true); QCOMPARE(tv.horizontalHeader()->sectionsMoved(), true); QCOMPARE(tv.horizontalHeader()->logicalIndex(8), 10); QCOMPARE(tv.horizontalHeader()->logicalIndex(10), 8); QCOMPARE(tv.horizontalHeader()->sectionSize(4), section4Size); tv.setColumnHidden(5, false); // unhide, section size must be properly restored QCOMPARE(tv.horizontalHeader()->sectionSize(5), section5Size); tv.setColumnHidden(5, true); } void tst_QHeaderView::QTBUG8650_crashOnInsertSections() { QStringList headerLabels; QHeaderView view(Qt::Horizontal); QStandardItemModel model(2, 2); view.setModel(&model); view.moveSection(1, 0); view.hideSection(0); model.insertColumn(0, { new QStandardItem("c") }); } static void setModelTexts(QStandardItemModel *model) { const int columnCount = model->columnCount(); for (int i = 0, rowCount = model->rowCount(); i < rowCount; ++i) { const QString prefix = QLatin1String("item [") + QString::number(i) + QLatin1Char(','); for (int j = 0; j < columnCount; ++j) model->setData(model->index(i, j), prefix + QString::number(j) + QLatin1Char(']')); } } void tst_QHeaderView::QTBUG12268_hiddenMovedSectionSorting() { QTableView view; // ### this test fails on QTableView &view = *m_tableview; !? + shadowing view member QStandardItemModel model(4, 3); setModelTexts(&model); view.setModel(&model); view.horizontalHeader()->setSectionsMovable(true); view.setSortingEnabled(true); view.sortByColumn(1, Qt::AscendingOrder); view.horizontalHeader()->moveSection(0,2); view.setColumnHidden(1, true); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QCOMPARE(view.horizontalHeader()->hiddenSectionCount(), 1); QTest::mouseClick(view.horizontalHeader()->viewport(), Qt::LeftButton); QCOMPARE(view.horizontalHeader()->hiddenSectionCount(), 1); } void tst_QHeaderView::QTBUG14242_hideSectionAutoSize() { QTableView qtv; QStandardItemModel amodel(4, 4); qtv.setModel(&amodel); QHeaderView *hv = qtv.verticalHeader(); hv->setDefaultSectionSize(25); hv->setSectionResizeMode(QHeaderView::ResizeToContents); qtv.show(); hv->hideSection(0); int afterlength = hv->length(); int calced_length = 0; for (int u = 0; u < hv->count(); ++u) calced_length += hv->sectionSize(u); QCOMPARE(calced_length, afterlength); } void tst_QHeaderView::QTBUG50171_visualRegionForSwappedItems() { protected_QHeaderView headerView(Qt::Horizontal); QtTestModel model(2, 3); headerView.setModel(&model); headerView.swapSections(1, 2); headerView.hideSection(0); headerView.testVisualRegionForSelection(); } class QTBUG53221_Model : public QAbstractItemModel { Q_OBJECT public: void insertRowAtBeginning() { Q_EMIT layoutAboutToBeChanged(); m_displayNames.insert(0, QStringLiteral("Item %1").arg(m_displayNames.size())); // Rows are always inserted at the beginning, so move all others. const auto pl = persistentIndexList(); // The vertical header view will have a persistent index stored here on the second call to insertRowAtBeginning. for (const QModelIndex &persIndex : pl) changePersistentIndex(persIndex, index(persIndex.row() + 1, persIndex.column(), persIndex.parent())); Q_EMIT layoutChanged(); } QVariant data(const QModelIndex &index, int role) const override { return (role == Qt::DisplayRole) ? m_displayNames.at(index.row()) : QVariant(); } QModelIndex index(int row, int column, const QModelIndex &) const override { return createIndex(row, column); } QModelIndex parent(const QModelIndex &) const override { return QModelIndex(); } int rowCount(const QModelIndex &) const override { return m_displayNames.size(); } int columnCount(const QModelIndex &) const override { return 1; } private: QStringList m_displayNames; }; void tst_QHeaderView::QTBUG53221_assertShiftHiddenRow() { QTableView tableView; QTBUG53221_Model modelTableView; tableView.setModel(&modelTableView); modelTableView.insertRowAtBeginning(); tableView.setRowHidden(0, true); QCOMPARE(tableView.verticalHeader()->isSectionHidden(0), true); modelTableView.insertRowAtBeginning(); QCOMPARE(tableView.verticalHeader()->isSectionHidden(0), false); QCOMPARE(tableView.verticalHeader()->isSectionHidden(1), true); modelTableView.insertRowAtBeginning(); QCOMPARE(tableView.verticalHeader()->isSectionHidden(0), false); QCOMPARE(tableView.verticalHeader()->isSectionHidden(1), false); QCOMPARE(tableView.verticalHeader()->isSectionHidden(2), true); } void tst_QHeaderView::QTBUG75615_sizeHintWithStylesheet() { QTableView tableView; QStandardItemModel model(1, 1); tableView.setModel(&model); tableView.show(); const auto headerView = tableView.horizontalHeader(); const auto oldSizeHint = headerView->sizeHint(); QVERIFY(oldSizeHint.isValid()); tableView.setStyleSheet("QTableView QHeaderView::section { height: 100px;}"); QCOMPARE(headerView->sizeHint().width(), oldSizeHint.width()); QCOMPARE(headerView->sizeHint().height(), 100); tableView.setStyleSheet("QTableView QHeaderView::section { width: 100px;}"); QCOMPARE(headerView->sizeHint().height(), oldSizeHint.height()); QCOMPARE(headerView->sizeHint().width(), 100); } void protected_QHeaderView::testVisualRegionForSelection() { QRegion r = visualRegionForSelection(QItemSelection(model()->index(1, 0), model()->index(1, 2))); QCOMPARE(r.boundingRect().contains(QRect(1, 1, length() - 2, 1)), true); } void tst_QHeaderView::ensureNoIndexAtLength() { QTableView qtv; QStandardItemModel amodel(4, 4); qtv.setModel(&amodel); QHeaderView *hv = qtv.verticalHeader(); QCOMPARE(hv->visualIndexAt(hv->length()), -1); hv->resizeSection(hv->count() - 1, 0); QCOMPARE(hv->visualIndexAt(hv->length()), -1); } void tst_QHeaderView::offsetConsistent() { // Ensure that a hidden section 'far away' // does not affect setOffsetToSectionPosition .. const int sectionToHide = 513; QTableView qtv; QStandardItemModel amodel(1000, 4); qtv.setModel(&amodel); QHeaderView *hv = qtv.verticalHeader(); for (int u = 0; u < 100; u += 2) hv->resizeSection(u, 0); hv->setOffsetToSectionPosition(150); int offset1 = hv->offset(); hv->hideSection(sectionToHide); hv->setOffsetToSectionPosition(150); int offset2 = hv->offset(); QCOMPARE(offset1, offset2); // Ensure that hidden indexes (still) is considered. hv->resizeSection(sectionToHide, hv->sectionSize(200) * 2); hv->setOffsetToSectionPosition(800); offset1 = hv->offset(); hv->showSection(sectionToHide); hv->setOffsetToSectionPosition(800); offset2 = hv->offset(); QVERIFY(offset2 > offset1); } void tst_QHeaderView::sectionsDontSortWhenNotClickingInThem() { QTableView qtv; QStandardItemModel amodel(1000, 4); qtv.setModel(&amodel); QHeaderView *hv = qtv.horizontalHeader(); hv->setSectionsClickable(true); hv->setFirstSectionMovable(true); hv->setSectionsMovable(false); enum { DefaultYOffset = 5, OutOfRangeYOffset = 10000 }; const auto pressOnSection = [&](int section, int yOffset = DefaultYOffset) { QTest::mousePress(hv->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(hv->sectionViewportPosition(section) + hv->sectionSize(section) / 2, yOffset)); }; const auto moveOntoSection = [&](int section, int yOffset = DefaultYOffset) { QTest::mouseMove(hv->viewport(), QPoint(hv->sectionViewportPosition(section) + hv->sectionSize(section) / 2, yOffset)); }; const auto releaseOnSection = [&](int section, int yOffset = DefaultYOffset) { QTest::mouseRelease(hv->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(hv->sectionViewportPosition(section) + hv->sectionSize(section) / 2, yOffset)); }; hv->setSortIndicator(-1, Qt::AscendingOrder); QCOMPARE(hv->sortIndicatorSection(), -1); pressOnSection(0); releaseOnSection(0); // RESULT: sorting QCOMPARE(hv->sortIndicatorSection(), 0); hv->setSortIndicator(-1, Qt::AscendingOrder); QCOMPARE(hv->sortIndicatorSection(), -1); pressOnSection(0); moveOntoSection(1); releaseOnSection(1); // RESULT: no sorting QCOMPARE(hv->sortIndicatorSection(), -1); pressOnSection(0); moveOntoSection(1); moveOntoSection(2); releaseOnSection(2); // RESULT: no sorting QCOMPARE(hv->sortIndicatorSection(), -1); pressOnSection(0); moveOntoSection(1); moveOntoSection(0); releaseOnSection(0); // RESULT: sorting by 0 QCOMPARE(hv->sortIndicatorSection(), 0); pressOnSection(0); moveOntoSection(1); releaseOnSection(1); // RESULT: no change, still sorting by 0 QCOMPARE(hv->sortIndicatorSection(), 0); auto sortOrder = hv->sortIndicatorOrder(); pressOnSection(1); moveOntoSection(0); releaseOnSection(0); // RESULT: no change, still sorting by 0 QCOMPARE(hv->sortIndicatorSection(), 0); QCOMPARE(hv->sortIndicatorOrder(), sortOrder); pressOnSection(1); moveOntoSection(0); moveOntoSection(1); releaseOnSection(1); // RESULT: sorting by 1 QCOMPARE(hv->sortIndicatorSection(), 1); pressOnSection(1); moveOntoSection(0); releaseOnSection(0); // RESULT: no change, still sorting by 1 QCOMPARE(hv->sortIndicatorSection(), 1); hv->setSortIndicator(-1, Qt::AscendingOrder); QCOMPARE(hv->sortIndicatorSection(), -1); pressOnSection(0); releaseOnSection(0, OutOfRangeYOffset); // RESULT: no sorting QCOMPARE(hv->sortIndicatorSection(), -1); pressOnSection(0); moveOntoSection(0, OutOfRangeYOffset); releaseOnSection(0, OutOfRangeYOffset); // RESULT: no sorting QCOMPARE(hv->sortIndicatorSection(), -1); pressOnSection(0); moveOntoSection(0, OutOfRangeYOffset); moveOntoSection(0); releaseOnSection(0); // RESULT: sorting by 0 QCOMPARE(hv->sortIndicatorSection(), 0); pressOnSection(1); releaseOnSection(1, OutOfRangeYOffset); // RESULT: no change, still sorting by 0 QCOMPARE(hv->sortIndicatorSection(), 0); pressOnSection(1); moveOntoSection(1, OutOfRangeYOffset); releaseOnSection(1, OutOfRangeYOffset); // RESULT: no change, still sorting by 0 QCOMPARE(hv->sortIndicatorSection(), 0); pressOnSection(1); moveOntoSection(1, OutOfRangeYOffset); moveOntoSection(1); releaseOnSection(1); // RESULT: sorting by 1 QCOMPARE(hv->sortIndicatorSection(), 1); pressOnSection(2); moveOntoSection(1); moveOntoSection(2); moveOntoSection(2, OutOfRangeYOffset); releaseOnSection(2, OutOfRangeYOffset); // RESULT: no change, still sorting by 1 QCOMPARE(hv->sortIndicatorSection(), 1); } void tst_QHeaderView::initialSortOrderRole() { QTableView view; // ### Shadowing member view (of type QHeaderView) QStandardItemModel model(4, 3); setModelTexts(&model); QStandardItem *ascendingItem = new QStandardItem(); QStandardItem *descendingItem = new QStandardItem(); ascendingItem->setData(Qt::AscendingOrder, Qt::InitialSortOrderRole); descendingItem->setData(Qt::DescendingOrder, Qt::InitialSortOrderRole); model.setHorizontalHeaderItem(1, ascendingItem); model.setHorizontalHeaderItem(2, descendingItem); view.setModel(&model); view.setSortingEnabled(true); view.sortByColumn(0, Qt::AscendingOrder); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); const int column1Pos = view.horizontalHeader()->sectionViewportPosition(1) + 5; // +5 not to be on the handle QTest::mouseClick(view.horizontalHeader()->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(column1Pos, 0)); QCOMPARE(view.horizontalHeader()->sortIndicatorSection(), 1); QCOMPARE(view.horizontalHeader()->sortIndicatorOrder(), Qt::AscendingOrder); const int column2Pos = view.horizontalHeader()->sectionViewportPosition(2) + 5; // +5 not to be on the handle QTest::mouseClick(view.horizontalHeader()->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(column2Pos, 0)); QCOMPARE(view.horizontalHeader()->sortIndicatorSection(), 2); QCOMPARE(view.horizontalHeader()->sortIndicatorOrder(), Qt::DescendingOrder); const int column0Pos = view.horizontalHeader()->sectionViewportPosition(0) + 5; // +5 not to be on the handle QTest::mouseClick(view.horizontalHeader()->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(column0Pos, 0)); QCOMPARE(view.horizontalHeader()->sortIndicatorSection(), 0); QCOMPARE(view.horizontalHeader()->sortIndicatorOrder(), Qt::AscendingOrder); } const bool block_some_signals = true; // The test should also work with this set to false const int rowcount = 500; const int colcount = 10; static inline QString istr(int n, bool comma = true) { QString s; s.setNum(n); if (comma) s += ", "; return s; } void tst_QHeaderView::calculateAndCheck(int cppline, const int precalced_comparedata[]) { qint64 endtimer = timer.elapsed(); const bool silentmode = true; if (!silentmode) qDebug().nospace() << "(Time:" << endtimer << ")"; QString sline; sline.setNum(cppline - 1); const int p1 = 3133777; // just a prime (maybe not that random ;) ) const int p2 = 135928393; // just a random large prime - a bit less than signed 32-bit int sum_visual = 0; int sum_logical = 0; int chk_visual = 1; int chk_logical = 1; int chk_sizes = 1; int chk_hidden_size = 1; int chk_lookup_visual = 1; int chk_lookup_logical = 1; int header_lenght = 0; int lastindex = view->count() - 1; // calculate information based on index for (int i = 0; i <= lastindex; ++i) { int visual = view->visualIndex(i); int logical = view->logicalIndex(i); int ssize = view->sectionSize(i); sum_visual += visual; sum_logical += logical; if (visual >= 0) { chk_visual %= p2; chk_visual *= (visual + 1) * (i + 1) * p1; } if (logical >= 0) { chk_logical %= p2; chk_logical *= (logical + 1) * (i + 1 + (logical != i) ) * p1; } if (ssize >= 0) { chk_sizes %= p2; chk_sizes *= ( (ssize + 2) * (i + 1) * p1); } if (view->isSectionHidden(i)) { view->showSection(i); int hiddensize = view->sectionSize(i); chk_hidden_size %= p2; chk_hidden_size += ( (hiddensize + 1) * (i + 1) * p1); // (hiddensize + 1) in the above to differ between hidden and size 0 // Though it can be changed (why isn't sections with size 0 hidden?) view->hideSection(i); } } // lookup indexes by pixel position const int max_lookup_count = 500; int lookup_to = view->height() + 1; if (lookup_to > max_lookup_count) lookup_to = max_lookup_count; // We limit this lookup - not to spend years when testing. // Notice that lookupTest also has its own extra test for (int u = 0; u < max_lookup_count; ++u) { int visu = view->visualIndexAt(u); int logi = view->logicalIndexAt(u); chk_lookup_visual %= p2; chk_lookup_visual *= ( (u + 1) * p1 * (visu + 2)); chk_lookup_logical %= p2; chk_lookup_logical *= ( (u + 1) * p1 * (logi + 2)); } header_lenght = view->length(); // visual and logical indexes. int sum_to_last_index = (lastindex * (lastindex + 1)) / 2; // == 0 + 1 + 2 + 3 + ... + lastindex const bool write_calced_data = false; // Do not write calculated output (unless the test fails) if (write_calced_data) { qDebug().nospace() << "(" << cppline - 1 << ")" // << " const int precalced[] = " << " {" << chk_visual << ", " << chk_logical << ", " << chk_sizes << ", " << chk_hidden_size << ", " << chk_lookup_visual << ", " << chk_lookup_logical << ", " << header_lenght << "};"; } const bool sanity_checks = true; if (sanity_checks) { QString msg = QString("sanity problem at ") + sline; const QScopedArrayPointer holder(QTest::toString(msg)); const auto verifytext = holder.data(); QVERIFY2(m_tableview->model()->rowCount() == view->count() , verifytext); QVERIFY2(view->visualIndex(lastindex + 1) <= 0, verifytext); // there is no such index in model QVERIFY2(view->logicalIndex(lastindex + 1) <= 0, verifytext); // there is no such index in model. QVERIFY2(view->logicalIndex(lastindex + 1) <= 0, verifytext); // there is no such index in model. QVERIFY2(lastindex < 0 || view->visualIndex(0) >= 0, verifytext); // no rows or legal index QVERIFY2(lastindex < 0 || view->logicalIndex(0) >= 0, verifytext); // no rows or legal index QVERIFY2(lastindex < 0 || view->visualIndex(lastindex) >= 0, verifytext); // no rows or legal index QVERIFY2(lastindex < 0 || view->logicalIndex(lastindex) >= 0, verifytext); // no rows or legal index QVERIFY2(view->visualIndexAt(-1) == -1, verifytext); QVERIFY2(view->logicalIndexAt(-1) == -1, verifytext); QVERIFY2(view->visualIndexAt(view->length()) == -1, verifytext); QVERIFY2(view->logicalIndexAt(view->length()) == -1, verifytext); QVERIFY2(sum_visual == sum_logical, verifytext); QVERIFY2(sum_to_last_index == sum_logical, verifytext); } // Semantic test const bool check_semantics = true; // Otherwise there is no 'real' test if (!check_semantics) return; const int *x = precalced_comparedata; QString msg = "semantic problem at " + QString(__FILE__) + " (" + sline + ")"; msg += "\nThe *expected* result was : {" + istr(x[0]) + istr(x[1]) + istr(x[2]) + istr(x[3]) + istr(x[4]) + istr(x[5]) + istr(x[6], false) + QLatin1Char('}'); msg += "\nThe calculated result was : {"; msg += istr(chk_visual) + istr(chk_logical) + istr(chk_sizes) + istr(chk_hidden_size) + istr(chk_lookup_visual) + istr(chk_lookup_logical) + istr(header_lenght, false) + "};"; const QScopedArrayPointer holder(QTest::toString(msg)); const auto verifytext = holder.data(); QVERIFY2(chk_visual == x[0], verifytext); QVERIFY2(chk_logical == x[1], verifytext); QVERIFY2(chk_sizes == x[2], verifytext); QVERIFY2(chk_hidden_size == x[3], verifytext); QVERIFY2(chk_lookup_visual == x[4], verifytext); QVERIFY2(chk_lookup_logical == x[5], verifytext); QVERIFY2(header_lenght == x[6], verifytext); } void tst_QHeaderView::setupTestData(bool also_use_reset_model) { QTest::addColumn("updates_enabled"); QTest::addColumn("special_prepare"); QTest::addColumn("reset_model"); if (also_use_reset_model) { QTest::newRow("no_updates+normal+reset") << false << false << true; QTest::newRow("hasupdates+normal+reset") << true << false << true; QTest::newRow("no_updates+special+reset") << false << true << true; QTest::newRow("hasupdates+special+reset") << true << true << true; } QTest::newRow("no_updates+normal") << false << false << false; QTest::newRow("hasupdates+normal") << true << false << false; QTest::newRow("no_updates+special") << false << true << false; QTest::newRow("hasupdates+special") << true << true << false; } void tst_QHeaderView::additionalInit() { m_tableview->setVerticalHeader(view); QFETCH(bool, updates_enabled); QFETCH(bool, special_prepare); QFETCH(bool, reset_model); m_using_reset_model = reset_model; m_special_prepare = special_prepare; if (m_using_reset_model) { XResetModel *m = new XResetModel(); m_tableview->setModel(m); delete model; model = m; } else { m_tableview->setModel(model); } const int default_section_size = 25; view->setDefaultSectionSize(default_section_size); // Important - otherwise there will be semantic changes model->clear(); if (special_prepare) { for (int u = 0; u <= rowcount; ++u) // ensures fragmented spans in e.g Qt 4.7 model->setRowCount(u); model->setColumnCount(colcount); view->swapSections(0, rowcount - 1); view->swapSections(0, rowcount - 1); // No real change - setup visual and log-indexes view->hideSection(rowcount - 1); view->showSection(rowcount - 1); // No real change - (maybe init hidden vector) } else { model->setColumnCount(colcount); model->setRowCount(rowcount); } QString s; for (int i = 0; i < model->rowCount(); ++i) { model->setData(model->index(i, 0), QVariant(i)); s.setNum(i); s += QLatin1Char('.'); s += QChar('a' + (i % 25)); model->setData(model->index(i, 1), QVariant(s)); } m_tableview->setUpdatesEnabled(updates_enabled); view->blockSignals(block_some_signals); timer.start(); } void tst_QHeaderView::logicalIndexAtTest() { additionalInit(); view->swapSections(4, 9); // Make sure that visual and logical Indexes are not just the same. int check1 = 0; int check2 = 0; for (int u = 0; u < model->rowCount(); ++u) { view->resizeSection(u, 10 + u % 30); int v = view->visualIndexAt(u * 29); view->visualIndexAt(u * 29); check1 += v; check2 += u * v; } view->resizeSection(0, 0); // Make sure that we have a 0 size section - before the result set view->setSectionHidden(6, true); // Make sure we have a real hidden section before result set //qDebug() << "logicalIndexAtTest" << check1 << check2; const int precalced_check1 = 106327; const int precalced_check2 = 29856418; QCOMPARE(precalced_check1, check1); QCOMPARE(precalced_check2, check2); const int precalced_results[] = { 1145298384, -1710423344, -650981936, 372919464, -1544372176, -426463328, 12124 }; calculateAndCheck(__LINE__, precalced_results); } void tst_QHeaderView::visualIndexAtTest() { additionalInit(); view->swapSections(4, 9); // Make sure that visual and logical Indexes are not just the same. int check1 = 0; int check2 = 0; for (int u = 0; u < model->rowCount(); ++u) { view->resizeSection(u, 3 + u % 17); int v = view->visualIndexAt(u * 29); check1 += v; check2 += u * v; } view->resizeSection(1, 0); // Make sure that we have a 0 size section - before the result set view->setSectionHidden(5, true); // Make sure we have a real hidden section before result set //qDebug() << "visualIndexAtTest" << check1 << check2; const int precalced_check1 = 72665; const int precalced_check2 = 14015890; QCOMPARE(precalced_check1, check1); QCOMPARE(precalced_check2, check2); const int precalced_results[] = { 1145298384, -1710423344, -1457520212, 169223959, 557466160, -324939600, 5453 }; calculateAndCheck(__LINE__, precalced_results); } void tst_QHeaderView::hideShowTest() { additionalInit(); for (int u = model->rowCount(); u >= 0; --u) if (u % 8 != 0) view->hideSection(u); for (int u = model->rowCount(); u >= 0; --u) if (u % 3 == 0) view->showSection(u); view->setSectionHidden(model->rowCount(), true); // invalid hide (should be ignored) view->setSectionHidden(-1, true); // invalid hide (should be ignored) const int precalced_results[] = { -1523279360, -1523279360, -1321506816, 2105322423, 1879611280, 1879611280, 5225 }; calculateAndCheck(__LINE__, precalced_results); } void tst_QHeaderView::swapSectionsTest() { additionalInit(); for (int u = 0; u < rowcount / 2; ++u) view->swapSections(u, rowcount - u - 1); for (int u = 0; u < rowcount; u += 2) view->swapSections(u, u + 1); view->swapSections(0, model->rowCount()); // invalid swapsection (should be ignored) const int precalced_results[] = { -1536450048, -1774641430, -1347156568, 1, 1719705216, -240077576, 12500 }; calculateAndCheck(__LINE__, precalced_results); } void tst_QHeaderView::moveSectionTest() { additionalInit(); for (int u = 1; u < 5; ++u) view->moveSection(u, model->rowCount() - u); view->moveSection(2, model->rowCount() / 2); view->moveSection(0, 10); view->moveSection(0, model->rowCount() - 10); view->moveSection(0, model->rowCount()); // invalid move (should be ignored) const int precalced_results[] = { 645125952, 577086896, -1347156568, 1, 1719705216, 709383416, 12500 }; calculateAndCheck(__LINE__, precalced_results); } void tst_QHeaderView::defaultSizeTest() { additionalInit(); view->hideSection(rowcount / 2); int restore_to = view->defaultSectionSize(); view->setDefaultSectionSize(restore_to + 5); const int precalced_results[] = { -1523279360, -1523279360, -1739688320, -1023807777, 997629696, 997629696, 14970 }; calculateAndCheck(__LINE__, precalced_results); view->setDefaultSectionSize(restore_to); } void tst_QHeaderView::removeTest() { additionalInit(); view->swapSections(0, 5); model->removeRows(0, 1); // remove one row model->removeRows(4, 10); model->setRowCount(model->rowCount() / 2 - 1); if (m_using_reset_model) { const int precalced_results[] = { 1741224292, -135269187, -569519837, 1, 1719705216, -1184395000, 6075 }; calculateAndCheck(__LINE__, precalced_results); } else { const int precalced_results[] = { 289162397, 289162397, -569519837, 1, 1719705216, 1719705216, 6075 }; calculateAndCheck(__LINE__, precalced_results); } } void tst_QHeaderView::insertTest() { additionalInit(); view->swapSections(0, model->rowCount() - 1); model->insertRows(0, 1); // insert one row model->insertRows(4, 10); model->setRowCount(model->rowCount() * 2 - 1); if (m_using_reset_model) { const int precalced_results[] = { 2040508069, -1280266538, -150350734, 1, 1719705216, 1331312784, 25525 }; calculateAndCheck(__LINE__, precalced_results); } else { const int precalced_results[] = { -1909447021, 339092083, -150350734, 1, 1719705216, -969712728, 25525 }; calculateAndCheck(__LINE__, precalced_results); } } void tst_QHeaderView::mixedTests() { additionalInit(); model->setRowCount(model->rowCount() + 10); for (int u = 0; u < model->rowCount(); u += 2) view->swapSections(u, u + 1); view->moveSection(0, 5); for (int u = model->rowCount(); u >= 0; --u) { if (u % 5 != 0) { view->hideSection(u); QVERIFY(view->isSectionHidden(u)); } if (u % 3 != 0) { view->showSection(u); QVERIFY(!view->isSectionHidden(u)); } } model->insertRows(3, 7); model->removeRows(8, 3); model->setRowCount(model->rowCount() - 10); // the upper is not visible (when m_using_reset_model is true) // the lower 11 are modified due to insert/removeRows for (int u = model->rowCount() - 1; u >= 11; --u) { // when using reset, the hidden rows will *not* move const int calcMod = m_using_reset_model ? u : u - 4; // 7 added, 3 removed if (calcMod % 5 != 0 && calcMod % 3 == 0) { QVERIFY(view->isSectionHidden(u)); } if (calcMod % 3 != 0) { QVERIFY(!view->isSectionHidden(u)); } } if (m_using_reset_model) { const int precalced_results[] = { 898296472, 337096378, -543340640, -1964432121, -1251526424, -568618976, 9250 }; calculateAndCheck(__LINE__, precalced_results); } else { const int precalced_results[] = { 1911338224, 1693514365, -613398968, -1912534953, 1582159424, -1851079000, 9300 }; calculateAndCheck(__LINE__, precalced_results); } } void tst_QHeaderView::resizeToContentTest() { additionalInit(); QModelIndex idx = model->index(2, 2); model->setData(idx, QVariant("A normal string")); idx = model->index(1, 3); model->setData(idx, QVariant("A normal longer string to test resize")); QHeaderView *hh = m_tableview->horizontalHeader(); hh->resizeSections(QHeaderView::ResizeToContents); QVERIFY(hh->sectionSize(3) > hh->sectionSize(2)); for (int u = 0; u < 10; ++u) view->resizeSection(u, 1); view->resizeSections(QHeaderView::ResizeToContents); QVERIFY(view->sectionSize(1) > 1); QVERIFY(view->sectionSize(2) > 1); // Check minimum section size hh->setMinimumSectionSize(150); model->setData(idx, QVariant("i")); hh->resizeSections(QHeaderView::ResizeToContents); QCOMPARE(hh->sectionSize(3), 150); hh->setMinimumSectionSize(-1); // Check maximumSection size hh->setMaximumSectionSize(200); model->setData(idx, QVariant("This is a even longer string that is expected to be more than 200 pixels")); hh->resizeSections(QHeaderView::ResizeToContents); QCOMPARE(hh->sectionSize(3), 200); hh->setMaximumSectionSize(-1); view->setDefaultSectionSize(25); // To make sure our precalced data are correct. We do not know font height etc. const int precalced_results[] = { -1523279360, -1523279360, -1347156568, 1, 1719705216, 1719705216, 12500 }; calculateAndCheck(__LINE__, precalced_results); } void tst_QHeaderView::testStreamWithHide() { #ifndef QT_NO_DATASTREAM m_tableview->setVerticalHeader(view); m_tableview->setModel(model); view->setDefaultSectionSize(25); view->hideSection(2); view->swapSections(1, 2); QByteArray s = view->saveState(); view->swapSections(1, 2); view->setDefaultSectionSize(30); // To make sure our precalced data are correct. view->restoreState(s); const int precalced_results[] = { -1116614432, -1528653200, -1914165644, 244434607, -1111214068, 750357900, 75}; calculateAndCheck(__LINE__, precalced_results); #else QSKIP("Datastream required for testStreamWithHide. Skipping this test."); #endif } void tst_QHeaderView::testStylePosition() { topLevel->show(); QVERIFY(QTest::qWaitForWindowExposed(topLevel)); protected_QHeaderView *header = static_cast(view); TestStyle proxy; header->setStyle(&proxy); QImage image(1, 1, QImage::Format_ARGB32); QPainter p(&image); // 0, 1, 2, 3 header->paintSection(&p, view->rect(), 0); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::Beginning); header->paintSection(&p, view->rect(), 1); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::Middle); header->paintSection(&p, view->rect(), 2); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::Middle); header->paintSection(&p, view->rect(), 3); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::End); // (0),2,1,3 view->setSectionHidden(0, true); view->swapSections(1, 2); header->paintSection(&p, view->rect(), 1); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::Middle); header->paintSection(&p, view->rect(), 2); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::Beginning); header->paintSection(&p, view->rect(), 3); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::End); // (1),2,0,(3) view->setSectionHidden(3, true); view->setSectionHidden(0, false); view->setSectionHidden(1, true); view->swapSections(0, 1); header->paintSection(&p, view->rect(), 0); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::End); header->paintSection(&p, view->rect(), 2); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::Beginning); // (1),2,(0),(3) view->setSectionHidden(0, true); header->paintSection(&p, view->rect(), 2); QCOMPARE(proxy.lastPosition, QStyleOptionHeader::OnlyOneSection); } void tst_QHeaderView::sizeHintCrash() { QTreeView treeView; QStandardItemModel *model = new QStandardItemModel(&treeView); model->appendRow(new QStandardItem("QTBUG-48543")); treeView.setModel(model); treeView.header()->sizeHintForColumn(0); treeView.header()->sizeHintForRow(0); } void tst_QHeaderView::stretchAndRestoreLastSection() { QStandardItemModel m(10, 10); QTableView tv; tv.setModel(&m); tv.showMaximized(); const int minimumSectionSize = 20; const int defaultSectionSize = 30; const int someOtherSectionSize = 40; const int biggerSizeThanAnySection = 50; QVERIFY(QTest::qWaitForWindowActive(&tv)); QHeaderView &header = *tv.horizontalHeader(); // set minimum size before resizeSections() is called // which is done inside setStretchLastSection header.setMinimumSectionSize(minimumSectionSize); header.setDefaultSectionSize(defaultSectionSize); header.resizeSection(9, someOtherSectionSize); header.setStretchLastSection(true); // Default last section is larger QCOMPARE(header.sectionSize(8), defaultSectionSize); QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); // Moving last section away (restore old last section 9 - and make 8 larger) header.swapSections(9, 8); QCOMPARE(header.sectionSize(9), someOtherSectionSize); QVERIFY(header.sectionSize(8) >= biggerSizeThanAnySection); // Make section 9 the large one again header.hideSection(8); QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); // Show section 8 again - and make that one the last one. header.showSection(8); QVERIFY(header.sectionSize(8) > biggerSizeThanAnySection); QCOMPARE(header.sectionSize(9), someOtherSectionSize); // Swap the sections so the logical indexes are equal to visible indexes again. header.moveSection(9, 8); QCOMPARE(header.sectionSize(8), defaultSectionSize); QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); // Append sections m.setColumnCount(15); QCOMPARE(header.sectionSize(9), someOtherSectionSize); QVERIFY(header.sectionSize(14) >= biggerSizeThanAnySection); // Truncate sections (remove sections with the last section) m.setColumnCount(10); QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); for (int u = 0; u < 9; ++u) QCOMPARE(header.sectionSize(u), defaultSectionSize); // Insert sections m.insertColumns(2, 2); QCOMPARE(header.sectionSize(9), defaultSectionSize); QCOMPARE(header.sectionSize(10), defaultSectionSize); QVERIFY(header.sectionSize(11) >= biggerSizeThanAnySection); // Append an extra section and check restore m.setColumnCount(m.columnCount() + 1); QCOMPARE(header.sectionSize(11), someOtherSectionSize); QVERIFY(header.sectionSize(12) >= biggerSizeThanAnySection); // Remove some sections but not the last one. m.removeColumns(2, 2); QCOMPARE(header.sectionSize(9), someOtherSectionSize); QVERIFY(header.sectionSize(10) >= biggerSizeThanAnySection); for (int u = 0; u < 9; ++u) QCOMPARE(header.sectionSize(u), defaultSectionSize); // Empty the header and start over with some more tests m.setColumnCount(0); m.setColumnCount(10); QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); // Check resize of the last section header.resizeSection(9, someOtherSectionSize); QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); // It should still be stretched header.swapSections(9, 8); QCOMPARE(header.sectionSize(9), someOtherSectionSize); // Restore the order header.swapSections(9, 8); QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); // Hide the last 3 sections and test stretch last section on swap/move // when hidden sections with a larger visual index exists. header.hideSection(7); header.hideSection(8); header.hideSection(9); QVERIFY(header.sectionSize(6) >= biggerSizeThanAnySection); header.moveSection(2, 7); QVERIFY(header.sectionSize(2) >= biggerSizeThanAnySection); header.swapSections(1, 8); QCOMPARE(header.sectionSize(2), defaultSectionSize); QVERIFY(header.sectionSize(1) >= biggerSizeThanAnySection); // Inserting sections 2 m.setColumnCount(0); m.setColumnCount(10); header.resizeSection(1, someOtherSectionSize); header.swapSections(1, 9); m.insertColumns(9, 2); header.swapSections(1, 11); QCOMPARE(header.sectionSize(1), someOtherSectionSize); // Clear and re-add. This triggers a different code path than seColumnCount(0) m.clear(); m.setColumnCount(3); QVERIFY(header.sectionSize(2) >= biggerSizeThanAnySection); // Test import/export of the original (not stretched) sectionSize. m.setColumnCount(0); m.setColumnCount(10); header.resizeSection(9, someOtherSectionSize); QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); QByteArray b = header.saveState(); m.setColumnCount(0); m.setColumnCount(10); header.restoreState(b); header.setStretchLastSection(false); QCOMPARE(header.sectionSize(9), someOtherSectionSize); } void tst_QHeaderView::testMinMaxSectionSize_data() { QTest::addColumn("stretchLastSection"); QTest::addRow("stretched") << true; QTest::addRow("not stretched") << false; } void tst_QHeaderView::testMinMaxSectionSize() { QFETCH(bool, stretchLastSection); QStandardItemModel m(5, 5); QTableView tv; tv.setModel(&m); tv.show(); const int sectionSizeMin = 20; const int sectionSizeMax = 40; const int defaultSectionSize = 30; QVERIFY(QTest::qWaitForWindowExposed(&tv)); QHeaderView &header = *tv.horizontalHeader(); header.setMinimumSectionSize(sectionSizeMin); header.setMaximumSectionSize(sectionSizeMax); // check bounds for default section size header.setDefaultSectionSize(sectionSizeMin - 1); QCOMPARE(header.defaultSectionSize(), sectionSizeMin); header.setDefaultSectionSize(sectionSizeMax + 1); QCOMPARE(header.defaultSectionSize(), sectionSizeMax); header.setDefaultSectionSize(defaultSectionSize); QCOMPARE(header.defaultSectionSize(), defaultSectionSize); header.setStretchLastSection(stretchLastSection); QCOMPARE(header.stretchLastSection(), stretchLastSection); // check defaults QCOMPARE(header.sectionSize(0), defaultSectionSize); QCOMPARE(header.sectionSize(3), defaultSectionSize); // do not go above maxSectionSize header.resizeSection(0, sectionSizeMax + 1); QCOMPARE(header.sectionSize(0), sectionSizeMax); // do not go below minSectionSize header.resizeSection(0, sectionSizeMin - 1); QCOMPARE(header.sectionSize(0), sectionSizeMin); // change section size on max change header.setMinimumSectionSize(sectionSizeMin); header.setMaximumSectionSize(sectionSizeMax); header.resizeSection(0, sectionSizeMax); QCOMPARE(header.sectionSize(0), sectionSizeMax); header.setMaximumSectionSize(defaultSectionSize); QTRY_COMPARE(header.sectionSize(0), defaultSectionSize); // change section size on min change header.setMinimumSectionSize(sectionSizeMin); header.setMaximumSectionSize(sectionSizeMax); header.resizeSection(0, sectionSizeMin); QCOMPARE(header.sectionSize(0), sectionSizeMin); header.setMinimumSectionSize(defaultSectionSize); QTRY_COMPARE(header.sectionSize(0), defaultSectionSize); } void tst_QHeaderView::testResetCachedSizeHint() { QtTestModel model(10, 10); QTableView tv; tv.setModel(&model); tv.show(); QVERIFY(QTest::qWaitForWindowExposed(&tv)); QSize s1 = tv.horizontalHeader()->sizeHint(); model.setMultiLineHeader(true); QSize s2 = tv.horizontalHeader()->sizeHint(); model.setMultiLineHeader(false); QSize s3 = tv.horizontalHeader()->sizeHint(); QCOMPARE(s1, s3); QVERIFY(s1 != s2); } class StatusTipHeaderView : public QHeaderView { Q_OBJECT public: using QHeaderView::QHeaderView; QString statusTipText; bool gotStatusTipEvent = false; protected: bool event(QEvent *e) override { if (e->type() == QEvent::StatusTip) { gotStatusTipEvent = true; statusTipText = static_cast(e)->tip(); } return QHeaderView::event(e); } }; void tst_QHeaderView::statusTips() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); StatusTipHeaderView headerView(Qt::Horizontal); QtTestModel model(5, 5); headerView.setModel(&model); headerView.viewport()->setMouseTracking(true); headerView.setGeometry(QRect(QPoint(QGuiApplication::primaryScreen()->geometry().center() - QPoint(250, 250)), QSize(500, 500))); headerView.show(); QApplicationPrivate::setActiveWindow(&headerView); QVERIFY(QTest::qWaitForWindowActive(&headerView)); // Ensure it is moved away first and then moved to the relevant section QTest::mouseMove(&headerView, headerView.rect().bottomLeft() + QPoint(20, 20)); QPoint centerPoint = QRect(headerView.sectionPosition(0), 0, headerView.sectionSize(0), headerView.height()).center(); QTest::mouseMove(headerView.windowHandle(), centerPoint); QTRY_VERIFY(headerView.gotStatusTipEvent); QCOMPARE(headerView.statusTipText, QLatin1String("[0,0,0] -- Header")); headerView.gotStatusTipEvent = false; headerView.statusTipText.clear(); centerPoint = QRect(headerView.sectionPosition(1), 0, headerView.sectionSize(1), headerView.height()).center(); QTest::mouseMove(headerView.windowHandle(), centerPoint); QTRY_VERIFY(headerView.gotStatusTipEvent); QCOMPARE(headerView.statusTipText, QLatin1String("[0,1,0] -- Header")); } void tst_QHeaderView::testRemovingColumnsViaLayoutChanged() { const int persistentSectionSize = 101; QtTestModel model(5, 5); view->setModel(&model); for (int i = 0; i < model.cols; ++i) view->resizeSection(i, persistentSectionSize + i); model.cleanup(); // down to 3 via layoutChanged (not columnsRemoved) for (int j = 0; j < model.cols; ++j) QCOMPARE(view->sectionSize(j), persistentSectionSize + j); // The main point of this test is that the section-size restoring code didn't go out of bounds. } void tst_QHeaderView::testModelMovingColumns() { QtTestModel model(10, 10); QHeaderView hv(Qt::Horizontal); hv.setModel(&model); hv.resizeSections(QHeaderView::ResizeToContents); hv.show(); QPersistentModelIndex index3 = model.index(0, 3); model.moveColumn(3, 1); QCOMPARE(index3.column(), 1); } QTEST_MAIN(tst_QHeaderView) #include "tst_qheaderview.moc"