File 0001-ProjectExplorer-standard-custom-parser-for-build-and.patch of Package qt-creator

From 2396068b59c2516753a2e87ec9d25177934f0a6f Mon Sep 17 00:00:00 2001
From: Ralf Habacker <ralf.habacker@freenet.de>
Date: Thu, 16 Jan 2025 16:39:33 +0100
Subject: [PATCH] ProjectExplorer: standard custom parser for build and run
 configurations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

With this commit, a custom parser can be configured as the default
for build and run configurations of a project.

On the settings page for custom parser, additional columns are
displayed with which the relevant parser can be used as the
default for build and/or run configurations.

In the configurations, appropriately marked parsers are output
with the suffix “(project default)” and activated by default
for opened or newly created projects.

Fixes: QTCREATORBUG-32342
Change-Id: I2027fd3cc88cc4c38b08ac7ba6d921d950d448f9
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
---
 .../projectexplorer/buildconfiguration.cpp    |  10 +-
 src/plugins/projectexplorer/customparser.cpp  |  39 ++-
 src/plugins/projectexplorer/customparser.h    |   7 +-
 .../customparserssettingspage.cpp             | 283 +++++++++++++++---
 4 files changed, 289 insertions(+), 50 deletions(-)

diff --git a/src/plugins/projectexplorer/buildconfiguration.cpp b/src/plugins/projectexplorer/buildconfiguration.cpp
index 8acfa6d90cd..c2006f236c7 100644
--- a/src/plugins/projectexplorer/buildconfiguration.cpp
+++ b/src/plugins/projectexplorer/buildconfiguration.cpp
@@ -109,14 +109,20 @@ public:
         layout->addWidget(pasteStdOutCB);
 
         connect(pasteStdOutCB, &QCheckBox::clicked, bc, &BuildConfiguration::setParseStdOut);
-        const auto selectionWidget = new CustomParsersSelectionWidget(this);
+        const auto selectionWidget =
+                new CustomParsersSelectionWidget(CustomParsersSelectionWidget::InBuildConfig, this);
         layout->addWidget(selectionWidget);
 
+        QList<Utils::Id> parsers = bc->customParsers();
+        for (const auto &s : ProjectExplorerPlugin::customParsers()) {
+            if (s.buildDefault && !parsers.contains(s.id))
+                parsers.append(s.id);
+        }
+        selectionWidget->setSelectedParsers(parsers);
         connect(selectionWidget, &CustomParsersSelectionWidget::selectionChanged, this,
                 [selectionWidget, bc] {
             bc->setCustomParsers(selectionWidget->selectedParsers());
         });
-        selectionWidget->setSelectedParsers(bc->customParsers());
     }
 };
 
diff --git a/src/plugins/projectexplorer/customparser.cpp b/src/plugins/projectexplorer/customparser.cpp
index ff500afb6a2..27ace59961b 100644
--- a/src/plugins/projectexplorer/customparser.cpp
+++ b/src/plugins/projectexplorer/customparser.cpp
@@ -26,6 +26,7 @@
 #   include "outputparser_test.h"
 #endif
 
+using namespace ProjectExplorer::Internal;
 using namespace Utils;
 
 const char idKey[] = "Id";
@@ -38,6 +39,8 @@ const char fileNameCapKey[] = "FileNameCap";
 const char messageCapKey[] = "MessageCap";
 const char channelKey[] = "Channel";
 const char exampleKey[] = "Example";
+const char buildDefaultKey[] = "BuildDefault";
+const char runDefaultKey[] = "RunDefault";
 
 namespace ProjectExplorer {
 
@@ -136,7 +139,9 @@ void CustomParserExpression::setFileNameCap(int fileNameCap)
 bool CustomParserSettings::operator ==(const CustomParserSettings &other) const
 {
     return id == other.id && displayName == other.displayName
-            && error == other.error && warning == other.warning;
+            && error == other.error && warning == other.warning
+            && buildDefault == other.buildDefault
+            && runDefault == other.runDefault;
 }
 
 Store CustomParserSettings::toMap() const
@@ -146,6 +151,8 @@ Store CustomParserSettings::toMap() const
     map.insert(nameKey, displayName);
     map.insert(errorKey, variantFromStore(error.toMap()));
     map.insert(warningKey, variantFromStore(warning.toMap()));
+    map.insert(buildDefaultKey, buildDefault);
+    map.insert(runDefaultKey, runDefault);
     return map;
 }
 
@@ -155,6 +162,8 @@ void CustomParserSettings::fromMap(const Store &map)
     displayName = map.value(nameKey).toString();
     error.fromMap(storeFromVariant(map.value(errorKey)));
     warning.fromMap(storeFromVariant(map.value(warningKey)));
+    buildDefault = map.value(buildDefaultKey).toBool();
+    runDefault = map.value(runDefaultKey).toBool();
 }
 
 CustomParsersAspect::CustomParsersAspect(Target *target)
@@ -165,7 +174,12 @@ CustomParsersAspect::CustomParsersAspect(Target *target)
     setDisplayName(Tr::tr("Custom Output Parsers"));
     addDataExtractor(this, &CustomParsersAspect::parsers, &Data::parsers);
     setConfigWidgetCreator([this] {
-        const auto widget = new Internal::CustomParsersSelectionWidget;
+        const auto widget =
+                new CustomParsersSelectionWidget(CustomParsersSelectionWidget::InRunConfig);
+        for (const auto &s : ProjectExplorerPlugin::customParsers()) {
+            if (s.runDefault && !m_parsers.contains(s.id))
+                m_parsers.append(s.id);
+        }
         widget->setSelectedParsers(m_parsers);
         connect(widget, &Internal::CustomParsersSelectionWidget::selectionChanged,
                 this, [this, widget] { m_parsers = widget->selectedParsers(); });
@@ -260,7 +274,8 @@ class SelectionWidget : public QWidget
 {
     Q_OBJECT
 public:
-    SelectionWidget(QWidget *parent = nullptr) : QWidget(parent)
+    SelectionWidget(CustomParsersSelectionWidget::Embedded where, QWidget *parent)
+        : QWidget(parent), m_where(where)
     {
         const auto layout = new QVBoxLayout(this);
         const auto explanatoryLabel = new QLabel(Tr::tr(
@@ -302,12 +317,22 @@ private:
     {
         const auto layout = qobject_cast<QVBoxLayout *>(this->layout());
         QTC_ASSERT(layout, return);
-        const QList<Utils::Id> parsers = selectedParsers();
+        QList<Utils::Id> parsers = selectedParsers();
         for (const auto &p : std::as_const(parserCheckBoxes))
             delete p.first;
         parserCheckBoxes.clear();
         for (const CustomParserSettings &s : ProjectExplorerPlugin::customParsers()) {
             const auto checkBox = new QCheckBox(s.displayName, this);
+            bool isSelected = parsers.contains(s.id);
+            bool projectDefault =
+                       (m_where == CustomParsersSelectionWidget::InBuildConfig && s.buildDefault)
+                    || (m_where == CustomParsersSelectionWidget::InRunConfig  && s.runDefault);
+            if (projectDefault) {
+                checkBox->setText(Tr::tr("%1 (Project default)").arg(s.displayName));
+                if (!isSelected)
+                     parsers.append(s.id);
+            }
+            checkBox->setCheckState(parsers.contains(s.id) ? Qt::Checked : Qt::Unchecked);
             connect(checkBox, &QCheckBox::stateChanged, this, &SelectionWidget::selectionChanged);
             parserCheckBoxes.push_back({checkBox, s.id});
             layout->addWidget(checkBox);
@@ -316,12 +341,14 @@ private:
     }
 
     QList<QPair<QCheckBox *, Utils::Id>> parserCheckBoxes;
+    const CustomParsersSelectionWidget::Embedded m_where;
 };
 } // anonymous namespace
 
-CustomParsersSelectionWidget::CustomParsersSelectionWidget(QWidget *parent) : DetailsWidget(parent)
+CustomParsersSelectionWidget::CustomParsersSelectionWidget(Embedded where, QWidget *parent)
+    : DetailsWidget(parent)
 {
-    const auto widget = new SelectionWidget(this);
+    const auto widget = new SelectionWidget(where, this);
     connect(widget, &SelectionWidget::selectionChanged, this, [this] {
         updateSummary();
         emit selectionChanged();
diff --git a/src/plugins/projectexplorer/customparser.h b/src/plugins/projectexplorer/customparser.h
index 383bf84eb71..27bd6e136bb 100644
--- a/src/plugins/projectexplorer/customparser.h
+++ b/src/plugins/projectexplorer/customparser.h
@@ -67,6 +67,8 @@ public:
 
     Utils::Id id;
     QString displayName;
+    bool buildDefault = false;
+    bool runDefault = false;
     CustomParserExpression error;
     CustomParserExpression warning;
 };
@@ -119,7 +121,9 @@ class CustomParsersSelectionWidget : public Utils::DetailsWidget
 {
     Q_OBJECT
 public:
-    CustomParsersSelectionWidget(QWidget *parent = nullptr);
+    enum Embedded { InRunConfig, InBuildConfig };
+
+    CustomParsersSelectionWidget(Embedded where, QWidget *parent = nullptr);
 
     void setSelectedParsers(const QList<Utils::Id> &parsers);
     QList<Utils::Id> selectedParsers() const;
@@ -134,4 +138,5 @@ private:
 } // namespace Internal
 } // namespace ProjectExplorer
 
+Q_DECLARE_METATYPE(ProjectExplorer::CustomParserSettings);
 Q_DECLARE_METATYPE(ProjectExplorer::CustomParserExpression::CustomParserChannel);
diff --git a/src/plugins/projectexplorer/customparserssettingspage.cpp b/src/plugins/projectexplorer/customparserssettingspage.cpp
index 4f3e429c91a..f64b9938893 100644
--- a/src/plugins/projectexplorer/customparserssettingspage.cpp
+++ b/src/plugins/projectexplorer/customparserssettingspage.cpp
@@ -10,25 +10,249 @@
 #include "projectexplorertr.h"
 
 #include <utils/algorithm.h>
+#include <utils/itemviews.h>
 #include <utils/qtcassert.h>
 
+#include <QAbstractTableModel>
+#include <QHeaderView>
 #include <QHBoxLayout>
 #include <QLabel>
 #include <QList>
-#include <QListWidget>
 #include <QPushButton>
 #include <QVBoxLayout>
 
 namespace ProjectExplorer {
 namespace Internal {
 
+class CustomParsersModel : public QAbstractTableModel
+{
+public:
+    explicit CustomParsersModel(QObject *parent = nullptr);
+
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+    Qt::ItemFlags flags(const QModelIndex &index) const override;
+    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+
+    bool add(const ProjectExplorer::CustomParserSettings& s);
+    bool remove(const QModelIndex &index);
+
+    void apply();
+
+protected:
+    QList<ProjectExplorer::CustomParserSettings> m_customParsers;
+};
+
+CustomParsersModel::CustomParsersModel(QObject *parent)
+    : QAbstractTableModel(parent)
+    , m_customParsers(ProjectExplorerPlugin::customParsers())
+{
+    connect(
+        ProjectExplorerPlugin::instance(),
+        &ProjectExplorerPlugin::customParsersChanged,
+        this,
+        [this] {
+            beginResetModel();
+            m_customParsers = ProjectExplorerPlugin::customParsers();
+            endResetModel();
+        });
+}
+
+QVariant CustomParsersModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    QVariant result;
+    if (orientation == Qt::Vertical)
+        return QVariant();
+
+    switch (role) {
+    case Qt::DisplayRole:
+        switch (section) {
+        case 0:
+            result = Tr::tr("Name");
+            break;
+        case 1:
+            result = Tr::tr("Build default");
+            break;
+        case 2:
+            result = Tr::tr("Run default");
+            break;
+        }
+        break;
+    case Qt::ToolTipRole:
+        switch (section) {
+        case 0:
+            result = Tr::tr("The name of the custom parser");
+            break;
+        case 1:
+            result = Tr::tr("This custom parser is used by default for all build configurations of "
+                            "the project");
+            break;
+        case 2:
+            result = Tr::tr(
+                "This custom parser is used by default for all run configurations of the project");
+            break;
+        }
+    }
+    return result;
+}
+
+int CustomParsersModel::columnCount(const QModelIndex &parent) const
+{
+    if (parent.isValid())
+        return 0;
+
+    return 3;
+}
+
+int CustomParsersModel::rowCount(const QModelIndex &parent) const
+{
+    if (parent.isValid())
+        return 0;
+
+    return m_customParsers.size();
+}
+
+QVariant CustomParsersModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid())
+        return QVariant();
+
+    const CustomParserSettings &s = m_customParsers[index.row()];
+
+    switch (role) {
+    case Qt::CheckStateRole:
+        switch (index.column()) {
+        case 1:
+            return s.buildDefault ? Qt::Checked : Qt::Unchecked;
+        case 2:
+            return s.runDefault ? Qt::Checked : Qt::Unchecked;
+        }
+        break;
+
+    case Qt::DisplayRole:
+        switch (index.column()) {
+        case 0:
+            return s.displayName;
+        }
+        break;
+
+    case Qt::EditRole:
+        switch (index.column()) {
+        case 1:
+            return s.buildDefault;
+        case 2:
+            return s.runDefault;
+        }
+        break;
+
+    case Qt::TextAlignmentRole:
+        switch (index.column()) {
+        case 1:
+        case 2:
+            return Qt::AlignCenter;
+        }
+        break;
+
+    case Qt::UserRole:
+        return QVariant::fromValue<CustomParserSettings>(s);
+    }
+
+    return QVariant();
+}
+
+bool CustomParsersModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if (!index.isValid())
+        return false;
+
+    if (index.row() >= m_customParsers.size())
+        return false;
+
+    CustomParserSettings &s = m_customParsers[index.row()];
+
+    bool result = true;
+    switch (role) {
+    case Qt::EditRole:
+        switch (index.column()) {
+        case 0:
+            s.displayName = value.toString();
+            emit dataChanged(index, index);
+            break;
+        }
+        break;
+
+    case Qt::CheckStateRole:
+        switch (index.column()) {
+        case 1:
+            s.buildDefault = value == Qt::Checked;
+            emit dataChanged(index, index);
+            break;
+        case 2:
+            s.runDefault = value == Qt::Checked;
+            emit dataChanged(index, index);
+            break;
+        }
+        break;
+
+    case Qt::UserRole:
+        if (value.canConvert<CustomParserSettings>()) {
+            s = value.value<CustomParserSettings>();
+            emit dataChanged(index, index);
+        } else
+            result = false;
+    }
+    return result;
+}
+
+Qt::ItemFlags CustomParsersModel::flags(const QModelIndex &index) const
+{
+    if (!index.isValid())
+        return Qt::NoItemFlags;
+
+    Qt::ItemFlags flags = Qt::ItemIsEnabled;
+
+    if (index.column() > 0)
+        flags |= Qt::ItemIsUserCheckable;
+    else
+        flags |= Qt::ItemIsEditable;
+
+    return flags;
+}
+
+bool CustomParsersModel::add(const CustomParserSettings &s)
+{
+    beginInsertRows(index(-1, -1), m_customParsers.size(), m_customParsers.size());
+    m_customParsers.append(s);
+    endInsertRows();
+    return true;
+}
+
+bool CustomParsersModel::remove(const QModelIndex &index)
+{
+    beginRemoveRows(index.parent(), index.row(), index.row());
+    m_customParsers.removeAt(index.row());
+    endRemoveRows();
+    return true;
+}
+
+void CustomParsersModel::apply()
+{
+    ProjectExplorerPlugin::setCustomParsers(m_customParsers);
+}
+
 class CustomParsersSettingsWidget final : public Core::IOptionsPageWidget
 {
 public:
     CustomParsersSettingsWidget()
     {
-        m_customParsers = ProjectExplorerPlugin::customParsers();
-        resetListView();
+        Utils::TreeView *parserView = new Utils::TreeView(this);
+        parserView->setModel(&m_model);
+        parserView->setSelectionMode(QAbstractItemView::SingleSelection);
+        parserView->setSelectionBehavior(QAbstractItemView::SelectRows);
 
         const auto mainLayout = new QVBoxLayout(this);
         const auto widgetLayout = new QHBoxLayout;
@@ -37,7 +261,7 @@ public:
             "Custom output parsers defined here can be enabled individually "
             "in the project's build or run settings."));
         mainLayout->addWidget(hintLabel);
-        widgetLayout->addWidget(&m_parserListView);
+        widgetLayout->addWidget(parserView);
         const auto buttonLayout = new QVBoxLayout;
         widgetLayout->addLayout(buttonLayout);
         const auto addButton = new QPushButton(Tr::tr("Add..."));
@@ -56,61 +280,38 @@ public:
             CustomParserSettings newParser = dlg.settings();
             newParser.id = Utils::Id::generate();
             newParser.displayName = Tr::tr("New Parser");
-            m_customParsers << newParser;
-            resetListView();
+            m_model.add(newParser);
         });
-        connect(removeButton, &QPushButton::clicked, this, [this] {
-            const QList<QListWidgetItem *> sel = m_parserListView.selectedItems();
-            QTC_ASSERT(sel.size() == 1, return);
-            m_customParsers.removeAt(m_parserListView.row(sel.first()));
-            delete sel.first();
+
+        connect(removeButton, &QPushButton::clicked, this, [this, parserView] {
+            m_model.remove(parserView->currentIndex());
         });
-        connect(editButton, &QPushButton::clicked, this, [this] {
-            const QList<QListWidgetItem *> sel = m_parserListView.selectedItems();
-            QTC_ASSERT(sel.size() == 1, return);
-            CustomParserSettings &s = m_customParsers[m_parserListView.row(sel.first())];
+
+        connect(editButton, &QPushButton::clicked, this, [this, parserView] {
+            CustomParserSettings s = m_model.data(parserView->currentIndex(), Qt::UserRole).value<CustomParserSettings>();
             CustomParserConfigDialog dlg(this);
             dlg.setSettings(s);
             if (dlg.exec() != QDialog::Accepted)
                 return;
             s.error = dlg.settings().error;
             s.warning = dlg.settings().warning;
+            m_model.setData(parserView->currentIndex(), QVariant::fromValue<CustomParserSettings>(s), Qt::UserRole);
         });
 
-        connect(&m_parserListView, &QListWidget::itemChanged, this, [this](QListWidgetItem *item) {
-            m_customParsers[m_parserListView.row(item)].displayName = item->text();
-            resetListView();
-        });
-
-        const auto updateButtons = [this, removeButton, editButton] {
-            const bool enable = !m_parserListView.selectedItems().isEmpty();
+        const auto updateButtons = [editButton, parserView, removeButton] {
+            const bool enable = parserView->currentIndex().isValid();
             removeButton->setEnabled(enable);
             editButton->setEnabled(enable);
         };
         updateButtons();
-        connect(m_parserListView.selectionModel(), &QItemSelectionModel::selectionChanged,
-                updateButtons);
+        connect(parserView->selectionModel(), &QItemSelectionModel::selectionChanged,
+            updateButtons);
     }
 
 private:
-    void apply() override { ProjectExplorerPlugin::setCustomParsers(m_customParsers); }
-
-    void resetListView()
-    {
-        m_parserListView.clear();
-        Utils::sort(m_customParsers,
-                    [](const CustomParserSettings &s1, const CustomParserSettings &s2) {
-            return s1.displayName < s2.displayName;
-        });
-        for (const CustomParserSettings &s : std::as_const(m_customParsers)) {
-            const auto item = new QListWidgetItem(s.displayName);
-            item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
-            m_parserListView.addItem(item);
-        }
-    }
+    void apply() override { m_model.apply(); }
 
-    QListWidget m_parserListView;
-    QList<CustomParserSettings> m_customParsers;
+    CustomParsersModel m_model;
 };
 
 CustomParsersSettingsPage::CustomParsersSettingsPage()
-- 
2.48.1

openSUSE Build Service is sponsored by