diff --git a/Makefile b/Makefile index 4b160c3..68eb141 100644 --- a/Makefile +++ b/Makefile @@ -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/ui.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 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 +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)/resources.o EXE = $(BIN_DIR)/PathEditor.exe all: $(BIN_DIR) $(OBJ_DIR) $(EXE) @@ -43,10 +43,19 @@ $(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.o: src/ui.c +$(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)/resources.o: ico/resources.rc @@ -54,4 +63,4 @@ $(OBJ_DIR)/resources.o: ico/resources.rc clean: if exist $(OBJ_DIR)\*.o del /Q $(OBJ_DIR)\*.o - if exist $(BIN_DIR)\*.exe del /Q $(BIN_DIR)\*.exe \ No newline at end of file + if exist $(BIN_DIR)\*.exe del /Q $(BIN_DIR)\*.exe diff --git a/bin/PathEditor.exe b/bin/PathEditor.exe index 0bce9f1..62f75d8 100644 Binary files a/bin/PathEditor.exe and b/bin/PathEditor.exe differ diff --git a/bin/records/backup_20260317_201852.reg b/bin/records/backup_20260317_201852.reg new file mode 100644 index 0000000..d33147a Binary files /dev/null and b/bin/records/backup_20260317_201852.reg differ diff --git a/include/callbacks.h b/include/callbacks.h deleted file mode 100644 index f3c9009..0000000 --- a/include/callbacks.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef CALLBACKS_H -#define CALLBACKS_H - -#include - -// 按钮回调 -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_clean_cb(Ihandle *self); -int btn_ok_cb(Ihandle *self); -int btn_cancel_cb(Ihandle *self); -int btn_help_cb(Ihandle *self); - -// 搜索回调 -int txt_search_cb(Ihandle *self); - -// 双击回调 -int list_dblclick_cb(Ihandle *self, int item, char *text); - -// 拖拽回调 -int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y); - -// 键盘按键回调 -int list_k_any_cb(Ihandle *self, int c); - -#endif // CALLBACKS_H \ No newline at end of file diff --git a/include/cb_edit.h b/include/cb_edit.h new file mode 100644 index 0000000..c8c5143 --- /dev/null +++ b/include/cb_edit.h @@ -0,0 +1,15 @@ +#ifndef CB_EDIT_H +#define CB_EDIT_H + +#include + +// 编辑相关回调 +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 diff --git a/include/cb_file.h b/include/cb_file.h new file mode 100644 index 0000000..2d028c1 --- /dev/null +++ b/include/cb_file.h @@ -0,0 +1,14 @@ +#ifndef CB_FILE_H +#define CB_FILE_H + +#include + +// 文件和历史记录回调 +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 diff --git a/include/cb_main.h b/include/cb_main.h new file mode 100644 index 0000000..1d00154 --- /dev/null +++ b/include/cb_main.h @@ -0,0 +1,17 @@ +#ifndef CB_MAIN_H +#define CB_MAIN_H + +#include + +// 主界面交互回调 +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 diff --git a/include/globals.h b/include/globals.h index 85e9949..1fa8ca6 100644 --- a/include/globals.h +++ b/include/globals.h @@ -9,22 +9,29 @@ #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; // 搜索框 +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 { @@ -36,9 +43,34 @@ typedef struct { 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 \ No newline at end of file diff --git a/include/ui.h b/include/ui.h index e894714..dc024ac 100644 --- a/include/ui.h +++ b/include/ui.h @@ -12,4 +12,6 @@ Ihandle *create_main_buttons(); // 创建底部按钮区域 Ihandle *create_bottom_buttons(); +Ihandle *create_main_dialog(); + #endif // UI_H \ No newline at end of file diff --git a/include/ui_utils.h b/include/ui_utils.h new file mode 100644 index 0000000..c63c144 --- /dev/null +++ b/include/ui_utils.h @@ -0,0 +1,18 @@ +#ifndef UI_UTILS_H +#define UI_UTILS_H + +#include +#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 diff --git a/include/utils.h b/include/utils.h index 72be326..ea62638 100644 --- a/include/utils.h +++ b/include/utils.h @@ -14,6 +14,9 @@ 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); diff --git a/src/callbacks.c b/src/callbacks.c deleted file mode 100644 index 97f7f0f..0000000 --- a/src/callbacks.c +++ /dev/null @@ -1,547 +0,0 @@ -#include "callbacks.h" -#include "globals.h" -#include "registry.h" -#include "utils.h" -#include -#include - -// 简单的自定义输入对话框,支持更宽的输入框 -// 返回 1 表示确定,0 表示取消 -int show_custom_input_dialog(const char *title, const char *label_text, char *buffer, int buffer_size) -{ - Ihandle *text = IupText(NULL); - IupSetAttribute(text, "VALUE", buffer); - IupSetAttribute(text, "EXPAND", "HORIZONTAL"); - IupSetAttribute(text, "RASTERSIZE", "600x"); // 设置宽度为600像素 - IupSetAttribute(text, "VISIBLECOLUMNS", "80"); // 设置可见列数 - - // 如果是编辑模式,全选文本 - if (strlen(buffer) > 0) - { - IupSetAttribute(text, "SELECTION", "ALL"); - } - - Ihandle *dlg = IupDialog( - IupVbox( - IupLabel(label_text), - text, - IupHbox( - IupFill(), - IupButton("确定", "1"), // "1" 会被返回 - IupButton("取消", "0"), // "0" 会被返回 - NULL), - NULL)); - - // 布局设置 - IupSetAttribute(dlg, "TITLE", title); - IupSetAttribute(dlg, "MINBOX", "NO"); - IupSetAttribute(dlg, "MAXBOX", "NO"); - IupSetAttribute(dlg, "RESIZE", "NO"); - IupSetAttribute(dlg, "MARGIN", "10x10"); - IupSetAttribute(dlg, "GAP", "10"); - - // 按钮响应 - // 这是一个简单的 hack:IupPopup 的参数 x, y 如果是 IUP_CENTER 等,它是一个模态循环。 - // 但是标准的 IupPopup 不返回按钮值。 - // 我们使用 IupAlarm 类似的逻辑,或者使用 IUP 提供的 IupGetParam。 - // 为了最简单实现,我们使用 IUP 的 IupGetParam,但是它很难调整宽度。 - // 所以还是手动构建对话框。 - - // 为了获取返回值,我们需要设置按钮回调。 - // 但为了避免定义额外的全局函数,我们可以使用 IupPopup 阻塞特性。 - // 我们需要定义两个简单的回调函数。 - // 为了简化,这里定义两个静态辅助函数。 - - // 由于 C 语言闭包限制,我们需要用全局或静态变量传递状态,或者使用 Dialog 的 Attribute。 - IupSetAttribute(dlg, "MY_STATUS", "0"); - - // 注册回调 (使用 IupSetCallback 注册 lambda 类似的逻辑比较难,这里用名字) - // 必须定义全局函数。为了避免污染,我们在文件顶部定义静态函数。 - // 见下文的 static int on_dialog_ok... - - // 由于不能在函数内部定义函数,我们需要调整代码结构。 - // 见下文重构。 - - return 0; // 占位 -} - -// 静态辅助函数:对话框确定 -static int on_dialog_ok(Ihandle *self) -{ - Ihandle *dlg = IupGetDialog(self); - IupSetAttribute(dlg, "MY_STATUS", "1"); - return IUP_CLOSE; -} - -// 静态辅助函数:对话框取消 -static int on_dialog_cancel(Ihandle *self) -{ - Ihandle *dlg = IupGetDialog(self); - IupSetAttribute(dlg, "MY_STATUS", "0"); - return IUP_CLOSE; -} - -// 真正的实现函数 -int custom_input_dialog(const char *title, const char *label_text, char *buffer, int buffer_size) -{ - Ihandle *text = IupText(NULL); - IupSetAttribute(text, "VALUE", buffer); - IupSetAttribute(text, "EXPAND", "HORIZONTAL"); - IupSetAttribute(text, "RASTERSIZE", "500x"); - IupSetAttribute(text, "NAME", "INPUT_TEXT"); - - Ihandle *btn_ok = IupButton("确定", NULL); - IupSetCallback(btn_ok, "ACTION", on_dialog_ok); - IupSetAttribute(btn_ok, "RASTERSIZE", "100x32"); - - Ihandle *btn_cancel = IupButton("取消", NULL); - IupSetCallback(btn_cancel, "ACTION", on_dialog_cancel); - IupSetAttribute(btn_cancel, "RASTERSIZE", "100x32"); - - Ihandle *vbox = IupVbox( - IupLabel(label_text), - text, - IupHbox(IupFill(), btn_ok, btn_cancel, NULL), - NULL); - IupSetAttribute(vbox, "MARGIN", "15x15"); - IupSetAttribute(vbox, "GAP", "10"); - - Ihandle *dlg = IupDialog(vbox); - IupSetAttribute(dlg, "TITLE", title); - IupSetAttribute(dlg, "MINBOX", "NO"); - IupSetAttribute(dlg, "MAXBOX", "NO"); - IupSetAttribute(dlg, "RESIZE", "NO"); - - IupSetAttributeHandle(dlg, "DEFAULTENTER", btn_ok); - IupSetAttributeHandle(dlg, "DEFAULTESC", btn_cancel); - - IupPopup(dlg, IUP_CENTER, IUP_CENTER); - - int result = IupGetInt(dlg, "MY_STATUS"); - if (result == 1) - { - char *val = IupGetAttribute(text, "VALUE"); - if (val) - { - strncpy(buffer, val, buffer_size); - buffer[buffer_size - 1] = '\0'; - } - } - - IupDestroy(dlg); - return result; -} - -// 辅助函数:获取当前选中的列表 -Ihandle *get_current_list() -{ - // 获取当前选中的 Tab 索引 - // 注意:IupTabs 的 VALUE 属性在某些版本可能返回 handle,某些版本返回 pos - // 这里使用 IupGetInt(tabs_main, "VALUEPOS") 更稳妥 - int pos = IupGetInt(tabs_main, "VALUEPOS"); - if (pos == 0) - return list_sys; - if (pos == 1) - return list_user; - return list_sys; // 默认 -} - -// 按钮回调:新建 -int btn_new_cb(Ihandle *self) -{ - char buffer[1024] = ""; - if (custom_input_dialog("新建环境变量", "请输入路径:", buffer, sizeof(buffer))) - { - if (strlen(buffer) > 0) - { - Ihandle *current_list = get_current_list(); - int count = IupGetInt(current_list, "COUNT"); - count++; - IupSetAttributeId(current_list, "", count, buffer); - IupSetInt(current_list, "COUNT", count); - IupSetInt(current_list, "VALUE", count); - - refresh_single_list_style(current_list); - } - } - return IUP_DEFAULT; -} - -// 按钮回调:编辑 -int btn_edit_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int selected = IupGetInt(current_list, "VALUE"); - if (selected == 0) - return IUP_DEFAULT; - - char *current_val = IupGetAttributeId(current_list, "", selected); - char buffer[4096]; // 假设单个路径不超过4096 - if (current_val) - { - strncpy(buffer, current_val, 4096); - buffer[4095] = '\0'; - } - else - { - buffer[0] = '\0'; - } - - if (custom_input_dialog("编辑环境变量", "编辑路径:", buffer, sizeof(buffer))) - { - if (strlen(buffer) > 0) - { - IupSetAttributeId(current_list, "", selected, buffer); - refresh_single_list_style(current_list); - } - } - return IUP_DEFAULT; -} - -// 双击回调 -int list_dblclick_cb(Ihandle *self, int item, char *text) -{ - // 这里的 self 就是触发双击的列表控件 - if (item > 0) - { - // 选中该行 - IupSetInt(self, "VALUE", item); - // 调用编辑逻辑 - btn_edit_cb(NULL); - } - return IUP_DEFAULT; -} - -// 按钮回调:浏览 -int btn_browse_cb(Ihandle *self) -{ - Ihandle *filedlg = IupFileDlg(); - IupSetAttribute(filedlg, "DIALOGTYPE", "DIR"); - IupSetAttribute(filedlg, "TITLE", "选择目录"); - - IupPopup(filedlg, IUP_CENTER, IUP_CENTER); - - if (IupGetInt(filedlg, "STATUS") != -1) - { - char *value = IupGetAttribute(filedlg, "VALUE"); - if (value) - { - Ihandle *current_list = get_current_list(); - int count = IupGetInt(current_list, "COUNT"); - count++; - IupSetAttributeId(current_list, "", count, value); - IupSetInt(current_list, "COUNT", count); - IupSetInt(current_list, "VALUE", count); - - refresh_single_list_style(current_list); - } - } - IupDestroy(filedlg); - return IUP_DEFAULT; -} - -// 辅助函数:从 raw_data 中删除指定字符串 -static void remove_from_raw_data(StringList *list, const char *str) -{ - if (!list || !str) - return; - for (int i = 0; i < list->count; i++) - { - if (strcmp(list->items[i], str) == 0) - { - free(list->items[i]); - // 移动后续元素 - for (int j = i; j < list->count - 1; j++) - { - list->items[j] = list->items[j + 1]; - } - list->count--; - break; // 假设没有重复,只删除第一个匹配 - } - } -} - -// 按钮回调:删除 -int btn_del_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int selected = IupGetInt(current_list, "VALUE"); - - if (selected == 0) - { - IupMessage("提示", "请先选择要删除的项"); - return IUP_DEFAULT; - } - - // 获取当前要删除的内容 - char *val = IupGetAttributeId(current_list, "", selected); - - // 确认删除 - // char msg[1024]; - // snprintf(msg, sizeof(msg), "确定要删除以下路径吗?\n\n%s", val ? val : "(空)"); - // if (IupAlarm("确认删除", msg, "是", "否", NULL) != 1) - // return IUP_DEFAULT; - - // 从 raw_data 缓存中同步删除 - int pos = IupGetInt(tabs_main, "VALUEPOS"); - StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths; - - // 注意:必须先保存 val 的副本,因为 REMOVEITEM 可能会导致 val 指针失效(如果它是指向列表内部缓冲区的) - char *val_copy = val ? _strdup(val) : NULL; - - // 先从界面删除 - // IupSetAttribute(current_list, "REMOVEITEM", "SELECTED"); - // 改为按索引删除,防止失去焦点导致 SELECTED 失效 - IupSetInt(current_list, "REMOVEITEM", selected); - - // 再从缓存删除 - if (val_copy && raw_data) - { - remove_from_raw_data(raw_data, val_copy); - free(val_copy); - } - - // 重新刷新 - refresh_single_list_style(current_list); - - // 更新状态栏,告知用户删除了什么 - IupSetAttribute(lbl_status, "TITLE", "状态: 已删除选中项"); - - return IUP_DEFAULT; -} - -// 按钮回调:上移 -int btn_up_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int selected = IupGetInt(current_list, "VALUE"); - if (selected <= 1) - return IUP_DEFAULT; // 已经是第一个或未选中 - - char *current = IupGetAttributeId(current_list, "", selected); - char *prev = IupGetAttributeId(current_list, "", selected - 1); - - // 交换内容 - char buf_curr[4096], buf_prev[4096]; - strncpy(buf_curr, current, 4096); - buf_curr[4095] = '\0'; - strncpy(buf_prev, prev, 4096); - buf_prev[4095] = '\0'; - - IupSetAttributeId(current_list, "", selected, buf_prev); - IupSetAttributeId(current_list, "", selected - 1, buf_curr); - - IupSetInt(current_list, "VALUE", selected - 1); - - // 刷新样式(虽然颜色不需要变,但为了保险) - refresh_single_list_style(current_list); - return IUP_DEFAULT; -} - -// 按钮回调:下移 -int btn_down_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int selected = IupGetInt(current_list, "VALUE"); - int count = IupGetInt(current_list, "COUNT"); - if (selected == 0 || selected >= count) - return IUP_DEFAULT; - - char *current = IupGetAttributeId(current_list, "", selected); - char *next = IupGetAttributeId(current_list, "", selected + 1); - - char buf_curr[4096], buf_next[4096]; - strncpy(buf_curr, current, 4096); - buf_curr[4095] = '\0'; - strncpy(buf_next, next, 4096); - buf_next[4095] = '\0'; - - IupSetAttributeId(current_list, "", selected, buf_next); - IupSetAttributeId(current_list, "", selected + 1, buf_curr); - - IupSetInt(current_list, "VALUE", selected + 1); - - refresh_single_list_style(current_list); - return IUP_DEFAULT; -} - -// 按钮回调:一键清理 -int btn_clean_cb(Ihandle *self) -{ - Ihandle *current_list = get_current_list(); - int count = IupGetInt(current_list, "COUNT"); - if (count == 0) - return IUP_DEFAULT; - - // 弹出确认对话框 - if (IupAlarm("确认清理", "此操作将移除当前列表中所有【无效路径】和【重复路径】。\n确定要继续吗?", "确定", "取消", NULL) != 1) - { - return IUP_DEFAULT; - } - - // 从后往前遍历删除,避免索引错位 - for (int i = count; i >= 1; i--) - { - char *item = IupGetAttributeId(current_list, "", i); - if (!item) - continue; - - int should_remove = 0; - - // 1. 检查有效性 - if (!is_path_valid(item)) - { - should_remove = 1; - } - else - { - // 2. 检查重复 (检查当前项之前是否出现过) - // 注意:这里需要再次遍历,性能稍低但最稳妥 - for (int j = 1; j < i; j++) - { - char *prev_item = IupGetAttributeId(current_list, "", j); - if (prev_item && _stricmp(item, prev_item) == 0) - { - should_remove = 1; - break; - } - } - } - - if (should_remove) - { - IupSetAttributeId(current_list, "REMOVEITEM", i, NULL); - } - } - - refresh_single_list_style(current_list); - IupMessage("提示", "清理完成!"); - return IUP_DEFAULT; -} - -// 搜索回调 -int txt_search_cb(Ihandle *self) -{ - char *filter = IupGetAttribute(self, "VALUE"); - if (!filter) - return IUP_DEFAULT; - - // 获取当前选中的 Tab 索引 - int pos = IupGetInt(tabs_main, "VALUEPOS"); - Ihandle *current_list = (pos == 0) ? list_sys : list_user; - StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths; - - // 清空列表 - IupSetAttribute(current_list, "REMOVEITEM", "ALL"); - - // 重新填充 - int count = 0; - for (int i = 0; i < raw_data->count; i++) - { - // 如果 filter 为空,或包含 filter (不区分大小写) - if (strlen(filter) == 0 || stristr(raw_data->items[i], filter) != NULL) - { - count++; - IupSetAttributeId(current_list, "", count, raw_data->items[i]); - } - } - - IupSetInt(current_list, "COUNT", count); - refresh_single_list_style(current_list); - - return IUP_DEFAULT; -} - -// 拖拽回调 -int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y) -{ - // 获取当前列表和原始数据 - // 注意:拖拽的目标列表可能是 list_sys 或 list_user,由 self 参数决定 - // 但为了确保数据一致性,我们还是重新获取一下 - Ihandle *current_list = self; - StringList *raw_data = NULL; - if (self == list_sys) - raw_data = &raw_sys_paths; - else if (self == list_user) - raw_data = &raw_user_paths; - else - return IUP_DEFAULT; // 异常情况 - - // 检查拖入的是否为目录 - DWORD attr = GetFileAttributesA(filename); - if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) - { - // 如果正在搜索,先清空搜索框 - IupSetAttribute(txt_search, "VALUE", ""); - - // 添加到列表末尾 - int count = IupGetInt(current_list, "COUNT"); - count++; - IupSetAttributeId(current_list, "", count, filename); - IupSetInt(current_list, "COUNT", count); - IupSetInt(current_list, "VALUE", count); // 选中新添加的项 - - // 同时添加到原始数据缓存,确保搜索时能搜到 - if (raw_data) - { - add_string_list(raw_data, filename); - } - - refresh_single_list_style(current_list); - } - else - { - // 如果拖入的不是文件夹,可以在状态栏提示 - IupSetAttribute(lbl_status, "TITLE", "提示: 只能拖拽文件夹添加到 PATH"); - } - - return IUP_DEFAULT; -} - -// 键盘按键回调 -int list_k_any_cb(Ihandle *self, int c) -{ - // 处理 Delete 键 - if (c == K_DEL) - { - btn_del_cb(NULL); - return IUP_IGNORE; // 阻止默认处理 - } - return IUP_DEFAULT; -} - -// 按钮回调:确定 -int btn_ok_cb(Ihandle *self) -{ - save_all_paths(); - return IUP_DEFAULT; -} - -// 按钮回调:取消 -int btn_cancel_cb(Ihandle *self) -{ - IupExitLoop(); - return IUP_DEFAULT; -} - -// 按钮回调:帮助 -int btn_help_cb(Ihandle *self) -{ - IupMessage("使用说明", - "1. 本程序用于编辑系统环境变量 PATH。\n" - "2. 必须以【管理员身份】运行才能保存更改。\n" - "3. 操作说明:\n" - " - 新建:添加新路径到列表末尾。\n" - " - 编辑:修改选中的路径。\n" - " - 浏览:从文件系统选择目录添加。\n" - " - 删除:移除选中的路径。\n" - " - 上移/下移:调整路径优先级。\n" - "4. 点击【确定】保存更改并生效。\n" - "5. 注意:某些正在运行的程序可能需要重启才能识别新的环境变量。\n\n" - "--------------------------------------------------\n" - "作者:LHY\n" - "邮箱:3364451258@qq.com\n" - "GitHub:https://github.com/LHY0125/PathEditor\n" - "记得给我的项目点个star!"); - return IUP_DEFAULT; -} \ No newline at end of file diff --git a/src/cb_edit.c b/src/cb_edit.c new file mode 100644 index 0000000..f90ac17 --- /dev/null +++ b/src/cb_edit.c @@ -0,0 +1,368 @@ +#include "cb_edit.h" +#include "ui_utils.h" +#include "globals.h" +#include "utils.h" +#include +#include + +// 按钮回调:新建 +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; +} \ No newline at end of file diff --git a/src/cb_file.c b/src/cb_file.c new file mode 100644 index 0000000..edc741c --- /dev/null +++ b/src/cb_file.c @@ -0,0 +1,260 @@ +#include "cb_file.h" +#include "ui_utils.h" +#include "globals.h" +#include "utils.h" +#include +#include +#include +#include // 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; +} diff --git a/src/cb_main.c b/src/cb_main.c new file mode 100644 index 0000000..69d778e --- /dev/null +++ b/src/cb_main.c @@ -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 +#include +#include + +// 搜索回调 +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" + "GitHub:https://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; +} \ No newline at end of file diff --git a/src/main.c b/src/main.c index b72ff57..fb0414e 100644 --- a/src/main.c +++ b/src/main.c @@ -1,137 +1,86 @@ -#include #include #include #include -#include #include "globals.h" #include "utils.h" #include "registry.h" -#include "callbacks.h" #include "ui.h" +#include "cb_main.h" -// 定义 Windows 消息常量 -#ifndef WM_COPYGLOBALDATA -#define WM_COPYGLOBALDATA 0x0049 -#endif +// 全局控件定义 +Ihandle *dlg; // 主对话框 +Ihandle *tabs_main; // 主选项卡 +Ihandle *list_sys, *list_user, *list_merged; // 列表控件 +Ihandle *lbl_status; // 状态栏 +Ihandle *btn_new, *btn_edit, *btn_browse, *btn_del, *btn_up, *btn_down; // 右侧按钮 +Ihandle *btn_undo, *btn_redo; // 撤销重做按钮 +Ihandle *btn_import, *btn_export; // 导入导出按钮 +Ihandle *btn_ok, *btn_cancel, *btn_help; // 确认取消帮助按钮 +Ihandle *btn_clean; // 一键清理按钮 +Ihandle *btn_theme; // 主题切换按钮 +Ihandle *txt_search; // 搜索框 -#ifndef MSGFLT_ADD -#define MSGFLT_ADD 1 -#endif +// 历史记录栈 +HistoryStack undo_stack = {0}; +HistoryStack redo_stack = {0}; // 全局变量定义 StringList raw_sys_paths = {0}; StringList raw_user_paths = {0}; - -// 全局控件定义 -Ihandle *dlg, *tabs_main, *list_sys, *list_user, *lbl_status; // 主窗口、标签页、系统路径列表、用户路径列表、状态标签 -Ihandle *btn_new, *btn_edit, *btn_browse, *btn_del, *btn_up, *btn_down; // 右侧按钮 -Ihandle *btn_ok, *btn_cancel, *btn_help; // 确认取消帮助按钮 -Ihandle *btn_clean; // 一键清理按钮 -Ihandle *txt_search; // 搜索框 +int is_dark_mode = 0; // 默认浅色模式 // 主函数 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"); - // 在管理员模式下,解决无法拖拽文件到列表框的问题 (UIPI) - // 需要加载 User32.dll 获取 ChangeWindowMessageFilter 函数 - HMODULE hUser32 = LoadLibraryW(L"user32.dll"); + // 启用 UIPI 绕过,允许拖拽 + HMODULE hUser32 = LoadLibraryA("user32.dll"); if (hUser32) { typedef BOOL(WINAPI * ChangeWindowMessageFilterProc)(UINT, DWORD); - ChangeWindowMessageFilterProc pChangeWindowMessageFilter = - (ChangeWindowMessageFilterProc)GetProcAddress(hUser32, "ChangeWindowMessageFilter"); - + ChangeWindowMessageFilterProc pChangeWindowMessageFilter = (ChangeWindowMessageFilterProc)GetProcAddress(hUser32, "ChangeWindowMessageFilter"); if (pChangeWindowMessageFilter) { - pChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD); - pChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD); - pChangeWindowMessageFilter(WM_COPYGLOBALDATA, MSGFLT_ADD); + // WM_DROPFILES = 0x0233, WM_COPYDATA = 0x004A, MSGFLT_ADD = 1 + pChangeWindowMessageFilter(0x0233, 1); + pChangeWindowMessageFilter(0x004A, 1); } FreeLibrary(hUser32); } - // 创建两个列表控件 - list_sys = create_path_list(); - list_user = create_path_list(); + // 初始化历史栈 + init_history_stack(&undo_stack); + init_history_stack(&redo_stack); - // 创建搜索框 - txt_search = IupText(NULL); - IupSetAttribute(txt_search, "EXPAND", "HORIZONTAL"); - IupSetAttribute(txt_search, "CUEBANNER", "输入关键词搜索..."); - IupSetCallback(txt_search, "VALUECHANGED_CB", (Icallback)txt_search_cb); + // 创建主界面 + dlg = create_main_dialog(); - // 创建 Tabs - tabs_main = IupTabs( - IupVbox(list_sys, NULL), - IupVbox(list_user, NULL), - NULL); - IupSetAttribute(tabs_main, "TABTITLE0", "系统变量 (System)"); - IupSetAttribute(tabs_main, "TABTITLE1", "用户变量 (User)"); - IupSetAttribute(tabs_main, "TABTYPE", "TOP"); - - // 创建右侧按钮区域 - Ihandle *vbox_btns = create_main_buttons(); - - // 上部布局:Tabs + 按钮 - Ihandle *hbox_main = IupHbox(tabs_main, vbox_btns, NULL); - IupSetAttribute(hbox_main, "GAP", "10"); - IupSetAttribute(hbox_main, "MARGIN", "10x10"); - - // 状态标签 - lbl_status = IupLabel("状态: 就绪"); - IupSetAttribute(lbl_status, "EXPAND", "HORIZONTAL"); - - // 创建底部按钮区域 - Ihandle *hbox_bottom = create_bottom_buttons(); - - // 总体布局 - Ihandle *vbox_all = IupVbox( - IupLabel("环境变量编辑器:"), - txt_search, - hbox_main, - hbox_bottom, - NULL); - IupSetAttribute(vbox_all, "MARGIN", "10x10"); - IupSetAttribute(vbox_all, "GAP", "5"); - - // 创建对话框 - dlg = IupDialog(vbox_all); - IupSetAttribute(dlg, "TITLE", "编辑环境变量 (IUP版)"); - IupSetAttribute(dlg, "SIZE", "500x400"); // 稍微调大一点 - 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", "状态: 只读模式 (权限不足)"); - - // 禁用修改类按钮 - IupSetAttribute(btn_new, "ACTIVE", "NO"); - IupSetAttribute(btn_edit, "ACTIVE", "NO"); - IupSetAttribute(btn_browse, "ACTIVE", "NO"); - IupSetAttribute(btn_del, "ACTIVE", "NO"); - IupSetAttribute(btn_up, "ACTIVE", "NO"); - IupSetAttribute(btn_down, "ACTIVE", "NO"); - IupSetAttribute(btn_clean, "ACTIVE", "NO"); - IupSetAttribute(btn_ok, "ACTIVE", "NO"); + IupMessage("警告", "未检测到管理员权限!\n您可能无法保存更改。\n请右键以【管理员身份运行】。"); } - IupShowXY(dlg, IUP_CENTER, IUP_CENTER); - - // IUP List APPEND 属性需要在控件 Map 之后才能生效 - // IupShowXY 会触发 Map load_all_paths(); + // 显示对话框 + IupShowXY(dlg, IUP_CENTER, IUP_CENTER); + + // 进入主循环 IupMainLoop(); + + // 清理资源 IupClose(); return 0; } \ No newline at end of file diff --git a/src/registry.c b/src/registry.c index 51cd288..b172aec 100644 --- a/src/registry.c +++ b/src/registry.c @@ -84,7 +84,7 @@ void load_all_paths() load_single_path(HKEY_CURRENT_USER, REG_PATH_USER, list_user, &raw_user_paths); refresh_list_style(); - IupSetAttribute(lbl_status, "TITLE", "状态: 已加载系统和用户变量"); + IupSetAttribute(lbl_status, "TITLE", "状态: 已加载变量"); } // 内部辅助函数:保存单个列表 diff --git a/src/ui.c b/src/ui.c index ddfbe94..c1feb65 100644 --- a/src/ui.c +++ b/src/ui.c @@ -1,6 +1,9 @@ #include "ui.h" #include "globals.h" -#include "callbacks.h" +#include "ui_utils.h" +#include "cb_edit.h" +#include "cb_file.h" +#include "cb_main.h" #include // 创建列表控件 @@ -8,6 +11,7 @@ Ihandle *create_path_list() { Ihandle *list = IupFlatList(); IupSetAttribute(list, "EXPAND", "YES"); + IupSetAttribute(list, "MULTIPLE", "YES"); IupSetAttribute(list, "ITEMPADDING", "5x5"); IupSetAttribute(list, "BACKCOLOR", "255 255 255"); IupSetAttribute(list, "BORDER", "YES"); @@ -16,6 +20,7 @@ Ihandle *create_path_list() 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; } @@ -27,6 +32,8 @@ Ihandle *create_main_buttons() 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); @@ -36,6 +43,8 @@ Ihandle *create_main_buttons() 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); @@ -45,6 +54,8 @@ Ihandle *create_main_buttons() IupSetAttribute(btn_edit, "RASTERSIZE", "100x32"); IupSetAttribute(btn_browse, "RASTERSIZE", "100x32"); IupSetAttribute(btn_del, "RASTERSIZE", "100x32"); + IupSetAttribute(btn_undo, "RASTERSIZE", "100x32"); + IupSetAttribute(btn_redo, "RASTERSIZE", "100x32"); IupSetAttribute(btn_up, "RASTERSIZE", "100x32"); IupSetAttribute(btn_down, "RASTERSIZE", "100x32"); IupSetAttribute(btn_clean, "RASTERSIZE", "100x32"); @@ -52,36 +63,109 @@ Ihandle *create_main_buttons() 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", "5"); IupSetAttribute(vbox_btns, "MARGIN", "0x0"); - + return vbox_btns; } // 创建底部按钮区域 Ihandle *create_bottom_buttons() { - // 底部按钮 - btn_ok = IupButton("确定", NULL); - btn_cancel = IupButton("取消", NULL); - btn_help = IupButton("帮助(?)", NULL); - - IupSetCallback(btn_ok, "ACTION", (Icallback)btn_ok_cb); - IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_cb); + // 创建底部按钮 + btn_help = IupButton("帮助(H)", NULL); IupSetCallback(btn_help, "ACTION", (Icallback)btn_help_cb); + IupSetAttribute(btn_help, "RASTERSIZE", "80x32"); + btn_theme = IupButton("深色模式", NULL); + IupSetCallback(btn_theme, "ACTION", (Icallback)btn_theme_cb); + IupSetAttribute(btn_theme, "RASTERSIZE", "80x32"); + + lbl_status = IupLabel("就绪"); + IupSetAttribute(lbl_status, "EXPAND", "HORIZONTAL"); + + btn_import = IupButton("导入配置", NULL); + IupSetCallback(btn_import, "ACTION", (Icallback)btn_import_cb); + IupSetAttribute(btn_import, "RASTERSIZE", "100x32"); + + btn_export = IupButton("导出配置", NULL); + IupSetCallback(btn_export, "ACTION", (Icallback)btn_export_cb); + IupSetAttribute(btn_export, "RASTERSIZE", "100x32"); + + btn_ok = IupButton("确定(O)", NULL); + IupSetCallback(btn_ok, "ACTION", (Icallback)btn_ok_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); + btn_cancel = IupButton("取消(C)", NULL); + IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_cb); + IupSetAttribute(btn_cancel, "RASTERSIZE", "100x32"); + + Ihandle *hbox_bottom = IupHbox( + btn_help, + btn_theme, + lbl_status, + IupFill(), + btn_import, + btn_export, + btn_ok, + btn_cancel, + NULL); IupSetAttribute(hbox_bottom, "GAP", "10"); - IupSetAttribute(hbox_bottom, "MARGIN", "10x10"); 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", "240 240 240"); // 灰色背景 + + // 创建标签页 + 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", "10"); + IupSetAttribute(hbox_mid, "MARGIN", "0x0"); + + Ihandle *bottom = create_bottom_buttons(); + + Ihandle *vbox_main = IupVbox(txt_search, hbox_mid, bottom, NULL); + IupSetAttribute(vbox_main, "GAP", "10"); + IupSetAttribute(vbox_main, "MARGIN", "10x10"); + + Ihandle *dlg = IupDialog(vbox_main); + IupSetAttribute(dlg, "TITLE", "Path Editor"); + IupSetAttribute(dlg, "SIZE", "480x400"); + + return dlg; } \ No newline at end of file diff --git a/src/ui_utils.c b/src/ui_utils.c new file mode 100644 index 0000000..c76425c --- /dev/null +++ b/src/ui_utils.c @@ -0,0 +1,225 @@ +#include "ui_utils.h" +#include "globals.h" +#include "utils.h" +#include +#include +#include + +// 获取第一个选中的索引(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); +} diff --git a/src/utils.c b/src/utils.c index cd8dc28..ffd78f0 100644 --- a/src/utils.c +++ b/src/utils.c @@ -42,14 +42,41 @@ int check_admin() return 0; } +// 展开环境变量 +char* expand_env_vars(const char* 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) { - // 如果包含 %,说明是变量,无法直接检查存在性,默认视为有效 - if (strchr(path, '%')) - return 1; + char *expanded_path = expand_env_vars(path); + if (!expanded_path) + return 0; - wchar_t *wpath = utf8_to_wide(path); + wchar_t *wpath = utf8_to_wide(expanded_path); + free(expanded_path); + if (!wpath) return 0; @@ -84,7 +111,11 @@ void refresh_single_list_style(Ihandle *list) continue; // 默认颜色:黑字 - char fg_color[32] = "0 0 0"; + 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)) @@ -108,13 +139,19 @@ void refresh_single_list_style(Ihandle *list) IupSetAttributeId(list, "ITEMFGCOLOR", i, fg_color); // 斑马纹背景 - if (i % 2 == 0) + if (is_dark_mode) { - IupSetAttributeId(list, "ITEMBGCOLOR", i, "245 245 245"); + if (i % 2 == 0) + IupSetAttributeId(list, "ITEMBGCOLOR", i, "60 60 60"); + else + IupSetAttributeId(list, "ITEMBGCOLOR", i, "50 50 50"); } else { - IupSetAttributeId(list, "ITEMBGCOLOR", i, "255 255 255"); + if (i % 2 == 0) + IupSetAttributeId(list, "ITEMBGCOLOR", i, "245 245 245"); + else + IupSetAttributeId(list, "ITEMBGCOLOR", i, "255 255 255"); } } } @@ -230,4 +267,73 @@ void clear_string_list(StringList *list) 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; } \ No newline at end of file