diff --git a/Inc/Workflow_Editor.hpp b/Inc/Workflow_Editor.hpp index ad09ace..767416d 100644 --- a/Inc/Workflow_Editor.hpp +++ b/Inc/Workflow_Editor.hpp @@ -7,6 +7,7 @@ #include "imnodes.h" #include "ID_Generator.hpp" #include "Nodes_And_Connectors.hpp" +#include "Json_Utilities.hpp" // 节点编辑器类 class Workflow_Editor @@ -29,6 +30,9 @@ protected: std::vector> Node_Pool; // 节点池 std::vector Edge_Pool; // 边池 + // 配置剪切板 + Json_Object Config_Clipboard; + /** * 顶层索引为起始工作流节点(N个元素即N个处理流程) * 次级索引为工作序列,表示完成一个处理流程的非冲突序列 @@ -41,6 +45,12 @@ protected: // 删除边 返回迭代器指定位置的下一个位置的迭代器,仅限内部使用 std::vector::iterator Del_Link(std::vector::iterator iterator); + // 从节点和边列表构建配置对象 + Json_Object Generate_Config(std::vector> Node_List, std::vector Edge_List); + + // 从配置对象应用 true -> success / false -> failed + bool Apply_Config(Json_Object Config_Object, ImVec2 initial_position = ImVec2(0, 0)); + public: // 绘制节点编辑器 void Show(void); diff --git a/Src/Workflow_Editor.cpp b/Src/Workflow_Editor.cpp index d7ff68d..e4bdd6a 100644 --- a/Src/Workflow_Editor.cpp +++ b/Src/Workflow_Editor.cpp @@ -1,7 +1,6 @@ #include "Workflow_Editor.hpp" #include "imgui_internal.h" #include "Global_Variables.hpp" -#include "Json_Utilities.hpp" extern "C" { @@ -12,12 +11,14 @@ extern "C" void Workflow_Editor::Show(void) { // 选区信息 - bool bounding_box_delete_flag = false; int bounding_box_link_nums = ImNodes::NumSelectedLinks(); int bounding_box_node_nums = ImNodes::NumSelectedNodes(); - // 加载配置文件标志位 - bool Config_Load_Flag = false; + // 操作标志位 + bool config_load_flag = false; // 加载配置文件 + bool clipboard_copy_flag = false; // 复制到剪切板 + bool clipboard_paste_flag = false; // 粘贴剪切板内容 + bool bounding_box_delete_flag = false; // 删除选区内容 // 浅色主题节点编辑器窗体 ImNodes::StyleColorsLight(); @@ -65,8 +66,17 @@ void Workflow_Editor::Show(void) ImGui::EndMenu(); } + // 复制框选内容 + if (ImGui::MenuItem(u8"复制 Ctrl + C", nullptr, nullptr, (bounding_box_link_nums > 0) || (bounding_box_node_nums > 0))) + clipboard_copy_flag = true; + // 剪切框选内容 + if (ImGui::MenuItem(u8"剪切 Ctrl + X", nullptr, nullptr, (bounding_box_link_nums > 0) || (bounding_box_node_nums > 0))) + clipboard_copy_flag = bounding_box_delete_flag = true; + // 粘贴框选内容 + if (ImGui::MenuItem(u8"粘贴 Ctrl + V", nullptr, nullptr, !this->Config_Clipboard.isNull())) + clipboard_paste_flag = true; // 删除框选内容 - if (ImGui::MenuItem(u8"删除所选内容", nullptr, nullptr, (bounding_box_link_nums > 0) || (bounding_box_node_nums > 0))) + if (ImGui::MenuItem(u8"删除 Delete", nullptr, nullptr, (bounding_box_link_nums > 0) || (bounding_box_node_nums > 0))) bounding_box_delete_flag = true; if (ImGui::MenuItem(u8"构建工作树(Debug)")) @@ -77,7 +87,7 @@ void Workflow_Editor::Show(void) this->Store_Config("[Debug]Config.Json"); if (ImGui::MenuItem(u8"加载工作区(Debug)")) - Config_Load_Flag = true; + config_load_flag = true; ImGui::EndPopup(); } @@ -94,7 +104,7 @@ void Workflow_Editor::Show(void) ImNodes::EndNodeEditor(); // 处理配置文件加载 - if (Config_Load_Flag == true) + if (config_load_flag == true) this->Load_Config("[Debug]Config.Json", ImGui::GetMousePos()); // 处理连线 @@ -120,12 +130,64 @@ void Workflow_Editor::Show(void) // Ctrl + X 剪切 if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_X) == true) { + clipboard_copy_flag = true; bounding_box_delete_flag = true; + } - /** - * @todo - * 剪切板实现 - */ + // Ctrl + C 复制到剪切板 + if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_C) || clipboard_copy_flag) + { + // 获取框选的节点 + std::vector> Target_Node_List; + { + std::vector Target_ID_List; + Target_ID_List.resize(ImNodes::NumSelectedNodes()); + if (Target_ID_List.size() > 0) + ImNodes::GetSelectedNodes(&Target_ID_List[0]); + + for (auto ID : Target_ID_List) + for (auto pNode : this->Node_Pool) + if (pNode->Get_ID() == ID) + Target_Node_List.push_back(pNode); + } + // 获取框选的边 + std::vector Target_Edge_List; + { + std::vector Target_Edge_ID_List; + Target_Edge_ID_List.resize(ImNodes::NumSelectedLinks()); + if (Target_Edge_ID_List.size() > 0) + ImNodes::GetSelectedLinks(&Target_Edge_ID_List[0]); + + for (auto ID : Target_Edge_ID_List) + for (auto Edge : this->Edge_Pool) + if (Edge.id == ID) + Target_Edge_List.push_back(Edge); + } + + // 写入剪切板 + this->Config_Clipboard = Generate_Config(Target_Node_List, Target_Edge_List); + } + + // Ctrl + V 粘贴 + if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_V) || clipboard_paste_flag) + { + this->Apply_Config(this->Config_Clipboard, ImGui::GetMousePos()); + } + + /** + * @todo 实现历史记录、重做功能 + * 可能的做法是使用配置数组方式实现 + * 但节点内选项更新可能需要向外暴露状态更新接口 + */ + + // Ctrl + Z 撤销 + if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_Z)) + { + } + + // Ctrl + Y 撤销 + if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_Y)) + { } // Delete 按键释放 或有删除请求 @@ -606,38 +668,126 @@ void Workflow_Editor::Clear_All(void) this->Clear_Process_Route(); } -// 加载取配置文件 true -> success / false -> failed -bool Workflow_Editor::Load_Config(const char *file_path, ImVec2 initial_position) +// 从节点列表构建配置对象 +Json_Object Workflow_Editor::Generate_Config(std::vector> Node_List, std::vector Edge_List) { - // 工具Lambda string 转 wstring - auto Lambda_String_To_WString = [](const std::string &str) -> std::wstring + std::vector Node_Info_List; + std::vector Link_Info_List; + + // 工具Lambda表达式,查找接口id的所属节点 + auto Lambda_Find_Owner = [&](int connector_id) -> std::shared_ptr { - if (str.empty()) - return std::wstring(); - int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0); - if (len <= 0) - return std::wstring(); - std::wstring wstr(len, L'\0'); - MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), &wstr[0], len); - return wstr; + for (auto pNode : this->Node_Pool) + { + for (auto pConnector : pNode->Get_Connector_List()) + { + if (pConnector->Get_ID() == connector_id) + return pNode; + } + } + + return nullptr; }; - Json_Value Config_Value; - std::string err_str = JsonFile_Parse(file_path, &Config_Value); - - if (!err_str.empty()) + // 遍历节点池,以遍历计数作为存储相对序列索引 + size_t Node_Count = Node_List.size(); + for (size_t node_index = 0; node_index < Node_Count; node_index++) { - MessageBoxW(Main_Window_hWnd, (L"文件解析错误!!\n" + Lambda_String_To_WString(err_str)).c_str(), L"", MB_OK | MB_ICONERROR); - return false; + // 存储节点相关信息 + ImVec2 position = ImNodes::GetNodeGridSpacePos(Node_List[node_index]->Get_ID()); + Node_Info_List.push_back( + Json_Object({{"index", (int)node_index}, // + {"type", Node_List[node_index]->Get_Type()}, // + { + "position", Json_Object({ + {"x", position.x}, // + {"y", position.y} // + }) // + }, // + {"args", Json_Arry()}} // + )); + + // 查询是有相关联的连接 + for (auto edge : Edge_List) + { + // 存在关联连接时 + if (Lambda_Find_Owner(edge.source_connector_id) == Node_List[node_index]) + { + + // 检查目标节点是否在传入的节点列表内 + bool find_target = false; + auto pTarget_Node = Lambda_Find_Owner(edge.target_connector_id); + for (auto pNode : Node_List) + if (pNode == pTarget_Node) + { + find_target = true; + break; + } + + // 源、目标节点均找到时 + if (find_target) + { + int target_node_index = -1; // 目标节点索引 + int source_connector_index = -1; // 源接口索引 + int target_connector_index = -1; // 目标接口索引 + + // 查询源接口在源节点的索引 + size_t list_size = Node_List[node_index]->Get_Connector_List().size(); + for (size_t count = 0; count < list_size; count++) + if (Node_List[node_index]->Get_Connector_List()[count]->Get_ID() == edge.source_connector_id) + { + source_connector_index = count; + break; + } + + // 查询目标节点索引 + for (size_t count = 0; count < Node_Count; count++) + if (Node_List[count] == pTarget_Node) + { + target_node_index = count; + break; + } + + // 查询目标接口在目标节点的索引 + list_size = pTarget_Node->Get_Connector_List().size(); + for (size_t count = 0; count < list_size; count++) + if (pTarget_Node->Get_Connector_List()[count]->Get_ID() == edge.target_connector_id) + { + target_connector_index = count; + break; + } + + Link_Info_List.push_back( + Json_Object( + { + {"source", Json_Object({ + {"owner", (int)node_index}, // + {"index", source_connector_index}, // + })}, // + {"target", Json_Object({ + {"owner", target_node_index}, // + {"index", target_connector_index}, // + })} // + })); + } + } + } } - // 清理工作区 - this->Clear_All(); + return Json_Object({ + {"Nodes", Json_Arry(Node_Info_List)}, // + {"Connections", Json_Arry(Link_Info_List)} // + }); +} + +// 从配置对象应用 true -> success / false -> failed +bool Workflow_Editor::Apply_Config(Json_Object Config_Object, ImVec2 initial_position) +{ decltype(this->Node_Pool) Created_Node_List; // 创建节点 { - auto Node_Info_List = Config_Value.asObject().get("Nodes").asArry().element_list; + auto Node_Info_List = Config_Object.get("Nodes").asArry().element_list; // 计算中心坐标 ImVec2 Position_Offset = {0, 0}; @@ -670,7 +820,7 @@ bool Workflow_Editor::Load_Config(const char *file_path, ImVec2 initial_position // 创建连接关系 { - auto Link_Info_List = Config_Value.asObject().get("Connections").asArry().element_list; + auto Link_Info_List = Config_Object.get("Connections").asArry().element_list; for (auto Link_Info : Link_Info_List) { auto Source_Info = Link_Info.asObject().get("source").asObject(); @@ -686,9 +836,42 @@ bool Workflow_Editor::Load_Config(const char *file_path, ImVec2 initial_position return true; } +// 加载取配置文件 true -> success / false -> failed +bool Workflow_Editor::Load_Config(const char *file_path, ImVec2 initial_position) +{ + // 工具Lambda string 转 wstring + auto Lambda_String_To_WString = [](const std::string &str) -> std::wstring + { + if (str.empty()) + return std::wstring(); + int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0); + if (len <= 0) + return std::wstring(); + std::wstring wstr(len, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), &wstr[0], len); + return wstr; + }; + + Json_Value Config_Value; + std::string err_str = JsonFile_Parse(file_path, &Config_Value); + + if (!err_str.empty()) + { + MessageBoxW(Main_Window_hWnd, (L"文件解析错误!!\n" + Lambda_String_To_WString(err_str)).c_str(), L"", MB_OK | MB_ICONERROR); + return false; + } + + // 清理工作区 + this->Clear_All(); + + // 应用配置 + return Apply_Config(Config_Value.asObject(), initial_position); +} + // 存储配置文件 true -> success / false -> failed bool Workflow_Editor::Store_Config(const char *file_path) { + // 打开或创建文件 FILE *pFile = fopen(file_path, "wb"); if (pFile == nullptr) { @@ -697,101 +880,8 @@ bool Workflow_Editor::Store_Config(const char *file_path) } fclose(pFile); - std::vector Node_Info_List; - std::vector Link_Info_List; - - // 工具Lambda表达式,查找接口id的所属节点 - auto Lambda_Find_Owner = [&](int connector_id) -> std::shared_ptr - { - for (auto pNode : this->Node_Pool) - { - for (auto pConnector : pNode->Get_Connector_List()) - { - if (pConnector->Get_ID() == connector_id) - return pNode; - } - } - - return nullptr; - }; - - // 遍历节点池,以遍历计数作为存储相对序列索引 - size_t Node_Count = this->Node_Pool.size(); - for (size_t node_index = 0; node_index < Node_Count; node_index++) - { - // 存储节点相关信息 - ImVec2 position = ImNodes::GetNodeGridSpacePos(this->Node_Pool[node_index]->Get_ID()); - Node_Info_List.push_back( - Json_Object({{"index", (int)node_index}, // - {"type", this->Node_Pool[node_index]->Get_Type()}, // - { - "position", Json_Object({ - {"x", position.x}, // - {"y", position.y} // - }) // - }, // - {"args", Json_Arry()}} // - )); - - // 查询是有相关联的连接 - for (auto edge : this->Edge_Pool) - { - // 存在关联连接时 - if (Lambda_Find_Owner(edge.source_connector_id) == this->Node_Pool[node_index]) - { - int target_node_index = -1; // 目标节点索引 - int source_connector_index = -1; // 源接口索引 - int target_connector_index = -1; // 目标接口索引 - - // 查询源接口在源节点的索引 - size_t list_size = this->Node_Pool[node_index]->Get_Connector_List().size(); - for (size_t count = 0; count < list_size; count++) - if (this->Node_Pool[node_index]->Get_Connector_List()[count]->Get_ID() == edge.source_connector_id) - { - source_connector_index = count; - break; - } - - // 查询目标节点索引 - auto pTarget_Node = Lambda_Find_Owner(edge.target_connector_id); - for (size_t count = 0; count < Node_Count; count++) - if (this->Node_Pool[count] == pTarget_Node) - { - target_node_index = count; - break; - } - - // 查询目标接口在目标节点的索引 - list_size = pTarget_Node->Get_Connector_List().size(); - for (size_t count = 0; count < list_size; count++) - if (pTarget_Node->Get_Connector_List()[count]->Get_ID() == edge.target_connector_id) - { - target_connector_index = count; - break; - } - - Link_Info_List.push_back( - Json_Object( - { - {"source", Json_Object({ - {"owner", (int)node_index}, // - {"index", source_connector_index}, // - })}, // - {"target", Json_Object({ - {"owner", target_node_index}, // - {"index", target_connector_index}, // - })} // - })); - } - } - } - - std::string err_str = Json_Export(file_path, - Json_Object({ - {"Nodes", Json_Arry(Node_Info_List)}, // - {"Connections", Json_Arry(Link_Info_List)} // - }), - true); + // 导出配置信息 + std::string err_str = Json_Export(file_path, Generate_Config(this->Node_Pool, this->Edge_Pool), true); // 输出错误信息 if (!err_str.empty())