cppcheck/gui/statsdialog.cpp

410 lines
16 KiB
C++

/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2021 Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "statsdialog.h"
#include "checkstatistics.h"
#include "common.h"
#include "projectfile.h"
#include <QClipboard>
#include <QDate>
#include <QFileDialog>
#include <QFileInfo>
#include <QMimeData>
#include <QPrinter>
#include <QTextDocument>
#include <QWidget>
static const QString CPPCHECK("cppcheck");
StatsDialog::StatsDialog(QWidget *parent)
: QDialog(parent),
mStatistics(nullptr)
{
mUI.setupUi(this);
setWindowFlags(Qt::Window);
connect(mUI.mCopyToClipboard, &QPushButton::pressed, this, &StatsDialog::copyToClipboard);
connect(mUI.mPDFexport, &QPushButton::pressed, this, &StatsDialog::pdfExport);
}
void StatsDialog::setProject(const ProjectFile* projectFile)
{
if (projectFile) {
mUI.mProject->setText(projectFile->getRootPath());
mUI.mPaths->setText(projectFile->getCheckPaths().join(";"));
mUI.mIncludePaths->setText(projectFile->getIncludeDirs().join(";"));
mUI.mDefines->setText(projectFile->getDefines().join(";"));
mUI.mUndefines->setText(projectFile->getUndefines().join(";"));
#ifndef HAVE_QCHART
mUI.mTabHistory->setVisible(false);
#else
QString statsFile;
if (!projectFile->getBuildDir().isEmpty()) {
const QString prjpath = QFileInfo(projectFile->getFilename()).absolutePath();
const QString buildDir = prjpath + '/' + projectFile->getBuildDir();
if (QDir(buildDir).exists()) {
statsFile = buildDir + "/statistics.txt";
}
}
mUI.mLblHistoryFile->setText(tr("File: ") + (statsFile.isEmpty() ? tr("No cppcheck build dir") : statsFile));
if (!statsFile.isEmpty()) {
QChartView *chartView;
chartView = createChart(statsFile, "cppcheck");
mUI.mTabHistory->layout()->addWidget(chartView);
if (projectFile->getClangAnalyzer()) {
chartView = createChart(statsFile, CLANG_ANALYZER);
mUI.mTabHistory->layout()->addWidget(chartView);
}
if (projectFile->getClangTidy()) {
chartView = createChart(statsFile, CLANG_TIDY);
mUI.mTabHistory->layout()->addWidget(chartView);
}
}
#endif
} else {
mUI.mProject->setText(QString());
mUI.mPaths->setText(QString());
mUI.mIncludePaths->setText(QString());
mUI.mDefines->setText(QString());
mUI.mUndefines->setText(QString());
}
}
void StatsDialog::setPathSelected(const QString& path)
{
mUI.mPath->setText(path);
}
void StatsDialog::setNumberOfFilesScanned(int num)
{
mUI.mNumberOfFilesScanned->setText(QString::number(num));
}
void StatsDialog::setScanDuration(double seconds)
{
// Factor the duration into units (days/hours/minutes/seconds)
int secs = seconds;
int days = secs / (24 * 60 * 60);
secs -= days * (24 * 60 * 60);
int hours = secs / (60 * 60);
secs -= hours * (60 * 60);
int mins = secs / 60;
secs -= mins * 60;
// Concatenate the two most significant units (e.g. "1 day and 3 hours")
QStringList parts;
if (days)
parts << ((days == 1) ? tr("1 day") : tr("%1 days").arg(days));
if (hours)
parts << ((hours == 1) ? tr("1 hour") : tr("%1 hours").arg(hours));
if (mins && parts.size() < 2)
parts << ((mins == 1) ? tr("1 minute") : tr("%1 minutes").arg(mins));
if (secs && parts.size() < 2)
parts << ((secs == 1) ? tr("1 second") : tr("%1 seconds").arg(secs));
// For durations < 1s, show the fraction of a second (e.g. "0.7 seconds")
if (parts.isEmpty())
parts << tr("0.%1 seconds").arg(int(10.0 *(seconds - secs)));
mUI.mScanDuration->setText(parts.join(tr(" and ")));
}
void StatsDialog::pdfExport()
{
const QString Stat = QString(
"<center><h1>%1 %2</h1></center>\n"
"<font color=\"red\"><h3>%3 : %4</h3></font>\n"
"<font color=\"green\"><h3>%5 : %6</h3></font>\n"
"<font color=\"orange\"><h3>%7 : %8</h3></font>\n"
"<font color=\"blue\"><h3>%9 : %10</h3></font>\n"
"<font color=\"blue\"><h3>%11 : %12</h3></font>\n"
"<font color=\"purple\"><h3>%13 : %14</h3></font>\n")
.arg(tr("Statistics"))
.arg(QDate::currentDate().toString("dd.MM.yyyy"))
.arg(tr("Errors"))
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowErrors))
.arg(tr("Warnings"))
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowWarnings))
.arg(tr("Style warnings"))
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowStyle))
.arg(tr("Portability warnings"))
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowPortability))
.arg(tr("Performance warnings"))
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowPerformance))
.arg(tr("Information messages"))
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowInformation));
QString fileName = QFileDialog::getSaveFileName((QWidget*)nullptr, tr("Export PDF"), QString(), "*.pdf");
if (QFileInfo(fileName).suffix().isEmpty()) {
fileName.append(".pdf");
}
QPrinter printer(QPrinter::PrinterResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setPageSize(QPageSize(QPageSize::A4));
printer.setOutputFileName(fileName);
QTextDocument doc;
doc.setHtml(Stat);
// doc.setPageSize(printer.pageRect().size());
doc.print(&printer);
}
void StatsDialog::copyToClipboard()
{
QClipboard *clipboard = QApplication::clipboard();
if (!clipboard)
return;
const QString projSettings(tr("Project Settings"));
const QString project(tr("Project"));
const QString paths(tr("Paths"));
const QString incPaths(tr("Include paths"));
const QString defines(tr("Defines"));
const QString undefines(tr("Undefines"));
const QString prevScan(tr("Previous Scan"));
const QString selPath(tr("Path selected"));
const QString numFiles(tr("Number of files scanned"));
const QString duration(tr("Scan duration"));
const QString stats(tr("Statistics"));
const QString errors(tr("Errors"));
const QString warnings(tr("Warnings"));
const QString style(tr("Style warnings"));
const QString portability(tr("Portability warnings"));
const QString performance(tr("Performance warnings"));
const QString information(tr("Information messages"));
// Plain text summary
const QString settings = QString(
"%1\n"
"\t%2:\t%3\n"
"\t%4:\t%5\n"
"\t%6:\t%7\n"
"\t%8:\t%9\n"
"\t%10:\t%11\n"
)
.arg(projSettings)
.arg(project)
.arg(mUI.mProject->text())
.arg(paths)
.arg(mUI.mPaths->text())
.arg(incPaths)
.arg(mUI.mIncludePaths->text())
.arg(defines)
.arg(mUI.mDefines->text())
.arg(undefines)
.arg(mUI.mUndefines->text());
const QString previous = QString(
"%1\n"
"\t%2:\t%3\n"
"\t%4:\t%5\n"
"\t%6:\t%7\n"
)
.arg(prevScan)
.arg(selPath)
.arg(mUI.mPath->text())
.arg(numFiles)
.arg(mUI.mNumberOfFilesScanned->text())
.arg(duration)
.arg(mUI.mScanDuration->text());
const QString statistics = QString(
"%1\n"
"\t%2:\t%3\n"
"\t%4:\t%5\n"
"\t%6:\t%7\n"
"\t%8:\t%9\n"
"\t%10:\t%11\n"
"\t%12:\t%13\n"
)
.arg(stats)
.arg(errors)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowErrors))
.arg(warnings)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowWarnings))
.arg(style)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowStyle))
.arg(portability)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowPortability))
.arg(performance)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowPerformance))
.arg(information)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowInformation));
const QString textSummary = settings + previous + statistics;
// HTML summary
const QString htmlSettings = QString(
"<h3>%1<h3>\n"
"<table>\n"
" <tr><th>%2:</th><td>%3</td></tr>\n"
" <tr><th>%4:</th><td>%5</td></tr>\n"
" <tr><th>%6:</th><td>%7</td></tr>\n"
" <tr><th>%8:</th><td>%9</td></tr>\n"
" <tr><th>%10:</th><td>%11</td></tr>\n"
"</table>\n"
)
.arg(projSettings)
.arg(project)
.arg(mUI.mProject->text())
.arg(paths)
.arg(mUI.mPaths->text())
.arg(incPaths)
.arg(mUI.mIncludePaths->text())
.arg(defines)
.arg(mUI.mDefines->text())
.arg(undefines)
.arg(mUI.mUndefines->text());
const QString htmlPrevious = QString(
"<h3>%1</h3>\n"
"<table>\n"
" <tr><th>%2:</th><td>%3</td></tr>\n"
" <tr><th>%4:</th><td>%5</td></tr>\n"
" <tr><th>%6:</th><td>%7</td></tr>\n"
"</table>\n"
)
.arg(prevScan)
.arg(selPath)
.arg(mUI.mPath->text())
.arg(numFiles)
.arg(mUI.mNumberOfFilesScanned->text())
.arg(duration)
.arg(mUI.mScanDuration->text());
const QString htmlStatistics = QString(
"<h3>%1</h3>\n"
" <tr><th>%2:</th><td>%3</td></tr>\n"
" <tr><th>%4:</th><td>%5</td></tr>\n"
" <tr><th>%6:</th><td>%7</td></tr>\n"
" <tr><th>%8:</th><td>%9</td></tr>\n"
" <tr><th>%10:</th><td>%11</td></tr>\n"
" <tr><th>%12:</th><td>%13</td></tr>\n"
"</table>\n"
)
.arg(stats)
.arg(errors)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowErrors))
.arg(warnings)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowWarnings))
.arg(style)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowStyle))
.arg(portability)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowPortability))
.arg(performance)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowPerformance))
.arg(information)
.arg(mStatistics->getCount(CPPCHECK,ShowTypes::ShowInformation));
const QString htmlSummary = htmlSettings + htmlPrevious + htmlStatistics;
QMimeData *mimeData = new QMimeData();
mimeData->setText(textSummary);
mimeData->setHtml(htmlSummary);
clipboard->setMimeData(mimeData);
}
void StatsDialog::setStatistics(const CheckStatistics *stats)
{
mStatistics = stats;
mUI.mLblErrors->setText(QString("%1").arg(stats->getCount(CPPCHECK,ShowTypes::ShowErrors)));
mUI.mLblWarnings->setText(QString("%1").arg(stats->getCount(CPPCHECK,ShowTypes::ShowWarnings)));
mUI.mLblStyle->setText(QString("%1").arg(stats->getCount(CPPCHECK,ShowTypes::ShowStyle)));
mUI.mLblPortability->setText(QString("%1").arg(stats->getCount(CPPCHECK,ShowTypes::ShowPortability)));
mUI.mLblPerformance->setText(QString("%1").arg(stats->getCount(CPPCHECK,ShowTypes::ShowPerformance)));
mUI.mLblInformation->setText(QString("%1").arg(stats->getCount(CPPCHECK,ShowTypes::ShowInformation)));
}
#ifdef HAVE_QCHART
QChartView *StatsDialog::createChart(const QString &statsFile, const QString &tool)
{
QChart *chart = new QChart;
chart->addSeries(numberOfReports(statsFile, tool + "-error"));
chart->addSeries(numberOfReports(statsFile, tool + "-warning"));
chart->addSeries(numberOfReports(statsFile, tool + "-style"));
chart->addSeries(numberOfReports(statsFile, tool + "-performance"));
chart->addSeries(numberOfReports(statsFile, tool + "-portability"));
QDateTimeAxis *axisX = new QDateTimeAxis;
axisX->setTitleText("Date");
chart->addAxis(axisX, Qt::AlignBottom);
foreach (QAbstractSeries *s, chart->series()) {
s->attachAxis(axisX);
}
QValueAxis *axisY = new QValueAxis;
axisY->setLabelFormat("%i");
axisY->setTitleText("Count");
chart->addAxis(axisY, Qt::AlignLeft);
qreal maxY = 0;
foreach (QAbstractSeries *s, chart->series()) {
s->attachAxis(axisY);
if (QLineSeries *ls = dynamic_cast<QLineSeries*>(s)) {
foreach (QPointF p, ls->points()) {
if (p.y() > maxY)
maxY = p.y();
}
}
}
axisY->setMax(maxY);
//chart->createDefaultAxes();
chart->setTitle(tool);
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
return chartView;
}
QLineSeries *StatsDialog::numberOfReports(const QString &fileName, const QString &severity) const
{
QLineSeries *series = new QLineSeries();
series->setName(severity);
QFile f(fileName);
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
quint64 t = 0;
QTextStream in(&f);
while (!in.atEnd()) {
QString line = in.readLine();
QRegExp rxdate("\\[(\\d\\d)\\.(\\d\\d)\\.(\\d\\d\\d\\d)\\]");
if (rxdate.exactMatch(line)) {
int y = rxdate.cap(3).toInt();
int m = rxdate.cap(2).toInt();
int d = rxdate.cap(1).toInt();
QDateTime dt;
dt.setDate(QDate(y,m,d));
if (t == dt.toMSecsSinceEpoch())
t += 1000;
else
t = dt.toMSecsSinceEpoch();
}
if (line.startsWith(severity + ':')) {
int y = line.mid(1+severity.length()).toInt();
series->append(t, y);
}
}
}
return series;
}
#endif