544 lines
16 KiB
C++
544 lines
16 KiB
C++
#include "xmlprojectmanager.h"
|
||
|
||
#include <xmlconfig.h>
|
||
#include <QDir>
|
||
#include <QRandomGenerator>
|
||
#include <QDateTime>
|
||
#include <QTextStream>
|
||
#include <QCoreApplication>
|
||
#include <QIcon>
|
||
|
||
using namespace Project;
|
||
using namespace Config;
|
||
|
||
class Impl_ProjectException : public ProjectException
|
||
{
|
||
public:
|
||
Impl_ProjectException(const QString& title, const QString& reason)
|
||
{
|
||
temp_string = QString("%1:%2").arg(title, reason);
|
||
title_length = title.length();
|
||
}
|
||
|
||
private:
|
||
int title_length;
|
||
QString temp_string;
|
||
|
||
// exception interface
|
||
public:
|
||
virtual const char *what() const override{
|
||
return temp_string.toLocal8Bit();
|
||
}
|
||
|
||
// ParseException interface
|
||
public:
|
||
virtual QString reason() const override{return temp_string.mid(title_length+1);}
|
||
virtual QString title() const override{ return temp_string.mid(0, title_length);}
|
||
};
|
||
|
||
|
||
XMLProjectManager::XMLProjectManager(QObject *parent)
|
||
: QObject(parent),
|
||
project_config(new XMLConfig(this)),
|
||
project_structure(new QStandardItemModel(this)),
|
||
open_status(false)
|
||
{
|
||
|
||
}
|
||
|
||
XMLProjectManager::~XMLProjectManager(){
|
||
delete project_config;
|
||
}
|
||
|
||
QStandardItemModel *XMLProjectManager::model() const
|
||
{
|
||
return this->project_structure;
|
||
}
|
||
|
||
QString XMLProjectManager::suffix() const
|
||
{
|
||
return "nsf";
|
||
}
|
||
|
||
bool XMLProjectManager::isOpen() const noexcept
|
||
{
|
||
return open_status;
|
||
}
|
||
|
||
bool XMLProjectManager::isModified() const noexcept
|
||
{
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* <?xml version='1.0'?>
|
||
* <project name="project-name" config="xml-path">
|
||
* <package name="name" path="filepath">
|
||
* <file name="name" path="filepath"/>
|
||
* <package name="name2" path="filepath" />
|
||
* </package>
|
||
* </project>
|
||
*/
|
||
void XMLProjectManager::openProject(const QString &project_file)
|
||
{
|
||
filepath_store = project_file;
|
||
QFileInfo info(project_file);
|
||
if(!info.exists())
|
||
throw new Impl_ProjectException("打开失败", "指定的项目文件不存在:"+project_file);
|
||
|
||
// 获取项目文件内容
|
||
QFile project_in(project_file);
|
||
if(!project_in.open(QIODevice::ReadOnly|QIODevice::Text))
|
||
throw new Impl_ProjectException("打开失败", "指定的项目文件无法打开:" + project_file);
|
||
|
||
// 载入文件内容
|
||
QDomDocument doc;
|
||
QString err; int row, col;
|
||
if(!doc.setContent(&project_in, false, &err, &row, &col))
|
||
throw new Impl_ProjectException("打开失败", QString("项目文件格式错误:%1,row:%2,col:%3").arg(err).arg(row).arg(col));
|
||
|
||
// 构建项目组织树
|
||
auto root_elm = doc.documentElement();
|
||
auto name = root_elm.attribute("name");
|
||
auto pnode = new ProjectNode(NodeType::ROOT, name);
|
||
pnode->setFile(root_elm.attribute("config"));
|
||
project_structure->appendRow(pnode);
|
||
|
||
// 载入项目配置
|
||
auto config = root_elm.attribute("config");
|
||
auto config_path = info.dir().filePath(config);
|
||
project_config->loadFile(config_path);
|
||
|
||
structure_parser(root_elm, pnode);
|
||
|
||
open_status = true;
|
||
pnode->setIcon(QIcon(":/icons/toplevel.png"));
|
||
}
|
||
|
||
void XMLProjectManager::newProject(const QString &project_dir, const QString &name)
|
||
{
|
||
// 确定目标项目文件
|
||
filepath_store = fmtedProjectPath(project_dir, name);
|
||
|
||
QFileInfo info(filepath_store);
|
||
if(info.exists())
|
||
throw new Impl_ProjectException("新建错误", "指定路径的项目文件已经存在,无法构建新项目");
|
||
|
||
// 构建项目表示树
|
||
auto project_node = new ProjectNode(NodeType::ROOT, name);
|
||
project_node->setFile(".project_config.xml");
|
||
project_structure->appendRow(project_node);
|
||
|
||
QDir root(project_dir);
|
||
project_config->loadFile(root.filePath(project_node->file()));
|
||
|
||
// 写出到磁盘
|
||
save();
|
||
open_status = true;
|
||
project_node->setIcon(QIcon(":/icons/toplevel.png"));
|
||
}
|
||
|
||
QString XMLProjectManager::fmtedProjectPath(const QString &project_dir, const QString &name) const
|
||
{
|
||
QDir root(project_dir);
|
||
auto project_file = root.filePath(name + ".nsf");
|
||
|
||
return project_file;
|
||
}
|
||
|
||
void XMLProjectManager::closeProject()
|
||
{
|
||
open_status = false;
|
||
project_structure->clear();
|
||
}
|
||
|
||
void XMLProjectManager::save()
|
||
{
|
||
project_config->save();
|
||
|
||
QDomDocument doc;
|
||
auto pnode = static_cast<ProjectNode*>(project_structure->item(0));
|
||
doc.appendChild(doc.createProcessingInstruction("xml", "version='1.0'"));
|
||
auto project_elm = doc.createElement("project");
|
||
project_elm.setAttribute("name", pnode->text());
|
||
project_elm.setAttribute("config", pnode->file());
|
||
doc.appendChild(project_elm);
|
||
|
||
structure_severlize(pnode, project_elm);
|
||
|
||
QFile records(filepath_store);
|
||
if(!records.open(QIODevice::WriteOnly|QIODevice::Text))
|
||
throw new Impl_ProjectException("保存错误", "保存过程中,指定项目文件无法打开保存:" + filepath_store);
|
||
|
||
QTextStream txout(&records);
|
||
doc.save(txout, 4);
|
||
txout.flush();
|
||
}
|
||
|
||
QString XMLProjectManager::projectName() const
|
||
{
|
||
return project_structure->item(0)->text();
|
||
}
|
||
|
||
void XMLProjectManager::resetProjectName(const QString &name)
|
||
{
|
||
this->project_structure->item(0)->setText(name);
|
||
}
|
||
|
||
Configration *XMLProjectManager::configraions() const
|
||
{
|
||
return this->project_config;
|
||
}
|
||
|
||
QDir XMLProjectManager::projectDir() const
|
||
{
|
||
return QFileInfo(filepath_store).absoluteDir();
|
||
}
|
||
|
||
void XMLProjectManager::structure_parser(QDomElement struct_elm, ProjectNode *pnode)
|
||
{
|
||
auto children = struct_elm.childNodes();
|
||
|
||
for(auto idx=0; idx<children.count(); ++idx) {
|
||
auto node = children.at(idx);
|
||
if(node.isElement()){
|
||
auto xnode = node.toElement();
|
||
|
||
ProjectNode *node = nullptr;
|
||
if(xnode.tagName()=="package"){
|
||
node = new ProjectNode(NodeType::GROUP, xnode.attribute("name"));
|
||
}
|
||
else if(xnode.tagName() == "file"){
|
||
node = new ProjectNode(NodeType::FILE, xnode.attribute("name"));
|
||
node->setFile(xnode.attribute("path"));
|
||
all_files_managed << node->file();
|
||
}
|
||
|
||
pnode->appendRow(node);
|
||
structure_parser(xnode, node);
|
||
}
|
||
}
|
||
}
|
||
|
||
void XMLProjectManager::structure_severlize(ProjectNode *pnode, QDomElement &elm)
|
||
{
|
||
auto doc = elm.ownerDocument();
|
||
for(auto idx=0; idx<pnode->rowCount(); ++idx){
|
||
auto item = static_cast<ProjectNode*>(pnode->child(idx));
|
||
|
||
QDomElement e = doc.createElement("package");
|
||
e.setAttribute("name", item->text());
|
||
|
||
if(item->nodeType() == NodeType::FILE){
|
||
e.setTagName("file");
|
||
e.setAttribute("path", item->file());
|
||
}
|
||
|
||
elm.appendChild(e);
|
||
structure_severlize(item, e);
|
||
}
|
||
}
|
||
|
||
QModelIndex XMLProjectManager::signFile(const QModelIndex &package_path, const QString &name, const QString &path_on_disk)
|
||
{
|
||
if(!package_path.isValid())
|
||
throw new Impl_ProjectException("参数错误", "指定的package_path无效");
|
||
|
||
auto group = static_cast<ProjectNode*>(project_structure->itemFromIndex(package_path));
|
||
if(group->nodeType() == NodeType::FILE)
|
||
throw new Impl_ProjectException("参数错误", "传入了文件逻辑路径,而不是包路径");
|
||
|
||
for(auto idx=0; idx<group->rowCount(); ++idx){
|
||
auto child = static_cast<ProjectNode*>(group->child(idx));
|
||
if(child->text() == name)
|
||
throw new Impl_ProjectException("参数错误", "传入了重复的节点名称:" + name);
|
||
}
|
||
|
||
if(all_files_managed.contains(path_on_disk))
|
||
throw new Impl_ProjectException("参数错误", "指定路径文件已经纳入项目管理:"+path_on_disk);
|
||
|
||
auto filenode = new ProjectNode(NodeType::FILE, name);
|
||
filenode->setFile(path_on_disk);
|
||
all_files_managed << path_on_disk;
|
||
|
||
group->appendRow(filenode);
|
||
|
||
return filenode->index();
|
||
}
|
||
|
||
void XMLProjectManager::rename(const QModelIndex &node, const QString &name)
|
||
{
|
||
auto item = this->model()->itemFromIndex(node);
|
||
|
||
auto parent = item->parent();
|
||
for(auto idx=0; idx<parent->rowCount(); ++idx)
|
||
if(item != parent->child(idx) && item->text() == parent->child(idx)->text())
|
||
throw new Impl_ProjectException("参数错误", "传入了重复的节点名称:" + name);
|
||
|
||
item->setText(name);
|
||
}
|
||
|
||
QList<QModelIndex> XMLProjectManager::filesGather(const QModelIndex &node) const
|
||
{
|
||
QList<QModelIndex> rets;
|
||
if(!node .isValid())
|
||
return rets;
|
||
|
||
auto item = static_cast<ProjectNode*>(project_structure->itemFromIndex(node));
|
||
switch (item->nodeType()) {
|
||
case NodeType::ROOT:
|
||
case NodeType::GROUP:
|
||
for(auto idx=0; idx<item->rowCount(); ++idx)
|
||
rets.append(filesGather(item->child(idx)->index()));
|
||
break;
|
||
case NodeType::FILE:
|
||
rets.append(item->index());
|
||
break;
|
||
}
|
||
return rets;
|
||
}
|
||
|
||
void XMLProjectManager::deleteTarget(const QModelIndex &node_path)
|
||
{
|
||
if(!node_path.isValid())
|
||
throw new Impl_ProjectException("参数错误", "指定的节点路径无效");
|
||
|
||
auto xnode = static_cast<ProjectNode*>(project_structure->itemFromIndex(node_path));
|
||
if(xnode->nodeType() == NodeType::ROOT)
|
||
throw new Impl_ProjectException("参数错误", "不允许删除项目节点");
|
||
|
||
xnode->parent()->removeRow(xnode->row());
|
||
}
|
||
|
||
QList<std::tuple<QString, QFileInfo> > XMLProjectManager::filesWithEnds(const QString &suffix) const
|
||
{
|
||
auto root_project = project_structure->item(0);
|
||
return nodes_search(static_cast<ProjectNode*>(root_project), suffix);
|
||
}
|
||
|
||
QModelIndex XMLProjectManager::newPackage(const QList<QString> &path)
|
||
{
|
||
auto pnode = project_structure->item(0);
|
||
return groups_rebuild(static_cast<ProjectNode*>(pnode), path);
|
||
}
|
||
|
||
QStringList XMLProjectManager::packagePath(const QModelIndex &path) const
|
||
{
|
||
auto item = static_cast<ProjectNode*>(project_structure->itemFromIndex(path));
|
||
|
||
auto project_node = project_structure->item(0);
|
||
auto query_path = [project_node](QStandardItem *it) -> QStringList{
|
||
QStringList values;
|
||
while (it != project_node) {
|
||
values.insert(0, it->text());
|
||
it = it->parent();
|
||
}
|
||
return values;
|
||
};
|
||
|
||
if(item->nodeType() == NodeType::GROUP)
|
||
return query_path(item);
|
||
else if(item->nodeType() == NodeType::FILE)
|
||
return query_path(item->parent());
|
||
else
|
||
return QStringList();
|
||
}
|
||
|
||
QFileInfo XMLProjectManager::queryInfo(const QModelIndex &path)
|
||
{
|
||
if(!path.isValid())
|
||
throw new Impl_ProjectException("参数错误", "指定的路径参数无效");
|
||
|
||
auto item = static_cast<ProjectNode*>(project_structure->itemFromIndex(path));
|
||
if(item->nodeType() == NodeType::GROUP){
|
||
return QFileInfo(QFileInfo(filepath_store).canonicalPath());
|
||
}
|
||
|
||
return QFileInfo(QFileInfo(filepath_store).dir().filePath(item->file()));
|
||
}
|
||
|
||
QModelIndex XMLProjectManager::queryIndex(const QFileInfo &file)
|
||
{
|
||
auto xinfo = query_index(file, project_structure->item(0)->index());
|
||
|
||
if(xinfo.isValid())
|
||
return xinfo;
|
||
|
||
return QModelIndex();
|
||
}
|
||
|
||
ProjectNode *XMLProjectManager::node_follows(ProjectNode *pnode, const QList<QString> &path_remains)
|
||
{
|
||
if(path_remains.size()==0)
|
||
return nullptr;
|
||
|
||
for(auto idx=0; idx<pnode->rowCount(); ++idx){
|
||
auto node = static_cast<ProjectNode*>(pnode->child(idx));
|
||
if(node->text() == path_remains[0]){
|
||
if(path_remains.size() == 1)
|
||
return node;
|
||
else
|
||
return node_follows(node, path_remains.mid(1));
|
||
}
|
||
}
|
||
|
||
return nullptr;
|
||
}
|
||
|
||
QList<std::tuple<QString, QFileInfo>> XMLProjectManager::nodes_search(ProjectNode *pnode, const QString &suffix) const
|
||
{
|
||
QDir root_dir = QFileInfo(this->filepath_store).absoluteDir();
|
||
QList<std::tuple<QString, QFileInfo>> infos_return;
|
||
|
||
for(auto idx=0; idx<pnode->rowCount(); ++idx){
|
||
auto item = static_cast<ProjectNode*>(pnode->child(idx));
|
||
|
||
if(item->nodeType() == NodeType::FILE){
|
||
auto file_path = root_dir.filePath(item->file());
|
||
QFileInfo xinfo(file_path);
|
||
if(xinfo.suffix() == suffix)
|
||
infos_return << std::make_tuple(item->text(), xinfo);
|
||
}
|
||
else{
|
||
auto elist = nodes_search(item, suffix);
|
||
infos_return.append(elist);
|
||
}
|
||
}
|
||
|
||
return infos_return;
|
||
}
|
||
|
||
QModelIndex XMLProjectManager::groups_rebuild(ProjectNode *pnode, const QList<QString> &path_remains)
|
||
{
|
||
// 查找对匹配结果
|
||
for(auto idx=0; idx<pnode->rowCount(); ++idx){
|
||
auto item = static_cast<ProjectNode*>(pnode->child(idx));
|
||
if(item->text() == path_remains[0] && item->nodeType() == NodeType::GROUP){
|
||
if(path_remains.size() > 1)
|
||
return groups_rebuild(item, path_remains.mid(1));
|
||
return item->index();
|
||
}
|
||
}
|
||
|
||
auto item = new ProjectNode(NodeType::GROUP, path_remains[0]);
|
||
pnode->appendRow(item);
|
||
|
||
if(path_remains.size() > 1)
|
||
return groups_rebuild(item, path_remains.mid(1));
|
||
|
||
return item->index();
|
||
}
|
||
|
||
QModelIndex XMLProjectManager::query_index(const QFileInfo &file, const QModelIndex &base)
|
||
{
|
||
if(!base.isValid())
|
||
return QModelIndex();
|
||
|
||
auto item = static_cast<ProjectNode*>(project_structure->itemFromIndex(base));
|
||
if(item->nodeType() == NodeType::FILE) {
|
||
auto dir = QFileInfo(filepath_store).absoluteDir();
|
||
auto target_info = QFileInfo(dir.filePath(item->file()));
|
||
if(target_info == file)
|
||
return item->index();
|
||
}
|
||
else {
|
||
for(auto idx=0; idx<item->rowCount(); ++idx)
|
||
{
|
||
auto idx_m = query_index(file, item->child(idx)->index());
|
||
if(idx_m.isValid())
|
||
return idx_m;
|
||
}
|
||
}
|
||
|
||
return QModelIndex();
|
||
}
|
||
|
||
|
||
void XMLProjectManager::moveTo(const QModelIndex &item_path, const QModelIndex &target_group, int index)
|
||
{
|
||
if(!item_path.isValid())
|
||
throw new Impl_ProjectException("参数错误", "传入的源文件路径无效");
|
||
|
||
if(!target_group.isValid())
|
||
throw new Impl_ProjectException("参数错误", "传入的目标包路径无效");
|
||
|
||
auto file = static_cast<ProjectNode*>(project_structure->itemFromIndex(item_path));
|
||
if(file->nodeType() == NodeType::GROUP)
|
||
throw new Impl_ProjectException("参数错误","传入的源文件节点路径无效");
|
||
|
||
auto group = static_cast<ProjectNode*>(project_structure->itemFromIndex(target_group));
|
||
if(group->nodeType() != NodeType::GROUP)
|
||
throw new Impl_ProjectException("参数错误", "传入的目标包路径无效");
|
||
|
||
if(index >= 0 && index < group->rowCount())
|
||
group->insertRow(index, file);
|
||
else
|
||
group->appendRow(file);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
// 完成线 =======================================================================================================
|
||
|
||
|
||
ProjectNode::ProjectNode(NodeType t, const QString &name)
|
||
: QStandardItem(name),type_indi(t)
|
||
{
|
||
if(t == NodeType::FILE)
|
||
{
|
||
setIcon(QIcon(":/icons/文件.png"));
|
||
}
|
||
else
|
||
{
|
||
setIcon(QIcon(":/icons/集合.png"));
|
||
}
|
||
|
||
setEditable(false);
|
||
}
|
||
|
||
NodeType ProjectNode::nodeType() const
|
||
{return type_indi;}
|
||
|
||
void ProjectNode::setFile(const QString &name)
|
||
{
|
||
this->filename = name;
|
||
|
||
if(name.endsWith("storychain"))
|
||
setIcon(QIcon(":/icons/脉络.png"));
|
||
if(name.endsWith("storyunit"))
|
||
setIcon(QIcon(":/icons/单元.png"));
|
||
if(name.endsWith("storyboard"))
|
||
setIcon(QIcon(":/icons/故事.png"));
|
||
if(name.endsWith("storyvolume"))
|
||
setIcon(QIcon(":/icons/叙述.png"));
|
||
if(name.endsWith("txt"))
|
||
setIcon(QIcon(":/icons/文件.png"));
|
||
if(name.endsWith("storyconcept"))
|
||
setIcon(QIcon(":/icons/概念.png"));
|
||
}
|
||
|
||
QString ProjectNode::file() const
|
||
{return filename;}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|