You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2169 lines
79 KiB

/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2024 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 "mainwindow.h"
#include "addoninfo.h"
#include "applicationlist.h"
#include "aboutdialog.h"
#include "analyzerinfo.h"
#include "checkthread.h"
#include "common.h"
#include "cppcheck.h"
#include "errortypes.h"
#include "filelist.h"
#include "filesettings.h"
#include "compliancereportdialog.h"
#include "fileviewdialog.h"
#include "helpdialog.h"
#include "importproject.h"
#include "librarydialog.h"
#include "platform.h"
#include "projectfile.h"
#include "projectfiledialog.h"
#include "report.h"
#include "resultsview.h"
#include "scratchpad.h"
#include "settings.h"
#include "showtypes.h"
#include "statsdialog.h"
#include "settingsdialog.h"
#include "standards.h"
#include "suppressions.h"
#include "threadhandler.h"
#include "threadresult.h"
#include "translationhandler.h"
#include "ui_mainwindow.h"
#include <algorithm>
#include <iterator>
#include <list>
#include <set>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include <QApplication>
#include <QAction>
#include <QActionGroup>
#include <QByteArray>
#include <QChar>
#include <QCloseEvent>
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDialog>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QKeySequence>
#include <QLabel>
#include <QLineEdit>
#include <QList>
#include <QMap>
#include <QMenu>
#include <QMessageBox>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QPushButton>
#include <QRegularExpression>
#include <QSettings>
#include <QSize>
#include <QTimer>
#include <QTemporaryFile>
#include <QToolBar>
#include <QUrl>
#include <QVariant>
#include <Qt>
#include "json.h"
static const QString compile_commands_json("compile_commands.json");
static QString fromNativePath(const QString& p) {
#ifdef Q_OS_WIN
QString ret(p);
ret.replace('\\', '/');
return ret;
#else
return p;
#endif
}
MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) :
mSettings(settings),
mApplications(new ApplicationList(this)),
mTranslation(th),
mUI(new Ui::MainWindow),
mPlatformActions(new QActionGroup(this)),
mCStandardActions(new QActionGroup(this)),
mCppStandardActions(new QActionGroup(this)),
mSelectLanguageActions(new QActionGroup(this))
{
{
Settings tempSettings;
tempSettings.exename = QCoreApplication::applicationFilePath().toStdString();
Settings::loadCppcheckCfg(tempSettings, tempSettings.supprs); // TODO: how to handle error?
mCppcheckCfgProductName = QString::fromStdString(tempSettings.cppcheckCfgProductName);
mCppcheckCfgAbout = QString::fromStdString(tempSettings.cppcheckCfgAbout);
}
mUI->setupUi(this);
mThread = new ThreadHandler(this);
mUI->mResults->initialize(mSettings, mApplications, mThread);
// Filter timer to delay filtering results slightly while typing
mFilterTimer = new QTimer(this);
mFilterTimer->setInterval(500);
mFilterTimer->setSingleShot(true);
connect(mFilterTimer, &QTimer::timeout, this, &MainWindow::filterResults);
// "Filter" toolbar
mLineEditFilter = new QLineEdit(mUI->mToolBarFilter);
mLineEditFilter->setPlaceholderText(tr("Quick Filter:"));
mLineEditFilter->setClearButtonEnabled(true);
mUI->mToolBarFilter->addWidget(mLineEditFilter);
connect(mLineEditFilter, SIGNAL(textChanged(QString)), mFilterTimer, SLOT(start()));
connect(mLineEditFilter, &QLineEdit::returnPressed, this, &MainWindow::filterResults);
connect(mUI->mActionPrint, SIGNAL(triggered()), mUI->mResults, SLOT(print()));
connect(mUI->mActionPrintPreview, SIGNAL(triggered()), mUI->mResults, SLOT(printPreview()));
connect(mUI->mActionQuit, &QAction::triggered, this, &MainWindow::close);
connect(mUI->mActionAnalyzeFiles, &QAction::triggered, this, &MainWindow::analyzeFiles);
connect(mUI->mActionAnalyzeDirectory, &QAction::triggered, this, &MainWindow::analyzeDirectory);
connect(mUI->mActionSettings, &QAction::triggered, this, &MainWindow::programSettings);
connect(mUI->mActionClearResults, &QAction::triggered, this, &MainWindow::clearResults);
connect(mUI->mActionOpenXML, &QAction::triggered, this, &MainWindow::openResults);
connect(mUI->mActionShowStyle, &QAction::toggled, this, &MainWindow::showStyle);
connect(mUI->mActionShowErrors, &QAction::toggled, this, &MainWindow::showErrors);
connect(mUI->mActionShowWarnings, &QAction::toggled, this, &MainWindow::showWarnings);
connect(mUI->mActionShowPortability, &QAction::toggled, this, &MainWindow::showPortability);
connect(mUI->mActionShowPerformance, &QAction::toggled, this, &MainWindow::showPerformance);
connect(mUI->mActionShowInformation, &QAction::toggled, this, &MainWindow::showInformation);
connect(mUI->mActionShowCppcheck, &QAction::toggled, mUI->mResults, &ResultsView::showCppcheckResults);
connect(mUI->mActionShowClang, &QAction::toggled, mUI->mResults, &ResultsView::showClangResults);
connect(mUI->mActionCheckAll, &QAction::triggered, this, &MainWindow::checkAll);
connect(mUI->mActionUncheckAll, &QAction::triggered, this, &MainWindow::uncheckAll);
connect(mUI->mActionCollapseAll, &QAction::triggered, mUI->mResults, &ResultsView::collapseAllResults);
connect(mUI->mActionExpandAll, &QAction::triggered, mUI->mResults, &ResultsView::expandAllResults);
connect(mUI->mActionShowHidden, &QAction::triggered, mUI->mResults, &ResultsView::showHiddenResults);
connect(mUI->mActionViewStats, &QAction::triggered, this, &MainWindow::showStatistics);
connect(mUI->mActionLibraryEditor, &QAction::triggered, this, &MainWindow::showLibraryEditor);
connect(mUI->mActionReanalyzeModified, &QAction::triggered, this, &MainWindow::reAnalyzeModified);
connect(mUI->mActionReanalyzeAll, &QAction::triggered, this, &MainWindow::reAnalyzeAll);
connect(mUI->mActionCheckLibrary, &QAction::triggered, this, &MainWindow::checkLibrary);
connect(mUI->mActionCheckConfiguration, &QAction::triggered, this, &MainWindow::checkConfiguration);
connect(mUI->mActionStop, &QAction::triggered, this, &MainWindow::stopAnalysis);
connect(mUI->mActionSave, &QAction::triggered, this, &MainWindow::save);
connect(mUI->mActionComplianceReport, &QAction::triggered, this, &MainWindow::complianceReport);
// About menu
connect(mUI->mActionAbout, &QAction::triggered, this, &MainWindow::about);
connect(mUI->mActionLicense, &QAction::triggered, this, &MainWindow::showLicense);
// View > Toolbar menu
connect(mUI->mActionToolBarMain, SIGNAL(toggled(bool)), this, SLOT(toggleMainToolBar()));
connect(mUI->mActionToolBarView, SIGNAL(toggled(bool)), this, SLOT(toggleViewToolBar()));
connect(mUI->mActionToolBarFilter, SIGNAL(toggled(bool)), this, SLOT(toggleFilterToolBar()));
connect(mUI->mActionAuthors, &QAction::triggered, this, &MainWindow::showAuthors);
connect(mThread, &ThreadHandler::done, this, &MainWindow::analysisDone);
connect(mThread, &ThreadHandler::log, mUI->mResults, &ResultsView::log);
connect(mThread, &ThreadHandler::debugError, mUI->mResults, &ResultsView::debugError);
connect(mUI->mResults, &ResultsView::gotResults, this, &MainWindow::resultsAdded);
connect(mUI->mResults, &ResultsView::resultsHidden, mUI->mActionShowHidden, &QAction::setEnabled);
connect(mUI->mResults, &ResultsView::checkSelected, this, &MainWindow::performSelectedFilesCheck);
connect(mUI->mResults, &ResultsView::suppressIds, this, &MainWindow::suppressIds);
connect(mUI->mMenuView, &QMenu::aboutToShow, this, &MainWindow::aboutToShowViewMenu);
// File menu
connect(mUI->mActionNewProjectFile, &QAction::triggered, this, &MainWindow::newProjectFile);
connect(mUI->mActionOpenProjectFile, &QAction::triggered, this, &MainWindow::openProjectFile);
connect(mUI->mActionShowScratchpad, &QAction::triggered, this, &MainWindow::showScratchpad);
connect(mUI->mActionCloseProjectFile, &QAction::triggered, this, &MainWindow::closeProjectFile);
connect(mUI->mActionEditProjectFile, &QAction::triggered, this, &MainWindow::editProjectFile);
connect(mUI->mActionHelpContents, &QAction::triggered, this, &MainWindow::openHelpContents);
loadSettings();
mThread->initialize(mUI->mResults);
if (mProjectFile)
formatAndSetTitle(tr("Project:") + ' ' + mProjectFile->getFilename());
else
formatAndSetTitle();
mUI->mActionComplianceReport->setVisible(isCppcheckPremium());
enableCheckButtons(true);
mUI->mActionPrint->setShortcut(QKeySequence::Print);
enableResultsButtons();
enableProjectOpenActions(true);
enableProjectActions(false);
// Must setup MRU menu before CLI param handling as it can load a
// project file and update MRU menu.
for (int i = 0; i < MaxRecentProjects; ++i) {
mRecentProjectActs[i] = new QAction(this);
mRecentProjectActs[i]->setVisible(false);
connect(mRecentProjectActs[i], SIGNAL(triggered()),
this, SLOT(openRecentProject()));
}
mRecentProjectActs[MaxRecentProjects] = nullptr; // The separator
mUI->mActionProjectMRU->setVisible(false);
updateMRUMenuItems();
QStringList args = QCoreApplication::arguments();
//Remove the application itself
args.removeFirst();
if (!args.isEmpty()) {
handleCLIParams(args);
}
mUI->mActionCloseProjectFile->setEnabled(mProjectFile != nullptr);
mUI->mActionEditProjectFile->setEnabled(mProjectFile != nullptr);
for (int i = 0; i < mPlatforms.getCount(); i++) {
PlatformData platform = mPlatforms.mPlatforms[i];
auto *action = new QAction(this);
platform.mActMainWindow = action;
mPlatforms.mPlatforms[i] = platform;
action->setText(platform.mTitle);
action->setData(platform.mType);
action->setCheckable(true);
action->setActionGroup(mPlatformActions);
mUI->mMenuAnalyze->insertAction(mUI->mActionPlatforms, action);
connect(action, SIGNAL(triggered()), this, SLOT(selectPlatform()));
}
mUI->mActionC89->setActionGroup(mCStandardActions);
mUI->mActionC99->setActionGroup(mCStandardActions);
mUI->mActionC11->setActionGroup(mCStandardActions);
mUI->mActionCpp03->setActionGroup(mCppStandardActions);
mUI->mActionCpp11->setActionGroup(mCppStandardActions);
mUI->mActionCpp14->setActionGroup(mCppStandardActions);
mUI->mActionCpp17->setActionGroup(mCppStandardActions);
mUI->mActionCpp20->setActionGroup(mCppStandardActions);
//mUI->mActionCpp23->setActionGroup(mCppStandardActions);
mUI->mActionEnforceC->setActionGroup(mSelectLanguageActions);
mUI->mActionEnforceCpp->setActionGroup(mSelectLanguageActions);
mUI->mActionAutoDetectLanguage->setActionGroup(mSelectLanguageActions);
// TODO: we no longer default to a Windows platform in CLI - so we should probably also get rid of this in the GUI
// For Windows platforms default to Win32 checked platform.
// For other platforms default to unspecified/default which means the
// platform Cppcheck GUI was compiled on.
#if defined(_WIN32)
constexpr Platform::Type defaultPlatform = Platform::Type::Win32W;
#else
constexpr Platform::Type defaultPlatform = Platform::Type::Unspecified;
#endif
PlatformData &platform = mPlatforms.get((Platform::Type)mSettings->value(SETTINGS_CHECKED_PLATFORM, defaultPlatform).toInt());
platform.mActMainWindow->setChecked(true);
mNetworkAccessManager = new QNetworkAccessManager(this);
connect(mNetworkAccessManager, &QNetworkAccessManager::finished,
this, &MainWindow::replyFinished);
mUI->mLabelInformation->setVisible(false);
mUI->mButtonHideInformation->setVisible(false);
connect(mUI->mButtonHideInformation, &QPushButton::clicked,
this, &MainWindow::hideInformation);
if (mSettings->value(SETTINGS_CHECK_FOR_UPDATES, false).toBool()) {
// Is there a new version?
if (isCppcheckPremium()) {
const QUrl url("https://files.cppchecksolutions.com/version.txt");
mNetworkAccessManager->get(QNetworkRequest(url));
} else {
const QUrl url("https://cppcheck.sourceforge.io/version.txt");
mNetworkAccessManager->get(QNetworkRequest(url));
}
} else {
delete mUI->mLayoutInformation;
}
}
MainWindow::~MainWindow()
{
delete mProjectFile;
delete mScratchPad;
delete mUI;
}
void MainWindow::handleCLIParams(const QStringList &params)
{
int index;
if (params.contains("-p")) {
index = params.indexOf("-p");
if ((index + 1) < params.length())
loadProjectFile(params[index + 1]);
} else if (params.contains("-l")) {
QString logFile;
index = params.indexOf("-l");
if ((index + 1) < params.length())
logFile = params[index + 1];
if (params.contains("-d")) {
QString checkedDir;
index = params.indexOf("-d");
if ((index + 1) < params.length())
checkedDir = params[index + 1];
loadResults(logFile, checkedDir);
} else {
loadResults(logFile);
}
} else if ((index = params.indexOf(QRegularExpression(".*\\.cppcheck$", QRegularExpression::CaseInsensitiveOption))) >= 0 && index < params.length() && QFile(params[index]).exists()) {
loadProjectFile(params[index]);
} else if ((index = params.indexOf(QRegularExpression(".*\\.xml$", QRegularExpression::CaseInsensitiveOption))) >= 0 && index < params.length() && QFile(params[index]).exists()) {
loadResults(params[index],QDir::currentPath());
} else
doAnalyzeFiles(params);
}
void MainWindow::loadSettings()
{
// Window/dialog sizes
if (mSettings->value(SETTINGS_WINDOW_MAXIMIZED, false).toBool()) {
showMaximized();
} else {
resize(mSettings->value(SETTINGS_WINDOW_WIDTH, 800).toInt(),
mSettings->value(SETTINGS_WINDOW_HEIGHT, 600).toInt());
}
const ShowTypes &types = mUI->mResults->getShowTypes();
mUI->mActionShowStyle->setChecked(types.isShown(ShowTypes::ShowStyle));
mUI->mActionShowErrors->setChecked(types.isShown(ShowTypes::ShowErrors));
mUI->mActionShowWarnings->setChecked(types.isShown(ShowTypes::ShowWarnings));
mUI->mActionShowPortability->setChecked(types.isShown(ShowTypes::ShowPortability));
mUI->mActionShowPerformance->setChecked(types.isShown(ShowTypes::ShowPerformance));
mUI->mActionShowInformation->setChecked(types.isShown(ShowTypes::ShowInformation));
mUI->mActionShowCppcheck->setChecked(true);
mUI->mActionShowClang->setChecked(true);
Standards standards;
standards.setC(mSettings->value(SETTINGS_STD_C, QString()).toString().toStdString());
mUI->mActionC89->setChecked(standards.c == Standards::C89);
mUI->mActionC99->setChecked(standards.c == Standards::C99);
mUI->mActionC11->setChecked(standards.c == Standards::C11);
standards.setCPP(mSettings->value(SETTINGS_STD_CPP, QString()).toString().toStdString());
mUI->mActionCpp03->setChecked(standards.cpp == Standards::CPP03);
mUI->mActionCpp11->setChecked(standards.cpp == Standards::CPP11);
mUI->mActionCpp14->setChecked(standards.cpp == Standards::CPP14);
mUI->mActionCpp17->setChecked(standards.cpp == Standards::CPP17);
mUI->mActionCpp20->setChecked(standards.cpp == Standards::CPP20);
//mUI->mActionCpp23->setChecked(standards.cpp == Standards::CPP23);
// Main window settings
const bool showMainToolbar = mSettings->value(SETTINGS_TOOLBARS_MAIN_SHOW, true).toBool();
mUI->mActionToolBarMain->setChecked(showMainToolbar);
mUI->mToolBarMain->setVisible(showMainToolbar);
const bool showViewToolbar = mSettings->value(SETTINGS_TOOLBARS_VIEW_SHOW, true).toBool();
mUI->mActionToolBarView->setChecked(showViewToolbar);
mUI->mToolBarView->setVisible(showViewToolbar);
const bool showFilterToolbar = mSettings->value(SETTINGS_TOOLBARS_FILTER_SHOW, true).toBool();
mUI->mActionToolBarFilter->setChecked(showFilterToolbar);
mUI->mToolBarFilter->setVisible(showFilterToolbar);
const Standards::Language enforcedLanguage = (Standards::Language)mSettings->value(SETTINGS_ENFORCED_LANGUAGE, 0).toInt();
if (enforcedLanguage == Standards::Language::CPP)
mUI->mActionEnforceCpp->setChecked(true);
else if (enforcedLanguage == Standards::Language::C)
mUI->mActionEnforceC->setChecked(true);
else
mUI->mActionAutoDetectLanguage->setChecked(true);
const bool succeeded = mApplications->loadSettings();
if (!succeeded) {
const QString msg = tr("There was a problem with loading the editor application settings.\n\n"
"This is probably because the settings were changed between the Cppcheck versions. "
"Please check (and fix) the editor application settings, otherwise the editor "
"program might not start correctly.");
QMessageBox msgBox(QMessageBox::Warning,
tr("Cppcheck"),
msg,
QMessageBox::Ok,
this);
msgBox.exec();
}
const QString projectFile = mSettings->value(SETTINGS_OPEN_PROJECT, QString()).toString();
if (!projectFile.isEmpty() && QCoreApplication::arguments().size()==1) {
QFileInfo inf(projectFile);
if (inf.exists() && inf.isReadable()) {
setPath(SETTINGS_LAST_PROJECT_PATH, projectFile);
mProjectFile = new ProjectFile(this);
mProjectFile->setActiveProject();
mProjectFile->read(projectFile);
loadLastResults();
QDir::setCurrent(inf.absolutePath());
}
}
}
void MainWindow::saveSettings() const
{
// Window/dialog sizes
mSettings->setValue(SETTINGS_WINDOW_WIDTH, size().width());
mSettings->setValue(SETTINGS_WINDOW_HEIGHT, size().height());
mSettings->setValue(SETTINGS_WINDOW_MAXIMIZED, isMaximized());
// Show * states
mSettings->setValue(SETTINGS_SHOW_STYLE, mUI->mActionShowStyle->isChecked());
mSettings->setValue(SETTINGS_SHOW_ERRORS, mUI->mActionShowErrors->isChecked());
mSettings->setValue(SETTINGS_SHOW_WARNINGS, mUI->mActionShowWarnings->isChecked());
mSettings->setValue(SETTINGS_SHOW_PORTABILITY, mUI->mActionShowPortability->isChecked());
mSettings->setValue(SETTINGS_SHOW_PERFORMANCE, mUI->mActionShowPerformance->isChecked());
mSettings->setValue(SETTINGS_SHOW_INFORMATION, mUI->mActionShowInformation->isChecked());
if (mUI->mActionC89->isChecked())
mSettings->setValue(SETTINGS_STD_C, "C89");
if (mUI->mActionC99->isChecked())
mSettings->setValue(SETTINGS_STD_C, "C99");
if (mUI->mActionC11->isChecked())
mSettings->setValue(SETTINGS_STD_C, "C11");
if (mUI->mActionCpp03->isChecked())
mSettings->setValue(SETTINGS_STD_CPP, "C++03");
if (mUI->mActionCpp11->isChecked())
mSettings->setValue(SETTINGS_STD_CPP, "C++11");
if (mUI->mActionCpp14->isChecked())
mSettings->setValue(SETTINGS_STD_CPP, "C++14");
if (mUI->mActionCpp17->isChecked())
mSettings->setValue(SETTINGS_STD_CPP, "C++17");
if (mUI->mActionCpp20->isChecked())
mSettings->setValue(SETTINGS_STD_CPP, "C++20");
//if (mUI.mActionCpp23->isChecked())
// mSettings->setValue(SETTINGS_STD_CPP, "C++23");
// Main window settings
mSettings->setValue(SETTINGS_TOOLBARS_MAIN_SHOW, mUI->mToolBarMain->isVisible());
mSettings->setValue(SETTINGS_TOOLBARS_VIEW_SHOW, mUI->mToolBarView->isVisible());
mSettings->setValue(SETTINGS_TOOLBARS_FILTER_SHOW, mUI->mToolBarFilter->isVisible());
if (mUI->mActionEnforceCpp->isChecked())
mSettings->setValue(SETTINGS_ENFORCED_LANGUAGE, Standards::Language::CPP);
else if (mUI->mActionEnforceC->isChecked())
mSettings->setValue(SETTINGS_ENFORCED_LANGUAGE, Standards::Language::C);
else
mSettings->setValue(SETTINGS_ENFORCED_LANGUAGE, Standards::Language::None);
mApplications->saveSettings();
mSettings->setValue(SETTINGS_LANGUAGE, mTranslation->getCurrentLanguage());
mSettings->setValue(SETTINGS_OPEN_PROJECT, mProjectFile ? mProjectFile->getFilename() : QString());
mUI->mResults->saveSettings(mSettings);
}
void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLibrary, const bool checkConfiguration)
{
QPair<bool,Settings> checkSettingsPair = getCppcheckSettings();
if (!checkSettingsPair.first)
return;
Settings& checkSettings = checkSettingsPair.second;
clearResults();
mIsLogfileLoaded = false;
if (mProjectFile) {
std::vector<std::string> v;
const QStringList excluded = mProjectFile->getExcludedPaths();
std::transform(excluded.cbegin(), excluded.cend(), std::back_inserter(v), [](const QString& e) {
return e.toStdString();
});
p.ignorePaths(v);
if (!mProjectFile->getAnalyzeAllVsConfigs()) {
const Platform::Type platform = (Platform::Type) mSettings->value(SETTINGS_CHECKED_PLATFORM, 0).toInt();
std::vector<std::string> configurations;
const QStringList configs = mProjectFile->getVsConfigurations();
std::transform(configs.cbegin(), configs.cend(), std::back_inserter(configurations), [](const QString& e) {
return e.toStdString();
});
p.selectVsConfigurations(platform, configurations);
}
} else {
enableProjectActions(false);
}
mUI->mResults->clear(true);
mThread->clearFiles();
mUI->mResults->checkingStarted(p.fileSettings.size());
QDir inf(mCurrentDirectory);
const QString checkPath = inf.canonicalPath();
setPath(SETTINGS_LAST_CHECK_PATH, checkPath);
checkLockDownUI(); // lock UI while checking
mUI->mResults->setCheckDirectory(checkPath);
checkSettings.force = false;
checkSettings.checkLibrary = checkLibrary;
checkSettings.checkConfiguration = checkConfiguration;
if (mProjectFile)
qDebug() << "Checking project file" << mProjectFile->getFilename();
if (!checkSettings.buildDir.empty()) {
checkSettings.loadSummaries();
std::list<std::string> sourcefiles;
AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, checkSettings.userDefines, p.fileSettings);
}
//mThread->SetanalyzeProject(true);
if (mProjectFile) {
mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools());
QString clangHeaders = mSettings->value(SETTINGS_VS_INCLUDE_PATHS).toString();
mThread->setClangIncludePaths(clangHeaders.split(";"));
mThread->setSuppressions(mProjectFile->getSuppressions());
}
mThread->setProject(p);
mThread->check(checkSettings);
mUI->mResults->setCheckSettings(checkSettings);
}
void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLibrary, const bool checkConfiguration)
{
if (files.isEmpty())
return;
QPair<bool, Settings> checkSettingsPair = getCppcheckSettings();
if (!checkSettingsPair.first)
return;
Settings& checkSettings = checkSettingsPair.second;
clearResults();
mIsLogfileLoaded = false;
FileList pathList;
pathList.addPathList(files);
if (mProjectFile) {
pathList.addExcludeList(mProjectFile->getExcludedPaths());
} else {
enableProjectActions(false);
}
QStringList fileNames = pathList.getFileList();
mUI->mResults->clear(true);
mThread->clearFiles();
if (fileNames.isEmpty()) {
QMessageBox msg(QMessageBox::Warning,
tr("Cppcheck"),
tr("No suitable files found to analyze!"),
QMessageBox::Ok,
this);
msg.exec();
return;
}
mUI->mResults->checkingStarted(fileNames.count());
mThread->setFiles(fileNames);
if (mProjectFile && !checkConfiguration)
mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools());
mThread->setSuppressions(mProjectFile ? mProjectFile->getCheckingSuppressions() : QList<SuppressionList::Suppression>());
QDir inf(mCurrentDirectory);
const QString checkPath = inf.canonicalPath();
setPath(SETTINGS_LAST_CHECK_PATH, checkPath);
checkLockDownUI(); // lock UI while checking
mUI->mResults->setCheckDirectory(checkPath);
checkSettings.checkLibrary = checkLibrary;
checkSettings.checkConfiguration = checkConfiguration;
if (mProjectFile)
qDebug() << "Checking project file" << mProjectFile->getFilename();
if (!checkSettings.buildDir.empty()) {
checkSettings.loadSummaries();
std::list<std::string> sourcefiles;
std::transform(fileNames.cbegin(), fileNames.cend(), std::back_inserter(sourcefiles), [](const QString& s) {
return s.toStdString();
});
AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, checkSettings.userDefines, {});
}
mThread->setCheckFiles(true);
mThread->check(checkSettings);
mUI->mResults->setCheckSettings(checkSettings);
}
void MainWindow::analyzeCode(const QString& code, const QString& filename)
{
const QPair<bool, Settings>& checkSettingsPair = getCppcheckSettings();
if (!checkSettingsPair.first)
return;
const Settings& checkSettings = checkSettingsPair.second;
// Initialize dummy ThreadResult as ErrorLogger
ThreadResult result;
result.setFiles(QStringList(filename));
connect(&result, SIGNAL(progress(int,QString)),
mUI->mResults, SLOT(progress(int,QString)));
connect(&result, SIGNAL(error(ErrorItem)),
mUI->mResults, SLOT(error(ErrorItem)));
connect(&result, SIGNAL(log(QString)),
mUI->mResults, SLOT(log(QString)));
connect(&result, SIGNAL(debugError(ErrorItem)),
mUI->mResults, SLOT(debugError(ErrorItem)));
// Create CppCheck instance
CppCheck cppcheck(result, true, nullptr);
cppcheck.settings() = checkSettings;
// Check
checkLockDownUI();
clearResults();
mUI->mResults->checkingStarted(1);
cppcheck.check(filename.toStdString(), code.toStdString());
analysisDone();
// Expand results
if (mUI->mResults->hasVisibleResults())
mUI->mResults->expandAllResults();
}
QStringList MainWindow::selectFilesToAnalyze(QFileDialog::FileMode mode)
{
if (mProjectFile) {
QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Cppcheck"));
const QString msg(tr("You must close the project file before selecting new files or directories!"));
msgBox.setText(msg);
msgBox.setIcon(QMessageBox::Critical);
msgBox.exec();
return QStringList();
}
QStringList selected;
// NOTE: we use QFileDialog::getOpenFileNames() and
// QFileDialog::getExistingDirectory() because they show native Windows
// selection dialog which is a lot more usable than Qt:s own dialog.
if (mode == QFileDialog::ExistingFiles) {
QMap<QString,QString> filters;
filters[tr("C/C++ Source")] = FileList::getDefaultFilters().join(" ");
filters[tr("Compile database")] = compile_commands_json;
filters[tr("Visual Studio")] = "*.sln *.vcxproj";
filters[tr("Borland C++ Builder 6")] = "*.bpr";
QString lastFilter = mSettings->value(SETTINGS_LAST_ANALYZE_FILES_FILTER).toString();
selected = QFileDialog::getOpenFileNames(this,
tr("Select files to analyze"),
getPath(SETTINGS_LAST_CHECK_PATH),
toFilterString(filters),
&lastFilter);
mSettings->setValue(SETTINGS_LAST_ANALYZE_FILES_FILTER, lastFilter);
if (selected.isEmpty())
mCurrentDirectory.clear();
else {
QFileInfo inf(selected[0]);
mCurrentDirectory = inf.absolutePath();
}
formatAndSetTitle();
} else if (mode == QFileDialog::Directory) {
QString dir = QFileDialog::getExistingDirectory(this,
tr("Select directory to analyze"),
getPath(SETTINGS_LAST_CHECK_PATH));
if (!dir.isEmpty()) {
qDebug() << "Setting current directory to: " << dir;
mCurrentDirectory = dir;
selected.append(dir);
dir = QDir::toNativeSeparators(dir);
formatAndSetTitle(dir);
}
}
if (!mCurrentDirectory.isEmpty())
setPath(SETTINGS_LAST_CHECK_PATH, mCurrentDirectory);
return selected;
}
void MainWindow::analyzeFiles()
{
Settings::terminate(false);
QStringList selected = selectFilesToAnalyze(QFileDialog::ExistingFiles);
const QString file0 = (!selected.empty() ? selected[0].toLower() : QString());
if (file0.endsWith(".sln")
|| file0.endsWith(".vcxproj")
|| file0.endsWith(compile_commands_json)
|| file0.endsWith(".bpr")) {
ImportProject p;
p.import(selected[0].toStdString());
if (file0.endsWith(".sln")) {
QStringList configs;
for (std::list<FileSettings>::const_iterator it = p.fileSettings.cbegin(); it != p.fileSettings.cend(); ++it) {
const QString cfg(QString::fromStdString(it->cfg));
if (!configs.contains(cfg))
configs.push_back(cfg);
}
configs.sort();
bool ok = false;
const QString cfg = QInputDialog::getItem(this, tr("Select configuration"), tr("Select the configuration that will be analyzed"), configs, 0, false, &ok);
if (!ok)
return;
p.ignoreOtherConfigs(cfg.toStdString());
}
doAnalyzeProject(p);
return;
}
doAnalyzeFiles(selected);
}
void MainWindow::analyzeDirectory()
{
QStringList dir = selectFilesToAnalyze(QFileDialog::Directory);
if (dir.isEmpty())
return;
QDir checkDir(dir[0]);
QStringList filters;
filters << "*.cppcheck";
checkDir.setFilter(QDir::Files | QDir::Readable);
checkDir.setNameFilters(filters);
QStringList projFiles = checkDir.entryList();
if (!projFiles.empty()) {
if (projFiles.size() == 1) {
// If one project file found, suggest loading it
QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Cppcheck"));
const QString msg(tr("Found project file: %1\n\nDo you want to "
"load this project file instead?").arg(projFiles[0]));
msgBox.setText(msg);
msgBox.setIcon(QMessageBox::Warning);
msgBox.addButton(QMessageBox::Yes);
msgBox.addButton(QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::Yes);
const int dlgResult = msgBox.exec();
if (dlgResult == QMessageBox::Yes) {
QString path = checkDir.canonicalPath();
if (!path.endsWith("/"))
path += "/";
path += projFiles[0];
loadProjectFile(path);
} else {
doAnalyzeFiles(dir);
}
} else {
// If multiple project files found inform that there are project
// files also available.
QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Cppcheck"));
const QString msg(tr("Found project files from the directory.\n\n"
"Do you want to proceed analysis without "
"using any of these project files?"));
msgBox.setText(msg);
msgBox.setIcon(QMessageBox::Warning);
msgBox.addButton(QMessageBox::Yes);
msgBox.addButton(QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::Yes);
const int dlgResult = msgBox.exec();
if (dlgResult == QMessageBox::Yes) {
doAnalyzeFiles(dir);
}
}
} else {
doAnalyzeFiles(dir);
}
}
void MainWindow::addIncludeDirs(const QStringList &includeDirs, Settings &result)
{
for (const QString& dir : includeDirs) {
QString incdir;
if (!QDir::isAbsolutePath(dir))
incdir = mCurrentDirectory + "/";
incdir += dir;
incdir = QDir::cleanPath(incdir);
// include paths must end with '/'
if (!incdir.endsWith("/"))
incdir += "/";
result.includePaths.push_back(incdir.toStdString());
}
}
Library::Error MainWindow::loadLibrary(Library &library, const QString &filename)
{
Library::Error ret;
// Try to load the library from the project folder..
if (mProjectFile) {
QString path = QFileInfo(mProjectFile->getFilename()).canonicalPath();
ret = library.load(nullptr, (path+"/"+filename).toLatin1());
if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND)
return ret;
}
// Try to load the library from the application folder..
const QString appPath = QFileInfo(QCoreApplication::applicationFilePath()).canonicalPath();
ret = library.load(nullptr, (appPath+"/"+filename).toLatin1());
if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND)
return ret;
ret = library.load(nullptr, (appPath+"/cfg/"+filename).toLatin1());
if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND)
return ret;
#ifdef FILESDIR
// Try to load the library from FILESDIR/cfg..
const QString filesdir = FILESDIR;
if (!filesdir.isEmpty()) {
ret = library.load(nullptr, (filesdir+"/cfg/"+filename).toLatin1());
if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND)
return ret;
ret = library.load(nullptr, (filesdir+filename).toLatin1());
if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND)
return ret;
}
#endif
// Try to load the library from the cfg subfolder..
const QString datadir = getDataDir();
if (!datadir.isEmpty()) {
ret = library.load(nullptr, (datadir+"/"+filename).toLatin1());
if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND)
return ret;
ret = library.load(nullptr, (datadir+"/cfg/"+filename).toLatin1());
if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND)
return ret;
}
return ret;
}
bool MainWindow::tryLoadLibrary(Library &library, const QString& filename)
{
const Library::Error error = loadLibrary(library, filename);
if (error.errorcode != Library::ErrorCode::OK) {
if (error.errorcode == Library::ErrorCode::UNKNOWN_ELEMENT) {
QMessageBox::information(this, tr("Information"), tr("The library '%1' contains unknown elements:\n%2").arg(filename).arg(error.reason.c_str()));
return true;
}
QString errmsg;
switch (error.errorcode) {
case Library::ErrorCode::OK:
break;
case Library::ErrorCode::FILE_NOT_FOUND:
errmsg = tr("File not found");
break;
case Library::ErrorCode::BAD_XML:
errmsg = tr("Bad XML");
break;
case Library::ErrorCode::MISSING_ATTRIBUTE:
errmsg = tr("Missing attribute");
break;
case Library::ErrorCode::BAD_ATTRIBUTE_VALUE:
errmsg = tr("Bad attribute value");
break;
case Library::ErrorCode::UNSUPPORTED_FORMAT:
errmsg = tr("Unsupported format");
break;
case Library::ErrorCode::DUPLICATE_PLATFORM_TYPE:
errmsg = tr("Duplicate platform type");
break;
case Library::ErrorCode::PLATFORM_TYPE_REDEFINED:
errmsg = tr("Platform type redefined");
break;
case Library::ErrorCode::DUPLICATE_DEFINE:
errmsg = tr("Duplicate define");
break;
case Library::ErrorCode::UNKNOWN_ELEMENT:
errmsg = tr("Unknown element");
break;
default:
errmsg = tr("Unknown issue");
break;
}
if (!error.reason.empty())
errmsg += " '" + QString::fromStdString(error.reason) + "'";
QMessageBox::information(this, tr("Information"), tr("Failed to load the selected library '%1'.\n%2").arg(filename).arg(errmsg));
return false;
}
return true;
}
QString MainWindow::loadAddon(Settings &settings, const QString &filesDir, const QString &pythonCmd, const QString& addon)
{
const QString addonFilePath = fromNativePath(ProjectFile::getAddonFilePath(filesDir, addon));
if (addonFilePath.isEmpty())
return tr("File not found: '%1'").arg(addon);
picojson::object obj;
obj["script"] = picojson::value(addonFilePath.toStdString());
if (!pythonCmd.isEmpty())
obj["python"] = picojson::value(pythonCmd.toStdString());
if (!isCppcheckPremium() && addon == "misra") {
const QString misraFile = fromNativePath(mSettings->value(SETTINGS_MISRA_FILE).toString());
if (!misraFile.isEmpty()) {
QString arg;
if (misraFile.endsWith(".pdf", Qt::CaseInsensitive))
arg = "--misra-pdf=" + misraFile;
else
arg = "--rule-texts=" + misraFile;
obj["args"] = picojson::value(arg.toStdString());
}
}
const std::string& json_str = picojson::value(obj).serialize();
AddonInfo addonInfo;
const std::string errmsg = addonInfo.getAddonInfo(json_str, settings.exename);
if (!errmsg.empty())
return tr("Failed to load/setup addon %1: %2").arg(addon, QString::fromStdString(errmsg));
settings.addonInfos.emplace_back(std::move(addonInfo));
settings.addons.emplace(json_str);
return "";
}
QPair<bool,Settings> MainWindow::getCppcheckSettings()
{
saveSettings(); // Save settings
Settings::terminate(true);
Settings result;
result.exename = QCoreApplication::applicationFilePath().toStdString();
// default to --check-level=normal for GUI for now
result.setCheckLevel(Settings::CheckLevel::normal);
const bool std = tryLoadLibrary(result.library, "std.cfg");
if (!std) {
QMessageBox::critical(this, tr("Error"), tr("Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured.\n\nAnalysis is aborted.").arg("std.cfg"));
return {false, {}};
}
const QString filesDir(getDataDir());
const QString pythonCmd = fromNativePath(mSettings->value(SETTINGS_PYTHON_PATH).toString());
{
const QString cfgErr = QString::fromStdString(Settings::loadCppcheckCfg(result, result.supprs));
if (!cfgErr.isEmpty()) {
QMessageBox::critical(this, tr("Error"), tr("Failed to load %1 - %2\n\nAnalysis is aborted.").arg("cppcheck.cfg").arg(cfgErr));
return {false, {}};
}
const auto cfgAddons = result.addons;
result.addons.clear();
for (const std::string& addon : cfgAddons) {
// TODO: support addons which are a script and not a file
const QString addonError = loadAddon(result, filesDir, pythonCmd, QString::fromStdString(addon));
if (!addonError.isEmpty()) {
QMessageBox::critical(this, tr("Error"), tr("%1\n\nAnalysis is aborted.").arg(addonError));
return {false, {}};
}
}
}
// If project file loaded, read settings from it
if (mProjectFile) {
QStringList dirs = mProjectFile->getIncludeDirs();
addIncludeDirs(dirs, result);
const QStringList defines = mProjectFile->getDefines();
for (const QString& define : defines) {
if (!result.userDefines.empty())
result.userDefines += ";";
result.userDefines += define.toStdString();
}
result.clang = mProjectFile->clangParser;
const QStringList undefines = mProjectFile->getUndefines();
for (const QString& undefine : undefines)
result.userUndefs.insert(undefine.toStdString());
const QStringList libraries = mProjectFile->getLibraries();
for (const QString& library : libraries) {
result.libraries.emplace_back(library.toStdString());
const QString filename = library + ".cfg";
tryLoadLibrary(result.library, filename);
}
for (const SuppressionList::Suppression &suppression : mProjectFile->getCheckingSuppressions()) {
result.supprs.nomsg.addSuppression(suppression);
}
// Only check the given -D configuration
if (!defines.isEmpty())
result.maxConfigs = 1;
// If importing a project, only check the given configuration
if (!mProjectFile->getImportProject().isEmpty())
result.checkAllConfigurations = false;
const QString &buildDir = fromNativePath(mProjectFile->getBuildDir());
if (!buildDir.isEmpty()) {
if (QDir(buildDir).isAbsolute()) {
result.buildDir = buildDir.toStdString();
} else {
QString prjpath = QFileInfo(mProjectFile->getFilename()).absolutePath();
result.buildDir = (prjpath + '/' + buildDir).toStdString();
}
}
const QString platform = mProjectFile->getPlatform();
if (platform.endsWith(".xml")) {
const QString applicationFilePath = QCoreApplication::applicationFilePath();
result.platform.loadFromFile(applicationFilePath.toStdString().c_str(), platform.toStdString());
} else {
for (int i = Platform::Type::Native; i <= Platform::Type::Unix64; i++) {
const auto p = (Platform::Type)i;
if (platform == Platform::toString(p)) {
result.platform.set(p);
break;
}
}
}
result.maxCtuDepth = mProjectFile->getMaxCtuDepth();
result.maxTemplateRecursion = mProjectFile->getMaxTemplateRecursion();
if (mProjectFile->isCheckLevelExhaustive())
result.setCheckLevel(Settings::CheckLevel::exhaustive);
else
result.setCheckLevel(Settings::CheckLevel::normal);
result.checkHeaders = mProjectFile->getCheckHeaders();
result.checkUnusedTemplates = mProjectFile->getCheckUnusedTemplates();
result.safeChecks.classes = mProjectFile->safeChecks.classes;
result.safeChecks.externalFunctions = mProjectFile->safeChecks.externalFunctions;
result.safeChecks.internalFunctions = mProjectFile->safeChecks.internalFunctions;
result.safeChecks.externalVariables = mProjectFile->safeChecks.externalVariables;
for (const QString& s : mProjectFile->getCheckUnknownFunctionReturn())
result.checkUnknownFunctionReturn.insert(s.toStdString());
for (const QString& addon : mProjectFile->getAddons()) {
const QString addonError = loadAddon(result, filesDir, pythonCmd, addon);
if (!addonError.isEmpty()) {
QMessageBox::critical(this, tr("Error"), tr("%1\n\nAnalysis is aborted.").arg(addonError));
return {false, {}};
}
}
if (isCppcheckPremium()) {
QString premiumArgs;
if (mProjectFile->getBughunting())
premiumArgs += " --bughunting";
if (mProjectFile->getCertIntPrecision() > 0)
premiumArgs += " --cert-c-int-precision=" + QString::number(mProjectFile->getCertIntPrecision());
for (const QString& c: mProjectFile->getCodingStandards())
premiumArgs += " --" + c;
if (!premiumArgs.contains("misra") && mProjectFile->getAddons().contains("misra"))
premiumArgs += " --misra-c-2012";
result.premiumArgs = premiumArgs.mid(1).toStdString();
result.setMisraRuleTexts(CheckThread::executeCommand);
}
}
// Include directories (and files) are searched in listed order.
// Global include directories must be added AFTER the per project include
// directories so per project include directories can override global ones.
const QString globalIncludes = mSettings->value(SETTINGS_GLOBAL_INCLUDE_PATHS).toString();
if (!globalIncludes.isEmpty()) {
QStringList includes = globalIncludes.split(";");
addIncludeDirs(includes, result);
}
result.severity.enable(Severity::warning);
result.severity.enable(Severity::style);
result.severity.enable(Severity::performance);
result.severity.enable(Severity::portability);
result.severity.enable(Severity::information);
result.checks.enable(Checks::missingInclude);
if (!result.buildDir.empty())
result.checks.enable(Checks::unusedFunction);
result.debugwarnings = mSettings->value(SETTINGS_SHOW_DEBUG_WARNINGS, false).toBool();
result.quiet = false;
result.verbose = true;
result.force = mSettings->value(SETTINGS_CHECK_FORCE, 1).toBool();
result.xml = false;
result.jobs = mSettings->value(SETTINGS_CHECK_THREADS, 1).toInt();
result.inlineSuppressions = mSettings->value(SETTINGS_INLINE_SUPPRESSIONS, false).toBool();
result.certainty.setEnabled(Certainty::inconclusive, mSettings->value(SETTINGS_INCONCLUSIVE_ERRORS, false).toBool());
if (!mProjectFile || result.platform.type == Platform::Type::Unspecified)
result.platform.set((Platform::Type) mSettings->value(SETTINGS_CHECKED_PLATFORM, 0).toInt());
result.standards.setCPP(mSettings->value(SETTINGS_STD_CPP, QString()).toString().toStdString());
result.standards.setC(mSettings->value(SETTINGS_STD_C, QString()).toString().toStdString());
result.enforcedLang = (Standards::Language)mSettings->value(SETTINGS_ENFORCED_LANGUAGE, 0).toInt();
if (result.jobs <= 1) {
result.jobs = 1;
}
Settings::terminate(false);
return {true, std::move(result)};
}
void MainWindow::analysisDone()
{
if (mExiting) {
close();
return;
}
mUI->mResults->checkingFinished();
enableCheckButtons(true);
mUI->mActionSettings->setEnabled(true);
mUI->mActionOpenXML->setEnabled(true);
if (mProjectFile) {
enableProjectActions(true);
} else if (mIsLogfileLoaded) {
mUI->mActionReanalyzeModified->setEnabled(false);
mUI->mActionReanalyzeAll->setEnabled(false);
}
enableProjectOpenActions(true);
mPlatformActions->setEnabled(true);
mCStandardActions->setEnabled(true);
mCppStandardActions->setEnabled(true);
mSelectLanguageActions->setEnabled(true);
mUI->mActionPosix->setEnabled(true);
if (mScratchPad)
mScratchPad->setEnabled(true);
mUI->mActionViewStats->setEnabled(true);
if (mProjectFile && !mProjectFile->getBuildDir().isEmpty()) {
const QString prjpath = QFileInfo(mProjectFile->getFilename()).absolutePath();
const QString buildDir = prjpath + '/' + mProjectFile->getBuildDir();
if (QDir(buildDir).exists()) {
mUI->mResults->saveStatistics(buildDir + "/statistics.txt");
mUI->mResults->updateFromOldReport(buildDir + "/lastResults.xml");
mUI->mResults->save(buildDir + "/lastResults.xml", Report::XMLV2, mCppcheckCfgProductName);
}
}
enableResultsButtons();
for (QAction* recentProjectAct : mRecentProjectActs) {
if (recentProjectAct != nullptr)
recentProjectAct->setEnabled(true);
}
// Notify user - if the window is not active - that check is ready
QApplication::alert(this, 3000);
if (mSettings->value(SETTINGS_SHOW_STATISTICS, false).toBool())
showStatistics();
}
void MainWindow::checkLockDownUI()
{
enableCheckButtons(false);
mUI->mActionSettings->setEnabled(false);
mUI->mActionOpenXML->setEnabled(false);
enableProjectActions(false);
enableProjectOpenActions(false);
mPlatformActions->setEnabled(false);
mCStandardActions->setEnabled(false);
mCppStandardActions->setEnabled(false);
mSelectLanguageActions->setEnabled(false);
mUI->mActionPosix->setEnabled(false);
if (mScratchPad)
mScratchPad->setEnabled(false);
for (QAction* recentProjectAct : mRecentProjectActs) {
if (recentProjectAct != nullptr)
recentProjectAct->setEnabled(false);
}
}
void MainWindow::programSettings()
{
SettingsDialog dialog(mApplications, mTranslation, isCppcheckPremium(), this);
if (dialog.exec() == QDialog::Accepted) {
dialog.saveSettingValues();
mSettings->sync();
mUI->mResults->updateSettings(dialog.showFullPath(),
dialog.saveFullPath(),
dialog.saveAllErrors(),
dialog.showNoErrorsMessage(),
dialog.showErrorId(),
dialog.showInconclusive());
mUI->mResults->updateStyleSetting(mSettings);
const QString newLang = mSettings->value(SETTINGS_LANGUAGE, "en").toString();
setLanguage(newLang);
}
}
void MainWindow::reAnalyzeModified()
{
reAnalyze(false);
}
void MainWindow::reAnalyzeAll()
{
if (mProjectFile)
analyzeProject(mProjectFile);
else
reAnalyze(true);
}
void MainWindow::checkLibrary()
{
if (mProjectFile)
analyzeProject(mProjectFile, true);
}
void MainWindow::checkConfiguration()
{
if (mProjectFile)
analyzeProject(mProjectFile, false, true);
}
void MainWindow::reAnalyzeSelected(const QStringList& files)
{
if (files.empty())
return;
if (mThread->isChecking())
return;
const QPair<bool, Settings> checkSettingsPair = getCppcheckSettings();
if (!checkSettingsPair.first)
return;
const Settings& checkSettings = checkSettingsPair.second;
// Clear details, statistics and progress
mUI->mResults->clear(false);
for (int i = 0; i < files.size(); ++i)
mUI->mResults->clearRecheckFile(files[i]);
mCurrentDirectory = mUI->mResults->getCheckDirectory();
FileList pathList;
pathList.addPathList(files);
if (mProjectFile)
pathList.addExcludeList(mProjectFile->getExcludedPaths());
QStringList fileNames = pathList.getFileList();
checkLockDownUI(); // lock UI while checking
mUI->mResults->checkingStarted(fileNames.size());
mThread->setCheckFiles(fileNames);
// Saving last check start time, otherwise unchecked modified files will not be
// considered in "Modified Files Check" performed after "Selected Files Check"
// TODO: Should we store per file CheckStartTime?
QDateTime saveCheckStartTime = mThread->getCheckStartTime();
mThread->check(checkSettings);
mUI->mResults->setCheckSettings(checkSettings);
mThread->setCheckStartTime(std::move(saveCheckStartTime));
}
void MainWindow::reAnalyze(bool all)
{
const QStringList files = mThread->getReCheckFiles(all);
if (files.empty())
return;
const QPair<bool, Settings>& checkSettingsPair = getCppcheckSettings();
if (!checkSettingsPair.first)
return;
const Settings& checkSettings = checkSettingsPair.second;
// Clear details, statistics and progress
mUI->mResults->clear(all);
// Clear results for changed files
for (int i = 0; i < files.size(); ++i)
mUI->mResults->clear(files[i]);
checkLockDownUI(); // lock UI while checking
mUI->mResults->checkingStarted(files.size());
if (mProjectFile)
qDebug() << "Rechecking project file" << mProjectFile->getFilename();
mThread->setCheckFiles(all);
mThread->check(checkSettings);
mUI->mResults->setCheckSettings(checkSettings);
}
void MainWindow::clearResults()
{
if (mProjectFile && !mProjectFile->getBuildDir().isEmpty()) {
QDir dir(QFileInfo(mProjectFile->getFilename()).absolutePath() + '/' + mProjectFile->getBuildDir());
for (const QString& f: dir.entryList(QDir::Files)) {
if (!f.endsWith("files.txt")) {
static const QRegularExpression rx("^.*.s[0-9]+$");
if (!rx.match(f).hasMatch())
dir.remove(f);
}
}
}
mUI->mResults->clear(true);
Q_ASSERT(false == mUI->mResults->hasResults());
enableResultsButtons();
}
void MainWindow::openResults()
{
if (mUI->mResults->hasResults()) {
QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Cppcheck"));
const QString msg(tr("Current results will be cleared.\n\n"
"Opening a new XML file will clear current results.\n"
"Do you want to proceed?"));
msgBox.setText(msg);
msgBox.setIcon(QMessageBox::Warning);
msgBox.addButton(QMessageBox::Yes);
msgBox.addButton(QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::Yes);
const int dlgResult = msgBox.exec();
if (dlgResult == QMessageBox::No) {
return;
}
}
QString selectedFilter;
const QString filter(tr("XML files (*.xml)"));
QString selectedFile = QFileDialog::getOpenFileName(this,
tr("Open the report file"),
getPath(SETTINGS_LAST_RESULT_PATH),
filter,
&selectedFilter);
if (!selectedFile.isEmpty()) {
loadResults(selectedFile);
}
}
void MainWindow::loadResults(const QString &selectedFile)
{
if (selectedFile.isEmpty())
return;
if (mProjectFile)
closeProjectFile();
mIsLogfileLoaded = true;
mUI->mResults->clear(true);
mUI->mActionReanalyzeModified->setEnabled(false);
mUI->mActionReanalyzeAll->setEnabled(false);
mUI->mResults->readErrorsXml(selectedFile);
setPath(SETTINGS_LAST_RESULT_PATH, selectedFile);
formatAndSetTitle(selectedFile);
}
void MainWindow::loadResults(const QString &selectedFile, const QString &sourceDirectory)
{
loadResults(selectedFile);
mUI->mResults->setCheckDirectory(sourceDirectory);
}
void MainWindow::enableCheckButtons(bool enable)
{
mUI->mActionStop->setEnabled(!enable);
mUI->mActionAnalyzeFiles->setEnabled(enable);
if (mProjectFile) {
mUI->mActionReanalyzeModified->setEnabled(false);
mUI->mActionReanalyzeAll->setEnabled(enable);
} else if (!enable || mThread->hasPreviousFiles()) {
mUI->mActionReanalyzeModified->setEnabled(enable);
mUI->mActionReanalyzeAll->setEnabled(enable);
}
mUI->mActionAnalyzeDirectory->setEnabled(enable);
if (isCppcheckPremium()) {
mUI->mActionComplianceReport->setEnabled(enable && mProjectFile && (mProjectFile->getAddons().contains("misra") || !mProjectFile->getCodingStandards().empty()));
}
}
void MainWindow::enableResultsButtons()
{
const bool enabled = mUI->mResults->hasResults();
mUI->mActionClearResults->setEnabled(enabled);
mUI->mActionSave->setEnabled(enabled);
mUI->mActionPrint->setEnabled(enabled);
mUI->mActionPrintPreview->setEnabled(enabled);
}
void MainWindow::showStyle(bool checked)
{
mUI->mResults->showResults(ShowTypes::ShowStyle, checked);
}
void MainWindow::showErrors(bool checked)
{
mUI->mResults->showResults(ShowTypes::ShowErrors, checked);
}
void MainWindow::showWarnings(bool checked)
{
mUI->mResults->showResults(ShowTypes::ShowWarnings, checked);
}
void MainWindow::showPortability(bool checked)
{
mUI->mResults->showResults(ShowTypes::ShowPortability, checked);
}
void MainWindow::showPerformance(bool checked)
{
mUI->mResults->showResults(ShowTypes::ShowPerformance, checked);
}
void MainWindow::showInformation(bool checked)
{
mUI->mResults->showResults(ShowTypes::ShowInformation, checked);
}
void MainWindow::checkAll()
{
toggleAllChecked(true);
}
void MainWindow::uncheckAll()
{
toggleAllChecked(false);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
// Check that we aren't checking files
if (!mThread->isChecking()) {
saveSettings();
event->accept();
} else {
const QString text(tr("Analyzer is running.\n\n" \
"Do you want to stop the analysis and exit Cppcheck?"));
QMessageBox msg(QMessageBox::Warning,
tr("Cppcheck"),
text,
QMessageBox::Yes | QMessageBox::No,
this);
msg.setDefaultButton(QMessageBox::No);
const int rv = msg.exec();
if (rv == QMessageBox::Yes) {
// This isn't really very clean way to close threads but since the app is
// exiting it doesn't matter.
mThread->stop();
saveSettings();
mExiting = true;
}
event->ignore();
}
}
void MainWindow::toggleAllChecked(bool checked)
{
mUI->mActionShowStyle->setChecked(checked);
showStyle(checked);
mUI->mActionShowErrors->setChecked(checked);
showErrors(checked);
mUI->mActionShowWarnings->setChecked(checked);
showWarnings(checked);
mUI->mActionShowPortability->setChecked(checked);
showPortability(checked);
mUI->mActionShowPerformance->setChecked(checked);
showPerformance(checked);
mUI->mActionShowInformation->setChecked(checked);
showInformation(checked);
}
void MainWindow::about()
{
if (!mCppcheckCfgAbout.isEmpty()) {
QMessageBox msg(QMessageBox::Information,
tr("About"),
mCppcheckCfgAbout,
QMessageBox::Ok,
this);
msg.exec();
}
else {
auto *dlg = new AboutDialog(CppCheck::version(), CppCheck::extraVersion(), this);
dlg->exec();
}
}
void MainWindow::showLicense()
{
auto *dlg = new FileViewDialog(":COPYING", tr("License"), this);
dlg->resize(570, 400);
dlg->exec();
}
void MainWindow::showAuthors()
{
auto *dlg = new FileViewDialog(":AUTHORS", tr("Authors"), this);
dlg->resize(350, 400);
dlg->exec();
}
void MainWindow::performSelectedFilesCheck(const QStringList &selectedFilesList)
{
reAnalyzeSelected(selectedFilesList);
}
void MainWindow::save()
{
QString selectedFilter;
const QString filter(tr("XML files (*.xml);;Text files (*.txt);;CSV files (*.csv)"));
QString selectedFile = QFileDialog::getSaveFileName(this,
tr("Save the report file"),
getPath(SETTINGS_LAST_RESULT_PATH),
filter,
&selectedFilter);
if (!selectedFile.isEmpty()) {
Report::Type type = Report::TXT;
if (selectedFilter == tr("XML files (*.xml)")) {
type = Report::XMLV2;
if (!selectedFile.endsWith(".xml", Qt::CaseInsensitive))
selectedFile += ".xml";
} else if (selectedFilter == tr("Text files (*.txt)")) {
type = Report::TXT;
if (!selectedFile.endsWith(".txt", Qt::CaseInsensitive))
selectedFile += ".txt";
} else if (selectedFilter == tr("CSV files (*.csv)")) {
type = Report::CSV;
if (!selectedFile.endsWith(".csv", Qt::CaseInsensitive))
selectedFile += ".csv";
} else {
if (selectedFile.endsWith(".xml", Qt::CaseInsensitive))
type = Report::XMLV2;
else if (selectedFile.endsWith(".txt", Qt::CaseInsensitive))
type = Report::TXT;
else if (selectedFile.endsWith(".csv", Qt::CaseInsensitive))
type = Report::CSV;
}
mUI->mResults->save(selectedFile, type, mCppcheckCfgProductName);
setPath(SETTINGS_LAST_RESULT_PATH, selectedFile);
}
}
void MainWindow::complianceReport()
{
if (!mUI->mResults->isSuccess()) {
QMessageBox m(QMessageBox::Critical,
"Cppcheck",
tr("Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors."),
QMessageBox::Ok,
this);
m.exec();
return;
}
QTemporaryFile tempResults;
tempResults.open();
tempResults.close();
mUI->mResults->save(tempResults.fileName(), Report::XMLV2, mCppcheckCfgProductName);
ComplianceReportDialog dlg(mProjectFile, tempResults.fileName());
dlg.exec();
}
void MainWindow::resultsAdded()
{}
void MainWindow::toggleMainToolBar()
{
mUI->mToolBarMain->setVisible(mUI->mActionToolBarMain->isChecked());
}
void MainWindow::toggleViewToolBar()
{
mUI->mToolBarView->setVisible(mUI->mActionToolBarView->isChecked());
}
void MainWindow::toggleFilterToolBar()
{
mUI->mToolBarFilter->setVisible(mUI->mActionToolBarFilter->isChecked());
mLineEditFilter->clear(); // Clearing the filter also disables filtering
}
void MainWindow::formatAndSetTitle(const QString &text)
{
QString nameWithVersion = QString("Cppcheck %1").arg(CppCheck::version());
QString extraVersion = CppCheck::extraVersion();
if (!extraVersion.isEmpty()) {
nameWithVersion += " (" + extraVersion + ")";
}
if (!mCppcheckCfgProductName.isEmpty())
nameWithVersion = mCppcheckCfgProductName;
QString title;
if (text.isEmpty())
title = nameWithVersion;
else
title = QString("%1 - %2").arg(nameWithVersion, text);
setWindowTitle(title);
}
void MainWindow::setLanguage(const QString &code)
{
const QString currentLang = mTranslation->getCurrentLanguage();
if (currentLang == code)
return;
if (mTranslation->setLanguage(code)) {
//Translate everything that is visible here
mUI->retranslateUi(this);
mUI->mResults->translate();
mLineEditFilter->setPlaceholderText(QCoreApplication::translate("MainWindow", "Quick Filter:"));
if (mProjectFile)
formatAndSetTitle(tr("Project:") + ' ' + mProjectFile->getFilename());
if (mScratchPad)
mScratchPad->translate();
}
}
void MainWindow::aboutToShowViewMenu()
{
mUI->mActionToolBarMain->setChecked(mUI->mToolBarMain->isVisible());
mUI->mActionToolBarView->setChecked(mUI->mToolBarView->isVisible());
mUI->mActionToolBarFilter->setChecked(mUI->mToolBarFilter->isVisible());
}
void MainWindow::stopAnalysis()
{
mThread->stop();
mUI->mResults->stopAnalysis();
mUI->mResults->disableProgressbar();
const QString &lastResults = getLastResults();
if (!lastResults.isEmpty()) {
mUI->mResults->updateFromOldReport(lastResults);
}
}
void MainWindow::openHelpContents()
{
openOnlineHelp();
}
void MainWindow::openOnlineHelp()
{
auto *helpDialog = new HelpDialog;
helpDialog->showMaximized();
}
void MainWindow::openProjectFile()
{
const QString filter = tr("Project files (*.cppcheck);;All files(*.*)");
const QString filepath = QFileDialog::getOpenFileName(this,
tr("Select Project File"),
getPath(SETTINGS_LAST_PROJECT_PATH),
filter);
if (!filepath.isEmpty()) {
const QFileInfo fi(filepath);
if (fi.exists() && fi.isFile() && fi.isReadable()) {
setPath(SETTINGS_LAST_PROJECT_PATH, filepath);
loadProjectFile(filepath);
}
}
}
void MainWindow::showScratchpad()
{
if (!mScratchPad)
mScratchPad = new ScratchPad(*this);
mScratchPad->show();
if (!mScratchPad->isActiveWindow())
mScratchPad->activateWindow();
}
void MainWindow::loadProjectFile(const QString &filePath)
{
QFileInfo inf(filePath);
const QString filename = inf.fileName();
formatAndSetTitle(tr("Project:") + ' ' + filename);
addProjectMRU(filePath);
mIsLogfileLoaded = false;
mUI->mActionCloseProjectFile->setEnabled(true);
mUI->mActionEditProjectFile->setEnabled(true);
delete mProjectFile;
mProjectFile = new ProjectFile(filePath, this);
mProjectFile->setActiveProject();
if (!loadLastResults())
analyzeProject(mProjectFile);
}
QString MainWindow::getLastResults() const
{
if (!mProjectFile || mProjectFile->getBuildDir().isEmpty())
return QString();
return QFileInfo(mProjectFile->getFilename()).absolutePath() + '/' + mProjectFile->getBuildDir() + "/lastResults.xml";
}
bool MainWindow::loadLastResults()
{
const QString &lastResults = getLastResults();
if (lastResults.isEmpty())
return false;
if (!QFileInfo::exists(lastResults))
return false;
mUI->mResults->readErrorsXml(lastResults);
mUI->mResults->setCheckDirectory(mSettings->value(SETTINGS_LAST_CHECK_PATH,QString()).toString());
mUI->mActionViewStats->setEnabled(true);
enableResultsButtons();
return true;
}
void MainWindow::analyzeProject(const ProjectFile *projectFile, const bool checkLibrary, const bool checkConfiguration)
{
Settings::terminate(false);
QFileInfo inf(projectFile->getFilename());
const QString& rootpath = projectFile->getRootPath();
QDir::setCurrent(inf.absolutePath());
mThread->setAddonsAndTools(projectFile->getAddonsAndTools());
// If the root path is not given or is not "current dir", use project
// file's location directory as root path
if (rootpath.isEmpty() || rootpath == ".")
mCurrentDirectory = inf.canonicalPath();
else if (rootpath.startsWith("./"))
mCurrentDirectory = inf.canonicalPath() + rootpath.mid(1);
else
mCurrentDirectory = rootpath;
if (!projectFile->getBuildDir().isEmpty()) {
QString buildDir = projectFile->getBuildDir();
if (!QDir::isAbsolutePath(buildDir))
buildDir = inf.canonicalPath() + '/' + buildDir;
if (!QDir(buildDir).exists()) {
QMessageBox msg(QMessageBox::Question,
tr("Cppcheck"),
tr("Build dir '%1' does not exist, create it?").arg(buildDir),
QMessageBox::Yes | QMessageBox::No,
this);
if (msg.exec() == QMessageBox::Yes) {
QDir().mkpath(buildDir);
} else if (!projectFile->getAddons().isEmpty()) {
QMessageBox m(QMessageBox::Critical,
tr("Cppcheck"),
tr("To check the project using addons, you need a build directory."),
QMessageBox::Ok,
this);
m.exec();
return;
}
}
}
if (!projectFile->getImportProject().isEmpty()) {
ImportProject p;
QString prjfile;
if (QFileInfo(projectFile->getImportProject()).isAbsolute()) {
prjfile = projectFile->getImportProject();
} else {
prjfile = inf.canonicalPath() + '/' + projectFile->getImportProject();
}
try {
const ImportProject::Type result = p.import(prjfile.toStdString());
QString errorMessage;
switch (result) {
case ImportProject::Type::COMPILE_DB:
case ImportProject::Type::VS_SLN:
case ImportProject::Type::VS_VCXPROJ:
case ImportProject::Type::BORLAND:
case ImportProject::Type::CPPCHECK_GUI:
// Loading was successful
break;
case ImportProject::Type::MISSING:
errorMessage = tr("Failed to open file");
break;
case ImportProject::Type::UNKNOWN:
errorMessage = tr("Unknown project file format");
break;
case ImportProject::Type::FAILURE:
errorMessage = tr("Failed to import project file");
break;
case ImportProject::Type::NONE:
// can never happen
break;
}
if (!errorMessage.isEmpty()) {
QMessageBox msg(QMessageBox::Critical,
tr("Cppcheck"),
tr("Failed to import '%1': %2\n\nAnalysis is stopped.").arg(prjfile).arg(errorMessage),
QMessageBox::Ok,
this);
msg.exec();
return;
}
} catch (InternalError &e) {
QMessageBox msg(QMessageBox::Critical,
tr("Cppcheck"),
tr("Failed to import '%1' (%2), analysis is stopped").arg(prjfile).arg(QString::fromStdString(e.errorMessage)),
QMessageBox::Ok,
this);
msg.exec();
return;
}
doAnalyzeProject(p, checkLibrary, checkConfiguration);
return;
}
QStringList paths = projectFile->getCheckPaths();
// If paths not given then check the root path (which may be the project
// file's location, see above). This is to keep the compatibility with
// old "silent" project file loading when we checked the director where the
// project file was located.
if (paths.isEmpty()) {
paths << mCurrentDirectory;
}
doAnalyzeFiles(paths, checkLibrary, checkConfiguration);
}
void MainWindow::newProjectFile()
{
const QString filter = tr("Project files (*.cppcheck)");
QString filepath = QFileDialog::getSaveFileName(this,
tr("Select Project Filename"),
getPath(SETTINGS_LAST_PROJECT_PATH),
filter);
if (filepath.isEmpty())
return;
if (!filepath.endsWith(".cppcheck", Qt::CaseInsensitive))
filepath += ".cppcheck";
setPath(SETTINGS_LAST_PROJECT_PATH, filepath);
QFileInfo inf(filepath);
const QString filename = inf.fileName();
formatAndSetTitle(tr("Project:") + QString(" ") + filename);
delete mProjectFile;
mProjectFile = new ProjectFile(this);
mProjectFile->setActiveProject();
mProjectFile->setFilename(filepath);
mProjectFile->setProjectName(filename.left(filename.indexOf(".")));
mProjectFile->setBuildDir(filename.left(filename.indexOf(".")) + "-cppcheck-build-dir");
ProjectFileDialog dlg(mProjectFile, isCppcheckPremium(), this);
if (dlg.exec() == QDialog::Accepted) {
addProjectMRU(filepath);
analyzeProject(mProjectFile);
} else {
closeProjectFile();
}
}
void MainWindow::closeProjectFile()
{
delete mProjectFile;
mProjectFile = nullptr;
mUI->mResults->clear(true);
enableProjectActions(false);
enableProjectOpenActions(true);
formatAndSetTitle();
}
void MainWindow::editProjectFile()
{
if (!mProjectFile) {
QMessageBox msg(QMessageBox::Critical,
tr("Cppcheck"),
QString(tr("No project file loaded")),
QMessageBox::Ok,
this);
msg.exec();
return;
}
ProjectFileDialog dlg(mProjectFile, isCppcheckPremium(), this);
if (dlg.exec() == QDialog::Accepted) {
mProjectFile->write();
analyzeProject(mProjectFile);
}
}
void MainWindow::showStatistics()
{
StatsDialog statsDialog(this);
// Show a dialog with the previous scan statistics and project information
statsDialog.setProject(mProjectFile);
statsDialog.setPathSelected(mCurrentDirectory);
statsDialog.setNumberOfFilesScanned(mThread->getPreviousFilesCount());
statsDialog.setScanDuration(mThread->getPreviousScanDuration() / 1000.0);
statsDialog.setStatistics(mUI->mResults->getStatistics());
statsDialog.exec();
}
void MainWindow::showLibraryEditor()
{
LibraryDialog libraryDialog(this);
libraryDialog.exec();
}
void MainWindow::filterResults()
{
mUI->mResults->filterResults(mLineEditFilter->text());
}
void MainWindow::enableProjectActions(bool enable)
{
mUI->mActionCloseProjectFile->setEnabled(enable);
mUI->mActionEditProjectFile->setEnabled(enable);
mUI->mActionCheckLibrary->setEnabled(enable);
mUI->mActionCheckConfiguration->setEnabled(enable);
}
void MainWindow::enableProjectOpenActions(bool enable)
{
mUI->mActionNewProjectFile->setEnabled(enable);
mUI->mActionOpenProjectFile->setEnabled(enable);
}
void MainWindow::openRecentProject()
{
auto *action = qobject_cast<QAction *>(sender());
if (!action)
return;
const QString project = action->data().toString();
QFileInfo inf(project);
if (inf.exists()) {
if (inf.suffix() == "xml")
loadResults(project);
else {
loadProjectFile(project);
loadLastResults();
}
} else {
const QString text(tr("The project file\n\n%1\n\n could not be found!\n\n"
"Do you want to remove the file from the recently "
"used projects -list?").arg(project));
QMessageBox msg(QMessageBox::Warning,
tr("Cppcheck"),
text,
QMessageBox::Yes | QMessageBox::No,
this);
msg.setDefaultButton(QMessageBox::No);
const int rv = msg.exec();
if (rv == QMessageBox::Yes) {
removeProjectMRU(project);
}
}
}
void MainWindow::updateMRUMenuItems()
{
for (QAction* recentProjectAct : mRecentProjectActs) {
if (recentProjectAct != nullptr)
mUI->mMenuFile->removeAction(recentProjectAct);
}
QStringList projects = mSettings->value(SETTINGS_MRU_PROJECTS).toStringList();
// Do a sanity check - remove duplicates and non-existing projects
int removed = projects.removeDuplicates();
for (int i = projects.size() - 1; i >= 0; i--) {
if (!QFileInfo::exists(projects[i])) {
projects.removeAt(i);
removed++;
}
}
if (removed)
mSettings->setValue(SETTINGS_MRU_PROJECTS, projects);
const int numRecentProjects = qMin(projects.size(), (int)MaxRecentProjects);
for (int i = 0; i < numRecentProjects; i++) {
const QString filename = QFileInfo(projects[i]).fileName();
const QString text = QString("&%1 %2").arg(i + 1).arg(filename);
mRecentProjectActs[i]->setText(text);
mRecentProjectActs[i]->setData(projects[i]);
mRecentProjectActs[i]->setVisible(true);
mUI->mMenuFile->insertAction(mUI->mActionProjectMRU, mRecentProjectActs[i]);
}
if (numRecentProjects > 1)
mRecentProjectActs[numRecentProjects] = mUI->mMenuFile->insertSeparator(mUI->mActionProjectMRU);
}
void MainWindow::addProjectMRU(const QString &project)
{
QStringList files = mSettings->value(SETTINGS_MRU_PROJECTS).toStringList();
files.removeAll(project);
files.prepend(project);
while (files.size() > MaxRecentProjects)
files.removeLast();
mSettings->setValue(SETTINGS_MRU_PROJECTS, files);
updateMRUMenuItems();
}
void MainWindow::removeProjectMRU(const QString &project)
{
QStringList files = mSettings->value(SETTINGS_MRU_PROJECTS).toStringList();
files.removeAll(project);
mSettings->setValue(SETTINGS_MRU_PROJECTS, files);
updateMRUMenuItems();
}
void MainWindow::selectPlatform()
{
auto *action = qobject_cast<QAction *>(sender());
if (action) {
const Platform::Type platform = (Platform::Type) action->data().toInt();
mSettings->setValue(SETTINGS_CHECKED_PLATFORM, platform);
}
}
void MainWindow::suppressIds(QStringList ids)
{
if (!mProjectFile)
return;
ids.removeDuplicates();
QList<SuppressionList::Suppression> suppressions = mProjectFile->getSuppressions();
for (const QString& id : ids) {
// Remove all matching suppressions
std::string id2 = id.toStdString();
for (int i = 0; i < suppressions.size();) {
if (suppressions[i].errorId == id2)
suppressions.removeAt(i);
else
++i;
}
SuppressionList::Suppression newSuppression;
newSuppression.errorId = id2;
suppressions << newSuppression;
}
mProjectFile->setSuppressions(suppressions);
mProjectFile->write();
}
static int getVersion(const QString& nameWithVersion) {
int ret = 0;
int v = 0;
int dot = 0;
for (const auto c: nameWithVersion) {
if (c == '\n' || c == '\r')
break;
if (c == ' ') {
if (ret > 0 && dot == 1 && nameWithVersion.endsWith(" dev"))
return ret * 1000000 + v * 1000 + 500;
dot = ret = v = 0;
}
else if (c == '.') {
++dot;
ret = ret * 1000 + v;
v = 0;
} else if (c >= '0' && c <= '9')
v = v * 10 + (c.toLatin1() - '0');
}
ret = ret * 1000 + v;
while (dot < 2) {
++dot;
ret *= 1000;
}
return ret;
}
void MainWindow::replyFinished(QNetworkReply *reply) {
reply->deleteLater();
if (reply->error()) {
mUI->mLayoutInformation->deleteLater();
qDebug() << "Response: ERROR";
return;
}
const QString str = reply->readAll();
qDebug() << "Response: " << str;
if (reply->url().fileName() == "version.txt") {
QString nameWithVersion = QString("Cppcheck %1").arg(CppCheck::version());
if (!mCppcheckCfgProductName.isEmpty())
nameWithVersion = mCppcheckCfgProductName;
const int appVersion = getVersion(nameWithVersion);
const int latestVersion = getVersion(str.trimmed());
if (appVersion < latestVersion) {
if (mSettings->value(SETTINGS_CHECK_VERSION, 0).toInt() != latestVersion) {
QString install;
if (isCppcheckPremium()) {
#ifdef Q_OS_WIN
const QString url("https://cppchecksolutions.com/cppcheck-premium-installation");
#else
const QString url("https://cppchecksolutions.com/cppcheck-premium-linux-installation");
#endif
install = "<a href=\"" + url + "\">" + tr("Install") + "</a>";
}
mUI->mButtonHideInformation->setVisible(true);
mUI->mLabelInformation->setVisible(true);
mUI->mLabelInformation->setText(tr("New version available: %1. %2").arg(str.trimmed()).arg(install));
}
}
}
if (!mUI->mLabelInformation->isVisible()) {
mUI->mLayoutInformation->deleteLater();
}
}
void MainWindow::hideInformation() {
int version = getVersion(mUI->mLabelInformation->text());
mSettings->setValue(SETTINGS_CHECK_VERSION, version);
mUI->mLabelInformation->setVisible(false);
mUI->mButtonHideInformation->setVisible(false);
mUI->mLayoutInformation->deleteLater();
}
bool MainWindow::isCppcheckPremium() const {
return mCppcheckCfgProductName.startsWith("Cppcheck Premium ");
}