diff --git a/autotests/allalbumsmodeltest.cpp b/autotests/allalbumsmodeltest.cpp index 961d8fa2..a7492143 100644 --- a/autotests/allalbumsmodeltest.cpp +++ b/autotests/allalbumsmodeltest.cpp @@ -1,339 +1,286 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "databasetestdata.h" #include "musicalbum.h" #include "musicaudiotrack.h" #include "databaseinterface.h" #include "models/allalbumsmodel.h" #include "qabstractitemmodeltester.h" #include #include #include #include #include #include #include #include #include #include #include #include class AllAlbumsModelTests: public QObject, public DatabaseTestData { Q_OBJECT private Q_SLOTS: void initTestCase() { qRegisterMetaType>("QHash"); qRegisterMetaType>("QHash"); qRegisterMetaType>>("QHash>"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QHash"); qRegisterMetaType("MusicArtist"); } void removeOneTrack() { DatabaseInterface musicDb; AllAlbumsModel albumsModel; - QAbstractItemModelTester testModel(&albumsModel, Qt::BlockingQueuedConnection); + QAbstractItemModelTester testModel(&albumsModel); connect(&musicDb, &DatabaseInterface::albumsAdded, &albumsModel, &AllAlbumsModel::albumsAdded); connect(&musicDb, &DatabaseInterface::albumModified, &albumsModel, &AllAlbumsModel::albumModified); connect(&musicDb, &DatabaseInterface::albumRemoved, &albumsModel, &AllAlbumsModel::albumRemoved); musicDb.init(QStringLiteral("testDb")); QSignalSpy beginInsertRowsSpy(&albumsModel, &AllAlbumsModel::rowsAboutToBeInserted); QSignalSpy endInsertRowsSpy(&albumsModel, &AllAlbumsModel::rowsInserted); QSignalSpy beginRemoveRowsSpy(&albumsModel, &AllAlbumsModel::rowsAboutToBeRemoved); QSignalSpy endRemoveRowsSpy(&albumsModel, &AllAlbumsModel::rowsRemoved); QSignalSpy dataChangedSpy(&albumsModel, &AllAlbumsModel::dataChanged); QCOMPARE(beginInsertRowsSpy.count(), 0); QCOMPARE(endInsertRowsSpy.count(), 0); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); musicDb.insertTracksList(mNewTracks, mNewCovers, QStringLiteral("autoTest")); - while(beginInsertRowsSpy.count() < 5) { - QCOMPARE(beginInsertRowsSpy.wait(500), true); - } - while(endInsertRowsSpy.count() < 5) { - QCOMPARE(endInsertRowsSpy.wait(500), true); - } - QCOMPARE(albumsModel.rowCount(), 5); - QCOMPARE(beginInsertRowsSpy.count(), 5); - QCOMPARE(endInsertRowsSpy.count(), 5); + QCOMPARE(beginInsertRowsSpy.count(), 1); + QCOMPARE(endInsertRowsSpy.count(), 1); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); auto trackId = musicDb.trackIdFromTitleAlbumTrackDiscNumber(QStringLiteral("track1"), QStringLiteral("artist1"), QStringLiteral("album1"), 1, 1); auto firstTrack = musicDb.trackFromDatabaseId(trackId); QCOMPARE(firstTrack.isValid(), true); musicDb.removeTracksList({firstTrack.resourceURI()}); - QCOMPARE(dataChangedSpy.wait(500), true); - QCOMPARE(albumsModel.rowCount(), 5); - QCOMPARE(beginInsertRowsSpy.count(), 5); - QCOMPARE(endInsertRowsSpy.count(), 5); + QCOMPARE(beginInsertRowsSpy.count(), 1); + QCOMPARE(endInsertRowsSpy.count(), 1); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 1); } void removeOneAlbum() { DatabaseInterface musicDb; AllAlbumsModel albumsModel; - QAbstractItemModelTester testModel(&albumsModel, Qt::BlockingQueuedConnection); + QAbstractItemModelTester testModel(&albumsModel); connect(&musicDb, &DatabaseInterface::albumsAdded, &albumsModel, &AllAlbumsModel::albumsAdded); connect(&musicDb, &DatabaseInterface::albumModified, &albumsModel, &AllAlbumsModel::albumModified); connect(&musicDb, &DatabaseInterface::albumRemoved, &albumsModel, &AllAlbumsModel::albumRemoved); musicDb.init(QStringLiteral("testDb")); QSignalSpy beginInsertRowsSpy(&albumsModel, &AllAlbumsModel::rowsAboutToBeInserted); QSignalSpy endInsertRowsSpy(&albumsModel, &AllAlbumsModel::rowsInserted); QSignalSpy beginRemoveRowsSpy(&albumsModel, &AllAlbumsModel::rowsAboutToBeRemoved); QSignalSpy endRemoveRowsSpy(&albumsModel, &AllAlbumsModel::rowsRemoved); QSignalSpy dataChangedSpy(&albumsModel, &AllAlbumsModel::dataChanged); QCOMPARE(albumsModel.rowCount(), 0); QCOMPARE(beginInsertRowsSpy.count(), 0); QCOMPARE(endInsertRowsSpy.count(), 0); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); musicDb.insertTracksList(mNewTracks, mNewCovers, QStringLiteral("autoTest")); - while(beginInsertRowsSpy.count() < 5) { - QCOMPARE(beginInsertRowsSpy.wait(500), true); - } - while(endInsertRowsSpy.count() < 5) { - QCOMPARE(endInsertRowsSpy.wait(500), true); - } - QCOMPARE(albumsModel.rowCount(), 5); - QCOMPARE(beginInsertRowsSpy.count(), 5); - QCOMPARE(endInsertRowsSpy.count(), 5); + QCOMPARE(beginInsertRowsSpy.count(), 1); + QCOMPARE(endInsertRowsSpy.count(), 1); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); auto firstTrackId = musicDb.trackIdFromTitleAlbumTrackDiscNumber(QStringLiteral("track1"), QStringLiteral("artist2"), QStringLiteral("album3"), 1, 1); auto firstTrack = musicDb.trackFromDatabaseId(firstTrackId); auto secondTrackId = musicDb.trackIdFromTitleAlbumTrackDiscNumber(QStringLiteral("track2"), QStringLiteral("artist2"), QStringLiteral("album3"), 2, 1); auto secondTrack = musicDb.trackFromDatabaseId(secondTrackId); auto thirdTrackId = musicDb.trackIdFromTitleAlbumTrackDiscNumber(QStringLiteral("track3"), QStringLiteral("artist2"), QStringLiteral("album3"), 3, 1); auto thirdTrack = musicDb.trackFromDatabaseId(thirdTrackId); musicDb.removeTracksList({firstTrack.resourceURI(), secondTrack.resourceURI(), thirdTrack.resourceURI()}); - while (beginRemoveRowsSpy.count() < 1) { - QCOMPARE(beginRemoveRowsSpy.wait(500), true); - } - while (endRemoveRowsSpy.count() < 1) { - QCOMPARE(endRemoveRowsSpy.wait(500), true); - } - QCOMPARE(albumsModel.rowCount(), 4); - QCOMPARE(beginInsertRowsSpy.count(), 5); - QCOMPARE(endInsertRowsSpy.count(), 5); + QCOMPARE(beginInsertRowsSpy.count(), 1); + QCOMPARE(endInsertRowsSpy.count(), 1); QCOMPARE(beginRemoveRowsSpy.count(), 1); QCOMPARE(endRemoveRowsSpy.count(), 1); QCOMPARE(dataChangedSpy.count(), 0); } void addOneTrack() { DatabaseInterface musicDb; AllAlbumsModel albumsModel; - QAbstractItemModelTester testModel(&albumsModel, Qt::BlockingQueuedConnection); + QAbstractItemModelTester testModel(&albumsModel); connect(&musicDb, &DatabaseInterface::albumsAdded, &albumsModel, &AllAlbumsModel::albumsAdded); connect(&musicDb, &DatabaseInterface::albumModified, &albumsModel, &AllAlbumsModel::albumModified); connect(&musicDb, &DatabaseInterface::albumRemoved, &albumsModel, &AllAlbumsModel::albumRemoved); musicDb.init(QStringLiteral("testDb")); QSignalSpy beginInsertRowsSpy(&albumsModel, &AllAlbumsModel::rowsAboutToBeInserted); QSignalSpy endInsertRowsSpy(&albumsModel, &AllAlbumsModel::rowsInserted); QSignalSpy beginRemoveRowsSpy(&albumsModel, &AllAlbumsModel::rowsAboutToBeRemoved); QSignalSpy endRemoveRowsSpy(&albumsModel, &AllAlbumsModel::rowsRemoved); QSignalSpy dataChangedSpy(&albumsModel, &AllAlbumsModel::dataChanged); QCOMPARE(beginInsertRowsSpy.count(), 0); QCOMPARE(endInsertRowsSpy.count(), 0); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); musicDb.insertTracksList(mNewTracks, mNewCovers, QStringLiteral("autoTest")); - while(beginInsertRowsSpy.count() < 5) { - QCOMPARE(beginInsertRowsSpy.wait(500), true); - } - while(endInsertRowsSpy.count() < 5) { - QCOMPARE(endInsertRowsSpy.wait(500), true); - } - - QCOMPARE(beginInsertRowsSpy.count(), 5); - QCOMPARE(endInsertRowsSpy.count(), 5); + QCOMPARE(beginInsertRowsSpy.count(), 1); + QCOMPARE(endInsertRowsSpy.count(), 1); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); auto newTrack = MusicAudioTrack{true, QStringLiteral("$23"), QStringLiteral("0"), QStringLiteral("track23"), QStringLiteral("artist2"), QStringLiteral("album4"), QStringLiteral("artist2"), 23, 1, QTime::fromMSecsSinceStartOfDay(23), {QUrl::fromLocalFile(QStringLiteral("/$23"))}, QDateTime::fromMSecsSinceEpoch(23), {QUrl::fromLocalFile(QStringLiteral("file://image$23"))}, 5, true, {}, QStringLiteral("composer1"), QStringLiteral("lyricist1")}; auto newTracks = QList(); newTracks.push_back(newTrack); musicDb.insertTracksList(newTracks, mNewCovers, QStringLiteral("autoTest")); - while(dataChangedSpy.count() < 1) { - QCOMPARE(dataChangedSpy.wait(500), true); - } - - QCOMPARE(beginInsertRowsSpy.count(), 5); - QCOMPARE(endInsertRowsSpy.count(), 5); + QCOMPARE(beginInsertRowsSpy.count(), 1); + QCOMPARE(endInsertRowsSpy.count(), 1); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 1); } void addOneAlbum() { DatabaseInterface musicDb; AllAlbumsModel albumsModel; - QAbstractItemModelTester testModel(&albumsModel, Qt::BlockingQueuedConnection); + QAbstractItemModelTester testModel(&albumsModel); connect(&musicDb, &DatabaseInterface::albumsAdded, &albumsModel, &AllAlbumsModel::albumsAdded); connect(&musicDb, &DatabaseInterface::albumModified, &albumsModel, &AllAlbumsModel::albumModified); connect(&musicDb, &DatabaseInterface::albumRemoved, &albumsModel, &AllAlbumsModel::albumRemoved); musicDb.init(QStringLiteral("testDb")); QSignalSpy beginInsertRowsSpy(&albumsModel, &AllAlbumsModel::rowsAboutToBeInserted); QSignalSpy endInsertRowsSpy(&albumsModel, &AllAlbumsModel::rowsInserted); QSignalSpy beginRemoveRowsSpy(&albumsModel, &AllAlbumsModel::rowsAboutToBeRemoved); QSignalSpy endRemoveRowsSpy(&albumsModel, &AllAlbumsModel::rowsRemoved); QSignalSpy dataChangedSpy(&albumsModel, &AllAlbumsModel::dataChanged); QCOMPARE(beginInsertRowsSpy.count(), 0); QCOMPARE(endInsertRowsSpy.count(), 0); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); auto newFiles = QList(); const auto &constNewTracks = mNewTracks; for (const auto &oneTrack : constNewTracks) { newFiles.push_back(oneTrack.resourceURI()); } musicDb.insertTracksList(mNewTracks, mNewCovers, QStringLiteral("autoTest")); - while(beginInsertRowsSpy.count() < 5) { - QCOMPARE(beginInsertRowsSpy.wait(500), true); - } - while(endInsertRowsSpy.count() < 5) { - QCOMPARE(endInsertRowsSpy.wait(500), true); - } - - QCOMPARE(beginInsertRowsSpy.count(), 5); - QCOMPARE(endInsertRowsSpy.count(), 5); + QCOMPARE(beginInsertRowsSpy.count(), 1); + QCOMPARE(endInsertRowsSpy.count(), 1); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); - auto newTrack = MusicAudioTrack{true, QStringLiteral("$19"), QStringLiteral("0"), QStringLiteral("track1"), + auto newTrack = MusicAudioTrack{true, QStringLiteral("$23"), QStringLiteral("0"), QStringLiteral("track1"), QStringLiteral("artist2"), QStringLiteral("album5"), QStringLiteral("artist2"), 1, 1, - QTime::fromMSecsSinceStartOfDay(19), {QUrl::fromLocalFile(QStringLiteral("/$19"))}, - QDateTime::fromMSecsSinceEpoch(19), - {QUrl::fromLocalFile(QStringLiteral("file://image$19"))}, 5, true, + QTime::fromMSecsSinceStartOfDay(23), {QUrl::fromLocalFile(QStringLiteral("/$23"))}, + QDateTime::fromMSecsSinceEpoch(23), + {QUrl::fromLocalFile(QStringLiteral("file://image$23"))}, 5, true, {}, QStringLiteral("composer1"), QStringLiteral("lyricist1")}; auto newTracks = QList(); newTracks.push_back(newTrack); auto newCover = QUrl::fromLocalFile(QStringLiteral("album5")); auto newCovers = QHash(); newCovers[QStringLiteral("album5")] = newCover; - auto newFiles2 = QList(); - for (const auto &oneTrack : newTracks) { - newFiles2.push_back(oneTrack.resourceURI()); - } - musicDb.insertTracksList(newTracks, newCovers, QStringLiteral("autoTest")); - while(beginInsertRowsSpy.count() < 6) { - QCOMPARE(beginInsertRowsSpy.wait(500), true); - } - while(endInsertRowsSpy.count() < 6) { - QCOMPARE(endInsertRowsSpy.wait(500), true); - } - - QCOMPARE(beginInsertRowsSpy.count(), 6); - QCOMPARE(endInsertRowsSpy.count(), 6); + QCOMPARE(beginInsertRowsSpy.count(), 2); + QCOMPARE(endInsertRowsSpy.count(), 2); QCOMPARE(beginRemoveRowsSpy.count(), 0); QCOMPARE(endRemoveRowsSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 0); } }; QTEST_GUILESS_MAIN(AllAlbumsModelTests) #include "allalbumsmodeltest.moc" diff --git a/autotests/qabstractitemmodeltester.cpp b/autotests/qabstractitemmodeltester.cpp index a6ce20e4..67efd36c 100644 --- a/autotests/qabstractitemmodeltester.cpp +++ b/autotests/qabstractitemmodeltester.cpp @@ -1,836 +1,831 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtTest module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** 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 Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** 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-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qabstractitemmodeltester.h" #include #include #include #include #include #include #include #include "elisa_debug.h" QT_BEGIN_NAMESPACE //Q_LOGGING_CATEGORY(lcModelTest, "qt.modeltest") #define MODELTESTER_VERIFY(statement) \ do { \ if (!verify(static_cast(statement), #statement, "", __FILE__, __LINE__)) \ return; \ } while (false) #define MODELTESTER_COMPARE(actual, expected) \ do { \ if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__)) \ return; \ } while (false) class QAbstractItemModelTesterPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QAbstractItemModelTester) public: - QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode, - Qt::ConnectionType type); + QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode); void nonDestructiveBasicTest(); void rowAndColumnCount(); void hasIndex(); void index(); void parent(); void data(); void runAllTests(); void layoutAboutToBeChanged(); void layoutChanged(); void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); void rowsInserted(const QModelIndex &parent, int start, int end); void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); void rowsRemoved(const QModelIndex &parent, int start, int end); void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void headerDataChanged(Qt::Orientation orientation, int start, int end); private: void checkChildren(const QModelIndex &parent, int currentDepth = 0); bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line); template bool compare(const T1 &t1, const T2 &t2, const char *actual, const char *expected, const char *file, int line); QPointer model; QAbstractItemModelTester::FailureReportingMode failureReportingMode; struct Changing { QModelIndex parent; int oldSize; QVariant last; QVariant next; }; QStack insert; QStack remove; bool fetchingMore; - Qt::ConnectionType type; - QList changing; }; /*! \class QAbstractItemModelTester \since 5.11 \inmodule QtTest \brief The QAbstractItemModelTester class helps testing QAbstractItemModel subclasses. The QAbstractItemModelTester class is a utility class to test item models. When implementing an item model (that is, a concrete QAbstractItemModel subclass) one must abide to a very strict set of rules that ensure consistency for users of the model (views, proxy models, and so on). For instance, for a given index, a model's reimplementation of \l{QAbstractItemModel::hasChildren()}{hasChildren()} must be consistent with the values returned by \l{QAbstractItemModel::rowCount()}{rowCount()} and \l{QAbstractItemModel::columnCount()}{columnCount()}. QAbstractItemModelTester helps catching the most common errors in custom item model classes. By performing a series of tests, it will try to check that the model status is consistent at all times. The tests will be repeated automatically every time the model is modified. QAbstractItemModelTester employs non-destructive tests, which typically consist in reading data and metadata out of a given item model. QAbstractItemModelTester will also attempt illegal modifications of the model. In models which are properly implemented, such attempts should be rejected, and no data should be changed as a consequence. \section1 Usage Using QAbstractItemModelTester is straightforward. In a \l{Qt Test Overview}{test case} it is sufficient to create an instance, passing the model that needs to be tested to the constructor: \code MyModel *modelToBeTested = ...; auto tester = new QAbstractItemModelTester(modelToBeTested); \endcode QAbstractItemModelTester will report testing failures through the Qt Test logging mechanisms. It is also possible to use QAbstractItemModelTester outside of a test case. For instance, it may be useful to test an item model used by an application without the need of building an explicit unit test for such a model (which might be challenging). In order to use QAbstractItemModelTester outside of a test case, pass one of the \c QAbstractItemModelTester::FailureReportingMode enumerators to its constructor, therefore specifying how failures should be logged. QAbstractItemModelTester may also report additional debugging information as logging messages under the \c qt.modeltest logging category. Such debug logging is disabled by default; refer to the QLoggingCategory documentation to learn how to enable it. \note While QAbstractItemModelTester is a valid help for development and testing of custom item models, it does not (and cannot) catch all possible problems in QAbstractItemModel subclasses. Notably, it will never perform meaningful destructive testing of a model, which must be therefore tested separately. \sa {Model/View Programming}, QAbstractItemModel */ /*! \enum QAbstractItemModelTester::FailureReportingMode This enumeration specifies how QAbstractItemModelTester should report a failure when it tests a QAbstractItemModel subclass. \value QtTest The failures will be reported through QtTest's logging mechanism. \value Warning The failures will be reported as warning messages in the \c{qt.modeltest} logging category. \value Fatal A failure will cause immediate and abnormal program termination. The reason for the failure will be reported using \c{qFatal()}. */ /*! Creates a model tester instance, with the given \a parent, that will test the model \a model. */ -QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, Qt::ConnectionType type, QObject *parent) - : QAbstractItemModelTester(model, FailureReportingMode::QtTest, type, parent) +QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent) + : QAbstractItemModelTester(model, FailureReportingMode::QtTest, parent) { } /*! Creates a model tester instance, with the given \a parent, that will test the model \a model, using the specified \a mode to report test failures. \sa QAbstractItemModelTester::FailureReportingMode */ -QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, Qt::ConnectionType type, QObject *parent) - : QObject(*new QAbstractItemModelTesterPrivate(model, mode, type), parent) +QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent) + : QObject(*new QAbstractItemModelTesterPrivate(model, mode), parent) { if (!model) qFatal("%s: model must not be null", Q_FUNC_INFO); Q_D(QAbstractItemModelTester); const auto &runAllTests = [d] { d->runAllTests(); }; connect(model, &QAbstractItemModel::columnsAboutToBeInserted, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::columnsAboutToBeRemoved, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::columnsInserted, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::columnsRemoved, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::dataChanged, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::headerDataChanged, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::layoutAboutToBeChanged, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::layoutChanged, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::modelReset, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::rowsAboutToBeInserted, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::rowsInserted, - this, runAllTests, d->type); + this, runAllTests); connect(model, &QAbstractItemModel::rowsRemoved, - this, runAllTests, d->type); + this, runAllTests); // Special checks for changes connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, [d]{ d->layoutAboutToBeChanged(); }); connect(model, &QAbstractItemModel::layoutChanged, this, [d]{ d->layoutChanged(); }); connect(model, &QAbstractItemModel::rowsAboutToBeInserted, - this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); }, d->type); + this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); }); connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, - this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); }, d->type); + this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); }); connect(model, &QAbstractItemModel::rowsInserted, - this, [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); }, d->type); + this, [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); }); connect(model, &QAbstractItemModel::rowsRemoved, - this, [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); }, d->type); + this, [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); }); connect(model, &QAbstractItemModel::dataChanged, - this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); }, d->type); + this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); }); connect(model, &QAbstractItemModel::headerDataChanged, - this, [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); }, d->type); + this, [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); }); runAllTests(); } /*! Returns the model that this instance is testing. */ QAbstractItemModel *QAbstractItemModelTester::model() const { Q_D(const QAbstractItemModelTester); return d->model.data(); } /*! Returns the mode that this instancing is using to report test failures. \sa QAbstractItemModelTester::FailureReportingMode */ QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const { Q_D(const QAbstractItemModelTester); return d->failureReportingMode; } bool QAbstractItemModelTester::verify(bool statement, const char *statementStr, const char *description, const char *file, int line) { Q_D(QAbstractItemModelTester); return d->verify(statement, statementStr, description, file, line); } -QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode, - Qt::ConnectionType type) +QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode) : model(model), failureReportingMode(failureReportingMode), - fetchingMore(false), - type(type) + fetchingMore(false) { } void QAbstractItemModelTesterPrivate::runAllTests() { if (fetchingMore) return; nonDestructiveBasicTest(); rowAndColumnCount(); hasIndex(); index(); parent(); data(); } /* nonDestructiveBasicTest tries to call a number of the basic functions (not all) to make sure the model doesn't outright segfault, testing the functions that makes sense. */ void QAbstractItemModelTesterPrivate::nonDestructiveBasicTest() { MODELTESTER_VERIFY(!model->buddy(QModelIndex()).isValid()); model->canFetchMore(QModelIndex()); MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0); fetchingMore = true; model->fetchMore(QModelIndex()); fetchingMore = false; Qt::ItemFlags flags = model->flags(QModelIndex()); MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0); model->hasChildren(QModelIndex()); model->hasIndex(0, 0); QVariant cache; model->match(QModelIndex(), -1, cache); model->mimeTypes(); MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid()); MODELTESTER_VERIFY(model->rowCount() >= 0); model->span(QModelIndex()); model->supportedDropActions(); model->roleNames(); } /* Tests model's implementation of QAbstractItemModel::rowCount(), columnCount() and hasChildren(). Models that are dynamically populated are not as fully tested here. */ void QAbstractItemModelTesterPrivate::rowAndColumnCount() { if (!model->hasChildren()) return; QModelIndex topIndex = model->index(0, 0, QModelIndex()); // check top row int rows = model->rowCount(topIndex); MODELTESTER_VERIFY(rows >= 0); int columns = model->columnCount(topIndex); MODELTESTER_VERIFY(columns >= 0); if (rows == 0 || columns == 0) return; MODELTESTER_VERIFY(model->hasChildren(topIndex)); QModelIndex secondLevelIndex = model->index(0, 0, topIndex); MODELTESTER_VERIFY(secondLevelIndex.isValid()); rows = model->rowCount(secondLevelIndex); MODELTESTER_VERIFY(rows >= 0); columns = model->columnCount(secondLevelIndex); MODELTESTER_VERIFY(columns >= 0); if (rows == 0 || columns == 0) return; MODELTESTER_VERIFY(model->hasChildren(secondLevelIndex)); // rowCount() / columnCount() are tested more extensively in checkChildren() } /* Tests model's implementation of QAbstractItemModel::hasIndex() */ void QAbstractItemModelTesterPrivate::hasIndex() { // Make sure that invalid values returns an invalid index MODELTESTER_VERIFY(!model->hasIndex(-2, -2)); MODELTESTER_VERIFY(!model->hasIndex(-2, 0)); MODELTESTER_VERIFY(!model->hasIndex(0, -2)); const int rows = model->rowCount(); const int columns = model->columnCount(); // check out of bounds MODELTESTER_VERIFY(!model->hasIndex(rows, columns)); MODELTESTER_VERIFY(!model->hasIndex(rows + 1, columns + 1)); if (rows > 0 && columns > 0) MODELTESTER_VERIFY(model->hasIndex(0, 0)); // hasIndex() is tested more extensively in checkChildren(), // but this catches the big mistakes } /* Tests model's implementation of QAbstractItemModel::index() */ void QAbstractItemModelTesterPrivate::index() { const int rows = model->rowCount(); const int columns = model->columnCount(); for (int row = 0; row < rows; ++row) { for (int column = 0; column < columns; ++column) { // Make sure that the same index is *always* returned QModelIndex a = model->index(row, column); QModelIndex b = model->index(row, column); MODELTESTER_VERIFY(a.isValid()); MODELTESTER_VERIFY(b.isValid()); MODELTESTER_COMPARE(a, b); } } // index() is tested more extensively in checkChildren(), // but this catches the big mistakes } /* Tests model's implementation of QAbstractItemModel::parent() */ void QAbstractItemModelTesterPrivate::parent() { // Make sure the model won't crash and will return an invalid QModelIndex // when asked for the parent of an invalid index. MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid()); if (!model->hasChildren()) return; // Column 0 | Column 1 | // QModelIndex() | | // \- topIndex | topIndex1 | // \- childIndex | childIndex1 | // Common error test #1, make sure that a top level index has a parent // that is a invalid QModelIndex. QModelIndex topIndex = model->index(0, 0, QModelIndex()); MODELTESTER_VERIFY(!model->parent(topIndex).isValid()); // Common error test #2, make sure that a second level index has a parent // that is the first level index. if (model->hasChildren(topIndex)) { QModelIndex childIndex = model->index(0, 0, topIndex); MODELTESTER_VERIFY(childIndex.isValid()); MODELTESTER_COMPARE(model->parent(childIndex), topIndex); } // Common error test #3, the second column should NOT have the same children // as the first column in a row. // Usually the second column shouldn't have children. if (model->hasIndex(0, 1)) { QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); MODELTESTER_VERIFY(topIndex1.isValid()); if (model->hasChildren(topIndex) && model->hasChildren(topIndex1)) { QModelIndex childIndex = model->index(0, 0, topIndex); MODELTESTER_VERIFY(childIndex.isValid()); QModelIndex childIndex1 = model->index(0, 0, topIndex1); MODELTESTER_VERIFY(childIndex1.isValid()); MODELTESTER_VERIFY(childIndex != childIndex1); } } // Full test, walk n levels deep through the model making sure that all // parent's children correctly specify their parent. checkChildren(QModelIndex()); } /* Called from the parent() test. A model that returns an index of parent X should also return X when asking for the parent of the index. This recursive function does pretty extensive testing on the whole model in an effort to catch edge cases. This function assumes that rowCount(), columnCount() and index() already work. If they have a bug it will point it out, but the above tests should have already found the basic bugs because it is easier to figure out the problem in those tests then this one. */ void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth) { // First just try walking back up the tree. QModelIndex p = parent; while (p.isValid()) p = p.parent(); // For models that are dynamically populated if (model->canFetchMore(parent)) { fetchingMore = true; model->fetchMore(parent); fetchingMore = false; } const int rows = model->rowCount(parent); const int columns = model->columnCount(parent); if (rows > 0) MODELTESTER_VERIFY(model->hasChildren(parent)); // Some further testing against rows(), columns(), and hasChildren() MODELTESTER_VERIFY(rows >= 0); MODELTESTER_VERIFY(columns >= 0); if (rows > 0 && columns > 0) MODELTESTER_VERIFY(model->hasChildren(parent)); const QModelIndex topLeftChild = model->index(0, 0, parent); MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent)); MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent)); for (int r = 0; r < rows; ++r) { MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent)); MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent)); for (int c = 0; c < columns; ++c) { MODELTESTER_VERIFY(model->hasIndex(r, c, parent)); QModelIndex index = model->index(r, c, parent); // rowCount() and columnCount() said that it existed... if (!index.isValid()) qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent; MODELTESTER_VERIFY(index.isValid()); // index() should always return the same index when called twice in a row QModelIndex modifiedIndex = model->index(r, c, parent); MODELTESTER_COMPARE(index, modifiedIndex); { const QModelIndex sibling = model->sibling(r, c, topLeftChild); MODELTESTER_COMPARE(index, sibling); } { const QModelIndex sibling = topLeftChild.sibling(r, c); MODELTESTER_COMPARE(index, sibling); } // Some basic checking on the index that is returned MODELTESTER_COMPARE(index.model(), model); MODELTESTER_COMPARE(index.row(), r); MODELTESTER_COMPARE(index.column(), c); // If the next test fails here is some somewhat useful debug you play with. if (model->parent(index) != parent) { qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:"; qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(index); qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth; qCWarning(lcModelTest) << " data for child" << model->data(index).toString(); qCWarning(lcModelTest) << " data for parent" << model->data(parent).toString(); } // Check that we can get back our real parent. MODELTESTER_COMPARE(model->parent(index), parent); QPersistentModelIndex persistentIndex = index; // recursively go down the children if (model->hasChildren(index) && currentDepth < 10) checkChildren(index, ++currentDepth); // make sure that after testing the children that the index doesn't change. QModelIndex newerIndex = model->index(r, c, parent); MODELTESTER_COMPARE(persistentIndex, newerIndex); } } } /* Tests model's implementation of QAbstractItemModel::data() */ void QAbstractItemModelTesterPrivate::data() { if (!model->hasChildren()) return; MODELTESTER_VERIFY(model->index(0, 0).isValid()); // General Purpose roles that should return a QString QVariant variant; variant = model->data(model->index(0, 0), Qt::DisplayRole); if (variant.isValid()) MODELTESTER_VERIFY(variant.canConvert()); variant = model->data(model->index(0, 0), Qt::ToolTipRole); if (variant.isValid()) MODELTESTER_VERIFY(variant.canConvert()); variant = model->data(model->index(0, 0), Qt::StatusTipRole); if (variant.isValid()) MODELTESTER_VERIFY(variant.canConvert()); variant = model->data(model->index(0, 0), Qt::WhatsThisRole); if (variant.isValid()) MODELTESTER_VERIFY(variant.canConvert()); // General Purpose roles that should return a QSize variant = model->data(model->index(0, 0), Qt::SizeHintRole); if (variant.isValid()) MODELTESTER_VERIFY(variant.canConvert()); // Check that the alignment is one we know about QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); if (textAlignmentVariant.isValid()) { Qt::Alignment alignment = textAlignmentVariant.value(); MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask))); } // Check that the "check state" is one we know about. QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); if (checkStateVariant.isValid()) { int state = checkStateVariant.toInt(); MODELTESTER_VERIFY(state == Qt::Unchecked || state == Qt::PartiallyChecked || state == Qt::Checked); } Q_Q(QAbstractItemModelTester); if (!QTestPrivate::testDataGuiRoles(q)) return; } /* Store what is about to be inserted to make sure it actually happens \sa rowsInserted() */ void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { qCDebug(lcModelTest) << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << parent << "parent data=" << (parent.isValid() ? model->data(parent).toString() : QString{}) << "current count of parent=" << model->rowCount(parent) << "last before insertion=" << (start > 1 ? model->index(start - 1, 0, parent) : QModelIndex{}) << (start > 1 ? model->data(model->index(start - 1, 0, parent)) : QModelIndex{}); Changing c; c.parent = parent; c.oldSize = model->rowCount(parent); c.last = (start - 1 >= 0) ? model->index(start - 1, 0, parent).data() : QVariant(); c.next = (start < c.oldSize) ? model->index(start, 0, parent).data() : QVariant(); insert.push(c); } /* Confirm that what was said was going to happen actually did \sa rowsAboutToBeInserted() */ void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end) { qCDebug(lcModelTest) << "rowsInserted" << "start=" << start << "end=" << end << "parent=" << parent << "parent data=" << (parent.isValid() ? model->data(parent).toString() : QString{}) << "current count of parent=" << model->rowCount(parent); for (int i = start; i <= end; ++i) { qCDebug(lcModelTest) << " itemWasInserted:" << i << model->index(i, 0, parent).data(); } Changing c = insert.pop(); MODELTESTER_COMPARE(parent, c.parent); MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1)); if (start - 1 >= 0) MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last); if (end + 1 < model->rowCount(c.parent)) { if (c.next != model->data(model->index(end + 1, 0, c.parent))) { qCDebug(lcModelTest) << start << end; for (int i = 0; i < model->rowCount(); ++i) qCDebug(lcModelTest) << model->index(i, 0).data().toString(); qCDebug(lcModelTest) << c.next << model->data(model->index(end + 1, 0, c.parent)); } MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next); } } void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged() { for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) changing.append(QPersistentModelIndex(model->index(i, 0))); } void QAbstractItemModelTesterPrivate::layoutChanged() { for (int i = 0; i < changing.count(); ++i) { QPersistentModelIndex p = changing[i]; MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p)); } changing.clear(); } /* Store what is about to be inserted to make sure it actually happens \sa rowsRemoved() */ void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { qCDebug(lcModelTest) << "rowsAboutToBeRemoved" << "start=" << start << "end=" << end << "parent=" << parent << "parent data=" << (parent.isValid() ? model->data(parent).toString() : QString{}) << "current count of parent=" << model->rowCount(parent) << "last before removal=" << (start > 1 ? model->index(start - 1, 0, parent) : QModelIndex{}) << (start > 1 ? model->data(model->index(start - 1, 0, parent)) : QVariant{}); Changing c; c.parent = parent; c.oldSize = model->rowCount(parent); c.last = (start > 1 ? model->data(model->index(start - 1, 0, parent)) : QVariant{}); c.next = (end < model->rowCount(parent) - 1 ? model->data(model->index(end + 1, 0, parent)) : QVariant{}); remove.push(c); } /* Confirm that what was said was going to happen actually did \sa rowsAboutToBeRemoved() */ void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end) { qCDebug(lcModelTest) << "rowsRemoved" << "start=" << start << "end=" << end << "parent=" << parent << "parent data=" << (parent.isValid() ? model->data(parent).toString() : QString{}) << "current count of parent=" << model->rowCount(parent); Changing c = remove.pop(); MODELTESTER_COMPARE(parent, c.parent); MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1)); MODELTESTER_COMPARE((start > 1 ? model->data(model->index(start - 1, 0, c.parent)) : QVariant{}), c.last); MODELTESTER_COMPARE((start < model->rowCount(parent) ? model->data(model->index(start, 0, c.parent)) : QVariant{}), c.next); } void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { MODELTESTER_VERIFY(topLeft.isValid()); MODELTESTER_VERIFY(bottomRight.isValid()); QModelIndex commonParent = bottomRight.parent(); MODELTESTER_COMPARE(topLeft.parent(), commonParent); MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row()); MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column()); int rowCount = model->rowCount(commonParent); int columnCount = model->columnCount(commonParent); MODELTESTER_VERIFY(bottomRight.row() < rowCount); MODELTESTER_VERIFY(bottomRight.column() < columnCount); } void QAbstractItemModelTesterPrivate::headerDataChanged(Qt::Orientation orientation, int start, int end) { MODELTESTER_VERIFY(start >= 0); MODELTESTER_VERIFY(end >= 0); MODELTESTER_VERIFY(start <= end); int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount(); MODELTESTER_VERIFY(start < itemCount); MODELTESTER_VERIFY(end < itemCount); } bool QAbstractItemModelTesterPrivate::verify(bool statement, const char *statementStr, const char *description, const char *file, int line) { static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)"; switch (failureReportingMode) { case QAbstractItemModelTester::FailureReportingMode::QtTest: return QTest::qVerify(statement, statementStr, description, file, line); break; case QAbstractItemModelTester::FailureReportingMode::Warning: if (!statement) qCWarning(lcModelTest, formatString, statementStr, description, file, line); break; case QAbstractItemModelTester::FailureReportingMode::Fatal: if (!statement) qFatal(formatString, statementStr, description, file, line); break; } return statement; } template bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2, const char *actual, const char *expected, const char *file, int line) { const bool result = static_cast(t1 == t2); static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)"; switch (failureReportingMode) { case QAbstractItemModelTester::FailureReportingMode::QtTest: return QTest::qCompare(t1, t2, actual, expected, file, line); break; case QAbstractItemModelTester::FailureReportingMode::Warning: if (!result) qCWarning(lcModelTest, formatString, actual, QTest::toString(t1), expected, QTest::toString(t2), file, line); break; case QAbstractItemModelTester::FailureReportingMode::Fatal: if (!result) qFatal(formatString, actual, QTest::toString(t1), expected, QTest::toString(t2), file, line); break; } return result; } QT_END_NAMESPACE diff --git a/autotests/qabstractitemmodeltester.h b/autotests/qabstractitemmodeltester.h index 1b6a74bf..e34b92e0 100644 --- a/autotests/qabstractitemmodeltester.h +++ b/autotests/qabstractitemmodeltester.h @@ -1,134 +1,134 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtTest module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** 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 Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** 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-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QABSTRACTITEMMODELTESTER_H #define QABSTRACTITEMMODELTESTER_H #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QAbstractItemModel; class QAbstractItemModelTester; class QAbstractItemModelTesterPrivate; namespace QTestPrivate { inline bool testDataGuiRoles(QAbstractItemModelTester *tester); } class QAbstractItemModelTester : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(QAbstractItemModelTester) public: enum class FailureReportingMode { QtTest, Warning, Fatal }; - QAbstractItemModelTester(QAbstractItemModel *model, Qt::ConnectionType type = Qt::AutoConnection, QObject *parent = nullptr); - QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, Qt::ConnectionType type = Qt::AutoConnection, QObject *parent = nullptr); + QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent = nullptr); + QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent = nullptr); QAbstractItemModel *model() const; FailureReportingMode failureReportingMode() const; private: friend inline bool QTestPrivate::testDataGuiRoles(QAbstractItemModelTester *tester); bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line); }; namespace QTestPrivate { inline bool testDataGuiRoles(QAbstractItemModelTester *tester) { #define MODELTESTER_VERIFY(statement) \ do { \ if (!tester->verify(static_cast(statement), #statement, "", __FILE__, __LINE__)) \ return false; \ } while (false) const auto model = tester->model(); Q_ASSERT(model); if (!model->hasChildren()) return true; QVariant variant; variant = model->data(model->index(0, 0), Qt::DecorationRole); if (variant.isValid()) { MODELTESTER_VERIFY(variant.canConvert() || variant.canConvert() || variant.canConvert() || variant.canConvert() || variant.canConvert()); } // General Purpose roles that should return a QFont variant = model->data(model->index(0, 0), Qt::FontRole); if (variant.isValid()) MODELTESTER_VERIFY(variant.canConvert()); // General Purpose roles that should return a QColor or a QBrush variant = model->data(model->index(0, 0), Qt::BackgroundColorRole); if (variant.isValid()) MODELTESTER_VERIFY(variant.canConvert() || variant.canConvert()); variant = model->data(model->index(0, 0), Qt::TextColorRole); if (variant.isValid()) MODELTESTER_VERIFY(variant.canConvert() || variant.canConvert()); #undef MODELTESTER_VERIFY return true; } } // namespaceQTestPrivate QT_END_NAMESPACE #endif // QABSTRACTITEMMODELTESTER_H diff --git a/src/models/allalbumsmodel.cpp b/src/models/allalbumsmodel.cpp index a8870934..db5510ab 100644 --- a/src/models/allalbumsmodel.cpp +++ b/src/models/allalbumsmodel.cpp @@ -1,345 +1,295 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "allalbumsmodel.h" #include "databaseinterface.h" #include "allartistsmodel.h" #include "albummodel.h" #include "elisautils.h" #include #include #include #include -#include -#include -#include -#include -#include +#include #include class AllAlbumsModelPrivate { public: - AllAlbumsModelPrivate() - { - mThreadPool.setMaxThreadCount(1); - } - - QVector mAllAlbums; - - QHash mAlbumsData; + QList mAllAlbums; AllArtistsModel *mAllArtistsModel = nullptr; - QReadWriteLock mDataLock; - - QThreadPool mThreadPool; - }; AllAlbumsModel::AllAlbumsModel(QObject *parent) : QAbstractItemModel(parent), d(std::make_unique()) { } AllAlbumsModel::~AllAlbumsModel() = default; int AllAlbumsModel::albumCount() const { - QReadLocker locker(&d->mDataLock); return d->mAllAlbums.size(); } int AllAlbumsModel::rowCount(const QModelIndex &parent) const { auto albumCount = 0; if (parent.isValid()) { return albumCount; } - QReadLocker locker(&d->mDataLock); - albumCount = d->mAllAlbums.size(); return albumCount; } QHash AllAlbumsModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ElisaUtils::ColumnsRoles::TitleRole)] = "title"; //roles[static_cast(ElisaUtils::ColumnsRoles::AllTracksTitleRole)] = "tracksTitle"; roles[static_cast(ElisaUtils::ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(ElisaUtils::ColumnsRoles::AllArtistsRole)] = "allArtists"; roles[static_cast(ElisaUtils::ColumnsRoles::ImageRole)] = "image"; //roles[static_cast(ElisaUtils::ColumnsRoles::CountRole)] = "count"; roles[static_cast(ElisaUtils::ColumnsRoles::IsSingleDiscAlbumRole)] = "isSingleDiscAlbum"; roles[static_cast(ElisaUtils::ColumnsRoles::ContainerDataRole)] = "containerData"; roles[static_cast(ElisaUtils::ColumnsRoles::HighestTrackRating)] = "highestTrackRating"; roles[static_cast(ElisaUtils::ColumnsRoles::DatabaseIdRole)] = "databaseId"; roles[static_cast(ElisaUtils::ColumnsRoles::SecondaryTextRole)] = "secondaryText"; roles[static_cast(ElisaUtils::ColumnsRoles::ImageUrlRole)] = "imageUrl"; roles[static_cast(ElisaUtils::ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; roles[static_cast(ElisaUtils::ColumnsRoles::ChildModelRole)] = "childModel"; //roles[static_cast(ElisaUtils::ColumnsRoles::IsTracksContainerRole)] = "isTracksContainer"; return roles; } Qt::ItemFlags AllAlbumsModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant AllAlbumsModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); - QReadLocker locker(&d->mDataLock); - const auto albumCount = d->mAllAlbums.size(); Q_ASSERT(index.isValid()); Q_ASSERT(index.column() == 0); Q_ASSERT(index.row() >= 0 && index.row() < albumCount); Q_ASSERT(!index.parent().isValid()); Q_ASSERT(index.model() == this); Q_ASSERT(index.internalId() == 0); result = internalDataAlbum(index.row(), role); return result; } QVariant AllAlbumsModel::internalDataAlbum(int albumIndex, int role) const { auto result = QVariant(); switch(role) { case Qt::DisplayRole: case ElisaUtils::ColumnsRoles::TitleRole: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].title(); + result = d->mAllAlbums[albumIndex].title(); break; - /*case ElisaUtils::ColumnsRoles::AllTracksTitleRole: + /*case ElisaUtils::ColumnsRoles::AllTracksTitleRole: result = d->mAlbumsData[d->mAllAlbums[albumIndex]].allTracksTitle(); break;*/ case ElisaUtils::ColumnsRoles::ArtistRole: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].artist(); + result = d->mAllAlbums[albumIndex].artist(); break; case ElisaUtils::ColumnsRoles::AllArtistsRole: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].allArtists().join(QStringLiteral(", ")); + result = d->mAllAlbums[albumIndex].allArtists().join(QStringLiteral(", ")); break; case ElisaUtils::ColumnsRoles::ImageRole: { - auto albumArt = d->mAlbumsData[d->mAllAlbums[albumIndex]].albumArtURI(); + auto albumArt = d->mAllAlbums[albumIndex].albumArtURI(); if (albumArt.isValid()) { result = albumArt; } break; } - /*case ElisaUtils::ColumnsRoles::CountRole: + /*case ElisaUtils::ColumnsRoles::CountRole: result = d->mAlbumsData[d->mAllAlbums[albumIndex]].tracksCount(); break;*/ case ElisaUtils::ColumnsRoles::IdRole: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].id(); + result = d->mAllAlbums[albumIndex].id(); break; case ElisaUtils::ColumnsRoles::IsSingleDiscAlbumRole: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].isSingleDiscAlbum(); + result = d->mAllAlbums[albumIndex].isSingleDiscAlbum(); break; case ElisaUtils::ColumnsRoles::ContainerDataRole: - result = QVariant::fromValue(d->mAlbumsData[d->mAllAlbums[albumIndex]]); + result = QVariant::fromValue(d->mAllAlbums[albumIndex]); break; case ElisaUtils::ColumnsRoles::DatabaseIdRole: - result = QVariant::fromValue(d->mAlbumsData[d->mAllAlbums[albumIndex]].databaseId()); + result = QVariant::fromValue(d->mAllAlbums[albumIndex].databaseId()); break; case ElisaUtils::ColumnsRoles::HighestTrackRating: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].highestTrackRating(); + result = d->mAllAlbums[albumIndex].highestTrackRating(); break; case ElisaUtils::ColumnsRoles::SecondaryTextRole: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].artist(); + result = d->mAllAlbums[albumIndex].artist(); break; case ElisaUtils::ColumnsRoles::ImageUrlRole: { - auto albumArt = d->mAlbumsData[d->mAllAlbums[albumIndex]].albumArtURI(); + auto albumArt = d->mAllAlbums[albumIndex].albumArtURI(); if (albumArt.isValid()) { result = albumArt; } else { result = QUrl(QStringLiteral("image://icon/media-optical-audio")); } break; } case ElisaUtils::ColumnsRoles::ShadowForImageRole: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].albumArtURI().isValid(); + result = d->mAllAlbums[albumIndex].albumArtURI().isValid(); break; case ElisaUtils::ColumnsRoles::ChildModelRole: { - auto albumData = d->mAlbumsData[d->mAllAlbums[albumIndex]]; + auto albumData = d->mAllAlbums[albumIndex]; result = QVariant::fromValue(albumData); break; } case ElisaUtils::ColumnsRoles::GenreRole: - result = d->mAlbumsData[d->mAllAlbums[albumIndex]].genres(); + result = d->mAllAlbums[albumIndex].genres(); break; - /*case ElisaUtils::ColumnsRoles::IsTracksContainerRole: + /*case ElisaUtils::ColumnsRoles::IsTracksContainerRole: result = true; break;*/ } return result; } QModelIndex AllAlbumsModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } result = createIndex(row, column); return result; } QModelIndex AllAlbumsModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } int AllAlbumsModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } AllArtistsModel *AllAlbumsModel::allArtists() const { return d->mAllArtistsModel; } -void AllAlbumsModel::albumsAdded(const QList &newAlbums) +void AllAlbumsModel::albumsAdded(QList newAlbums) { - QtConcurrent::run(&d->mThreadPool, [=] () { - for (const auto &newAlbum : newAlbums) { - if (newAlbum.isValid()) { - beginInsertRows({}, d->mAllAlbums.size(), d->mAllAlbums.size()); - - { - QWriteLocker locker(&d->mDataLock); - - d->mAllAlbums.push_back(newAlbum.databaseId()); - d->mAlbumsData[newAlbum.databaseId()] = newAlbum; - } - - endInsertRows(); + if (d->mAllAlbums.isEmpty()) { + beginInsertRows({}, d->mAllAlbums.size(), d->mAllAlbums.size() + newAlbums.size() - 1); + d->mAllAlbums.swap(newAlbums); + endInsertRows(); + } else { + beginInsertRows({}, d->mAllAlbums.size(), d->mAllAlbums.size() + newAlbums.size() - 1); + d->mAllAlbums.append(newAlbums); + endInsertRows(); + } - Q_EMIT albumCountChanged(); - } - } - }); + Q_EMIT albumCountChanged(); } void AllAlbumsModel::albumRemoved(const MusicAlbum &removedAlbum) { - QtConcurrent::run(&d->mThreadPool, [=] () { - auto removedAlbumIterator = d->mAllAlbums.end(); - - { - QReadLocker locker(&d->mDataLock); + auto removedAlbumIterator = d->mAllAlbums.end(); - removedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), removedAlbum.databaseId()); + removedAlbumIterator = std::find_if(d->mAllAlbums.begin(), d->mAllAlbums.end(), + [removedAlbum](auto album) {return album.databaseId() == removedAlbum.databaseId();}); - if (removedAlbumIterator == d->mAllAlbums.end()) { - return; - } + if (removedAlbumIterator == d->mAllAlbums.end()) { + return; + } - int albumIndex = removedAlbumIterator - d->mAllAlbums.begin(); + int albumIndex = removedAlbumIterator - d->mAllAlbums.begin(); - beginRemoveRows({}, albumIndex, albumIndex); + beginRemoveRows({}, albumIndex, albumIndex); - } + d->mAllAlbums.erase(removedAlbumIterator); - { - QWriteLocker writeLocker(&d->mDataLock); - d->mAlbumsData.remove(removedAlbum.databaseId()); - d->mAllAlbums.erase(removedAlbumIterator); - } + endRemoveRows(); - endRemoveRows(); - - Q_EMIT albumCountChanged(); - }); + Q_EMIT albumCountChanged(); } void AllAlbumsModel::albumModified(const MusicAlbum &modifiedAlbum) { - QtConcurrent::run(&d->mThreadPool, [=] () { - auto modifiedAlbumIterator = d->mAllAlbums.end(); - - { - QReadLocker locker(&d->mDataLock); + auto modifiedAlbumIterator = std::find_if(d->mAllAlbums.begin(), d->mAllAlbums.end(), + [modifiedAlbum](auto album) {return album.databaseId() == modifiedAlbum.databaseId();}); - modifiedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), modifiedAlbum.databaseId()); - } - - int albumIndex = 0; - - { - QWriteLocker writeLocker(&d->mDataLock); - - if (modifiedAlbumIterator == d->mAllAlbums.end()) { - return; - } + if (modifiedAlbumIterator == d->mAllAlbums.end()) { + return; + } - albumIndex = modifiedAlbumIterator - d->mAllAlbums.begin(); + auto albumIndex = modifiedAlbumIterator - d->mAllAlbums.begin(); - d->mAlbumsData[modifiedAlbum.databaseId()] = modifiedAlbum; - } + d->mAllAlbums[albumIndex] = modifiedAlbum; - Q_EMIT dataChanged(index(albumIndex, 0), index(albumIndex, 0)); - }); + Q_EMIT dataChanged(index(albumIndex, 0), index(albumIndex, 0)); } void AllAlbumsModel::setAllArtists(AllArtistsModel *model) { if (d->mAllArtistsModel == model) { return; } d->mAllArtistsModel = model; Q_EMIT allArtistsChanged(); } #include "moc_allalbumsmodel.cpp" diff --git a/src/models/allalbumsmodel.h b/src/models/allalbumsmodel.h index 88f603e2..56417669 100644 --- a/src/models/allalbumsmodel.h +++ b/src/models/allalbumsmodel.h @@ -1,97 +1,97 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ALLALBUMSMODEL_H #define ALLALBUMSMODEL_H #include "elisaLib_export.h" #include #include #include #include #include "musicalbum.h" #include "musicaudiotrack.h" #include class AllAlbumsModelPrivate; class AllArtistsModel; class ELISALIB_EXPORT AllAlbumsModel : public QAbstractItemModel { Q_OBJECT Q_PROPERTY(int albumCount READ albumCount NOTIFY albumCountChanged) Q_PROPERTY(AllArtistsModel* allArtists READ allArtists WRITE setAllArtists NOTIFY allArtistsChanged) public: explicit AllAlbumsModel(QObject *parent = nullptr); ~AllAlbumsModel() override; Q_INVOKABLE int albumCount() const; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; AllArtistsModel *allArtists() const; public Q_SLOTS: - void albumsAdded(const QList &newAlbums); + void albumsAdded(QList newAlbums); void albumRemoved(const MusicAlbum &removedAlbum); void albumModified(const MusicAlbum &modifiedAlbum); void setAllArtists(AllArtistsModel *model); Q_SIGNALS: void albumCountChanged(); void allArtistsChanged(); private: QVariant internalDataAlbum(int albumIndex, int role) const; std::unique_ptr d; }; #endif // ALLALBUMSMODEL_H