From c5c5517ded0eb1e79142ae99617969880962ca28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Fri, 1 May 2026 23:49:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(multi-select):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=A4=9A=E9=80=89=E5=8F=8A=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 列表控件启用 MULTIPLE=YES 属性 - btn_del_cb 支持批量删除:解析分号分隔的多选索引,从大到小删除 - 批量删除推送一条合并的 undo record,支持一步撤销 Co-Authored-By: Claude Opus 4.7 --- src/controller/callbacks_basic.c | 66 +++++++++++++++++++++++++------- src/ui/main_window.c | 1 + 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/controller/callbacks_basic.c b/src/controller/callbacks_basic.c index 9bc40cd..45caa35 100644 --- a/src/controller/callbacks_basic.c +++ b/src/controller/callbacks_basic.c @@ -180,39 +180,77 @@ int btn_browse_cb(Ihandle *self) return IUP_DEFAULT; } -// 按钮回调:删除 +// 按钮回调:删除(支持多选批量删除) int btn_del_cb(Ihandle *self) { Ihandle *dlg = IupGetDialog(self); Ihandle *current_list = get_current_list(dlg); - int selected = IupGetInt(current_list, "VALUE"); - if (selected == 0) + // 解析多选索引(VALUE 为 "1;3;5" 格式) + char *value_str = IupGetAttribute(current_list, "VALUE"); + if (!value_str || value_str[0] == '\0') { IupMessage(_("Info"), _("Please select an item to delete first")); return IUP_DEFAULT; } StringList *raw_data = get_current_raw_data(dlg); - int del_index = selected - 1; + if (!raw_data || raw_data->count == 0) + return IUP_DEFAULT; - // 记录撤销信息(被删除的路径) - char *del_path = _strdup(string_list_get(raw_data, del_index)); - char *paths[1] = {del_path}; - push_record(dlg, OP_DELETE, del_index, 1, paths, NULL); - free(del_path); - - ErrorCode result = path_manager_remove_at(raw_data, del_index); - if (result != ERR_OK) + // 解析选中索引并排序(从大到小,方便从尾部删除避免索引偏移) + int indices[256]; + int sel_count = 0; + char *token = strtok(value_str, ";"); + while (token && sel_count < 256) { - log_error("Failed to remove path at index %d", del_index); + int idx = atoi(token) - 1; // 转为 0-based + if (idx >= 0 && idx < raw_data->count) + indices[sel_count++] = idx; + token = strtok(NULL, ";"); } + if (sel_count == 0) + return IUP_DEFAULT; + + // 从大到小排序 + for (int i = 0; i < sel_count - 1; i++) + { + for (int j = i + 1; j < sel_count; j++) + { + if (indices[i] < indices[j]) + { + int tmp = indices[i]; + indices[i] = indices[j]; + indices[j] = tmp; + } + } + } + + // 记录撤销信息(所有被删除的路径) + char **old_paths = (char **)malloc(sel_count * sizeof(char *)); + for (int i = 0; i < sel_count; i++) + old_paths[i] = _strdup(string_list_get(raw_data, indices[i])); + + push_record(dlg, OP_DELETE, indices[sel_count - 1], sel_count, old_paths, NULL); + + for (int i = 0; i < sel_count; i++) + free(old_paths[i]); + free(old_paths); + + // 从大到小删除(避免索引偏移) + for (int i = 0; i < sel_count; i++) + path_manager_remove_at(raw_data, indices[i]); + sync_string_list_to_ui(current_list, raw_data); Ihandle *lbl_status = IupGetDialogChild(dlg, CTRL_LBL_STATUS); if (lbl_status) - IupSetAttribute(lbl_status, "TITLE", lua_config_get_string("status", "deleted")); + { + char msg[64]; + snprintf(msg, sizeof(msg), _("Deleted %d items"), sel_count); + IupSetAttribute(lbl_status, "TITLE", msg); + } refresh_undo_redo_buttons(dlg); return IUP_DEFAULT; diff --git a/src/ui/main_window.c b/src/ui/main_window.c index b7a2654..10a5e39 100644 --- a/src/ui/main_window.c +++ b/src/ui/main_window.c @@ -17,6 +17,7 @@ static Ihandle *create_path_list(const char *name) IupSetAttribute(list, "BORDER", "YES"); IupSetAttribute(list, "CANFOCUS", "YES"); IupSetAttribute(list, "HLINE", "NO"); + IupSetAttribute(list, "MULTIPLE", "YES"); 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);