// 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 <QHeaderView>
#include <QProxyStyle>
#include <QSignalSpy>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QStringListModel>
#include <QTableView>
#include <QTest>
#include <QTreeWidget>
#include <QtWidgets/private/qheaderview_p.h>
#include <QtWidgets/private/qapplication_p.h>

using BoolList = QList<bool>;
using IntList = QList<int>;
using ResizeVec = QList<QHeaderView::ResizeMode>;

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<const QStyleOptionHeader *>(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<int>::min());
    QCOMPARE(std::numeric_limits<int>::min(), obj1.offset());
    obj1.setOffset(std::numeric_limits<int>::max());
    QCOMPARE(std::numeric_limits<int>::max(), obj1.offset());

}

tst_QHeaderView::tst_QHeaderView()
{
    qRegisterMetaType<Qt::SortOrder>("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<IntList>("boundsCheck");
    QTest::addColumn<IntList>("defaultSizes");
    QTest::addColumn<int>("initialDefaultSize");
    QTest::addColumn<int>("lastVisibleSectionSize");
    QTest::addColumn<int>("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<IntList>("hidden");
    QTest::addColumn<IntList>("from");
    QTest::addColumn<IntList>("to");
    QTest::addColumn<IntList>("coordinate");
    QTest::addColumn<IntList>("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<IntList>("hidden");
    QTest::addColumn<IntList>("from");
    QTest::addColumn<IntList>("to");
    QTest::addColumn<BoolList>("moved");
    QTest::addColumn<IntList>("logical");
    QTest::addColumn<int>("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<IntList>("logicalIndexes");
    QTest::addColumn<IntList>("sizes");
    QTest::addColumn<int>("logicalFrom");
    QTest::addColumn<int>("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<int>("section");
    QTest::addColumn<int>("initialSize");
    QTest::addColumn<int>("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<int>("section");
    QTest::addColumn<int>("size");
    QTest::addColumn<int>("insert");
    QTest::addColumn<int>("compare");
    QTest::addColumn<int>("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<int>("size");
    QTest::addColumn<IntList>("sections");
    QTest::addColumn<ResizeVec>("modes");
    QTest::addColumn<IntList>("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<int>("from");
    QTest::addColumn<int>("to");
    QTest::addColumn<int>("insert");
    QTest::addColumn<IntList>("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<int>("initial");
    QTest::addColumn<IntList>("logical");
    QTest::addColumn<IntList>("size");
    QTest::addColumn<ResizeVec>("mode");
    QTest::addColumn<int>("resized");
    QTest::addColumn<IntList>("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<Qt::Orientation>("direction");
    QTest::addColumn<Qt::Alignment>("initial");
    QTest::addColumn<Qt::Alignment>("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<Qt::Orientation>("direction");
    QTest::addColumn<QHeaderView::ResizeMode>("mode");
    QTest::addColumn<int>("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<Qt::Orientation>("direction");
    QTest::addColumn<bool>("clickable");
    QTest::addColumn<int>("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<Qt::Orientation>("direction");
    QTest::addColumn<int>("oldDefaultSize");
    QTest::addColumn<int>("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<Qt::Orientation>("direction");
    QTest::addColumn<int>("hide");
    QTest::addColumn<int>("insert");
    QTest::addColumn<int>("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<IntList>("hide1");
    QTest::addColumn<IntList>("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<QStandardItem *> 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<char> 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<char> 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<bool>("updates_enabled");
    QTest::addColumn<bool>("special_prepare");
    QTest::addColumn<bool>("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<protected_QHeaderView *>(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<bool>("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<QStatusTipEvent *>(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"