QtNovelUI/libProjectManager/xmlprojectmanager.cpp

583 lines
17 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 noexcept 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::isOpenning() const noexcept { return open_status; }
/*
* <?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("项目文件格式错误:%1row%2col%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;
unsaved_status = false;
pnode->setIcon(QIcon(":/icons/toplevel.png"));
notify_all([&](ManagerListener *it){it->ProjectOPEN(project_file);});
}
void XMLProjectManager::newProject(const QDir &project_dir, const QString &name) {
// 确定目标项目文件
filepath_store = project_dir.filePath("novel.nsf");
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 = info.absoluteDir();
project_config->loadFile(root.filePath(project_node->file()));
// 写出到磁盘
save();
open_status = true;
project_node->setIcon(QIcon(":/icons/toplevel.png"));
notify_all([&](ManagerListener *it){it->ProjectNEW(filepath_store);});
}
void XMLProjectManager::closeProject()
{
notify_all([&](ManagerListener *it){it->aboutToBeCLOSE();});
open_status = false;
unsaved_status = false;
project_structure->clear();
notify_all([&](ManagerListener *it){it->hasBeenCLOSE();});
}
void XMLProjectManager::save()
{
notify_all([&](ManagerListener *it){it->aboutToBeSAVE();});
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);
unsaved_status = false;
QTextStream txout(&records);
doc.save(txout, 4);
txout.flush();
notify_all([&](ManagerListener *it){it->hasBeenSAVE();});
}
QString XMLProjectManager::name() const { return project_structure->item(0)->text(); }
void XMLProjectManager::resetName(const QString &name) {
this->project_structure->item(0)->setText(name);
notify_all([&](ManagerListener *it){it->hasBeenRENAME(this->project_structure->item(0)->index(), name);});
}
Configration *XMLProjectManager::configraions() const
{
return this->project_config;
}
QDir XMLProjectManager::directory() const
{
return QFileInfo(filepath_store).absoluteDir();
}
FilesOperate *XMLProjectManager::operateAccess() const { return const_cast<XMLProjectManager *>(this); }
FilesQuery *XMLProjectManager::queryAccess() const { return const_cast<XMLProjectManager *>(this); }
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"));
} else {
throw new Impl_ProjectException("项目解析错误", "未对指定节点类型进行解析:" + xnode.tagName());
}
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);
}
}
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);
unsaved_status = true;
item->setText(name);
notify_all([&](ManagerListener *it){it->hasBeenRENAME(node, 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::deleteT(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("参数错误", "不允许删除项目节点");
notify_all([&](ManagerListener *it){it->aboutToBeDELETE(node_path);});
xnode->parent()->removeRow(xnode->row());
unsaved_status = true;
}
void XMLProjectManager::addListener(ManagerListener *inst)
{
if(!this->listeners_list.contains(inst))
this->listeners_list.append(inst);
}
void XMLProjectManager::removeListener(ManagerListener *inst)
{
if(this->listeners_list.contains(inst))
this->listeners_list.removeAll(inst);
}
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)
{
unsaved_status = true;
auto pnode = project_structure->item(0);
auto nidx = groups_rebuild(static_cast<ProjectNode*>(pnode), path);
notify_all([&](ManagerListener *it){it->hasBeenAPPEND(nidx);});
return nidx;
}
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();
}
QModelIndex XMLProjectManager::newFile(const QModelIndex &package_path, const QString &name, const QString &suffix) {
auto node = static_cast<ProjectNode *>(model()->itemFromIndex(package_path));
if (node->nodeType() == NodeType::FILE) {
node = static_cast<ProjectNode *>(node->parent());
}
for (auto idx = 0; idx < node->rowCount(); ++idx) {
auto current = node->child(idx);
if (current->text() == name) {
throw new Impl_ProjectException("新建文件错误", "该路径下指定节点名称已存在,不可重复命名:" + name);
}
}
auto dir = directory();
auto filenames = dir.entryList(QDir::Filter::Files, QDir::SortFlag::Name);
QRandomGenerator gen(QDateTime::currentDateTime().secsTo(QDateTime(QDate(1992, 12, 04))));
auto target_file = "file_zero." + suffix;
while (filenames.contains(target_file)) {
target_file = QString("file_%1.%2").arg(gen.generate64()).arg(suffix);
}
QFile target_f(dir.filePath(target_file));
if (!target_f.open(QIODevice::WriteOnly))
throw new Impl_ProjectException("新建文件错误", "指定路径无法创建文件:" + dir.filePath(target_file));
target_f.write("空白文件");
target_f.flush();
target_f.close();
auto leaf = new ProjectNode(NodeType::FILE, name);
leaf->setFile(target_file);
node->appendRow(leaf);
unsaved_status = true;
auto nidx = leaf->index();
notify_all([&](ManagerListener *it){it->hasBeenAPPEND(nidx);});
return nidx;
}
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::notify_all(std::function<void (ManagerListener *)> proc)
{
for(auto l : listeners_list)
proc(l);
}
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("参数错误", "传入的目标包路径无效");
notify_all([&](ManagerListener *it){it->aboutToBeMOVE(item_path);});
auto node = static_cast<ProjectNode *>(project_structure->itemFromIndex(item_path));
auto group = static_cast<ProjectNode*>(project_structure->itemFromIndex(target_group));
if(group->nodeType() != NodeType::GROUP)
throw new Impl_ProjectException("参数错误", "传入的目标包路径无效");
QStandardItem *tgroup = group;
while (tgroup != nullptr) {
if (tgroup == node)
throw new Impl_ProjectException("参数错误", "无法将父节点移动到子节点下");
tgroup = tgroup->parent();
}
if(index >= 0 && index < group->rowCount())
group->insertRow(index, node);
else
group->appendRow(node);
unsaved_status = true;
notify_all([&](ManagerListener *it){it->hasBeenMOVE(node->index());});
}
// 完成线 =======================================================================================================
ProjectNode::ProjectNode(NodeType t, const QString &name)
: QStandardItem(name),type_indi(t)
{
if(t == NodeType::FILE)
{
setIcon(QIcon(":/icons/file.png"));
}
else
{
setIcon(QIcon(":/icons/sets.png"));
}
setEditable(false);
}
NodeType ProjectNode::nodeType() const
{return type_indi;}
void ProjectNode::setFile(const QString &name)
{
this->filename = name;
if(name.endsWith("storyboard"))
setIcon(QIcon(":/icons/story.png"));
if (name.endsWith("txt"))
setIcon(QIcon(":/icons/file.png"));
}
QString ProjectNode::file() const
{return filename;}