/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd ** All rights reserved. ** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us ** ** This file is part of the Qt Enterprise LicenseChecker Add-on. ** ** Licensees holding valid Qt Enterprise licenses may use this file in ** accordance with the Qt Enterprise License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. ** ** If you have questions regarding the use of this file, please use ** contact form at http://www.qt.io/contact-us ** ****************************************************************************/ #include "clangstaticanalyzerdiagnosticview.h" #include "clangstaticanalyzerlogfilereader.h" #include "clangstaticanalyzerdiagnosticmodel.h" #include "clangstaticanalyzerprojectsettings.h" #include "clangstaticanalyzerprojectsettingsmanager.h" #include "clangstaticanalyzerutils.h" #include #include #include #include #include #include #include #include using namespace Analyzer; namespace { QLabel *createCommonLabel() { QLabel *label = new QLabel; label->setWordWrap(true); label->setContentsMargins(0, 0, 0, 0); label->setMargin(0); label->setIndent(10); return label; } QString createSummaryText(const ClangStaticAnalyzer::Internal::Diagnostic &diagnostic, const QPalette &palette) { const QColor color = palette.color(QPalette::Text); const QString linkStyle = QString::fromLatin1("style=\"color:rgba(%1, %2, %3, %4);\"") .arg(color.red()) .arg(color.green()) .arg(color.blue()) .arg(int(0.7 * 255)); const QString fileName = QFileInfo(diagnostic.location.filePath).fileName(); const QString location = fileName + QLatin1Char(' ') + QString::number(diagnostic.location.line); return QString::fromLatin1("%1  %2") .arg(diagnostic.description.toHtmlEscaped(), location, linkStyle); } QLabel *createSummaryLabel(const ClangStaticAnalyzer::Internal::Diagnostic &diagnostic) { QLabel *label = createCommonLabel(); QPalette palette = label->palette(); palette.setBrush(QPalette::Text, palette.highlightedText()); label->setPalette(palette); label->setText(createSummaryText(diagnostic, palette)); return label; } QLabel *createExplainingStepLabel(const QFont &font, bool useAlternateRowPalette) { QLabel *label = createCommonLabel(); // Font QFont fixedPitchFont = font; fixedPitchFont.setFixedPitch(true); label->setFont(fixedPitchFont); // Background label->setAutoFillBackground(true); if (useAlternateRowPalette) { QPalette p = label->palette(); p.setBrush(QPalette::Base, p.alternateBase()); label->setPalette(p); } return label; } QString createLocationString(const ClangStaticAnalyzer::Internal::Location &location, bool withMarkup, bool withAbsolutePath) { const QString filePath = location.filePath; const QString lineNumber = QString::number(location.line); const QString columnNumber = QString::number(location.column - 1); const QString fileAndLine = (withAbsolutePath ? filePath : QFileInfo(filePath).fileName()) + QLatin1Char(':') + lineNumber; if (withMarkup) { return QLatin1String("in ") + fileAndLine + QLatin1String(""); } else { return QLatin1String("in ") + fileAndLine; } } QString createExplainingStepNumberString(int number, bool withMarkup) { const int fieldWidth = 2; const QString result = QString::fromLatin1("%1:").arg(number, fieldWidth); return withMarkup ? QLatin1String("") + result + QLatin1String("") : result; } QString createExplainingStepToolTipString(const ClangStaticAnalyzer::Internal::ExplainingStep &step) { if (step.message == step.extendedMessage) return createFullLocationString(step.location); typedef QPair StringPair; QList lines; if (!step.message.isEmpty()) { lines << qMakePair( QCoreApplication::translate("ClangStaticAnalyzer::ExplainingStep", "Message:"), step.message.toHtmlEscaped()); } if (!step.extendedMessage.isEmpty()) { lines << qMakePair( QCoreApplication::translate("ClangStaticAnalyzer::ExplainingStep", "Extended Message:"), step.extendedMessage.toHtmlEscaped()); } lines << qMakePair( QCoreApplication::translate("ClangStaticAnalyzer::ExplainingStep", "Location:"), createFullLocationString(step.location)); QString html = QLatin1String("" "" "\n" "
"); foreach (const StringPair &pair, lines) { html += QLatin1String("
"); html += pair.first; html += QLatin1String("
"); html += pair.second; html += QLatin1String("
\n"); } html += QLatin1String("
"); return html; } QString createExplainingStepString( const ClangStaticAnalyzer::Internal::ExplainingStep &explainingStep, int number, bool withMarkup, bool withAbsolutePath) { return createExplainingStepNumberString(number, withMarkup) + QLatin1Char(' ') + (withMarkup ? explainingStep.extendedMessage.toHtmlEscaped() : explainingStep.extendedMessage) + QLatin1Char(' ') + createLocationString(explainingStep.location, withMarkup, withAbsolutePath); } } // anonymous namespace namespace ClangStaticAnalyzer { namespace Internal { ClangStaticAnalyzerDiagnosticDelegate::ClangStaticAnalyzerDiagnosticDelegate(QListView *parent) : DetailedErrorDelegate(parent) { } DetailedErrorDelegate::SummaryLineInfo ClangStaticAnalyzerDiagnosticDelegate::summaryInfo( const QModelIndex &index) const { const Diagnostic diagnostic = index.data(Qt::UserRole).value(); QTC_ASSERT(diagnostic.isValid(), return SummaryLineInfo()); DetailedErrorDelegate::SummaryLineInfo info; info.errorText = diagnostic.description; info.errorLocation = createLocationString(diagnostic.location, /*withMarkup=*/ false, /*withAbsolutePath=*/ false); return info; } Diagnostic ClangStaticAnalyzerDiagnosticDelegate::getDiagnostic(const QModelIndex &index) const { return index.data(Qt::UserRole).value(); } QWidget *ClangStaticAnalyzerDiagnosticDelegate::createDetailsWidget(const QFont &font, const QModelIndex &index, QWidget *parent) const { QWidget *widget = new QWidget(parent); const Diagnostic diagnostic = getDiagnostic(index); if (!diagnostic.isValid()) return widget; QVBoxLayout *layout = new QVBoxLayout; // Add summary label QLabel *summaryLineLabel = createSummaryLabel(diagnostic); connect(summaryLineLabel, &QLabel::linkActivated, this, &ClangStaticAnalyzerDiagnosticDelegate::openLinkInEditor); layout->addWidget(summaryLineLabel); // Add labels for explaining steps int explainingStepNumber = 1; foreach (const ExplainingStep &explainingStep, diagnostic.explainingSteps) { const QString text = createExplainingStepString(explainingStep, explainingStepNumber++, /*withMarkup=*/ true, /*withAbsolutePath=*/ false); QLabel *label = createExplainingStepLabel(font, explainingStepNumber % 2 == 0); label->setParent(widget); label->setText(text); label->setToolTip(createExplainingStepToolTipString(explainingStep)); connect(label, &QLabel::linkActivated, this, &ClangStaticAnalyzerDiagnosticDelegate::openLinkInEditor); layout->addWidget(label); } layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); widget->setLayout(layout); return widget; } QString ClangStaticAnalyzerDiagnosticDelegate::textualRepresentation() const { QTC_ASSERT(m_detailsIndex.isValid(), return QString()); const Diagnostic diagnostic = getDiagnostic(m_detailsIndex); QTC_ASSERT(diagnostic.isValid(), return QString()); // Create summary QString clipboardText = diagnostic.category + QLatin1String(": ") + diagnostic.type; if (diagnostic.type != diagnostic.description) clipboardText += QLatin1String(": ") + diagnostic.description; clipboardText += QLatin1Char('\n'); // Create explaining steps int explainingStepNumber = 1; foreach (const ExplainingStep &explainingStep, diagnostic.explainingSteps) { clipboardText += createExplainingStepString(explainingStep, explainingStepNumber++, /*withMarkup=*/ false, /*withAbsolutePath=*/ true) + QLatin1Char('\n'); } clipboardText.chop(1); // Remove \n return clipboardText; } ClangStaticAnalyzerDiagnosticView::ClangStaticAnalyzerDiagnosticView(QWidget *parent) : Analyzer::DetailedErrorView(parent) { ClangStaticAnalyzerDiagnosticDelegate *delegate = new ClangStaticAnalyzerDiagnosticDelegate(this); setItemDelegate(delegate); m_suppressAction = new QAction(tr("Suppress this diagnostic"), this); connect(m_suppressAction, &QAction::triggered, [this](bool) { suppressCurrentDiagnostic(); }); } void ClangStaticAnalyzerDiagnosticView::suppressCurrentDiagnostic() { const QModelIndexList indexes = selectedIndexes(); QTC_ASSERT(indexes.count() == 1, return); const Diagnostic diag = static_cast(itemDelegate()) ->getDiagnostic(indexes.first()); QTC_ASSERT(diag.isValid(), return); // If the original project was closed, we work directly on the filter model, otherwise // we go via the project settings. auto * const filterModel = static_cast(model()); ProjectExplorer::Project * const project = filterModel->project(); if (project) { Utils::FileName filePath = Utils::FileName::fromString(diag.location.filePath); const Utils::FileName relativeFilePath = filePath.relativeChildPath(project->projectDirectory()); if (!relativeFilePath.isEmpty()) filePath = relativeFilePath; const SuppressedDiagnostic supDiag(filePath, diag.description, diag.issueContextKind, diag.issueContext, diag.explainingSteps.count()); ProjectSettingsManager::getSettings(project)->addSuppressedDiagnostic(supDiag); } else { filterModel->addSuppressedDiagnostic(SuppressedDiagnostic(diag)); } } QList ClangStaticAnalyzerDiagnosticView::customActions() const { return QList() << m_suppressAction; } } // namespace Internal } // namespace ClangStaticAnalyzer