GUI: Rework handling of tags. The allowed tags are now userdefined in the project file

This commit is contained in:
Daniel Marjamäki 2017-08-18 17:25:08 +02:00
parent 94c5c3703c
commit 330ceccdc9
11 changed files with 102 additions and 83 deletions

View File

@ -35,7 +35,6 @@ ErrorItem::ErrorItem()
: severity(Severity::none) : severity(Severity::none)
, inconclusive(false) , inconclusive(false)
, cwe(-1) , cwe(-1)
, tag(NONE)
{ {
} }
@ -46,7 +45,6 @@ ErrorItem::ErrorItem(const ErrorLogger::ErrorMessage &errmsg)
, summary(QString::fromStdString(errmsg.shortMessage())) , summary(QString::fromStdString(errmsg.shortMessage()))
, message(QString::fromStdString(errmsg.verboseMessage())) , message(QString::fromStdString(errmsg.verboseMessage()))
, cwe(errmsg._cwe.id) , cwe(errmsg._cwe.id)
, tag(NONE)
{ {
for (std::list<ErrorLogger::ErrorMessage::FileLocation>::const_iterator loc = errmsg._callStack.begin(); for (std::list<ErrorLogger::ErrorMessage::FileLocation>::const_iterator loc = errmsg._callStack.begin();
loc != errmsg._callStack.end(); loc != errmsg._callStack.end();

View File

@ -92,7 +92,7 @@ public:
// Special GUI properties // Special GUI properties
QString sinceDate; QString sinceDate;
enum Tag { NONE, FP, IGNORE, BUG } tag; QString tags;
}; };
Q_DECLARE_METATYPE(ErrorItem); Q_DECLARE_METATYPE(ErrorItem);
@ -111,7 +111,7 @@ public:
QString summary; QString summary;
QString message; QString message;
QString sinceDate; QString sinceDate;
ErrorItem::Tag tag; QString tags;
}; };
/// @} /// @}

View File

@ -1399,6 +1399,8 @@ QString MainWindow::getLastResults() const
bool MainWindow::loadLastResults() bool MainWindow::loadLastResults()
{ {
if (mProjectFile)
mUI.mResults->setTags(mProjectFile->getTags());
const QString &lastResults = getLastResults(); const QString &lastResults = getLastResults();
if (lastResults.isEmpty()) if (lastResults.isEmpty())
return false; return false;
@ -1416,6 +1418,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile)
const QString rootpath = projectFile->getRootPath(); const QString rootpath = projectFile->getRootPath();
mThread->setAddons(projectFile->getAddons()); mThread->setAddons(projectFile->getAddons());
mUI.mResults->setTags(projectFile->getTags());
// If the root path is not given or is not "current dir", use project // If the root path is not given or is not "current dir", use project
// file's location directory as root path // file's location directory as root path
@ -1508,6 +1511,7 @@ void MainWindow::closeProjectFile()
delete mProjectFile; delete mProjectFile;
mProjectFile = nullptr; mProjectFile = nullptr;
mUI.mResults->clear(true); mUI.mResults->clear(true);
mUI.mResults->setTags(QStringList());
enableProjectActions(false); enableProjectActions(false);
enableProjectOpenActions(true); enableProjectOpenActions(true);
formatAndSetTitle(); formatAndSetTitle();

View File

@ -53,6 +53,8 @@ static const char SuppressionsElementName[] = "suppressions";
static const char SuppressionElementName[] = "suppression"; static const char SuppressionElementName[] = "suppression";
static const char AddonElementName[] = "addon"; static const char AddonElementName[] = "addon";
static const char AddonsElementName[] = "addons"; static const char AddonsElementName[] = "addons";
static const char TagsElementName[] = "tags";
static const char TagElementName[] = "tag";
ProjectFile::ProjectFile(QObject *parent) : ProjectFile::ProjectFile(QObject *parent) :
QObject(parent) QObject(parent)
@ -151,6 +153,9 @@ bool ProjectFile::read(const QString &filename)
if (insideProject && xmlReader.name() == AddonsElementName) if (insideProject && xmlReader.name() == AddonsElementName)
readStringList(mAddons, xmlReader, AddonElementName); readStringList(mAddons, xmlReader, AddonElementName);
if (insideProject && xmlReader.name() == TagsElementName)
readStringList(mTags, xmlReader, TagElementName);
break; break;
case QXmlStreamReader::EndElement: case QXmlStreamReader::EndElement:
@ -583,6 +588,8 @@ bool ProjectFile::write(const QString &filename)
AddonsElementName, AddonsElementName,
AddonElementName); AddonElementName);
writeStringList(xmlWriter, mTags, TagsElementName, TagElementName);
xmlWriter.writeEndDocument(); xmlWriter.writeEndDocument();
file.close(); file.close();
return true; return true;

View File

@ -122,6 +122,10 @@ public:
return mAddons; return mAddons;
} }
QStringList getTags() const {
return mTags;
}
/** /**
* @brief Get filename for the project file. * @brief Get filename for the project file.
* @return file name. * @return file name.
@ -138,7 +142,6 @@ public:
mRootPath = rootpath; mRootPath = rootpath;
} }
void setBuildDir(const QString &buildDir) { void setBuildDir(const QString &buildDir) {
mBuildDir = buildDir; mBuildDir = buildDir;
} }
@ -193,6 +196,14 @@ public:
*/ */
void setAddons(const QStringList &addons); void setAddons(const QStringList &addons);
/**
* @brief Set tags.
* @param tags tag list
*/
void setTags(const QStringList &tags) {
mTags = tags;
}
/** /**
* @brief Write project file (to disk). * @brief Write project file (to disk).
* @param filename Filename to use. * @param filename Filename to use.
@ -335,6 +346,11 @@ private:
* @brief List of addons. * @brief List of addons.
*/ */
QStringList mAddons; QStringList mAddons;
/**
* @brief Warning tags
*/
QStringList mTags;
}; };
/// @} /// @}
#endif // PROJECT_FILE_H #endif // PROJECT_FILE_H

View File

@ -90,6 +90,8 @@ ProjectFileDialog::ProjectFileDialog(ProjectFile *projectFile, QWidget *parent)
mLibraryCheckboxes << checkbox; mLibraryCheckboxes << checkbox;
} }
mUI.mEditTags->setValidator(new QRegExpValidator(QRegExp("[a-zA-Z0-9 ;]*"),this));
connect(mUI.mButtons, &QDialogButtonBox::accepted, this, &ProjectFileDialog::ok); connect(mUI.mButtons, &QDialogButtonBox::accepted, this, &ProjectFileDialog::ok);
connect(mUI.mBtnBrowseBuildDir, &QPushButton::clicked, this, &ProjectFileDialog::browseBuildDir); connect(mUI.mBtnBrowseBuildDir, &QPushButton::clicked, this, &ProjectFileDialog::browseBuildDir);
connect(mUI.mBtnClearImportProject, &QPushButton::clicked, this, &ProjectFileDialog::clearImportProject); connect(mUI.mBtnClearImportProject, &QPushButton::clicked, this, &ProjectFileDialog::clearImportProject);
@ -146,6 +148,14 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile)
mUI.mAddonThreadSafety->setChecked(projectFile->getAddons().contains("threadsafety")); mUI.mAddonThreadSafety->setChecked(projectFile->getAddons().contains("threadsafety"));
mUI.mAddonY2038->setChecked(projectFile->getAddons().contains("y2038")); mUI.mAddonY2038->setChecked(projectFile->getAddons().contains("y2038"));
mUI.mAddonCert->setChecked(projectFile->getAddons().contains("cert")); mUI.mAddonCert->setChecked(projectFile->getAddons().contains("cert"));
QString tags;
foreach (const QString tag, projectFile->getTags()) {
if (tags.isEmpty())
tags = tag;
else
tags += ';' + tag;
}
mUI.mEditTags->setText(tags);
updatePathsAndDefines(); updatePathsAndDefines();
} }
@ -171,6 +181,9 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
if (mUI.mAddonCert->isChecked()) if (mUI.mAddonCert->isChecked())
list << "cert"; list << "cert";
projectFile->setAddons(list); projectFile->setAddons(list);
QStringList tags(mUI.mEditTags->text().split(";"));
tags.removeAll(QString());
projectFile->setTags(tags);
} }
void ProjectFileDialog::ok() void ProjectFileDialog::ok()

View File

@ -285,6 +285,18 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Warning tags (separated by semicolon)</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="mEditTags"/>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">
<property name="title"> <property name="title">

View File

@ -51,7 +51,7 @@
// These must match column headers given in ResultsTree::translate() // These must match column headers given in ResultsTree::translate()
static const unsigned int COLUMN_SINCE_DATE = 6; static const unsigned int COLUMN_SINCE_DATE = 6;
static const unsigned int COLUMN_TAG = 7; static const unsigned int COLUMN_TAGS = 7;
ResultsTree::ResultsTree(QWidget * parent) : ResultsTree::ResultsTree(QWidget * parent) :
QTreeView(parent), QTreeView(parent),
@ -161,7 +161,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
line.message = item.message; line.message = item.message;
line.severity = item.severity; line.severity = item.severity;
line.sinceDate = item.sinceDate; line.sinceDate = item.sinceDate;
line.tag = item.tag; line.tags = item.tags;
//Create the base item for the error and ensure it has a proper //Create the base item for the error and ensure it has a proper
//file item as a parent //file item as a parent
QStandardItem* fileItem = ensureFileItem(loc.file, item.file0, hide); QStandardItem* fileItem = ensureFileItem(loc.file, item.file0, hide);
@ -187,7 +187,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
data["inconclusive"] = item.inconclusive; data["inconclusive"] = item.inconclusive;
data["file0"] = stripPath(item.file0, true); data["file0"] = stripPath(item.file0, true);
data["sinceDate"] = item.sinceDate; data["sinceDate"] = item.sinceDate;
data["tag"] = item.tag; data["tags"] = item.tags;
stditem->setData(QVariant(data)); stditem->setData(QVariant(data));
//Add backtrace files as children //Add backtrace files as children
@ -245,21 +245,9 @@ QStandardItem *ResultsTree::addBacktraceFiles(QStandardItem *parent,
<< createNormalItem(childOfMessage ? QString() : item.errorId) << createNormalItem(childOfMessage ? QString() : item.errorId)
<< (childOfMessage ? createNormalItem(QString()) : createCheckboxItem(item.inconclusive)) << (childOfMessage ? createNormalItem(QString()) : createCheckboxItem(item.inconclusive))
<< createNormalItem(item.summary) << createNormalItem(item.summary)
<< createNormalItem(item.sinceDate); << createNormalItem(item.sinceDate)
switch (item.tag) { << createNormalItem(item.tags);
case ErrorItem::NONE:
list << createNormalItem("");
break;
case ErrorItem::FP:
list << createNormalItem(tr("False positive"));
break;
case ErrorItem::IGNORE:
list << createNormalItem(tr("Ignore"));
break;
case ErrorItem::BUG:
list << createNormalItem(tr("bug"));
break;
};
//TODO message has parameter names so we'll need changes to the core //TODO message has parameter names so we'll need changes to the core
//cppcheck so we can get proper translations //cppcheck so we can get proper translations
@ -619,13 +607,16 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e)
recheckSelectedFiles->setDisabled(false); recheckSelectedFiles->setDisabled(false);
menu.addAction(recheckSelectedFiles); menu.addAction(recheckSelectedFiles);
menu.addSeparator();
menu.addAction(copyfilename); menu.addAction(copyfilename);
menu.addAction(copypath); menu.addAction(copypath);
menu.addAction(copymessage); menu.addAction(copymessage);
menu.addAction(copymessageid); menu.addAction(copymessageid);
menu.addSeparator();
menu.addAction(hide); menu.addAction(hide);
menu.addAction(hideallid); menu.addAction(hideallid);
menu.addAction(suppress); menu.addAction(suppress);
menu.addSeparator();
menu.addAction(opencontainingfolder); menu.addAction(opencontainingfolder);
connect(recheckSelectedFiles, SIGNAL(triggered()), this, SLOT(recheckSelectedFiles())); connect(recheckSelectedFiles, SIGNAL(triggered()), this, SLOT(recheckSelectedFiles()));
@ -638,16 +629,21 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e)
connect(suppress, SIGNAL(triggered()), this, SLOT(suppressSelectedIds())); connect(suppress, SIGNAL(triggered()), this, SLOT(suppressSelectedIds()));
connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder())); connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder()));
if (!mTags.isEmpty()) {
menu.addSeparator(); menu.addSeparator();
QAction *fp = new QAction(tr("False positive"), &menu); QMenu *tagMenu = menu.addMenu(tr("Tag"));
QAction *ignore = new QAction(tr("Ignore"), &menu); {
QAction *bug = new QAction(tr("Bug"), &menu); QAction *action = new QAction(tr("No tag"), tagMenu);
menu.addAction(fp); tagMenu->addAction(action);
menu.addAction(ignore); connect(action, &QAction::triggered, [=](){ tagSelectedItems(QString()); });
menu.addAction(bug); }
connect(fp, &QAction::triggered, this, &ResultsTree::tagFP);
connect(ignore, &QAction::triggered, this, &ResultsTree::tagIgnore); foreach (const QString tagstr, mTags) {
connect(bug, &QAction::triggered, this, &ResultsTree::tagBug); QAction *action = new QAction(tagstr, tagMenu);
tagMenu->addAction(action);
connect(action, &QAction::triggered, [=](){ tagSelectedItems(tagstr); });
}
}
} }
//Start the menu //Start the menu
@ -985,7 +981,7 @@ void ResultsTree::openContainingFolder()
} }
} }
void ResultsTree::tagSelectedItems(int tagNumber, const QString &tag) void ResultsTree::tagSelectedItems(const QString &tag)
{ {
if (!mSelectionModel) if (!mSelectionModel)
return; return;
@ -993,10 +989,10 @@ void ResultsTree::tagSelectedItems(int tagNumber, const QString &tag)
foreach (QModelIndex index, mSelectionModel->selectedRows()) { foreach (QModelIndex index, mSelectionModel->selectedRows()) {
QStandardItem *item = mModel.itemFromIndex(index); QStandardItem *item = mModel.itemFromIndex(index);
QVariantMap data = item->data().toMap(); QVariantMap data = item->data().toMap();
if (data.contains("tag")) { if (data.contains("tags")) {
data["tag"] = tagNumber; data["tags"] = tag;
item->setData(QVariant(data)); item->setData(QVariant(data));
item->parent()->child(index.row(), COLUMN_TAG)->setText(tag); item->parent()->child(index.row(), COLUMN_TAGS)->setText(tag);
isTagged = true; isTagged = true;
} }
} }
@ -1004,22 +1000,6 @@ void ResultsTree::tagSelectedItems(int tagNumber, const QString &tag)
emit tagged(); emit tagged();
} }
void ResultsTree::tagFP(bool)
{
tagSelectedItems(ErrorItem::FP, tr("False positive"));
}
void ResultsTree::tagIgnore(bool)
{
tagSelectedItems(ErrorItem::IGNORE, tr("Ignore"));
}
void ResultsTree::tagBug(bool)
{
tagSelectedItems(ErrorItem::BUG, tr("Bug"));
}
void ResultsTree::context(int application) void ResultsTree::context(int application)
{ {
startApplication(mContextItem, application); startApplication(mContextItem, application);
@ -1164,17 +1144,11 @@ void ResultsTree::updateFromOldReport(const QString &filename)
continue; continue;
} }
if (errorItem.tag != ErrorItem::NONE) if (!errorItem.tags.isEmpty())
continue; continue;
const ErrorItem &oldErrorItem = oldErrors[oldErrorIndex]; const ErrorItem &oldErrorItem = oldErrors[oldErrorIndex];
data["tags"] = oldErrorItem.tags;
if (oldErrorItem.tag == ErrorItem::FP)
data["tag"] = "fp";
else if (oldErrorItem.tag == ErrorItem::IGNORE)
data["tag"] = "ignore";
else if (oldErrorItem.tag == ErrorItem::BUG)
data["tag"] = "bug";
error->setData(data); error->setData(data);
} }
} }
@ -1192,7 +1166,7 @@ void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) con
item->inconclusive = data["inconclusive"].toBool(); item->inconclusive = data["inconclusive"].toBool();
item->file0 = data["file0"].toString(); item->file0 = data["file0"].toString();
item->sinceDate = data["sinceDate"].toString(); item->sinceDate = data["sinceDate"].toString();
item->tag = (ErrorItem::Tag)data["tag"].toInt(); item->tags = data["tags"].toString();
if (error->rowCount() == 0) { if (error->rowCount() == 0) {
QErrorPathItem e; QErrorPathItem e;

View File

@ -52,6 +52,10 @@ public:
virtual ~ResultsTree(); virtual ~ResultsTree();
void initialize(QSettings *settings, ApplicationList *list, ThreadHandler *checkThreadHandler); void initialize(QSettings *settings, ApplicationList *list, ThreadHandler *checkThreadHandler);
void setTags(const QStringList &tags) {
mTags = tags;
}
/** /**
* @brief Add a new item to the tree * @brief Add a new item to the tree
* *
@ -286,10 +290,6 @@ protected slots:
*/ */
virtual void currentChanged(const QModelIndex &current, const QModelIndex &previous); virtual void currentChanged(const QModelIndex &current, const QModelIndex &previous);
void tagFP(bool);
void tagIgnore(bool);
void tagBug(bool);
protected: protected:
/** /**
@ -519,11 +519,13 @@ protected:
private: private:
/** tag selected items */ /** tag selected items */
void tagSelectedItems(int tagNumber, const QString &tag); void tagSelectedItems(const QString &tag);
/** @brief Convert GUI error item into data error item */ /** @brief Convert GUI error item into data error item */
void readErrorItem(const QStandardItem *error, ErrorItem *item) const; void readErrorItem(const QStandardItem *error, ErrorItem *item) const;
QStringList mTags;
QItemSelectionModel *mSelectionModel; QItemSelectionModel *mSelectionModel;
ThreadHandler *mThread; ThreadHandler *mThread;
}; };

View File

@ -48,6 +48,10 @@ public:
void initialize(QSettings *settings, ApplicationList *list, ThreadHandler *checkThreadHandler); void initialize(QSettings *settings, ApplicationList *list, ThreadHandler *checkThreadHandler);
virtual ~ResultsView(); virtual ~ResultsView();
void setTags(const QStringList &tags) {
mUI.mTree->setTags(tags);
}
/** /**
* @brief Function to show/hide certain type of errors * @brief Function to show/hide certain type of errors
* Refreshes the tree. * Refreshes the tree.

View File

@ -36,7 +36,7 @@ static const QString LocationElementName = "location";
static const QString ColAttribute = "col"; static const QString ColAttribute = "col";
static const QString CWEAttribute = "cwe"; static const QString CWEAttribute = "cwe";
static const QString SinceDateAttribute = "sinceDate"; static const QString SinceDateAttribute = "sinceDate";
static const QString TagAttribute = "tag"; static const QString TagsAttribute = "tag";
static const QString FilenameAttribute = "file"; static const QString FilenameAttribute = "file";
static const QString IncludedFromFilenameAttribute = "file0"; static const QString IncludedFromFilenameAttribute = "file0";
static const QString InconclusiveAttribute = "inconclusive"; static const QString InconclusiveAttribute = "inconclusive";
@ -124,12 +124,8 @@ void XmlReportV2::writeError(const ErrorItem &error)
mXmlWriter->writeAttribute(CWEAttribute, QString::number(error.cwe)); mXmlWriter->writeAttribute(CWEAttribute, QString::number(error.cwe));
if (!error.sinceDate.isEmpty()) if (!error.sinceDate.isEmpty())
mXmlWriter->writeAttribute(SinceDateAttribute, error.sinceDate); mXmlWriter->writeAttribute(SinceDateAttribute, error.sinceDate);
if (error.tag == ErrorItem::FP) if (!error.tags.isEmpty())
mXmlWriter->writeAttribute(TagAttribute, "fp"); mXmlWriter->writeAttribute(TagsAttribute, error.tags);
else if (error.tag == ErrorItem::IGNORE)
mXmlWriter->writeAttribute(TagAttribute, "ignore");
else if (error.tag == ErrorItem::BUG)
mXmlWriter->writeAttribute(TagAttribute, "bug");
for (int i = error.errorPath.count() - 1; i >= 0; i--) { for (int i = error.errorPath.count() - 1; i >= 0; i--) {
mXmlWriter->writeStartElement(LocationElementName); mXmlWriter->writeStartElement(LocationElementName);
@ -221,15 +217,8 @@ ErrorItem XmlReportV2::readError(QXmlStreamReader *reader)
item.cwe = attribs.value(QString(), CWEAttribute).toString().toInt(); item.cwe = attribs.value(QString(), CWEAttribute).toString().toInt();
if (attribs.hasAttribute(QString(), SinceDateAttribute)) if (attribs.hasAttribute(QString(), SinceDateAttribute))
item.sinceDate = attribs.value(QString(), SinceDateAttribute).toString(); item.sinceDate = attribs.value(QString(), SinceDateAttribute).toString();
if (attribs.hasAttribute(QString(), TagAttribute)) { if (attribs.hasAttribute(QString(), TagsAttribute))
const QString tag = attribs.value(QString(), TagAttribute).toString(); item.tags = attribs.value(QString(), TagsAttribute).toString();
if (tag == "fp")
item.tag = ErrorItem::FP;
else if (tag == "ignore")
item.tag = ErrorItem::IGNORE;
else if (tag == "bug")
item.tag = ErrorItem::BUG;
}
} }
bool errorRead = false; bool errorRead = false;