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