Merge branch 'projfiles'
This commit is contained in:
commit
2a76d5c4b0
|
@ -96,12 +96,54 @@ void FileList::AddPathList(const QStringList &paths)
|
|||
|
||||
QStringList FileList::GetFileList() const
|
||||
{
|
||||
QStringList names;
|
||||
QFileInfo item;
|
||||
foreach(item, mFileList)
|
||||
if (mIgnoredPaths.empty())
|
||||
{
|
||||
QStringList names;
|
||||
foreach(QFileInfo item, mFileList)
|
||||
{
|
||||
QString name = QDir::fromNativeSeparators(item.canonicalFilePath());
|
||||
names << name;
|
||||
}
|
||||
return names;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ApplyIgnoreList();
|
||||
}
|
||||
}
|
||||
|
||||
void FileList::AddIngoreList(const QStringList &paths)
|
||||
{
|
||||
mIgnoredPaths = paths;
|
||||
}
|
||||
|
||||
QStringList FileList::ApplyIgnoreList() const
|
||||
{
|
||||
QStringList paths;
|
||||
foreach(QFileInfo item, mFileList)
|
||||
{
|
||||
QString name = QDir::fromNativeSeparators(item.canonicalFilePath());
|
||||
names << name;
|
||||
if (!Match(name))
|
||||
paths << name;
|
||||
}
|
||||
return names;
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool FileList::Match(const QString &path) const
|
||||
{
|
||||
for (int i = 0; i < mIgnoredPaths.size(); i++)
|
||||
{
|
||||
if (mIgnoredPaths[i].endsWith('/'))
|
||||
{
|
||||
const QString pathignore("/" + mIgnoredPaths[i]);
|
||||
if (path.indexOf(pathignore) != -1)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (path.endsWith(mIgnoredPaths[i]))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
* can be also added recursively when all files in subdirectories are added too.
|
||||
* The filenames are matched against the filter and only those files whose
|
||||
* filename extension is included in the filter list are added.
|
||||
*
|
||||
* This class also handles filtering of paths against ignore filters given. If
|
||||
* there is ignore filters then only paths not matching those filters are
|
||||
* returned.
|
||||
*/
|
||||
class FileList
|
||||
{
|
||||
|
@ -60,6 +64,12 @@ public:
|
|||
*/
|
||||
QStringList GetFileList() const;
|
||||
|
||||
/**
|
||||
* @brief Add list of paths to ignore list.
|
||||
* @param paths Paths to ignore.
|
||||
*/
|
||||
void AddIngoreList(const QStringList &paths);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
|
@ -74,8 +84,25 @@ protected:
|
|||
*/
|
||||
bool FilterMatches(const QFileInfo &inf);
|
||||
|
||||
/**
|
||||
* @brief Get filtered list of paths.
|
||||
* This method takes the list of paths and applies the ignore lists to
|
||||
* it. And then returns the list of paths that did not match the
|
||||
* ignore filters.
|
||||
* @return Filtered list of paths.
|
||||
*/
|
||||
QStringList ApplyIgnoreList() const;
|
||||
|
||||
/**
|
||||
* @brief Test if path matches any of the ignore filters.
|
||||
* @param path Path to test against filters.
|
||||
* @return true if any of the filters matches, false otherwise.
|
||||
*/
|
||||
bool Match(const QString &path) const;
|
||||
|
||||
private:
|
||||
QFileInfoList mFileList;
|
||||
QStringList mIgnoredPaths;
|
||||
};
|
||||
|
||||
#endif // FILELIST_H
|
||||
|
|
|
@ -191,6 +191,8 @@ void MainWindow::DoCheckFiles(const QStringList &files)
|
|||
|
||||
FileList pathList;
|
||||
pathList.AddPathList(files);
|
||||
if (mProject)
|
||||
pathList.AddIngoreList(mProject->GetProjectFile()->GetIgnoredPaths());
|
||||
QStringList fileNames = pathList.GetFileList();
|
||||
|
||||
mUI.mResults->Clear();
|
||||
|
@ -724,8 +726,6 @@ void MainWindow::OpenHtmlHelpContents()
|
|||
|
||||
void MainWindow::OpenProjectFile()
|
||||
{
|
||||
delete mProject;
|
||||
|
||||
const QString filter = tr("Project files (*.cppcheck);;All files(*.*)");
|
||||
QString filepath = QFileDialog::getOpenFileName(this,
|
||||
tr("Select Project File"),
|
||||
|
@ -740,6 +740,7 @@ void MainWindow::OpenProjectFile()
|
|||
|
||||
mUI.mActionCloseProjectFile->setEnabled(true);
|
||||
mUI.mActionEditProjectFile->setEnabled(true);
|
||||
delete mProject;
|
||||
mProject = new Project(filepath, this);
|
||||
mProject->Open();
|
||||
QString rootpath = mProject->GetProjectFile()->GetRootPath();
|
||||
|
|
|
@ -85,6 +85,9 @@ void Project::Edit()
|
|||
dlg.SetDefines(defines);
|
||||
QStringList paths = mPFile->GetCheckPaths();
|
||||
dlg.SetPaths(paths);
|
||||
QStringList ignorepaths = mPFile->GetIgnoredPaths();
|
||||
dlg.SetIgnorePaths(ignorepaths);
|
||||
|
||||
int rv = dlg.exec();
|
||||
if (rv == QDialog::Accepted)
|
||||
{
|
||||
|
@ -96,6 +99,9 @@ void Project::Edit()
|
|||
mPFile->SetDefines(defines);
|
||||
QStringList paths = dlg.GetPaths();
|
||||
mPFile->SetCheckPaths(paths);
|
||||
QStringList ignorepaths = dlg.GetIgnorePaths();
|
||||
mPFile->SetIgnoredPaths(ignorepaths);
|
||||
|
||||
bool writeSuccess = mPFile->Write();
|
||||
if (!writeSuccess)
|
||||
{
|
||||
|
|
|
@ -37,6 +37,9 @@ static const char PathName[] = "dir";
|
|||
static const char PathNameAttrib[] = "name";
|
||||
static const char RootPathName[] = "root";
|
||||
static const char RootPathNameAttrib[] = "name";
|
||||
static const char IgnoreElementName[] = "ignore";
|
||||
static const char IgnorePathName[] = "path";
|
||||
static const char IgnorePathNameAttrib[] = "name";
|
||||
|
||||
ProjectFile::ProjectFile(QObject *parent) :
|
||||
QObject(parent)
|
||||
|
@ -87,6 +90,10 @@ bool ProjectFile::Read(const QString &filename)
|
|||
if (insideProject && xmlReader.name() == DefinesElementName)
|
||||
ReadDefines(xmlReader);
|
||||
|
||||
// Find ignore list from inside project element
|
||||
if (insideProject && xmlReader.name() == IgnoreElementName)
|
||||
ReadIgnores(xmlReader);
|
||||
|
||||
break;
|
||||
|
||||
case QXmlStreamReader::EndElement:
|
||||
|
@ -130,6 +137,11 @@ QStringList ProjectFile::GetCheckPaths() const
|
|||
return mPaths;
|
||||
}
|
||||
|
||||
QStringList ProjectFile::GetIgnoredPaths() const
|
||||
{
|
||||
return mIgnoredPaths;
|
||||
}
|
||||
|
||||
void ProjectFile::ReadRootPath(QXmlStreamReader &reader)
|
||||
{
|
||||
QXmlStreamAttributes attribs = reader.attributes();
|
||||
|
@ -263,21 +275,67 @@ void ProjectFile::ReadCheckPaths(QXmlStreamReader &reader)
|
|||
while (!allRead);
|
||||
}
|
||||
|
||||
void ProjectFile::SetIncludes(QStringList includes)
|
||||
void ProjectFile::ReadIgnores(QXmlStreamReader &reader)
|
||||
{
|
||||
QXmlStreamReader::TokenType type;
|
||||
bool allRead = false;
|
||||
do
|
||||
{
|
||||
type = reader.readNext();
|
||||
switch (type)
|
||||
{
|
||||
case QXmlStreamReader::StartElement:
|
||||
// Read define-elements
|
||||
if (reader.name().toString() == IgnorePathName)
|
||||
{
|
||||
QXmlStreamAttributes attribs = reader.attributes();
|
||||
QString name = attribs.value("", IgnorePathNameAttrib).toString();
|
||||
if (!name.isEmpty())
|
||||
mIgnoredPaths << name;
|
||||
}
|
||||
break;
|
||||
|
||||
case QXmlStreamReader::EndElement:
|
||||
if (reader.name().toString() == IgnoreElementName)
|
||||
allRead = true;
|
||||
break;
|
||||
|
||||
// Not handled
|
||||
case QXmlStreamReader::NoToken:
|
||||
case QXmlStreamReader::Invalid:
|
||||
case QXmlStreamReader::StartDocument:
|
||||
case QXmlStreamReader::EndDocument:
|
||||
case QXmlStreamReader::Characters:
|
||||
case QXmlStreamReader::Comment:
|
||||
case QXmlStreamReader::DTD:
|
||||
case QXmlStreamReader::EntityReference:
|
||||
case QXmlStreamReader::ProcessingInstruction:
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (!allRead);
|
||||
}
|
||||
|
||||
void ProjectFile::SetIncludes(const QStringList &includes)
|
||||
{
|
||||
mIncludeDirs = includes;
|
||||
}
|
||||
|
||||
void ProjectFile::SetDefines(QStringList defines)
|
||||
void ProjectFile::SetDefines(const QStringList &defines)
|
||||
{
|
||||
mDefines = defines;
|
||||
}
|
||||
|
||||
void ProjectFile::SetCheckPaths(QStringList paths)
|
||||
void ProjectFile::SetCheckPaths(const QStringList &paths)
|
||||
{
|
||||
mPaths = paths;
|
||||
}
|
||||
|
||||
void ProjectFile::SetIgnoredPaths(const QStringList &paths)
|
||||
{
|
||||
mIgnoredPaths = paths;
|
||||
}
|
||||
|
||||
bool ProjectFile::Write(const QString &filename)
|
||||
{
|
||||
if (!filename.isEmpty())
|
||||
|
@ -303,8 +361,7 @@ bool ProjectFile::Write(const QString &filename)
|
|||
if (!mIncludeDirs.isEmpty())
|
||||
{
|
||||
xmlWriter.writeStartElement(IncludDirElementName);
|
||||
QString incdir;
|
||||
foreach(incdir, mIncludeDirs)
|
||||
foreach(QString incdir, mIncludeDirs)
|
||||
{
|
||||
xmlWriter.writeStartElement(DirElementName);
|
||||
xmlWriter.writeAttribute(DirNameAttrib, incdir);
|
||||
|
@ -316,8 +373,7 @@ bool ProjectFile::Write(const QString &filename)
|
|||
if (!mDefines.isEmpty())
|
||||
{
|
||||
xmlWriter.writeStartElement(DefinesElementName);
|
||||
QString define;
|
||||
foreach(define, mDefines)
|
||||
foreach(QString define, mDefines)
|
||||
{
|
||||
xmlWriter.writeStartElement(DefineName);
|
||||
xmlWriter.writeAttribute(DefineNameAttrib, define);
|
||||
|
@ -329,8 +385,7 @@ bool ProjectFile::Write(const QString &filename)
|
|||
if (!mPaths.isEmpty())
|
||||
{
|
||||
xmlWriter.writeStartElement(PathsElementName);
|
||||
QString path;
|
||||
foreach(path, mPaths)
|
||||
foreach(QString path, mPaths)
|
||||
{
|
||||
xmlWriter.writeStartElement(PathName);
|
||||
xmlWriter.writeAttribute(PathNameAttrib, path);
|
||||
|
@ -339,6 +394,18 @@ bool ProjectFile::Write(const QString &filename)
|
|||
xmlWriter.writeEndElement();
|
||||
}
|
||||
|
||||
if (!mIgnoredPaths.isEmpty())
|
||||
{
|
||||
xmlWriter.writeStartElement(IgnoreElementName);
|
||||
foreach(QString path, mIgnoredPaths)
|
||||
{
|
||||
xmlWriter.writeStartElement(IgnorePathName);
|
||||
xmlWriter.writeAttribute(IgnorePathNameAttrib, path);
|
||||
xmlWriter.writeEndElement();
|
||||
}
|
||||
xmlWriter.writeEndElement();
|
||||
}
|
||||
|
||||
xmlWriter.writeEndDocument();
|
||||
file.close();
|
||||
return true;
|
||||
|
|
|
@ -74,6 +74,12 @@ public:
|
|||
*/
|
||||
QStringList GetCheckPaths() const;
|
||||
|
||||
/**
|
||||
* @brief Get list of paths to ignore.
|
||||
* @return list of paths.
|
||||
*/
|
||||
QStringList GetIgnoredPaths() const;
|
||||
|
||||
/**
|
||||
* @brief Set project root path.
|
||||
* @param rootpath new project root path.
|
||||
|
@ -87,19 +93,25 @@ public:
|
|||
* @brief Set list of includes.
|
||||
* @param includes List of defines.
|
||||
*/
|
||||
void SetIncludes(QStringList includes);
|
||||
void SetIncludes(const QStringList &includes);
|
||||
|
||||
/**
|
||||
* @brief Set list of defines.
|
||||
* @param defines List of defines.
|
||||
*/
|
||||
void SetDefines(QStringList defines);
|
||||
void SetDefines(const QStringList &defines);
|
||||
|
||||
/**
|
||||
* @brief Set list of paths to check.
|
||||
* @param defines List of paths.
|
||||
*/
|
||||
void SetCheckPaths(QStringList paths);
|
||||
void SetCheckPaths(const QStringList &paths);
|
||||
|
||||
/**
|
||||
* @brief Set list of paths to ignore.
|
||||
* @param defines List of paths.
|
||||
*/
|
||||
void SetIgnoredPaths(const QStringList &paths);
|
||||
|
||||
/**
|
||||
* @brief Write project file (to disk).
|
||||
|
@ -142,6 +154,12 @@ protected:
|
|||
*/
|
||||
void ReadCheckPaths(QXmlStreamReader &reader);
|
||||
|
||||
/**
|
||||
* @brief Read lists of ignores.
|
||||
* @param reader XML stream reader.
|
||||
*/
|
||||
void ReadIgnores(QXmlStreamReader &reader);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
@ -171,6 +189,11 @@ private:
|
|||
* @brief List of paths to check.
|
||||
*/
|
||||
QStringList mPaths;
|
||||
|
||||
/**
|
||||
* @brief Paths ignored from the check.
|
||||
*/
|
||||
QStringList mIgnoredPaths;
|
||||
};
|
||||
/// @}
|
||||
#endif // PROJECT_FILE_H
|
||||
|
|
|
@ -27,6 +27,9 @@ program. The format is:
|
|||
<define name="_MSC_VER=1400" />
|
||||
<define name="_WIN32" />
|
||||
</defines>
|
||||
<ignore>
|
||||
<path name="gui/temp/" />
|
||||
</ignore>
|
||||
</project>
|
||||
|
||||
where:
|
||||
|
@ -42,5 +45,7 @@ where:
|
|||
recommended that relative paths are used for paths inside the project root
|
||||
folder for better portability.
|
||||
- defines element contains a list of C/C++ preprocessor defines.
|
||||
- ignore element contains list of paths to ignore. The path can be a
|
||||
directory (must end with path separator) or file.
|
||||
|
||||
See also gui.cppcheck file in gui-directory of cppcheck sources.
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
|
@ -196,6 +196,65 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Ignore</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Paths:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QListWidget" name="mListIgnoredPaths"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QPushButton" name="mBtnAddIgnorePath">
|
||||
<property name="text">
|
||||
<string>Add...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="mBtnEditIgnorePath">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="mBtnRemoveIgnorePath">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
|
|
@ -43,6 +43,9 @@ ProjectFileDialog::ProjectFileDialog(const QString &path, QWidget *parent)
|
|||
connect(mUI.mBtnRemoveInclude, SIGNAL(clicked()), this, SLOT(RemoveIncludeDir()));
|
||||
connect(mUI.mBtnEditPath, SIGNAL(clicked()), this, SLOT(EditPath()));
|
||||
connect(mUI.mBtnRemovePath, SIGNAL(clicked()), this, SLOT(RemovePath()));
|
||||
connect(mUI.mBtnAddIgnorePath, SIGNAL(clicked()), this, SLOT(AddIgnorePath()));
|
||||
connect(mUI.mBtnEditIgnorePath, SIGNAL(clicked()), this, SLOT(EditIgnorePath()));
|
||||
connect(mUI.mBtnRemoveIgnorePath, SIGNAL(clicked()), this, SLOT(RemoveIgnorePath()));
|
||||
}
|
||||
|
||||
void ProjectFileDialog::AddIncludeDir(const QString &dir)
|
||||
|
@ -65,6 +68,16 @@ void ProjectFileDialog::AddPath(const QString &path)
|
|||
mUI.mListPaths->addItem(item);
|
||||
}
|
||||
|
||||
void ProjectFileDialog::AddIgnorePath(const QString &path)
|
||||
{
|
||||
if (path.isNull() || path.isEmpty())
|
||||
return;
|
||||
|
||||
QListWidgetItem *item = new QListWidgetItem(path);
|
||||
item->setFlags(item->flags() | Qt::ItemIsEditable);
|
||||
mUI.mListIgnoredPaths->addItem(item);
|
||||
}
|
||||
|
||||
QString ProjectFileDialog::GetRootPath() const
|
||||
{
|
||||
QString root = mUI.mEditProjectRoot->text();
|
||||
|
@ -111,6 +124,18 @@ QStringList ProjectFileDialog::GetPaths() const
|
|||
return paths;
|
||||
}
|
||||
|
||||
QStringList ProjectFileDialog::GetIgnorePaths() const
|
||||
{
|
||||
const int count = mUI.mListIgnoredPaths->count();
|
||||
QStringList paths;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
QListWidgetItem *item = mUI.mListIgnoredPaths->item(i);
|
||||
paths << item->text();
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
void ProjectFileDialog::SetRootPath(const QString &root)
|
||||
{
|
||||
mUI.mEditProjectRoot->setText(root);
|
||||
|
@ -147,6 +172,14 @@ void ProjectFileDialog::SetPaths(const QStringList &paths)
|
|||
}
|
||||
}
|
||||
|
||||
void ProjectFileDialog::SetIgnorePaths(const QStringList &paths)
|
||||
{
|
||||
foreach(QString path, paths)
|
||||
{
|
||||
AddIgnorePath(path);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectFileDialog::AddIncludeDir()
|
||||
{
|
||||
QString selectedDir = QFileDialog::getExistingDirectory(this,
|
||||
|
@ -196,3 +229,30 @@ void ProjectFileDialog::RemovePath()
|
|||
QListWidgetItem *item = mUI.mListPaths->takeItem(row);
|
||||
delete item;
|
||||
}
|
||||
|
||||
void ProjectFileDialog::AddIgnorePath()
|
||||
{
|
||||
QString selectedDir = QFileDialog::getExistingDirectory(this,
|
||||
tr("Select directory to ignore"),
|
||||
QString());
|
||||
|
||||
if (!selectedDir.isEmpty())
|
||||
{
|
||||
if (!selectedDir.endsWith('/'))
|
||||
selectedDir += '/';
|
||||
AddIgnorePath(selectedDir);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectFileDialog::EditIgnorePath()
|
||||
{
|
||||
QListWidgetItem *item = mUI.mListIgnoredPaths->currentItem();
|
||||
mUI.mListIgnoredPaths->editItem(item);
|
||||
}
|
||||
|
||||
void ProjectFileDialog::RemoveIgnorePath()
|
||||
{
|
||||
const int row = mUI.mListIgnoredPaths->currentRow();
|
||||
QListWidgetItem *item = mUI.mListIgnoredPaths->takeItem(row);
|
||||
delete item;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,12 @@ public:
|
|||
*/
|
||||
QStringList GetPaths() const;
|
||||
|
||||
/**
|
||||
* @brief Return ignored paths from the dialog control.
|
||||
* @return List of ignored paths.
|
||||
*/
|
||||
QStringList GetIgnorePaths() const;
|
||||
|
||||
/**
|
||||
* @brief Set project root path to dialog control.
|
||||
* @param root Project root path to set to dialog control.
|
||||
|
@ -90,6 +96,12 @@ public:
|
|||
*/
|
||||
void SetPaths(const QStringList &paths);
|
||||
|
||||
/**
|
||||
* @brief Set ignored paths to dialog control.
|
||||
* @param paths List of path names to set to dialog control.
|
||||
*/
|
||||
void SetIgnorePaths(const QStringList &paths);
|
||||
|
||||
protected slots:
|
||||
/**
|
||||
* @brief Browse for include directory.
|
||||
|
@ -122,6 +134,21 @@ protected slots:
|
|||
*/
|
||||
void RemovePath();
|
||||
|
||||
/**
|
||||
* @brief Add new path to ignore.
|
||||
*/
|
||||
void AddIgnorePath();
|
||||
|
||||
/**
|
||||
* @brief Edit ignored path in the list.
|
||||
*/
|
||||
void EditIgnorePath();
|
||||
|
||||
/**
|
||||
* @brief Remove ignored path from the list.
|
||||
*/
|
||||
void RemoveIgnorePath();
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
|
@ -136,6 +163,12 @@ protected:
|
|||
*/
|
||||
void AddPath(const QString &path);
|
||||
|
||||
/**
|
||||
* @brief Add new path to ignore list.
|
||||
* @param path Path to add.
|
||||
*/
|
||||
void AddIgnorePath(const QString &path);
|
||||
|
||||
private:
|
||||
Ui::ProjectFile mUI;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue