/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/


#include <QtTest/QtTest>
#include <qeventloop.h>
#include <qlist.h>
#include <qpair.h>
#include <qheaderview.h>
#include <qlineedit.h>

#include <qtablewidget.h>

class QObjectTableItem : public QObject, public QTableWidgetItem
{
    Q_OBJECT
};

class tst_QTableWidget : public QObject
{
    Q_OBJECT

public:
    tst_QTableWidget();

private slots:
    void initTestCase();
    void cleanupTestCase();
    void init();
    void getSetCheck();
    void clear();
    void clearContents();
    void rowCount();
    void columnCount();
    void itemAssignment();
    void item_data();
    void item();
    void takeItem_data();
    void takeItem();
    void selectedItems_data();
    void selectedItems();
    void removeRow_data();
    void removeRow();
    void removeColumn_data();
    void removeColumn();
    void insertRow_data();
    void insertRow();
    void insertColumn_data();
    void insertColumn();
    void itemStreaming_data();
    void itemStreaming();
    void itemOwnership();
    void sortItems_data();
    void sortItems();
    void setItemWithSorting_data();
    void setItemWithSorting();
    void itemData();
    void setItemData();
    void cellWidget();
    void cellWidgetGeometry();
    void sizeHint_data();
    void sizeHint();
    void task231094();
    void task219380_removeLastRow();
    void task262056_sortDuplicate();
    void itemWithHeaderItems();
    void mimeData();
    void selectedRowAfterSorting();
    void search();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    void clearItemData();
#endif

private:
    QTableWidget *testWidget;
};

typedef QPair<int, int> IntPair;
typedef QList<int> IntList;
typedef QList<IntPair> IntIntList;

Q_DECLARE_METATYPE(QTableWidgetSelectionRange)


// Testing get/set functions
void tst_QTableWidget::getSetCheck()
{
    QTableWidget obj1;
    // int QTableWidget::rowCount()
    // void QTableWidget::setRowCount(int)
    obj1.setRowCount(0);
    QCOMPARE(0, obj1.rowCount());
    obj1.setRowCount(INT_MIN);
    QCOMPARE(0, obj1.rowCount()); // Row count can never be negative
//    obj1.setRowCount(INT_MAX);
//    QCOMPARE(INT_MAX, obj1.rowCount());
    obj1.setRowCount(100);
    QCOMPARE(100, obj1.rowCount());


    // int QTableWidget::columnCount()
    // void QTableWidget::setColumnCount(int)
    obj1.setColumnCount(0);
    QCOMPARE(0, obj1.columnCount());
    obj1.setColumnCount(INT_MIN);
    QCOMPARE(0, obj1.columnCount()); // Column count can never be negative
    obj1.setColumnCount(1000);
    QCOMPARE(1000, obj1.columnCount());
//    obj1.setColumnCount(INT_MAX);
//    QCOMPARE(INT_MAX, obj1.columnCount());

    // QTableWidgetItem * QTableWidget::currentItem()
    // void QTableWidget::setCurrentItem(QTableWidgetItem *)
    QTableWidgetItem *var3 = new QTableWidgetItem("0,0");
    obj1.setItem(0, 0, var3);
    obj1.setItem(1, 1, new QTableWidgetItem("1,1"));
    obj1.setItem(2, 2, new QTableWidgetItem("2,2"));
    obj1.setItem(3, 3, new QTableWidgetItem("3,3"));
    obj1.setCurrentItem(var3);
    QCOMPARE(var3, obj1.currentItem());
    obj1.setCurrentItem((QTableWidgetItem *)0);
    QCOMPARE((QTableWidgetItem *)0, obj1.currentItem());
    obj1.setItem(0, 0, 0);
    QCOMPARE((QTableWidgetItem *)0, obj1.item(0, 0));

    // const QTableWidgetItem * QTableWidget::itemPrototype()
    // void QTableWidget::setItemPrototype(const QTableWidgetItem *)
    const QTableWidgetItem *var4 = new QTableWidgetItem;
    obj1.setItemPrototype(var4);
    QCOMPARE(var4, obj1.itemPrototype());
    obj1.setItemPrototype((QTableWidgetItem *)0);
    QCOMPARE((const QTableWidgetItem *)0, obj1.itemPrototype());
}

tst_QTableWidget::tst_QTableWidget(): testWidget(0)
{
}

void tst_QTableWidget::initTestCase()
{
    testWidget = new QTableWidget();
    testWidget->show();
    QApplication::setKeyboardInputInterval(100);
}

void tst_QTableWidget::cleanupTestCase()
{
    delete testWidget;
}

void tst_QTableWidget::init()
{
    testWidget->clear();
    testWidget->setRowCount(5);
    testWidget->setColumnCount(5);

    for (int row=0; row < testWidget->rowCount(); ++row)
        testWidget->showRow(row);
    for (int column=0; column < testWidget->columnCount(); ++column)
        testWidget->showColumn(column);
}

void tst_QTableWidget::clearContents()
{
    QTableWidgetItem *item = new QTableWidgetItem("test");
    testWidget->setHorizontalHeaderItem(0, item);
    QCOMPARE(testWidget->horizontalHeaderItem(0), item);
    testWidget->clearContents();
    QCOMPARE(testWidget->horizontalHeaderItem(0), item);
}

void tst_QTableWidget::clear()
{
    QTableWidgetItem *item = new QTableWidgetItem("foo");
    testWidget->setItem(0, 0, item);
    item->setSelected(true);

    QVERIFY(testWidget->item(0, 0) == item);
    QVERIFY(item->isSelected());


    QPointer<QObjectTableItem> bla = new QObjectTableItem();
    testWidget->setItem(1, 1, bla);

    testWidget->clear();

    QVERIFY(bla.isNull());

    QVERIFY(!testWidget->item(0,0));
    QVERIFY(!testWidget->selectedRanges().count());
    QVERIFY(!testWidget->selectedItems().count());
}

void tst_QTableWidget::rowCount()
{
    int rowCountBefore = 5;
    int rowCountAfter = 10;

    int rowCount = testWidget->rowCount();
    QCOMPARE(rowCount, rowCountBefore);

    testWidget->setRowCount(rowCountAfter);
    rowCount = testWidget->rowCount();
    QCOMPARE(rowCount, rowCountAfter);

    QPersistentModelIndex index(testWidget->model()->index(rowCountAfter - 1, 0,
                                                           testWidget->rootIndex()));
    QCOMPARE(index.row(), rowCountAfter - 1);
    QCOMPARE(index.column(), 0);
    QVERIFY(index.isValid());
    testWidget->setRowCount(rowCountBefore);
    QCOMPARE(index.row(), -1);
    QCOMPARE(index.column(), -1);
    QVERIFY(!index.isValid());

    rowCountBefore = testWidget->rowCount();
    testWidget->setRowCount(-1);
    QCOMPARE(testWidget->rowCount(), rowCountBefore);
}

void tst_QTableWidget::columnCount()
{
    int columnCountBefore = 5;
    int columnCountAfter = 10;

    int columnCount = testWidget->columnCount();
    QCOMPARE(columnCount, columnCountBefore);

    testWidget->setColumnCount(columnCountAfter);
    columnCount = testWidget->columnCount();
    QCOMPARE(columnCount, columnCountAfter);

    QPersistentModelIndex index(testWidget->model()->index(0, columnCountAfter - 1,
                                                           testWidget->rootIndex()));
    QCOMPARE(index.row(), 0);
    QCOMPARE(index.column(), columnCountAfter - 1);
    QVERIFY(index.isValid());
    testWidget->setColumnCount(columnCountBefore);
    QCOMPARE(index.row(), -1);
    QCOMPARE(index.column(), -1);
    QVERIFY(!index.isValid());

    columnCountBefore = testWidget->columnCount();
    testWidget->setColumnCount(-1);
    QCOMPARE(testWidget->columnCount(), columnCountBefore);
}

void tst_QTableWidget::itemAssignment()
{
    QTableWidgetItem itemInWidget("inWidget");
    testWidget->setItem(0, 0, &itemInWidget);
    itemInWidget.setFlags(itemInWidget.flags() | Qt::ItemIsUserTristate);
    QTableWidgetItem itemOutsideWidget("outsideWidget");

    QVERIFY(itemInWidget.tableWidget());
    QCOMPARE(itemInWidget.text(), QString("inWidget"));
    QVERIFY(itemInWidget.flags() & Qt::ItemIsUserTristate);

    QVERIFY(!itemOutsideWidget.tableWidget());
    QCOMPARE(itemOutsideWidget.text(), QString("outsideWidget"));
    QVERIFY(!(itemOutsideWidget.flags() & Qt::ItemIsUserTristate));

    itemOutsideWidget = itemInWidget;
    QVERIFY(!itemOutsideWidget.tableWidget());
    QCOMPARE(itemOutsideWidget.text(), QString("inWidget"));
    QVERIFY(itemOutsideWidget.flags() & Qt::ItemIsUserTristate);
}

void tst_QTableWidget::item_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<int>("row");
    QTest::addColumn<int>("column");
    QTest::addColumn<bool>("expectItem");

    QTest::newRow("0x0 take [0,0]") << 0 << 0 << 0 << 0 << false;
    QTest::newRow("0x0 take [4,4]") << 0 << 0 << 4 << 4 << false;
    QTest::newRow("4x4 take [0,0]") << 4 << 4 << 0 << 0 << true;
    QTest::newRow("4x4 take [4,4]") << 4 << 4 << 4 << 4 << false;
    QTest::newRow("4x4 take [2,2]") << 4 << 4 << 2 << 2 << true;
}

void tst_QTableWidget::item()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(int, row);
    QFETCH(int, column);
    QFETCH(bool, expectItem);

    testWidget->setRowCount(rowCount);
    testWidget->setColumnCount(columnCount);
    QCOMPARE(testWidget->rowCount(), rowCount);
    QCOMPARE(testWidget->columnCount(), columnCount);

    for (int r = 0; r < testWidget->rowCount(); ++r)
        for (int c = 0; c < testWidget->columnCount(); ++c)
            testWidget->setItem(r, c, new QTableWidgetItem(QString::number(r * c + c)));

    for (int r = 0; r < testWidget->rowCount(); ++r)
        for (int c = 0; c < testWidget->columnCount(); ++c)
            QCOMPARE(testWidget->item(r, c)->text(), QString::number(r * c + c));

    QTableWidgetItem *item = testWidget->item(row, column);
    QCOMPARE(!!item, expectItem);
    if (expectItem)
        QCOMPARE(item->text(), QString::number(row * column + column));
}

void tst_QTableWidget::takeItem_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<int>("row");
    QTest::addColumn<int>("column");
    QTest::addColumn<bool>("expectItem");

    QTest::newRow("0x0 take [0,0]") << 0 << 0 << 0 << 0 << false;
    QTest::newRow("0x0 take [4,4]") << 0 << 0 << 4 << 4 << false;
    QTest::newRow("4x4 take [0,0]") << 4 << 4 << 0 << 0 << true;
    QTest::newRow("4x4 take [4,4]") << 4 << 4 << 4 << 4 << false;
    QTest::newRow("4x4 take [2,2]") << 4 << 4 << 2 << 2 << true;
}

void tst_QTableWidget::takeItem()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(int, row);
    QFETCH(int, column);
    QFETCH(bool, expectItem);

    testWidget->setRowCount(rowCount);
    testWidget->setColumnCount(columnCount);
    QCOMPARE(testWidget->rowCount(), rowCount);
    QCOMPARE(testWidget->columnCount(), columnCount);

    for (int r = 0; r < testWidget->rowCount(); ++r)
        for (int c = 0; c < testWidget->columnCount(); ++c)
            testWidget->setItem(r, c, new QTableWidgetItem(QString::number(r * c + c)));

    for (int r = 0; r < testWidget->rowCount(); ++r)
        for (int c = 0; c < testWidget->columnCount(); ++c)
            QCOMPARE(testWidget->item(r, c)->text(), QString::number(r * c + c));

    QSignalSpy spy(testWidget, &QTableWidget::cellChanged);
    QTableWidgetItem *item = testWidget->takeItem(row, column);
    QCOMPARE(!!item, expectItem);
    if (expectItem) {
        QCOMPARE(item->text(), QString::number(row * column + column));
        delete item;

        QTRY_COMPARE(spy.count(), 1);
        const QList<QVariant> arguments = spy.takeFirst();
        QCOMPARE(arguments.size(), 2);
        QCOMPARE(arguments.at(0).toInt(), row);
        QCOMPARE(arguments.at(1).toInt(), column);
    }
    QVERIFY(!testWidget->takeItem(row, column));
}

void tst_QTableWidget::selectedItems_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<IntIntList>("createItems");
    QTest::addColumn<IntList>("hiddenRows");
    QTest::addColumn<IntList>("hiddenColumns");
    QTest::addColumn<QTableWidgetSelectionRange>("selectionRange");
    QTest::addColumn<IntIntList>("expectedItems");

    QTest::newRow("3x3 empty cells, no hidden rows/columns, none selected")
        << 3 << 3
        << IntIntList()
        << IntList()
        << IntList()
        << QTableWidgetSelectionRange()
        << IntIntList();

    QTest::newRow("3x3 empty cells,no hidden rows/columnms, all selected")
        << 3 << 3
        << IntIntList()
        << IntList()
        << IntList()
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << IntIntList();

    QTest::newRow("3x3 (1,1) exists, no hidden rows/columnms, all selected")
        << 3 << 3
        << (IntIntList() << IntPair(1,1))
        << IntList()
        << IntList()
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << (IntIntList() << IntPair(1,1));

    QTest::newRow("3x3 (1,1) exists, row 1 hidden, all selected")
        << 3 << 3
        << (IntIntList() << IntPair(1,1))
        << (IntList() << 1)
        << IntList()
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << IntIntList();

    QTest::newRow("3x3 (1,1) exists, column 1 hidden, all selected")
        << 3 << 3
        << (IntIntList() << IntPair(1,1))
        << IntList()
        << (IntList() << 1)
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << IntIntList();

    QTest::newRow("3x3 all exists, no hidden rows/columns, all selected")
        << 3 << 3
        << (IntIntList()
            << IntPair(0,0) << IntPair(0,1) << IntPair(0,2)
            << IntPair(1,0) << IntPair(1,1) << IntPair(1,2)
            << IntPair(2,0) << IntPair(2,1) << IntPair(2,2))
        << IntList()
        << IntList()
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << (IntIntList()
            << IntPair(0,0) << IntPair(0,1) << IntPair(0,2)
            << IntPair(1,0) << IntPair(1,1) << IntPair(1,2)
            << IntPair(2,0) << IntPair(2,1) << IntPair(2,2));

    QTest::newRow("3x3 all exists, row 1 hidden, all selected")
        << 3 << 3
        << (IntIntList()
            << IntPair(0,0) << IntPair(0,1) << IntPair(0,2)
            << IntPair(1,0) << IntPair(1,1) << IntPair(1,2)
            << IntPair(2,0) << IntPair(2,1) << IntPair(2,2))
        << (IntList() << 1)
        << IntList()
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << (IntIntList()
            << IntPair(0,0) << IntPair(0,1) << IntPair(0,2)
            << IntPair(2,0) << IntPair(2,1) << IntPair(2,2));

    QTest::newRow("3x3 all exists, column 1 hidden, all selected")
        << 3 << 3
        << (IntIntList()
            << IntPair(0,0) << IntPair(0,1) << IntPair(0,2)
            << IntPair(1,0) << IntPair(1,1) << IntPair(1,2)
            << IntPair(2,0) << IntPair(2,1) << IntPair(2,2))
        << IntList()
        << (IntList() << 1)
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << (IntIntList()
            << IntPair(0,0) << IntPair(0,2)
            << IntPair(1,0) << IntPair(1,2)
            << IntPair(2,0) << IntPair(2,2));

    QTest::newRow("3x3 none exists, no hidden rows/columns, all selected")
        << 3 << 3
        << IntIntList()
        << IntList()
        << IntList()
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << IntIntList();
//         << (IntIntList()
//             << IntPair(0,0) << IntPair(0,1) << IntPair(0,2)
//             << IntPair(1,0) << IntPair(1,1) << IntPair(1,2)
//             << IntPair(2,0) << IntPair(2,1) << IntPair(2,2));

    QTest::newRow("3x3 none exists,  row 1 hidden, all selected, filling empty cells")
        << 3 << 3
        << IntIntList()
        << (IntList() << 1)
        << IntList()
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << IntIntList();
//         << (IntIntList()
//             << IntPair(0,0) << IntPair(0,1) << IntPair(0,2)
//             << IntPair(2,0) << IntPair(2,1) << IntPair(2,2));

    QTest::newRow("3x3 none exists,  column 1 hidden, all selected")
        << 3 << 3
        << IntIntList()
        << IntList()
        << (IntList() << 1)
        << QTableWidgetSelectionRange(0, 0, 2, 2)
        << IntIntList();
//         << (IntIntList()
//             << IntPair(0,0) << IntPair(0,2)
//             << IntPair(1,0) << IntPair(1,2)
//             << IntPair(2,0) << IntPair(2,2));
}

void tst_QTableWidget::selectedItems()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(IntIntList, createItems);
    QFETCH(IntList, hiddenRows);
    QFETCH(IntList, hiddenColumns);
    QFETCH(QTableWidgetSelectionRange, selectionRange);
    QFETCH(IntIntList, expectedItems);

    // set dimensions and test they are ok
    testWidget->setRowCount(rowCount);
    testWidget->setColumnCount(columnCount);
    QCOMPARE(testWidget->rowCount(), rowCount);
    QCOMPARE(testWidget->columnCount(), columnCount);

    // create and set items
    foreach (IntPair intPair, createItems) {
        testWidget->setItem(intPair.first, intPair.second,
                            new QTableWidgetItem(QString("Item %1 %2")
                                                 .arg(intPair.first).arg(intPair.second)));
    }
    // hide rows/columns
    foreach (int row, hiddenRows)
        testWidget->setRowHidden(row, true);
    foreach (int column, hiddenColumns)
        testWidget->setColumnHidden(column, true);

    // make sure we don't have any previous selections hanging around
    QVERIFY(!testWidget->selectedRanges().count());
    QVERIFY(!testWidget->selectedItems().count());

    // select range and check that it is set correctly
    testWidget->setRangeSelected(selectionRange, true);
    if (selectionRange.topRow() >= 0) {
        QCOMPARE(testWidget->selectedRanges().count(), 1);
        QCOMPARE(testWidget->selectedRanges().at(0).topRow(), selectionRange.topRow());
        QCOMPARE(testWidget->selectedRanges().at(0).bottomRow(), selectionRange.bottomRow());
        QCOMPARE(testWidget->selectedRanges().at(0).leftColumn(), selectionRange.leftColumn());
        QCOMPARE(testWidget->selectedRanges().at(0).rightColumn(), selectionRange.rightColumn());
    } else {
        QCOMPARE(testWidget->selectedRanges().count(), 0);
    }

    // check that the correct number of items and the expected items are there
    QList<QTableWidgetItem *> selectedItems = testWidget->selectedItems();
    QCOMPARE(selectedItems.count(), expectedItems.count());
    foreach (IntPair intPair, expectedItems)
        QVERIFY(selectedItems.contains(testWidget->item(intPair.first, intPair.second)));

    // check that setItemSelected agrees with selectedItems
    for (int row = 0; row<testWidget->rowCount(); ++row) {
        bool hidden = false;
        foreach (int hiddenRow, hiddenRows){
            if(hiddenRow == row){
                hidden = true;
                break;
            }
        }
        if (hidden)
            continue;

        for (int column = 0; column<testWidget->columnCount(); ++column) {
            foreach (int hiddenColumn, hiddenColumns){
                if(hiddenColumn == column){
                    hidden = true;
                    break;
                }
            }
            if (hidden)
                continue;

            QTableWidgetItem *item = testWidget->item(row, column);
            if (item && item->isSelected())
                QVERIFY(selectedItems.contains(item));
        }
    }
}

void tst_QTableWidget::removeRow_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<int>("row");
    QTest::addColumn<int>("expectedRowCount");
    QTest::addColumn<int>("expectedColumnCount");

    QTest::newRow("Empty") << 0 << 0 << 0 << 0 << 0;
    QTest::newRow("1x1:0") << 1 << 1 << 0 << 0 << 1;
    QTest::newRow("3x3:0") << 3 << 3 << 0 << 2 << 3;
    QTest::newRow("3x3:1") << 3 << 3 << 1 << 2 << 3;
    QTest::newRow("3x3:2") << 3 << 3 << 2 << 2 << 3;
}

void tst_QTableWidget::removeRow()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(int, row);
    QFETCH(int, expectedRowCount);
    QFETCH(int, expectedColumnCount);

    // set dimensions and test they are ok
    testWidget->setRowCount(rowCount);
    testWidget->setColumnCount(columnCount);
    QCOMPARE(testWidget->rowCount(), rowCount);
    QCOMPARE(testWidget->columnCount(), columnCount);

    // fill table with items
    for (int r = 0; r < rowCount; ++r)
        for (int c = 0; c < columnCount; ++c)
            testWidget->setItem(r, c,
                                new QTableWidgetItem(
                                    QString::number(r) + ":" + QString::number(c)));

    // remove and compare the results
    testWidget->removeRow(row);
    QCOMPARE(testWidget->rowCount(), expectedRowCount);
    QCOMPARE(testWidget->columnCount(), expectedColumnCount);

    // check if the correct items were removed
    for (int r = 0; r < expectedRowCount; ++r)
        for (int c = 0; c < expectedColumnCount; ++c)
            if (r < row)
                QCOMPARE(testWidget->item(r, c)->text(),
                        QString::number(r) + ":" + QString::number(c));
            else
                QCOMPARE(testWidget->item(r, c)->text(),
                        QString::number(r + 1) + ":" + QString::number(c));
}

void tst_QTableWidget::removeColumn_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<int>("column");
    QTest::addColumn<int>("expectedRowCount");
    QTest::addColumn<int>("expectedColumnCount");

    QTest::newRow("Empty") << 0 << 0 << 0 << 0 << 0;
    QTest::newRow("1x1:0") << 1 << 1 << 0 << 1 << 0;
    QTest::newRow("3x3:0") << 3 << 3 << 0 << 3 << 2;
    QTest::newRow("3x3:1") << 3 << 3 << 1 << 3 << 2;
    QTest::newRow("3x3:2") << 3 << 3 << 2 << 3 << 2;
}

void tst_QTableWidget::removeColumn()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(int, column);
    QFETCH(int, expectedRowCount);
    QFETCH(int, expectedColumnCount);

    // set dimensions and test they are ok
    testWidget->setRowCount(rowCount);
    testWidget->setColumnCount(columnCount);
    QCOMPARE(testWidget->rowCount(), rowCount);
    QCOMPARE(testWidget->columnCount(), columnCount);

    // fill table with items
    for (int r = 0; r < rowCount; ++r)
        for (int c = 0; c < columnCount; ++c)
            testWidget->setItem(r, c,
                                new QTableWidgetItem(
                                    QString::number(r) + ":" + QString::number(c)));

    // remove and compare the results
    testWidget->removeColumn(column);
    QCOMPARE(testWidget->rowCount(), expectedRowCount);
    QCOMPARE(testWidget->columnCount(), expectedColumnCount);


    // check if the correct items were removed
    for (int r = 0; r < expectedRowCount; ++r)
        for (int c = 0; c < expectedColumnCount; ++c)
            if (c < column)
                QCOMPARE(testWidget->item(r, c)->text(),
                        QString::number(r) + ":" + QString::number(c));
            else
                QCOMPARE(testWidget->item(r, c)->text(),
                        QString::number(r) + ":" + QString::number(c + 1));
}

void tst_QTableWidget::insertRow_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<int>("row");
    QTest::addColumn<int>("expectedRowCount");
    QTest::addColumn<int>("expectedColumnCount");

    QTest::newRow("Empty")  << 0 << 0 << 0  << 1 << 0;
    QTest::newRow("1x1:0")  << 1 << 1 << 0  << 2 << 1;
    QTest::newRow("3x3:-1") << 3 << 3 << -1 << 3 << 3;
    QTest::newRow("3x3:0")  << 3 << 3 << 0  << 4 << 3;
    QTest::newRow("3x3:1")  << 3 << 3 << 1  << 4 << 3;
    QTest::newRow("3x3:2")  << 3 << 3 << 2  << 4 << 3;
    QTest::newRow("3x3:3")  << 3 << 3 << 3  << 4 << 3;
    QTest::newRow("3x3:4")  << 3 << 3 << 4  << 3 << 3;
}

void tst_QTableWidget::insertRow()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(int, row);
    QFETCH(int, expectedRowCount);
    QFETCH(int, expectedColumnCount);

    // set dimensions and test they are ok
    testWidget->setRowCount(rowCount);
    testWidget->setColumnCount(columnCount);
    QCOMPARE(testWidget->rowCount(), rowCount);
    QCOMPARE(testWidget->columnCount(), columnCount);

    // insert and compare the results
    testWidget->insertRow(row);
    QCOMPARE(testWidget->rowCount(), expectedRowCount);
    QCOMPARE(testWidget->columnCount(), expectedColumnCount);
}

void tst_QTableWidget::insertColumn_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<int>("column");
    QTest::addColumn<int>("expectedRowCount");
    QTest::addColumn<int>("expectedColumnCount");

    QTest::newRow("Empty")  << 0 << 0 << 0  << 0 << 1;
    QTest::newRow("1x1:0")  << 1 << 1 << 0  << 1 << 2;
    QTest::newRow("3x3:-1") << 3 << 3 << -1 << 3 << 3;
    QTest::newRow("3x3:0")  << 3 << 3 << 0  << 3 << 4;
    QTest::newRow("3x3:1")  << 3 << 3 << 1  << 3 << 4;
    QTest::newRow("3x3:2")  << 3 << 3 << 2  << 3 << 4;
    QTest::newRow("3x3:3")  << 3 << 3 << 3  << 3 << 4;
    QTest::newRow("3x3:4")  << 3 << 3 << 4  << 3 << 3;
}

void tst_QTableWidget::insertColumn()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(int, column);
    QFETCH(int, expectedRowCount);
    QFETCH(int, expectedColumnCount);

    // set dimensions and test they are ok
    testWidget->setRowCount(rowCount);
    testWidget->setColumnCount(columnCount);
    QCOMPARE(testWidget->rowCount(), rowCount);
    QCOMPARE(testWidget->columnCount(), columnCount);

    // insert and compare the results
    testWidget->insertColumn(column);
    QCOMPARE(testWidget->rowCount(), expectedRowCount);
    QCOMPARE(testWidget->columnCount(), expectedColumnCount);
}

void tst_QTableWidget::itemStreaming_data()
{
    QTest::addColumn<QString>("text");
    QTest::addColumn<QString>("toolTip");

    QTest::newRow("Data") << "item text" << "tool tip text";
}

void tst_QTableWidget::itemStreaming()
{
    QFETCH(QString, text);
    QFETCH(QString, toolTip);

    QTableWidgetItem item;
    QCOMPARE(item.text(), QString());
    QCOMPARE(item.toolTip(), QString());

    item.setText(text);
    item.setToolTip(toolTip);
    QCOMPARE(item.text(), text);
    QCOMPARE(item.toolTip(), toolTip);

    QByteArray buffer;
    QDataStream out(&buffer, QIODevice::WriteOnly);
    out << item;

    QTableWidgetItem item2;
    QCOMPARE(item2.text(), QString());
    QCOMPARE(item2.toolTip(), QString());

    QVERIFY(!buffer.isEmpty());

    QDataStream in(&buffer, QIODevice::ReadOnly);
    in >> item2;
    QCOMPARE(item2.text(), text);
    QCOMPARE(item2.toolTip(), toolTip);
}

void tst_QTableWidget::itemOwnership()
{
    QPointer<QObjectTableItem> item;
    QPointer<QObjectTableItem> headerItem;

    //delete from outside
    item = new QObjectTableItem();
    testWidget->setItem(0, 0, item);
    delete item;
    QCOMPARE(testWidget->item(0, 0), (QTableWidgetItem *)0);

    //delete vertical headeritem from outside
    headerItem = new QObjectTableItem();
    testWidget->setVerticalHeaderItem(0, headerItem);
    delete headerItem;
    QCOMPARE(testWidget->verticalHeaderItem(0), nullptr);

    //delete horizontal headeritem from outside
    headerItem = new QObjectTableItem();
    testWidget->setHorizontalHeaderItem(0, headerItem);
    delete headerItem;
    QCOMPARE(testWidget->horizontalHeaderItem(0), nullptr);

    //setItem
    item = new QObjectTableItem();
    testWidget->setItem(0, 0, item);
    testWidget->setItem(0, 0, new QTableWidgetItem());
    QVERIFY(item.isNull());

    //setHorizontalHeaderItem
    headerItem = new QObjectTableItem();
    testWidget->setHorizontalHeaderItem(0, headerItem);
    testWidget->setHorizontalHeaderItem(0, new QTableWidgetItem());
    QVERIFY(headerItem.isNull());

    //setVerticalHeaderItem
    headerItem = new QObjectTableItem();
    testWidget->setVerticalHeaderItem(0, headerItem);
    testWidget->setVerticalHeaderItem(0, new QTableWidgetItem());
    QVERIFY(headerItem.isNull());

    //takeItem
    item = new QObjectTableItem();
    testWidget->setItem(0, 0, item);
    testWidget->takeItem(0, 0);
    QVERIFY(!item.isNull());
    delete item;

    // removeRow
    item = new QObjectTableItem();
    headerItem = new QObjectTableItem();
    testWidget->setItem(0, 0, item);
    testWidget->setVerticalHeaderItem(0, headerItem);
    testWidget->removeRow(0);
    QVERIFY(item.isNull());
    QVERIFY(headerItem.isNull());

    // removeColumn
    item = new QObjectTableItem();
    headerItem = new QObjectTableItem();
    testWidget->setItem(0, 0, item);
    testWidget->setHorizontalHeaderItem(0, headerItem);
    testWidget->removeColumn(0);
    QVERIFY(item.isNull());
    QVERIFY(headerItem.isNull());

    // clear
    item = new QObjectTableItem();
    testWidget->setItem(0, 0, item);
    testWidget->clear();
    QVERIFY(item.isNull());
}

void tst_QTableWidget::sortItems_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<int>("sortOrder");
    QTest::addColumn<int>("sortColumn");
    QTest::addColumn<QStringList>("initial");
    QTest::addColumn<QStringList>("expected");
    QTest::addColumn<IntList>("rows");
    QTest::addColumn<IntList>("initialHidden");
    QTest::addColumn<IntList>("expectedHidden");

    QTest::newRow("ascending")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 0
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            << "1" << "b" << "7" << "3" << "u")
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "1" << "b" << "7" << "3" << "u"
            << "2" << "c" << "9" << "y" << "8"
            << "3" << "d" << "k" << "o" << "6")
        << (IntList() << 0 << 3 << 2 << 1)
        << IntList()
        << IntList();

    QTest::newRow("descending")
        << 4 << 5
        << static_cast<int>(Qt::DescendingOrder)
        << 0
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            << "1" << "b" << "7" << "3" << "u")
        << (QStringList()
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            << "1" << "b" << "7" << "3" << "u"
            << "0" << "a" << "o" << "8" << "k")
        << (IntList() << 3 << 0 << 1 << 2)
        << IntList()
        << IntList();

    QTest::newRow("empty table")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 0
        << (QStringList()
            <<  0  <<  0  <<  0  <<  0  << 0
            <<  0  <<  0  <<  0  <<  0  << 0
            <<  0  <<  0  <<  0  <<  0  << 0
            <<  0  <<  0  <<  0  <<  0  << 0)
        << (QStringList()
            <<  0  <<  0  <<  0  <<  0  << 0
            <<  0  <<  0  <<  0  <<  0  << 0
            <<  0  <<  0  <<  0  <<  0  << 0
            <<  0  <<  0  <<  0  <<  0  << 0)
        << IntList()
        << IntList()
        << IntList();


    QTest::newRow("half-empty table")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 0
        << (QStringList()
            <<  "0"  <<   0   <<  0  <<  0  << 0
            <<  "3"  <<  "d"  <<  0  <<  0  << 0
            <<  "2"  <<  "c"  <<  0  <<  0  << 0
            <<   0   <<   0   <<  0  <<  0  << 0)
        << (QStringList()
            <<  "0"  <<   0   <<  0  <<  0  << 0
            <<  "2"  <<  "c"  <<  0  <<  0  << 0
            <<  "3"  <<  "d"  <<  0  <<  0  << 0
            <<   0   <<   0   <<  0  <<  0  << 0)
        << (IntList() << 0 << 2 << 1)
        << IntList()
        << IntList();

    QTest::newRow("empty column, should not sort.")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 3
        << (QStringList()
            <<  "0"  <<   0   <<  0  <<  0  << 0
            <<  "3"  <<  "d"  <<  0  <<  0  << 0
            <<  "2"  <<  "c"  <<  0  <<  0  << 0
            <<   0   <<   0   <<  0  <<  0  << 0)
        << (QStringList()
            <<  "0"  <<   0   <<  0  <<  0  << 0
            <<  "3"  <<  "d"  <<  0  <<  0  << 0
            <<  "2"  <<  "c"  <<  0  <<  0  << 0
            <<   0   <<   0   <<  0  <<  0  << 0)
        << IntList()
        << IntList()
        << IntList();

    QTest::newRow("descending with null cell, the null cell should be placed at the bottom")
        << 4 << 5
        << static_cast<int>(Qt::DescendingOrder)
        << 0
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            <<  0  << "b" << "7" << "3" << "u")
        << (QStringList()
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            << "0" << "a" << "o" << "8" << "k"
            <<  0  << "b" << "7" << "3" << "u")
        << (IntList() << 2 << 0 << 1)
        << IntList()
        << IntList();

    QTest::newRow("ascending with null cell, the null cell should be placed at the bottom")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 0
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            <<  0  << "b" << "7" << "3" << "u")
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "2" << "c" << "9" << "y" << "8"
            << "3" << "d" << "k" << "o" << "6"
            <<  0  << "b" << "7" << "3" << "u")
        << (IntList() << 0 << 2 << 1)
        << IntList()
        << IntList();

    QTest::newRow("ascending with null cells, the null cells should be placed at the bottom")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 0
        << (QStringList()
            << "3" << "d" << "k" << "o" << "6"
            << "0" << "a" << "o" << "8" << "k"
            <<  0  << "c" << "9" << "y" << "8"
            <<  0  << "b" << "7" << "3" << "u")
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "3" << "d" << "k" << "o" << "6"
            <<  0  << "c" << "9" << "y" << "8"
            <<  0  << "b" << "7" << "3" << "u")
        << (IntList() << 1 << 0)
        << IntList()
        << IntList();

    QTest::newRow("ascending... Check a bug in PersistentIndexes")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 0
        << (QStringList()
            << "3" << "c" << "9" << "y" << "8"
            << "2" << "b" << "7" << "3" << "u"
            << "4" << "d" << "k" << "o" << "6"
            << "1" << "a" << "o" << "8" << "k"
            )
        << (QStringList()
            << "1" << "a" << "o" << "8" << "k"
            << "2" << "b" << "7" << "3" << "u"
            << "3" << "c" << "9" << "y" << "8"
            << "4" << "d" << "k" << "o" << "6"
            )
        << (IntList() << 2 << 1 << 3 << 0)
        << IntList()
        << IntList();

    QTest::newRow("ascending with some null cells inbetween")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 0
        << (QStringList()
            <<  0  << "a" << "o" << "8" << "k"
            << "2" << "c" << "9" << "y" << "8"
            <<  0  << "d" << "k" << "o" << "6"
            << "1" << "b" << "7" << "3" << "u")
        << (QStringList()
            << "1" << "b" << "7" << "3" << "u"
            << "2" << "c" << "9" << "y" << "8"
            <<  0  << "a" << "o" << "8" << "k"
            <<  0  << "d" << "k" << "o" << "6")
        << (IntList() << 1 << 0)
        << IntList()
        << IntList();

    QTest::newRow("ascending hidden")
        << 4 << 5
        << static_cast<int>(Qt::AscendingOrder)
        << 0
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            << "1" << "b" << "7" << "3" << "u")
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "1" << "b" << "7" << "3" << "u"
            << "2" << "c" << "9" << "y" << "8"
            << "3" << "d" << "k" << "o" << "6")
        << (IntList() << 0 << 3 << 2 << 1)
        << (IntList() << 0 << 2)
        << (IntList() << 0 << 2);

    QTest::newRow("descending hidden")
        << 4 << 5
        << static_cast<int>(Qt::DescendingOrder)
        << 0
        << (QStringList()
            << "0" << "a" << "o" << "8" << "k"
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            << "1" << "b" << "7" << "3" << "u")
        << (QStringList()
            << "3" << "d" << "k" << "o" << "6"
            << "2" << "c" << "9" << "y" << "8"
            << "1" << "b" << "7" << "3" << "u"
            << "0" << "a" << "o" << "8" << "k")
        << (IntList() << 3 << 0 << 1 << 2)
        << (IntList() << 0 << 2)
        << (IntList() << 3 << 1);
}

void tst_QTableWidget::sortItems()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(int, sortOrder);
    QFETCH(int, sortColumn);
    QFETCH(QStringList, initial);
    QFETCH(QStringList, expected);
    QFETCH(IntList, rows);
    QFETCH(IntList, initialHidden);
    QFETCH(IntList, expectedHidden);

    testWidget->setRowCount(rowCount);
    testWidget->setColumnCount(columnCount);

    QAbstractItemModel *model = testWidget->model();
    QList<QPersistentModelIndex> persistent;

    int ti = 0;
    for (int r = 0; r < rowCount; ++r) {
        for (int c = 0; c < columnCount; ++c) {
        QString str = initial.at(ti++);
            if (!str.isNull()) {
                testWidget->setItem(r, c, new QTableWidgetItem(str));
            }
        }
        if (testWidget->item(r, sortColumn))
            persistent << model->index(r, sortColumn, QModelIndex());
    }

    for (int h = 0; h < initialHidden.count(); ++h)
        testWidget->hideRow(initialHidden.at(h));

    QCOMPARE(testWidget->verticalHeader()->hiddenSectionCount(), initialHidden.count());

    testWidget->sortItems(sortColumn, static_cast<Qt::SortOrder>(sortOrder));

    int te = 0;
    for (int i = 0; i < rows.count(); ++i) {
        for (int j = 0; j < columnCount; ++j) {
            QString value;
            QTableWidgetItem *itm = testWidget->item(i, j);
            if (itm) {
                value = itm->text();
            }
            QCOMPARE(value, expected.at(te++));
        }
        QCOMPARE(persistent.at(i).row(), rows.at(i));
        //qDebug() << "persistent" << persistent.at(i).row()
        //         << "expected" << rows.at(i);
    }

    for (int k = 0; k < expectedHidden.count(); ++k)
        QVERIFY(testWidget->isRowHidden(expectedHidden.at(k)));
}

void tst_QTableWidget::setItemWithSorting_data()
{
    QTest::addColumn<int>("rowCount");
    QTest::addColumn<int>("columnCount");
    QTest::addColumn<int>("sortOrder");
    QTest::addColumn<int>("sortColumn");
    QTest::addColumn<QStringList>("initialValues");
    QTest::addColumn<int>("row");
    QTest::addColumn<int>("column");
    QTest::addColumn<QString>("newValue");
    QTest::addColumn<QStringList>("expectedValues");
    QTest::addColumn<IntList>("expectedRows");
    QTest::addColumn<bool>("reorderingExpected");

    QTest::newRow("2x1 no change (ascending)")
        << 2 << 1
        << static_cast<int>(Qt::AscendingOrder) << 0
        << (QStringList() << "0" << "1")
        << 1 << 0 << "2"
        << (QStringList() << "0" << "2")
        << (IntList() << 0 << 1)
        << false;
    QTest::newRow("2x1 no change (descending)")
        << 2 << 1
        << static_cast<int>(Qt::DescendingOrder) << 0
        << (QStringList() << "1" << "0")
        << 0 << 0 << "2"
        << (QStringList() << "2" << "0")
        << (IntList() << 0 << 1)
        << false;
    QTest::newRow("2x1 reorder (ascending)")
        << 2 << 1
        << static_cast<int>(Qt::AscendingOrder) << 0
        << (QStringList() << "0" << "1")
        << 0 << 0 << "2"
        << (QStringList() << "1" << "2")
        << (IntList() << 1 << 0)
        << true;
    QTest::newRow("2x1 reorder (descending)")
        << 2 << 1
        << static_cast<int>(Qt::DescendingOrder) << 0
        << (QStringList() << "1" << "0")
        << 1 << 0 << "2"
        << (QStringList() << "2" << "1")
        << (IntList() << 1 << 0)
        << true;
    QTest::newRow("2x2 no change (ascending)")
        << 2 << 2
        << static_cast<int>(Qt::AscendingOrder) << 0
        << (QStringList()
            << "0" << "00"
            << "1" << "11")
        << 1 << 0 << "2"
        << (QStringList()
            << "0" << "00"
            << "2" << "11")
        << (IntList() << 0 << 1)
        << false;
    QTest::newRow("2x2 reorder (ascending)")
        << 2 << 2
        << static_cast<int>(Qt::AscendingOrder) << 0
        << (QStringList()
            << "0" << "00"
            << "1" << "11")
        << 0 << 0 << "2"
        << (QStringList()
            << "1" << "11"
            << "2" << "00")
        << (IntList() << 1 << 0)
        << true;
    QTest::newRow("2x2 reorder (ascending, sortColumn = 1)")
        << 2 << 2
        << static_cast<int>(Qt::AscendingOrder) << 1
        << (QStringList()
            << "00" << "0"
            << "11" << "1")
        << 0 << 1 << "2"
        << (QStringList()
            << "11" << "1"
            << "00" << "2")
        << (IntList() << 1 << 0)
        << true;
    QTest::newRow("2x2 no change (column != sortColumn)")
        << 2 << 2
        << static_cast<int>(Qt::AscendingOrder) << 1
        << (QStringList()
            << "00" << "0"
            << "11" << "1")
        << 0 << 0 << "22"
        << (QStringList()
            << "22" << "0"
            << "11" << "1")
        << (IntList() << 0 << 1)
        << false;
    QTest::newRow("8x4 reorder (ascending, sortColumn = 3)")
        << 8 << 4
        << static_cast<int>(Qt::AscendingOrder) << 3
        << (QStringList()
            << "q" << "v" << "u" << "0"
            << "e" << "j" << "i" << "10"
            << "h" << "d" << "c" << "12"
            << "k" << "g" << "f" << "14"
            << "w" << "y" << "x" << "2"
            << "t" << "s" << "o" << "4"
            << "z" << "p" << "r" << "6"
            << "n" << "m" << "l" << "8")
        << 2 << 3 << "5"
        << (QStringList()
            << "q" << "v" << "u" << "0"
            << "e" << "j" << "i" << "10"
            << "k" << "g" << "f" << "14"
            << "w" << "y" << "x" << "2"
            << "t" << "s" << "o" << "4"
            << "h" << "d" << "c" << "5"
            << "z" << "p" << "r" << "6"
            << "n" << "m" << "l" << "8")
        << (IntList() << 0 << 1 << 5 << 2 << 3 << 4 << 6 << 7)
        << true;
}

void tst_QTableWidget::setItemWithSorting()
{
    QFETCH(int, rowCount);
    QFETCH(int, columnCount);
    QFETCH(int, sortOrder);
    QFETCH(int, sortColumn);
    QFETCH(QStringList, initialValues);
    QFETCH(int, row);
    QFETCH(int, column);
    QFETCH(QString, newValue);
    QFETCH(QStringList, expectedValues);
    QFETCH(IntList, expectedRows);
    QFETCH(bool, reorderingExpected);

    for (int i = 0; i < 2; ++i) {
        QTableWidget w(rowCount, columnCount);

        QAbstractItemModel *model = w.model();
        QList<QPersistentModelIndex> persistent;

        int ti = 0;
        for (int r = 0; r < rowCount; ++r) {
            for (int c = 0; c < columnCount; ++c) {
                QString str = initialValues.at(ti++);
                w.setItem(r, c, new QTableWidgetItem(str));
            }
            persistent << model->index(r, sortColumn);
        }

        w.sortItems(sortColumn, static_cast<Qt::SortOrder>(sortOrder));
        w.setSortingEnabled(true);

        QSignalSpy dataChangedSpy(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
        QSignalSpy layoutChangedSpy(model, SIGNAL(layoutChanged()));

        if (i == 0) {
            // set a new item
            QTableWidgetItem *item = new QTableWidgetItem(newValue);
            w.setItem(row, column, item);
        } else {
            // change the data of existing item
            QTableWidgetItem *item = w.item(row, column);
            item->setText(newValue);
        }

        ti = 0;
        for (int r = 0; r < rowCount; ++r) {
            for (int c = 0; c < columnCount; ++c) {
                QString str = expectedValues.at(ti++);
                QCOMPARE(w.item(r, c)->text(), str);
            }
        }

        for (int k = 0; k < persistent.count(); ++k) {
            QCOMPARE(persistent.at(k).row(), expectedRows.at(k));
            int i = (persistent.at(k).row() * columnCount) + sortColumn;
            QCOMPARE(persistent.at(k).data().toString(), expectedValues.at(i));
        }

        if (i == 0)
            QCOMPARE(dataChangedSpy.count(), reorderingExpected ? 0 : 1);
        else
            QCOMPARE(dataChangedSpy.count(), 1);

        QCOMPARE(layoutChangedSpy.count(), reorderingExpected ? 1 : 0);
    }
}

class QTableWidgetDataChanged : public QTableWidget
{
    Q_OBJECT
public:
    using QTableWidget::QTableWidget;

    void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) override
    {
        QTableWidget::dataChanged(topLeft, bottomRight, roles);
        currentRoles = roles;
    }
    QVector<int> currentRoles;
};

void tst_QTableWidget::itemData()
{
    QTableWidgetDataChanged widget(2, 2);
    widget.setItem(0, 0, new QTableWidgetItem());
    QTableWidgetItem *item = widget.item(0, 0);
    QVERIFY(item);
    item->setFlags(item->flags() | Qt::ItemIsEditable);
    item->setData(Qt::DisplayRole,  QString("0"));
    QCOMPARE(widget.currentRoles, QVector<int>({Qt::DisplayRole, Qt::EditRole}));
    item->setData(Qt::CheckStateRole, Qt::PartiallyChecked);
    QCOMPARE(widget.currentRoles, QVector<int>{Qt::CheckStateRole});
    for (int i = 0; i < 4; ++i)
    {
        item->setData(Qt::UserRole + i, QString::number(i + 1));
        QCOMPARE(widget.currentRoles, QVector<int>{Qt::UserRole + i});
    }
    QMap<int, QVariant> flags = widget.model()->itemData(widget.model()->index(0, 0));
    QCOMPARE(flags.count(), 6);
    for (int i = 0; i < 4; ++i)
        QCOMPARE(flags[Qt::UserRole + i].toString(), QString::number(i + 1));
}

void tst_QTableWidget::setItemData()
{
    QTableWidgetDataChanged table(10, 10);
    table.setSortingEnabled(false);
    QSignalSpy dataChangedSpy(table.model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)));

    QTableWidgetItem *item = new QTableWidgetItem;
    table.setItem(0, 0, item);
    QCOMPARE(dataChangedSpy.count(), 1);
    QModelIndex idx = qvariant_cast<QModelIndex>(dataChangedSpy.takeFirst().at(0));

    QMap<int, QVariant> data;
    data.insert(Qt::DisplayRole, QLatin1String("Display"));
    data.insert(Qt::ToolTipRole, QLatin1String("ToolTip"));
    table.model()->setItemData(idx, data);
    QCOMPARE(table.currentRoles, QVector<int>({Qt::DisplayRole, Qt::EditRole, Qt::ToolTipRole}));

    QCOMPARE(table.model()->data(idx, Qt::DisplayRole).toString(), QLatin1String("Display"));
    QCOMPARE(table.model()->data(idx, Qt::ToolTipRole).toString(), QLatin1String("ToolTip"));
    QCOMPARE(dataChangedSpy.count(), 1);
    QCOMPARE(idx, qvariant_cast<QModelIndex>(dataChangedSpy.takeFirst().at(0)));

    table.model()->setItemData(idx, data);
    QCOMPARE(dataChangedSpy.count(), 0);

    data.clear();
    data.insert(Qt::DisplayRole, QLatin1String("dizplaye"));
    table.model()->setItemData(idx, data);
    QCOMPARE(table.model()->data(idx, Qt::DisplayRole).toString(), QLatin1String("dizplaye"));
    QCOMPARE(dataChangedSpy.count(), 1);
}

void tst_QTableWidget::cellWidget()
{
    QTableWidget table(10, 10);
    QWidget widget;

    QCOMPARE(table.cellWidget(5, 5), static_cast<QWidget*>(0));
    table.setCellWidget(5, 5, &widget);
    QCOMPARE(table.cellWidget(5, 5), &widget);
    table.removeCellWidget(5, 5);
    QCOMPARE(table.cellWidget(5, 5), static_cast<QWidget*>(0));
}

void tst_QTableWidget::cellWidgetGeometry()
{
    QTableWidget tw(3,2);
    tw.show();
    // make sure the next row added is not completely visibile
    tw.resize(300, tw.rowHeight(0) * 3 + tw.rowHeight(0) / 2);
    QVERIFY(QTest::qWaitForWindowExposed(&tw));

    tw.scrollToBottom();
    tw.setRowCount(tw.rowCount() + 1);
    auto item = new QTableWidgetItem("Hello");
    tw.setItem(0,0,item);
    auto le = new QLineEdit("world");
    tw.setCellWidget(0,1,le);
    // process delayedPendingLayout triggered by setting the row count
    tw.doItemsLayout();
    // so y pos is 0 for the first row
    tw.scrollToTop();
    // check if updateEditorGeometries has set the correct y pos for the editors
    QCOMPARE(tw.visualItemRect(item).top(), le->geometry().top());
}

void tst_QTableWidget::sizeHint_data()
{
    QTest::addColumn<int>("scrollBarPolicy");
    QTest::addColumn<QSize>("viewSize");
    QTest::newRow("ScrollBarAlwaysOn") << static_cast<int>(Qt::ScrollBarAlwaysOn) << QSize();
    QTest::newRow("ScrollBarAlwaysOff") << static_cast<int>(Qt::ScrollBarAlwaysOff) << QSize();
    // make sure the scrollbars are shown by resizing the view to 40x40
    QTest::newRow("ScrollBarAsNeeded (40x40)") << static_cast<int>(Qt::ScrollBarAsNeeded) << QSize(40, 40);
    QTest::newRow("ScrollBarAsNeeded (1000x1000)") << static_cast<int>(Qt::ScrollBarAsNeeded) << QSize(1000, 1000);
}

void tst_QTableWidget::sizeHint()
{
    QFETCH(int, scrollBarPolicy);
    QFETCH(QSize, viewSize);

    QTableWidget view(2, 2);
    view.setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
    view.setVerticalScrollBarPolicy(static_cast<Qt::ScrollBarPolicy>(scrollBarPolicy));
    view.setHorizontalScrollBarPolicy(static_cast<Qt::ScrollBarPolicy>(scrollBarPolicy));
    for (int r = 0 ; r < view.rowCount(); ++r)
        for (int c = 0 ; c < view.columnCount(); ++c)
            view.setItem(r, c, new QTableWidgetItem(QString("%1/%2").arg(r).arg(c)));

    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    if (viewSize.isValid()) {
        view.resize(viewSize);
        view.setColumnWidth(0, 100);
        view.setRowHeight(0, 100);
        QTRY_COMPARE(view.size(), viewSize);
    }

    auto sizeHint = view.sizeHint();
    view.hide();
    QCOMPARE(view.sizeHint(), sizeHint);

    view.horizontalHeader()->hide();
    view.show();
    sizeHint = view.sizeHint();
    view.hide();
    QCOMPARE(view.sizeHint(), sizeHint);

    view.verticalHeader()->hide();
    view.show();
    sizeHint = view.sizeHint();
    view.hide();
    QCOMPARE(view.sizeHint(), sizeHint);
}

void tst_QTableWidget::task231094()
{
    QTableWidget tw(5, 3);
    for (int x = 0; x < 3; x++) {
        for (int y = 0; y < 5; y++) {
            QTableWidgetItem *twi = new QTableWidgetItem(QLatin1String("1"));
            if (y == 1)
                twi->setFlags(Qt::ItemIsEnabled);
            else
                twi->setFlags(0);
            tw.setItem(y, x, twi);
        }
    }

    tw.setCurrentCell(1, 1);
    QCOMPARE(tw.currentRow(), 1);
    QCOMPARE(tw.currentColumn(), 1);

    //this would provoke a end-less loop
    QTest::keyClick(&tw, '1');

    //all the items are disabled: the current item shouldn't have changed
    QCOMPARE(tw.currentRow(), 1);
    QCOMPARE(tw.currentColumn(), 1);
}

void tst_QTableWidget::task219380_removeLastRow()
{
    testWidget->setColumnCount(1);
    testWidget->setRowCount(20);
    QTableWidgetItem item;
    testWidget->setItem(18, 0, &item); //we put the item in the second last row
    testWidget->openPersistentEditor(&item);

    testWidget->scrollToBottom();

    testWidget->removeRow(19); //we remove the last row

    //we make sure the editor is at the cell position
    QTRY_COMPARE(testWidget->cellWidget(18, 0)->geometry(), testWidget->visualItemRect(&item));
}

void tst_QTableWidget::task262056_sortDuplicate()
{
    testWidget->setColumnCount(2);
    testWidget->setRowCount(8);
    testWidget->setSortingEnabled(true);
    QStringList items = (QStringList() << "AAA" << "BBB" << "CCC" << "CCC" << "DDD"\
                         << "EEE" << "FFF" << "GGG");
    for (int i = 0; i<8; i++ ) {
        QTableWidgetItem *twi = new QTableWidgetItem(items.at(i));
        testWidget->setItem(i,0,twi);
        testWidget->setItem(i,1,new QTableWidgetItem(QLatin1String("item ") + QString::number(i)));
    }
    testWidget->sortItems(0, Qt::AscendingOrder);
    QSignalSpy layoutChangedSpy(testWidget->model(), SIGNAL(layoutChanged()));
    testWidget->item(3,0)->setBackground(Qt::red);

    QCOMPARE(layoutChangedSpy.count(),0);

}

void tst_QTableWidget::itemWithHeaderItems()
{
    // Need a separate testcase for this because the tst_QTableWidget::item testcase
    // does creates QTableWidgetItems for each available cell in the table. We're testing
    // the case of not all available cells having a QTableWidgetItem set.
    QTableWidget table(2, 1);

    QTableWidgetItem *item0_0 = new QTableWidgetItem(QTableWidgetItem::UserType);
    table.setItem(0, 0, item0_0);

    QTableWidgetItem *item1_0 = new QTableWidgetItem(QTableWidgetItem::UserType);
    table.setItem(1, 0, item1_0);

    QCOMPARE(table.item(0, 1), static_cast<QTableWidgetItem *>(0));
}

class TestTableWidget : public QTableWidget
{
    Q_OBJECT
public:
    TestTableWidget(int rows, int columns, QWidget *parent = 0)
        : QTableWidget(rows, columns, parent)
    {
    }

    using QTableWidget::mimeData;
    using QTableWidget::indexFromItem;
    using QTableWidget::keyPressEvent;
};

void tst_QTableWidget::mimeData()
{
    TestTableWidget table(10, 10);

    for (int x = 0; x < 10; ++x) {
        for (int y = 0; y < 10; ++y) {
            QTableWidgetItem *item = new QTableWidgetItem(QStringLiteral("123"));
            table.setItem(y, x, item);
        }
    }

    QList<QTableWidgetItem *> tableWidgetItemList;
    QModelIndexList modelIndexList;

    // do these checks more than once to ensure that the "cached indexes" work as expected
    QVERIFY(!table.mimeData(tableWidgetItemList));
    QVERIFY(!table.model()->mimeData(modelIndexList));
    QVERIFY(!table.model()->mimeData(modelIndexList));
    QVERIFY(!table.mimeData(tableWidgetItemList));

    tableWidgetItemList << table.item(1, 1);
    modelIndexList << table.indexFromItem(table.item(1, 1));

    QMimeData *data;

    QVERIFY((data = table.mimeData(tableWidgetItemList)));
    delete data;

    QVERIFY((data = table.model()->mimeData(modelIndexList)));
    delete data;

    QVERIFY((data = table.model()->mimeData(modelIndexList)));
    delete data;

    QVERIFY((data = table.mimeData(tableWidgetItemList)));
    delete data;

    // check the saved data is actually the same

    QMimeData *data2;

    data = table.mimeData(tableWidgetItemList);
    data2 = table.model()->mimeData(modelIndexList);

    const QString format = QStringLiteral("application/x-qabstractitemmodeldatalist");

    QVERIFY(data->hasFormat(format));
    QVERIFY(data2->hasFormat(format));
    QCOMPARE(data->data(format), data2->data(format));

    delete data;
    delete data2;
}

void tst_QTableWidget::selectedRowAfterSorting()
{
    TestTableWidget table(3,3);
    table.setSelectionBehavior(QAbstractItemView::SelectRows);
    for (int r = 0; r < 3; r++)
        for (int c = 0; c < 3; c++)
            table.setItem(r,c,new QTableWidgetItem(QStringLiteral("0")));
    QHeaderView *localHorizontalHeader = table.horizontalHeader();
    localHorizontalHeader->setSortIndicator(1,Qt::DescendingOrder);
    table.setProperty("sortingEnabled",true);
    table.selectRow(1);
    table.item(1,1)->setText("9");
    QCOMPARE(table.selectedItems().count(),3);
    foreach (QTableWidgetItem *item, table.selectedItems()) {
        QCOMPARE(item->row(),0);
    }
}

void tst_QTableWidget::search()
{
    auto createItem = [](const QString &txt)
    {
        auto item = new QTableWidgetItem(txt);
        item->setFlags(item->flags().setFlag(Qt::ItemIsEditable, false));
        return item;
    };

    auto checkSeries = [](TestTableWidget &tw, const QVector<QPair<QKeyEvent, int>> &series)
    {
        for (const auto &p : series) {
            QKeyEvent e = p.first;
            tw.keyPressEvent(&e);
            QVERIFY(tw.selectionModel()->isSelected(tw.model()->index(p.second, 0)));
        }
    };
    TestTableWidget tw(5, 1);
    tw.setItem(0, 0, createItem("12"));
    tw.setItem(1, 0, createItem("123"));
    tw.setItem(2, 0, createItem("123 4"));
    tw.setItem(3, 0, createItem("123 5"));
    tw.setItem(4, 0, createItem(" "));
    tw.show();

    QKeyEvent evSpace(QEvent::KeyPress, Qt::Key_Space, Qt::NoModifier, " ");
    QKeyEvent ev1(QEvent::KeyPress, Qt::Key_1, Qt::NoModifier, "1");
    QKeyEvent ev2(QEvent::KeyPress, Qt::Key_2, Qt::NoModifier, "2");
    QKeyEvent ev3(QEvent::KeyPress, Qt::Key_3, Qt::NoModifier, "3");
    QKeyEvent ev4(QEvent::KeyPress, Qt::Key_4, Qt::NoModifier, "4");
    QKeyEvent ev5(QEvent::KeyPress, Qt::Key_5, Qt::NoModifier, "5");

    checkSeries(tw, {{evSpace, 4}, {ev1, 4}});
    QTest::qWait(QApplication::keyboardInputInterval() * 2);
    checkSeries(tw, {{ev1, 0}, {ev2, 0}, {ev3, 1}, {evSpace, 2}, {ev5, 3}});
    QTest::qWait(QApplication::keyboardInputInterval() * 2);
    checkSeries(tw, {{ev1, 0}, {ev2, 0}, {ev3, 1}, {evSpace, 2}, {ev4, 2}});
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void tst_QTableWidget::clearItemData()
{
    QTableWidget table(3,3);
    for (int r = 0; r < 3; r++)
        for (int c = 0; c < 3; c++)
            table.setItem(r,c,new QTableWidgetItem(QStringLiteral("0")));
    QSignalSpy dataChangeSpy(table.model(), &QAbstractItemModel::dataChanged);
    QVERIFY(dataChangeSpy.isValid());
    QVERIFY(!table.model()->clearItemData(QModelIndex()));
    QCOMPARE(dataChangeSpy.size(), 0);
    QVERIFY(table.model()->clearItemData(table.model()->index(0, 0)));
    QVERIFY(!table.model()->index(0, 0).data().isValid());
    QCOMPARE(dataChangeSpy.size(), 1);
    const QList<QVariant> dataChangeArgs = dataChangeSpy.takeFirst();
    QCOMPARE(dataChangeArgs.at(0).value<QModelIndex>(), table.model()->index(0, 0));
    QCOMPARE(dataChangeArgs.at(1).value<QModelIndex>(), table.model()->index(0, 0));
    QVERIFY(dataChangeArgs.at(2).value<QVector<int>>().isEmpty());
    QVERIFY(table.model()->clearItemData(table.model()->index(0, 0)));
    QCOMPARE(dataChangeSpy.size(), 0);
}
#endif

QTEST_MAIN(tst_QTableWidget)
#include "tst_qtablewidget.moc"