From fa4e79dc862e10ff97f5a51d6c0241a3d1224bb5 Mon Sep 17 00:00:00 2001 From: LuChiChick <1084116302@qq.com> Date: Fri, 24 Apr 2026 17:07:07 +0800 Subject: [PATCH] Implemented the copy, paste, and cut functions in the workspace, with corresponding shortcut key responses. The clipboard functionality is implemented internally and does not interact with the operating system for now. --- Inc/Workflow_Editor.hpp | 10 ++ Src/Workflow_Editor.cpp | 346 +++++++++++++++++++++++++--------------- 2 files changed, 228 insertions(+), 128 deletions(-) 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())