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.

This commit is contained in:
LuChiChick 2026-04-24 17:07:07 +08:00
parent e0afc4963f
commit fa4e79dc86
2 changed files with 228 additions and 128 deletions

View File

@ -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<std::shared_ptr<Abs_Node>> Node_Pool; // 节点池
std::vector<Edge> Edge_Pool; // 边池
// 配置剪切板
Json_Object Config_Clipboard;
/**
* N个元素即N个处理流程
*
@ -41,6 +45,12 @@ protected:
// 删除边 返回迭代器指定位置的下一个位置的迭代器,仅限内部使用
std::vector<Edge>::iterator Del_Link(std::vector<Edge>::iterator iterator);
// 从节点和边列表构建配置对象
Json_Object Generate_Config(std::vector<std::shared_ptr<Abs_Node>> Node_List, std::vector<Edge> Edge_List);
// 从配置对象应用 true -> success / false -> failed
bool Apply_Config(Json_Object Config_Object, ImVec2 initial_position = ImVec2(0, 0));
public:
// 绘制节点编辑器
void Show(void);

View File

@ -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<std::shared_ptr<Abs_Node>> Target_Node_List;
{
std::vector<int> 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<Edge> Target_Edge_List;
{
std::vector<int> 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<std::shared_ptr<Abs_Node>> Node_List, std::vector<Edge> Edge_List)
{
// 工具Lambda string 转 wstring
auto Lambda_String_To_WString = [](const std::string &str) -> std::wstring
std::vector<Json_Value> Node_Info_List;
std::vector<Json_Value> Link_Info_List;
// 工具Lambda表达式查找接口id的所属节点
auto Lambda_Find_Owner = [&](int connector_id) -> std::shared_ptr<Abs_Node>
{
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<Json_Value> Node_Info_List;
std::vector<Json_Value> Link_Info_List;
// 工具Lambda表达式查找接口id的所属节点
auto Lambda_Find_Owner = [&](int connector_id) -> std::shared_ptr<Abs_Node>
{
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())