1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "clangtoolsdiagnosticmodel.h"
27 
28 #include "clangtoolsdiagnosticview.h"
29 #include "clangtoolsprojectsettings.h"
30 #include "clangtoolsutils.h"
31 #include "diagnosticmark.h"
32 
33 #include <coreplugin/fileiconprovider.h>
34 #include <projectexplorer/project.h>
35 #include <projectexplorer/session.h>
36 #include <texteditor/textmark.h>
37 #include <utils/qtcassert.h>
38 #include <utils/utilsicons.h>
39 
40 #include <QFileInfo>
41 #include <QLoggingCategory>
42 
43 #include <tuple>
44 
45 static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.model", QtWarningMsg)
46 
47 namespace ClangTools {
48 namespace Internal {
49 
FilePathItem(const Utils::FilePath & filePath)50 FilePathItem::FilePathItem(const Utils::FilePath &filePath)
51     : m_filePath(filePath)
52 {}
53 
data(int column,int role) const54 QVariant FilePathItem::data(int column, int role) const
55 {
56     if (column == DiagnosticView::DiagnosticColumn) {
57         switch (role) {
58         case Qt::DisplayRole:
59             return m_filePath.toUserOutput();
60         case Qt::DecorationRole:
61             return Core::FileIconProvider::icon(m_filePath);
62         case Debugger::DetailedErrorView::FullTextRole:
63             return m_filePath.toUserOutput();
64         default:
65             return QVariant();
66         }
67     }
68 
69     return QVariant();
70 }
71 
72 class ExplainingStepItem : public Utils::TreeItem
73 {
74 public:
75     ExplainingStepItem(const ExplainingStep &step, int index);
index() const76     int index() const { return m_index; }
77 
78 private:
79     QVariant data(int column, int role) const override;
80 
81     const ExplainingStep m_step;
82     const int m_index = 0;
83 };
84 
ClangToolsDiagnosticModel(QObject * parent)85 ClangToolsDiagnosticModel::ClangToolsDiagnosticModel(QObject *parent)
86     : ClangToolsDiagnosticModelBase(parent)
87     , m_filesWatcher(std::make_unique<QFileSystemWatcher>())
88 {
89     setRootItem(new Utils::StaticTreeItem(QString()));
90     connectFileWatcher();
91 }
92 
operator <<(QDebug debug,const Diagnostic & d)93 QDebug operator<<(QDebug debug, const Diagnostic &d)
94 {
95     return debug << "name:" << d.name
96                  << "category:" << d.category
97                  << "type:" << d.type
98                  << "hasFixits:" << d.hasFixits
99                  << "explainingSteps:" << d.explainingSteps.size()
100                  << "location:" << d.location
101                  << "description:" << d.description
102                  ;
103 }
104 
addDiagnostics(const Diagnostics & diagnostics,bool generateMarks)105 void ClangToolsDiagnosticModel::addDiagnostics(const Diagnostics &diagnostics, bool generateMarks)
106 {
107     const auto onFixitStatusChanged =
108         [this](const QModelIndex &index, FixitStatus oldStatus, FixitStatus newStatus) {
109             emit fixitStatusChanged(index, oldStatus, newStatus);
110         };
111 
112     for (const Diagnostic &d : diagnostics) {
113         // Check for duplicates
114         const int previousItemCount = m_diagnostics.count();
115         m_diagnostics.insert(d);
116         if (m_diagnostics.count() == previousItemCount) {
117             qCDebug(LOG) << "Not adding duplicate diagnostic:" << d;
118             continue;
119         }
120 
121         // Create file path item if necessary
122         const Utils::FilePath &filePath = d.location.filePath;
123         FilePathItem *&filePathItem = m_filePathToItem[filePath];
124         if (!filePathItem) {
125             filePathItem = new FilePathItem(filePath);
126             rootItem()->appendChild(filePathItem);
127             addWatchedPath(filePath.toString());
128         }
129 
130         // Add to file path item
131         qCDebug(LOG) << "Adding diagnostic:" << d;
132         filePathItem->appendChild(new DiagnosticItem(d, onFixitStatusChanged, generateMarks, this));
133     }
134 }
135 
diagnostics() const136 QSet<Diagnostic> ClangToolsDiagnosticModel::diagnostics() const
137 {
138     return m_diagnostics;
139 }
140 
allChecks() const141 QSet<QString> ClangToolsDiagnosticModel::allChecks() const
142 {
143     QSet<QString> checks;
144     forItemsAtLevel<2>([&](DiagnosticItem *item) {
145         checks.insert(item->diagnostic().name);
146     });
147 
148     return checks;
149 }
150 
clear()151 void ClangToolsDiagnosticModel::clear()
152 {
153     beginResetModel();
154     m_filePathToItem.clear();
155     m_diagnostics.clear();
156     clearAndSetupCache();
157     ClangToolsDiagnosticModelBase::clear();
158     endResetModel();
159 }
160 
updateItems(const DiagnosticItem * changedItem)161 void ClangToolsDiagnosticModel::updateItems(const DiagnosticItem *changedItem)
162 {
163     for (auto item : qAsConst(stepsToItemsCache[changedItem->diagnostic().explainingSteps])) {
164         if (item != changedItem)
165             item->setFixItStatus(changedItem->fixItStatus());
166     }
167 }
168 
connectFileWatcher()169 void ClangToolsDiagnosticModel::connectFileWatcher()
170 {
171     connect(m_filesWatcher.get(),
172             &QFileSystemWatcher::fileChanged,
173             this,
174             &ClangToolsDiagnosticModel::onFileChanged);
175 }
176 
clearAndSetupCache()177 void ClangToolsDiagnosticModel::clearAndSetupCache()
178 {
179     m_filesWatcher = std::make_unique<QFileSystemWatcher>();
180     connectFileWatcher();
181     stepsToItemsCache.clear();
182 }
183 
onFileChanged(const QString & path)184 void ClangToolsDiagnosticModel::onFileChanged(const QString &path)
185 {
186     forItemsAtLevel<2>([&](DiagnosticItem *item){
187         if (item->diagnostic().location.filePath == Utils::FilePath::fromString(path))
188             item->setFixItStatus(FixitStatus::Invalidated);
189     });
190     removeWatchedPath(path);
191 }
192 
removeWatchedPath(const QString & path)193 void ClangToolsDiagnosticModel::removeWatchedPath(const QString &path)
194 {
195     m_filesWatcher->removePath(path);
196 }
197 
addWatchedPath(const QString & path)198 void ClangToolsDiagnosticModel::addWatchedPath(const QString &path)
199 {
200     m_filesWatcher->addPath(path);
201 }
202 
lineColumnString(const Debugger::DiagnosticLocation & location)203 static QString lineColumnString(const Debugger::DiagnosticLocation &location)
204 {
205     return QString("%1:%2").arg(QString::number(location.line), QString::number(location.column));
206 }
207 
createExplainingStepToolTipString(const ExplainingStep & step)208 static QString createExplainingStepToolTipString(const ExplainingStep &step)
209 {
210     using StringPair = QPair<QString, QString>;
211     QList<StringPair> lines;
212 
213     if (!step.message.isEmpty()) {
214         lines << qMakePair(
215             QCoreApplication::translate("ClangTools::ExplainingStep", "Message:"),
216                 step.message.toHtmlEscaped());
217     }
218 
219     lines << qMakePair(
220         QCoreApplication::translate("ClangTools::ExplainingStep", "Location:"),
221                 createFullLocationString(step.location));
222 
223     QString html = QLatin1String("<html>"
224                    "<head>"
225                    "<style>dt { font-weight:bold; } dd { font-family: monospace; }</style>\n"
226                    "<body><dl>");
227 
228     foreach (const StringPair &pair, lines) {
229         html += QLatin1String("<dt>");
230         html += pair.first;
231         html += QLatin1String("</dt><dd>");
232         html += pair.second;
233         html += QLatin1String("</dd>\n");
234     }
235     html += QLatin1String("</dl></body></html>");
236     return html;
237 }
238 
createLocationString(const Debugger::DiagnosticLocation & location)239 static QString createLocationString(const Debugger::DiagnosticLocation &location)
240 {
241     const QString filePath = location.filePath.toUserOutput();
242     const QString lineNumber = QString::number(location.line);
243     const QString fileAndLine = filePath + QLatin1Char(':') + lineNumber;
244     return QLatin1String("in ") + fileAndLine;
245 }
246 
createExplainingStepNumberString(int number)247 static QString createExplainingStepNumberString(int number)
248 {
249     const int fieldWidth = 2;
250     return QString::fromLatin1("%1:").arg(number, fieldWidth);
251 }
252 
createExplainingStepString(const ExplainingStep & explainingStep,int number)253 static QString createExplainingStepString(const ExplainingStep &explainingStep, int number)
254 {
255     return createExplainingStepNumberString(number)
256             + QLatin1Char(' ')
257             + explainingStep.message
258             + QLatin1Char(' ')
259             + createLocationString(explainingStep.location);
260 }
261 
262 
fullText(const Diagnostic & diagnostic)263 static QString fullText(const Diagnostic &diagnostic)
264 {
265     QString text = diagnostic.location.filePath.toUserOutput() + QLatin1Char(':');
266     text += lineColumnString(diagnostic.location) + QLatin1String(": ");
267     if (!diagnostic.category.isEmpty())
268         text += diagnostic.category + QLatin1String(": ");
269     text += diagnostic.type;
270     if (diagnostic.type != diagnostic.description)
271         text += QLatin1String(": ") + diagnostic.description;
272     text += QLatin1Char('\n');
273 
274     // Explaining steps.
275     int explainingStepNumber = 1;
276     foreach (const ExplainingStep &explainingStep, diagnostic.explainingSteps) {
277         text += createExplainingStepString(explainingStep, explainingStepNumber++)
278                 + QLatin1Char('\n');
279     }
280 
281     text.chop(1); // Trailing newline.
282     return text;
283 }
284 
DiagnosticItem(const Diagnostic & diag,const OnFixitStatusChanged & onFixitStatusChanged,bool generateMark,ClangToolsDiagnosticModel * parent)285 DiagnosticItem::DiagnosticItem(const Diagnostic &diag,
286                                const OnFixitStatusChanged &onFixitStatusChanged,
287                                bool generateMark,
288                                ClangToolsDiagnosticModel *parent)
289     : m_diagnostic(diag)
290     , m_onFixitStatusChanged(onFixitStatusChanged)
291     , m_parentModel(parent)
292     , m_mark(generateMark ? new DiagnosticMark(diag) : nullptr)
293 {
294     if (diag.hasFixits)
295         m_fixitStatus = FixitStatus::NotScheduled;
296 
297     // Don't show explaining steps if they add no information.
298     if (diag.explainingSteps.count() == 1) {
299         const ExplainingStep &step = diag.explainingSteps.first();
300         if (step.message == diag.description && step.location == diag.location)
301             return;
302     }
303 
304     if (!diag.explainingSteps.isEmpty())
305         m_parentModel->stepsToItemsCache[diag.explainingSteps].push_back(this);
306 
307     for (int i = 0; i < diag.explainingSteps.size(); ++i )
308         appendChild(new ExplainingStepItem(diag.explainingSteps[i], i));
309 }
310 
~DiagnosticItem()311 DiagnosticItem::~DiagnosticItem()
312 {
313     setFixitOperations(ReplacementOperations());
314     delete m_mark;
315 }
316 
setTextMarkVisible(bool visible)317 void DiagnosticItem::setTextMarkVisible(bool visible)
318 {
319     if (m_mark)
320         m_mark->setVisible(visible);
321 }
322 
flags(int column) const323 Qt::ItemFlags DiagnosticItem::flags(int column) const
324 {
325     const Qt::ItemFlags itemFlags = TreeItem::flags(column);
326     if (column == DiagnosticView::DiagnosticColumn)
327         return itemFlags | Qt::ItemIsUserCheckable;
328     return itemFlags;
329 }
330 
iconData(const Diagnostic & diagnostic)331 static QVariant iconData(const Diagnostic &diagnostic)
332 {
333     QIcon icon = diagnostic.icon();
334     return icon.isNull() ? QVariant() : QVariant(icon);
335 }
336 
data(int column,int role) const337 QVariant DiagnosticItem::data(int column, int role) const
338 {
339     if (column == DiagnosticView::DiagnosticColumn) {
340         switch (role) {
341         case Debugger::DetailedErrorView::LocationRole:
342             return QVariant::fromValue(m_diagnostic.location);
343         case Debugger::DetailedErrorView::FullTextRole:
344             return fullText(m_diagnostic);
345         case ClangToolsDiagnosticModel::DiagnosticRole:
346             return QVariant::fromValue(m_diagnostic);
347         case ClangToolsDiagnosticModel::TextRole:
348             return m_diagnostic.description;
349         case ClangToolsDiagnosticModel::CheckBoxEnabledRole:
350             switch (m_fixitStatus) {
351             case FixitStatus::NotAvailable:
352             case FixitStatus::Applied:
353             case FixitStatus::FailedToApply:
354             case FixitStatus::Invalidated:
355                 return false;
356             case FixitStatus::Scheduled:
357             case FixitStatus::NotScheduled:
358                 return true;
359             }
360             break;
361         case Qt::CheckStateRole: {
362             switch (m_fixitStatus) {
363             case FixitStatus::NotAvailable:
364             case FixitStatus::Invalidated:
365             case FixitStatus::Applied:
366             case FixitStatus::FailedToApply:
367             case FixitStatus::NotScheduled:
368                 return Qt::Unchecked;
369             case FixitStatus::Scheduled:
370                 return Qt::Checked;
371             }
372             break;
373         }
374         case ClangToolsDiagnosticModel::DocumentationUrlRole:
375             return documentationUrl(m_diagnostic.name);
376         case Qt::DisplayRole:
377             return QString("%1: %2").arg(lineColumnString(m_diagnostic.location),
378                                          m_diagnostic.description);
379         case Qt::ToolTipRole:
380             return createDiagnosticToolTipString(m_diagnostic, m_fixitStatus, false);
381         case Qt::DecorationRole:
382             return iconData(m_diagnostic);
383         default:
384             return QVariant();
385         }
386     }
387 
388     return QVariant();
389 }
390 
setData(int column,const QVariant & data,int role)391 bool DiagnosticItem::setData(int column, const QVariant &data, int role)
392 {
393     if (column == DiagnosticView::DiagnosticColumn && role == Qt::CheckStateRole) {
394         if (m_fixitStatus != FixitStatus::Scheduled && m_fixitStatus != FixitStatus::NotScheduled)
395             return false;
396 
397         const FixitStatus newStatus = data.value<Qt::CheckState>() == Qt::Checked
398                                           ? FixitStatus::Scheduled
399                                           : FixitStatus::NotScheduled;
400 
401         setFixItStatus(newStatus);
402         m_parentModel->updateItems(this);
403         return true;
404     }
405 
406     return Utils::TreeItem::setData(column, data, role);
407 }
408 
setFixItStatus(const FixitStatus & status)409 void DiagnosticItem::setFixItStatus(const FixitStatus &status)
410 {
411     const FixitStatus oldStatus = m_fixitStatus;
412     m_fixitStatus = status;
413     update();
414     if (m_onFixitStatusChanged && status != oldStatus)
415         m_onFixitStatusChanged(index(), oldStatus, status);
416     if (status == FixitStatus::Applied || status == FixitStatus::Invalidated) {
417         delete m_mark;
418         m_mark = nullptr;
419     }
420 }
421 
setFixitOperations(const ReplacementOperations & replacements)422 void DiagnosticItem::setFixitOperations(const ReplacementOperations &replacements)
423 {
424     qDeleteAll(m_fixitOperations);
425     m_fixitOperations = replacements;
426 }
427 
hasNewFixIts() const428 bool DiagnosticItem::hasNewFixIts() const
429 {
430     if (m_diagnostic.explainingSteps.empty())
431         return false;
432 
433     return m_parentModel->stepsToItemsCache[m_diagnostic.explainingSteps].front() == this;
434 }
435 
ExplainingStepItem(const ExplainingStep & step,int index)436 ExplainingStepItem::ExplainingStepItem(const ExplainingStep &step, int index)
437     : m_step(step)
438     , m_index(index)
439 {}
440 
rangeString(const QVector<Debugger::DiagnosticLocation> & ranges)441 static QString rangeString(const QVector<Debugger::DiagnosticLocation> &ranges)
442 {
443     return QString("%1-%2").arg(lineColumnString(ranges[0]), lineColumnString(ranges[1]));
444 }
445 
data(int column,int role) const446 QVariant ExplainingStepItem::data(int column, int role) const
447 {
448     if (column == DiagnosticView::DiagnosticColumn) {
449         // DiagnosticColumn
450         switch (role) {
451         case Debugger::DetailedErrorView::LocationRole:
452             return QVariant::fromValue(m_step.location);
453         case Debugger::DetailedErrorView::FullTextRole: {
454             return QString("%1:%2: %3")
455                 .arg(m_step.location.filePath.toUserOutput(),
456                      lineColumnString(m_step.location),
457                      m_step.message);
458         }
459         case ClangToolsDiagnosticModel::TextRole:
460             return m_step.message;
461         case ClangToolsDiagnosticModel::DiagnosticRole:
462             return QVariant::fromValue(static_cast<DiagnosticItem *>(parent())->diagnostic());
463         case ClangToolsDiagnosticModel::DocumentationUrlRole:
464             return parent()->data(column, role);
465         case Qt::DisplayRole: {
466             const Utils::FilePath mainFilePath
467                 = static_cast<DiagnosticItem *>(parent())->diagnostic().location.filePath;
468             const QString locationString
469                 = m_step.location.filePath == mainFilePath
470                       ? lineColumnString(m_step.location)
471                       : QString("%1:%2").arg(m_step.location.filePath.fileName(),
472                                              lineColumnString(m_step.location));
473 
474             if (m_step.isFixIt) {
475                 if (m_step.ranges[0] == m_step.ranges[1]) {
476                     return QString("%1: Insertion of \"%2\".")
477                         .arg(locationString, m_step.message);
478                 }
479                 if (m_step.message.isEmpty()) {
480                     return QString("%1: Removal of %2.")
481                         .arg(locationString, rangeString(m_step.ranges));
482                 }
483                 return QString("%1: Replacement of %2 with: \"%3\".")
484                     .arg(locationString,
485                          rangeString(m_step.ranges),
486                          m_step.message);
487             }
488             return QString("%1: %2").arg(locationString, m_step.message);
489         }
490         case Qt::ToolTipRole:
491             return createExplainingStepToolTipString(m_step);
492         case Qt::DecorationRole:
493             if (m_step.isFixIt)
494                 return Utils::Icons::CODEMODEL_FIXIT.icon();
495             return Utils::Icons::INFO.icon();
496         default:
497             return QVariant();
498         }
499     }
500 
501     return QVariant();
502 }
503 
DiagnosticFilterModel(QObject * parent)504 DiagnosticFilterModel::DiagnosticFilterModel(QObject *parent)
505     : QSortFilterProxyModel(parent)
506 {
507     // So that when a user closes and re-opens a project and *then* clicks "Suppress",
508     // we enter that information into the project settings.
509     connect(ProjectExplorer::SessionManager::instance(),
510             &ProjectExplorer::SessionManager::projectAdded, this,
511             [this](ProjectExplorer::Project *project) {
512                 if (!m_project && project->projectDirectory() == m_lastProjectDirectory)
513                     setProject(project);
514             });
515     connect(this, &QAbstractItemModel::modelReset, this, [this]() {
516         reset();
517         emit fixitCountersChanged(m_fixitsScheduled, m_fixitsScheduable);
518     });
519     connect(this, &QAbstractItemModel::rowsInserted,
520             this, [this](const QModelIndex &parent, int first, int last) {
521         const Counters counters = countDiagnostics(parent, first, last);
522         m_diagnostics += counters.diagnostics;
523         m_fixitsScheduable += counters.fixits;
524         emit fixitCountersChanged(m_fixitsScheduled, m_fixitsScheduable);
525     });
526     connect(this, &QAbstractItemModel::rowsAboutToBeRemoved,
527             this, [this](const QModelIndex &parent, int first, int last) {
528         const Counters counters = countDiagnostics(parent, first, last);
529         m_diagnostics -= counters.diagnostics;
530         m_fixitsScheduable -= counters.fixits;
531         emit fixitCountersChanged(m_fixitsScheduled, m_fixitsScheduable);
532     });
533 }
534 
setProject(ProjectExplorer::Project * project)535 void DiagnosticFilterModel::setProject(ProjectExplorer::Project *project)
536 {
537     QTC_ASSERT(project, return);
538     if (m_project) {
539         disconnect(ClangToolsProjectSettings::getSettings(m_project).data(),
540                    &ClangToolsProjectSettings::suppressedDiagnosticsChanged, this,
541                    &DiagnosticFilterModel::handleSuppressedDiagnosticsChanged);
542     }
543     m_project = project;
544     m_lastProjectDirectory = m_project->projectDirectory();
545     connect(ClangToolsProjectSettings::getSettings(m_project).data(),
546             &ClangToolsProjectSettings::suppressedDiagnosticsChanged,
547             this, &DiagnosticFilterModel::handleSuppressedDiagnosticsChanged);
548     handleSuppressedDiagnosticsChanged();
549 }
550 
addSuppressedDiagnostics(const SuppressedDiagnosticsList & diags)551 void DiagnosticFilterModel::addSuppressedDiagnostics(const SuppressedDiagnosticsList &diags)
552 {
553     m_suppressedDiagnostics << diags;
554     invalidate();
555 }
556 
addSuppressedDiagnostic(const SuppressedDiagnostic & diag)557 void DiagnosticFilterModel::addSuppressedDiagnostic(const SuppressedDiagnostic &diag)
558 {
559     QTC_ASSERT(!m_project, return);
560     m_suppressedDiagnostics << diag;
561     invalidate();
562 }
563 
onFixitStatusChanged(const QModelIndex & sourceIndex,FixitStatus oldStatus,FixitStatus newStatus)564 void DiagnosticFilterModel::onFixitStatusChanged(const QModelIndex &sourceIndex,
565                                                  FixitStatus oldStatus,
566                                                  FixitStatus newStatus)
567 {
568     if (!mapFromSource(sourceIndex).isValid())
569         return;
570 
571     if (newStatus == FixitStatus::Scheduled)
572         ++m_fixitsScheduled;
573     else if (oldStatus == FixitStatus::Scheduled) {
574         --m_fixitsScheduled;
575         if (newStatus != FixitStatus::NotScheduled)
576             --m_fixitsScheduable;
577     }
578 
579     emit fixitCountersChanged(m_fixitsScheduled, m_fixitsScheduable);
580 }
581 
reset()582 void DiagnosticFilterModel::reset()
583 {
584     m_filterOptions.reset();
585 
586     m_fixitsScheduled = 0;
587     m_fixitsScheduable = 0;
588     m_diagnostics = 0;
589 }
590 
countDiagnostics(const QModelIndex & parent,int first,int last) const591 DiagnosticFilterModel::Counters DiagnosticFilterModel::countDiagnostics(const QModelIndex &parent,
592                                                                         int first,
593                                                                         int last) const
594 {
595     Counters counters;
596     const auto countItem = [&](Utils::TreeItem *item){
597         if (!mapFromSource(item->index()).isValid())
598             return; // Do not count filtered out items.
599         ++counters.diagnostics;
600         if (static_cast<DiagnosticItem *>(item)->diagnostic().hasFixits)
601             ++counters.fixits;
602     };
603 
604     auto model = static_cast<ClangToolsDiagnosticModel *>(sourceModel());
605     for (int row = first; row <= last; ++row) {
606         Utils::TreeItem *treeItem = model->itemForIndex(mapToSource(index(row, 0, parent)));
607         if (treeItem->level() == 1)
608             static_cast<FilePathItem *>(treeItem)->forChildrenAtLevel(1, countItem);
609         else if (treeItem->level() == 2)
610             countItem(treeItem);
611     }
612 
613     return counters;
614 }
615 
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const616 bool DiagnosticFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
617 {
618     auto model = static_cast<ClangToolsDiagnosticModel *>(sourceModel());
619 
620     // FilePathItem - hide if no diagnostics match
621     if (!sourceParent.isValid()) {
622         const QModelIndex filePathIndex = model->index(sourceRow, 0);
623         const int rowCount = model->rowCount(filePathIndex);
624         if (rowCount == 0)
625             return true; // Children not yet added.
626         for (int row = 0; row < rowCount; ++row) {
627             if (filterAcceptsRow(row, filePathIndex))
628                 return true;
629         }
630         return false;
631     }
632 
633     // DiagnosticItem
634     Utils::TreeItem *parentItem = model->itemForIndex(sourceParent);
635     QTC_ASSERT(parentItem, return true);
636     if (parentItem->level() == 1) {
637         auto filePathItem = static_cast<FilePathItem *>(parentItem);
638         auto diagnosticItem = static_cast<DiagnosticItem *>(filePathItem->childAt(sourceRow));
639         const Diagnostic &diag = diagnosticItem->diagnostic();
640 
641         // Filtered out?
642         if (m_filterOptions && !m_filterOptions->checks.contains(diag.name)) {
643             diagnosticItem->setTextMarkVisible(false);
644             return false;
645         }
646 
647         // Explicitly suppressed?
648         foreach (const SuppressedDiagnostic &d, m_suppressedDiagnostics) {
649             if (d.description != diag.description)
650                 continue;
651             Utils::FilePath filePath = d.filePath;
652             if (d.filePath.toFileInfo().isRelative())
653                 filePath = m_lastProjectDirectory.pathAppended(filePath.toString());
654             if (filePath == diag.location.filePath) {
655                 diagnosticItem->setTextMarkVisible(false);
656                 return false;
657             }
658         }
659         diagnosticItem->setTextMarkVisible(true);
660         return true;
661     }
662 
663     return true; // ExplainingStepItem
664 }
665 
lessThan(const QModelIndex & l,const QModelIndex & r) const666 bool DiagnosticFilterModel::lessThan(const QModelIndex &l, const QModelIndex &r) const
667 {
668     auto model = static_cast<ClangToolsDiagnosticModel *>(sourceModel());
669     Utils::TreeItem *itemLeft = model->itemForIndex(l);
670     QTC_ASSERT(itemLeft, return QSortFilterProxyModel::lessThan(l, r));
671     const bool isComparingDiagnostics = itemLeft->level() > 1;
672 
673     if (sortColumn() == Debugger::DetailedErrorView::DiagnosticColumn && isComparingDiagnostics) {
674         bool result = false;
675         if (itemLeft->level() == 2) {
676             using Debugger::DiagnosticLocation;
677             const int role = Debugger::DetailedErrorView::LocationRole;
678 
679             const auto leftLoc = sourceModel()->data(l, role).value<DiagnosticLocation>();
680             const auto leftText
681                 = sourceModel()->data(l, ClangToolsDiagnosticModel::TextRole).toString();
682 
683             const auto rightLoc = sourceModel()->data(r, role).value<DiagnosticLocation>();
684             const auto rightText
685                 = sourceModel()->data(r, ClangToolsDiagnosticModel::TextRole).toString();
686 
687             result = std::tie(leftLoc.line, leftLoc.column, leftText)
688                      < std::tie(rightLoc.line, rightLoc.column, rightText);
689         } else if (itemLeft->level() == 3) {
690             Utils::TreeItem *itemRight = model->itemForIndex(r);
691             QTC_ASSERT(itemRight, QSortFilterProxyModel::lessThan(l, r));
692             const auto left = static_cast<ExplainingStepItem *>(itemLeft);
693             const auto right = static_cast<ExplainingStepItem *>(itemRight);
694             result = left->index() < right->index();
695         } else {
696             QTC_CHECK(false && "Unexpected item");
697         }
698 
699         if (sortOrder() == Qt::DescendingOrder)
700             return !result; // Do not change the order of these item as this might be confusing.
701         return result;
702     }
703 
704     // FilePathItem
705     return QSortFilterProxyModel::lessThan(l, r);
706 }
707 
handleSuppressedDiagnosticsChanged()708 void DiagnosticFilterModel::handleSuppressedDiagnosticsChanged()
709 {
710     QTC_ASSERT(m_project, return);
711     m_suppressedDiagnostics
712             = ClangToolsProjectSettings::getSettings(m_project)->suppressedDiagnostics();
713     invalidate();
714 }
715 
filterOptions() const716 OptionalFilterOptions DiagnosticFilterModel::filterOptions() const
717 {
718     return m_filterOptions;
719 }
720 
setFilterOptions(const OptionalFilterOptions & filterOptions)721 void DiagnosticFilterModel::setFilterOptions(const OptionalFilterOptions &filterOptions)
722 {
723     m_filterOptions = filterOptions;
724     invalidateFilter();
725 }
726 
727 } // namespace Internal
728 } // namespace ClangTools
729