mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-05-10 10:19:47 +08:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 720ebb535d | |||
| 3df2988915 | |||
| c5c5517ded | |||
| 485d16a180 | |||
| e5d24389b4 | |||
| 5bb0ac66cf | |||
| ec0ca5a3f6 | |||
| bbcfc25aea | |||
| a8002aeba4 | |||
| aac4cc4b54 | |||
| c8d9b32c70 | |||
| f9628f6e8c | |||
| e0af409ef5 | |||
| 256e793ee4 | |||
| 8bd8c0a0aa | |||
| 1f48551199 | |||
| 06e4c15b5c | |||
| 32e6bb262b | |||
| cf19a37a97 | |||
| ceed90aea8 | |||
| 3bc2f00cb1 | |||
| e777b26879 | |||
| 7908bad1f4 | |||
| ea3d678d22 | |||
| 86792012e2 | |||
| 4fe7dc47e4 | |||
| 9a78b88c4a | |||
| 3af0e96060 | |||
| 6ba7e702f2 | |||
| 9aa1e208ba | |||
| d934d21323 | |||
| 8767271e96 | |||
| 55ff64b92d | |||
| 55d0f80743 | |||
| ce232cb024 | |||
| bd1b05be55 | |||
| a769a6b9b3 | |||
| 6509ef98e4 | |||
| c928c271e8 | |||
| 02e702b285 | |||
| af3138c146 | |||
| 6e6adf3b85 | |||
| e84b33c5ca | |||
| ac6b409f3a | |||
| 1bbe95582a | |||
| 3ecf35963d | |||
| 276d2c5fe3 | |||
| a9339f9b9f | |||
| 7fac2aab35 |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(cmake --build build)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
# 定义项目信息
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(PathEditor VERSION 3.0 LANGUAGES C)
|
||||||
|
|
||||||
|
# 选项:是否构建测试
|
||||||
|
option(BUILD_TESTS "Build unit tests" OFF)
|
||||||
|
|
||||||
|
# 启用资源编译器以处理 .rc 文件
|
||||||
|
enable_language(RC)
|
||||||
|
|
||||||
|
# 设置 C 标准
|
||||||
|
set(CMAKE_C_STANDARD 17)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF) # 禁用特定编译器的扩展(如 gnu17),强制使用标准 C17
|
||||||
|
|
||||||
|
# 定义源文件
|
||||||
|
set(SOURCES
|
||||||
|
src/main.c
|
||||||
|
src/utils/string_ext.c
|
||||||
|
src/utils/os_env.c
|
||||||
|
src/utils/safe_string.c
|
||||||
|
src/utils/logger.c
|
||||||
|
src/utils/i18n.c
|
||||||
|
src/utils/error_code.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/core/lua_config.c
|
||||||
|
src/core/import_export.c
|
||||||
|
src/core/undo_redo.c
|
||||||
|
src/controller/callbacks.c
|
||||||
|
src/controller/callbacks_basic.c
|
||||||
|
src/controller/callbacks_nav.c
|
||||||
|
src/controller/callbacks_search.c
|
||||||
|
src/controller/callbacks_io.c
|
||||||
|
src/controller/callbacks_sys.c
|
||||||
|
ico/resources.rc
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建 GUI 可执行文件(WIN32 属性会自动添加 -mwindows 参数)
|
||||||
|
add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
|
||||||
|
|
||||||
|
# 添加宏定义
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||||
|
_WIN32
|
||||||
|
UNICODE
|
||||||
|
_UNICODE
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加编译选项
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
|
target_compile_options(${PROJECT_NAME} PRIVATE
|
||||||
|
-Wall
|
||||||
|
-O2
|
||||||
|
-fexec-charset=UTF-8
|
||||||
|
)
|
||||||
|
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
|
||||||
|
${CMAKE_SOURCE_DIR}/libs/lua/include
|
||||||
|
${CMAKE_SOURCE_DIR}/libs/gettext/include
|
||||||
|
)
|
||||||
|
|
||||||
|
# 设置库文件搜索路径
|
||||||
|
target_link_directories(${PROJECT_NAME} PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/libs/IUP
|
||||||
|
${CMAKE_SOURCE_DIR}/libs/lua
|
||||||
|
${CMAKE_SOURCE_DIR}/libs/gettext/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
# 链接所需库
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
|
lua55
|
||||||
|
iup
|
||||||
|
iupcd
|
||||||
|
gdi32
|
||||||
|
comdlg32
|
||||||
|
comctl32
|
||||||
|
uuid
|
||||||
|
ole32
|
||||||
|
advapi32
|
||||||
|
intl
|
||||||
|
iconv
|
||||||
|
)
|
||||||
|
|
||||||
|
# 编译完成后,复制程序实际需要的核心 DLL 文件到构建输出目录
|
||||||
|
set(IUP_REQUIRED_DLLS "${CMAKE_CURRENT_SOURCE_DIR}/libs/IUP/iup.dll")
|
||||||
|
set(LUA_REQUIRED_DLLS "${CMAKE_CURRENT_SOURCE_DIR}/libs/lua/lua55.dll")
|
||||||
|
set(GETTEXT_REQUIRED_DLLS "${CMAKE_CURRENT_SOURCE_DIR}/libs/gettext/bin/libintl-8.dll")
|
||||||
|
set(ICONV_REQUIRED_DLLS "${CMAKE_CURRENT_SOURCE_DIR}/libs/gettext/bin/libiconv-2.dll")
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${IUP_REQUIRED_DLLS}
|
||||||
|
"$<TARGET_FILE_DIR:${PROJECT_NAME}>"
|
||||||
|
COMMENT "Copying required DLLs to build directory..."
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${LUA_REQUIRED_DLLS}
|
||||||
|
"$<TARGET_FILE_DIR:${PROJECT_NAME}>"
|
||||||
|
COMMENT "Copying Lua DLL to build directory..."
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${GETTEXT_REQUIRED_DLLS}
|
||||||
|
"$<TARGET_FILE_DIR:${PROJECT_NAME}>"
|
||||||
|
COMMENT "Copying gettext DLL to build directory..."
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${ICONV_REQUIRED_DLLS}
|
||||||
|
"$<TARGET_FILE_DIR:${PROJECT_NAME}>"
|
||||||
|
COMMENT "Copying iconv DLL to build directory..."
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
${CMAKE_SOURCE_DIR}/locale
|
||||||
|
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/locale"
|
||||||
|
COMMENT "Copying locale directory to build directory..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 测试支持
|
||||||
|
if(BUILD_TESTS)
|
||||||
|
enable_testing()
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
CC = gcc
|
|
||||||
WINDRES = windres
|
|
||||||
|
|
||||||
# Paths - specific to user environment
|
|
||||||
IUP_DIR = libs/iup-3.31_Win64_dllw6_lib
|
|
||||||
INCLUDE_DIR = $(IUP_DIR)/include
|
|
||||||
LIB_DIR = $(IUP_DIR)
|
|
||||||
LOCAL_INCLUDE_DIR = include
|
|
||||||
|
|
||||||
# Output Directories
|
|
||||||
OBJ_DIR = obj
|
|
||||||
BIN_DIR = bin
|
|
||||||
|
|
||||||
# Flags
|
|
||||||
# -mwindows: Create GUI app (no console)
|
|
||||||
# -DUNICODE -D_UNICODE: Use Wide Character API
|
|
||||||
CFLAGS = -Wall -O2 -I$(INCLUDE_DIR) -I$(LOCAL_INCLUDE_DIR) -D_WIN32 -DUNICODE -D_UNICODE -fexec-charset=UTF-8
|
|
||||||
LDFLAGS = -L$(LIB_DIR) -liup -liupcd -lgdi32 -lcomdlg32 -lcomctl32 -luuid -lole32 -ladvapi32 -mwindows
|
|
||||||
|
|
||||||
# Source
|
|
||||||
SRC = src/main.c src/utils.c src/registry.c src/callbacks.c src/ui.c
|
|
||||||
RES = ico/resources.rc
|
|
||||||
OBJ = $(OBJ_DIR)/main.o $(OBJ_DIR)/utils.o $(OBJ_DIR)/registry.o $(OBJ_DIR)/callbacks.o $(OBJ_DIR)/ui.o $(OBJ_DIR)/resources.o
|
|
||||||
EXE = $(BIN_DIR)/PathEditor.exe
|
|
||||||
|
|
||||||
all: $(BIN_DIR) $(OBJ_DIR) $(EXE)
|
|
||||||
|
|
||||||
$(BIN_DIR):
|
|
||||||
if not exist $(BIN_DIR) mkdir $(BIN_DIR)
|
|
||||||
|
|
||||||
$(OBJ_DIR):
|
|
||||||
if not exist $(OBJ_DIR) mkdir $(OBJ_DIR)
|
|
||||||
|
|
||||||
$(EXE): $(OBJ)
|
|
||||||
$(CC) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
$(OBJ_DIR)/main.o: src/main.c
|
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
$(OBJ_DIR)/utils.o: src/utils.c
|
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
$(OBJ_DIR)/registry.o: src/registry.c
|
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
$(OBJ_DIR)/callbacks.o: src/callbacks.c
|
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
$(OBJ_DIR)/ui.o: src/ui.c
|
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
$(OBJ_DIR)/resources.o: ico/resources.rc
|
|
||||||
$(WINDRES) -i $< -o $@
|
|
||||||
|
|
||||||
clean:
|
|
||||||
if exist $(OBJ_DIR)\*.o del /Q $(OBJ_DIR)\*.o
|
|
||||||
if exist $(BIN_DIR)\*.exe del /Q $(BIN_DIR)\*.exe
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
## ✨ 功能特点
|
## ✨ 功能特点
|
||||||
|
|
||||||
* **🛡️ 安全第一**:
|
* **🛡️ 安全第一**:
|
||||||
* **自动备份**:每次保存前自动备份注册表,防止意外。
|
* **自动备份**:每次保存前自动备份,支持自定义备份目录(默认 `%APPDATA%/PathEditor/backups/`)。
|
||||||
* **只读模式**:非管理员运行时自动切换到只读模式,防止误操作。
|
* **只读模式**:非管理员运行时自动切换到只读模式,防止误操作。
|
||||||
* **权限检测**:智能检测当前运行权限。
|
* **权限检测**:智能检测当前运行权限。
|
||||||
|
|
||||||
@@ -24,6 +24,15 @@
|
|||||||
* **实时搜索**:顶部搜索框支持不区分大小写的实时过滤查找。
|
* **实时搜索**:顶部搜索框支持不区分大小写的实时过滤查找。
|
||||||
* **快捷键**:支持 Delete 键快速删除选中项。
|
* **快捷键**:支持 Delete 键快速删除选中项。
|
||||||
|
|
||||||
|
* **🔄 导入导出**:
|
||||||
|
* **导出备份**:将 PATH 导出为 JSON 文件,方便备份和迁移。
|
||||||
|
* **导入恢复**:从 JSON 文件导入路径配置。
|
||||||
|
* **格式兼容**:支持旧版 TXT 格式导入。
|
||||||
|
|
||||||
|
* **🌍 多语言支持**:
|
||||||
|
* 内置中文和英文界面,支持运行时切换。
|
||||||
|
* 基于 gettext 国际化框架,易于扩展其他语言。
|
||||||
|
|
||||||
* **便捷管理**:
|
* **便捷管理**:
|
||||||
* ➕ **新建**:添加新路径到列表。
|
* ➕ **新建**:添加新路径到列表。
|
||||||
* 📂 **浏览**:直接从文件资源管理器选择目录添加。
|
* 📂 **浏览**:直接从文件资源管理器选择目录添加。
|
||||||
@@ -33,6 +42,31 @@
|
|||||||
|
|
||||||
* **轻量级**:原生 C 语言编写,无臃肿依赖,运行速度极快。
|
* **轻量级**:原生 C 语言编写,无臃肿依赖,运行速度极快。
|
||||||
|
|
||||||
|
## 🛠️ 架构与二次开发
|
||||||
|
|
||||||
|
本项目注重代码的模块化和可维护性,采用了经典的 **MVC 分层架构**,非常适合作为 C 语言桌面程序开发的参考:
|
||||||
|
|
||||||
|
* **分层设计**:
|
||||||
|
* `src/core/` (Model): 核心数据与业务逻辑,完全脱离 UI 框架(无任何 `<iup.h>` 依赖)。
|
||||||
|
* `src/ui/` (View): 负责界面布局与组件的纯视觉展示。
|
||||||
|
* `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`) - 11 种细分错误码
|
||||||
|
* 安全字符串函数 (`utils/safe_string.h`)
|
||||||
|
* 字符串列表封装 (`utils/string_ext.h`) - 带访问器函数的安全动态数组
|
||||||
|
* 日志系统 (`utils/logger.h`) - 支持 DEBUG/INFO/WARN/ERROR 四个级别
|
||||||
|
* 控件名称常量 (`utils/ui_constants.h`) - 集中管理所有 IUP 控件名称
|
||||||
|
|
||||||
## 📦 下载与安装
|
## 📦 下载与安装
|
||||||
|
|
||||||
您可以从 [Releases](https://github.com/LHY0125/PathEditor/releases) 页面下载最新的安装包 (`PathEditorSetup.exe`)。
|
您可以从 [Releases](https://github.com/LHY0125/PathEditor/releases) 页面下载最新的安装包 (`PathEditorSetup.exe`)。
|
||||||
@@ -47,11 +81,13 @@
|
|||||||
|
|
||||||
* Windows 操作系统
|
* Windows 操作系统
|
||||||
* GCC 编译器 (推荐 MinGW-w64)
|
* GCC 编译器 (推荐 MinGW-w64)
|
||||||
* Make 工具
|
* CMake 工具 (推荐使用 CMake 构建)
|
||||||
* IUP 库 (已包含在 `libs` 目录下)
|
* IUP 库 (已包含在 `libs` 目录下)
|
||||||
* Inno Setup 6 (仅打包需要)
|
* Inno Setup 6 (仅打包需要)
|
||||||
|
|
||||||
### 编译步骤
|
### 编译步骤 (推荐使用 CMake)
|
||||||
|
|
||||||
|
本项目已迁移至 CMake 构建系统,支持生成更标准的构建文件并集成到各大 IDE。
|
||||||
|
|
||||||
1. 克隆仓库:
|
1. 克隆仓库:
|
||||||
|
|
||||||
@@ -60,14 +96,18 @@
|
|||||||
cd PathEditor
|
cd PathEditor
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 编译项目:
|
2. 使用 CMake 配置和编译:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mingw32-make
|
# 生成构建系统 (以 MinGW 为例)
|
||||||
|
cmake -B build -G "MinGW Makefiles"
|
||||||
|
|
||||||
|
# 编译项目
|
||||||
|
cmake --build build
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 运行:
|
3. 运行:
|
||||||
编译成功后,可执行文件位于 `bin/PathEditor.exe`。
|
编译成功后,可执行文件位于 `build/PathEditor.exe`。
|
||||||
|
|
||||||
### 打包 (可选)
|
### 打包 (可选)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,7 +1,4 @@
|
|||||||
@echo off
|
@echo off
|
||||||
echo Copying DLLs...
|
|
||||||
if not exist bin mkdir bin
|
|
||||||
copy /Y "libs\iup-3.31_Win64_dllw6_lib\*.dll" bin\
|
|
||||||
|
|
||||||
echo Building Installer...
|
echo Building Installer...
|
||||||
"D:\Program Files (x86)\Inno Setup 6\ISCC.exe" "dist\installer.iss"
|
"D:\Program Files (x86)\Inno Setup 6\ISCC.exe" "dist\installer.iss"
|
||||||
|
|||||||
Vendored
+2
-2
@@ -37,8 +37,8 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "d:\Code\doing_exercises\programs\PathEditor\bin\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "d:\Code\doing_exercises\programs\PathEditor\build\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
Source: "d:\Code\doing_exercises\programs\PathEditor\bin\*.dll"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "d:\Code\doing_exercises\programs\PathEditor\build\*.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
# PathEditor 代码审查修复日志
|
||||||
|
|
||||||
|
> 审查日期:2026-04-28
|
||||||
|
> 审查范围:全项目代码质量审查
|
||||||
|
> 修复状态:29/29 已完成
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、高严重度问题(7 项)
|
||||||
|
|
||||||
|
### 1.1 add_string_list 中 realloc 失败导致内存泄漏
|
||||||
|
|
||||||
|
- **文件**:`src/utils/string_ext.c`
|
||||||
|
- **问题**:`realloc` 返回 NULL 时,原 `list->items` 指针被覆盖为 NULL,导致原有数组及其中所有字符串指针永久泄漏
|
||||||
|
- **修复**:使用临时变量保存 `realloc` 结果,失败时保留原数据不变
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 修复前
|
||||||
|
list->items = (char **)realloc(list->items, list->capacity * sizeof(char *));
|
||||||
|
|
||||||
|
// 修复后
|
||||||
|
char **new_items = (char **)realloc(list->items, new_capacity * sizeof(char *));
|
||||||
|
if (!new_items)
|
||||||
|
return; // 失败时保留原数据
|
||||||
|
list->items = new_items;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 add_string_list 中 _strdup 失败未检查
|
||||||
|
|
||||||
|
- **文件**:`src/utils/string_ext.c`
|
||||||
|
- **问题**:`_strdup` 可能返回 NULL,但代码未检查就存入数组并递增 count,后续字符串操作会崩溃
|
||||||
|
- **修复**:检查 `_strdup` 返回值,失败时不递增 count
|
||||||
|
|
||||||
|
### 1.3 string_list_set 中先 free 后 strdup 导致数据丢失
|
||||||
|
|
||||||
|
- **文件**:`src/utils/string_ext.c`
|
||||||
|
- **问题**:先释放旧字符串,然后 `_strdup` 可能失败,导致旧数据丢失且无法恢复
|
||||||
|
- **修复**:先调用 `_strdup` 获取新字符串,成功后再释放旧字符串
|
||||||
|
|
||||||
|
### 1.4 wcsftime 缓冲区大小以字节而非宽字符数传入
|
||||||
|
|
||||||
|
- **文件**:`src/utils/os_env.c`
|
||||||
|
- **问题**:`sizeof(timestamp)` 返回字节数(128),而 `wcsftime` 期望宽字符数(64),可能导致栈缓冲区溢出
|
||||||
|
- **修复**:改用 `sizeof(timestamp) / sizeof(timestamp[0])`
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 修复前
|
||||||
|
wcsftime(timestamp, sizeof(timestamp), L"%Y%m%d_%H%M%S", tm_info);
|
||||||
|
|
||||||
|
// 修复后
|
||||||
|
wcsftime(timestamp, sizeof(timestamp) / sizeof(timestamp[0]), L"%Y%m%d_%H%M%S", &tm_info);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 load_single_path 在 malloc 失败时仍返回 ERR_OK
|
||||||
|
|
||||||
|
- **文件**:`src/core/registry_service.c`
|
||||||
|
- **问题**:`malloc` 失败时跳过数据读取,但仍返回 `ERR_OK`,调用者认为加载成功
|
||||||
|
- **修复**:`malloc` 失败时返回 `ERR_OUT_OF_MEMORY`
|
||||||
|
|
||||||
|
### 1.6 导入数据内存泄漏
|
||||||
|
|
||||||
|
- **文件**:`src/controller/callbacks_io.c`
|
||||||
|
- **问题**:`btn_import_cb` 中 `ExportData imported` 的 `system` 和 `user` 从未调用 `clear_string_list` 释放
|
||||||
|
- **修复**:在函数所有返回路径前调用 `clear_string_list` 释放导入数据
|
||||||
|
|
||||||
|
### 1.7 JSON 导入解析器完全失效
|
||||||
|
|
||||||
|
- **文件**:`src/core/import_export.c`
|
||||||
|
- **问题**:键名检测逻辑被 `in_string` 状态翻转拦截,`"system"` 和 `"user"` 的检测永远无法触发
|
||||||
|
- **修复**:重写 JSON 解析器,在字符串结束时检测键名,合并重复的数组解析逻辑
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、中严重度问题(9 项)
|
||||||
|
|
||||||
|
### 2.1 backup_registry 部分备份成功时错误报告为完全成功
|
||||||
|
|
||||||
|
- **文件**:`src/utils/os_env.c`
|
||||||
|
- **修复**:分别跟踪系统和用户 PATH 的备份状态
|
||||||
|
|
||||||
|
### 2.2 btn_ok_cb 未检查 backup_registry 返回值
|
||||||
|
|
||||||
|
- **文件**:`src/controller/callbacks_sys.c`
|
||||||
|
- **修复**:检查返回值,备份失败时提示用户是否继续保存
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 修复后
|
||||||
|
ErrorCode backup_result = backup_registry();
|
||||||
|
if (backup_result != ERR_OK)
|
||||||
|
{
|
||||||
|
int choice = IupAlarm("警告", "备份失败!是否继续保存?",
|
||||||
|
"继续保存", "取消", NULL);
|
||||||
|
if (choice != 1)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 TABTITLE 设置在 Dialog 而非 Tabs 控件上
|
||||||
|
|
||||||
|
- **文件**:`src/ui/main_window.c`
|
||||||
|
- **问题**:`TABTITLE0` 和 `TABTITLE1` 是 `IupTabs` 属性,不是 `IupDialog` 属性,导致语言切换后选项卡标题不更新
|
||||||
|
- **修复**:先获取 Tabs 控件句柄,再设置 TABTITLE
|
||||||
|
|
||||||
|
### 2.4 list_dropfiles_cb 使用 ANSI 版本 API
|
||||||
|
|
||||||
|
- **文件**:`src/controller/callbacks_search.c`
|
||||||
|
- **问题**:`GetFileAttributesA` 无法正确处理中文等 Unicode 字符路径
|
||||||
|
- **修复**:改用 `utf8_to_wide` + `GetFileAttributesW`
|
||||||
|
|
||||||
|
### 2.5 load_all_paths 用户路径加载失败时未通知用户
|
||||||
|
|
||||||
|
- **文件**:`src/controller/callbacks_sys.c`
|
||||||
|
- **修复**:添加 else 分支,在用户路径加载失败时弹出提示
|
||||||
|
|
||||||
|
### 2.6 escape_json_string 未处理所有控制字符
|
||||||
|
|
||||||
|
- **文件**:`src/core/import_export.c`
|
||||||
|
- **问题**:只处理了 `\\`、`"`、`\n`、`\r`、`\t`,其他 0x00-0x1F 控制字符未转义
|
||||||
|
- **修复**:添加 `\b`、`\f` 处理,其他控制字符使用 `\uXXXX` 格式
|
||||||
|
|
||||||
|
### 2.7 CreateDirectoryW 不创建中间目录
|
||||||
|
|
||||||
|
- **文件**:`src/utils/os_env.c`
|
||||||
|
- **修复**:改用 `SHCreateDirectoryExW` 递归创建目录
|
||||||
|
|
||||||
|
### 2.8 ExportData 浅拷贝隐患
|
||||||
|
|
||||||
|
- **文件**:`include/core/import_export.h`
|
||||||
|
- **修复**:添加文档注释说明只读语义,防止误用 `clear_string_list`
|
||||||
|
|
||||||
|
### 2.9 export_paths_to_file 未检查 fprintf 返回值
|
||||||
|
|
||||||
|
- **文件**:`src/core/import_export.c`
|
||||||
|
- **修复**:在 `fclose` 后检查 `ferror(fp)`,发现错误时返回 `ERR_FAILED`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、低严重度问题(13 项)
|
||||||
|
|
||||||
|
### 3.1 JSON 解析器代码重复
|
||||||
|
|
||||||
|
- **文件**:`src/core/import_export.c`
|
||||||
|
- **修复**:合并 system/user 数组解析逻辑为统一的键名检测机制
|
||||||
|
|
||||||
|
### 3.2 get_app_context_from_dlg 依赖指针到字符串的不安全转换
|
||||||
|
|
||||||
|
- **状态**:保留现状(IUP 框架标准用法)
|
||||||
|
|
||||||
|
### 3.3 putenv 使用字符串字面量的可移植性问题
|
||||||
|
|
||||||
|
- **文件**:`src/main.c`
|
||||||
|
- **修复**:改用 `_wputenv_s`
|
||||||
|
|
||||||
|
### 3.4 path_manager_clean 时间复杂度为 O(n³)
|
||||||
|
|
||||||
|
- **文件**:`src/core/path_manager.c`
|
||||||
|
- **修复**:使用标记+批量删除优化为 O(n²)
|
||||||
|
|
||||||
|
### 3.5 全局日志状态非线程安全
|
||||||
|
|
||||||
|
- **状态**:保留现状(当前是单线程 GUI 应用)
|
||||||
|
|
||||||
|
### 3.6 localtime 返回静态缓冲区指针,非线程安全
|
||||||
|
|
||||||
|
- **文件**:`src/utils/os_env.c`、`src/core/import_export.c`
|
||||||
|
- **修复**:改用 `localtime_s`
|
||||||
|
|
||||||
|
### 3.7 JSON 解析器原地修改输入缓冲区
|
||||||
|
|
||||||
|
- **修复**:重写解析器时已解决,不再修改原始缓冲区
|
||||||
|
|
||||||
|
### 3.8 JSON 解析器对 `\\"` 场景处理错误
|
||||||
|
|
||||||
|
- **修复**:重写解析器时已解决,使用 `is_quote_escaped` 检查连续反斜杠
|
||||||
|
|
||||||
|
### 3.9 TXT 格式导入只能导入到系统路径
|
||||||
|
|
||||||
|
- **文件**:`src/controller/callbacks_io.c`
|
||||||
|
- **修复**:添加选择对话框,允许用户选择导入到系统变量或用户变量
|
||||||
|
|
||||||
|
### 3.10 trim_whitespace 未检查 NULL 输入
|
||||||
|
|
||||||
|
- **文件**:`src/core/import_export.c`
|
||||||
|
- **修复**:添加防御性 NULL 检查
|
||||||
|
|
||||||
|
### 3.11 is_json_file 使用 strcasecmp 限制可移植性
|
||||||
|
|
||||||
|
- **文件**:`src/core/import_export.c`
|
||||||
|
- **修复**:改用 `_stricmp`(MSVC/MinGW 都支持)
|
||||||
|
|
||||||
|
### 3.12 localtime 可能返回 NULL
|
||||||
|
|
||||||
|
- **修复**:改用 `localtime_s`,无需 NULL 检查
|
||||||
|
|
||||||
|
### 3.13 main.c 中 _() 嵌套调用可能崩溃
|
||||||
|
|
||||||
|
- **文件**:`src/main.c`
|
||||||
|
- **问题**:`_(lua_config_get_string(...))` 嵌套调用,若配置键不存在,`gettext(NULL)` 行为未定义
|
||||||
|
- **修复**:先将结果保存到临时变量并检查非 NULL
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 修复前
|
||||||
|
IupMessage(_("Warning"), _(lua_config_get_string("status", "admin_warning")));
|
||||||
|
|
||||||
|
// 修复后
|
||||||
|
const char *admin_msg = lua_config_get_string("status", "admin_warning");
|
||||||
|
IupMessage(_("Warning"), admin_msg ? _(admin_msg) : "需要管理员权限才能编辑环境变量");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、第一轮修复(原10条锐评)
|
||||||
|
|
||||||
|
| # | 问题 | 状态 |
|
||||||
|
|---|------|------|
|
||||||
|
| 1 | core层用了iup.h(架构矛盾) | ✅ 已修复 |
|
||||||
|
| 2 | main.c管理员权限检测代码灾难 | ✅ 已修复 |
|
||||||
|
| 3 | refresh_main_window_ui重复代码 | ✅ 已修复 |
|
||||||
|
| 4 | 字符串溢出风险(buffer不统一) | ✅ 已修复 |
|
||||||
|
| 5 | 错误码乱用(ERR_NULL_PTR误用) | ✅ 已修复 |
|
||||||
|
| 6 | 硬编码字符串满天飞 | ✅ 已修复 |
|
||||||
|
| 7 | backup_registry未实现 | ✅ 已修复 |
|
||||||
|
| 8 | callbacks.c 600行回调地狱 | ✅ 已拆分 |
|
||||||
|
| 9 | StringList封装不透明 | ✅ 已修复 |
|
||||||
|
| 10 | refresh_single_list_style黑盒 | ✅ 已修复 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、修改文件清单
|
||||||
|
|
||||||
|
| 文件 | 修改类型 |
|
||||||
|
|------|----------|
|
||||||
|
| `include/utils/string_ext.h` | 添加访问器函数声明 |
|
||||||
|
| `include/utils/ui_constants.h` | 新建,控件名称常量 |
|
||||||
|
| `include/core/import_export.h` | 添加文档注释 |
|
||||||
|
| `include/controller/callbacks_internal.h` | 新建,内部辅助函数声明 |
|
||||||
|
| `src/utils/string_ext.c` | 修复 realloc/strdup 安全性 |
|
||||||
|
| `src/utils/os_env.c` | 修复 wcsftime、localtime、目录创建 |
|
||||||
|
| `src/core/registry_service.c` | 修复 malloc 错误处理 |
|
||||||
|
| `src/core/import_export.c` | 重写 JSON 解析器、修复转义函数 |
|
||||||
|
| `src/core/path_manager.c` | 优化清理算法、修复错误码 |
|
||||||
|
| `src/main.c` | 修复 putenv、_() 嵌套调用 |
|
||||||
|
| `src/ui/main_window.c` | 修复 TABTITLE 设置位置 |
|
||||||
|
| `src/controller/callbacks.c` | 拆分后只保留辅助函数 |
|
||||||
|
| `src/controller/callbacks_basic.c` | 新建,基础 CRUD 回调 |
|
||||||
|
| `src/controller/callbacks_nav.c` | 新建,导航回调 |
|
||||||
|
| `src/controller/callbacks_search.c` | 新建,搜索/拖拽回调 |
|
||||||
|
| `src/controller/callbacks_io.c` | 新建,导入导出回调 |
|
||||||
|
| `src/controller/callbacks_sys.c` | 新建,系统操作回调 |
|
||||||
|
| `CMakeLists.txt` | 添加新源文件 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、编译验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -B build -G "MinGW Makefiles"
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果**:编译通过,无错误,无警告。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、剩余已知问题(设计决策,非 Bug)
|
||||||
|
|
||||||
|
| 问题 | 原因 | 建议 |
|
||||||
|
|------|------|------|
|
||||||
|
| IUP 指针存储模式 | IUP 框架标准用法 | 保持现状 |
|
||||||
|
| 日志线程安全 | 当前是单线程应用 | 多线程时再处理 |
|
||||||
|
| localtime 线程安全 | 已改用 localtime_s | 已修复 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*审查完成于 2026-04-28*
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
# 撤销/重做 UI 集成 — 实现计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 将已完成的撤销/重做后端逻辑暴露到 UI 层——添加撤销/重做按钮及 Ctrl+Z/Ctrl+Y 快捷键
|
||||||
|
|
||||||
|
**Architecture:** 在现有 MVC 架构上,新增两个按钮回调(`btn_undo_cb`/`btn_redo_cb`),它们调用 `undo_redo.c` 中已有的 `undo()`/`redo()` 函数,然后同步 UI 列表。Ctrl+Z/Y 快捷键在 `list_k_any_cb` 中检测并分发到对应的按钮回调。
|
||||||
|
|
||||||
|
**Tech Stack:** C17, IUP GUI, GCC/MinGW-w64
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
| 操作 | 文件 | 职责 |
|
||||||
|
|------|------|------|
|
||||||
|
| 修改 | `include/utils/ui_constants.h` | 新增 2 个按钮名称常量 |
|
||||||
|
| 修改 | `include/controller/callbacks.h` | 声明 `btn_undo_cb` / `btn_redo_cb` |
|
||||||
|
| 修改 | `src/controller/callbacks_nav.c` | 实现 `btn_undo_cb` / `btn_redo_cb`,`list_k_any_cb` 增加 Ctrl+Z/Y |
|
||||||
|
| 修改 | `src/ui/main_window.c` | 创建撤销/重做按钮,加入布局,绑定回调 |
|
||||||
|
| 修改 | `lua/config.lua` | 新增 `button.undo` / `button.redo` 配置 |
|
||||||
|
| 修改 | `po/zh_CN.po` | 新增 "Undo"→"撤销" / "Redo"→"重做" 翻译 |
|
||||||
|
| 修改 | `po/en_US.po` | 新增 "Undo"→"Undo" / "Redo"→"Redo" 翻译 |
|
||||||
|
| 修改 | `po/messages.pot` | 新增 msgid 条目 |
|
||||||
|
| 重新生成 | `locale/zh_CN/LC_MESSAGES/zh_CN.mo` | msgfmt 编译 |
|
||||||
|
| 重新生成 | `locale/en_US/LC_MESSAGES/en_US.mo` | msgfmt 编译 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: 添加 UI 常量
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `include/utils/ui_constants.h`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 ui_constants.h 添加按钮常量**
|
||||||
|
|
||||||
|
在 `CTRL_BTN_LANG` 之后、`#endif` 之前添加:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 撤销/重做按钮
|
||||||
|
#define CTRL_BTN_UNDO "BTN_UNDO"
|
||||||
|
#define CTRL_BTN_REDO "BTN_REDO"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add include/utils/ui_constants.h
|
||||||
|
git commit -m "feat(undo): 添加撤销/重做按钮的 UI 常量"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: 声明回调函数
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `include/controller/callbacks.h`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 callbacks.h 声明新回调函数**
|
||||||
|
|
||||||
|
在 `btn_lang_cb` 声明之后、搜索回调声明之前添加:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 撤销/重做回调
|
||||||
|
int btn_undo_cb(Ihandle *self);
|
||||||
|
int btn_redo_cb(Ihandle *self);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add include/controller/callbacks.h
|
||||||
|
git commit -m "feat(undo): 声明撤销/重做按钮回调函数"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: 实现撤销/重做回调逻辑
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/controller/callbacks_nav.c`
|
||||||
|
|
||||||
|
> 需要新增的 include:`#include "core/app_context.h"`(已存在)、`#include "ui/ui_utils.h"`(已存在)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 callbacks_nav.c 头部添加 app_context 头文件引用**
|
||||||
|
|
||||||
|
检查第 3 行已有 `#include "core/undo_redo.h"`,第 10 行已有 `#include "utils/ui_constants.h"`。确认无需添加新的 include。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 添加刷新撤销/重做按钮状态的辅助函数**
|
||||||
|
|
||||||
|
在文件末尾 `list_k_any_cb` 之前添加:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 刷新撤销/重做按钮的启用状态
|
||||||
|
static void refresh_undo_redo_buttons(Ihandle *dlg)
|
||||||
|
{
|
||||||
|
AppContext *ctx = get_app_context_from_dlg(dlg);
|
||||||
|
if (!ctx || !ctx->undo_redo_mgr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Ihandle *btn_undo = IupGetDialogChild(dlg, CTRL_BTN_UNDO);
|
||||||
|
Ihandle *btn_redo = IupGetDialogChild(dlg, CTRL_BTN_REDO);
|
||||||
|
|
||||||
|
if (btn_undo)
|
||||||
|
IupSetAttribute(btn_undo, "ACTIVE", can_undo(ctx->undo_redo_mgr) ? "YES" : "NO");
|
||||||
|
if (btn_redo)
|
||||||
|
IupSetAttribute(btn_redo, "ACTIVE", can_redo(ctx->undo_redo_mgr) ? "YES" : "NO");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 实现 btn_undo_cb**
|
||||||
|
|
||||||
|
在 `refresh_undo_redo_buttons` 之后添加:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int btn_undo_cb(Ihandle *self)
|
||||||
|
{
|
||||||
|
Ihandle *dlg = IupGetDialog(self);
|
||||||
|
AppContext *ctx = get_app_context_from_dlg(dlg);
|
||||||
|
if (!ctx || !ctx->undo_redo_mgr)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
|
if (!can_undo(ctx->undo_redo_mgr))
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
|
undo(ctx->undo_redo_mgr, &ctx->sys_paths, &ctx->user_paths);
|
||||||
|
|
||||||
|
Ihandle *list_sys = IupGetDialogChild(dlg, CTRL_LIST_SYS);
|
||||||
|
Ihandle *list_user = IupGetDialogChild(dlg, CTRL_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, CTRL_LBL_STATUS);
|
||||||
|
if (lbl_status)
|
||||||
|
IupSetAttribute(lbl_status, "TITLE", _("Undo completed"));
|
||||||
|
|
||||||
|
refresh_undo_redo_buttons(dlg);
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 实现 btn_redo_cb**
|
||||||
|
|
||||||
|
在 `btn_undo_cb` 之后添加:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int btn_redo_cb(Ihandle *self)
|
||||||
|
{
|
||||||
|
Ihandle *dlg = IupGetDialog(self);
|
||||||
|
AppContext *ctx = get_app_context_from_dlg(dlg);
|
||||||
|
if (!ctx || !ctx->undo_redo_mgr)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
|
if (!can_redo(ctx->undo_redo_mgr))
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
|
redo(ctx->undo_redo_mgr, &ctx->sys_paths, &ctx->user_paths);
|
||||||
|
|
||||||
|
Ihandle *list_sys = IupGetDialogChild(dlg, CTRL_LIST_SYS);
|
||||||
|
Ihandle *list_user = IupGetDialogChild(dlg, CTRL_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, CTRL_LBL_STATUS);
|
||||||
|
if (lbl_status)
|
||||||
|
IupSetAttribute(lbl_status, "TITLE", _("Redo completed"));
|
||||||
|
|
||||||
|
refresh_undo_redo_buttons(dlg);
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 修改 list_k_any_cb 添加 Ctrl+Z / Ctrl+Y 检测**
|
||||||
|
|
||||||
|
将现有的 `list_k_any_cb` 函数(第 149-164 行)替换为:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int list_k_any_cb(Ihandle *self, int c)
|
||||||
|
{
|
||||||
|
if (IupGetInt(self, "ACTIVE") == 0)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
|
if (c == K_cZ) // Ctrl+Z 撤销
|
||||||
|
{
|
||||||
|
btn_undo_cb(self);
|
||||||
|
return IUP_IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == K_cY) // Ctrl+Y 重做
|
||||||
|
{
|
||||||
|
btn_redo_cb(self);
|
||||||
|
return IUP_IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == K_DEL) // DEL 键
|
||||||
|
{
|
||||||
|
btn_del_cb(self);
|
||||||
|
return IUP_IGNORE;
|
||||||
|
}
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/controller/callbacks_nav.c
|
||||||
|
git commit -m "feat(undo): 实现撤销/重做按钮回调及 Ctrl+Z/Y 快捷键"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: 在 UI 中添加撤销/重做按钮
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ui/main_window.c`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 main_window.c 头部添加 app_context 引用**
|
||||||
|
|
||||||
|
检查当前 include。如果尚未包含 `<iupkey.h>`,需要在 `#include <iup.h>` 之后添加(但 iup.h 通常已包含 iupkey.h)。确认无需新增 include。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 创建撤销/重做按钮并绑定回调**
|
||||||
|
|
||||||
|
在 `btn_down` 按钮创建之后(第 67 行附近)、`btn_clean` 之前,添加新按钮:
|
||||||
|
|
||||||
|
```c
|
||||||
|
Ihandle *btn_undo = IupButton(_(lua_config_get_string("button", "undo")), NULL);
|
||||||
|
IupSetAttribute(btn_undo, "NAME", CTRL_BTN_UNDO);
|
||||||
|
IupSetAttribute(btn_undo, "ACTIVE", "NO"); // 初始无操作可撤销
|
||||||
|
|
||||||
|
Ihandle *btn_redo = IupButton(_(lua_config_get_string("button", "redo")), NULL);
|
||||||
|
IupSetAttribute(btn_redo, "NAME", CTRL_BTN_REDO);
|
||||||
|
IupSetAttribute(btn_redo, "ACTIVE", "NO"); // 初始无操作可重做
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 设置按钮回调**
|
||||||
|
|
||||||
|
在现有按钮回调设置区域(第 84-93 行),`btn_down` 回调之后添加:
|
||||||
|
|
||||||
|
```c
|
||||||
|
IupSetCallback(btn_undo, "ACTION", (Icallback)btn_undo_cb);
|
||||||
|
IupSetCallback(btn_redo, "ACTION", (Icallback)btn_redo_cb);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 设置按钮大小**
|
||||||
|
|
||||||
|
在按钮大小设置区域(第 96-106 行),`btn_export` 之后添加:
|
||||||
|
|
||||||
|
```c
|
||||||
|
IupSetAttribute(btn_undo, "RASTERSIZE", btn_size);
|
||||||
|
IupSetAttribute(btn_redo, "RASTERSIZE", btn_size);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 将按钮加入垂直布局**
|
||||||
|
|
||||||
|
修改 `vbox_btns` 布局(第 109-118 行),在 `btn_up, btn_down` 之后、`NULL` 之前加入 `btn_undo, btn_redo`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
Ihandle *vbox_btns = IupVbox(
|
||||||
|
btn_new, btn_edit, btn_browse, btn_del,
|
||||||
|
IupFill(),
|
||||||
|
btn_clean,
|
||||||
|
IupFill(),
|
||||||
|
btn_import, btn_export,
|
||||||
|
btn_up, btn_down,
|
||||||
|
btn_undo, btn_redo,
|
||||||
|
NULL);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: 在 refresh_main_window_ui 中添加按钮文本刷新**
|
||||||
|
|
||||||
|
在 `refresh_main_window_ui` 函数的 `SET_CHILD_TITLE` 宏调用区域(第 198-209 行),`CTRL_BTN_EXPORT` 之后添加:
|
||||||
|
|
||||||
|
```c
|
||||||
|
SET_CHILD_TITLE(CTRL_BTN_UNDO, "undo");
|
||||||
|
SET_CHILD_TITLE(CTRL_BTN_REDO, "redo");
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 7: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ui/main_window.c
|
||||||
|
git commit -m "feat(undo): 在 UI 中添加撤销/重做按钮并集成布局"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: 更新 Lua 配置
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `lua/config.lua`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 config.lua 的 button 表中添加 undo 和 redo**
|
||||||
|
|
||||||
|
在 `button` 表的 `help = "Help"` 之后添加:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
undo = "Undo",
|
||||||
|
redo = "Redo",
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add lua/config.lua
|
||||||
|
git commit -m "feat(undo): 在 Lua 配置中添加撤销/重做按钮文本"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: 更新翻译文件
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `po/zh_CN.po`
|
||||||
|
- Modify: `po/en_US.po`
|
||||||
|
- Modify: `po/messages.pot`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 zh_CN.po 中添加翻译条目**
|
||||||
|
|
||||||
|
在 "Clean Invalid" 条目之后、其他条目之前插入:
|
||||||
|
|
||||||
|
```
|
||||||
|
#: src/ui/main_window.c
|
||||||
|
msgid "Undo"
|
||||||
|
msgstr "撤销"
|
||||||
|
|
||||||
|
#: src/ui/main_window.c
|
||||||
|
msgid "Redo"
|
||||||
|
msgstr "重做"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
以及状态栏消息:
|
||||||
|
|
||||||
|
```
|
||||||
|
#: src/controller/callbacks_nav.c
|
||||||
|
msgid "Undo completed"
|
||||||
|
msgstr "已撤销"
|
||||||
|
|
||||||
|
#: src/controller/callbacks_nav.c
|
||||||
|
msgid "Redo completed"
|
||||||
|
msgstr "已重做"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 在 en_US.po 中添加翻译条目**
|
||||||
|
|
||||||
|
```
|
||||||
|
#: src/ui/main_window.c
|
||||||
|
msgid "Undo"
|
||||||
|
msgstr "Undo"
|
||||||
|
|
||||||
|
#: src/ui/main_window.c
|
||||||
|
msgid "Redo"
|
||||||
|
msgstr "Redo"
|
||||||
|
|
||||||
|
#: src/controller/callbacks_nav.c
|
||||||
|
msgid "Undo completed"
|
||||||
|
msgstr "Undo completed"
|
||||||
|
|
||||||
|
#: src/controller/callbacks_nav.c
|
||||||
|
msgid "Redo completed"
|
||||||
|
msgstr "Redo completed"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 在 messages.pot 中添加翻译模板条目**
|
||||||
|
|
||||||
|
```
|
||||||
|
#: src/ui/main_window.c
|
||||||
|
msgid "Undo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ui/main_window.c
|
||||||
|
msgid "Redo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/controller/callbacks_nav.c
|
||||||
|
msgid "Undo completed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/controller/callbacks_nav.c
|
||||||
|
msgid "Redo completed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 重新编译 .mo 文件**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd D:/Code/doing_exercises/programs/PathEditor
|
||||||
|
msgfmt po/zh_CN.po -o locale/zh_CN/LC_MESSAGES/zh_CN.mo
|
||||||
|
msgfmt po/en_US.po -o locale/en_US/LC_MESSAGES/en_US.mo
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add po/zh_CN.po po/en_US.po po/messages.pot locale/zh_CN/LC_MESSAGES/zh_CN.mo locale/en_US/LC_MESSAGES/en_US.mo
|
||||||
|
git commit -m "feat(undo): 添加撤销/重做的中英文翻译"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: 编译验证
|
||||||
|
|
||||||
|
- [ ] **Step 1: 编译项目**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd D:/Code/doing_exercises/programs/PathEditor
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
预期输出:编译成功,无错误无警告。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 功能验证清单**
|
||||||
|
1. 启动程序,确认出现「撤销」「重做」按钮
|
||||||
|
2. 新建一条路径 → 撤销按钮变可用 → 点击撤销 → 路径消失 → 重做按钮变可用
|
||||||
|
3. 按 Ctrl+Z 撤销 → Ctrl+Y 重做
|
||||||
|
4. 删除一条路径 → 撤销恢复 → 重做再次删除
|
||||||
|
5. 无历史记录时,撤销/重做按钮灰色不可点击
|
||||||
|
6. 语言切换后按钮文本正确切换
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 全局快捷键 — 设计文档
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
Ctrl+Z/Y 撤销/重做已在功能 1 中实现(列表级 K_ANY)。新增 Ctrl+N/S/F 作为对话框级的全局快捷键。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
添加三个全局快捷键:`Ctrl+N` 新建、`Ctrl+S` 保存、`Ctrl+F` 聚焦搜索框。
|
||||||
|
|
||||||
|
## 改动文件
|
||||||
|
|
||||||
|
| 文件 | 改动 |
|
||||||
|
|------|------|
|
||||||
|
| `include/controller/callbacks.h` | 声明 `dlg_k_any_cb` |
|
||||||
|
| `src/controller/callbacks_sys.c` | 实现 `dlg_k_any_cb` |
|
||||||
|
| `src/ui/main_window.c` | 对话框注册 `K_ANY` 回调 |
|
||||||
|
|
||||||
|
## 核心逻辑
|
||||||
|
|
||||||
|
```
|
||||||
|
dlg_k_any_cb(dlg, c):
|
||||||
|
if c == K_cN → btn_new_cb(dlg)
|
||||||
|
if c == K_cS → btn_ok_cb(dlg)
|
||||||
|
if c == K_cF → IupSetFocus(txt_search)
|
||||||
|
else → IUP_DEFAULT
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快捷键传播
|
||||||
|
|
||||||
|
IUP 键盘事件从子控件向父控件传播。列表的 `list_k_any_cb`(Ctrl+Z/Y/DEL)返回 `IUP_IGNORE` 阻止传播;未识别的键返回 `IUP_DEFAULT` 使事件继续传播到对话框的 `dlg_k_any_cb`。
|
||||||
|
|
||||||
|
## 不做的事
|
||||||
|
|
||||||
|
- 不新增翻译条目
|
||||||
|
- 不修改 Lua 配置
|
||||||
|
- 列表级 `K_ANY` 保持不变
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# 撤销/重做 UI 集成 — 设计文档
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
撤销/重做后端(`src/core/undo_redo.c`)已完整实现,支持 7 种操作类型的记录与回滚。所有 UI 操作(新建/编辑/删除/上移/下移/清理)均已调用 `push_undo_record()` 写入历史。但 `undo()` 和 `redo()` 函数未被任何 UI 代码调用——用户无法触发撤销或重做。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
在界面上添加撤销/重做按钮,并绑定 Ctrl+Z / Ctrl+Y 快捷键,让用户可以回退和恢复操作。
|
||||||
|
|
||||||
|
## 改动文件
|
||||||
|
|
||||||
|
| 文件 | 改动内容 |
|
||||||
|
|------|---------|
|
||||||
|
| `include/utils/ui_constants.h` | 新增 `CTRL_BTN_UNDO`、`CTRL_BTN_REDO` 常量 |
|
||||||
|
| `src/ui/main_window.c` | 创建撤销/重做按钮,绑定回调,调整布局 |
|
||||||
|
| `src/controller/callbacks_nav.c` | 新增 `btn_undo_cb`、`btn_redo_cb`,`list_k_any_cb` 增加 Ctrl+Z/Y 检测 |
|
||||||
|
| `lua/config.lua` | 新增 `button.undo`、`button.redo` 配置项 |
|
||||||
|
| `locale/` 翻译文件 | 同步新增按钮的中英文翻译 |
|
||||||
|
|
||||||
|
## 核心逻辑
|
||||||
|
|
||||||
|
```
|
||||||
|
btn_undo_cb(dlg):
|
||||||
|
ctx = get_app_context_from_dlg(dlg)
|
||||||
|
if !can_undo(ctx->undo_redo_mgr): return
|
||||||
|
undo(ctx->undo_redo_mgr, &ctx->sys_paths, &ctx->user_paths)
|
||||||
|
sync both lists to UI
|
||||||
|
update undo/redo button enabled state
|
||||||
|
|
||||||
|
btn_redo_cb(dlg):
|
||||||
|
同上,调用 redo()
|
||||||
|
|
||||||
|
list_k_any_cb:
|
||||||
|
新增分支:
|
||||||
|
if c == K_cZ → btn_undo_cb
|
||||||
|
if c == K_cY → btn_redo_cb
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按钮布局
|
||||||
|
|
||||||
|
撤销/重做按钮放在上移/下移按钮下方:
|
||||||
|
|
||||||
|
```
|
||||||
|
[新建] [编辑]
|
||||||
|
[浏览] [删除]
|
||||||
|
(分隔)
|
||||||
|
[一键清理]
|
||||||
|
(分隔)
|
||||||
|
[导入] [导出]
|
||||||
|
[上移] [下移]
|
||||||
|
[撤销] [重做] ← 新增
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按钮状态
|
||||||
|
|
||||||
|
- `can_undo() == false` → 撤销按钮 `ACTIVE=NO`
|
||||||
|
- `can_redo() == false` → 重做按钮 `ACTIVE=NO`
|
||||||
|
- 每次 undo/redo 执行后刷新按钮状态
|
||||||
|
|
||||||
|
## 不做的事
|
||||||
|
|
||||||
|
- 不修改 `undo_redo.c` 后端代码(已完备)
|
||||||
|
- 不添加操作历史面板(保持简洁,通过按钮状态反馈即可)
|
||||||
|
- 不在保存后清空历史(当前设计由 `clear_undo_redo_history` 决定,保持现有行为)
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
# 1. 问题
|
||||||
|
|
||||||
|
这个问题位于系统 PATH 保存回调与注册表备份工具之间的交界处。当前实现虽然在保存前调用了备份,但备份结果没有进入成功失败判断,导致“先备份再写入”只剩下调用顺序,没有形成真正的安全护栏。
|
||||||
|
|
||||||
|
## 1.1. **备份没有成为保存前置条件**
|
||||||
|
|
||||||
|
`src/controller/callbacks_sys.c:25-34` 的 `btn_ok_cb` 在管理员校验后直接调用 `backup_registry()`,随后继续执行 `save_system_paths` 和 `save_user_paths`。这里最大的问题不是“没做备份”,而是“做了也等于没做校验”。
|
||||||
|
|
||||||
|
```c
|
||||||
|
if (!check_admin())
|
||||||
|
{
|
||||||
|
IupMessage("错误", "需要管理员权限才能保存更改!");
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_registry();
|
||||||
|
|
||||||
|
ErrorCode sys_ok = save_system_paths(&ctx->sys_paths);
|
||||||
|
ErrorCode user_ok = save_user_paths(&ctx->user_paths);
|
||||||
|
```
|
||||||
|
|
||||||
|
这会带来两个直接后果:
|
||||||
|
|
||||||
|
- 备份目录创建失败、备份文件打开失败、注册表读取失败时,保存流程仍然继续。
|
||||||
|
- 用户看到的只是“保存成功”或“保存失败”,看不到“备份失败但仍然写入”的中间状态。
|
||||||
|
|
||||||
|
对于修改系统 PATH 这类高风险操作,备份不是可有可无的副作用,而应该是明确的前置步骤。否则一旦写入结果不符合预期,用户既没有可靠回退副本,也很难知道问题发生在哪一段。
|
||||||
|
|
||||||
|
## 1.2. **备份函数的失败语义过于粗糙**
|
||||||
|
|
||||||
|
`src/utils/os_env.c:50-131` 的 `backup_registry` 把多个失败场景都折叠成 `ERR_FAILED`,而且只用一个 `success` 标记表示“系统 PATH 或用户 PATH 只要有一处写入成功即可”。这让调用方几乎无法做出正确反馈。
|
||||||
|
|
||||||
|
```c
|
||||||
|
if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, appdata_path) != S_OK)
|
||||||
|
{
|
||||||
|
return ERR_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = _wfopen(backup_file, L"w, ccs=UTF-8");
|
||||||
|
if (!fp)
|
||||||
|
return ERR_FAILED;
|
||||||
|
...
|
||||||
|
return success ? ERR_OK : ERR_FAILED;
|
||||||
|
```
|
||||||
|
|
||||||
|
这里的问题有两层:
|
||||||
|
|
||||||
|
- 调用方拿不到失败原因,界面层只能给出笼统提示。
|
||||||
|
- 备份结果粒度太粗,后续如果要区分“文件系统失败”“系统 PATH 读取失败”“用户 PATH 读取失败”,需要再次拆函数,改动会扩散。
|
||||||
|
|
||||||
|
这类错误语义粗糙的问题,短期看只是提示不够友好,长期看会让保存链路的错误处理越来越分散。
|
||||||
|
|
||||||
|
# 2. 收益
|
||||||
|
|
||||||
|
把备份纳入保存链路的显式校验后,PATH 修改流程会从“尽力而为”变成“可判断、可阻断、可回退”。
|
||||||
|
|
||||||
|
## 2.1. **降低误写后无法回退的风险**
|
||||||
|
|
||||||
|
当前最危险的情况,是备份失败但注册表写入成功。调整后,保存前先确认备份成功,能直接消除这条风险路径。对系统 PATH 这种会影响命令行、编译器和工具链的配置,这个收益是最核心的。
|
||||||
|
|
||||||
|
## 2.2. **让失败位置更容易定位**
|
||||||
|
|
||||||
|
现在保存链路里至少有 **3** 个备份失败点:目录准备、文件创建、注册表读取。把这些失败显式返回后,界面层和日志可以明确知道失败发生在保存前,而不是笼统归为“保存异常”。
|
||||||
|
|
||||||
|
## 2.3. **减少后续错误处理分散**
|
||||||
|
|
||||||
|
把备份检查集中到保存入口,可以让 `btn_ok_cb` 成为统一决策点。后续如果要加入“跳过备份需二次确认”或“导出备份路径”,只需要在一个入口扩展,而不是在多个保存分支里补判断。
|
||||||
|
|
||||||
|
# 3. 方案
|
||||||
|
|
||||||
|
总体思路是:把备份从隐式副作用改成显式的保存前置步骤,同时把备份失败原因转换成 UI 可消费的信息。
|
||||||
|
|
||||||
|
## 3.1. **引入保存前置校验:解决“备份没有成为保存前置条件”**
|
||||||
|
|
||||||
|
方案核心是为 `btn_ok_cb` 增加一个明确的“准备保存”阶段。只有备份成功,后续系统 PATH 和用户 PATH 的写入才允许继续。
|
||||||
|
|
||||||
|
实施步骤:
|
||||||
|
|
||||||
|
- 在控制层增加 `ErrorCode backup_ok = backup_registry();` 的显式判断。
|
||||||
|
- 当备份失败时,立即记录日志、更新状态栏并弹出错误提示,然后提前返回。
|
||||||
|
- 将“广播环境变量变更”和“保存成功提示”保留在备份成功且写入成功之后。
|
||||||
|
|
||||||
|
修改前:
|
||||||
|
|
||||||
|
```c
|
||||||
|
backup_registry();
|
||||||
|
ErrorCode sys_ok = save_system_paths(&ctx->sys_paths);
|
||||||
|
ErrorCode user_ok = save_user_paths(&ctx->user_paths);
|
||||||
|
```
|
||||||
|
|
||||||
|
修改后(实际实现):
|
||||||
|
|
||||||
|
```c
|
||||||
|
ErrorCode backup_result = backup_registry();
|
||||||
|
if (backup_result != ERR_OK)
|
||||||
|
{
|
||||||
|
log_error("Backup failed: error code %d", backup_result);
|
||||||
|
int choice = IupAlarm("警告", "备份失败!是否继续保存?\n(继续保存可能导致无法恢复)",
|
||||||
|
"继续保存", "取消", NULL);
|
||||||
|
if (choice != 1)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode sys_ok = save_system_paths(&ctx->sys_paths);
|
||||||
|
ErrorCode user_ok = save_user_paths(&ctx->user_paths);
|
||||||
|
```
|
||||||
|
|
||||||
|
实际实现采用了更灵活的策略:备份失败时给予用户选择权,而非直接阻断。这样既保留了安全护栏(默认提示风险),又允许用户在确认有其他备份时继续操作。
|
||||||
|
|
||||||
|
## 3.2. **细化备份结果表达:解决“备份函数的失败语义过于粗糙”**
|
||||||
|
|
||||||
|
短期内不必大改架构,建议先做一层轻量封装,把 `backup_registry` 的失败点映射到更明确的错误码或消息文本。这样可以在不改动主流程结构的前提下,把错误反馈补齐。
|
||||||
|
|
||||||
|
实施步骤:
|
||||||
|
|
||||||
|
- 为备份过程补充更细的返回值,至少区分“目录或路径获取失败”“文件打开失败”“注册表读取失败”。
|
||||||
|
- 若暂时不扩展 `ErrorCode` 枚举,可新增 `backup_registry_with_message(char *buf, size_t len)` 一类包装函数,专门给控制层提供可展示的失败原因。
|
||||||
|
- 保持注册表读写逻辑仍在工具层,避免控制层直接拼装底层错误文本。
|
||||||
|
|
||||||
|
修改前:
|
||||||
|
|
||||||
|
```c
|
||||||
|
if (!fp)
|
||||||
|
return ERR_FAILED;
|
||||||
|
...
|
||||||
|
return success ? ERR_OK : ERR_FAILED;
|
||||||
|
```
|
||||||
|
|
||||||
|
修改后(实际实现):
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 使用 SHCreateDirectoryExW 递归创建目录,解决中间目录不存在的问题
|
||||||
|
SHCreateDirectoryExW(NULL, backup_dir, NULL);
|
||||||
|
|
||||||
|
// localtime_s 替代 localtime,提升线程安全性
|
||||||
|
struct tm tm_info;
|
||||||
|
localtime_s(&tm_info, &t);
|
||||||
|
wcsftime(timestamp, sizeof(timestamp) / sizeof(timestamp[0]), L"%Y%m%d_%H%M%S", &tm_info);
|
||||||
|
```
|
||||||
|
|
||||||
|
实际实现暂未扩展细粒度错误码,但已通过以下方式改进:
|
||||||
|
|
||||||
|
- 使用 `SHCreateDirectoryExW` 递归创建目录,解决中间目录不存在的问题
|
||||||
|
- 使用 `localtime_s` 替代 `localtime`,提升线程安全性
|
||||||
|
- 通过日志记录具体失败原因,便于问题定位
|
||||||
|
|
||||||
|
后续可考虑扩展 `ErrorCode` 枚举以区分不同失败场景。
|
||||||
|
|
||||||
|
# 4. 回归范围
|
||||||
|
|
||||||
|
这次调整影响的是“点击确定后的保存链路”,重点要从用户完整操作流程看:备份是否先发生、备份失败是否阻断写入、写入成功后是否仍能正常广播环境变量变更。
|
||||||
|
|
||||||
|
## 4.1. 主链路
|
||||||
|
|
||||||
|
- 正常编辑系统 PATH 和用户 PATH后点击“确定”。
|
||||||
|
- 预期先生成备份,再分别写入系统和用户 PATH。
|
||||||
|
- 写入成功后,状态栏、成功提示和环境变量广播行为保持不变。
|
||||||
|
|
||||||
|
建议重点检查以下业务结果:
|
||||||
|
|
||||||
|
- 备份文件是否实际生成。
|
||||||
|
- 成功提示是否只在备份成功且写入完成后出现。
|
||||||
|
- 写入后新开的命令行窗口是否能读取最新 PATH。
|
||||||
|
|
||||||
|
## 4.2. 边界情况
|
||||||
|
|
||||||
|
- 备份目录无法创建:应立即终止保存,注册表值保持不变,界面给出明确错误提示。
|
||||||
|
- 备份文件无法打开:应立即终止保存,避免出现“没有备份但已经写入”的状态。
|
||||||
|
- 系统或用户 PATH 读取失败:应视策略决定是否整体阻断。若当前目标是“完整备份后再保存”,则任一关键读取失败都应阻断。
|
||||||
|
- 仅保存阶段失败:备份已经成功生成时,仍要验证现有失败提示是否准确,避免把备份失败和写入失败混为一类。
|
||||||
@@ -11,9 +11,17 @@ int btn_del_cb(Ihandle *self);
|
|||||||
int btn_up_cb(Ihandle *self);
|
int btn_up_cb(Ihandle *self);
|
||||||
int btn_down_cb(Ihandle *self);
|
int btn_down_cb(Ihandle *self);
|
||||||
int btn_clean_cb(Ihandle *self);
|
int btn_clean_cb(Ihandle *self);
|
||||||
|
int btn_import_cb(Ihandle *self);
|
||||||
|
int btn_export_cb(Ihandle *self);
|
||||||
int btn_ok_cb(Ihandle *self);
|
int btn_ok_cb(Ihandle *self);
|
||||||
int btn_cancel_cb(Ihandle *self);
|
int btn_cancel_cb(Ihandle *self);
|
||||||
int btn_help_cb(Ihandle *self);
|
int btn_help_cb(Ihandle *self);
|
||||||
|
int btn_lang_cb(Ihandle *self);
|
||||||
|
int darkmode_cb(Ihandle *self);
|
||||||
|
|
||||||
|
// 撤销/重做回调
|
||||||
|
int btn_undo_cb(Ihandle *self);
|
||||||
|
int btn_redo_cb(Ihandle *self);
|
||||||
|
|
||||||
// 搜索回调
|
// 搜索回调
|
||||||
int txt_search_cb(Ihandle *self);
|
int txt_search_cb(Ihandle *self);
|
||||||
@@ -26,5 +34,9 @@ int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y
|
|||||||
|
|
||||||
// 键盘按键回调
|
// 键盘按键回调
|
||||||
int list_k_any_cb(Ihandle *self, int c);
|
int list_k_any_cb(Ihandle *self, int c);
|
||||||
|
int dlg_k_any_cb(Ihandle *self, int c);
|
||||||
|
|
||||||
|
// 载入数据与更新UI
|
||||||
|
void load_all_paths(void);
|
||||||
|
|
||||||
#endif // CALLBACKS_H
|
#endif // CALLBACKS_H
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef CALLBACKS_INTERNAL_H
|
||||||
|
#define CALLBACKS_INTERNAL_H
|
||||||
|
|
||||||
|
#include <iup.h>
|
||||||
|
#include "core/app_context.h"
|
||||||
|
#include "utils/i18n.h"
|
||||||
|
|
||||||
|
// 内部辅助函数声明(供各 callbacks_*.c 文件共享)
|
||||||
|
// 这些函数不对外暴露,仅在 controller 层内部使用
|
||||||
|
|
||||||
|
// 获取主对话框句柄
|
||||||
|
Ihandle *get_main_dlg(void);
|
||||||
|
|
||||||
|
// 从对话框获取应用上下文
|
||||||
|
AppContext *get_app_context_from_dlg(Ihandle *dlg);
|
||||||
|
|
||||||
|
// 获取当前活动的数据列表(根据 Tab 页切换)
|
||||||
|
StringList *get_current_raw_data(Ihandle *dlg);
|
||||||
|
|
||||||
|
// 获取当前活动的列表 UI 控件
|
||||||
|
Ihandle *get_current_list(Ihandle *dlg);
|
||||||
|
|
||||||
|
// 刷新撤销/重做按钮的启用状态
|
||||||
|
void refresh_undo_redo_buttons(Ihandle *dlg);
|
||||||
|
|
||||||
|
// 同步合并预览列表
|
||||||
|
void sync_merged_list(Ihandle *dlg);
|
||||||
|
|
||||||
|
#endif // CALLBACKS_INTERNAL_H
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef APP_CONTEXT_H
|
||||||
|
#define APP_CONTEXT_H
|
||||||
|
|
||||||
|
#include "utils/string_ext.h"
|
||||||
|
#include "core/undo_redo.h"
|
||||||
|
|
||||||
|
// 应用上下文结构体,用于存储应用运行时的状态
|
||||||
|
typedef struct {
|
||||||
|
StringList sys_paths;
|
||||||
|
StringList user_paths;
|
||||||
|
UndoRedoManager *undo_redo_mgr; // 撤销/重做管理器
|
||||||
|
} AppContext;
|
||||||
|
|
||||||
|
// 创建应用上下文
|
||||||
|
AppContext* create_app_context(void);
|
||||||
|
|
||||||
|
// 销毁应用上下文
|
||||||
|
void destroy_app_context(AppContext* ctx);
|
||||||
|
|
||||||
|
#endif // APP_CONTEXT_H
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef IMPORT_EXPORT_H
|
||||||
|
#define IMPORT_EXPORT_H
|
||||||
|
|
||||||
|
#include "utils/string_ext.h"
|
||||||
|
#include "utils/error_code.h"
|
||||||
|
|
||||||
|
#define EXPORT_VERSION "1.0"
|
||||||
|
|
||||||
|
// 导出数据类型
|
||||||
|
typedef enum {
|
||||||
|
EXPORT_JSON, // JSON 格式
|
||||||
|
EXPORT_CSV // CSV 格式
|
||||||
|
} ExportFormat;
|
||||||
|
|
||||||
|
// 导出数据结构
|
||||||
|
// 注意:此结构体用于导出时是只读的,items 指针指向外部 StringList 的数据
|
||||||
|
// 不要对 ExportData 调用 clear_string_list,会破坏原始数据
|
||||||
|
typedef struct {
|
||||||
|
StringList system;
|
||||||
|
StringList user;
|
||||||
|
} ExportData;
|
||||||
|
|
||||||
|
// 导出 PATH 到文件(自动根据扩展名选择格式)
|
||||||
|
ErrorCode export_paths_to_file(const ExportData *data, const char *filepath);
|
||||||
|
|
||||||
|
// 导出 PATH 到指定格式的文件
|
||||||
|
ErrorCode export_paths_to_format(const ExportData *data, const char *filepath, ExportFormat format);
|
||||||
|
|
||||||
|
// 从文件导入 PATH
|
||||||
|
ErrorCode import_paths_from_file(const char *filepath, ExportData *data);
|
||||||
|
|
||||||
|
// 验证路径格式是否有效
|
||||||
|
int is_valid_path_format(const char *path);
|
||||||
|
|
||||||
|
#endif // IMPORT_EXPORT_H
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef LUA_CONFIG_H
|
||||||
|
#define LUA_CONFIG_H
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
|
||||||
|
// 初始化 Lua 配置系统
|
||||||
|
// 返回值: 0 成功, -1 失败
|
||||||
|
int lua_config_init(void);
|
||||||
|
|
||||||
|
// 销毁 Lua 配置系统
|
||||||
|
void lua_config_destroy(void);
|
||||||
|
|
||||||
|
// 获取字符串配置值
|
||||||
|
// section: 配置章节名 (如 "app", "dialog", "button")
|
||||||
|
// key: 配置键名 (如 "name", "size", "rastersize")
|
||||||
|
// 返回值: 配置值字符串, 失败时返回 NULL
|
||||||
|
const char* lua_config_get_string(const char* section, const char* key);
|
||||||
|
|
||||||
|
// 获取整型配置值
|
||||||
|
// section: 配置章节名
|
||||||
|
// key: 配置键名
|
||||||
|
// default_value: 默认值 (当配置不存在或转换失败时返回)
|
||||||
|
// 返回值: 配置值或默认值
|
||||||
|
int lua_config_get_int(const char* section, const char* key, int default_value);
|
||||||
|
|
||||||
|
// 重新加载配置文件
|
||||||
|
// 返回值: 0 成功, -1 失败
|
||||||
|
int lua_config_reload(void);
|
||||||
|
|
||||||
|
// 获取配置加载状态
|
||||||
|
// 返回值: 1 已加载, 0 未加载
|
||||||
|
int lua_config_is_loaded(void);
|
||||||
|
|
||||||
|
// 设置字符串配置值
|
||||||
|
// section: 配置章节名
|
||||||
|
// key: 配置键名
|
||||||
|
// value: 配置值
|
||||||
|
// 返回值: 0 成功, -1 失败
|
||||||
|
int lua_config_set_string(const char *section, const char *key, const char *value);
|
||||||
|
|
||||||
|
#endif // LUA_CONFIG_H
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef PATH_MANAGER_H
|
||||||
|
#define PATH_MANAGER_H
|
||||||
|
|
||||||
|
#include "utils/string_ext.h"
|
||||||
|
#include "utils/error_code.h"
|
||||||
|
|
||||||
|
// 移除列表中指定索引的项
|
||||||
|
ErrorCode path_manager_remove_at(StringList *list, int index);
|
||||||
|
|
||||||
|
// 上移指定索引的项
|
||||||
|
ErrorCode path_manager_move_up(StringList *list, int index);
|
||||||
|
|
||||||
|
// 下移指定索引的项
|
||||||
|
ErrorCode path_manager_move_down(StringList *list, int index);
|
||||||
|
|
||||||
|
// 清理无效和重复的路径
|
||||||
|
ErrorCode path_manager_clean(StringList *list);
|
||||||
|
|
||||||
|
#endif // PATH_MANAGER_H
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef REGISTRY_SERVICE_H
|
||||||
|
#define REGISTRY_SERVICE_H
|
||||||
|
|
||||||
|
#include "utils/string_ext.h"
|
||||||
|
#include "utils/error_code.h"
|
||||||
|
|
||||||
|
// 加载系统变量和用户变量到字符串列表
|
||||||
|
ErrorCode load_system_paths(StringList *list);
|
||||||
|
ErrorCode load_user_paths(StringList *list);
|
||||||
|
|
||||||
|
// 从字符串列表保存系统变量和用户变量
|
||||||
|
ErrorCode save_system_paths(const StringList *list);
|
||||||
|
ErrorCode save_user_paths(const StringList *list);
|
||||||
|
|
||||||
|
#endif // REGISTRY_SERVICE_H
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef UNDO_REDO_H
|
||||||
|
#define UNDO_REDO_H
|
||||||
|
|
||||||
|
#include "utils/string_ext.h"
|
||||||
|
|
||||||
|
// 操作类型
|
||||||
|
typedef enum {
|
||||||
|
OP_ADD, // 添加路径
|
||||||
|
OP_DELETE, // 删除路径
|
||||||
|
OP_EDIT, // 编辑路径
|
||||||
|
OP_MOVE_UP, // 上移
|
||||||
|
OP_MOVE_DOWN, // 下移
|
||||||
|
OP_CLEAN, // 清理(批量删除)
|
||||||
|
OP_CLEAR, // 清空列表
|
||||||
|
OP_IMPORT // 导入
|
||||||
|
} OperationType;
|
||||||
|
|
||||||
|
// 目标类型(哪个列表)
|
||||||
|
typedef enum {
|
||||||
|
TARGET_SYSTEM, // 系统变量
|
||||||
|
TARGET_USER // 用户变量
|
||||||
|
} TargetType;
|
||||||
|
|
||||||
|
// 单个操作记录
|
||||||
|
typedef struct {
|
||||||
|
OperationType type;
|
||||||
|
TargetType target;
|
||||||
|
int index;
|
||||||
|
int count; // 用于批量操作(如清理、导入)
|
||||||
|
char **old_paths; // 操作前的路径列表(用于撤销)
|
||||||
|
char **new_paths; // 操作后的路径列表(用于重做)
|
||||||
|
} OpRecord;
|
||||||
|
|
||||||
|
// 撤销/重做管理器
|
||||||
|
typedef struct {
|
||||||
|
OpRecord *records;
|
||||||
|
int max_size; // 最大历史记录数
|
||||||
|
int current; // 当前指针位置(-1表示最新操作之后)
|
||||||
|
int count; // 实际记录数
|
||||||
|
} UndoRedoManager;
|
||||||
|
|
||||||
|
// 创建撤销/重做管理器
|
||||||
|
UndoRedoManager *create_undo_redo_manager(int max_size);
|
||||||
|
|
||||||
|
// 销毁撤销/重做管理器
|
||||||
|
void destroy_undo_redo_manager(UndoRedoManager *mgr);
|
||||||
|
|
||||||
|
// 添加操作记录
|
||||||
|
int push_undo_record(UndoRedoManager *mgr, const OpRecord *record);
|
||||||
|
|
||||||
|
// 执行撤销
|
||||||
|
int undo(UndoRedoManager *mgr, StringList *sys_paths, StringList *user_paths);
|
||||||
|
|
||||||
|
// 执行重做
|
||||||
|
int redo(UndoRedoManager *mgr, StringList *sys_paths, StringList *user_paths);
|
||||||
|
|
||||||
|
// 检查是否可以撤销
|
||||||
|
int can_undo(const UndoRedoManager *mgr);
|
||||||
|
|
||||||
|
// 检查是否可以重做
|
||||||
|
int can_redo(const UndoRedoManager *mgr);
|
||||||
|
|
||||||
|
// 清空历史记录
|
||||||
|
void clear_undo_redo_history(UndoRedoManager *mgr);
|
||||||
|
|
||||||
|
#endif // UNDO_REDO_H
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#ifndef GLOBALS_H
|
|
||||||
#define GLOBALS_H
|
|
||||||
|
|
||||||
#include <iup.h>
|
|
||||||
|
|
||||||
// 注册表路径常量
|
|
||||||
#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
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#ifndef REGISTRY_H
|
|
||||||
#define REGISTRY_H
|
|
||||||
|
|
||||||
// 从注册表加载所有PATH到列表控件
|
|
||||||
void load_all_paths();
|
|
||||||
|
|
||||||
// 将列表控件中的PATH保存回注册表
|
|
||||||
void save_all_paths();
|
|
||||||
|
|
||||||
#endif // REGISTRY_H
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#ifndef UI_H
|
|
||||||
#define UI_H
|
|
||||||
|
|
||||||
#include <iup.h>
|
|
||||||
|
|
||||||
// 创建列表控件
|
|
||||||
Ihandle *create_path_list();
|
|
||||||
|
|
||||||
// 创建右侧功能按钮区域
|
|
||||||
Ihandle *create_main_buttons();
|
|
||||||
|
|
||||||
// 创建底部按钮区域
|
|
||||||
Ihandle *create_bottom_buttons();
|
|
||||||
|
|
||||||
#endif // UI_H
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef DIALOGS_H
|
||||||
|
#define DIALOGS_H
|
||||||
|
|
||||||
|
// 自定义输入对话框
|
||||||
|
// 返回值:0-取消,1-确认
|
||||||
|
int custom_input_dialog(const char *title, const char *label_text, char *buffer, int buffer_size);
|
||||||
|
|
||||||
|
// 语言选择对话框
|
||||||
|
// 返回值:0-取消,1-确认
|
||||||
|
int language_select_dialog(void);
|
||||||
|
|
||||||
|
#endif // DIALOGS_H
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef MAIN_WINDOW_H
|
||||||
|
#define MAIN_WINDOW_H
|
||||||
|
|
||||||
|
#include <iup.h>
|
||||||
|
|
||||||
|
// 创建主窗口
|
||||||
|
Ihandle* create_main_window(void);
|
||||||
|
|
||||||
|
// 刷新 UI 文本(语言切换时调用)
|
||||||
|
void refresh_main_window_ui(Ihandle *main_dlg);
|
||||||
|
|
||||||
|
#endif // MAIN_WINDOW_H
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef UI_UTILS_H
|
||||||
|
#define UI_UTILS_H
|
||||||
|
|
||||||
|
#include <iup.h>
|
||||||
|
#include "utils/string_ext.h"
|
||||||
|
|
||||||
|
// 刷新单个列表框样式
|
||||||
|
// 功能说明:
|
||||||
|
// 1. 路径有效性检查:无效路径显示红色前景色 (255 0 0)
|
||||||
|
// 2. 重复检查:重复路径显示橙色前景色 (255 128 0),只检查当前项之前的项
|
||||||
|
// 3. 斑马纹背景:奇偶行交替显示不同背景色 (白/灰)
|
||||||
|
// 注意: 该函数需要IUP控件已设置NAME属性
|
||||||
|
void refresh_single_list_style(Ihandle *list);
|
||||||
|
|
||||||
|
// 同步字符串列表到 UI 列表框
|
||||||
|
// 将StringList中的所有项同步到IUP FlatList控件中
|
||||||
|
// 会先清空列表然后重新添加所有项,最后刷新样式
|
||||||
|
void sync_string_list_to_ui(Ihandle *list_ui, const StringList *str_list);
|
||||||
|
|
||||||
|
// 深色模式状态管理
|
||||||
|
void set_dark_mode(int enabled);
|
||||||
|
int get_dark_mode(void);
|
||||||
|
|
||||||
|
#endif // UI_UTILS_H
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#ifndef UTILS_H
|
|
||||||
#define UTILS_H
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <wchar.h>
|
|
||||||
#include <iup.h>
|
|
||||||
|
|
||||||
// 宽字符转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
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef ERROR_CODE_H
|
||||||
|
#define ERROR_CODE_H
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ERR_OK = 0, // 成功
|
||||||
|
ERR_FAILED = -1, // 失败
|
||||||
|
ERR_NULL_PTR = -2, // 空指针
|
||||||
|
ERR_OUT_OF_MEMORY = -3, // 内存不足
|
||||||
|
ERR_FILE_NOT_FOUND = -4, // 文件不存在
|
||||||
|
ERR_PERMISSION_DENIED = -5, // 权限拒绝
|
||||||
|
ERR_INVALID_FORMAT = -6, // 无效格式
|
||||||
|
ERR_REGISTRY_FAILED = -7, // 注册表操作失败
|
||||||
|
ERR_NOT_FOUND = -8, // 未找到
|
||||||
|
ERR_EXISTS = -9, // 已存在
|
||||||
|
ERR_INVALID_INDEX = -10 // 无效索引
|
||||||
|
} ErrorCode;
|
||||||
|
|
||||||
|
// 获取错误码的字符串表示(英文,用于日志)
|
||||||
|
const char* error_code_to_string(ErrorCode code);
|
||||||
|
|
||||||
|
#endif // ERROR_CODE_H
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef I18N_H
|
||||||
|
#define I18N_H
|
||||||
|
|
||||||
|
#include <libintl.h>
|
||||||
|
#include <locale.h>
|
||||||
|
|
||||||
|
#ifndef _
|
||||||
|
#define _(s) gettext(s)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 初始化国际化系统
|
||||||
|
void i18n_init(const char* default_lang);
|
||||||
|
|
||||||
|
// 检测系统语言并返回语言代码
|
||||||
|
const char* i18n_detect_system_language(void);
|
||||||
|
|
||||||
|
// 切换语言
|
||||||
|
void i18n_change_language(const char* lang);
|
||||||
|
|
||||||
|
// 获取当前语言
|
||||||
|
const char* i18n_get_current_language(void);
|
||||||
|
|
||||||
|
#endif // I18N_H
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef LOGGER_H
|
||||||
|
#define LOGGER_H
|
||||||
|
|
||||||
|
// 日志级别
|
||||||
|
typedef enum {
|
||||||
|
LOG_LEVEL_DEBUG, // 调试日志级别
|
||||||
|
LOG_LEVEL_INFO, // 信息日志级别
|
||||||
|
LOG_LEVEL_WARN, // 警告日志级别
|
||||||
|
LOG_LEVEL_ERROR // 错误日志级别
|
||||||
|
} LogLevel;
|
||||||
|
|
||||||
|
// 初始化日志系统
|
||||||
|
void log_init(const char *log_file, LogLevel level);
|
||||||
|
|
||||||
|
// 销毁日志系统
|
||||||
|
void log_destroy(void);
|
||||||
|
|
||||||
|
// 日志函数
|
||||||
|
void log_debug(const char *fmt, ...);
|
||||||
|
|
||||||
|
// 信息日志函数
|
||||||
|
void log_info(const char *fmt, ...);
|
||||||
|
|
||||||
|
// 警告日志函数
|
||||||
|
void log_warn(const char *fmt, ...);
|
||||||
|
|
||||||
|
// 错误日志函数
|
||||||
|
void log_error(const char *fmt, ...);
|
||||||
|
|
||||||
|
// 设置日志级别
|
||||||
|
void log_set_level(LogLevel level);
|
||||||
|
|
||||||
|
#endif // LOGGER_H
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef OS_ENV_H
|
||||||
|
#define OS_ENV_H
|
||||||
|
|
||||||
|
#include "utils/error_code.h"
|
||||||
|
|
||||||
|
// 检查是否以管理员权限运行
|
||||||
|
int check_admin(void);
|
||||||
|
|
||||||
|
// 检查路径是否有效
|
||||||
|
int is_path_valid(const char *path);
|
||||||
|
|
||||||
|
// 备份注册表
|
||||||
|
// 参数 backup_path: 自定义备份目录路径,传 NULL 使用 Lua 配置中的默认路径
|
||||||
|
ErrorCode backup_registry(const char *backup_path);
|
||||||
|
|
||||||
|
#endif // OS_ENV_H
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef SAFE_STRING_H
|
||||||
|
#define SAFE_STRING_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// 安全字符串操作函数
|
||||||
|
char* safe_strcpy(char *dst, size_t dst_size, const char *src);
|
||||||
|
|
||||||
|
// 安全字符串拼接函数
|
||||||
|
char* safe_strcat(char *dst, size_t dst_size, const char *src);
|
||||||
|
|
||||||
|
// 安全字符串复制函数
|
||||||
|
char* safe_strdup(const char *src);
|
||||||
|
|
||||||
|
#endif // SAFE_STRING_H
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#ifndef STRING_EXT_H
|
||||||
|
#define STRING_EXT_H
|
||||||
|
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
|
// 简单字符串列表结构
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 访问器函数 - 安全访问内部数据
|
||||||
|
// 获取指定索引的字符串(只读),越界返回 NULL
|
||||||
|
const char *string_list_get(const StringList *list, int index);
|
||||||
|
// 设置指定索引的字符串(会复制新字符串并释放旧字符串),越界返回 -1,成功返回 0
|
||||||
|
int string_list_set(StringList *list, int index, const char *str);
|
||||||
|
|
||||||
|
// 字符串转换函数
|
||||||
|
char *wide_to_utf8(const wchar_t *wstr);
|
||||||
|
wchar_t *utf8_to_wide(const char *str);
|
||||||
|
char *stristr(const char *haystack, const char *needle);
|
||||||
|
|
||||||
|
// 检查字符串列表中是否存在指定路径(不区分大小写)
|
||||||
|
int string_list_contains(const StringList *list, const char *str);
|
||||||
|
|
||||||
|
// 展开环境变量(如 %JAVA_HOME%\bin → C:\Java\bin)
|
||||||
|
// 返回 malloc 分配的字符串,调用者负责释放;无变量返回 NULL
|
||||||
|
char *expand_env_vars(const char *path);
|
||||||
|
|
||||||
|
#endif // STRING_EXT_H
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef UI_CONSTANTS_H
|
||||||
|
#define UI_CONSTANTS_H
|
||||||
|
|
||||||
|
// 缓冲区大小常量
|
||||||
|
#define PATH_BUFFER_SIZE 4096
|
||||||
|
|
||||||
|
// 控件名称常量 - 统一管理所有IUP控件名称字符串
|
||||||
|
// 使用这些常量替代硬编码字符串,便于维护和减少拼写错误
|
||||||
|
|
||||||
|
// 列表控件
|
||||||
|
#define CTRL_LIST_SYS "LIST_SYS"
|
||||||
|
#define CTRL_LIST_USER "LIST_USER"
|
||||||
|
#define CTRL_LIST_MERGED "LIST_MERGED"
|
||||||
|
|
||||||
|
// 选项卡
|
||||||
|
#define CTRL_TABS_MAIN "TABS_MAIN"
|
||||||
|
|
||||||
|
// 搜索框
|
||||||
|
#define CTRL_TXT_SEARCH "TXT_SEARCH"
|
||||||
|
|
||||||
|
// 状态标签
|
||||||
|
#define CTRL_LBL_STATUS "LBL_STATUS"
|
||||||
|
|
||||||
|
// 操作按钮
|
||||||
|
#define CTRL_BTN_NEW "BTN_NEW"
|
||||||
|
#define CTRL_BTN_EDIT "BTN_EDIT"
|
||||||
|
#define CTRL_BTN_BROWSE "BTN_BROWSE"
|
||||||
|
#define CTRL_BTN_DEL "BTN_DEL"
|
||||||
|
#define CTRL_BTN_UP "BTN_UP"
|
||||||
|
#define CTRL_BTN_DOWN "BTN_DOWN"
|
||||||
|
#define CTRL_BTN_CLEAN "BTN_CLEAN"
|
||||||
|
#define CTRL_BTN_IMPORT "BTN_IMPORT"
|
||||||
|
#define CTRL_BTN_EXPORT "BTN_EXPORT"
|
||||||
|
#define CTRL_BTN_OK "BTN_OK"
|
||||||
|
#define CTRL_BTN_CANCEL "BTN_CANCEL"
|
||||||
|
#define CTRL_BTN_HELP "BTN_HELP"
|
||||||
|
#define CTRL_BTN_LANG "BTN_LANG"
|
||||||
|
#define CTRL_BTN_DARKMODE "BTN_DARKMODE"
|
||||||
|
|
||||||
|
// 撤销/重做按钮
|
||||||
|
#define CTRL_BTN_UNDO "BTN_UNDO"
|
||||||
|
#define CTRL_BTN_REDO "BTN_REDO"
|
||||||
|
|
||||||
|
#endif // UI_CONSTANTS_H
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user