diff --git a/CMakeLists.txt b/CMakeLists.txt index fdceb7a..c45fb7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,11 +12,15 @@ set(CMAKE_C_EXTENSIONS OFF) # 禁用特定编译器的扩展(如 gnu17), # 定义源文件 set(SOURCES src/main.c - src/utils.c - src/registry.c - src/callbacks.c - src/ui.c - src/globals.c + src/utils/string_ext.c + src/utils/os_env.c + src/ui/ui_utils.c + src/ui/dialogs.c + src/ui/main_window.c + src/core/registry_service.c + src/core/path_manager.c + src/core/app_context.c + src/controller/callbacks.c ico/resources.rc ) @@ -42,6 +46,10 @@ endif() # 设置头文件搜索路径 target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include/core + ${CMAKE_SOURCE_DIR}/include/ui + ${CMAKE_SOURCE_DIR}/include/controller + ${CMAKE_SOURCE_DIR}/include/utils ${CMAKE_SOURCE_DIR}/libs/IUP/include ) diff --git a/README.md b/README.md index 7ae50ca..49ead58 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,15 @@ ## 🏗️ 架构与二次开发 -本项目注重代码的模块化和可维护性,非常适合作为 C 语言桌面程序开发的参考: +本项目注重代码的模块化和可维护性,采用了经典的 **MVC 分层架构**,非常适合作为 C 语言桌面程序开发的参考: +* **分层设计**: + * `src/core/` (Model): 核心数据与业务逻辑,完全脱离 UI 框架(无任何 `` 依赖)。 + * `src/ui/` (View): 负责界面布局与组件的纯视觉展示。 + * `src/controller/` (Controller): 负责连接用户交互与底层数据。 + * `src/utils/` (Utils): 纯粹的底层工具类封装(系统级调用、字符串处理)。 * **统一配置中心**:所有的 UI 尺寸、间距、颜色等常量配置均提取在 `include/config.h` 中,只需修改宏定义即可轻松定制属于你的专属界面风格。 -* **清晰的全局状态**:全局变量和常量被独立分离在 `src/globals.c` / `include/globals.h` 中管理,使得核心业务逻辑更加整洁。 +* **清晰的应用状态**:摒弃了脆弱的全局变量模式,采用 `AppContext` 统一管理应用运行时的上下文状态,通过指针传递,安全可靠。 ## 📦 下载与安装 @@ -82,8 +87,6 @@ 3. 运行: 编译成功后,可执行文件位于 `build/PathEditor.exe`。 -*(注:项目依然保留了传统的 `mingw32-make` 方式,您可以直接在根目录运行 `mingw32-make` 进行编译,产物位于 `bin/` 目录。)* - ### 打包 (可选) 本项目使用 Inno Setup 生成安装包。 diff --git a/include/callbacks.h b/include/controller/callbacks.h similarity index 92% rename from include/callbacks.h rename to include/controller/callbacks.h index f3c9009..47b22ab 100644 --- a/include/callbacks.h +++ b/include/controller/callbacks.h @@ -27,4 +27,7 @@ int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y // 键盘按键回调 int list_k_any_cb(Ihandle *self, int c); +// 载入数据与更新UI +void load_all_paths(void); + #endif // CALLBACKS_H \ No newline at end of file diff --git a/include/core/app_context.h b/include/core/app_context.h new file mode 100644 index 0000000..c77beee --- /dev/null +++ b/include/core/app_context.h @@ -0,0 +1,22 @@ +#ifndef APP_CONTEXT_H +#define APP_CONTEXT_H + +#include "utils/string_ext.h" +#include + +// 应用上下文结构体,用于存储应用运行时的状态 +typedef struct { + StringList sys_paths; + StringList user_paths; +} AppContext; + +// 创建应用上下文 +AppContext* create_app_context(void); + +// 销毁应用上下文 +void destroy_app_context(AppContext* ctx); + +// 获取应用上下文 +AppContext* get_app_context(Ihandle *ih); + +#endif // APP_CONTEXT_H diff --git a/include/core/path_manager.h b/include/core/path_manager.h new file mode 100644 index 0000000..ce3724f --- /dev/null +++ b/include/core/path_manager.h @@ -0,0 +1,19 @@ +#ifndef PATH_MANAGER_H +#define PATH_MANAGER_H + +#include "utils/string_ext.h" + +// 移除列表中指定索引的项 +void path_manager_remove_at(StringList *list, int index); + +// 上移指定索引的项 +void path_manager_move_up(StringList *list, int index); + +// 下移指定索引的项 +void path_manager_move_down(StringList *list, int index); + +// 清理无效和重复的路径 +// 返回被清理的项数 +int path_manager_clean(StringList *list); + +#endif // PATH_MANAGER_H diff --git a/include/core/registry_service.h b/include/core/registry_service.h new file mode 100644 index 0000000..108c2d8 --- /dev/null +++ b/include/core/registry_service.h @@ -0,0 +1,14 @@ +#ifndef REGISTRY_SERVICE_H +#define REGISTRY_SERVICE_H + +#include "utils/string_ext.h" + +// 加载系统变量和用户变量到字符串列表 +int load_system_paths(StringList *list); +int load_user_paths(StringList *list); + +// 从字符串列表保存系统变量和用户变量 +int save_system_paths(const StringList *list); +int save_user_paths(const StringList *list); + +#endif // REGISTRY_SERVICE_H diff --git a/include/globals.h b/include/globals.h deleted file mode 100644 index cb102b7..0000000 --- a/include/globals.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef GLOBALS_H -#define GLOBALS_H - -#include - -// 定义 Windows 消息常量 -#ifndef WM_COPYGLOBALDATA -#define WM_COPYGLOBALDATA 0x0049 -#endif - -// 消息过滤器常量 -#ifndef MSGFLT_ADD -#define MSGFLT_ADD 1 -#endif - -// 注册表路径常量 -#define REG_PATH_SYS L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment" -#define REG_PATH_USER L"Environment" -#define REG_VALUE L"Path" - -// 全局控件句柄声明 -extern Ihandle *dlg; // 主对话框句柄 -extern Ihandle *tabs_main; // 标签页容器 -extern Ihandle *list_sys; // 系统变量列表 -extern Ihandle *list_user; // 用户变量列表 -extern Ihandle *lbl_status; // 状态标签句柄 -extern Ihandle *btn_new; // 新增按钮句柄 -extern Ihandle *btn_edit; // 编辑按钮句柄 -extern Ihandle *btn_browse; // 浏览按钮句柄 -extern Ihandle *btn_del; // 删除按钮句柄 -extern Ihandle *btn_up; // 上移按钮句柄 -extern Ihandle *btn_down; // 下移按钮句柄 -extern Ihandle *btn_clean; // 一键清理按钮句柄 -extern Ihandle *btn_ok; // 确认按钮句柄 -extern Ihandle *btn_cancel; // 取消按钮句柄 -extern Ihandle *btn_help; // 帮助按钮句柄 -extern Ihandle *txt_search; // 搜索框 - -// 简单字符串列表结构,用于搜索缓存 -typedef struct { - char **items; - int count; - int capacity; -} StringList; - -extern StringList raw_sys_paths; -extern StringList raw_user_paths; - -// 缓存操作函数声明 -void init_string_list(StringList *list); -void add_string_list(StringList *list, const char *str); -void clear_string_list(StringList *list); - -#endif // GLOBALS_H \ No newline at end of file diff --git a/include/registry.h b/include/registry.h deleted file mode 100644 index c42d7bf..0000000 --- a/include/registry.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef REGISTRY_H -#define REGISTRY_H - -// 从注册表加载所有PATH到列表控件 -void load_all_paths(); - -// 将列表控件中的PATH保存回注册表 -void save_all_paths(); - -#endif // REGISTRY_H \ No newline at end of file diff --git a/include/ui.h b/include/ui.h deleted file mode 100644 index e894714..0000000 --- a/include/ui.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef UI_H -#define UI_H - -#include - -// 创建列表控件 -Ihandle *create_path_list(); - -// 创建右侧功能按钮区域 -Ihandle *create_main_buttons(); - -// 创建底部按钮区域 -Ihandle *create_bottom_buttons(); - -#endif // UI_H \ No newline at end of file diff --git a/include/ui/dialogs.h b/include/ui/dialogs.h new file mode 100644 index 0000000..6a25dc5 --- /dev/null +++ b/include/ui/dialogs.h @@ -0,0 +1,8 @@ +#ifndef DIALOGS_H +#define DIALOGS_H + +// 自定义输入对话框 +// 返回值:0-取消,1-确认 +int custom_input_dialog(const char *title, const char *label_text, char *buffer, int buffer_size); + +#endif // DIALOGS_H diff --git a/include/ui/main_window.h b/include/ui/main_window.h new file mode 100644 index 0000000..b2f3629 --- /dev/null +++ b/include/ui/main_window.h @@ -0,0 +1,9 @@ +#ifndef MAIN_WINDOW_H +#define MAIN_WINDOW_H + +#include + +// 创建主窗口 +Ihandle* create_main_window(void); + +#endif // MAIN_WINDOW_H diff --git a/include/ui/ui_utils.h b/include/ui/ui_utils.h new file mode 100644 index 0000000..edaebbe --- /dev/null +++ b/include/ui/ui_utils.h @@ -0,0 +1,13 @@ +#ifndef UI_UTILS_H +#define UI_UTILS_H + +#include +#include "utils/string_ext.h" + +// 刷新单个列表框样式 +void refresh_single_list_style(Ihandle *list); + +// 同步字符串列表到 UI 列表框 +void sync_string_list_to_ui(Ihandle *list_ui, const StringList *str_list); + +#endif // UI_UTILS_H diff --git a/include/utils.h b/include/utils.h deleted file mode 100644 index 72be326..0000000 --- a/include/utils.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H - -#include -#include -#include - -// 宽字符转UTF-8 -char* wide_to_utf8(const wchar_t* wstr); - -// UTF-8转宽字符 -wchar_t* utf8_to_wide(const char* str); - -// 检查管理员权限 -int check_admin(); - -// 检查路径是否有效(存在且为目录) -int is_path_valid(const char *path); - -// 刷新列表样式(斑马纹) -void refresh_list_style(); -void refresh_single_list_style(Ihandle *list); - -// 备份注册表 -void backup_registry(); - -// 不区分大小写的字符串查找 -char *stristr(const char *haystack, const char *needle); - -#endif // UTILS_H \ No newline at end of file diff --git a/include/utils/os_env.h b/include/utils/os_env.h new file mode 100644 index 0000000..039f892 --- /dev/null +++ b/include/utils/os_env.h @@ -0,0 +1,13 @@ +#ifndef OS_ENV_H +#define OS_ENV_H + +// 检查是否以管理员权限运行 +int check_admin(void); + +// 检查路径是否有效 +int is_path_valid(const char *path); + +// 备份注册表 +void backup_registry(void); + +#endif // OS_ENV_H diff --git a/include/utils/string_ext.h b/include/utils/string_ext.h new file mode 100644 index 0000000..96fbd47 --- /dev/null +++ b/include/utils/string_ext.h @@ -0,0 +1,24 @@ +#ifndef STRING_EXT_H +#define STRING_EXT_H + +#include + +// 简单字符串列表结构 +typedef struct +{ + char **items; + int count; + int capacity; +} StringList; + +// 字符串列表 +void init_string_list(StringList *list); +void add_string_list(StringList *list, const char *str); +void clear_string_list(StringList *list); + +// 字符串转换函数 +char *wide_to_utf8(const wchar_t *wstr); +wchar_t *utf8_to_wide(const char *str); +char *stristr(const char *haystack, const char *needle); + +#endif // STRING_EXT_H diff --git a/src/callbacks.c b/src/callbacks.c deleted file mode 100644 index 97f7f0f..0000000 --- a/src/callbacks.c +++ /dev/null @@ -1,547 +0,0 @@ -#include "callbacks.h" -#include "globals.h" -#include "registry.h" -#include "utils.h" -#include -#include - -// 简单的自定义输入对话框,支持更宽的输入框 -// 返回 1 表示确定,0 表示取消 -int show_custom_input_dialog(const char *title, const char *label_text, char *buffer, int buffer_size) -{ - Ihandle *text = IupText(NULL); - IupSetAttribute(text, "VALUE", buffer); - IupSetAttribute(text, "EXPAND", "HORIZONTAL"); - IupSetAttribute(text, "RASTERSIZE", "600x"); // 设置宽度为600像素 - IupSetAttribute(text, "VISIBLECOLUMNS", "80"); // 设置可见列数 - - // 如果是编辑模式,全选文本 - if (strlen(buffer) > 0) - { - IupSetAttribute(text, "SELECTION", "ALL"); - } - - Ihandle *dlg = IupDialog( - IupVbox( - IupLabel(label_text), - text, - IupHbox( - IupFill(), - IupButton("确定", "1"), // "1" 会被返回 - IupButton("取消", "0"), // "0" 会被返回 - NULL), - NULL)); - - // 布局设置 - IupSetAttribute(dlg, "TITLE", title); - IupSetAttribute(dlg, "MINBOX", "NO"); - IupSetAttribute(dlg, "MAXBOX", "NO"); - IupSetAttribute(dlg, "RESIZE", "NO"); - IupSetAttribute(dlg, "MARGIN", "10x10"); - IupSetAttribute(dlg, "GAP", "10"); - - // 按钮响应 - // 这是一个简单的 hack:IupPopup 的参数 x, y 如果是 IUP_CENTER 等,它是一个模态循环。 - // 但是标准的 IupPopup 不返回按钮值。 - // 我们使用 IupAlarm 类似的逻辑,或者使用 IUP 提供的 IupGetParam。 - // 为了最简单实现,我们使用 IUP 的 IupGetParam,但是它很难调整宽度。 - // 所以还是手动构建对话框。 - - // 为了获取返回值,我们需要设置按钮回调。 - // 但为了避免定义额外的全局函数,我们可以使用 IupPopup 阻塞特性。 - // 我们需要定义两个简单的回调函数。 - // 为了简化,这里定义两个静态辅助函数。 - - // 由于 C 语言闭包限制,我们需要用全局或静态变量传递状态,或者使用 Dialog 的 Attribute。 - IupSetAttribute(dlg, "MY_STATUS", "0"); - - // 注册回调 (使用 IupSetCallback 注册 lambda 类似的逻辑比较难,这里用名字) - // 必须定义全局函数。为了避免污染,我们在文件顶部定义静态函数。 - // 见下文的 static int on_dialog_ok... - - // 由于不能在函数内部定义函数,我们需要调整代码结构。 - // 见下文重构。 - - return 0; // 占位 -} - -// 静态辅助函数:对话框确定 -static int on_dialog_ok(Ihandle *self) -{ - Ihandle *dlg = IupGetDialog(self); - IupSetAttribute(dlg, "MY_STATUS", "1"); - return IUP_CLOSE; -} - -// 静态辅助函数:对话框取消 -static int on_dialog_cancel(Ihandle *self) -{ - Ihandle *dlg = IupGetDialog(self); - IupSetAttribute(dlg, "MY_STATUS", "0"); - return IUP_CLOSE; -} - -// 真正的实现函数 -int custom_input_dialog(const char *title, const char *label_text, char *buffer, int buffer_size) -{ - Ihandle *text = IupText(NULL); - IupSetAttribute(text, "VALUE", buffer); - IupSetAttribute(text, "EXPAND", "HORIZONTAL"); - IupSetAttribute(text, "RASTERSIZE", "500x"); - IupSetAttribute(text, "NAME", "INPUT_TEXT"); - - Ihandle *btn_ok = IupButton("确定", NULL); - IupSetCallback(btn_ok, "ACTION", on_dialog_ok); - IupSetAttribute(btn_ok, "RASTERSIZE", "100x32"); - - Ihandle *btn_cancel = IupButton("取消", NULL); - IupSetCallback(btn_cancel, "ACTION", on_dialog_cancel); - IupSetAttribute(btn_cancel, "RASTERSIZE", "100x32"); - - Ihandle *vbox = IupVbox( - IupLabel(label_text), - text, - IupHbox(IupFill(), btn_ok, btn_cancel, NULL), - NULL); - IupSetAttribute(vbox, "MARGIN", "15x15"); - IupSetAttribute(vbox, "GAP", "10"); - - Ihandle *dlg = IupDialog(vbox); - IupSetAttribute(dlg, "TITLE", title); - IupSetAttribute(dlg, "MINBOX", "NO"); - IupSetAttribute(dlg, "MAXBOX", "NO"); - IupSetAttribute(dlg, "RESIZE", "NO"); - - IupSetAttributeHandle(dlg, "DEFAULTENTER", btn_ok); - IupSetAttributeHandle(dlg, "DEFAULTESC", btn_cancel); - - IupPopup(dlg, IUP_CENTER, IUP_CENTER); - - int result = IupGetInt(dlg, "MY_STATUS"); - if (result == 1) - { - char *val = IupGetAttribute(text, "VALUE"); - if (val) - { - strncpy(buffer, val, buffer_size); - buffer[buffer_size - 1] = '\0'; - } - } - - IupDestroy(dlg); - return result; -} - -// 辅助函数:获取当前选中的列表 -Ihandle *get_current_list() -{ - // 获取当前选中的 Tab 索引 - // 注意:IupTabs 的 VALUE 属性在某些版本可能返回 handle,某些版本返回 pos - // 这里使用 IupGetInt(tabs_main, "VALUEPOS") 更稳妥 - int pos = IupGetInt(tabs_main, "VALUEPOS"); - if (pos == 0) - return list_sys; - if (pos == 1) - return list_user; - return list_sys; // 默认 -} - -// 按钮回调:新建 -int btn_new_cb(Ihandle *self) -{ - char buffer[1024] = ""; - if (custom_input_dialog("新建环境变量", "请输入路径:", buffer, sizeof(buffer))) - { - if (strlen(buffer) > 0) - { - Ihandle *current_list = get_current_list(); - int count = IupGetInt(current_list, "COUNT"); - count++; - IupSetAttributeId(current_list, "", count, buffer); - IupSetInt(current_list, "COUNT", count); - IupSetInt(current_list, "VALUE", count); - - refresh_single_list_style(current_list); - } - } - return IUP_DEFAULT; -} - -// 按钮回调:编辑 -int btn_edit_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int selected = IupGetInt(current_list, "VALUE"); - if (selected == 0) - return IUP_DEFAULT; - - char *current_val = IupGetAttributeId(current_list, "", selected); - char buffer[4096]; // 假设单个路径不超过4096 - if (current_val) - { - strncpy(buffer, current_val, 4096); - buffer[4095] = '\0'; - } - else - { - buffer[0] = '\0'; - } - - if (custom_input_dialog("编辑环境变量", "编辑路径:", buffer, sizeof(buffer))) - { - if (strlen(buffer) > 0) - { - IupSetAttributeId(current_list, "", selected, buffer); - refresh_single_list_style(current_list); - } - } - return IUP_DEFAULT; -} - -// 双击回调 -int list_dblclick_cb(Ihandle *self, int item, char *text) -{ - // 这里的 self 就是触发双击的列表控件 - if (item > 0) - { - // 选中该行 - IupSetInt(self, "VALUE", item); - // 调用编辑逻辑 - btn_edit_cb(NULL); - } - return IUP_DEFAULT; -} - -// 按钮回调:浏览 -int btn_browse_cb(Ihandle *self) -{ - Ihandle *filedlg = IupFileDlg(); - IupSetAttribute(filedlg, "DIALOGTYPE", "DIR"); - IupSetAttribute(filedlg, "TITLE", "选择目录"); - - IupPopup(filedlg, IUP_CENTER, IUP_CENTER); - - if (IupGetInt(filedlg, "STATUS") != -1) - { - char *value = IupGetAttribute(filedlg, "VALUE"); - if (value) - { - Ihandle *current_list = get_current_list(); - int count = IupGetInt(current_list, "COUNT"); - count++; - IupSetAttributeId(current_list, "", count, value); - IupSetInt(current_list, "COUNT", count); - IupSetInt(current_list, "VALUE", count); - - refresh_single_list_style(current_list); - } - } - IupDestroy(filedlg); - return IUP_DEFAULT; -} - -// 辅助函数:从 raw_data 中删除指定字符串 -static void remove_from_raw_data(StringList *list, const char *str) -{ - if (!list || !str) - return; - for (int i = 0; i < list->count; i++) - { - if (strcmp(list->items[i], str) == 0) - { - free(list->items[i]); - // 移动后续元素 - for (int j = i; j < list->count - 1; j++) - { - list->items[j] = list->items[j + 1]; - } - list->count--; - break; // 假设没有重复,只删除第一个匹配 - } - } -} - -// 按钮回调:删除 -int btn_del_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int selected = IupGetInt(current_list, "VALUE"); - - if (selected == 0) - { - IupMessage("提示", "请先选择要删除的项"); - return IUP_DEFAULT; - } - - // 获取当前要删除的内容 - char *val = IupGetAttributeId(current_list, "", selected); - - // 确认删除 - // char msg[1024]; - // snprintf(msg, sizeof(msg), "确定要删除以下路径吗?\n\n%s", val ? val : "(空)"); - // if (IupAlarm("确认删除", msg, "是", "否", NULL) != 1) - // return IUP_DEFAULT; - - // 从 raw_data 缓存中同步删除 - int pos = IupGetInt(tabs_main, "VALUEPOS"); - StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths; - - // 注意:必须先保存 val 的副本,因为 REMOVEITEM 可能会导致 val 指针失效(如果它是指向列表内部缓冲区的) - char *val_copy = val ? _strdup(val) : NULL; - - // 先从界面删除 - // IupSetAttribute(current_list, "REMOVEITEM", "SELECTED"); - // 改为按索引删除,防止失去焦点导致 SELECTED 失效 - IupSetInt(current_list, "REMOVEITEM", selected); - - // 再从缓存删除 - if (val_copy && raw_data) - { - remove_from_raw_data(raw_data, val_copy); - free(val_copy); - } - - // 重新刷新 - refresh_single_list_style(current_list); - - // 更新状态栏,告知用户删除了什么 - IupSetAttribute(lbl_status, "TITLE", "状态: 已删除选中项"); - - return IUP_DEFAULT; -} - -// 按钮回调:上移 -int btn_up_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int selected = IupGetInt(current_list, "VALUE"); - if (selected <= 1) - return IUP_DEFAULT; // 已经是第一个或未选中 - - char *current = IupGetAttributeId(current_list, "", selected); - char *prev = IupGetAttributeId(current_list, "", selected - 1); - - // 交换内容 - char buf_curr[4096], buf_prev[4096]; - strncpy(buf_curr, current, 4096); - buf_curr[4095] = '\0'; - strncpy(buf_prev, prev, 4096); - buf_prev[4095] = '\0'; - - IupSetAttributeId(current_list, "", selected, buf_prev); - IupSetAttributeId(current_list, "", selected - 1, buf_curr); - - IupSetInt(current_list, "VALUE", selected - 1); - - // 刷新样式(虽然颜色不需要变,但为了保险) - refresh_single_list_style(current_list); - return IUP_DEFAULT; -} - -// 按钮回调:下移 -int btn_down_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int selected = IupGetInt(current_list, "VALUE"); - int count = IupGetInt(current_list, "COUNT"); - if (selected == 0 || selected >= count) - return IUP_DEFAULT; - - char *current = IupGetAttributeId(current_list, "", selected); - char *next = IupGetAttributeId(current_list, "", selected + 1); - - char buf_curr[4096], buf_next[4096]; - strncpy(buf_curr, current, 4096); - buf_curr[4095] = '\0'; - strncpy(buf_next, next, 4096); - buf_next[4095] = '\0'; - - IupSetAttributeId(current_list, "", selected, buf_next); - IupSetAttributeId(current_list, "", selected + 1, buf_curr); - - IupSetInt(current_list, "VALUE", selected + 1); - - refresh_single_list_style(current_list); - return IUP_DEFAULT; -} - -// 按钮回调:一键清理 -int btn_clean_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int count = IupGetInt(current_list, "COUNT"); - if (count == 0) - return IUP_DEFAULT; - - // 弹出确认对话框 - if (IupAlarm("确认清理", "此操作将移除当前列表中所有【无效路径】和【重复路径】。\n确定要继续吗?", "确定", "取消", NULL) != 1) - { - return IUP_DEFAULT; - } - - // 从后往前遍历删除,避免索引错位 - for (int i = count; i >= 1; i--) - { - char *item = IupGetAttributeId(current_list, "", i); - if (!item) - continue; - - int should_remove = 0; - - // 1. 检查有效性 - if (!is_path_valid(item)) - { - should_remove = 1; - } - else - { - // 2. 检查重复 (检查当前项之前是否出现过) - // 注意:这里需要再次遍历,性能稍低但最稳妥 - for (int j = 1; j < i; j++) - { - char *prev_item = IupGetAttributeId(current_list, "", j); - if (prev_item && _stricmp(item, prev_item) == 0) - { - should_remove = 1; - break; - } - } - } - - if (should_remove) - { - IupSetAttributeId(current_list, "REMOVEITEM", i, NULL); - } - } - - refresh_single_list_style(current_list); - IupMessage("提示", "清理完成!"); - return IUP_DEFAULT; -} - -// 搜索回调 -int txt_search_cb(Ihandle *self) -{ - char *filter = IupGetAttribute(self, "VALUE"); - if (!filter) - return IUP_DEFAULT; - - // 获取当前选中的 Tab 索引 - int pos = IupGetInt(tabs_main, "VALUEPOS"); - Ihandle *current_list = (pos == 0) ? list_sys : list_user; - StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths; - - // 清空列表 - IupSetAttribute(current_list, "REMOVEITEM", "ALL"); - - // 重新填充 - int count = 0; - for (int i = 0; i < raw_data->count; i++) - { - // 如果 filter 为空,或包含 filter (不区分大小写) - if (strlen(filter) == 0 || stristr(raw_data->items[i], filter) != NULL) - { - count++; - IupSetAttributeId(current_list, "", count, raw_data->items[i]); - } - } - - IupSetInt(current_list, "COUNT", count); - refresh_single_list_style(current_list); - - return IUP_DEFAULT; -} - -// 拖拽回调 -int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y) -{ - // 获取当前列表和原始数据 - // 注意:拖拽的目标列表可能是 list_sys 或 list_user,由 self 参数决定 - // 但为了确保数据一致性,我们还是重新获取一下 - Ihandle *current_list = self; - StringList *raw_data = NULL; - if (self == list_sys) - raw_data = &raw_sys_paths; - else if (self == list_user) - raw_data = &raw_user_paths; - else - return IUP_DEFAULT; // 异常情况 - - // 检查拖入的是否为目录 - DWORD attr = GetFileAttributesA(filename); - if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) - { - // 如果正在搜索,先清空搜索框 - IupSetAttribute(txt_search, "VALUE", ""); - - // 添加到列表末尾 - int count = IupGetInt(current_list, "COUNT"); - count++; - IupSetAttributeId(current_list, "", count, filename); - IupSetInt(current_list, "COUNT", count); - IupSetInt(current_list, "VALUE", count); // 选中新添加的项 - - // 同时添加到原始数据缓存,确保搜索时能搜到 - if (raw_data) - { - add_string_list(raw_data, filename); - } - - refresh_single_list_style(current_list); - } - else - { - // 如果拖入的不是文件夹,可以在状态栏提示 - IupSetAttribute(lbl_status, "TITLE", "提示: 只能拖拽文件夹添加到 PATH"); - } - - return IUP_DEFAULT; -} - -// 键盘按键回调 -int list_k_any_cb(Ihandle *self, int c) -{ - // 处理 Delete 键 - if (c == K_DEL) - { - btn_del_cb(NULL); - return IUP_IGNORE; // 阻止默认处理 - } - return IUP_DEFAULT; -} - -// 按钮回调:确定 -int btn_ok_cb(Ihandle *self) -{ - save_all_paths(); - return IUP_DEFAULT; -} - -// 按钮回调:取消 -int btn_cancel_cb(Ihandle *self) -{ - IupExitLoop(); - return IUP_DEFAULT; -} - -// 按钮回调:帮助 -int btn_help_cb(Ihandle *self) -{ - IupMessage("使用说明", - "1. 本程序用于编辑系统环境变量 PATH。\n" - "2. 必须以【管理员身份】运行才能保存更改。\n" - "3. 操作说明:\n" - " - 新建:添加新路径到列表末尾。\n" - " - 编辑:修改选中的路径。\n" - " - 浏览:从文件系统选择目录添加。\n" - " - 删除:移除选中的路径。\n" - " - 上移/下移:调整路径优先级。\n" - "4. 点击【确定】保存更改并生效。\n" - "5. 注意:某些正在运行的程序可能需要重启才能识别新的环境变量。\n\n" - "--------------------------------------------------\n" - "作者:LHY\n" - "邮箱:3364451258@qq.com\n" - "GitHub:https://github.com/LHY0125/PathEditor\n" - "记得给我的项目点个star!"); - return IUP_DEFAULT; -} \ No newline at end of file diff --git a/src/controller/callbacks.c b/src/controller/callbacks.c new file mode 100644 index 0000000..aafe76e --- /dev/null +++ b/src/controller/callbacks.c @@ -0,0 +1,407 @@ +#include "controller/callbacks.h" +#include "core/app_context.h" +#include "core/registry_service.h" +#include "core/path_manager.h" +#include "utils/string_ext.h" +#include "utils/os_env.h" +#include "ui/ui_utils.h" +#include "ui/dialogs.h" +#include +#include +#include +#include + +// 辅助函数:获取主对话框 +static Ihandle *get_main_dlg() +{ + // 在实际情况中,可以通过 IupGetHandle 注册名字,或者通过某个全局/静态缓存 + // 但如果想彻底不用全局,我们可以在 IupSetHandle 里面把主窗口存下来 + return IupGetHandle("MAIN_DIALOG"); +} + +// 获取当前的缓存数据列表 +static StringList *get_current_raw_data(Ihandle *dlg) +{ + AppContext *ctx = get_app_context(dlg); + if (!ctx) + return NULL; + + Ihandle *tabs_main = IupGetDialogChild(dlg, "TABS_MAIN"); + int pos = IupGetInt(tabs_main, "VALUEPOS"); + if (pos == 0) + return &ctx->sys_paths; + if (pos == 1) + return &ctx->user_paths; + return &ctx->sys_paths; +} + +// 辅助函数:获取当前选中的列表UI控件 +static Ihandle *get_current_list(Ihandle *dlg) +{ + Ihandle *tabs_main = IupGetDialogChild(dlg, "TABS_MAIN"); + int pos = IupGetInt(tabs_main, "VALUEPOS"); + if (pos == 0) + return IupGetDialogChild(dlg, "LIST_SYS"); + if (pos == 1) + return IupGetDialogChild(dlg, "LIST_USER"); + return IupGetDialogChild(dlg, "LIST_SYS"); +} + +// 按钮回调:新建 +int btn_new_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + char buffer[1024] = ""; + if (custom_input_dialog("新建环境变量", "请输入路径:", buffer, sizeof(buffer))) + { + if (strlen(buffer) > 0) + { + StringList *raw_data = get_current_raw_data(dlg); + add_string_list(raw_data, buffer); + + Ihandle *current_list = get_current_list(dlg); + sync_string_list_to_ui(current_list, raw_data); + + int count = IupGetInt(current_list, "COUNT"); + IupSetInt(current_list, "VALUE", count); + } + } + return IUP_DEFAULT; +} + +// 按钮回调:编辑 +int btn_edit_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + Ihandle *current_list = get_current_list(dlg); + int selected = IupGetInt(current_list, "VALUE"); + if (selected == 0) + return IUP_DEFAULT; + + StringList *raw_data = get_current_raw_data(dlg); + if (selected - 1 >= raw_data->count) + return IUP_DEFAULT; + + char buffer[4096]; + strncpy(buffer, raw_data->items[selected - 1], 4096); + buffer[4095] = '\0'; + + if (custom_input_dialog("编辑环境变量", "编辑路径:", buffer, sizeof(buffer))) + { + if (strlen(buffer) > 0) + { + free(raw_data->items[selected - 1]); + raw_data->items[selected - 1] = _strdup(buffer); + + sync_string_list_to_ui(current_list, raw_data); + IupSetInt(current_list, "VALUE", selected); + } + } + return IUP_DEFAULT; +} + +// 双击回调 +int list_dblclick_cb(Ihandle *self, int item, char *text) +{ + if (item > 0) + { + IupSetInt(self, "VALUE", item); + btn_edit_cb(self); + } + return IUP_DEFAULT; +} + +// 按钮回调:浏览 +int btn_browse_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + Ihandle *filedlg = IupFileDlg(); + IupSetAttribute(filedlg, "DIALOGTYPE", "DIR"); + IupSetAttribute(filedlg, "TITLE", "选择目录"); + + IupPopup(filedlg, IUP_CENTER, IUP_CENTER); + + if (IupGetInt(filedlg, "STATUS") != -1) + { + char *value = IupGetAttribute(filedlg, "VALUE"); + if (value) + { + StringList *raw_data = get_current_raw_data(dlg); + add_string_list(raw_data, value); + + Ihandle *current_list = get_current_list(dlg); + sync_string_list_to_ui(current_list, raw_data); + + int count = IupGetInt(current_list, "COUNT"); + IupSetInt(current_list, "VALUE", count); + } + } + IupDestroy(filedlg); + return IUP_DEFAULT; +} + +// 按钮回调:删除 +int btn_del_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + Ihandle *current_list = get_current_list(dlg); + int selected = IupGetInt(current_list, "VALUE"); + + if (selected == 0) + { + IupMessage("提示", "请先选择要删除的项"); + return IUP_DEFAULT; + } + + StringList *raw_data = get_current_raw_data(dlg); + path_manager_remove_at(raw_data, selected - 1); + + sync_string_list_to_ui(current_list, raw_data); + + Ihandle *lbl_status = IupGetDialogChild(dlg, "LBL_STATUS"); + if (lbl_status) + IupSetAttribute(lbl_status, "TITLE", "状态: 已删除选中项"); + + return IUP_DEFAULT; +} + +// 按钮回调:上移 +int btn_up_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + Ihandle *current_list = get_current_list(dlg); + int selected = IupGetInt(current_list, "VALUE"); + if (selected <= 1) + return IUP_DEFAULT; + + StringList *raw_data = get_current_raw_data(dlg); + path_manager_move_up(raw_data, selected - 1); + + sync_string_list_to_ui(current_list, raw_data); + IupSetInt(current_list, "VALUE", selected - 1); + + return IUP_DEFAULT; +} + +// 按钮回调:下移 +int btn_down_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + Ihandle *current_list = get_current_list(dlg); + int selected = IupGetInt(current_list, "VALUE"); + StringList *raw_data = get_current_raw_data(dlg); + + if (selected == 0 || selected >= raw_data->count) + return IUP_DEFAULT; + + path_manager_move_down(raw_data, selected - 1); + + sync_string_list_to_ui(current_list, raw_data); + IupSetInt(current_list, "VALUE", selected + 1); + + return IUP_DEFAULT; +} + +// 按钮回调:一键清理 +int btn_clean_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + StringList *raw_data = get_current_raw_data(dlg); + if (!raw_data || raw_data->count == 0) + return IUP_DEFAULT; + + if (IupAlarm("确认清理", "此操作将移除当前列表中所有【无效路径】和【重复路径】。\n确定要继续吗?", "确定", "取消", NULL) != 1) + { + return IUP_DEFAULT; + } + + int removed = path_manager_clean(raw_data); + + Ihandle *current_list = get_current_list(dlg); + sync_string_list_to_ui(current_list, raw_data); + + char msg[128]; + snprintf(msg, sizeof(msg), "清理完成!共移除了 %d 个无效或重复路径。", removed); + IupMessage("提示", msg); + return IUP_DEFAULT; +} + +// 搜索回调 +int txt_search_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + char *filter = IupGetAttribute(self, "VALUE"); + if (!filter) + return IUP_DEFAULT; + + Ihandle *current_list = get_current_list(dlg); + StringList *raw_data = get_current_raw_data(dlg); + + IupSetAttribute(current_list, "REMOVEITEM", "ALL"); + + int count = 0; + for (int i = 0; i < raw_data->count; i++) + { + if (strlen(filter) == 0 || stristr(raw_data->items[i], filter) != NULL) + { + count++; + IupSetAttributeId(current_list, "", count, raw_data->items[i]); + } + } + + IupSetInt(current_list, "COUNT", count); + refresh_single_list_style(current_list); + + return IUP_DEFAULT; +} + +// 拖拽回调 +int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y) +{ + Ihandle *dlg = IupGetDialog(self); + Ihandle *current_list = self; + AppContext *ctx = get_app_context(dlg); + if (!ctx) + return IUP_DEFAULT; + + StringList *raw_data = NULL; + if (self == IupGetDialogChild(dlg, "LIST_SYS")) + raw_data = &ctx->sys_paths; + else if (self == IupGetDialogChild(dlg, "LIST_USER")) + raw_data = &ctx->user_paths; + else + return IUP_DEFAULT; + + DWORD attr = GetFileAttributesA(filename); + if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) + { + Ihandle *txt_search = IupGetDialogChild(dlg, "TXT_SEARCH"); + if (txt_search) + IupSetAttribute(txt_search, "VALUE", ""); + + add_string_list(raw_data, filename); + sync_string_list_to_ui(current_list, raw_data); + + IupSetInt(current_list, "VALUE", raw_data->count); + } + else + { + Ihandle *lbl_status = IupGetDialogChild(dlg, "LBL_STATUS"); + if (lbl_status) + IupSetAttribute(lbl_status, "TITLE", "提示: 只能拖拽文件夹添加到 PATH"); + } + + return IUP_DEFAULT; +} + +// 键盘按键回调 +int list_k_any_cb(Ihandle *self, int c) +{ + if (c == K_DEL) + { + btn_del_cb(self); + return IUP_IGNORE; + } + return IUP_DEFAULT; +} + +// 按钮回调:确定 (保存所有) +int btn_ok_cb(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + AppContext *ctx = get_app_context(dlg); + if (!ctx) + return IUP_DEFAULT; + + if (!check_admin()) + { + IupMessage("错误", "需要管理员权限才能保存更改!"); + return IUP_DEFAULT; + } + + backup_registry(); + + int sys_ok = save_system_paths(&ctx->sys_paths); + int user_ok = save_user_paths(&ctx->user_paths); + + Ihandle *lbl_status = IupGetDialogChild(dlg, "LBL_STATUS"); + + if (sys_ok && user_ok) + { + SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL); + IupMessage("成功", "系统和用户 PATH 环境变量均已更新!"); + if (lbl_status) + IupSetAttribute(lbl_status, "TITLE", "状态: 全部保存成功"); + } + else if (sys_ok) + { + IupMessage("提示", "系统变量保存成功,但用户变量保存失败。"); + } + else if (user_ok) + { + IupMessage("提示", "用户变量保存成功,但系统变量保存失败。"); + } + else + { + IupMessage("错误", "保存失败!"); + if (lbl_status) + IupSetAttribute(lbl_status, "TITLE", "状态: 保存失败"); + } + return IUP_DEFAULT; +} + +// 按钮回调:取消 +int btn_cancel_cb(Ihandle *self) +{ + IupExitLoop(); + return IUP_DEFAULT; +} + +// 按钮回调:帮助 +int btn_help_cb(Ihandle *self) +{ + IupMessage("使用说明", + "1. 本程序用于编辑系统环境变量 PATH。\n" + "2. 必须以【管理员身份】运行才能保存更改。\n" + "3. 操作说明:\n" + " - 新建:添加新路径到列表末尾。\n" + " - 编辑:修改选中的路径。\n" + " - 浏览:从文件系统选择目录添加。\n" + " - 删除:移除选中的路径。\n" + " - 上移/下移:调整路径优先级。\n" + "4. 点击【确定】保存更改并生效。\n" + "5. 注意:某些正在运行的程序可能需要重启才能识别新的环境变量。\n\n" + "--------------------------------------------------\n" + "作者:LHY\n" + "邮箱:3364451258@qq.com\n" + "GitHub:https://github.com/LHY0125/PathEditor\n" + "记得给我的项目点个star!"); + return IUP_DEFAULT; +} + +// 载入所有路径 +void load_all_paths(void) +{ + Ihandle *dlg = get_main_dlg(); + if (!dlg) + return; + AppContext *ctx = get_app_context(dlg); + if (!ctx) + return; + + if (!load_system_paths(&ctx->sys_paths)) + { + IupMessage("错误", "无法打开系统环境变量注册表键,请尝试以管理员身份运行。"); + } + load_user_paths(&ctx->user_paths); + + Ihandle *list_sys = IupGetDialogChild(dlg, "LIST_SYS"); + Ihandle *list_user = IupGetDialogChild(dlg, "LIST_USER"); + + sync_string_list_to_ui(list_sys, &ctx->sys_paths); + sync_string_list_to_ui(list_user, &ctx->user_paths); + + Ihandle *lbl_status = IupGetDialogChild(dlg, "LBL_STATUS"); + if (lbl_status) + IupSetAttribute(lbl_status, "TITLE", "状态: 已加载系统和用户变量"); +} \ No newline at end of file diff --git a/src/core/app_context.c b/src/core/app_context.c new file mode 100644 index 0000000..d2f0bd3 --- /dev/null +++ b/src/core/app_context.c @@ -0,0 +1,36 @@ +#include "core/app_context.h" +#include + +// 创建应用上下文 +AppContext *create_app_context(void) +{ + AppContext *ctx = (AppContext *)malloc(sizeof(AppContext)); + if (ctx) + { + init_string_list(&ctx->sys_paths); + init_string_list(&ctx->user_paths); + } + return ctx; +} + +// 销毁应用上下文 +void destroy_app_context(AppContext *ctx) +{ + if (ctx) + { + clear_string_list(&ctx->sys_paths); + clear_string_list(&ctx->user_paths); + free(ctx); + } +} + +// 获取应用上下文 +AppContext *get_app_context(Ihandle *ih) +{ + if (!ih) + return NULL; + Ihandle *dlg = IupGetDialog(ih); + if (!dlg) + return NULL; + return (AppContext *)IupGetAttribute(dlg, "APP_CONTEXT"); +} \ No newline at end of file diff --git a/src/core/path_manager.c b/src/core/path_manager.c new file mode 100644 index 0000000..2cc9d40 --- /dev/null +++ b/src/core/path_manager.c @@ -0,0 +1,82 @@ +#include "core/path_manager.h" +#include "utils/os_env.h" +#include +#include + +// 删除指定索引的路径项 +void path_manager_remove_at(StringList *list, int index) +{ + if (!list || index < 0 || index >= list->count) + return; + + free(list->items[index]); + for (int i = index; i < list->count - 1; i++) + { + list->items[i] = list->items[i + 1]; + } + list->count--; +} + +// 向上移动路径项 +void path_manager_move_up(StringList *list, int index) +{ + if (!list || index <= 0 || index >= list->count) + return; + + char *temp = list->items[index]; + list->items[index] = list->items[index - 1]; + list->items[index - 1] = temp; +} + +// 向下移动路径项 +void path_manager_move_down(StringList *list, int index) +{ + if (!list || index < 0 || index >= list->count - 1) + return; + + char *temp = list->items[index]; + list->items[index] = list->items[index + 1]; + list->items[index + 1] = temp; +} + +// 清理无效路径项 +int path_manager_clean(StringList *list) +{ + if (!list) return 0; + int removed_count = 0; + + // 从后往前遍历,方便删除 + for (int i = list->count - 1; i >= 0; i--) + { + char *item = list->items[i]; + if (!item) continue; + + int should_remove = 0; + + // 1. 检查有效性 + if (!is_path_valid(item)) + { + should_remove = 1; + } + else + { + // 2. 检查重复 (检查当前项之前是否出现过) + for (int j = 0; j < i; j++) + { + char *prev_item = list->items[j]; + if (prev_item && _stricmp(item, prev_item) == 0) + { + should_remove = 1; + break; + } + } + } + + if (should_remove) + { + path_manager_remove_at(list, i); + removed_count++; + } + } + return removed_count; +} \ No newline at end of file diff --git a/src/core/registry_service.c b/src/core/registry_service.c new file mode 100644 index 0000000..f498552 --- /dev/null +++ b/src/core/registry_service.c @@ -0,0 +1,144 @@ +#include "core/registry_service.h" +#include "utils/string_ext.h" +#include +#include +#include + +#define REG_PATH_SYS L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment" +#define REG_PATH_USER L"Environment" +#define REG_VALUE L"Path" + +// 内部辅助函数:加载单个列表 +static int load_single_path(HKEY hKeyRoot, const wchar_t *regPath, StringList *list) +{ + clear_string_list(list); + + HKEY hKey; + LONG res = RegOpenKeyExW(hKeyRoot, regPath, 0, KEY_READ, &hKey); + if (res != ERROR_SUCCESS) + { + return 0; // 失败 + } + + DWORD type, size; + res = RegQueryValueExW(hKey, REG_VALUE, NULL, &type, NULL, &size); + if (res == ERROR_SUCCESS) + { + wchar_t *buffer = (wchar_t *)malloc(size + 2); + if (buffer) + { + memset(buffer, 0, size + 2); + if (RegQueryValueExW(hKey, REG_VALUE, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS) + { + wchar_t *current = buffer; + wchar_t *next_semicolon = NULL; + + while (*current) + { + next_semicolon = wcschr(current, L';'); + if (next_semicolon) + *next_semicolon = L'\0'; + + if (wcslen(current) > 0) + { + char *utf8_str = wide_to_utf8(current); + if (utf8_str) + { + add_string_list(list, utf8_str); + free(utf8_str); + } + } + + if (next_semicolon) + current = next_semicolon + 1; + else + break; + } + } + free(buffer); + } + } + RegCloseKey(hKey); + return 1; // 成功 +} + +// 加载系统环境变量路径 +int load_system_paths(StringList *list) +{ + return load_single_path(HKEY_LOCAL_MACHINE, REG_PATH_SYS, list); +} + +// 加载用户环境变量路径 +int load_user_paths(StringList *list) +{ + return load_single_path(HKEY_CURRENT_USER, REG_PATH_USER, list); +} + +// 内部辅助函数:保存单个列表 +static int save_single_path(HKEY hKeyRoot, const wchar_t *regPath, const StringList *list) +{ + if (!list) return 0; + + // 计算大小 + size_t total_len = 0; + for (int i = 0; i < list->count; i++) + { + if (list->items[i]) + { + wchar_t *witem = utf8_to_wide(list->items[i]); + if (witem) + { + total_len += wcslen(witem) + 1; + free(witem); + } + } + } + total_len += 1; + + wchar_t *buffer = (wchar_t *)malloc(total_len * sizeof(wchar_t)); + if (!buffer) + return 0; + + buffer[0] = L'\0'; + for (int i = 0; i < list->count; i++) + { + if (list->items[i]) + { + wchar_t *witem = utf8_to_wide(list->items[i]); + if (witem) + { + wcscat(buffer, witem); + if (i < list->count - 1) + wcscat(buffer, L";"); + free(witem); + } + } + } + + HKEY hKey; + int success = 0; + if (RegOpenKeyExW(hKeyRoot, regPath, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS) + { + DWORD size = (DWORD)((wcslen(buffer) + 1) * sizeof(wchar_t)); + if (RegSetValueExW(hKey, REG_VALUE, 0, REG_EXPAND_SZ, (LPBYTE)buffer, size) == ERROR_SUCCESS) + { + success = 1; + } + RegCloseKey(hKey); + } + + free(buffer); + return success; +} + +// 保存系统环境变量路径 +int save_system_paths(const StringList *list) +{ + return save_single_path(HKEY_LOCAL_MACHINE, REG_PATH_SYS, list); +} + +// 保存用户环境变量路径 +int save_user_paths(const StringList *list) +{ + return save_single_path(HKEY_CURRENT_USER, REG_PATH_USER, list); +} \ No newline at end of file diff --git a/src/globals.c b/src/globals.c deleted file mode 100644 index 5621e44..0000000 --- a/src/globals.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "globals.h" -#include -#include - -// 全局变量定义 -StringList raw_sys_paths = {0}; -StringList raw_user_paths = {0}; - -// 全局控件定义 -Ihandle *dlg, *tabs_main, *list_sys, *list_user, *lbl_status; // 主窗口、标签页、系统路径列表、用户路径列表、状态标签 -Ihandle *btn_new, *btn_edit, *btn_browse, *btn_del, *btn_up, *btn_down; // 右侧按钮 -Ihandle *btn_ok, *btn_cancel, *btn_help; // 确认取消帮助按钮 -Ihandle *btn_clean; // 一键清理按钮 -Ihandle *txt_search; // 搜索框 \ No newline at end of file diff --git a/src/main.c b/src/main.c index e70379b..10ffc43 100644 --- a/src/main.c +++ b/src/main.c @@ -3,11 +3,11 @@ #include #include #include -#include "globals.h" -#include "utils.h" -#include "registry.h" -#include "callbacks.h" -#include "ui.h" +#include "core/app_context.h" +#include "utils/string_ext.h" +#include "utils/os_env.h" +#include "controller/callbacks.h" +#include "ui/main_window.h" #include "config.h" /* @@ -18,6 +18,16 @@ cmake --build build build_installer.bat */ +// 定义 Windows 消息常量 +#ifndef WM_COPYGLOBALDATA +#define WM_COPYGLOBALDATA 0x0049 +#endif + +// 消息过滤器常量 +#ifndef MSGFLT_ADD +#define MSGFLT_ADD 1 +#endif + // 主函数 int main(int argc, char **argv) { @@ -45,74 +55,57 @@ int main(int argc, char **argv) FreeLibrary(hUser32); } - // 创建两个列表控件 - list_sys = create_path_list(); - list_user = create_path_list(); + // 禁用默认的全局按键处理 + IupSetGlobal("INPUTCALLBACKS", "NO"); - // 创建搜索框 - txt_search = IupText(NULL); - IupSetAttribute(txt_search, "EXPAND", "HORIZONTAL"); - IupSetAttribute(txt_search, "CUEBANNER", "输入关键词搜索..."); - IupSetCallback(txt_search, "VALUECHANGED_CB", (Icallback)txt_search_cb); + // 创建应用上下文 + AppContext *ctx = create_app_context(); + if (!ctx) + { + IupMessage("错误", "无法分配内存创建应用上下文"); + IupClose(); + return 1; + } - // 创建 Tabs - tabs_main = IupTabs( - IupVbox(list_sys, NULL), - IupVbox(list_user, NULL), - NULL); - IupSetAttribute(tabs_main, "TABTITLE0", "系统变量 (System)"); - IupSetAttribute(tabs_main, "TABTITLE1", "用户变量 (User)"); - IupSetAttribute(tabs_main, "TABTYPE", "TOP"); + Ihandle *dlg = create_main_window(); - // 创建右侧按钮区域 - Ihandle *vbox_btns = create_main_buttons(); + // 绑定上下文到对话框 + IupSetAttribute(dlg, "APP_CONTEXT", (char *)ctx); + // 注册主窗口句柄,方便其他地方获取 + IupSetHandle("MAIN_DIALOG", dlg); - // 上部布局:Tabs + 按钮 - Ihandle *hbox_main = IupHbox(tabs_main, vbox_btns, NULL); - IupSetAttribute(hbox_main, "GAP", UI_HBOX_GAP); - IupSetAttribute(hbox_main, "MARGIN", UI_HBOX_MARGIN); - - // 状态标签 - lbl_status = IupLabel("状态: 就绪"); - IupSetAttribute(lbl_status, "EXPAND", "HORIZONTAL"); - - // 创建底部按钮区域 - Ihandle *hbox_bottom = create_bottom_buttons(); - - // 总体布局 - Ihandle *vbox_all = IupVbox( - IupLabel("环境变量编辑器:"), - txt_search, - hbox_main, - hbox_bottom, - NULL); - IupSetAttribute(vbox_all, "MARGIN", UI_VBOX_ALL_MARGIN); - IupSetAttribute(vbox_all, "GAP", UI_VBOX_ALL_GAP); - - // 创建对话框 - dlg = IupDialog(vbox_all); - IupSetAttribute(dlg, "TITLE", APP_NAME); // 对话框标题 - IupSetAttribute(dlg, "RASTERSIZE", UI_DLG_SIZE); // 对话框初始大小 (像素) - IupSetAttribute(dlg, "MINSIZE", UI_DLG_MINSIZE); // 对话框最小大小 (像素) - IupSetAttribute(dlg, "MINBOX", "NO"); - IupSetAttribute(dlg, "MAXBOX", "NO"); - - // 加载数据 + // 检查管理员权限 if (!check_admin()) { - IupMessage("警告", "程序未以管理员身份运行,您只能查看,无法保存更改!"); - IupSetAttribute(dlg, "TITLE", APP_NAME_READONLY); // 对话框标题 (只读模式) - IupSetAttribute(lbl_status, "TITLE", "状态: 只读模式 (权限不足)"); + Ihandle *lbl_status = IupGetDialogChild(dlg, "LBL_STATUS"); + if (lbl_status) + IupSetAttribute(lbl_status, "TITLE", "状态: ⚠️ 只读模式 (无管理员权限)"); - // 禁用修改类按钮 - IupSetAttribute(btn_new, "ACTIVE", "NO"); - IupSetAttribute(btn_edit, "ACTIVE", "NO"); - IupSetAttribute(btn_browse, "ACTIVE", "NO"); - IupSetAttribute(btn_del, "ACTIVE", "NO"); - IupSetAttribute(btn_up, "ACTIVE", "NO"); - IupSetAttribute(btn_down, "ACTIVE", "NO"); - IupSetAttribute(btn_clean, "ACTIVE", "NO"); - IupSetAttribute(btn_ok, "ACTIVE", "NO"); + Ihandle *btn_new = IupGetDialogChild(dlg, "BTN_NEW"); + Ihandle *btn_edit = IupGetDialogChild(dlg, "BTN_EDIT"); + Ihandle *btn_browse = IupGetDialogChild(dlg, "BTN_BROWSE"); + Ihandle *btn_del = IupGetDialogChild(dlg, "BTN_DEL"); + Ihandle *btn_up = IupGetDialogChild(dlg, "BTN_UP"); + Ihandle *btn_down = IupGetDialogChild(dlg, "BTN_DOWN"); + Ihandle *btn_clean = IupGetDialogChild(dlg, "BTN_CLEAN"); + Ihandle *btn_ok = IupGetDialogChild(dlg, "BTN_OK"); + + if (btn_new) + IupSetAttribute(btn_new, "ACTIVE", "NO"); + if (btn_edit) + IupSetAttribute(btn_edit, "ACTIVE", "NO"); + if (btn_browse) + IupSetAttribute(btn_browse, "ACTIVE", "NO"); + if (btn_del) + IupSetAttribute(btn_del, "ACTIVE", "NO"); + if (btn_up) + IupSetAttribute(btn_up, "ACTIVE", "NO"); + if (btn_down) + IupSetAttribute(btn_down, "ACTIVE", "NO"); + if (btn_clean) + IupSetAttribute(btn_clean, "ACTIVE", "NO"); + if (btn_ok) + IupSetAttribute(btn_ok, "ACTIVE", "NO"); } IupShowXY(dlg, IUP_CENTER, IUP_CENTER); @@ -122,6 +115,8 @@ int main(int argc, char **argv) load_all_paths(); IupMainLoop(); + + destroy_app_context(ctx); IupClose(); return 0; } \ No newline at end of file diff --git a/src/registry.c b/src/registry.c deleted file mode 100644 index 51cd288..0000000 --- a/src/registry.c +++ /dev/null @@ -1,177 +0,0 @@ -#include "registry.h" -#include "globals.h" -#include "utils.h" -#include -#include -#include -#include - -// 内部辅助函数:加载单个列表 -static void load_single_path(HKEY hKeyRoot, const wchar_t *regPath, Ihandle *list, StringList *cache) -{ - // 清空旧缓存 - clear_string_list(cache); - - HKEY hKey; - LONG res = RegOpenKeyExW(hKeyRoot, regPath, 0, KEY_READ, &hKey); - if (res != ERROR_SUCCESS) - { - // 只有 HKLM 失败才提示需要管理员,HKCU 失败可能是其他原因 - if (hKeyRoot == HKEY_LOCAL_MACHINE) - { - char msg[512]; - snprintf(msg, sizeof(msg), "无法打开注册表键 (HKLM)。\n路径: %ls\n错误码: %ld\n\n请尝试右键点击程序 -> '以管理员身份运行'。", regPath, res); - IupMessage("错误", msg); - } - return; - } - - DWORD type, size; - res = RegQueryValueExW(hKey, REG_VALUE, NULL, &type, NULL, &size); - if (res == ERROR_SUCCESS) - { - wchar_t *buffer = (wchar_t *)malloc(size + 2); - if (buffer) - { - memset(buffer, 0, size + 2); - if (RegQueryValueExW(hKey, REG_VALUE, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS) - { - wchar_t *current = buffer; - wchar_t *next_semicolon = NULL; - int count = 0; - - IupSetAttribute(list, "REMOVEITEM", "ALL"); - - while (*current) - { - next_semicolon = wcschr(current, L';'); - if (next_semicolon) - *next_semicolon = L'\0'; - - if (wcslen(current) > 0) - { - char *utf8_str = wide_to_utf8(current); - - // 添加到列表 - count++; - IupSetAttributeId(list, "", count, utf8_str); - - // 添加到缓存 - add_string_list(cache, utf8_str); - - free(utf8_str); - } - - if (next_semicolon) - current = next_semicolon + 1; - else - break; - } - - IupSetInt(list, "COUNT", count); - IupSetInt(list, "VALUE", 1); - } - free(buffer); - } - } - RegCloseKey(hKey); -} - -// 加载所有PATH -void load_all_paths() -{ - load_single_path(HKEY_LOCAL_MACHINE, REG_PATH_SYS, list_sys, &raw_sys_paths); - load_single_path(HKEY_CURRENT_USER, REG_PATH_USER, list_user, &raw_user_paths); - - refresh_list_style(); - IupSetAttribute(lbl_status, "TITLE", "状态: 已加载系统和用户变量"); -} - -// 内部辅助函数:保存单个列表 -static int save_single_path(HKEY hKeyRoot, const wchar_t *regPath, Ihandle *list) -{ - int count = IupGetInt(list, "COUNT"); - - // 计算大小 - size_t total_len = 0; - for (int i = 1; i <= count; i++) - { - char *item = IupGetAttributeId(list, "", i); - if (item) - { - wchar_t *witem = utf8_to_wide(item); - total_len += wcslen(witem) + 1; - free(witem); - } - } - total_len += 1; - - wchar_t *buffer = (wchar_t *)malloc(total_len * sizeof(wchar_t)); - if (!buffer) - return 0; - - buffer[0] = L'\0'; - for (int i = 1; i <= count; i++) - { - char *item = IupGetAttributeId(list, "", i); - if (item) - { - wchar_t *witem = utf8_to_wide(item); - wcscat(buffer, witem); - if (i < count) - wcscat(buffer, L";"); - free(witem); - } - } - - HKEY hKey; - int success = 0; - if (RegOpenKeyExW(hKeyRoot, regPath, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS) - { - DWORD size = (wcslen(buffer) + 1) * sizeof(wchar_t); - if (RegSetValueExW(hKey, REG_VALUE, 0, REG_EXPAND_SZ, (LPBYTE)buffer, size) == ERROR_SUCCESS) - { - success = 1; - } - RegCloseKey(hKey); - } - - free(buffer); - return success; -} - -// 保存所有PATH -void save_all_paths() -{ - if (!check_admin()) - { - IupMessage("错误", "需要管理员权限才能保存更改!"); - return; - } - - // 备份 - backup_registry(); - - int sys_ok = save_single_path(HKEY_LOCAL_MACHINE, REG_PATH_SYS, list_sys); - int user_ok = save_single_path(HKEY_CURRENT_USER, REG_PATH_USER, list_user); - - if (sys_ok && user_ok) - { - SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL); - IupMessage("成功", "系统和用户 PATH 环境变量均已更新!"); - IupSetAttribute(lbl_status, "TITLE", "状态: 全部保存成功"); - } - else if (sys_ok) - { - IupMessage("提示", "系统变量保存成功,但用户变量保存失败。"); - } - else if (user_ok) - { - IupMessage("提示", "用户变量保存成功,但系统变量保存失败。"); - } - else - { - IupMessage("错误", "保存失败!"); - IupSetAttribute(lbl_status, "TITLE", "状态: 保存失败"); - } -} \ No newline at end of file diff --git a/src/ui.c b/src/ui.c deleted file mode 100644 index 20fa52c..0000000 --- a/src/ui.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "ui.h" -#include "globals.h" -#include "callbacks.h" -#include "config.h" -#include - -// 创建列表控件 -Ihandle *create_path_list() -{ - Ihandle *list = IupFlatList(); - IupSetAttribute(list, "EXPAND", "YES"); - IupSetAttribute(list, "ITEMPADDING", UI_LIST_ITEM_PADDING); - IupSetAttribute(list, "BACKCOLOR", UI_LIST_BACKCOLOR); - IupSetAttribute(list, "BORDER", "YES"); - IupSetAttribute(list, "CANFOCUS", "YES"); - IupSetAttribute(list, "HLINE", "NO"); - IupSetCallback(list, "DBLCLICK_CB", (Icallback)list_dblclick_cb); - IupSetCallback(list, "DROPFILES_CB", (Icallback)list_dropfiles_cb); - IupSetCallback(list, "K_ANY", (Icallback)list_k_any_cb); - return list; -} - -// 创建右侧功能按钮区域 -Ihandle *create_main_buttons() -{ - // 创建右侧按钮 - btn_new = IupButton("新建(N)", NULL); - btn_edit = IupButton("编辑(E)", NULL); - btn_browse = IupButton("浏览(B)...", NULL); - btn_del = IupButton("删除(D)", NULL); - btn_up = IupButton("上移(U)", NULL); - btn_down = IupButton("下移(O)", NULL); - btn_clean = IupButton("一键清理", NULL); - - // 设置按钮回调 - IupSetCallback(btn_new, "ACTION", (Icallback)btn_new_cb); - IupSetCallback(btn_edit, "ACTION", (Icallback)btn_edit_cb); - IupSetCallback(btn_browse, "ACTION", (Icallback)btn_browse_cb); - IupSetCallback(btn_del, "ACTION", (Icallback)btn_del_cb); - IupSetCallback(btn_up, "ACTION", (Icallback)btn_up_cb); - IupSetCallback(btn_down, "ACTION", (Icallback)btn_down_cb); - IupSetCallback(btn_clean, "ACTION", (Icallback)btn_clean_cb); - - // 设置按钮大小 - IupSetAttribute(btn_new, "RASTERSIZE", UI_BTN_RASTERSIZE); - IupSetAttribute(btn_edit, "RASTERSIZE", UI_BTN_RASTERSIZE); - IupSetAttribute(btn_browse, "RASTERSIZE", UI_BTN_RASTERSIZE); - IupSetAttribute(btn_del, "RASTERSIZE", UI_BTN_RASTERSIZE); - IupSetAttribute(btn_up, "RASTERSIZE", UI_BTN_RASTERSIZE); - IupSetAttribute(btn_down, "RASTERSIZE", UI_BTN_RASTERSIZE); - IupSetAttribute(btn_clean, "RASTERSIZE", UI_BTN_RASTERSIZE); - - Ihandle *vbox_btns = IupVbox( - btn_new, btn_edit, btn_browse, btn_del, - IupFill(), // 间隔 - btn_clean, // 放在上移下移之前,或者最下面,这里放在中间偏下 - IupFill(), - btn_up, btn_down, - NULL); - IupSetAttribute(vbox_btns, "GAP", UI_VBOX_GAP); - IupSetAttribute(vbox_btns, "MARGIN", UI_VBOX_MARGIN); - - return vbox_btns; -} - -// 创建底部按钮区域 -Ihandle *create_bottom_buttons() -{ - // 底部按钮 - btn_ok = IupButton("确定", NULL); - btn_cancel = IupButton("取消", NULL); - btn_help = IupButton("帮助(?)", NULL); - - IupSetCallback(btn_ok, "ACTION", (Icallback)btn_ok_cb); - IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_cb); - IupSetCallback(btn_help, "ACTION", (Icallback)btn_help_cb); - - IupSetAttribute(btn_ok, "RASTERSIZE", UI_BTN_RASTERSIZE); - IupSetAttribute(btn_cancel, "RASTERSIZE", UI_BTN_RASTERSIZE); - IupSetAttribute(btn_help, "RASTERSIZE", UI_BTN_RASTERSIZE); - - Ihandle *hbox_bottom = IupHbox(lbl_status, IupFill(), btn_help, btn_ok, btn_cancel, NULL); - IupSetAttribute(hbox_bottom, "GAP", UI_HBOX_GAP); - IupSetAttribute(hbox_bottom, "MARGIN", UI_HBOX_MARGIN); - IupSetAttribute(hbox_bottom, "ALIGNMENT", UI_HBOX_ALIGNMENT); - - return hbox_bottom; -} \ No newline at end of file diff --git a/src/ui/dialogs.c b/src/ui/dialogs.c new file mode 100644 index 0000000..5c6cc05 --- /dev/null +++ b/src/ui/dialogs.c @@ -0,0 +1,70 @@ +#include "ui/dialogs.h" +#include +#include + +// 静态辅助函数:对话框确定 +static int on_dialog_ok(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + IupSetAttribute(dlg, "MY_STATUS", "1"); + return IUP_CLOSE; +} + +// 静态辅助函数:对话框取消 +static int on_dialog_cancel(Ihandle *self) +{ + Ihandle *dlg = IupGetDialog(self); + IupSetAttribute(dlg, "MY_STATUS", "0"); + return IUP_CLOSE; +} + +// 真正的实现函数 +int custom_input_dialog(const char *title, const char *label_text, char *buffer, int buffer_size) +{ + Ihandle *text = IupText(NULL); + IupSetAttribute(text, "VALUE", buffer); + IupSetAttribute(text, "EXPAND", "HORIZONTAL"); + IupSetAttribute(text, "RASTERSIZE", "500x"); + IupSetAttribute(text, "NAME", "INPUT_TEXT"); + + Ihandle *btn_ok = IupButton("确定", NULL); + IupSetCallback(btn_ok, "ACTION", on_dialog_ok); + IupSetAttribute(btn_ok, "RASTERSIZE", "100x32"); + + Ihandle *btn_cancel = IupButton("取消", NULL); + IupSetCallback(btn_cancel, "ACTION", on_dialog_cancel); + IupSetAttribute(btn_cancel, "RASTERSIZE", "100x32"); + + Ihandle *vbox = IupVbox( + IupLabel(label_text), + text, + IupHbox(IupFill(), btn_ok, btn_cancel, NULL), + NULL); + IupSetAttribute(vbox, "MARGIN", "15x15"); + IupSetAttribute(vbox, "GAP", "10"); + + Ihandle *dlg = IupDialog(vbox); + IupSetAttribute(dlg, "TITLE", title); + IupSetAttribute(dlg, "MINBOX", "NO"); + IupSetAttribute(dlg, "MAXBOX", "NO"); + IupSetAttribute(dlg, "RESIZE", "NO"); + + IupSetAttributeHandle(dlg, "DEFAULTENTER", btn_ok); + IupSetAttributeHandle(dlg, "DEFAULTESC", btn_cancel); + + IupPopup(dlg, IUP_CENTER, IUP_CENTER); + + int result = IupGetInt(dlg, "MY_STATUS"); + if (result == 1) + { + char *val = IupGetAttribute(text, "VALUE"); + if (val) + { + strncpy(buffer, val, buffer_size); + buffer[buffer_size - 1] = '\0'; + } + } + + IupDestroy(dlg); + return result; +} \ No newline at end of file diff --git a/src/ui/main_window.c b/src/ui/main_window.c new file mode 100644 index 0000000..6ad6bab --- /dev/null +++ b/src/ui/main_window.c @@ -0,0 +1,146 @@ +#include "ui/main_window.h" +#include "controller/callbacks.h" +#include "config.h" +#include + +// 创建路径列表控件 +static Ihandle *create_path_list(const char *name) +{ + Ihandle *list = IupFlatList(); + IupSetAttribute(list, "NAME", name); + IupSetAttribute(list, "EXPAND", "YES"); + IupSetAttribute(list, "ITEMPADDING", UI_LIST_ITEM_PADDING); + IupSetAttribute(list, "BACKCOLOR", UI_LIST_BACKCOLOR); + IupSetAttribute(list, "BORDER", "YES"); + IupSetAttribute(list, "CANFOCUS", "YES"); + IupSetAttribute(list, "HLINE", "NO"); + IupSetCallback(list, "DBLCLICK_CB", (Icallback)list_dblclick_cb); + IupSetCallback(list, "DROPFILES_CB", (Icallback)list_dropfiles_cb); + IupSetCallback(list, "K_ANY", (Icallback)list_k_any_cb); + return list; +} + +// 创建主窗口 +Ihandle* create_main_window(void) +{ + // 创建系统路径列表 + Ihandle *list_sys = create_path_list("LIST_SYS"); + // 创建用户路径列表 + Ihandle *list_user = create_path_list("LIST_USER"); + + // 创建搜索框 + Ihandle *txt_search = IupText(NULL); + IupSetAttribute(txt_search, "NAME", "TXT_SEARCH"); + IupSetAttribute(txt_search, "EXPAND", "HORIZONTAL"); + IupSetAttribute(txt_search, "CUEBANNER", "输入关键词搜索..."); + IupSetCallback(txt_search, "VALUECHANGED_CB", (Icallback)txt_search_cb); + + // 创建选项卡 + Ihandle *tabs_main = IupTabs( + IupVbox(list_sys, NULL), + IupVbox(list_user, NULL), + NULL); + IupSetAttribute(tabs_main, "NAME", "TABS_MAIN"); + IupSetAttribute(tabs_main, "TABTITLE0", "系统变量 (System)"); + IupSetAttribute(tabs_main, "TABTITLE1", "用户变量 (User)"); + IupSetAttribute(tabs_main, "TABTYPE", "TOP"); + + // 创建操作按钮 + Ihandle *btn_new = IupButton("新建(N)", NULL); + IupSetAttribute(btn_new, "NAME", "BTN_NEW"); + Ihandle *btn_edit = IupButton("编辑(E)", NULL); + IupSetAttribute(btn_edit, "NAME", "BTN_EDIT"); + Ihandle *btn_browse = IupButton("浏览(B)...", NULL); + IupSetAttribute(btn_browse, "NAME", "BTN_BROWSE"); + Ihandle *btn_del = IupButton("删除(D)", NULL); + IupSetAttribute(btn_del, "NAME", "BTN_DEL"); + Ihandle *btn_up = IupButton("上移(U)", NULL); + IupSetAttribute(btn_up, "NAME", "BTN_UP"); + Ihandle *btn_down = IupButton("下移(O)", NULL); + IupSetAttribute(btn_down, "NAME", "BTN_DOWN"); + Ihandle *btn_clean = IupButton("一键清理", NULL); + IupSetAttribute(btn_clean, "NAME", "BTN_CLEAN"); + + // 设置按钮回调 + IupSetCallback(btn_new, "ACTION", (Icallback)btn_new_cb); + IupSetCallback(btn_edit, "ACTION", (Icallback)btn_edit_cb); + IupSetCallback(btn_browse, "ACTION", (Icallback)btn_browse_cb); + IupSetCallback(btn_del, "ACTION", (Icallback)btn_del_cb); + IupSetCallback(btn_up, "ACTION", (Icallback)btn_up_cb); + IupSetCallback(btn_down, "ACTION", (Icallback)btn_down_cb); + IupSetCallback(btn_clean, "ACTION", (Icallback)btn_clean_cb); + + // 设置按钮大小 + IupSetAttribute(btn_new, "RASTERSIZE", UI_BTN_RASTERSIZE); + IupSetAttribute(btn_edit, "RASTERSIZE", UI_BTN_RASTERSIZE); + IupSetAttribute(btn_browse, "RASTERSIZE", UI_BTN_RASTERSIZE); + IupSetAttribute(btn_del, "RASTERSIZE", UI_BTN_RASTERSIZE); + IupSetAttribute(btn_up, "RASTERSIZE", UI_BTN_RASTERSIZE); + IupSetAttribute(btn_down, "RASTERSIZE", UI_BTN_RASTERSIZE); + IupSetAttribute(btn_clean, "RASTERSIZE", UI_BTN_RASTERSIZE); + + // 创建操作按钮垂直布局 + Ihandle *vbox_btns = IupVbox( + btn_new, btn_edit, btn_browse, btn_del, + IupFill(), + btn_clean, + IupFill(), + btn_up, btn_down, + NULL); + IupSetAttribute(vbox_btns, "GAP", UI_VBOX_GAP); + IupSetAttribute(vbox_btns, "MARGIN", UI_VBOX_MARGIN); + + // 创建主窗口水平布局 + Ihandle *hbox_main = IupHbox(tabs_main, vbox_btns, NULL); + IupSetAttribute(hbox_main, "GAP", UI_HBOX_GAP); + IupSetAttribute(hbox_main, "MARGIN", UI_HBOX_MARGIN); + + // 创建状态标签 + Ihandle *lbl_status = IupLabel("状态: 就绪"); + IupSetAttribute(lbl_status, "NAME", "LBL_STATUS"); + IupSetAttribute(lbl_status, "EXPAND", "HORIZONTAL"); + + // 创建底部按钮 + Ihandle *btn_ok = IupButton("确定", NULL); + IupSetAttribute(btn_ok, "NAME", "BTN_OK"); + Ihandle *btn_cancel = IupButton("取消", NULL); + IupSetAttribute(btn_cancel, "NAME", "BTN_CANCEL"); + Ihandle *btn_help = IupButton("帮助(?)", NULL); + IupSetAttribute(btn_help, "NAME", "BTN_HELP"); + + // 设置底部按钮回调 + IupSetCallback(btn_ok, "ACTION", (Icallback)btn_ok_cb); + IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_cb); + IupSetCallback(btn_help, "ACTION", (Icallback)btn_help_cb); + + // 设置底部按钮大小 + IupSetAttribute(btn_ok, "RASTERSIZE", UI_BTN_RASTERSIZE); + IupSetAttribute(btn_cancel, "RASTERSIZE", UI_BTN_RASTERSIZE); + IupSetAttribute(btn_help, "RASTERSIZE", UI_BTN_RASTERSIZE); + + // 创建底部按钮水平布局 + Ihandle *hbox_bottom = IupHbox(lbl_status, IupFill(), btn_help, btn_ok, btn_cancel, NULL); + IupSetAttribute(hbox_bottom, "GAP", UI_HBOX_GAP); + IupSetAttribute(hbox_bottom, "MARGIN", UI_HBOX_MARGIN); + IupSetAttribute(hbox_bottom, "ALIGNMENT", UI_HBOX_ALIGNMENT); + + // 创建主窗口垂直布局 + Ihandle *vbox_all = IupVbox( + IupLabel("环境变量编辑器:"), + txt_search, + hbox_main, + hbox_bottom, + NULL); + IupSetAttribute(vbox_all, "MARGIN", UI_VBOX_ALL_MARGIN); + IupSetAttribute(vbox_all, "GAP", UI_VBOX_ALL_GAP); + + // 创建主窗口对话框 + Ihandle *dlg = IupDialog(vbox_all); + IupSetAttribute(dlg, "TITLE", APP_NAME); + IupSetAttribute(dlg, "RASTERSIZE", UI_DLG_SIZE); + IupSetAttribute(dlg, "MINSIZE", UI_DLG_MINSIZE); + IupSetAttribute(dlg, "MINBOX", "NO"); + IupSetAttribute(dlg, "MAXBOX", "NO"); + + return dlg; +} \ No newline at end of file diff --git a/src/ui/ui_utils.c b/src/ui/ui_utils.c new file mode 100644 index 0000000..c1075e6 --- /dev/null +++ b/src/ui/ui_utils.c @@ -0,0 +1,69 @@ +#include "ui/ui_utils.h" +#include "utils/os_env.h" +#include +#include + +// 刷新列表样式(斑马纹 + 有效性检查) +void refresh_single_list_style(Ihandle *list) +{ + if (!list) + return; + int count = IupGetInt(list, "COUNT"); + + for (int i = 1; i <= count; i++) + { + char *item = IupGetAttributeId(list, "", i); + if (!item) + continue; + + // 默认颜色:黑字 + char fg_color[32] = "0 0 0"; + + // 1. 检查有效性 + if (!is_path_valid(item)) + { + // 无效路径:红色 + sprintf(fg_color, "255 0 0"); + } + + // 2. 检查重复 (只检查当前项之前的项,如果之前出现过,当前项标橙) + for (int j = 1; j < i; j++) + { + char *prev_item = IupGetAttributeId(list, "", j); + if (prev_item && _stricmp(item, prev_item) == 0) // Windows 路径不区分大小写 + { + // 重复路径:橙色 + sprintf(fg_color, "255 128 0"); + break; + } + } + + IupSetAttributeId(list, "ITEMFGCOLOR", i, fg_color); + + // 斑马纹背景 + if (i % 2 == 0) + { + IupSetAttributeId(list, "ITEMBGCOLOR", i, "245 245 245"); + } + else + { + IupSetAttributeId(list, "ITEMBGCOLOR", i, "255 255 255"); + } + } +} + +// 同步字符串列表到 UI 列表 +void sync_string_list_to_ui(Ihandle *list_ui, const StringList *str_list) +{ + if (!list_ui || !str_list) return; + + IupSetAttribute(list_ui, "REMOVEITEM", "ALL"); + + for (int i = 0; i < str_list->count; i++) + { + IupSetAttributeId(list_ui, "", i + 1, str_list->items[i]); + } + IupSetInt(list_ui, "COUNT", str_list->count); + + refresh_single_list_style(list_ui); +} \ No newline at end of file diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index cd8dc28..0000000 --- a/src/utils.c +++ /dev/null @@ -1,233 +0,0 @@ -#include "utils.h" -#include "globals.h" -#include -#include -#include -#include -#include -#include - -// 宽字符转UTF-8 (用于IUP显示) -char *wide_to_utf8(const wchar_t *wstr) -{ - if (!wstr) - return NULL; - int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); - char *str = (char *)malloc(size_needed); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, size_needed, NULL, NULL); - return str; -} - -// UTF-8转宽字符 (用于Windows API) -wchar_t *utf8_to_wide(const char *str) -{ - if (!str) - return NULL; - int size_needed = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); - wchar_t *wstr = (wchar_t *)malloc(size_needed * sizeof(wchar_t)); - MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, size_needed); - return wstr; -} - -// 检查管理员权限 -int check_admin() -{ - HKEY hKey; - LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_WRITE, &hKey); - if (result == ERROR_SUCCESS) - { - RegCloseKey(hKey); - return 1; - } - return 0; -} - -// 检查路径是否存在 -static int path_exists(const char *path) -{ - // 如果包含 %,说明是变量,无法直接检查存在性,默认视为有效 - if (strchr(path, '%')) - return 1; - - wchar_t *wpath = utf8_to_wide(path); - if (!wpath) - return 0; - - DWORD attr = GetFileAttributesW(wpath); - free(wpath); - - if (attr == INVALID_FILE_ATTRIBUTES) - return 0; - return (attr & FILE_ATTRIBUTE_DIRECTORY); // 必须是目录 -} - -// 检查路径是否存在(公开给外部使用) -int is_path_valid(const char *path) -{ - return path_exists(path); -} - -// 刷新列表样式(斑马纹 + 有效性检查) -void refresh_single_list_style(Ihandle *list) -{ - if (!list) - return; - int count = IupGetInt(list, "COUNT"); - - // 用于查重的简单数组(实际项目可以用哈希表) - // 为了简单,我们只用双重循环检查重复,性能在几百个条目下没问题 - - for (int i = 1; i <= count; i++) - { - char *item = IupGetAttributeId(list, "", i); - if (!item) - continue; - - // 默认颜色:黑字 - char fg_color[32] = "0 0 0"; - - // 1. 检查有效性 - if (!path_exists(item)) - { - // 无效路径:红色 - sprintf(fg_color, "255 0 0"); - } - - // 2. 检查重复 (只检查当前项之前的项,如果之前出现过,当前项标橙) - for (int j = 1; j < i; j++) - { - char *prev_item = IupGetAttributeId(list, "", j); - if (prev_item && _stricmp(item, prev_item) == 0) // Windows 路径不区分大小写 - { - // 重复路径:橙色 - sprintf(fg_color, "255 128 0"); - break; - } - } - - IupSetAttributeId(list, "ITEMFGCOLOR", i, fg_color); - - // 斑马纹背景 - if (i % 2 == 0) - { - IupSetAttributeId(list, "ITEMBGCOLOR", i, "245 245 245"); - } - else - { - IupSetAttributeId(list, "ITEMBGCOLOR", i, "255 255 255"); - } - } -} - -// 刷新所有列表样式 -void refresh_list_style() -{ - refresh_single_list_style(list_sys); - refresh_single_list_style(list_user); -} - -// 备份注册表 -void backup_registry() -{ - // 创建 records 目录 - if (_mkdir("records") == -1) - { - // 目录可能已存在,忽略错误 - } - - // 获取当前时间 - time_t t = time(NULL); - struct tm *tm_info = localtime(&t); - char time_str[64]; - strftime(time_str, sizeof(time_str), "%Y%m%d_%H%M%S", tm_info); - - // 构造备份文件名 - char filename[256]; - snprintf(filename, sizeof(filename), "records\\backup_%s.reg", time_str); - - // 构造 reg export 命令 - char full_cmd[1024]; - snprintf(full_cmd, sizeof(full_cmd), "reg.exe export \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" \"%s\" /y", filename); - - // 使用 CreateProcess 隐藏窗口执行 - STARTUPINFOA si; - PROCESS_INFORMATION pi; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; // 隐藏窗口 - memset(&pi, 0, sizeof(pi)); - - if (CreateProcessA(NULL, full_cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) - { - // 等待进程结束 - WaitForSingleObject(pi.hProcess, INFINITE); - - DWORD exit_code = 0; - GetExitCodeProcess(pi.hProcess, &exit_code); - - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } -} - -// 初始化字符串列表 -void init_string_list(StringList *list) -{ - list->items = NULL; - list->count = 0; - list->capacity = 0; -} - -// 不区分大小写的字符串查找 (简单实现) -char *stristr(const char *haystack, const char *needle) -{ - if (!haystack || !needle) - return NULL; - if (*needle == '\0') - return (char *)haystack; - - const char *h = haystack; - const char *n = needle; - const char *h_start = haystack; - - while (*h) - { - h_start = h; - n = needle; - while (*h && *n && (tolower((unsigned char)*h) == tolower((unsigned char)*n))) - { - h++; - n++; - } - if (*n == '\0') - return (char *)h_start; - h = h_start + 1; - } - return NULL; -} - -// 添加字符串到列表 -void add_string_list(StringList *list, const char *str) -{ - if (list->count >= list->capacity) - { - list->capacity = (list->capacity == 0) ? 16 : list->capacity * 2; - list->items = (char **)realloc(list->items, list->capacity * sizeof(char *)); - } - list->items[list->count] = _strdup(str); // 复制字符串 - list->count++; -} - -// 清空字符串列表 -void clear_string_list(StringList *list) -{ - for (int i = 0; i < list->count; i++) - { - free(list->items[i]); - } - free(list->items); - list->items = NULL; - list->count = 0; - list->capacity = 0; -} \ No newline at end of file diff --git a/src/utils/os_env.c b/src/utils/os_env.c new file mode 100644 index 0000000..ae5f55c --- /dev/null +++ b/src/utils/os_env.c @@ -0,0 +1,90 @@ +#include "utils/os_env.h" +#include "utils/string_ext.h" +#include +#include +#include +#include +#include + +// 检查管理员权限 +int check_admin(void) +{ + HKEY hKey; + LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_WRITE, &hKey); + if (result == ERROR_SUCCESS) + { + RegCloseKey(hKey); + return 1; + } + return 0; +} + +// 内部:检查路径是否存在 +static int path_exists(const char *path) +{ + // 如果包含 %,说明是变量,无法直接检查存在性,默认视为有效 + if (strchr(path, '%')) + return 1; + + wchar_t *wpath = utf8_to_wide(path); + if (!wpath) + return 0; + + DWORD attr = GetFileAttributesW(wpath); + free(wpath); + + if (attr == INVALID_FILE_ATTRIBUTES) + return 0; + return (attr & FILE_ATTRIBUTE_DIRECTORY); // 必须是目录 +} + +// 检查路径是否存在(公开给外部使用) +int is_path_valid(const char *path) +{ + return path_exists(path); +} + +// 备份注册表 +void backup_registry(void) +{ + // 创建 records 目录 + if (_mkdir("records") == -1) + { + // 目录可能已存在,忽略错误 + } + + // 获取当前时间 + time_t t = time(NULL); + struct tm *tm_info = localtime(&t); + char time_str[64]; + strftime(time_str, sizeof(time_str), "%Y%m%d_%H%M%S", tm_info); + + // 构造备份文件名 + char filename[256]; + snprintf(filename, sizeof(filename), "records\\backup_%s.reg", time_str); + + // 构造 reg export 命令 + char full_cmd[1024]; + snprintf(full_cmd, sizeof(full_cmd), "reg.exe export \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" \"%s\" /y", filename); + + // 使用 CreateProcess 隐藏窗口执行 + STARTUPINFOA si; + PROCESS_INFORMATION pi; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; // 隐藏窗口 + memset(&pi, 0, sizeof(pi)); + + if (CreateProcessA(NULL, full_cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) + { + // 等待进程结束 + WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD exit_code = 0; + GetExitCodeProcess(pi.hProcess, &exit_code); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } +} \ No newline at end of file diff --git a/src/utils/string_ext.c b/src/utils/string_ext.c new file mode 100644 index 0000000..6ea5080 --- /dev/null +++ b/src/utils/string_ext.c @@ -0,0 +1,101 @@ +#include "utils/string_ext.h" +#include +#include +#include +#include +#include + +// 宽字符转UTF-8 (用于IUP显示) +char *wide_to_utf8(const wchar_t *wstr) +{ + if (!wstr) + return NULL; + int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + char *str = (char *)malloc(size_needed); + if (str) + { + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, size_needed, NULL, NULL); + } + return str; +} + +// UTF-8转宽字符 (用于Windows API) +wchar_t *utf8_to_wide(const char *str) +{ + if (!str) + return NULL; + int size_needed = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + wchar_t *wstr = (wchar_t *)malloc(size_needed * sizeof(wchar_t)); + if (wstr) + { + MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, size_needed); + } + return wstr; +} + +// 初始化字符串列表 +void init_string_list(StringList *list) +{ + if (!list) + return; + list->items = NULL; + list->count = 0; + list->capacity = 0; +} + +// 不区分大小写的字符串查找 (简单实现) +char *stristr(const char *haystack, const char *needle) +{ + if (!haystack || !needle) + return NULL; + if (*needle == '\0') + return (char *)haystack; + + const char *h = haystack; + const char *n = needle; + const char *h_start = haystack; + + while (*h) + { + h_start = h; + n = needle; + while (*h && *n && (tolower((unsigned char)*h) == tolower((unsigned char)*n))) + { + h++; + n++; + } + if (*n == '\0') + return (char *)h_start; + h = h_start + 1; + } + return NULL; +} + +// 添加字符串到列表 +void add_string_list(StringList *list, const char *str) +{ + if (!list || !str) + return; + if (list->count >= list->capacity) + { + list->capacity = (list->capacity == 0) ? 16 : list->capacity * 2; + list->items = (char **)realloc(list->items, list->capacity * sizeof(char *)); + } + list->items[list->count] = _strdup(str); // 复制字符串 + list->count++; +} + +// 清空字符串列表 +void clear_string_list(StringList *list) +{ + if (!list) + return; + for (int i = 0; i < list->count; i++) + { + free(list->items[i]); + } + free(list->items); + list->items = NULL; + list->count = 0; + list->capacity = 0; +} \ No newline at end of file