From 08bbe885b49d6782178f0bc7b19c1bb162633666 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 21 Jul 2023 09:21:20 +0200 Subject: [PATCH] Examples: Support manifest-defined category order Reads a separate sorted list of categories from the manifest files. The first of these lists that is found in the manifest files is used. For example the Qt documentation defines the list in the manifest file for qtdoc. Change-Id: I57c2779862a5ebfc27707b53d43d4ed9e7e8c5f9 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Christian Stenger --- src/plugins/qtsupport/exampleslistmodel.cpp | 10 ++-- src/plugins/qtsupport/examplesparser.cpp | 47 ++++++++++++++----- src/plugins/qtsupport/examplesparser.h | 11 ++++- src/plugins/qtsupport/qtcreator_tutorials.xml | 6 +++ tests/auto/examples/tst_examples.cpp | 47 ++++++++++++++----- 5 files changed, 92 insertions(+), 29 deletions(-) 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));