diff options
| author | Nikolai Kosjar <nikolai.kosjar@qt.io> | 2019-11-04 14:44:36 +0100 |
|---|---|---|
| committer | Nikolai Kosjar <nikolai.kosjar@qt.io> | 2019-12-04 14:07:04 +0000 |
| commit | 07ec6de8d94e1498407158525baba4be441a791c (patch) | |
| tree | 78f7fd864dcd7857f86e020c7485871501c928b4 | |
| parent | 92bb42dd36394162a9badecf029234e1c343b5a7 (diff) | |
| download | qt-creator-07ec6de8d94e1498407158525baba4be441a791c.tar.gz | |
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 <cristian.adam@qt.io>
| -rw-r--r-- | src/plugins/clangtools/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtool.cpp | 123 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtool.h | 14 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtools.pro | 3 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtools.qbs | 3 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnostic.cpp | 6 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnostic.h | 1 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp | 48 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticmodel.h | 16 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticview.cpp | 60 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticview.h | 32 | ||||
| -rw-r--r-- | src/plugins/clangtools/clangtoolslogfilereader.cpp | 4 | ||||
| -rw-r--r-- | src/plugins/clangtools/filterdialog.cpp | 141 | ||||
| -rw-r--r-- | src/plugins/clangtools/filterdialog.h | 63 | ||||
| -rw-r--r-- | src/plugins/clangtools/filterdialog.ui | 99 |
15 files changed, 553 insertions, 61 deletions
diff --git a/src/plugins/clangtools/CMakeLists.txt b/src/plugins/clangtools/CMakeLists.txt index c45e7afa5a..88b25830d7 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 decad6ca1d..9b390ae830 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 <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/actionmanager/actionmanager.h> @@ -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<QAbstractItemModel *>({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<DiagnosticView *>(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<DiagnosticItem *>(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<QString, Check> 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<QString> 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 2630c0d510..375027a303 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 <debugger/debuggermainwindow.h> @@ -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<Debugger::DetailedErrorView> 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 586add7121..9b1076fbda 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 293da3a8c9..16d9455eb4 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 5e850659bf..1623d2f6b4 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 99df078fdc..5c41ec8716 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 1e8780a1cc..07030687d0 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<QFileSystemWatcher>()) { + 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<Diagnostic> ClangToolsDiagnosticModel::diagnostics() const return m_diagnostics; } +QSet<QString> ClangToolsDiagnosticModel::allChecks() const +{ + QSet<QString> 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<FilePathItem *>(parentItem); auto diagnosticItem = static_cast<DiagnosticItem *>(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 c6ca1a6e31..ecd3e21e91 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticmodel.h +++ b/src/plugins/clangtools/clangtoolsdiagnosticmodel.h @@ -31,6 +31,7 @@ #include <debugger/analyzer/detailederrorview.h> #include <utils/fileutils.h> +#include <utils/optional.h> #include <utils/treemodel.h> #include <QFileSystemWatcher> @@ -124,6 +125,8 @@ public: CheckBoxEnabledRole }; + QSet<QString> allChecks() const; + void clear(); void removeWatchedPath(const QString &path); void addWatchedPath(const QString &path); @@ -144,6 +147,12 @@ private: std::unique_ptr<QFileSystemWatcher> m_filesWatcher; }; +class FilterOptions { +public: + QSet<QString> checks; +}; +using OptionalFilterOptions = Utils::optional<FilterOptions>; + 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 5df289b8e8..3a42ef7fe0 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp +++ b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp @@ -32,8 +32,12 @@ #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/manhattanstyle.h> +#include <debugger/analyzer/diagnosticlocation.h> + #include <utils/fileutils.h> #include <utils/qtcassert.h> +#include <utils/theme/theme.h> +#include <utils/utilsicons.h> #include <QAction> #include <QApplication> @@ -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<QAction *> 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 d8698f846f..6e591bb376 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticview.h +++ b/src/plugins/clangtools/clangtoolsdiagnosticview.h @@ -27,8 +27,6 @@ #include <debugger/analyzer/detailederrorview.h> -#include <memory> - namespace ClangTools { namespace Internal { @@ -45,23 +43,35 @@ public: void scheduleAllFixits(bool schedule); +signals: + void showFilter(); + void clearFilter(); + void filterForCurrentKind(); + void filterOutCurrentKind(); + private: - void openEditorForCurrentIndex(); - void suppressCurrentDiagnostic(); + bool eventFilter(QObject *watched, QEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + QList<QAction *> 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<QAction *> customActions() const override; - bool eventFilter(QObject *watched, QEvent *event) override; - void mouseDoubleClickEvent(QMouseEvent *event) override; - - QAction *m_suppressAction; - std::unique_ptr<DiagnosticViewStyle> m_style; - std::unique_ptr<DiagnosticViewDelegate> 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 8c4211544b..fc80a1b829 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 0000000000..6fc19915d8 --- /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 <utils/algorithm.h> +#include <utils/treemodel.h> + +#include <QItemSelectionModel> + +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<Utils::TreeItem, CheckItem> +{ + 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<QString> FilterDialog::selectedChecks() const +{ + QSet<QString> 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 0000000000..74bbf603e1 --- /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 <QDialog> + +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<Check>; + +class FilterDialog : public QDialog +{ + Q_OBJECT + +public: + explicit FilterDialog(const Checks &selectedChecks, QWidget *parent = nullptr); + ~FilterDialog(); + + QSet<QString> 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 0000000000..d1f4b77388 --- /dev/null +++ b/src/plugins/clangtools/filterdialog.ui @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ClangTools::Internal::FilterDialog</class> + <widget class="QDialog" name="ClangTools::Internal::FilterDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>400</height> + </rect> + </property> + <property name="windowTitle"> + <string>Filter Diagnostics</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Select the diagnostics to display.</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="selectAll"> + <property name="text"> + <string>Select All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="selectWithFixits"> + <property name="text"> + <string>Select All With Fixits</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="selectNone"> + <property name="text"> + <string>Clear Selection</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTreeView" name="view"/> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ClangTools::Internal::FilterDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ClangTools::Internal::FilterDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> |
