9 Commits

Author SHA1 Message Date
Serendipity 3302132644 docs: 更新注释与README,补充编译说明和架构描述
- 在 main.c 中添加编译和打包命令的注释
- 在 README.md 中新增“架构与二次开发”章节,说明项目的模块化设计和配置管理
2026-03-18 22:23:54 +08:00
Serendipity 59db3dc33b refactor(ui): 提取UI常量到配置文件并重构全局变量
- 新增 config.h 集中管理所有UI常量,如窗口标题、尺寸、颜色等
- 将全局变量和控件指针移至 globals.c 进行统一管理
- 更新 Makefile 以包含新增的源文件
- 修改 ui.c 使用配置常量替代硬编码值,提高可维护性
2026-03-18 22:21:36 +08:00
Serendipity 197b318c61 docs: 更新 README 以反映新增功能与改进
更新 README.md 文件,详细说明新增的深色模式、多选操作、撤销/重做、导入导出、变量展开预览、合并预览标签页以及全局快捷键等功能。同时完善了使用步骤,使其与当前版本的功能特性保持一致。
2026-03-17 21:35:20 +08:00
Serendipity b35fac5358 refactor: 重构项目结构,拆分回调函数并新增功能模块
- 将 callbacks.c 拆分为 cb_edit.c、cb_file.c、cb_main.c 三个模块,提高代码可维护性
- 新增 ui_utils.c 提供通用 UI 辅助函数
- 新增历史记录功能(撤销/重做)和主题切换支持
- 增加合并预览标签页,优化路径有效性检查和环境变量展开
- 更新 Makefile 以支持新的源文件结构
2026-03-17 21:18:21 +08:00
Serendipity 7db190306c docs: 更新 README 项目描述与功能亮点
重写项目概述,使其更具吸引力和信息性。新增对目标用户、核心优势(如双视图、智能检测、自动备份)的说明,以更好地向潜在用户展示工具价值。
2026-03-16 20:32:16 +08:00
Serendipity 575fcca5c4 refactor: 提取UI组件到独立模块并改进拖拽支持
- 将列表、按钮等UI创建代码从main.c移至ui.c/ui.h
- 添加Windows UIPI消息过滤以支持管理员模式下的文件拖拽
- 更新Makefile和构建脚本以包含新的UI模块
- 清理旧的备份注册表文件并更新README文档
2026-03-16 20:15:10 +08:00
Serendipity 39d06e20e0 feat: 新增系统/用户变量分离、搜索、拖拽和清理功能
- 将单一列表拆分为系统和用户变量两个标签页
- 新增搜索框支持实时过滤路径
- 支持拖拽文件夹直接添加到列表
- 新增一键清理功能移除无效和重复路径
- 增加注册表备份机制和删除确认
- 优化界面布局和权限提示逻辑
2026-03-16 19:58:41 +08:00
Serendipity f21d302565 refactor: 移除未使用的Windows平台头文件包含
清理main.c中已不再需要的条件编译头文件引入,简化代码结构。
2026-03-16 18:49:47 +08:00
Serendipity adbbe099b9 docs: 添加 MIT 许可证文件
为项目添加标准的 MIT 许可证,明确软件的使用、复制、修改和分发权限。
2026-03-16 18:48:19 +08:00
26 changed files with 1998 additions and 620 deletions
+21
View File
@@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2026 LHY
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+18 -3
View File
@@ -18,9 +18,9 @@ CFLAGS = -Wall -O2 -I$(INCLUDE_DIR) -I$(LOCAL_INCLUDE_DIR) -D_WIN32 -DUNICODE -D
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 = src/main.c src/utils.c src/registry.c src/ui.c src/ui_utils.c src/cb_edit.c src/cb_file.c src/cb_main.c src/globals.c
RES = ico/resources.rc
OBJ = $(OBJ_DIR)/main.o $(OBJ_DIR)/utils.o $(OBJ_DIR)/registry.o $(OBJ_DIR)/callbacks.o $(OBJ_DIR)/resources.o
OBJ = $(OBJ_DIR)/main.o $(OBJ_DIR)/utils.o $(OBJ_DIR)/registry.o $(OBJ_DIR)/ui.o $(OBJ_DIR)/ui_utils.o $(OBJ_DIR)/cb_edit.o $(OBJ_DIR)/cb_file.o $(OBJ_DIR)/cb_main.o $(OBJ_DIR)/globals.o $(OBJ_DIR)/resources.o
EXE = $(BIN_DIR)/PathEditor.exe
all: $(BIN_DIR) $(OBJ_DIR) $(EXE)
@@ -43,7 +43,22 @@ $(OBJ_DIR)/utils.o: src/utils.c
$(OBJ_DIR)/registry.o: src/registry.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/callbacks.o: src/callbacks.c
$(OBJ_DIR)/ui.o: src/ui.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/ui_utils.o: src/ui_utils.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/cb_edit.o: src/cb_edit.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/cb_file.o: src/cb_file.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/cb_main.o: src/cb_main.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/globals.o: src/globals.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/resources.o: ico/resources.rc
+54 -10
View File
@@ -1,18 +1,51 @@
# Path Editor (系统环境变量编辑器)
一个简单、轻量级的 Windows 系统环境变量(PATH编辑器,基于 C 语言和 IUP 图形库开发。
* Path Editor 是一个专为 Windows 用户设计的系统环境变量(PATH管理工具。它基于原生 C 语言和 IUP 图形库开发,旨在替代 Windows 自带的简陋编辑界面
* 相比系统自带的编辑器,Path Editor 提供了更加直观的双视图(系统/用户变量)界面、智能的路径有效性检测、自动备份机制以及便捷的拖拽操作,让环境变量的管理变得安全、高效且轻松。无论您是开发者还是系统管理员,它都是您配置开发环境的得力助手。
## ✨ 功能特点
* **可视化编辑**:直观地查看和管理系统 PATH 环境变量。
* **安全操作**:必须以管理员身份运行才能保存更改,防止误操作
* **🛡️ 安全第一**
* **自动备份**:每次保存前自动备份注册表,防止意外
* **只读模式**:非管理员运行时自动切换到只读模式,防止误操作。
* **权限检测**:智能检测当前运行权限。
* **📑 双视图与预览**
* **分离管理**:完美支持 **System (系统变量)****User (用户变量)** 的分离查看与编辑。
* **合并预览**:新增标签页,展示最终解析后的完整 PATH 顺序(系统在前,用户在后),方便排查冲突。
* **变量展开**:鼠标悬停在包含变量(如 `%JAVA_HOME%`)的路径上时,自动显示解析后的绝对路径。
* **🔴 智能诊断与维护**
* **无效路径高亮**:自动检测路径是否存在,不存在的显示为红色。
* **重复路径高亮**:自动检测重复项,重复的显示为橙色。
* **一键清理**:智能移除所有无效和重复的路径,保持环境整洁。
* **📂 高级交互**
* **多选支持**:支持使用 `Ctrl``Shift` 进行多选,批量删除或移动路径。
* **撤销/重做**:支持 `Ctrl+Z` 撤销和 `Ctrl+Y` 重做,防止误操作,并提供直观的工具栏按钮。
* **拖拽支持**:直接将文件夹拖入窗口即可添加(支持管理员模式下的 UIPI 穿透)。
* **实时搜索**:顶部搜索框支持不区分大小写的实时过滤查找(支持快捷键 `Ctrl+F`)。
* **全局快捷键**:支持 `Ctrl+N` 新建、`Ctrl+S` 保存、`Delete` 删除等快捷操作。
* **🎨 个性化**
* **深色模式**:一键切换深色主题,保护视力。
* **便捷管理**
* **新建**:添加新路径到列表。
* 📂 **浏览**:直接从文件资源管理器选择目录添加。
* ✏️ **编辑**:修改现有路径。
* ✏️ **编辑**双击或点击按钮修改现有路径。
* 🗑️ **删除**:移除不需要的路径。
* ⬆️⬇️ **排序**:上移/下移调整路径优先级。
* **轻量级**:原生 C 语言编写,运行速度快,占用资源少
* 📥/📤 **导入导出**:支持将环境变量导出为 `.txt` 文本文件备份,或从文件导入恢复
* **轻量级**:原生 C 语言编写,无臃肿依赖,运行速度极快。
## 🏗️ 架构与二次开发
本项目注重代码的模块化和可维护性,非常适合作为 C 语言桌面程序开发的参考:
* **统一配置中心**:所有的 UI 尺寸、间距、颜色等常量配置均提取在 `include/config.h` 中,只需修改宏定义即可轻松定制属于你的专属界面风格。
* **清晰的全局状态**:全局变量和常量被独立分离在 `src/globals.c` / `include/globals.h` 中管理,使得核心业务逻辑更加整洁。
## 📦 下载与安装
@@ -30,6 +63,7 @@
* GCC 编译器 (推荐 MinGW-w64)
* Make 工具
* IUP 库 (已包含在 `libs` 目录下)
* Inno Setup 6 (仅打包需要)
### 编译步骤
@@ -54,16 +88,26 @@
本项目使用 Inno Setup 生成安装包。
1. 确保已安装 [Inno Setup 6](https://jrsoftware.org/isdl.php)。
2. 编译项目生成 exe 文件
3. 使用 Inno Setup 编译 `dist/installer.iss` 脚本
2. 运行根目录下的 `build_installer.bat` 脚本
3. 生成的安装包将位于 `dist/dist/PathEditorSetup.exe`
## 📝 使用说明
1. **启动**:右键点击程序图标,选择“以管理员身份运行”。
2. **查看**:程序启动后会自动加载当前的系统 PATH 变量。
3. **修改**:使用右侧按钮栏进行添加、删除、移动等操作
4. **保存**:操作完成后,务必点击底部的【确定】按钮保存更改
5. **生效**:保存后,某些正在运行的程序可能需要重启才能识别新的环境变量。CMD 或 PowerShell 窗口需要重新打开
* **红色**条目表示路径不存在
* **橙色**条目表示路径重复
* **变量预览**:鼠标悬停在带 `%` 的变量上可查看实际路径
3. **搜索**:在顶部输入关键词或按 `Ctrl+F` 可快速筛选。
4. **修改**
* **拖拽**:将文件夹拖入列表可直接添加。
* **多选**:按住 `Ctrl` 或 `Shift` 可选中多项进行批量删除。
* **撤销/重做**:误操作时可使用 `Ctrl+Z` / `Ctrl+Y` 或工具栏按钮回退。
* **常规操作**:使用右侧按钮栏进行新建、编辑、移动等操作。
* **清理**:点击“一键清理”可自动删除无效和重复项。
* **导入/导出**:使用导入导出功能备份或恢复配置。
5. **保存**:操作完成后,务必点击底部的【确定】按钮(或按 `Ctrl+S`)保存更改。
6. **生效**:保存后,某些正在运行的程序可能需要重启才能识别新的环境变量。CMD 或 PowerShell 窗口需要重新打开。
## 👤 作者信息
Binary file not shown.
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
@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...
"D:\Program Files (x86)\Inno Setup 6\ISCC.exe" "dist\installer.iss"
if %ERRORLEVEL% NEQ 0 (
echo Installer build failed!
exit /b %ERRORLEVEL%
)
echo Done! Installer is in dist\dist\
pause
-20
View File
@@ -1,20 +0,0 @@
#ifndef CALLBACKS_H
#define CALLBACKS_H
#include <iup.h>
// 按钮回调
int btn_new_cb(Ihandle* self);
int btn_edit_cb(Ihandle* self);
int btn_browse_cb(Ihandle* self);
int btn_del_cb(Ihandle* self);
int btn_up_cb(Ihandle* self);
int btn_down_cb(Ihandle* self);
int btn_ok_cb(Ihandle* self);
int btn_cancel_cb(Ihandle* self);
int btn_help_cb(Ihandle* self);
// 双击回调
int list_dblclick_cb(Ihandle* self, int item, char* text);
#endif // CALLBACKS_H
+15
View File
@@ -0,0 +1,15 @@
#ifndef CB_EDIT_H
#define CB_EDIT_H
#include <iup.h>
// 编辑相关回调
int btn_new_cb(Ihandle *self);
int btn_edit_cb(Ihandle *self);
int list_dblclick_cb(Ihandle *self, int item, char *text);
int btn_del_cb(Ihandle *self);
int btn_up_cb(Ihandle *self);
int btn_down_cb(Ihandle *self);
int btn_clean_cb(Ihandle *self);
#endif // CB_EDIT_H
+14
View File
@@ -0,0 +1,14 @@
#ifndef CB_FILE_H
#define CB_FILE_H
#include <iup.h>
// 文件和历史记录回调
int btn_browse_cb(Ihandle *self);
int btn_undo_cb(Ihandle *self);
int btn_redo_cb(Ihandle *self);
int btn_export_cb(Ihandle *self);
int btn_import_cb(Ihandle *self);
int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y);
#endif // CB_FILE_H
+17
View File
@@ -0,0 +1,17 @@
#ifndef CB_MAIN_H
#define CB_MAIN_H
#include <iup.h>
// 主界面交互回调
int txt_search_cb(Ihandle *self);
int list_k_any_cb(Ihandle *self, int c);
int list_motion_cb(Ihandle *self, int x, int y, char *status);
int dialog_k_any_cb(Ihandle *self, int c);
int btn_ok_cb(Ihandle *self);
int btn_cancel_cb(Ihandle *self);
int btn_help_cb(Ihandle *self);
int tabs_tabchange_cb(Ihandle *self, int new_pos, int old_pos);
int btn_theme_cb(Ihandle *self);
#endif // CB_MAIN_H
+23
View File
@@ -0,0 +1,23 @@
#ifndef CONFIG_H
#define CONFIG_H
// UI 常量定义
#define UI_WINDOW_TITLE "Path Editor" // 窗口标题
#define UI_WINDOW_SIZE "800x800" // 窗口默认大小 (像素)
// 按钮尺寸
#define UI_BUTTON_SIZE "100x32" // 按钮默认大小 (像素)
#define UI_BUTTON_SMALL_SIZE "80x32" // 小按钮大小 (像素)
// 布局间距
#define UI_MARGIN_MAIN "10x10" // 主布局外边距 (像素)
#define UI_GAP_MAIN "10" // 主布局间距 (像素)
#define UI_GAP_BUTTONS "5" // 按钮间距 (像素)
#define UI_GAP_BOTTOM "10" // 底部间距 (像素)
// 列表属性
#define UI_LIST_ITEM_PADDING "5x5" // 列表项内边距 (像素)
#define UI_LIST_BGCOLOR "255 255 255" // 列表背景颜色 (RGB)
#define UI_LIST_MERGED_BGCOLOR "240 240 240"// 合并列表背景颜色 (RGB)
#endif // CONFIG_H
+65 -13
View File
@@ -4,21 +4,73 @@
#include <iup.h>
// 注册表路径常量
#define REG_PATH L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
#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 *list_path; // 路径列表控件句柄
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_ok; // 确认按钮句柄
extern Ihandle *btn_cancel; // 取消按钮句柄
extern Ihandle *btn_help; // 帮助按钮句柄
extern Ihandle *dlg; // 主对话框句柄
extern Ihandle *tabs_main; // 标签页容器
extern Ihandle *list_sys; // 系统变量列表
extern Ihandle *list_user; // 用户变量列表
extern Ihandle *list_merged; // 合并变量列表
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_undo; // 撤销按钮句柄
extern Ihandle *btn_redo; // 重做按钮句柄
extern Ihandle *btn_import; // 导入按钮句柄
extern Ihandle *btn_export; // 导出按钮句柄
extern Ihandle *btn_theme; // 主题切换按钮句柄
extern Ihandle *btn_ok; // 确认按钮句柄
extern int is_dark_mode; // 深色模式状态
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;
// 历史记录节点
typedef struct HistoryNode {
StringList sys_paths;
StringList user_paths;
struct HistoryNode *next;
} HistoryNode;
// 历史记录栈
typedef struct {
HistoryNode *top;
int count;
} HistoryStack;
extern HistoryStack undo_stack;
extern HistoryStack redo_stack;
extern Ihandle *btn_undo;
extern Ihandle *btn_redo;
// 缓存操作函数声明
void init_string_list(StringList *list);
void add_string_list(StringList *list, const char *str);
void clear_string_list(StringList *list);
void copy_string_list(StringList *dest, StringList *src);
// 历史记录操作
void init_history_stack(HistoryStack *stack);
void push_history(HistoryStack *stack, StringList *sys, StringList *user);
int pop_history(HistoryStack *stack, StringList *out_sys, StringList *out_user);
void clear_history_stack(HistoryStack *stack);
#endif // GLOBALS_H
+3 -3
View File
@@ -1,10 +1,10 @@
#ifndef REGISTRY_H
#define REGISTRY_H
// 从注册表加载PATH到列表控件
void load_path();
// 从注册表加载所有PATH到列表控件
void load_all_paths();
// 将列表控件中的PATH保存回注册表
void save_path();
void save_all_paths();
#endif // REGISTRY_H
+17
View File
@@ -0,0 +1,17 @@
#ifndef UI_H
#define UI_H
#include <iup.h>
// 创建列表控件
Ihandle *create_path_list();
// 创建右侧功能按钮区域
Ihandle *create_main_buttons();
// 创建底部按钮区域
Ihandle *create_bottom_buttons();
Ihandle *create_main_dialog();
#endif // UI_H
+18
View File
@@ -0,0 +1,18 @@
#ifndef UI_UTILS_H
#define UI_UTILS_H
#include <iup.h>
#include "globals.h"
// 辅助函数声明
int get_first_selected_index(Ihandle *list);
void set_single_selection(Ihandle *list, int index);
void refresh_ui_from_raw(Ihandle *list, StringList *raw);
void record_history();
int custom_input_dialog(const char *title, const char *label_text, char *buffer, int buffer_size);
Ihandle *get_current_list();
void remove_from_raw_data(StringList *list, const char *str);
void toggle_edit_buttons(int enable);
void apply_theme();
#endif // UI_UTILS_H
+14
View File
@@ -3,6 +3,7 @@
#include <windows.h>
#include <wchar.h>
#include <iup.h>
// 宽字符转UTF-8
char* wide_to_utf8(const wchar_t* wstr);
@@ -13,7 +14,20 @@ wchar_t* utf8_to_wide(const char* str);
// 检查管理员权限
int check_admin();
// 展开环境变量
char* expand_env_vars(const char* path);
// 检查路径是否有效(存在且为目录)
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
-327
View File
@@ -1,327 +0,0 @@
#include "callbacks.h"
#include "globals.h"
#include "registry.h"
#include "utils.h"
#include <string.h>
#include <stdio.h>
// 简单的自定义输入对话框,支持更宽的输入框
// 返回 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" will be returned by IupPopup
IupButton("取消", "0"), // "0" will be returned by IupPopup
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");
// 按钮响应
// 这是一个简单的 hackIupPopup 的参数 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;
}
// 按钮回调:新建
int btn_new_cb(Ihandle *self)
{
char buffer[1024] = "";
if (custom_input_dialog("新建环境变量", "请输入路径:", buffer, sizeof(buffer)))
{
if (strlen(buffer) > 0)
{
int count = IupGetInt(list_path, "COUNT");
count++;
IupSetAttributeId(list_path, "", count, buffer);
IupSetInt(list_path, "COUNT", count);
IupSetInt(list_path, "VALUE", count);
refresh_list_style();
}
}
return IUP_DEFAULT;
}
// 按钮回调:编辑
int btn_edit_cb(Ihandle *self)
{
int selected = IupGetInt(list_path, "VALUE");
if (selected == 0)
return IUP_DEFAULT;
char *current_val = IupGetAttributeId(list_path, "", 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(list_path, "", selected, buffer);
refresh_list_style();
}
}
return IUP_DEFAULT;
}
// 双击回调
int list_dblclick_cb(Ihandle *self, int item, char *text)
{
// 这里的 item 是点击的行号
if (item > 0)
{
// 选中该行
IupSetInt(list_path, "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)
{
int count = IupGetInt(list_path, "COUNT");
count++;
IupSetAttributeId(list_path, "", count, value);
IupSetInt(list_path, "COUNT", count);
IupSetInt(list_path, "VALUE", count);
refresh_list_style();
}
}
IupDestroy(filedlg);
return IUP_DEFAULT;
}
// 按钮回调:删除
int btn_del_cb(Ihandle *self)
{
int selected = IupGetInt(list_path, "VALUE");
if (selected == 0)
return IUP_DEFAULT;
IupSetAttribute(list_path, "REMOVEITEM", "SELECTED");
// 重新刷新,因为删除了中间项,后面的奇偶性变了
refresh_list_style();
return IUP_DEFAULT;
}
// 按钮回调:上移
int btn_up_cb(Ihandle *self)
{
int selected = IupGetInt(list_path, "VALUE");
if (selected <= 1)
return IUP_DEFAULT; // 已经是第一个或未选中
char *current = IupGetAttributeId(list_path, "", selected);
char *prev = IupGetAttributeId(list_path, "", 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(list_path, "", selected, buf_prev);
IupSetAttributeId(list_path, "", selected - 1, buf_curr);
IupSetInt(list_path, "VALUE", selected - 1);
// 刷新样式(虽然颜色不需要变,但为了保险)
refresh_list_style();
return IUP_DEFAULT;
}
// 按钮回调:下移
int btn_down_cb(Ihandle *self)
{
int selected = IupGetInt(list_path, "VALUE");
int count = IupGetInt(list_path, "COUNT");
if (selected == 0 || selected >= count)
return IUP_DEFAULT;
char *current = IupGetAttributeId(list_path, "", selected);
char *next = IupGetAttributeId(list_path, "", 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(list_path, "", selected, buf_next);
IupSetAttributeId(list_path, "", selected + 1, buf_curr);
IupSetInt(list_path, "VALUE", selected + 1);
refresh_list_style();
return IUP_DEFAULT;
}
// 按钮回调:确定
int btn_ok_cb(Ihandle *self)
{
save_path();
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"
"GitHubhttps://github.com/LHY0125/PathEditor\n"
"记得给我的项目点个star");
return IUP_DEFAULT;
}
+368
View File
@@ -0,0 +1,368 @@
#include "cb_edit.h"
#include "ui_utils.h"
#include "globals.h"
#include "utils.h"
#include <string.h>
#include <stdlib.h>
// 按钮回调:新建
int btn_new_cb(Ihandle *self)
{
char buffer[1024] = "";
if (custom_input_dialog("新建环境变量", "请输入路径:", buffer, sizeof(buffer)))
{
if (strlen(buffer) > 0)
{
// 记录历史
record_history();
Ihandle *current_list = get_current_list();
int count = IupGetInt(current_list, "COUNT");
count++;
IupSetAttributeId(current_list, "", count, buffer);
IupSetInt(current_list, "COUNT", count);
// 更新选中状态
set_single_selection(current_list, count);
// 同时添加到 raw_data
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
if (raw_data) {
add_string_list(raw_data, buffer);
}
refresh_single_list_style(current_list);
}
}
return IUP_DEFAULT;
}
// 按钮回调:编辑
int btn_edit_cb(Ihandle *self)
{
Ihandle *current_list = get_current_list();
// 获取第一个选中的项
int selected = get_first_selected_index(current_list);
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)
{
// 记录历史
record_history();
// 更新 UI
IupSetAttributeId(current_list, "", selected, buffer);
// 更新 raw_data
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
char *filter = IupGetAttribute(txt_search, "VALUE");
if (!filter || strlen(filter) == 0) {
if (raw_data && selected <= raw_data->count) {
free(raw_data->items[selected-1]);
raw_data->items[selected-1] = _strdup(buffer);
}
} else {
// 搜索状态下,忽略同步问题,或者编辑后清除搜索。
}
refresh_single_list_style(current_list);
}
}
return IUP_DEFAULT;
}
// 双击回调
int list_dblclick_cb(Ihandle *self, int item, char *text)
{
// 这里的 self 就是触发双击的列表控件
if (item > 0)
{
// 选中该行 (单选)
set_single_selection(self, item);
// 调用编辑逻辑
btn_edit_cb(NULL);
}
return IUP_DEFAULT;
}
// 按钮回调:删除 (支持多选)
int btn_del_cb(Ihandle *self)
{
Ihandle *current_list = get_current_list();
char *value = IupGetAttribute(current_list, "VALUE");
if (!value)
return IUP_DEFAULT;
int len = strlen(value);
int has_selection = 0;
for (int i = 0; i < len; i++) {
if (value[i] == '+') {
has_selection = 1;
break;
}
}
if (!has_selection)
{
IupMessage("提示", "请先选择要删除的项");
return IUP_DEFAULT;
}
// 记录历史
record_history();
// 获取 raw_data 缓存
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
// 从后往前遍历删除,避免索引错位
for (int i = len - 1; i >= 0; i--)
{
if (value[i] == '+')
{
int item_index = i + 1; // IUP 索引从 1 开始
char *val = IupGetAttributeId(current_list, "", item_index);
// 从缓存删除
if (val && raw_data)
{
char *val_copy = _strdup(val);
remove_from_raw_data(raw_data, val_copy);
free(val_copy);
}
// 从界面删除
IupSetInt(current_list, "REMOVEITEM", item_index);
}
}
// 重新刷新
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();
char *value = IupGetAttribute(current_list, "VALUE");
if (!value)
return IUP_DEFAULT;
int len = strlen(value);
char *new_value = _strdup(value);
int moved = 0;
// 预检查是否有移动
for (int i = 1; i < len; i++) {
if (new_value[i] == '+' && new_value[i - 1] == '-') {
moved = 1;
break;
}
}
if (moved) {
// 记录历史
record_history();
// 同步 raw_data (假设非搜索状态,raw_data 与 UI 一致)
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
// 从前往后遍历,如果当前项被选中且前一项未选中,则交换
for (int i = 1; i < len; i++)
{
if (new_value[i] == '+' && new_value[i - 1] == '-')
{
// 交换列表项内容
char *curr_text = IupGetAttributeId(current_list, "", i + 1);
char *prev_text = IupGetAttributeId(current_list, "", i);
// 需要复制,防止指针失效
char *curr_copy = curr_text ? _strdup(curr_text) : NULL;
char *prev_copy = prev_text ? _strdup(prev_text) : NULL;
IupSetAttributeId(current_list, "", i, curr_copy);
IupSetAttributeId(current_list, "", i + 1, prev_copy);
if (curr_copy) free(curr_copy);
if (prev_copy) free(prev_copy);
// 交换 raw_data
if (raw_data && i < raw_data->count) {
char *temp = raw_data->items[i];
raw_data->items[i] = raw_data->items[i-1];
raw_data->items[i-1] = temp;
}
// 交换选中状态
new_value[i] = '-';
new_value[i - 1] = '+';
}
}
// 更新选中状态
IupSetAttribute(current_list, "VALUE", new_value);
refresh_single_list_style(current_list);
}
free(new_value);
return IUP_DEFAULT;
}
// 按钮回调:下移 (支持多选)
int btn_down_cb(Ihandle *self)
{
Ihandle *current_list = get_current_list();
char *value = IupGetAttribute(current_list, "VALUE");
if (!value)
return IUP_DEFAULT;
int len = strlen(value);
char *new_value = _strdup(value);
int moved = 0;
// 预检查
for (int i = len - 2; i >= 0; i--) {
if (new_value[i] == '+' && new_value[i + 1] == '-') {
moved = 1;
break;
}
}
if (moved) {
// 记录历史
record_history();
// 同步 raw_data
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
// 从后往前遍历,如果当前项被选中且后一项未选中,则交换
for (int i = len - 2; i >= 0; i--)
{
if (new_value[i] == '+' && new_value[i + 1] == '-')
{
// 交换列表项内容
char *curr_text = IupGetAttributeId(current_list, "", i + 1);
char *next_text = IupGetAttributeId(current_list, "", i + 2);
// 需要复制
char *curr_copy = curr_text ? _strdup(curr_text) : NULL;
char *next_copy = next_text ? _strdup(next_text) : NULL;
IupSetAttributeId(current_list, "", i + 2, curr_copy);
IupSetAttributeId(current_list, "", i + 1, next_copy);
if (curr_copy) free(curr_copy);
if (next_copy) free(next_copy);
// 交换 raw_data
if (raw_data && i + 1 < raw_data->count) {
char *temp = raw_data->items[i];
raw_data->items[i] = raw_data->items[i+1];
raw_data->items[i+1] = temp;
}
// 交换选中状态
new_value[i] = '-';
new_value[i + 1] = '+';
}
}
// 更新选中状态
IupSetAttribute(current_list, "VALUE", new_value);
refresh_single_list_style(current_list);
}
free(new_value);
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;
}
// 记录历史 (放在循环外,一次操作)
record_history();
// 获取 raw_data 用于同步删除
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
// 从后往前遍历删除,避免索引错位
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)
{
// 从 raw_data 删除
if (raw_data) {
char *val_copy = _strdup(item);
remove_from_raw_data(raw_data, val_copy);
free(val_copy);
}
IupSetAttributeId(current_list, "REMOVEITEM", i, NULL);
}
}
refresh_single_list_style(current_list);
IupMessage("提示", "清理完成!");
return IUP_DEFAULT;
}
+260
View File
@@ -0,0 +1,260 @@
#include "cb_file.h"
#include "ui_utils.h"
#include "globals.h"
#include "utils.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h> // for GetFileAttributesA
// 按钮回调:浏览
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)
{
// 记录历史
record_history();
Ihandle *current_list = get_current_list();
int count = IupGetInt(current_list, "COUNT");
count++;
IupSetAttributeId(current_list, "", count, value);
IupSetInt(current_list, "COUNT", count);
// 更新选中状态
set_single_selection(current_list, count);
// 同步 raw_data
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
if (raw_data) {
add_string_list(raw_data, value);
}
refresh_single_list_style(current_list);
}
}
IupDestroy(filedlg);
return IUP_DEFAULT;
}
// 撤销回调
int btn_undo_cb(Ihandle *self)
{
StringList sys = {0}, user = {0};
if (pop_history(&undo_stack, &sys, &user)) {
// Push current state to redo
push_history(&redo_stack, &raw_sys_paths, &raw_user_paths);
// Restore
clear_string_list(&raw_sys_paths);
clear_string_list(&raw_user_paths);
raw_sys_paths = sys;
raw_user_paths = user;
// Refresh UI
refresh_ui_from_raw(list_sys, &raw_sys_paths);
refresh_ui_from_raw(list_user, &raw_user_paths);
IupSetAttribute(lbl_status, "TITLE", "状态: 已撤销");
} else {
IupSetAttribute(lbl_status, "TITLE", "状态: 没有可撤销的操作");
}
return IUP_DEFAULT;
}
// 重做回调
int btn_redo_cb(Ihandle *self)
{
StringList sys = {0}, user = {0};
if (pop_history(&redo_stack, &sys, &user)) {
// Push current state to undo
push_history(&undo_stack, &raw_sys_paths, &raw_user_paths);
// Restore
clear_string_list(&raw_sys_paths);
clear_string_list(&raw_user_paths);
raw_sys_paths = sys;
raw_user_paths = user;
// Refresh UI
refresh_ui_from_raw(list_sys, &raw_sys_paths);
refresh_ui_from_raw(list_user, &raw_user_paths);
IupSetAttribute(lbl_status, "TITLE", "状态: 已重做");
} else {
IupSetAttribute(lbl_status, "TITLE", "状态: 没有可重做的操作");
}
return IUP_DEFAULT;
}
// 导出配置
int btn_export_cb(Ihandle *self)
{
Ihandle *current_list = get_current_list();
int count = IupGetInt(current_list, "COUNT");
if (count == 0) {
IupMessage("提示", "当前列表为空,无法导出");
return IUP_DEFAULT;
}
Ihandle *filedlg = IupFileDlg();
IupSetAttribute(filedlg, "DIALOGTYPE", "SAVE");
IupSetAttribute(filedlg, "TITLE", "导出配置");
IupSetAttribute(filedlg, "FILTER", "*.txt");
IupSetAttribute(filedlg, "FILTERINFO", "Text Files (*.txt)");
IupPopup(filedlg, IUP_CENTER, IUP_CENTER);
if (IupGetInt(filedlg, "STATUS") != -1) {
char *filename = IupGetAttribute(filedlg, "VALUE");
if (filename) {
char final_path[1024];
strncpy(final_path, filename, sizeof(final_path));
final_path[sizeof(final_path)-1] = '\0';
// 检查是否以 .txt 结尾 (不区分大小写)
size_t len = strlen(final_path);
if (len < 4 || _stricmp(final_path + len - 4, ".txt") != 0) {
if (len + 4 < sizeof(final_path)) {
strcat(final_path, ".txt");
}
}
FILE *fp = fopen(final_path, "w");
if (fp) {
for (int i = 1; i <= count; i++) {
char *item = IupGetAttributeId(current_list, "", i);
if (item) fprintf(fp, "%s\n", item);
}
fclose(fp);
IupMessage("提示", "导出成功!");
} else {
IupMessage("错误", "无法打开文件进行写入");
}
}
}
IupDestroy(filedlg);
return IUP_DEFAULT;
}
// 导入配置
int btn_import_cb(Ihandle *self)
{
Ihandle *filedlg = IupFileDlg();
IupSetAttribute(filedlg, "DIALOGTYPE", "OPEN");
IupSetAttribute(filedlg, "TITLE", "导入配置");
IupSetAttribute(filedlg, "FILTER", "*.txt");
IupSetAttribute(filedlg, "FILTERINFO", "Text Files (*.txt)");
IupPopup(filedlg, IUP_CENTER, IUP_CENTER);
if (IupGetInt(filedlg, "STATUS") != -1) {
char *filename = IupGetAttribute(filedlg, "VALUE");
if (filename) {
FILE *fp = fopen(filename, "r");
if (fp) {
// Record history
record_history();
Ihandle *current_list = get_current_list();
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
char line[4096];
int imported_count = 0;
while (fgets(line, sizeof(line), fp)) {
// Trim newline
size_t len = strlen(line);
while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r')) {
line[len-1] = '\0';
len--;
}
if (len > 0) {
// Add to UI
int count = IupGetInt(current_list, "COUNT");
count++;
IupSetAttributeId(current_list, "", count, line);
IupSetInt(current_list, "COUNT", count);
// Add to raw_data
if (raw_data) add_string_list(raw_data, line);
imported_count++;
}
}
fclose(fp);
refresh_single_list_style(current_list);
char msg[64];
snprintf(msg, sizeof(msg), "导入成功!共导入 %d 条路径。", imported_count);
IupMessage("提示", msg);
} else {
IupMessage("错误", "无法打开文件进行读取");
}
}
}
IupDestroy(filedlg);
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))
{
// 记录历史
record_history();
// 如果正在搜索,先清空搜索框
IupSetAttribute(txt_search, "VALUE", "");
// 添加到列表末尾
int count = IupGetInt(current_list, "COUNT");
count++;
IupSetAttributeId(current_list, "", count, filename);
IupSetInt(current_list, "COUNT", count);
// 更新选中状态
set_single_selection(current_list, 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;
}
+214
View File
@@ -0,0 +1,214 @@
#include "cb_main.h"
#include "ui_utils.h"
#include "globals.h"
#include "registry.h"
#include "utils.h"
#include "cb_edit.h"
#include "cb_file.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
// 搜索回调
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_k_any_cb(Ihandle *self, int c)
{
// 处理 Delete 键
if (c == K_DEL)
{
btn_del_cb(NULL);
return IUP_IGNORE; // 阻止默认处理
}
return IUP_DEFAULT;
}
// 鼠标移动回调
int list_motion_cb(Ihandle *self, int x, int y, char *status)
{
int pos = IupConvertXYToPos(self, x, y);
if (pos > 0)
{
char *item = IupGetAttributeId(self, "", pos);
if (item)
{
char *expanded = expand_env_vars(item);
if (expanded)
{
IupSetAttribute(self, "TIP", expanded);
free(expanded);
}
else
{
IupSetAttribute(self, "TIP", item);
}
}
else
{
IupSetAttribute(self, "TIP", NULL);
}
}
else
{
IupSetAttribute(self, "TIP", NULL);
}
return IUP_DEFAULT;
}
// 对话框全局按键回调
int dialog_k_any_cb(Ihandle *self, int c)
{
switch (c)
{
case K_cN: // Ctrl+N 新建
btn_new_cb(NULL);
return IUP_IGNORE;
case K_cS: // Ctrl+S 保存
btn_ok_cb(NULL);
return IUP_IGNORE;
case K_cF: // Ctrl+F 搜索
if (txt_search)
{
IupSetFocus(txt_search);
}
return IUP_IGNORE;
case K_cZ: // Ctrl+Z 撤销
btn_undo_cb(NULL);
return IUP_IGNORE;
case K_cY: // Ctrl+Y 重做
btn_redo_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"
" - 导入/导出:备份和恢复配置。\n"
" - 快捷键:\n"
" Ctrl+N: 新建路径\n"
" Ctrl+S: 保存更改\n"
" Ctrl+F: 聚焦搜索框\n"
" Ctrl+Z: 撤销\n"
" Ctrl+Y: 重做\n"
"4. 点击【确定】保存更改并生效。\n"
"5. 注意:某些正在运行的程序可能需要重启才能识别新的环境变量。\n\n"
"--------------------------------------------------\n"
"作者:LHY\n"
"邮箱:3364451258@qq.com\n"
"GitHubhttps://github.com/LHY0125/PathEditor\n"
"记得给我的项目点个star");
return IUP_DEFAULT;
}
// 标签页切换回调
int tabs_tabchange_cb(Ihandle *self, int new_pos, int old_pos)
{
if (new_pos == 2)
{
// 合并预览模式
IupSetAttribute(list_merged, "REMOVEITEM", "ALL");
int count = 0;
// 添加系统变量
for (int i = 0; i < raw_sys_paths.count; i++)
{
count++;
IupSetAttributeId(list_merged, "", count, raw_sys_paths.items[i]);
}
// 添加用户变量
for (int i = 0; i < raw_user_paths.count; i++)
{
count++;
IupSetAttributeId(list_merged, "", count, raw_user_paths.items[i]);
}
IupSetInt(list_merged, "COUNT", count);
refresh_single_list_style(list_merged);
// 禁用编辑按钮
toggle_edit_buttons(0);
}
else
{
// 编辑模式 (检查管理员权限)
if (check_admin())
{
toggle_edit_buttons(1);
}
else
{
toggle_edit_buttons(0);
}
}
return IUP_DEFAULT;
}
// 主题切换回调
int btn_theme_cb(Ihandle *self)
{
is_dark_mode = !is_dark_mode;
if (is_dark_mode)
IupSetAttribute(btn_theme, "TITLE", "浅色模式");
else
IupSetAttribute(btn_theme, "TITLE", "深色模式");
apply_theme();
return IUP_DEFAULT;
}
+24
View File
@@ -0,0 +1,24 @@
#include <stdlib.h>
#include "globals.h"
// 全局控件定义
Ihandle *dlg = NULL; // 主对话框
Ihandle *tabs_main = NULL; // 主选项卡
Ihandle *list_sys = NULL, *list_user = NULL, *list_merged = NULL; // 列表控件
Ihandle *lbl_status = NULL; // 状态栏
Ihandle *btn_new = NULL, *btn_edit = NULL, *btn_browse = NULL, *btn_del = NULL, *btn_up = NULL, *btn_down = NULL; // 右侧按钮
Ihandle *btn_undo = NULL, *btn_redo = NULL; // 撤销重做按钮
Ihandle *btn_import = NULL, *btn_export = NULL; // 导入导出按钮
Ihandle *btn_ok = NULL, *btn_cancel = NULL, *btn_help = NULL; // 确认取消帮助按钮
Ihandle *btn_clean = NULL; // 一键清理按钮
Ihandle *btn_theme = NULL; // 主题切换按钮
Ihandle *txt_search = NULL; // 搜索框
// 历史记录栈
HistoryStack undo_stack = {0};
HistoryStack redo_stack = {0};
// 全局变量定义
StringList raw_sys_paths = {0};
StringList raw_user_paths = {0};
int is_dark_mode = 0; // 默认浅色模式
+42 -106
View File
@@ -1,135 +1,71 @@
#include <windows.h>
#include <iup.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include "globals.h"
#include "utils.h"
#include "registry.h"
#include "callbacks.h"
#include "ui.h"
#include "cb_main.h"
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#endif
// 全局控件定义
Ihandle *dlg, *list_path, *lbl_status;
Ihandle *btn_new, *btn_edit, *btn_browse, *btn_del, *btn_up, *btn_down;
Ihandle *btn_ok, *btn_cancel, *btn_help;
/*
编译命令:
Remove-Item -Path "obj\*.o" -Force -ErrorAction SilentlyContinue && mingw32-make
打包命令:
build_installer.bat
*/
// 主函数
int main(int argc, char **argv)
{
// 强制设置 UTF8MODE 环境变量,必须在 IupOpen 之前
putenv("IUP_UTF8MODE=YES");
// 初始化 IUP
if (IupOpen(&argc, &argv) == IUP_ERROR)
{
return 1;
}
IupOpen(&argc, &argv);
// 开启 UTF-8 支持
IupSetGlobal("UTF8MODE", "YES");
// 创建列表控件
list_path = IupFlatList();
IupSetAttribute(list_path, "EXPAND", "YES");
IupSetAttribute(list_path, "ITEMPADDING", "5x5");
IupSetAttribute(list_path, "BACKCOLOR", "255 255 255");
IupSetAttribute(list_path, "BORDER", "YES");
IupSetAttribute(list_path, "CANFOCUS", "YES");
IupSetAttribute(list_path, "HLINE", "NO"); // 禁用横线,使用斑马纹
// IupFlatList 不支持 VISIBLELINES,高度由 EXPAND 和布局决定
// 启用 UIPI 绕过,允许拖拽
HMODULE hUser32 = LoadLibraryA("user32.dll");
if (hUser32)
{
typedef BOOL(WINAPI * ChangeWindowMessageFilterProc)(UINT, DWORD);
ChangeWindowMessageFilterProc pChangeWindowMessageFilter = (ChangeWindowMessageFilterProc)GetProcAddress(hUser32, "ChangeWindowMessageFilter");
if (pChangeWindowMessageFilter)
{
// WM_DROPFILES = 0x0233, WM_COPYDATA = 0x004A, MSGFLT_ADD = 1
pChangeWindowMessageFilter(0x0233, 1);
pChangeWindowMessageFilter(0x004A, 1);
}
FreeLibrary(hUser32);
}
// 创建右侧按钮
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);
// 初始化历史栈
init_history_stack(&undo_stack);
init_history_stack(&redo_stack);
// 设置按钮回调
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);
// 创建主界面
dlg = create_main_dialog();
// 设置双击回调
IupSetCallback(list_path, "DBLCLICK_CB", (Icallback)list_dblclick_cb);
// 设置按钮大小 (宽度和高度都增加约1/4)
IupSetAttribute(btn_new, "RASTERSIZE", "100x32");
IupSetAttribute(btn_edit, "RASTERSIZE", "100x32");
IupSetAttribute(btn_browse, "RASTERSIZE", "100x32");
IupSetAttribute(btn_del, "RASTERSIZE", "100x32");
IupSetAttribute(btn_up, "RASTERSIZE", "100x32");
IupSetAttribute(btn_down, "RASTERSIZE", "100x32");
Ihandle *vbox_btns = IupVbox(
btn_new, btn_edit, btn_browse, btn_del,
IupFill(), // 间隔
btn_up, btn_down,
NULL);
IupSetAttribute(vbox_btns, "GAP", "5");
IupSetAttribute(vbox_btns, "MARGIN", "0x0");
// 上部布局:列表 + 按钮
Ihandle *hbox_main = IupHbox(list_path, vbox_btns, NULL);
IupSetAttribute(hbox_main, "GAP", "10");
IupSetAttribute(hbox_main, "MARGIN", "10x10");
// 状态标签
lbl_status = IupLabel("状态: 就绪");
IupSetAttribute(lbl_status, "EXPAND", "HORIZONTAL");
// 底部按钮
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", "100x32");
IupSetAttribute(btn_cancel, "RASTERSIZE", "100x32");
IupSetAttribute(btn_help, "RASTERSIZE", "100x32");
Ihandle *hbox_bottom = IupHbox(lbl_status, IupFill(), btn_help, btn_ok, btn_cancel, NULL);
IupSetAttribute(hbox_bottom, "GAP", "10");
IupSetAttribute(hbox_bottom, "MARGIN", "10x10");
IupSetAttribute(hbox_bottom, "ALIGNMENT", "ACENTER");
// 总体布局
Ihandle *vbox_all = IupVbox(
IupLabel("系统变量 Path:"),
hbox_main,
hbox_bottom,
NULL);
IupSetAttribute(vbox_all, "MARGIN", "10x10");
IupSetAttribute(vbox_all, "GAP", "5");
// 创建对话框
dlg = IupDialog(vbox_all);
IupSetAttribute(dlg, "TITLE", "编辑环境变量 (IUP版)");
IupSetAttribute(dlg, "SIZE", "450x350");
IupSetAttribute(dlg, "MINBOX", "NO");
IupSetAttribute(dlg, "MAXBOX", "NO");
// 设置全局按键回调 (如果在 ui.c 中未设置)
IupSetCallback(dlg, "K_ANY", (Icallback)dialog_k_any_cb);
// 加载数据
if (!check_admin())
{
IupMessage("警告", "程序未以管理员身份运行,您只能查看,无法保存更改!");
IupSetAttribute(dlg, "TITLE", "编辑环境变量 (只读模式)");
IupSetAttribute(lbl_status, "TITLE", "状态: 只读模式 (权限不足)");
IupMessage("警告", "未检测到管理员权限!\n您可能无法保存更改。\n请右键以【管理员身份运行】。");
}
load_all_paths();
// 显示对话框
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
// IUP List APPEND 属性需要在控件 Map 之后才能生效
// IupShowXY 会触发 Map
load_path();
// 进入主循环
IupMainLoop();
// 清理资源
IupClose();
return 0;
}
+110 -128
View File
@@ -6,17 +6,23 @@
#include <stdlib.h>
#include <wchar.h>
// 从注册表加载PATH
void load_path()
// 内部辅助函数:加载单个列表
static void load_single_path(HKEY hKeyRoot, const wchar_t *regPath, Ihandle *list, StringList *cache)
{
// 清空旧缓存
clear_string_list(cache);
HKEY hKey;
LONG res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, REG_PATH, 0, KEY_READ, &hKey);
LONG res = RegOpenKeyExW(hKeyRoot, regPath, 0, KEY_READ, &hKey);
if (res != ERROR_SUCCESS)
{
char msg[512];
snprintf(msg, sizeof(msg), "无法打开注册表键 (HKLM)。\n路径: %ls\n错误码: %ld\n\n请尝试右键点击程序 -> '以管理员身份运行'。", REG_PATH, res);
IupMessage("错误", msg);
IupSetAttribute(lbl_status, "TITLE", "状态: 注册表读取失败");
// 只有 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;
}
@@ -24,172 +30,148 @@ void load_path()
res = RegQueryValueExW(hKey, REG_VALUE, NULL, &type, NULL, &size);
if (res == ERROR_SUCCESS)
{
// 安全分配内存:size 是字节数,多分配 2 个字节给 null 终止符
wchar_t *buffer = (wchar_t *)malloc(size + 2);
if (!buffer)
if (buffer)
{
IupMessage("错误", "内存分配失败!");
RegCloseKey(hKey);
return;
}
// 初始化内存
memset(buffer, 0, size + 2);
if (RegQueryValueExW(hKey, REG_VALUE, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS)
{
// 重新实现分割逻辑,避免 wcstok 的兼容性问题
wchar_t *current = buffer;
wchar_t *next_semicolon = NULL;
int count = 0;
// 清空列表
IupSetAttribute(list_path, "REMOVEITEM", "ALL");
// 检查内容是否为空
if (wcslen(buffer) == 0)
memset(buffer, 0, size + 2);
if (RegQueryValueExW(hKey, REG_VALUE, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS)
{
IupMessage("提示", "读取到的 PATH 变量为空!");
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);
}
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_path, "", count, utf8_str);
free(utf8_str);
}
if (next_semicolon)
{
current = next_semicolon + 1;
}
else
{
break;
}
}
IupSetInt(list_path, "COUNT", count); // 显式设置列表项数量
IupSetInt(list_path, "VALUE", 1); // 选中第一项
// 刷新斑马纹样式
refresh_list_style();
char status_msg[100];
sprintf(status_msg, "状态: 已加载 %d 个条目", count);
IupSetAttribute(lbl_status, "TITLE", status_msg);
free(buffer);
}
else
{
IupMessage("错误", "读取 PATH 值内容失败!");
IupSetAttribute(lbl_status, "TITLE", "状态: 读取值内容失败");
}
free(buffer);
}
else
{
char msg[256];
sprintf(msg, "查询 PATH 值大小失败。错误码: %ld", res);
IupMessage("错误", msg);
IupSetAttribute(lbl_status, "TITLE", "状态: 查询值失败");
}
RegCloseKey(hKey);
}
// 保存PATH到注册表
void save_path()
// 加载所有PATH
void load_all_paths()
{
if (!check_admin())
{
IupMessage("错误", "需要管理员权限才能保存更改!\n请重新以管理员身份运行程序。");
return;
}
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);
int count = IupGetInt(list_path, "COUNT");
if (count == 0)
{
// 警告:清空PATH是很危险的
if (IupAlarm("警告", "PATH 为空!这可能导致系统命令无法使用。\n确定要保存吗?", "确定", "取消", NULL) != 1)
{
return;
}
}
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_path, "", i);
char *item = IupGetAttributeId(list, "", i);
if (item)
{
wchar_t *witem = utf8_to_wide(item);
total_len += wcslen(witem) + 1; // +1 for ';'
total_len += wcslen(witem) + 1;
free(witem);
}
}
total_len += 1; // null terminator
total_len += 1;
wchar_t *buffer = (wchar_t *)malloc(total_len * sizeof(wchar_t));
if (!buffer)
{
IupMessage("错误", "内存分配失败 (保存时)");
return;
}
buffer[0] = L'\0';
return 0;
buffer[0] = L'\0';
for (int i = 1; i <= count; i++)
{
char *item = IupGetAttributeId(list_path, "", 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;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, REG_PATH, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS)
int success = 0;
if (RegOpenKeyExW(hKeyRoot, regPath, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS)
{
// 使用 REG_EXPAND_SZ 类型,因为 PATH 可能包含 %SystemRoot%
DWORD size = (wcslen(buffer) + 1) * sizeof(wchar_t);
if (RegSetValueExW(hKey, REG_VALUE, 0, REG_EXPAND_SZ, (LPBYTE)buffer, size) == ERROR_SUCCESS)
{
// 发送系统广播通知环境变量已更改
SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL);
IupMessage("成功", "PATH 环境变量已更新!");
IupSetAttribute(lbl_status, "TITLE", "状态: 保存成功");
}
else
{
IupMessage("错误", "写入注册表失败!");
IupSetAttribute(lbl_status, "TITLE", "状态: 保存失败");
success = 1;
}
RegCloseKey(hKey);
}
else
{
IupMessage("错误", "无法打开注册表进行写入。请检查权限!");
IupSetAttribute(lbl_status, "TITLE", "状态: 打开注册表失败");
}
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", "状态: 保存失败");
}
}
+173
View File
@@ -0,0 +1,173 @@
#include "ui.h"
#include "config.h"
#include "globals.h"
#include "ui_utils.h"
#include "cb_edit.h"
#include "cb_file.h"
#include "cb_main.h"
#include <stdlib.h>
// 创建列表控件
Ihandle *create_path_list()
{
Ihandle *list = IupFlatList();
IupSetAttribute(list, "EXPAND", "YES");
IupSetAttribute(list, "MULTIPLE", "YES");
IupSetAttribute(list, "ITEMPADDING", UI_LIST_ITEM_PADDING);
IupSetAttribute(list, "BACKCOLOR", UI_LIST_BGCOLOR);
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);
IupSetCallback(list, "MOTION_CB", (Icallback)list_motion_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_undo = IupButton("撤销(Z)", NULL);
btn_redo = IupButton("重做(Y)", 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_undo, "ACTION", (Icallback)btn_undo_cb);
IupSetCallback(btn_redo, "ACTION", (Icallback)btn_redo_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_BUTTON_SIZE);
IupSetAttribute(btn_edit, "RASTERSIZE", UI_BUTTON_SIZE);
IupSetAttribute(btn_browse, "RASTERSIZE", UI_BUTTON_SIZE);
IupSetAttribute(btn_del, "RASTERSIZE", UI_BUTTON_SIZE);
IupSetAttribute(btn_undo, "RASTERSIZE", UI_BUTTON_SIZE);
IupSetAttribute(btn_redo, "RASTERSIZE", UI_BUTTON_SIZE);
IupSetAttribute(btn_up, "RASTERSIZE", UI_BUTTON_SIZE);
IupSetAttribute(btn_down, "RASTERSIZE", UI_BUTTON_SIZE);
IupSetAttribute(btn_clean, "RASTERSIZE", UI_BUTTON_SIZE);
Ihandle *vbox_btns = IupVbox(
btn_new, btn_edit, btn_browse, btn_del,
IupFill(), // 间隔
btn_undo, btn_redo,
IupFill(),
btn_clean, // 放在上移下移之前,或者最下面,这里放在中间偏下
IupFill(),
btn_up, btn_down,
NULL);
IupSetAttribute(vbox_btns, "GAP", UI_GAP_BUTTONS);
IupSetAttribute(vbox_btns, "MARGIN", "0x0");
return vbox_btns;
}
// 创建底部按钮区域
Ihandle *create_bottom_buttons()
{
// 创建底部按钮
btn_help = IupButton("帮助(H)", NULL);
IupSetCallback(btn_help, "ACTION", (Icallback)btn_help_cb);
IupSetAttribute(btn_help, "RASTERSIZE", UI_BUTTON_SMALL_SIZE);
btn_theme = IupButton("深色模式", NULL);
IupSetCallback(btn_theme, "ACTION", (Icallback)btn_theme_cb);
IupSetAttribute(btn_theme, "RASTERSIZE", UI_BUTTON_SMALL_SIZE);
lbl_status = IupLabel("就绪");
IupSetAttribute(lbl_status, "EXPAND", "HORIZONTAL");
btn_import = IupButton("导入配置", NULL);
IupSetCallback(btn_import, "ACTION", (Icallback)btn_import_cb);
IupSetAttribute(btn_import, "RASTERSIZE", UI_BUTTON_SIZE);
btn_export = IupButton("导出配置", NULL);
IupSetCallback(btn_export, "ACTION", (Icallback)btn_export_cb);
IupSetAttribute(btn_export, "RASTERSIZE", UI_BUTTON_SIZE);
btn_ok = IupButton("确定(O)", NULL);
IupSetCallback(btn_ok, "ACTION", (Icallback)btn_ok_cb);
IupSetAttribute(btn_ok, "RASTERSIZE", UI_BUTTON_SIZE);
btn_cancel = IupButton("取消(C)", NULL);
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_cb);
IupSetAttribute(btn_cancel, "RASTERSIZE", UI_BUTTON_SIZE);
Ihandle *hbox_bottom = IupHbox(
btn_help,
btn_theme,
lbl_status,
IupFill(),
btn_import,
btn_export,
btn_ok,
btn_cancel,
NULL);
IupSetAttribute(hbox_bottom, "GAP", UI_GAP_BOTTOM);
IupSetAttribute(hbox_bottom, "ALIGNMENT", "ACENTER");
IupSetAttribute(hbox_bottom, "MARGIN", "0x0");
return hbox_bottom;
}
// 创建主对话框
Ihandle *create_main_dialog()
{
// 创建两个列表
list_sys = create_path_list();
list_user = create_path_list();
list_merged = create_path_list();
IupSetAttribute(list_merged, "READONLY", "YES");
IupSetAttribute(list_merged, "MULTIPLE", "NO");
IupSetAttribute(list_merged, "BGCOLOR", UI_LIST_MERGED_BGCOLOR); // 灰色背景
// 创建标签页
tabs_main = IupTabs(list_sys, list_user, list_merged, NULL);
IupSetAttribute(tabs_main, "TABTITLE0", "系统变量 (System PATH)");
IupSetAttribute(tabs_main, "TABTITLE1", "用户变量 (User PATH)");
IupSetAttribute(tabs_main, "TABTITLE2", "合并预览 (Merged PATH)");
// 设置标签页切换回调
IupSetCallback(tabs_main, "TABCHANGEPOS_CB", (Icallback)tabs_tabchange_cb);
// 搜索框
txt_search = IupText(NULL);
IupSetAttribute(txt_search, "NAME", "TXT_SEARCH");
IupSetAttribute(txt_search, "CUEBANNER", "搜索...");
IupSetCallback(txt_search, "VALUECHANGED_CB", (Icallback)txt_search_cb);
IupSetAttribute(txt_search, "EXPAND", "HORIZONTAL");
// 布局
Ihandle *btns = create_main_buttons();
Ihandle *hbox_mid = IupHbox(tabs_main, btns, NULL);
IupSetAttribute(hbox_mid, "GAP", UI_GAP_MAIN);
IupSetAttribute(hbox_mid, "MARGIN", "0x0");
Ihandle *bottom = create_bottom_buttons();
Ihandle *vbox_main = IupVbox(txt_search, hbox_mid, bottom, NULL);
IupSetAttribute(vbox_main, "GAP", UI_GAP_MAIN);
IupSetAttribute(vbox_main, "MARGIN", UI_MARGIN_MAIN);
Ihandle *dlg = IupDialog(vbox_main);
IupSetAttribute(dlg, "TITLE", UI_WINDOW_TITLE);
IupSetAttribute(dlg, "RASTERSIZE", UI_WINDOW_SIZE);
IupSetAttribute(dlg, "MINSIZE", UI_WINDOW_SIZE);
return dlg;
}
+225
View File
@@ -0,0 +1,225 @@
#include "ui_utils.h"
#include "globals.h"
#include "utils.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
// 获取第一个选中的索引(1-based),如果没有选中则返回 0
int get_first_selected_index(Ihandle *list)
{
char *value = IupGetAttribute(list, "VALUE");
if (!value)
return 0;
int len = strlen(value);
for (int i = 0; i < len; i++)
{
if (value[i] == '+')
return i + 1;
}
return 0;
}
// 设置单选(1-based
void set_single_selection(Ihandle *list, int index)
{
int count = IupGetInt(list, "COUNT");
if (count <= 0)
return;
char *new_val = (char *)malloc(count + 1);
if (!new_val)
return;
for (int i = 0; i < count; i++)
{
new_val[i] = '-';
}
new_val[count] = '\0';
if (index >= 1 && index <= count)
{
new_val[index - 1] = '+';
}
IupSetAttribute(list, "VALUE", new_val);
free(new_val);
}
// 从原始数据刷新UI
void refresh_ui_from_raw(Ihandle *list, StringList *raw)
{
IupSetAttribute(list, "REMOVEITEM", "ALL");
for (int i = 0; i < raw->count; i++)
{
IupSetAttributeId(list, "", i + 1, raw->items[i]);
}
IupSetInt(list, "COUNT", raw->count);
refresh_single_list_style(list);
}
// 记录历史
void record_history()
{
push_history(&undo_stack, &raw_sys_paths, &raw_user_paths);
clear_history_stack(&redo_stack);
// 更新按钮状态(可选)
// IupSetAttribute(btn_undo, "ACTIVE", "YES");
// IupSetAttribute(btn_redo, "ACTIVE", "NO");
}
// 静态辅助函数:对话框确定
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);
// 应用主题到对话框
if (is_dark_mode) {
IupSetAttribute(dlg, "BGCOLOR", "50 50 50");
IupSetAttribute(text, "BGCOLOR", "30 30 30");
IupSetAttribute(text, "FGCOLOR", "255 255 255");
IupSetAttribute(IupGetChild(vbox, 0), "FGCOLOR", "255 255 255"); // Label
}
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 索引
int pos = IupGetInt(tabs_main, "VALUEPOS");
if (pos == 0)
return list_sys;
if (pos == 1)
return list_user;
if (pos == 2)
return list_merged;
return list_sys; // 默认
}
// 从 raw_data 中删除指定字符串
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; // 假设没有重复,只删除第一个匹配
}
}
}
// 切换编辑按钮状态
void toggle_edit_buttons(int enable)
{
char *val = enable ? "YES" : "NO";
IupSetAttribute(btn_new, "ACTIVE", val); // 新建按钮
IupSetAttribute(btn_edit, "ACTIVE", val); // 编辑按钮
IupSetAttribute(btn_browse, "ACTIVE", val); // 浏览按钮
IupSetAttribute(btn_del, "ACTIVE", val); // 删除按钮
IupSetAttribute(btn_clean, "ACTIVE", val); // 清除按钮
IupSetAttribute(btn_up, "ACTIVE", val); // 上移按钮
IupSetAttribute(btn_down, "ACTIVE", val); // 下移按钮
IupSetAttribute(btn_import, "ACTIVE", val); // 导入按钮
IupSetAttribute(btn_export, "ACTIVE", "YES"); // 导出按钮始终可用
}
// 应用主题
void apply_theme()
{
char *bg_color = is_dark_mode ? "50 50 50" : "240 240 240";
char *fg_color = is_dark_mode ? "255 255 255" : "0 0 0";
char *list_bg = is_dark_mode ? "60 60 60" : "255 255 255";
char *text_bg = is_dark_mode ? "30 30 30" : "255 255 255";
// 主对话框
IupSetAttribute(dlg, "BGCOLOR", bg_color);
// 列表
IupSetAttribute(list_sys, "BACKCOLOR", list_bg);
IupSetAttribute(list_sys, "FGCOLOR", fg_color);
IupSetAttribute(list_user, "BACKCOLOR", list_bg);
IupSetAttribute(list_user, "FGCOLOR", fg_color);
IupSetAttribute(list_merged, "BACKCOLOR", list_bg);
IupSetAttribute(list_merged, "FGCOLOR", fg_color);
// 文本框
IupSetAttribute(txt_search, "BGCOLOR", text_bg);
IupSetAttribute(txt_search, "FGCOLOR", fg_color);
// 标签
IupSetAttribute(lbl_status, "FGCOLOR", fg_color);
// 刷新列表样式
refresh_list_style();
refresh_single_list_style(list_merged);
}
+287 -9
View File
@@ -3,6 +3,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <iup.h>
#include <time.h>
#include <direct.h>
#include <string.h>
// 宽字符转UTF-8 (用于IUP显示)
char *wide_to_utf8(const wchar_t *wstr)
@@ -39,23 +42,298 @@ int check_admin()
return 0;
}
// 刷新列表样式(斑马纹)
void refresh_list_style()
// 展开环境变量
char* expand_env_vars(const char* path)
{
if (!list_path)
if (!path) return NULL;
// 先转换为宽字符,因为ExpandEnvironmentStringsW不支持UTF-8
wchar_t *wpath = utf8_to_wide(path);
if (!wpath) return NULL;
DWORD size = ExpandEnvironmentStringsW(wpath, NULL, 0);
if (size == 0) {
free(wpath);
return NULL;
}
wchar_t *wexpanded = (wchar_t *)malloc(size * sizeof(wchar_t));
ExpandEnvironmentStringsW(wpath, wexpanded, size);
free(wpath);
char *expanded = wide_to_utf8(wexpanded);
free(wexpanded);
return expanded;
}
// 检查路径是否存在
static int path_exists(const char *path)
{
char *expanded_path = expand_env_vars(path);
if (!expanded_path)
return 0;
wchar_t *wpath = utf8_to_wide(expanded_path);
free(expanded_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_path, "COUNT");
int count = IupGetInt(list, "COUNT");
// 用于查重的简单数组(实际项目可以用哈希表)
// 为了简单,我们只用双重循环检查重复,性能在几百个条目下没问题
for (int i = 1; i <= count; i++)
{
// 奇数行:白色
// 偶数行:极浅灰色 (245 245 245)
if (i % 2 == 0)
char *item = IupGetAttributeId(list, "", i);
if (!item)
continue;
// 默认颜色:黑字
char fg_color[32];
if (is_dark_mode)
strcpy(fg_color, "255 255 255");
else
strcpy(fg_color, "0 0 0");
// 1. 检查有效性
if (!path_exists(item))
{
IupSetAttributeId(list_path, "ITEMBGCOLOR", i, "245 245 245");
// 无效路径:红色
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 (is_dark_mode)
{
if (i % 2 == 0)
IupSetAttributeId(list, "ITEMBGCOLOR", i, "60 60 60");
else
IupSetAttributeId(list, "ITEMBGCOLOR", i, "50 50 50");
}
else
{
IupSetAttributeId(list_path, "ITEMBGCOLOR", i, "255 255 255");
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;
}
// 复制字符串列表
void copy_string_list(StringList *dest, StringList *src)
{
init_string_list(dest);
if (!src || src->count == 0)
return;
for (int i = 0; i < src->count; i++)
{
add_string_list(dest, src->items[i]);
}
}
// 初始化历史栈
void init_history_stack(HistoryStack *stack)
{
stack->top = NULL;
stack->count = 0;
}
// 压入历史
void push_history(HistoryStack *stack, StringList *sys, StringList *user)
{
HistoryNode *node = (HistoryNode *)malloc(sizeof(HistoryNode));
if (!node)
return;
copy_string_list(&node->sys_paths, sys);
copy_string_list(&node->user_paths, user);
node->next = stack->top;
stack->top = node;
stack->count++;
// 简单限制:如果超过 50 个,就不处理底部了(太麻烦),反正内存够用
}
// 弹出历史
int pop_history(HistoryStack *stack, StringList *out_sys, StringList *out_user)
{
if (!stack->top)
return 0;
HistoryNode *node = stack->top;
stack->top = node->next;
stack->count--;
// 转移所有权,避免复制
*out_sys = node->sys_paths;
*out_user = node->user_paths;
free(node);
return 1;
}
// 清空历史栈
void clear_history_stack(HistoryStack *stack)
{
while (stack->top)
{
HistoryNode *node = stack->top;
stack->top = node->next;
clear_string_list(&node->sys_paths);
clear_string_list(&node->user_paths);
free(node);
}
stack->count = 0;
}