/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd ** All rights reserved. ** For any questions to The Qt Company, please use contact form at ** http://www.qt.io/contact-us ** ** This file is part of the Qt Creator Enterprise Auto Test Add-on. ** ** Licensees holding valid Qt Enterprise licenses may use this file in ** accordance with the Qt Enterprise License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. ** ** If you have questions regarding the use of this file, please use ** contact form at http://www.qt.io/contact-us ** ****************************************************************************/ #include "autotestconstants.h" #include "testcodeparser.h" #include "testtreeitem.h" #include "testtreemodel.h" #include #include #include #include #include #include #include namespace Autotest { namespace Internal { TestTreeModel::TestTreeModel(QObject *parent) : QAbstractItemModel(parent), m_rootItem(new TestTreeItem(QString(), QString(), TestTreeItem::ROOT)), m_autoTestRootItem(new TestTreeItem(tr("Auto Tests"), QString(), TestTreeItem::ROOT)), m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::ROOT)), m_parser(new TestCodeParser(this)), m_connectionsInitialized(false) { m_rootItem->appendChild(m_autoTestRootItem); m_rootItem->appendChild(m_quickTestRootItem); connect(m_parser, &TestCodeParser::cacheCleared, this, &TestTreeModel::removeAllTestItems, Qt::QueuedConnection); connect(m_parser, &TestCodeParser::testItemCreated, this, &TestTreeModel::addTestTreeItem, Qt::QueuedConnection); connect(m_parser, &TestCodeParser::testItemsCreated, this, &TestTreeModel::addTestTreeItems, Qt::QueuedConnection); connect(m_parser, &TestCodeParser::testItemModified, this, &TestTreeModel::modifyTestTreeItem, Qt::QueuedConnection); connect(m_parser, &TestCodeParser::testItemsRemoved, this, &TestTreeModel::removeTestTreeItems, Qt::QueuedConnection); connect(m_parser, &TestCodeParser::unnamedQuickTestsUpdated, this, &TestTreeModel::updateUnnamedQuickTest, Qt::QueuedConnection); connect(m_parser, &TestCodeParser::unnamedQuickTestsRemoved, this, &TestTreeModel::removeUnnamedQuickTests, Qt::QueuedConnection); // CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance(); // if (cppMM) { // // replace later on by // // cppMM->registerAstProcessor([this](const CplusPlus::Document::Ptr &doc, // // const CPlusPlus::Snapshot &snapshot) { // // checkForQtTestStuff(doc, snapshot); // // }); // connect(cppMM, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)), // this, SLOT(checkForQtTestStuff(CPlusPlus::Document::Ptr)), // Qt::DirectConnection); // } } static TestTreeModel *m_instance = 0; TestTreeModel *TestTreeModel::instance() { if (!m_instance) m_instance = new TestTreeModel; return m_instance; } TestTreeModel::~TestTreeModel() { delete m_rootItem; m_instance = 0; } void TestTreeModel::enableParsing() { m_refCounter.ref(); if (!m_connectionsInitialized) m_parser->setDirty(); m_parser->setState(TestCodeParser::Idle); if (m_connectionsInitialized) return; ProjectExplorer::SessionManager *sm = ProjectExplorer::SessionManager::instance(); connect(sm, &ProjectExplorer::SessionManager::startupProjectChanged, m_parser, &TestCodeParser::onStartupProjectChanged); CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); connect(cppMM, &CppTools::CppModelManager::documentUpdated, m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection); connect(cppMM, &CppTools::CppModelManager::aboutToRemoveFiles, m_parser, &TestCodeParser::removeFiles, Qt::QueuedConnection); connect(cppMM, &CppTools::CppModelManager::projectPartsUpdated, m_parser, &TestCodeParser::onProjectPartsUpdated); QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance(); connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated, m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection); connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles, m_parser, &TestCodeParser::removeFiles, Qt::QueuedConnection); m_connectionsInitialized = true; } void TestTreeModel::disableParsing() { if (!m_refCounter.deref()) m_parser->setState(TestCodeParser::Disabled); } QModelIndex TestTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); TestTreeItem *parentItem = parent.isValid() ? static_cast(parent.internalPointer()) : m_rootItem; TestTreeItem *childItem = parentItem->child(row); return childItem ? createIndex(row, column, childItem) : QModelIndex(); } QModelIndex TestTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); TestTreeItem *childItem = static_cast(index.internalPointer()); TestTreeItem *parentItem = childItem->parent(); if (parentItem == m_rootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } bool TestTreeModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) return true; TestTreeItem *item = static_cast(parent.internalPointer()); return item->childCount() > 0; } int TestTreeModel::rowCount(const QModelIndex &parent) const { TestTreeItem *parentItem = parent.isValid() ? static_cast(parent.internalPointer()) : m_rootItem; return parentItem->childCount(); } int TestTreeModel::columnCount(const QModelIndex &) const { return 1; } static QIcon testTreeIcon(TestTreeItem::Type type) { static QIcon icons[] = { QIcon(), QIcon(QLatin1String(":/images/class.png")), QIcon(QLatin1String(":/images/func.png")), QIcon(QLatin1String(":/images/data.png")) }; if (type >= sizeof(icons) / sizeof(icons[0])) return icons[2]; return icons[type]; } QVariant TestTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); TestTreeItem *item = static_cast(index.internalPointer()); if (!item) return QVariant(); if (role == Qt::DisplayRole) { if ((item == m_autoTestRootItem && m_autoTestRootItem->childCount() == 0) || (item == m_quickTestRootItem && m_quickTestRootItem->childCount() == 0)) { return QString(item->name() + tr(" (none)")); } else { if (item->name().isEmpty() && item->type() == TestTreeItem::TEST_CLASS) return tr(Constants::UNNAMED_QUICKTESTS); return item->name(); } return QVariant(); // TODO ? } switch(role) { case Qt::ToolTipRole: if (item->type() == TestTreeItem::TEST_CLASS && item->name().isEmpty()) return tr("

Give all test cases a name to ensure correct behavior " "when running test cases and to be able to select them.

"); return item->filePath(); case Qt::DecorationRole: return testTreeIcon(item->type()); case Qt::CheckStateRole: switch (item->type()) { case TestTreeItem::ROOT: case TestTreeItem::TEST_DATATAG: case TestTreeItem::TEST_DATAFUNCTION: case TestTreeItem::TEST_SPECIALFUNCTION: return QVariant(); case TestTreeItem::TEST_CLASS: if (item->name().isEmpty()) return QVariant(); else return item->checked(); case TestTreeItem::TEST_FUNCTION: if (TestTreeItem *parent = item->parent()) return parent->name().isEmpty() ? QVariant() : item->checked(); else return item->checked(); default: return item->checked(); } case LinkRole: { QVariant itemLink; TextEditor::TextEditorWidget::Link link(item->filePath(), item->line(), item->column()); itemLink.setValue(link); return itemLink; } case ItalicRole: switch (item->type()) { case TestTreeItem::TEST_DATAFUNCTION: case TestTreeItem::TEST_SPECIALFUNCTION: return true; case TestTreeItem::TEST_CLASS: return item->name().isEmpty(); case TestTreeItem::TEST_FUNCTION: if (TestTreeItem *parent = item->parent()) return parent->name().isEmpty(); else return false; default: return false; } case TypeRole: return item->type(); } // TODO ? return QVariant(); } bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; if (role == Qt::CheckStateRole) { TestTreeItem *item = static_cast(index.internalPointer()); Qt::CheckState old = item->checked(); item->setChecked((Qt::CheckState)value.toInt()); if (item->checked() != old) { switch(item->type()) { case TestTreeItem::TEST_CLASS: emit dataChanged(index, index); if (item->childCount() > 0) { emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0)); } break; case TestTreeItem::TEST_FUNCTION: emit dataChanged(index, index); emit dataChanged(index.parent(), index.parent()); break; default: // avoid warning regarding unhandled enum member break; } return true; } } return false; } Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; TestTreeItem *item = static_cast(index.internalPointer()); switch(item->type()) { case TestTreeItem::TEST_CLASS: if (item->name().isEmpty()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable; case TestTreeItem::TEST_FUNCTION: if (item->parent()->name().isEmpty()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; case TestTreeItem::ROOT: return Qt::ItemIsEnabled; case TestTreeItem::TEST_DATAFUNCTION: case TestTreeItem::TEST_SPECIALFUNCTION: default: return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } } bool TestTreeModel::removeRows(int row, int count, const QModelIndex &parent) { if (!parent.isValid()) return false; TestTreeItem *parentItem = static_cast(parent.internalPointer()); if (!parentItem) return false; bool subItemsSuccess = true; bool itemSuccess = true; for (int i = row + count - 1; i >= row; --i) { QModelIndex child = index(i, 0, parent); subItemsSuccess &= removeRows(0, rowCount(child), child); beginRemoveRows(parent, i, i); itemSuccess &= parentItem->removeChild(i); endRemoveRows(); } return subItemsSuccess && itemSuccess; } bool TestTreeModel::hasTests() const { return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0; } QList TestTreeModel::getAllTestCases() const { QList result; ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); if (!project) return result; // get all Auto Tests for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) { const TestTreeItem *child = m_autoTestRootItem->child(row); TestConfiguration *tc = new TestConfiguration(child->name(), QStringList(), child->childCount()); tc->setMainFilePath(child->filePath()); tc->setProject(project); result << tc; } // get all Quick Tests QMap foundMains; for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { TestTreeItem *child = m_quickTestRootItem->child(row); // unnamed Quick Tests must be handled separately if (child->name().isEmpty()) { for (int childRow = 0, ccount = child->childCount(); childRow < ccount; ++ childRow) { const TestTreeItem *grandChild = child->child(childRow); const QString mainFile = grandChild->mainFile(); foundMains.insert(mainFile, foundMains.contains(mainFile) ? foundMains.value(mainFile) + 1 : 1); } continue; } // named Quick Test const QString mainFile = child->mainFile(); foundMains.insert(mainFile, foundMains.contains(mainFile) ? foundMains.value(mainFile) + child->childCount() : child->childCount()); } // create TestConfiguration for each main foreach (const QString &mainFile, foundMains.keys()) { TestConfiguration *tc = new TestConfiguration(QString(), QStringList(), foundMains.value(mainFile)); tc->setMainFilePath(mainFile); tc->setProject(project); result << tc; } return result; } QList TestTreeModel::getSelectedTests() const { QList result; ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); if (!project) return result; TestConfiguration *testConfiguration = 0; for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) { TestTreeItem *child = m_autoTestRootItem->child(row); switch (child->checked()) { case Qt::Unchecked: continue; case Qt::Checked: testConfiguration = new TestConfiguration(child->name(), QStringList(), child->childCount()); testConfiguration->setMainFilePath(child->filePath()); testConfiguration->setProject(project); result << testConfiguration; continue; case Qt::PartiallyChecked: default: const QString childName = child->name(); int grandChildCount = child->childCount(); QStringList testCases; for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) { const TestTreeItem *grandChild = child->child(grandChildRow); if (grandChild->checked() == Qt::Checked) testCases << grandChild->name(); } testConfiguration = new TestConfiguration(childName, testCases); testConfiguration->setMainFilePath(child->filePath()); testConfiguration->setProject(project); result << testConfiguration; } } // Quick Tests must be handled differently - need the calling cpp file to use this in // addProjectInformation() - additionally this must be unique to not execute the same executable // on and on and on... // TODO: do this later on for Auto Tests as well to support strange setups? or redo the model QMap foundMains; if (TestTreeItem *unnamed = unnamedQuickTests()) { for (int childRow = 0, ccount = unnamed->childCount(); childRow < ccount; ++ childRow) { const TestTreeItem *grandChild = unnamed->child(childRow); const QString mainFile = grandChild->mainFile(); if (foundMains.contains(mainFile)) { QTC_ASSERT(testConfiguration, qWarning() << "Illegal state (unnamed Quick Test listed as named)"; return QList()); foundMains[mainFile]->setTestCaseCount(testConfiguration->testCaseCount() + 1); } else { testConfiguration = new TestConfiguration(QString(), QStringList()); testConfiguration->setTestCaseCount(1); testConfiguration->setUnnamedOnly(true); testConfiguration->setMainFilePath(mainFile); testConfiguration->setProject(project); foundMains.insert(mainFile, testConfiguration); } } } for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { TestTreeItem *child = m_quickTestRootItem->child(row); // unnamed Quick Tests have been handled separately already if (child->name().isEmpty()) continue; // named Quick Tests switch (child->checked()) { case Qt::Unchecked: continue; case Qt::Checked: case Qt::PartiallyChecked: default: QStringList testFunctions; int grandChildCount = child->childCount(); for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) { const TestTreeItem *grandChild = child->child(grandChildRow); if (grandChild->type() != TestTreeItem::TEST_FUNCTION) continue; if (grandChild->checked() == Qt::Checked) testFunctions << child->name() + QLatin1String("::") + grandChild->name(); } TestConfiguration *tc; if (foundMains.contains(child->mainFile())) { tc = foundMains[child->mainFile()]; QStringList oldFunctions(tc->testCases()); // if oldFunctions.size() is 0 this test configuration is used for at least one // unnamed test case if (oldFunctions.size() == 0) { tc->setTestCaseCount(tc->testCaseCount() + testFunctions.size()); tc->setUnnamedOnly(false); } else { oldFunctions << testFunctions; tc->setTestCases(oldFunctions); } } else { tc = new TestConfiguration(QString(), testFunctions); tc->setMainFilePath(child->mainFile()); tc->setProject(project); foundMains.insert(child->mainFile(), tc); } break; } } foreach (TestConfiguration *config, foundMains.values()) if (!config->unnamedOnly()) result << config; return result; } TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item) const { QTC_ASSERT(item != 0, return 0); ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); QTC_ASSERT(project, return 0); TestConfiguration *config = 0; switch (item->type()) { case TestTreeItem::TEST_CLASS: { if (item->parent() == m_quickTestRootItem) { // Quick Test TestCase QStringList testFunctions; for (int row = 0, count = item->childCount(); row < count; ++row) { testFunctions << item->name() + QLatin1String("::") + item->child(row)->name(); } config = new TestConfiguration(QString(), testFunctions); config->setMainFilePath(item->mainFile()); config->setProject(project); } else { // normal auto test config = new TestConfiguration(item->name(), QStringList(), item->childCount()); config->setMainFilePath(item->filePath()); config->setProject(project); } break; } case TestTreeItem::TEST_FUNCTION: { const TestTreeItem *parent = item->parent(); if (parent->parent() == m_quickTestRootItem) { // it's a Quick Test function of a named TestCase QStringList testFunction(parent->name() + QLatin1String("::") + item->name()); config = new TestConfiguration(QString(), testFunction); config->setMainFilePath(parent->mainFile()); config->setProject(project); } else { // normal auto test config = new TestConfiguration(parent->name(), QStringList() << item->name()); config->setMainFilePath(parent->filePath()); config->setProject(project); } break; } case TestTreeItem::TEST_DATATAG: { const TestTreeItem *function = item->parent(); const TestTreeItem *parent = function ? function->parent() : 0; if (!parent) return 0; const QString functionWithTag = function->name() + QLatin1Char(':') + item->name(); config = new TestConfiguration(parent->name(), QStringList() << functionWithTag); config->setMainFilePath(parent->filePath()); config->setProject(project); break; } // not supported items default: return 0; } return config; } QString TestTreeModel::getMainFileForUnnamedQuickTest(const QString &qmlFile) const { const TestTreeItem *unnamed = unnamedQuickTests(); const int count = unnamed ? unnamed->childCount() : 0; for (int row = 0; row < count; ++row) { const TestTreeItem *child = unnamed->child(row); if (qmlFile == child->filePath()) return child->mainFile(); } return QString(); } void TestTreeModel::qmlFilesForMainFile(const QString &mainFile, QSet *filePaths) const { TestTreeItem *unnamed = unnamedQuickTests(); if (!unnamed) return; for (int i = 0; i < unnamed->childCount(); ++i) { const TestTreeItem *child = unnamed->child(i); if (child->mainFile() == mainFile) filePaths->insert(child->filePath()); } } QList TestTreeModel::getUnnamedQuickTestFunctions() const { TestTreeItem *unnamed = unnamedQuickTests(); if (unnamed) return unnamed->getChildNames(); return QList(); } QSet TestTreeModel::qmlFilesForProFile(const QString &proFile) const { QSet filePaths; CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); if (TestTreeItem *unnamed = unnamedQuickTests()) { for (int i = 0; i < unnamed->childCount(); ++i) { const TestTreeItem *child = unnamed->child(i); QList ppList = modelManager->projectPart(child->mainFile()); if (ppList.size() && ppList.at(0)->projectFile == proFile) filePaths.insert(child->filePath()); } } return filePaths; } bool TestTreeModel::hasUnnamedQuickTests() const { for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) if (m_quickTestRootItem->child(row)->name().isEmpty()) return true; return false; } TestTreeItem *TestTreeModel::unnamedQuickTests() const { for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { TestTreeItem *child = m_quickTestRootItem->child(row); if (child->name().isEmpty()) return child; } return 0; } void TestTreeModel::removeUnnamedQuickTests(const QString &filePath) { TestTreeItem *unnamedQT = unnamedQuickTests(); if (!unnamedQT) return; const QModelIndex unnamedQTIndex = index(1, 0).child(unnamedQT->row(), 0); for (int childRow = unnamedQT->childCount() - 1; childRow >= 0; --childRow) { const TestTreeItem *child = unnamedQT->child(childRow); if (filePath == child->filePath()) removeRow(childRow, unnamedQTIndex); } if (unnamedQT->childCount() == 0) removeRow(unnamedQT->row(), unnamedQTIndex.parent()); emit testTreeModelChanged(); } void TestTreeModel::addTestTreeItem(const TestTreeItem &item, TestTreeModel::Type type) { TestTreeItem *parent = rootItemForType(type); QModelIndex index = rootIndexForType(type); TestTreeItem *toBeAdded = new TestTreeItem(item); beginInsertRows(index, parent->childCount(), parent->childCount()); parent->appendChild(toBeAdded); endInsertRows(); emit testTreeModelChanged(); } void TestTreeModel::addTestTreeItems(const QList &itemList, TestTreeModel::Type type) { TestTreeItem *parent = rootItemForType(type); QModelIndex index = rootIndexForType(type); beginInsertRows(index, parent->childCount(), parent->childCount() + itemList.size() - 1); foreach (const TestTreeItem &item, itemList) { TestTreeItem *toBeAdded = new TestTreeItem(item); parent->appendChild(toBeAdded); } endInsertRows(); emit testTreeModelChanged(); } void TestTreeModel::updateUnnamedQuickTest(const QString &fileName, const QString &mainFile, const QMap &functions) { removeUnnamedQuickTests(fileName); TestTreeItem unnamed = hasUnnamedQuickTests() ? TestTreeItem(*unnamedQuickTests()) : TestTreeItem(QString(), QString(), TestTreeItem::TEST_CLASS); foreach (const QString &functionName, functions.keys()) { const TestCodeLocationAndType locationAndType = functions.value(functionName); TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_name, locationAndType.m_type); testFunction->setLine(locationAndType.m_line); testFunction->setColumn(locationAndType.m_column); testFunction->setMainFile(mainFile); unnamed.appendChild(testFunction); } if (hasUnnamedQuickTests()) modifyTestTreeItem(unnamed, QuickTest, QString()); else addTestTreeItem(unnamed, QuickTest); } void TestTreeModel::modifyTestTreeItem(TestTreeItem item, TestTreeModel::Type type, const QString &file) { QModelIndex index = rootIndexForType(type); TestTreeItem *parent = rootItemForType(type); if (file.isEmpty()) { if (TestTreeItem *unnamed = unnamedQuickTests()) { index = index.child(unnamed->row(), 0); modifyTestSubtree(index, item); } } else { for (int row = 0; row < parent->childCount(); ++row) { if (parent->child(row)->filePath() == file) { index = index.child(row, 0); modifyTestSubtree(index, item); break; } } } } void TestTreeModel::removeAllTestItems() { beginResetModel(); m_autoTestRootItem->removeChildren(); m_quickTestRootItem->removeChildren(); endResetModel(); emit testTreeModelChanged(); } void TestTreeModel::removeTestTreeItems(const QString &filePath, Type type) { bool removed = false; const QModelIndex rootIndex = rootIndexForType(type); const int count = rowCount(rootIndex); for (int row = count - 1; row >= 0; --row) { const QModelIndex childIndex = rootIndex.child(row, 0); TestTreeItem *childItem = static_cast(childIndex.internalPointer()); if (filePath == childItem->filePath()) removed |= removeRow(row, rootIndex); } if (removed) emit testTreeModelChanged(); } TestTreeItem *TestTreeModel::rootItemForType(TestTreeModel::Type type) { switch (type) { case AutoTest: return m_autoTestRootItem; case QuickTest: return m_quickTestRootItem; } QTC_ASSERT(false, return 0); } QModelIndex TestTreeModel::rootIndexForType(TestTreeModel::Type type) { switch (type) { case AutoTest: return index(0, 0); case QuickTest: return index(1, 0); } QTC_ASSERT(false, return QModelIndex()); } void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, const TestTreeItem &newItem) { if (!toBeModifiedIndex.isValid()) return; TestTreeItem *toBeModifiedItem = static_cast(toBeModifiedIndex.internalPointer()); if (toBeModifiedItem->modifyContent(&newItem)) emit dataChanged(toBeModifiedIndex, toBeModifiedIndex, QVector() << Qt::DisplayRole << Qt::ToolTipRole << LinkRole); // process sub-items as well... const int childCount = toBeModifiedItem->childCount(); const int newChildCount = newItem.childCount(); // for keeping the CheckState on modifications // TODO might still fail for duplicate entries QHash checkStates; for (int row = 0; row < childCount; ++row) { const TestTreeItem *child = toBeModifiedItem->child(row); checkStates.insert(child->name(), child->checked()); } if (childCount <= newChildCount) { processChildren(toBeModifiedIndex, newItem, childCount, checkStates); // add additional items for (int row = childCount; row < newChildCount; ++row) { TestTreeItem *newChild = newItem.child(row); TestTreeItem *toBeAdded = new TestTreeItem(*newChild); if (checkStates.contains(toBeAdded->name()) && checkStates.value(toBeAdded->name()) != Qt::Checked) toBeAdded->setChecked(checkStates.value(toBeAdded->name())); beginInsertRows(toBeModifiedIndex, row, row); toBeModifiedItem->appendChild(toBeAdded); endInsertRows(); } } else { processChildren(toBeModifiedIndex, newItem, newChildCount, checkStates); // remove rest of the items removeRows(newChildCount, childCount - newChildCount, toBeModifiedIndex); } emit testTreeModelChanged(); } void TestTreeModel::processChildren(QModelIndex &parentIndex, const TestTreeItem &newItem, const int upperBound, const QHash &checkStates) { static QVector modificationRoles = QVector() << Qt::DisplayRole << Qt::ToolTipRole << LinkRole; TestTreeItem *toBeModifiedItem = static_cast(parentIndex.internalPointer()); for (int row = 0; row < upperBound; ++row) { QModelIndex child = parentIndex.child(row, 0); TestTreeItem *toBeModifiedChild = toBeModifiedItem->child(row); TestTreeItem *modifiedChild = newItem.child(row); if (toBeModifiedChild->modifyContent(modifiedChild)) emit dataChanged(child, child, modificationRoles); // handle data tags - just remove old and add them if (modifiedChild->childCount() || toBeModifiedChild->childCount()) { beginRemoveRows(child, 0, toBeModifiedChild->childCount()); toBeModifiedChild->removeChildren(); endRemoveRows(); const int count = modifiedChild->childCount(); beginInsertRows(child, 0, count); for (int childRow = 0; childRow < count; ++childRow) toBeModifiedChild->appendChild(new TestTreeItem(*modifiedChild->child(childRow))); endInsertRows(); } if (checkStates.contains(toBeModifiedChild->name())) { Qt::CheckState state = checkStates.value(toBeModifiedChild->name()); if (state != toBeModifiedChild->checked()) { toBeModifiedChild->setChecked(state); emit dataChanged(child, child, QVector() << Qt::CheckStateRole); } } else { // newly added (BAD: happens for renaming as well) toBeModifiedChild->setChecked(Qt::Checked); emit dataChanged(child, child, QVector() << Qt::CheckStateRole); } } } #ifdef WITH_TESTS int TestTreeModel::autoTestsCount() const { return m_autoTestRootItem ? m_autoTestRootItem->childCount() : 0; } int TestTreeModel::namedQuickTestsCount() const { return m_quickTestRootItem ? m_quickTestRootItem->childCount() - (hasUnnamedQuickTests() ? 1 : 0) : 0; } int TestTreeModel::unnamedQuickTestsCount() const { if (TestTreeItem *unnamed = unnamedQuickTests()) return unnamed->childCount(); return 0; } #endif /***************************** Sort/Filter Model **********************************/ TestTreeSortFilterModel::TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent) : QSortFilterProxyModel(parent), m_sourceModel(sourceModel), m_sortMode(Alphabetically), m_filterMode(Basic) { setSourceModel(sourceModel); } void TestTreeSortFilterModel::setSortMode(SortMode sortMode) { m_sortMode = sortMode; invalidate(); } void TestTreeSortFilterModel::setFilterMode(FilterMode filterMode) { m_filterMode = filterMode; invalidateFilter(); } void TestTreeSortFilterModel::toggleFilter(FilterMode filterMode) { m_filterMode = toFilterMode(m_filterMode ^ filterMode); invalidateFilter(); } TestTreeSortFilterModel::FilterMode TestTreeSortFilterModel::toFilterMode(int f) { switch (f) { case TestTreeSortFilterModel::ShowInitAndCleanup: return TestTreeSortFilterModel::ShowInitAndCleanup; case TestTreeSortFilterModel::ShowTestData: return TestTreeSortFilterModel::ShowTestData; case TestTreeSortFilterModel::ShowAll: return TestTreeSortFilterModel::ShowAll; default: return TestTreeSortFilterModel::Basic; } } bool TestTreeSortFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { // root items keep the intended order: 1st Auto Tests, 2nd Quick Tests const TestTreeItem *leftItem = static_cast(left.internalPointer()); if (leftItem->type() == TestTreeItem::ROOT) return left.row() > right.row(); const QString leftVal = m_sourceModel->data(left).toString(); const QString rightVal = m_sourceModel->data(right).toString(); // unnamed Quick Tests will always be listed first if (leftVal == tr(Constants::UNNAMED_QUICKTESTS)) return false; if (rightVal == tr(Constants::UNNAMED_QUICKTESTS)) return true; switch (m_sortMode) { case Alphabetically: if (leftVal == rightVal) return left.row() > right.row(); return leftVal > rightVal; case Naturally: { const TextEditor::TextEditorWidget::Link leftLink = m_sourceModel->data(left, LinkRole).value(); const TextEditor::TextEditorWidget::Link rightLink = m_sourceModel->data(right, LinkRole).value(); if (leftLink.targetFileName == rightLink.targetFileName) { return leftLink.targetLine == rightLink.targetLine ? leftLink.targetColumn > rightLink.targetColumn : leftLink.targetLine > rightLink.targetLine; } else { return leftLink.targetFileName > rightLink.targetFileName; } } default: return true; } } bool TestTreeSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index = m_sourceModel->index(sourceRow, 0,sourceParent); if (!index.isValid()) return false; const TestTreeItem *item = static_cast(index.internalPointer()); switch (item->type()) { case TestTreeItem::TEST_DATAFUNCTION: return m_filterMode & ShowTestData; case TestTreeItem::TEST_SPECIALFUNCTION: return m_filterMode & ShowInitAndCleanup; default: return true; } } } // namespace Internal } // namespace Autotest