diff --git a/gui/erroritem.cpp b/gui/erroritem.cpp index 778bab122..cfe3c6351 100644 --- a/gui/erroritem.cpp +++ b/gui/erroritem.cpp @@ -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::const_iterator loc = errmsg._callStack.begin(); loc != errmsg._callStack.end(); diff --git a/gui/erroritem.h b/gui/erroritem.h index e0403f205..36900209d 100644 --- a/gui/erroritem.h +++ b/gui/erroritem.h @@ -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; }; /// @} diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 2e8f227f1..551474e49 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -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(); diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index fa7a44acb..aeab628bd 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -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; diff --git a/gui/projectfile.h b/gui/projectfile.h index ee64951ec..2e69f3ae9 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -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 diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp index 6f22d3a20..107adf94b 100644 --- a/gui/projectfiledialog.cpp +++ b/gui/projectfiledialog.cpp @@ -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() diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui index 34ceb46ed..9594441a9 100644 --- a/gui/projectfiledialog.ui +++ b/gui/projectfiledialog.ui @@ -285,6 +285,18 @@ + + + + Warning tags (separated by semicolon) + + + + + + + + diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 7b27e6ddf..756f3b252 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -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; diff --git a/gui/resultstree.h b/gui/resultstree.h index f1bf646d7..f25f96595 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -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 ¤t, 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; }; diff --git a/gui/resultsview.h b/gui/resultsview.h index 39902622a..1ff12a66d 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -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. diff --git a/gui/xmlreportv2.cpp b/gui/xmlreportv2.cpp index 921b8c5c5..c1b403275 100644 --- a/gui/xmlreportv2.cpp +++ b/gui/xmlreportv2.cpp @@ -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;