#include "xmlprojectmanager.h" #include #include #include #include #include #include #include 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::isOpenning() const noexcept { return open_status; } bool XMLProjectManager::isModified() const noexcept { return unsaved_status; } /* * * * * * * * */ 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; unsaved_status = false; pnode->setIcon(QIcon(":/icons/toplevel.png")); } 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")); } void XMLProjectManager::closeProject() { open_status = false; unsaved_status = false; project_structure->clear(); } void XMLProjectManager::save() { project_config->save(); QDomDocument doc; auto pnode = static_cast(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(); } QString XMLProjectManager::name() const { return project_structure->item(0)->text(); } void XMLProjectManager::resetName(const QString &name) { this->project_structure->item(0)->setText(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(this); } FilesQuery *XMLProjectManager::queryAccess() const { return const_cast(this); } void XMLProjectManager::structure_parser(QDomElement struct_elm, ProjectNode *pnode) { auto children = struct_elm.childNodes(); for(auto idx=0; idxsetFile(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; idxrowCount(); ++idx){ auto item = static_cast(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; idxrowCount(); ++idx) if(item != parent->child(idx) && item->text() == parent->child(idx)->text()) throw new Impl_ProjectException("参数错误", "传入了重复的节点名称:" + name); unsaved_status = true; item->setText(name); } QList XMLProjectManager::filesGather(const QModelIndex &node) const { QList rets; if(!node .isValid()) return rets; auto item = static_cast(project_structure->itemFromIndex(node)); switch (item->nodeType()) { case NodeType::ROOT: case NodeType::GROUP: for(auto idx=0; idxrowCount(); ++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(project_structure->itemFromIndex(node_path)); if(xnode->nodeType() == NodeType::ROOT) throw new Impl_ProjectException("参数错误", "不允许删除项目节点"); xnode->parent()->removeRow(xnode->row()); unsaved_status = true; } QList > XMLProjectManager::filesWithEnds(const QString &suffix) const { auto root_project = project_structure->item(0); return nodes_search(static_cast(root_project), suffix); } QModelIndex XMLProjectManager::newPackage(const QList &path) { unsaved_status = true; auto pnode = project_structure->item(0); return groups_rebuild(static_cast(pnode), path); } QStringList XMLProjectManager::packagePath(const QModelIndex &path) const { auto item = static_cast(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(model()->itemFromIndex(package_path)); if (node->nodeType() == NodeType::FILE) { node = static_cast(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; return leaf->index(); } QFileInfo XMLProjectManager::queryInfo(const QModelIndex &path) { if(!path.isValid()) throw new Impl_ProjectException("参数错误", "指定的路径参数无效"); auto item = static_cast(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 &path_remains) { if(path_remains.size()==0) return nullptr; for(auto idx=0; idxrowCount(); ++idx){ auto node = static_cast(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> XMLProjectManager::nodes_search(ProjectNode *pnode, const QString &suffix) const { QDir root_dir = QFileInfo(this->filepath_store).absoluteDir(); QList> infos_return; for(auto idx=0; idxrowCount(); ++idx){ auto item = static_cast(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 &path_remains) { // 查找对匹配结果 for(auto idx=0; idxrowCount(); ++idx){ auto item = static_cast(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(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; idxrowCount(); ++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 node = static_cast(project_structure->itemFromIndex(item_path)); auto group = static_cast(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; } // 完成线 ======================================================================================================= 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("storyvolume")) // setIcon(QIcon(":/icons/叙述.png")); // if(name.endsWith("storyconcept")) // setIcon(QIcon(":/icons/概念.png")); if(name.endsWith("storyboard")) setIcon(QIcon(":/icons/故事.png")); if (name.endsWith("txt")) setIcon(QIcon(":/icons/文件.png")); } QString ProjectNode::file() const {return filename;}