From 07ec6de8d94e1498407158525baba4be441a791c Mon Sep 17 00:00:00 2001 From: Nikolai Kosjar Date: Mon, 4 Nov 2019 14:44:36 +0100 Subject: [PATCH] ClangTools: Improve filtering Replace the filter line edit in the toolbar by a tool button that pop ups a dialog. In the dialog, the available checkers can be selectd/unselected to filter the diagnostic view. Also, the diagnostic view can be limited to diagnostics with fixits so that these can be selected and applied as the next step. For convience, add also some context menu entries to modify the filter with regard to the current diagnostic. Change-Id: Ifba3028805840658d72a39516c2b02da9864d4a6 Reviewed-by: Cristian Adam --- src/plugins/clangtools/CMakeLists.txt | 1 + src/plugins/clangtools/clangtool.cpp | 123 ++++++++++++--- src/plugins/clangtools/clangtool.h | 14 +- src/plugins/clangtools/clangtools.pro | 3 + src/plugins/clangtools/clangtools.qbs | 3 + .../clangtools/clangtoolsdiagnostic.cpp | 6 +- src/plugins/clangtools/clangtoolsdiagnostic.h | 1 + .../clangtools/clangtoolsdiagnosticmodel.cpp | 48 ++++-- .../clangtools/clangtoolsdiagnosticmodel.h | 16 +- .../clangtools/clangtoolsdiagnosticview.cpp | 60 +++++++- .../clangtools/clangtoolsdiagnosticview.h | 34 +++-- .../clangtools/clangtoolslogfilereader.cpp | 4 +- src/plugins/clangtools/filterdialog.cpp | 141 ++++++++++++++++++ src/plugins/clangtools/filterdialog.h | 63 ++++++++ src/plugins/clangtools/filterdialog.ui | 99 ++++++++++++ 15 files changed, 554 insertions(+), 62 deletions(-) create mode 100644 src/plugins/clangtools/filterdialog.cpp create mode 100644 src/plugins/clangtools/filterdialog.h create mode 100644 src/plugins/clangtools/filterdialog.ui diff --git a/src/plugins/clangtools/CMakeLists.txt b/src/plugins/clangtools/CMakeLists.txt index c45e7afa5a3..88b25830d7e 100644 --- a/src/plugins/clangtools/CMakeLists.txt +++ b/src/plugins/clangtools/CMakeLists.txt @@ -31,6 +31,7 @@ add_qtc_plugin(ClangTools clazychecks.ui diagnosticconfigswidget.cpp diagnosticconfigswidget.h executableinfo.cpp executableinfo.h + filterdialog.cpp filterdialog.h filterdialog.ui runsettingswidget.cpp runsettingswidget.h runsettingswidget.ui settingswidget.cpp settingswidget.h settingswidget.ui tidychecks.ui diff --git a/src/plugins/clangtools/clangtool.cpp b/src/plugins/clangtools/clangtool.cpp index decad6ca1de..9b390ae8302 100644 --- a/src/plugins/clangtools/clangtool.cpp +++ b/src/plugins/clangtools/clangtool.cpp @@ -37,6 +37,7 @@ #include "clangtoolsprojectsettings.h" #include "clangtoolssettings.h" #include "clangtoolsutils.h" +#include "filterdialog.h" #include #include @@ -462,6 +463,14 @@ ClangTool::ClangTool() m_diagnosticView->setSortingEnabled(true); m_diagnosticView->sortByColumn(Debugger::DetailedErrorView::DiagnosticColumn, Qt::AscendingOrder); + connect(m_diagnosticView, &DiagnosticView::showFilter, + this, &ClangTool::filter); + connect(m_diagnosticView, &DiagnosticView::clearFilter, + this, &ClangTool::clearFilter); + connect(m_diagnosticView, &DiagnosticView::filterForCurrentKind, + this, &ClangTool::filterForCurrentKind); + connect(m_diagnosticView, &DiagnosticView::filterOutCurrentKind, + this, &ClangTool::filterOutCurrentKind); foreach (auto * const model, QList({m_diagnosticModel, m_diagnosticFilterModel})) { @@ -526,15 +535,13 @@ ClangTool::ClangTool() }); m_expandCollapse = action; - // Filter line edit - m_filterLineEdit = new Utils::FancyLineEdit(); - m_filterLineEdit->setFiltering(true); - m_filterLineEdit->setPlaceholderText(tr("Filter Diagnostics")); - m_filterLineEdit->setHistoryCompleter("CppTools.ClangTidyClazyIssueFilter", true); - connect(m_filterLineEdit, &Utils::FancyLineEdit::filterChanged, [this](const QString &filter) { - m_diagnosticFilterModel->setFilterRegExp( - QRegExp(filter, Qt::CaseSensitive, QRegExp::WildcardUnix)); - }); + // Filter button + action = m_showFilter = new QAction(this); + action->setIcon( + Utils::Icon({{":/utils/images/filtericon.png", Utils::Theme::IconsBaseColor}}).icon()); + action->setToolTip(tr("Filter Diagnostics")); + action->setCheckable(true); + connect(action, &QAction::triggered, this, &ClangTool::filter); // Schedule/Unschedule all fixits m_selectFixitsCheckBox = new SelectFixitsCheckBox; @@ -542,8 +549,7 @@ ClangTool::ClangTool() m_selectFixitsCheckBox->setEnabled(false); m_selectFixitsCheckBox->setTristate(true); connect(m_selectFixitsCheckBox, &QCheckBox::clicked, this, [this]() { - auto view = static_cast(m_diagnosticView.data()); - view->scheduleAllFixits(m_selectFixitsCheckBox->isChecked()); + m_diagnosticView->scheduleAllFixits(m_selectFixitsCheckBox->isChecked()); }); // Apply fixits button @@ -625,12 +631,12 @@ ClangTool::ClangTool() m_perspective.addToolbarSeparator(); m_perspective.addToolBarAction(m_loadExported); m_perspective.addToolBarAction(m_clear); - m_perspective.addToolBarAction(m_expandCollapse); m_perspective.addToolbarSeparator(); + m_perspective.addToolBarAction(m_expandCollapse); m_perspective.addToolBarAction(m_goBack); m_perspective.addToolBarAction(m_goNext); - m_perspective.addToolBarWidget(m_filterLineEdit); m_perspective.addToolbarSeparator(); + m_perspective.addToolBarAction(m_showFilter); m_perspective.addToolBarWidget(m_selectFixitsCheckBox); m_perspective.addToolBarWidget(m_applyFixitsButton); @@ -644,11 +650,6 @@ ClangTool::ClangTool() this, &ClangTool::update); } -ClangTool::~ClangTool() -{ - delete m_diagnosticView; -} - void ClangTool::selectPerspective() { m_perspective.select(); @@ -860,6 +861,20 @@ void ClangTool::loadDiagnosticsFromFiles() setState(State::ImportFinished); } +DiagnosticItem *ClangTool::diagnosticItem(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + TreeItem *item = m_diagnosticModel->itemForIndex(m_diagnosticFilterModel->mapToSource(index)); + if (item->level() == 3) + item = item->parent(); + if (item->level() == 2) + return static_cast(item); + + return {}; +} + void ClangTool::showOutputPane() { ProjectExplorerPlugin::showOutputPaneForRunControl(m_runControl); @@ -868,11 +883,13 @@ void ClangTool::showOutputPane() void ClangTool::reset() { m_clear->setEnabled(false); + m_showFilter->setEnabled(false); + m_showFilter->setChecked(false); m_selectFixitsCheckBox->setEnabled(false); m_applyFixitsButton->setEnabled(false); m_diagnosticModel->clear(); - m_diagnosticFilterModel->resetCounters(); + m_diagnosticFilterModel->reset(); m_infoBarWidget->reset(); @@ -956,6 +973,71 @@ void ClangTool::updateForInitialState() } } +void ClangTool::setFilterOptions(const OptionalFilterOptions &filterOptions) +{ + m_diagnosticFilterModel->setFilterOptions(filterOptions); + const bool isFilterActive = filterOptions + ? (filterOptions->checks != m_diagnosticModel->allChecks()) + : false; + m_showFilter->setChecked(isFilterActive); +} + +void ClangTool::filter() +{ + const OptionalFilterOptions filterOptions = m_diagnosticFilterModel->filterOptions(); + + // Collect available and currently shown checks + QHash checks; + m_diagnosticModel->forItemsAtLevel<2>([&](DiagnosticItem *item) { + const QString checkName = item->diagnostic().name; + Check &check = checks[checkName]; + if (check.name.isEmpty()) { + check.name = checkName; + check.displayName = checkName; + const QString clangDiagPrefix = "clang-diagnostic-"; + if (check.displayName.startsWith(clangDiagPrefix)) + check.displayName = QString("-W%1").arg(check.name.mid(clangDiagPrefix.size())); + check.count = 1; + check.isShown = filterOptions ? filterOptions->checks.contains(checkName) : true; + check.hasFixit = check.hasFixit || item->diagnostic().hasFixits; + checks.insert(check.name, check); + } else { + ++check.count; + } + }); + + // Show dialog + FilterDialog dialog(checks.values()); + if (dialog.exec() == QDialog::Rejected) + return; + + // Apply filter + setFilterOptions(FilterOptions{dialog.selectedChecks()}); +} + +void ClangTool::clearFilter() +{ + m_diagnosticFilterModel->setFilterOptions({}); + m_showFilter->setChecked(false); +} + +void ClangTool::filterForCurrentKind() +{ + if (DiagnosticItem *item = diagnosticItem(m_diagnosticView->currentIndex())) + setFilterOptions(FilterOptions{{item->diagnostic().name}}); +} + +void ClangTool::filterOutCurrentKind() +{ + if (DiagnosticItem *item = diagnosticItem(m_diagnosticView->currentIndex())) { + const OptionalFilterOptions filterOpts = m_diagnosticFilterModel->filterOptions(); + QSet checks = filterOpts ? filterOpts->checks : m_diagnosticModel->allChecks(); + checks.remove(item->diagnostic().name); + + setFilterOptions(FilterOptions{checks}); + } +} + void ClangTool::onBuildFailed() { m_infoBarWidget->setError(InfoBarWidget::Error, @@ -1070,8 +1152,6 @@ void ClangTool::onNewDiagnosticsAvailable(const Diagnostics &diagnostics) { QTC_ASSERT(m_diagnosticModel, return); m_diagnosticModel->addDiagnostics(diagnostics); - if (!m_diagnosticFilterModel->filterRegExp().pattern().isEmpty()) - m_diagnosticFilterModel->invalidateFilter(); } void ClangTool::updateForCurrentState() @@ -1103,6 +1183,7 @@ void ClangTool::updateForCurrentState() m_clear->setEnabled(!isRunning); m_expandCollapse->setEnabled(issuesVisible); m_loadExported->setEnabled(!isRunning); + m_showFilter->setEnabled(issuesFound > 1); // Diagnostic view m_diagnosticView->setCursor(isRunning ? Qt::BusyCursor : Qt::ArrowCursor); diff --git a/src/plugins/clangtools/clangtool.h b/src/plugins/clangtools/clangtool.h index 2630c0d5101..375027a3032 100644 --- a/src/plugins/clangtools/clangtool.h +++ b/src/plugins/clangtools/clangtool.h @@ -27,6 +27,7 @@ #include "clangfileinfo.h" #include "clangtoolsdiagnostic.h" +#include "clangtoolsdiagnosticmodel.h" #include "clangtoolslogfilereader.h" #include @@ -61,6 +62,7 @@ class ClangToolsDiagnosticModel; class ClangToolRunWorker; class Diagnostic; class DiagnosticFilterModel; +class DiagnosticView; class RunSettings; class SelectFixitsCheckBox; @@ -74,7 +76,6 @@ public: static ClangTool *instance(); ClangTool(); - ~ClangTool() override; void selectPerspective(); @@ -125,6 +126,12 @@ private: void updateForCurrentState(); void updateForInitialState(); + void filter(); + void clearFilter(); + void filterForCurrentKind(); + void filterOutCurrentKind(); + void setFilterOptions(const OptionalFilterOptions &filterOptions); + void onBuildFailed(); void onStartFailed(); void onStarted(); @@ -133,6 +140,7 @@ private: void initDiagnosticView(); void loadDiagnosticsFromFiles(); + DiagnosticItem *diagnosticItem(const QModelIndex &index) const; void showOutputPane(); void reset(); @@ -145,7 +153,7 @@ private: ClangToolRunWorker *m_runWorker = nullptr; InfoBarWidget *m_infoBarWidget = nullptr; - QPointer m_diagnosticView; + DiagnosticView *m_diagnosticView = nullptr;; QAction *m_startAction = nullptr; QAction *m_startOnCurrentFileAction = nullptr; @@ -155,7 +163,7 @@ private: DiagnosticFilterModel *m_diagnosticFilterModel = nullptr; - Utils::FancyLineEdit *m_filterLineEdit = nullptr; + QAction *m_showFilter = nullptr; SelectFixitsCheckBox *m_selectFixitsCheckBox = nullptr; QToolButton *m_applyFixitsButton = nullptr; diff --git a/src/plugins/clangtools/clangtools.pro b/src/plugins/clangtools/clangtools.pro index 586add71215..9b1076fbda5 100644 --- a/src/plugins/clangtools/clangtools.pro +++ b/src/plugins/clangtools/clangtools.pro @@ -33,6 +33,7 @@ SOURCES += \ clangtoolsutils.cpp \ diagnosticconfigswidget.cpp \ executableinfo.cpp \ + filterdialog.cpp \ runsettingswidget.cpp \ settingswidget.cpp \ @@ -57,6 +58,7 @@ HEADERS += \ clangtoolsutils.h \ diagnosticconfigswidget.h \ executableinfo.h \ + filterdialog.h \ runsettingswidget.h \ settingswidget.h \ @@ -64,6 +66,7 @@ FORMS += \ clangselectablefilesdialog.ui \ clangtoolsprojectsettingswidget.ui \ clazychecks.ui \ + filterdialog.ui \ runsettingswidget.ui \ settingswidget.ui \ tidychecks.ui \ diff --git a/src/plugins/clangtools/clangtools.qbs b/src/plugins/clangtools/clangtools.qbs index 293da3a8c9a..16d9455eb4f 100644 --- a/src/plugins/clangtools/clangtools.qbs +++ b/src/plugins/clangtools/clangtools.qbs @@ -71,6 +71,9 @@ QtcPlugin { "diagnosticconfigswidget.h", "executableinfo.cpp", "executableinfo.h", + "filterdialog.cpp", + "filterdialog.h", + "filterdialog.ui", "runsettingswidget.cpp", "runsettingswidget.h", "runsettingswidget.ui", diff --git a/src/plugins/clangtools/clangtoolsdiagnostic.cpp b/src/plugins/clangtools/clangtoolsdiagnostic.cpp index 5e850659bf5..1623d2f6b45 100644 --- a/src/plugins/clangtools/clangtoolsdiagnostic.cpp +++ b/src/plugins/clangtools/clangtoolsdiagnostic.cpp @@ -49,7 +49,8 @@ bool Diagnostic::isValid() const quint32 qHash(const Diagnostic &diagnostic) { - return qHash(diagnostic.description) + return qHash(diagnostic.name) + ^ qHash(diagnostic.description) ^ qHash(diagnostic.location.filePath) ^ diagnostic.location.line ^ diagnostic.location.column; @@ -57,7 +58,8 @@ quint32 qHash(const Diagnostic &diagnostic) bool operator==(const Diagnostic &lhs, const Diagnostic &rhs) { - return lhs.description == rhs.description + return lhs.name == rhs.name + && lhs.description == rhs.description && lhs.category == rhs.category && lhs.type == rhs.type && lhs.location == rhs.location diff --git a/src/plugins/clangtools/clangtoolsdiagnostic.h b/src/plugins/clangtools/clangtoolsdiagnostic.h index 99df078fdc5..5c41ec87162 100644 --- a/src/plugins/clangtools/clangtoolsdiagnostic.h +++ b/src/plugins/clangtools/clangtoolsdiagnostic.h @@ -54,6 +54,7 @@ class Diagnostic public: bool isValid() const; + QString name; QString description; QString category; QString type; diff --git a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp index 1e8780a1cc3..07030687d0b 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp +++ b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp @@ -84,12 +84,14 @@ ClangToolsDiagnosticModel::ClangToolsDiagnosticModel(QObject *parent) : ClangToolsDiagnosticModelBase(parent) , m_filesWatcher(std::make_unique()) { + setRootItem(new Utils::StaticTreeItem(QString())); connectFileWatcher(); } QDebug operator<<(QDebug debug, const Diagnostic &d) { - return debug << "category:" << d.category + return debug << "name:" << d.name + << "category:" << d.category << "type:" << d.type << "hasFixits:" << d.hasFixits << "explainingSteps:" << d.explainingSteps.size() @@ -120,7 +122,6 @@ void ClangToolsDiagnosticModel::addDiagnostics(const Diagnostics &diagnostics) if (!filePathItem) { filePathItem = new FilePathItem(filePath); rootItem()->appendChild(filePathItem); - addWatchedPath(d.location.filePath); } @@ -135,6 +136,16 @@ QSet ClangToolsDiagnosticModel::diagnostics() const return m_diagnostics; } +QSet ClangToolsDiagnosticModel::allChecks() const +{ + QSet checks; + forItemsAtLevel<2>([&](DiagnosticItem *item) { + checks.insert(item->diagnostic().name); + }); + + return checks; +} + void ClangToolsDiagnosticModel::clear() { beginResetModel(); @@ -553,7 +564,7 @@ DiagnosticFilterModel::DiagnosticFilterModel(QObject *parent) setProject(project); }); connect(this, &QAbstractItemModel::modelReset, this, [this]() { - resetCounters(); + reset(); emit fixitCountersChanged(m_fixitsScheduled, m_fixitsScheduable); }); connect(this, &QAbstractItemModel::rowsInserted, @@ -595,11 +606,6 @@ void DiagnosticFilterModel::addSuppressedDiagnostic(const SuppressedDiagnostic & invalidate(); } -void DiagnosticFilterModel::invalidateFilter() -{ - QSortFilterProxyModel::invalidateFilter(); -} - void DiagnosticFilterModel::onFixitStatusChanged(const QModelIndex &sourceIndex, FixitStatus oldStatus, FixitStatus newStatus) @@ -618,8 +624,10 @@ void DiagnosticFilterModel::onFixitStatusChanged(const QModelIndex &sourceIndex, emit fixitCountersChanged(m_fixitsScheduled, m_fixitsScheduable); } -void DiagnosticFilterModel::resetCounters() +void DiagnosticFilterModel::reset() { + m_filterOptions.reset(); + m_fixitsScheduled = 0; m_fixitsScheduable = 0; m_diagnostics = 0; @@ -673,9 +681,13 @@ bool DiagnosticFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &s if (parentItem->level() == 1) { auto filePathItem = static_cast(parentItem); auto diagnosticItem = static_cast(filePathItem->childAt(sourceRow)); - - // Is the diagnostic explicitly suppressed? const Diagnostic &diag = diagnosticItem->diagnostic(); + + // Filtered out? + if (m_filterOptions && !m_filterOptions->checks.contains(diag.name)) + return false; + + // Explicitly suppressed? foreach (const SuppressedDiagnostic &d, m_suppressedDiagnostics) { if (d.description != diag.description) continue; @@ -687,8 +699,7 @@ bool DiagnosticFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &s return false; } - // Does the diagnostic match the filter? - return diag.description.contains(filterRegExp()); + return true; } return true; // ExplainingStepItem @@ -744,5 +755,16 @@ void DiagnosticFilterModel::handleSuppressedDiagnosticsChanged() invalidate(); } +OptionalFilterOptions DiagnosticFilterModel::filterOptions() const +{ + return m_filterOptions; +} + +void DiagnosticFilterModel::setFilterOptions(const OptionalFilterOptions &filterOptions) +{ + m_filterOptions = filterOptions; + invalidateFilter(); +} + } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/clangtoolsdiagnosticmodel.h b/src/plugins/clangtools/clangtoolsdiagnosticmodel.h index c6ca1a6e319..ecd3e21e91f 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticmodel.h +++ b/src/plugins/clangtools/clangtoolsdiagnosticmodel.h @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -124,6 +125,8 @@ public: CheckBoxEnabledRole }; + QSet allChecks() const; + void clear(); void removeWatchedPath(const QString &path); void addWatchedPath(const QString &path); @@ -144,6 +147,12 @@ private: std::unique_ptr m_filesWatcher; }; +class FilterOptions { +public: + QSet checks; +}; +using OptionalFilterOptions = Utils::optional; + class DiagnosticFilterModel : public QSortFilterProxyModel { Q_OBJECT @@ -155,13 +164,14 @@ public: void addSuppressedDiagnostic(const SuppressedDiagnostic &diag); ProjectExplorer::Project *project() const { return m_project; } - void invalidateFilter(); + OptionalFilterOptions filterOptions() const; + void setFilterOptions(const OptionalFilterOptions &filterOptions); void onFixitStatusChanged(const QModelIndex &sourceIndex, FixitStatus oldStatus, FixitStatus newStatus); - void resetCounters(); + void reset(); int diagnostics() const { return m_diagnostics; } int fixitsScheduable() const { return m_fixitsScheduable; } int fixitsScheduled() const { return m_fixitsScheduled; } @@ -183,6 +193,8 @@ private: Utils::FilePath m_lastProjectDirectory; SuppressedDiagnosticsList m_suppressedDiagnostics; + OptionalFilterOptions m_filterOptions; + int m_diagnostics = 0; int m_fixitsScheduable = 0; int m_fixitsScheduled = 0; diff --git a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp index 5df289b8e86..3a42ef7fe01 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp +++ b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp @@ -32,8 +32,12 @@ #include #include +#include + #include #include +#include +#include #include #include @@ -95,8 +99,9 @@ class DiagnosticViewDelegate : public QStyledItemDelegate Q_OBJECT public: - DiagnosticViewDelegate(DiagnosticViewStyle *style) - : m_style(style) + DiagnosticViewDelegate(DiagnosticViewStyle *style, QObject *parent) + : QStyledItemDelegate(parent) + , m_style(style) {} void paint(QPainter *painter, @@ -119,16 +124,41 @@ private: DiagnosticView::DiagnosticView(QWidget *parent) : Debugger::DetailedErrorView(parent) , m_style(new DiagnosticViewStyle) - , m_delegate(new DiagnosticViewDelegate(m_style.get())) + , m_delegate(new DiagnosticViewDelegate(m_style, this)) { header()->hide(); + + const QIcon filterIcon + = Utils::Icon({{":/utils/images/filtericon.png", Utils::Theme::IconsBaseColor}}).icon(); + + m_showFilter = new QAction(tr("Filter..."), this); + m_showFilter->setIcon(filterIcon); + connect(m_showFilter, &QAction::triggered, + this, &DiagnosticView::showFilter); + m_clearFilter = new QAction(tr("Clear Filter"), this); + m_clearFilter->setIcon(filterIcon); + connect(m_clearFilter, &QAction::triggered, + this, &DiagnosticView::clearFilter); + m_filterForCurrentKind = new QAction(tr("Filter for This Diagnostic Kind"), this); + m_filterForCurrentKind->setIcon(filterIcon); + connect(m_filterForCurrentKind, &QAction::triggered, + this, &DiagnosticView::filterForCurrentKind); + m_filterOutCurrentKind = new QAction(tr("Filter out This Diagnostic Kind"), this); + m_filterOutCurrentKind->setIcon(filterIcon); + connect(m_filterOutCurrentKind, &QAction::triggered, + this, &DiagnosticView::filterOutCurrentKind); + + m_separator = new QAction(this); + m_separator->setSeparator(true); + m_suppressAction = new QAction(tr("Suppress This Diagnostic"), this); connect(m_suppressAction, &QAction::triggered, this, &DiagnosticView::suppressCurrentDiagnostic); + installEventFilter(this); - setStyle(m_style.get()); - setItemDelegate(m_delegate.get()); + setStyle(m_style); + setItemDelegate(m_delegate); } void DiagnosticView::scheduleAllFixits(bool schedule) @@ -147,7 +177,10 @@ void DiagnosticView::scheduleAllFixits(bool schedule) } } -DiagnosticView::~DiagnosticView() = default; +DiagnosticView::~DiagnosticView() +{ + delete m_style; +} void DiagnosticView::suppressCurrentDiagnostic() { @@ -225,7 +258,20 @@ QModelIndex DiagnosticView::getTopLevelIndex(const QModelIndex &index, Direction QList DiagnosticView::customActions() const { - return {m_suppressAction}; + const QModelIndex currentIndex = selectionModel()->currentIndex(); + const bool isDiagnosticItem = currentIndex.parent().isValid(); + m_filterForCurrentKind->setEnabled(isDiagnosticItem); + m_filterOutCurrentKind->setEnabled(isDiagnosticItem); + m_suppressAction->setEnabled(isDiagnosticItem); + + return { + m_showFilter, + m_clearFilter, + m_filterForCurrentKind, + m_filterOutCurrentKind, + m_separator, + m_suppressAction, + }; } bool DiagnosticView::eventFilter(QObject *watched, QEvent *event) diff --git a/src/plugins/clangtools/clangtoolsdiagnosticview.h b/src/plugins/clangtools/clangtoolsdiagnosticview.h index d8698f846f2..6e591bb376f 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticview.h +++ b/src/plugins/clangtools/clangtoolsdiagnosticview.h @@ -27,8 +27,6 @@ #include -#include - namespace ClangTools { namespace Internal { @@ -45,23 +43,35 @@ public: void scheduleAllFixits(bool schedule); -private: - void openEditorForCurrentIndex(); - void suppressCurrentDiagnostic(); +signals: + void showFilter(); + void clearFilter(); + void filterForCurrentKind(); + void filterOutCurrentKind(); +private: + bool eventFilter(QObject *watched, QEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + + QList customActions() const override; void goNext() override; void goBack() override; + + void openEditorForCurrentIndex(); + void suppressCurrentDiagnostic(); enum Direction { Next = 1, Previous = -1 }; QModelIndex getIndex(const QModelIndex &index, Direction direction) const; QModelIndex getTopLevelIndex(const QModelIndex &index, Direction direction) const; - QList customActions() const override; - bool eventFilter(QObject *watched, QEvent *event) override; - void mouseDoubleClickEvent(QMouseEvent *event) override; - - QAction *m_suppressAction; - std::unique_ptr m_style; - std::unique_ptr m_delegate; +private: + QAction *m_showFilter = nullptr; + QAction *m_clearFilter = nullptr; + QAction *m_filterForCurrentKind = nullptr; + QAction *m_filterOutCurrentKind = nullptr; + QAction *m_separator = nullptr; + QAction *m_suppressAction = nullptr; + DiagnosticViewStyle *m_style = nullptr; + DiagnosticViewDelegate *m_delegate = nullptr; bool m_ignoreSetSelectedFixItsCount = false; }; diff --git a/src/plugins/clangtools/clangtoolslogfilereader.cpp b/src/plugins/clangtools/clangtoolslogfilereader.cpp index 8c4211544b2..fc80a1b8299 100644 --- a/src/plugins/clangtools/clangtoolslogfilereader.cpp +++ b/src/plugins/clangtools/clangtoolslogfilereader.cpp @@ -432,8 +432,8 @@ Diagnostics readExportedDiagnostics(const Utils::FilePath &logFilePath, Diagnostic diag; diag.location = loc.toDiagnosticLocation(); diag.type = "warning"; - diag.description = asString(node["Message"]) + " [" - + (asString(diagNode["DiagnosticName"])) + "]"; + diag.name = asString(diagNode["DiagnosticName"]); + diag.description = asString(node["Message"]) + " [" + diag.name + "]"; // Process fixits/replacements const YAML::Node &replacementsNode = node["Replacements"]; diff --git a/src/plugins/clangtools/filterdialog.cpp b/src/plugins/clangtools/filterdialog.cpp new file mode 100644 index 00000000000..6fc19915d85 --- /dev/null +++ b/src/plugins/clangtools/filterdialog.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#include "filterdialog.h" +#include "ui_filterdialog.h" + +#include +#include + +#include + +namespace ClangTools { +namespace Internal { + +enum Columns { CheckName, Count }; + +class CheckItem : public Utils::TreeItem +{ +public: + CheckItem(const Check &check) : check(check) {} + + QVariant data(int column, int role) const override + { + if (role != Qt::DisplayRole) + return {}; + switch (column) { + case Columns::CheckName: return check.displayName; + case Columns::Count: return check.count; + } + return {}; + } + + Check check; +}; + +static QItemSelectionModel::SelectionFlags selectionFlags() +{ + return QItemSelectionModel::Select | QItemSelectionModel::Rows; +} + +class FilterChecksModel : public Utils::TreeModel +{ + Q_OBJECT + +public: + FilterChecksModel(const Checks &checks) + { + Checks sortedChecks = checks; + Utils::sort(sortedChecks, [](const Check &lhs, const Check &rhs) { + return lhs.displayName < rhs.displayName; + }); + + setHeader({tr("Check"), "#"}); + setRootItem(new Utils::StaticTreeItem(QString())); + for (const Check &check : sortedChecks) + m_root->appendChild(new CheckItem(check)); + } +}; + +FilterDialog::FilterDialog(const Checks &checks, QWidget *parent) + : QDialog(parent) + , m_ui(new Ui::FilterDialog) +{ + m_ui->setupUi(this); + + m_model = new FilterChecksModel(checks); + + // View + m_ui->view->setModel(m_model); + m_ui->view->header()->setStretchLastSection(false); + m_ui->view->header()->setSectionResizeMode(Columns::CheckName, QHeaderView::Stretch); + m_ui->view->header()->setSectionResizeMode(Columns::Count, QHeaderView::ResizeToContents); + m_ui->view->setSelectionMode(QAbstractItemView::MultiSelection); + m_ui->view->setSelectionBehavior(QAbstractItemView::SelectRows); + m_ui->view->setIndentation(0); + connect(m_ui->view->selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](){ + const bool hasSelection = !m_ui->view->selectionModel()->selectedRows().isEmpty(); + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(hasSelection); + }); + + // Buttons + connect(m_ui->selectNone, &QPushButton::clicked, m_ui->view, &QTreeView::clearSelection); + connect(m_ui->selectAll, &QPushButton::clicked, m_ui->view, &QTreeView::selectAll); + connect(m_ui->selectWithFixits, &QPushButton::clicked, m_ui->view, [this](){ + m_ui->view->clearSelection(); + m_model->forItemsAtLevel<1>([&](CheckItem *item) { + if (item->check.hasFixit) + m_ui->view->selectionModel()->select(item->index(), selectionFlags()); + }); + }); + m_ui->selectWithFixits->setEnabled( + Utils::anyOf(checks, [](const Check &c) { return c.hasFixit; })); + + // Select checks that are not filtered out + m_model->forItemsAtLevel<1>([this](CheckItem *item) { + if (item->check.isShown) + m_ui->view->selectionModel()->select(item->index(), selectionFlags()); + }); +} + +FilterDialog::~FilterDialog() +{ + delete m_ui; +} + +QSet FilterDialog::selectedChecks() const +{ + QSet checks; + m_model->forItemsAtLevel<1>([&](CheckItem *item) { + if (m_ui->view->selectionModel()->isSelected(item->index())) + checks << item->check.name; + }); + return checks; +} + +} // namespace Internal +} // namespace ClangTools + +#include "filterdialog.moc" diff --git a/src/plugins/clangtools/filterdialog.h b/src/plugins/clangtools/filterdialog.h new file mode 100644 index 00000000000..74bbf603e10 --- /dev/null +++ b/src/plugins/clangtools/filterdialog.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace ClangTools { +namespace Internal { + +namespace Ui { class FilterDialog; } + +class FilterChecksModel; + +class Check { +public: + QString name; + QString displayName; + int count = 0; + bool isShown = false; + bool hasFixit = false; +}; +using Checks = QList; + +class FilterDialog : public QDialog +{ + Q_OBJECT + +public: + explicit FilterDialog(const Checks &selectedChecks, QWidget *parent = nullptr); + ~FilterDialog(); + + QSet selectedChecks() const; + +private: + Ui::FilterDialog *m_ui; + FilterChecksModel *m_model; +}; + +} // namespace Internal +} // namespace ClangTools diff --git a/src/plugins/clangtools/filterdialog.ui b/src/plugins/clangtools/filterdialog.ui new file mode 100644 index 00000000000..d1f4b773888 --- /dev/null +++ b/src/plugins/clangtools/filterdialog.ui @@ -0,0 +1,99 @@ + + + ClangTools::Internal::FilterDialog + + + + 0 + 0 + 400 + 400 + + + + Filter Diagnostics + + + + + + Select the diagnostics to display. + + + + + + + + + Select All + + + + + + + Select All With Fixits + + + + + + + Clear Selection + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ClangTools::Internal::FilterDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ClangTools::Internal::FilterDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +