diff --git a/README.md b/README.md index 4004784..ba7f810 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## ✨ 功能特点 * **🛡️ 安全第一**: - * **自动备份**:每次保存前自动备份注册表,防止意外。 + * **自动备份**:每次保存前自动备份,支持自定义备份目录(默认 `%APPDATA%/PathEditor/backups/`)。 * **只读模式**:非管理员运行时自动切换到只读模式,防止误操作。 * **权限检测**:智能检测当前运行权限。 @@ -29,6 +29,10 @@ * **导入恢复**:从 JSON 文件导入路径配置。 * **格式兼容**:支持旧版 TXT 格式导入。 +* **🌍 多语言支持**: + * 内置中文和英文界面,支持运行时切换。 + * 基于 gettext 国际化框架,易于扩展其他语言。 + * **便捷管理**: * ➕ **新建**:添加新路径到列表。 * 📂 **浏览**:直接从文件资源管理器选择目录添加。 @@ -45,14 +49,23 @@ * **分层设计**: * `src/core/` (Model): 核心数据与业务逻辑,完全脱离 UI 框架(无任何 `` 依赖)。 * `src/ui/` (View): 负责界面布局与组件的纯视觉展示。 - * `src/controller/` (Controller): 负责连接用户交互与底层数据。 + * `src/controller/` (Controller): 负责连接用户交互与底层数据,已按功能拆分为 6 个模块: + * `callbacks.c` - 辅助函数与上下文管理 + * `callbacks_basic.c` - 基础 CRUD 操作(新建、编辑、浏览、删除) + * `callbacks_nav.c` - 导航操作(上移、下移、清理) + * `callbacks_search.c` - 搜索过滤与拖拽 + * `callbacks_io.c` - 导入导出 + * `callbacks_sys.c` - 系统操作(保存、取消、帮助、语言切换) * `src/utils/` (Utils): 纯粹的底层工具类封装(系统级调用、字符串处理)。 * **热配置系统**:所有 UI 参数(窗口大小、按钮文本、布局间距等)均通过 `lua/config.lua` 配置,修改无需重新编译即可生效。 +* **国际化支持**:基于 gettext 框架,支持中英文运行时切换,语言文件位于 `locale/` 目录。 * **清晰的应用状态**:摒弃了脆弱的全局变量模式,采用 `AppContext` 统一管理应用运行时的上下文状态,通过指针传递,安全可靠。 * **开发工具库**: - * 统一错误码系统 (`utils/error_code.h`) + * 统一错误码系统 (`utils/error_code.h`) - 11 种细分错误码 * 安全字符串函数 (`utils/safe_string.h`) - * 日志系统 (`utils/logger.h`) + * 字符串列表封装 (`utils/string_ext.h`) - 带访问器函数的安全动态数组 + * 日志系统 (`utils/logger.h`) - 支持 DEBUG/INFO/WARN/ERROR 四个级别 + * 控件名称常量 (`utils/ui_constants.h`) - 集中管理所有 IUP 控件名称 ## 📦 下载与安装 diff --git a/include/utils/os_env.h b/include/utils/os_env.h index fd7e90e..5b84251 100644 --- a/include/utils/os_env.h +++ b/include/utils/os_env.h @@ -10,6 +10,7 @@ int check_admin(void); int is_path_valid(const char *path); // 备份注册表 -ErrorCode backup_registry(void); +// 参数 backup_path: 自定义备份目录路径,传 NULL 使用 Lua 配置中的默认路径 +ErrorCode backup_registry(const char *backup_path); #endif // OS_ENV_H diff --git a/lua/config.lua b/lua/config.lua index 41c4d3f..dbe9534 100644 --- a/lua/config.lua +++ b/lua/config.lua @@ -15,6 +15,11 @@ local config = { select_dir = "Select Directory" }, + -- 备份设置 + backup = { + dir = "", -- 默认备份目录,留空使用 %APPDATA%/PathEditor/backups/ + }, + -- 列表控件设置 list = { item_padding = "5x5", diff --git a/src/controller/callbacks_sys.c b/src/controller/callbacks_sys.c index 16a7321..15fdf50 100644 --- a/src/controller/callbacks_sys.c +++ b/src/controller/callbacks_sys.c @@ -28,23 +28,71 @@ int btn_ok_cb(Ihandle *self) return IUP_DEFAULT; } - ErrorCode backup_result = backup_registry(); - if (backup_result != ERR_OK) - { - log_error("Backup failed: error code %d", backup_result); - const char *reason = "未知错误"; - if (backup_result == ERR_FAILED) - reason = "无法获取 AppData 路径"; - else if (backup_result == ERR_FILE_NOT_FOUND) - reason = "无法创建备份目录或文件"; - else if (backup_result == ERR_REGISTRY_FAILED) - reason = "无法读取注册表中的 PATH 值"; + // 询问用户是否自定义备份目录 + char custom_backup_dir[MAX_PATH] = ""; + int do_backup = 1; // 是否执行备份 - char msg[512]; - snprintf(msg, sizeof(msg), "备份失败!原因:%s\n\n是否继续保存?\n(继续保存可能导致无法恢复)", reason); - int choice = IupAlarm("警告", msg, "继续保存", "取消", NULL); - if (choice != 1) - return IUP_DEFAULT; + int backup_choice = IupAlarm("备份设置", + "是否自定义备份目录?\n\n" + "选择「使用默认」将备份到 %APPDATA%/PathEditor/backups/\n" + "选择「自定义目录」可选择其他位置", + "使用默认", "自定义目录", "跳过备份"); + + if (backup_choice == 2) // 自定义目录 + { + 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) + strncpy(custom_backup_dir, value, MAX_PATH - 1); + } + IupDestroy(filedlg); + + if (strlen(custom_backup_dir) == 0) + { + IupMessage("提示", "未选择目录,将使用默认备份路径。"); + } + } + else if (backup_choice == 3) // 跳过备份 + { + int skip_confirm = IupAlarm("确认", "确定跳过备份吗?\n跳过备份可能导致无法恢复!", + "确定跳过", "返回备份", NULL); + if (skip_confirm != 1) + { + // 用户反悔,重新询问 + return btn_ok_cb(self); + } + do_backup = 0; + } + + // 执行备份(如果用户没有跳过) + if (do_backup) + { + const char *backup_path = strlen(custom_backup_dir) > 0 ? custom_backup_dir : NULL; + ErrorCode backup_result = backup_registry(backup_path); + if (backup_result != ERR_OK) + { + log_error("Backup failed: error code %d", backup_result); + const char *reason = "未知错误"; + if (backup_result == ERR_FAILED) + reason = "无法获取 AppData 路径"; + else if (backup_result == ERR_FILE_NOT_FOUND) + reason = "无法创建备份目录或文件"; + else if (backup_result == ERR_REGISTRY_FAILED) + reason = "无法读取注册表中的 PATH 值"; + + char msg[512]; + snprintf(msg, sizeof(msg), "备份失败!原因:%s\n\n是否继续保存?\n(继续保存可能导致无法恢复)", reason); + int choice = IupAlarm("警告", msg, "继续保存", "取消", NULL); + if (choice != 1) + return IUP_DEFAULT; + } } ErrorCode sys_ok = save_system_paths(&ctx->sys_paths); diff --git a/src/utils/os_env.c b/src/utils/os_env.c index 4310a29..987aad1 100644 --- a/src/utils/os_env.c +++ b/src/utils/os_env.c @@ -1,6 +1,7 @@ #include "utils/os_env.h" #include "utils/string_ext.h" #include "utils/logger.h" +#include "core/lua_config.h" #include #include #include @@ -47,25 +48,65 @@ int is_path_valid(const char *path) } // 备份注册表 -// 备份到 %APPDATA%/PathEditor/backups/ 目录下 +// 参数 backup_path: 自定义备份目录路径,传 NULL 使用 Lua 配置中的默认路径 // 返回值: // ERR_OK - 备份成功(系统和用户 PATH 都已备份) // ERR_FAILED - AppData 路径获取失败 // ERR_FILE_NOT_FOUND - 备份文件创建失败 // ERR_REGISTRY_FAILED - 系统和用户 PATH 都读取失败 -ErrorCode backup_registry(void) +ErrorCode backup_registry(const char *backup_path) { - // 获取 AppData 路径 - wchar_t appdata_path[MAX_PATH]; - if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, appdata_path) != S_OK) + wchar_t backup_dir[MAX_PATH]; + + if (backup_path && strlen(backup_path) > 0) { - log_error("Failed to get AppData path"); - return ERR_FAILED; + // 使用用户指定的路径 + wchar_t *wpath = utf8_to_wide(backup_path); + if (wpath) + { + wcsncpy(backup_dir, wpath, MAX_PATH - 1); + backup_dir[MAX_PATH - 1] = L'\0'; + free(wpath); + } + else + { + log_error("Failed to convert backup path to wide string"); + return ERR_FAILED; + } + } + else + { + // 使用默认路径:Lua 配置 > %APPDATA%/PathEditor/backups/ + const char *lua_dir = lua_config_get_string("backup", "dir"); + if (lua_dir && strlen(lua_dir) > 0) + { + wchar_t *wpath = utf8_to_wide(lua_dir); + if (wpath) + { + wcsncpy(backup_dir, wpath, MAX_PATH - 1); + backup_dir[MAX_PATH - 1] = L'\0'; + free(wpath); + } + else + { + log_error("Failed to convert Lua backup dir to wide string"); + return ERR_FAILED; + } + } + else + { + // 获取 AppData 路径作为默认值 + wchar_t appdata_path[MAX_PATH]; + if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, appdata_path) != S_OK) + { + log_error("Failed to get AppData path"); + return ERR_FAILED; + } + swprintf(backup_dir, MAX_PATH, L"%s\\PathEditor\\backups", appdata_path); + } } // 创建备份目录(递归创建中间目录) - wchar_t backup_dir[MAX_PATH]; - swprintf(backup_dir, MAX_PATH, L"%s\\PathEditor\\backups", appdata_path); if (SHCreateDirectoryExW(NULL, backup_dir, NULL) != ERROR_SUCCESS) { log_error("Failed to create backup directory: %ls", backup_dir);