diff --git a/src/plugins/qtsupport/exampleslistmodel.cpp b/src/plugins/qtsupport/exampleslistmodel.cpp index a35015d6aa8..6835ec60009 100644 --- a/src/plugins/qtsupport/exampleslistmodel.cpp +++ b/src/plugins/qtsupport/exampleslistmodel.cpp @@ -355,13 +355,14 @@ void ExamplesViewController::updateExamples() &qtVersion); m_view->clear(); + QStringList categoryOrder; QList items; for (const QString &exampleSource : sources) { const auto manifest = FilePath::fromUserInput(exampleSource); qCDebug(log) << QString::fromLatin1("Reading file \"%1\"...") .arg(manifest.absoluteFilePath().toUserOutput()); - const expected_str> result + const expected_str result = parseExamples(manifest, FilePath::fromUserInput(examplesInstallPath), FilePath::fromUserInput(demosInstallPath), @@ -371,7 +372,9 @@ void ExamplesViewController::updateExamples() << result.error(); continue; } - items += filtered(*result, isValidExampleOrDemo); + items += filtered(result->items, isValidExampleOrDemo); + if (categoryOrder.isEmpty()) + categoryOrder = result->categoryOrder; } if (m_isExamples) { @@ -386,7 +389,8 @@ void ExamplesViewController::updateExamples() } const bool sortIntoCategories = !m_isExamples || qtVersion >= *minQtVersionForCategories; - const QStringList order = m_isExamples ? *defaultOrder : QStringList(); + const QStringList order = categoryOrder.isEmpty() && m_isExamples ? *defaultOrder + : categoryOrder; const QList>> sections = getCategories(items, sortIntoCategories, order, m_isExamples); for (int i = 0; i < sections.size(); ++i) { diff --git a/src/plugins/qtsupport/examplesparser.cpp b/src/plugins/qtsupport/examplesparser.cpp index f961846e1d7..eb082f08f8e 100644 --- a/src/plugins/qtsupport/examplesparser.cpp +++ b/src/plugins/qtsupport/examplesparser.cpp @@ -75,6 +75,26 @@ static QHash parseMeta(QXmlStreamReader *reader) return result; } +static QStringList parseCategories(QXmlStreamReader *reader) +{ + QStringList categoryOrder; + while (!reader->atEnd()) { + switch (reader->readNext()) { + case QXmlStreamReader::StartElement: + if (reader->name() == QLatin1String("category")) + categoryOrder.append(reader->readElementText()); + break; + case QXmlStreamReader::EndElement: + if (reader->name() == QLatin1String("categories")) + return categoryOrder; + break; + default: + break; + } + } + return categoryOrder; +} + static QList parseExamples(QXmlStreamReader *reader, const FilePath &projectsOffset, const FilePath &examplesInstallPath) @@ -257,10 +277,10 @@ static QList parseTutorials(QXmlStreamReader *reader, const FileP return result; } -expected_str> parseExamples(const FilePath &manifest, - const FilePath &examplesInstallPath, - const FilePath &demosInstallPath, - const bool examples) +expected_str parseExamples(const FilePath &manifest, + const FilePath &examplesInstallPath, + const FilePath &demosInstallPath, + const bool examples) { const expected_str contents = manifest.fileContents(); if (!contents) @@ -269,19 +289,22 @@ expected_str> parseExamples(const FilePath &manifest, return parseExamples(*contents, manifest, examplesInstallPath, demosInstallPath, examples); } -expected_str> parseExamples(const QByteArray &manifestData, - const Utils::FilePath &manifestPath, - const FilePath &examplesInstallPath, - const FilePath &demosInstallPath, - const bool examples) +expected_str parseExamples(const QByteArray &manifestData, + const Utils::FilePath &manifestPath, + const FilePath &examplesInstallPath, + const FilePath &demosInstallPath, + const bool examples) { const FilePath path = manifestPath.parentDir(); + QStringList categoryOrder; QList items; QXmlStreamReader reader(manifestData); while (!reader.atEnd()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement: - if (examples && reader.name() == QLatin1String("examples")) + if (categoryOrder.isEmpty() && reader.name() == QLatin1String("categories")) + categoryOrder = parseCategories(&reader); + else if (examples && reader.name() == QLatin1String("examples")) items += parseExamples(&reader, path, examplesInstallPath); else if (examples && reader.name() == QLatin1String("demos")) items += parseDemos(&reader, path, demosInstallPath); @@ -301,7 +324,7 @@ expected_str> parseExamples(const QByteArray &manifestData, .arg(reader.columnNumber()) .arg(reader.errorString())); } - return items; + return {{items, categoryOrder}}; } static bool sortByHighlightedAndName(ExampleItem *first, ExampleItem *second) @@ -355,7 +378,7 @@ QList>> getCategories(const QList< } else { // All original items have been copied into a category or other, delete. qDeleteAll(items); - static const int defaultOrderSize = defaultOrder.size(); + const int defaultOrderSize = defaultOrder.size(); int index = 0; const auto end = categoryMap.constKeyValueEnd(); for (auto it = categoryMap.constKeyValueBegin(); it != end; ++it) { diff --git a/src/plugins/qtsupport/examplesparser.h b/src/plugins/qtsupport/examplesparser.h index d57e7a536ad..aee539ad62b 100644 --- a/src/plugins/qtsupport/examplesparser.h +++ b/src/plugins/qtsupport/examplesparser.h @@ -31,13 +31,20 @@ public: QHash metaData; }; -QTSUPPORT_TEST_EXPORT Utils::expected_str> parseExamples( +class QTSUPPORT_TEST_EXPORT ParsedExamples +{ +public: + QList items; + QStringList categoryOrder; +}; + +QTSUPPORT_TEST_EXPORT Utils::expected_str parseExamples( const Utils::FilePath &manifest, const Utils::FilePath &examplesInstallPath, const Utils::FilePath &demosInstallPath, bool examples); -QTSUPPORT_TEST_EXPORT Utils::expected_str> parseExamples( +QTSUPPORT_TEST_EXPORT Utils::expected_str parseExamples( const QByteArray &manifestData, const Utils::FilePath &manifestPath, const Utils::FilePath &examplesInstallPath, diff --git a/src/plugins/qtsupport/qtcreator_tutorials.xml b/src/plugins/qtsupport/qtcreator_tutorials.xml index f374f1b6301..af657454432 100644 --- a/src/plugins/qtsupport/qtcreator_tutorials.xml +++ b/src/plugins/qtsupport/qtcreator_tutorials.xml @@ -1,5 +1,11 @@ + + Help + Learning + Online + Talk + diff --git a/tests/auto/examples/tst_examples.cpp b/tests/auto/examples/tst_examples.cpp index e01336d65a8..5d30c8710b5 100644 --- a/tests/auto/examples/tst_examples.cpp +++ b/tests/auto/examples/tst_examples.cpp @@ -90,9 +90,11 @@ void tst_Examples::parsing_data() QTest::addColumn("platforms"); QTest::addColumn("metaData"); QTest::addColumn("categories"); + QTest::addColumn("categoryOrder"); QTest::addRow("example") << QByteArray(R"raw( + + + Application Examples + Desktop + Mobile + Embedded + + )raw") << /*isExamples=*/true << "Analog Clock" << "The Analog Clock example shows how to draw the contents of a custom widget." @@ -126,23 +135,33 @@ void tst_Examples::parsing_data() << FilePaths() << Example << true << false << false << "" << "" << QStringList() << MetaData({{"category", {"Graphics", "Graphics", "Foobar"}}, {"tags", {"widgets"}}}) - << QStringList{"Foobar", "Graphics"}; + << QStringList{"Foobar", "Graphics"} + << QStringList{"Application Examples", "Desktop", "Mobile", "Embedded"}; QTest::addRow("no category, highlighted") << QByteArray(R"raw( + + )raw") << /*isExamples=*/true << "No Category, highlighted" << QString() << QString() << QStringList() << FilePath("examples") << QString() << FilePaths() << FilePath() << FilePaths() << Example << /*hasSourceCode=*/false << false << /*isHighlighted=*/true << "" - << "" << QStringList() << MetaData() << QStringList{"Featured"}; + << "" << QStringList() << MetaData() << QStringList{"Featured"} << QStringList(); QTest::addRow("tutorial with category") << QByteArray(R"raw( + + + Help + Learning + Online + Talk + @@ -152,6 +171,7 @@ void tst_Examples::parsing_data() + )raw") << /*isExamples=*/false << "A tutorial" << "A dummy tutorial." @@ -160,7 +180,8 @@ void tst_Examples::parsing_data() << "qthelp://org.qt-project.qtcreator/doc/dummytutorial.html" << FilePaths() << FilePath() << FilePaths() << Tutorial << /*hasSourceCode=*/false << /*isVideo=*/false << /*isHighlighted=*/false << QString() << QString() << QStringList() - << MetaData({{"category", {"Help"}}}) << QStringList("Help"); + << MetaData({{"category", {"Help"}}}) << QStringList("Help") + << QStringList{"Help", "Learning", "Online", "Talk"}; } void tst_Examples::parsing() @@ -168,16 +189,18 @@ void tst_Examples::parsing() QFETCH(QByteArray, data); QFETCH(bool, isExamples); QFETCH(QStringList, categories); + QFETCH(QStringList, categoryOrder); const ExampleItem expected = fetchItem(); - const expected_str> result - = parseExamples(data, - FilePath("manifest/examples-manifest.xml"), - FilePath("examples"), - FilePath("demos"), - isExamples); + const expected_str result = parseExamples(data, + FilePath( + "manifest/examples-manifest.xml"), + FilePath("examples"), + FilePath("demos"), + isExamples); QVERIFY(result); - QCOMPARE(result->size(), 1); - const ExampleItem item = *result->at(0); + QCOMPARE(result->categoryOrder, categoryOrder); + QCOMPARE(result->items.size(), 1); + const ExampleItem item = *result->items.at(0); QCOMPARE(item.name, expected.name); QCOMPARE(item.description, expected.description); QCOMPARE(item.imageUrl, expected.imageUrl); @@ -197,7 +220,7 @@ void tst_Examples::parsing() QCOMPARE(item.metaData, expected.metaData); const QList>> resultCategories - = getCategories(*result, true, {}, true); + = getCategories(result->items, true, {}, true); QCOMPARE(resultCategories.size(), categories.size()); for (int i = 0; i < resultCategories.size(); ++i) { QCOMPARE(resultCategories.at(i).first.name, categories.at(i));