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

View File

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

View File

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

View File

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

View File

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

View File

@ -90,6 +90,8 @@ ProjectFileDialog::ProjectFileDialog(ProjectFile *projectFile, QWidget *parent)
mLibraryCheckboxes << checkbox;
}
mUI.mEditTags->setValidator(new QRegExpValidator(QRegExp("[a-zA-Z0-9 ;]*"),this));
connect(mUI.mButtons, &QDialogButtonBox::accepted, this, &ProjectFileDialog::ok);
connect(mUI.mBtnBrowseBuildDir, &QPushButton::clicked, this, &ProjectFileDialog::browseBuildDir);
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.mAddonY2038->setChecked(projectFile->getAddons().contains("y2038"));
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();
}
@ -171,6 +181,9 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
if (mUI.mAddonCert->isChecked())
list << "cert";
projectFile->setAddons(list);
QStringList tags(mUI.mEditTags->text().split(";"));
tags.removeAll(QString());
projectFile->setTags(tags);
}
void ProjectFileDialog::ok()

View File

@ -285,6 +285,18 @@
</layout>
</widget>
</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>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">

View File

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

View File

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

View File

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

View File

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