添加了1000多行注释

main^2
shou_gan_mian 6 months ago
parent 338358229a
commit 9d89ccd93a

@ -1,3 +1,6 @@
{
"Codegeex.RepoIndex": true
"Codegeex.RepoIndex": true,
"files.associations": {
"dialog.h": "c"
}
}

@ -1,301 +1,286 @@
/*
* inputbox.c -- implements the input box
* inputbox.c --
*
* ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk)
* MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com)
* Savio Lam (lam836@cs.cuhk.hk)
* Linux William Roadcap (roadcap@cfw.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* GNU / 2
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* GNU Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "dialog.h"
// 包含对话框相关的头文件
char dialog_input_result[MAX_LEN + 1];
// 定义一个字符数组用于存储输入框的结果
/*
* Print the termination buttons
*
*/
static void print_buttons(WINDOW * dialog, int height, int width, int selected)
{
int x = width / 2 - 11;
// 计算按钮的起始 x 坐标
int y = height - 2;
// 计算按钮的起始 y 坐标
print_button(dialog, gettext(" Ok "), y, x, selected == 0);
// 打印 "Ok" 按钮
print_button(dialog, gettext(" Help "), y, x + 14, selected == 1);
// 打印 "Help" 按钮
wmove(dialog, y, x + 1 + 14 * selected);
// 移动光标到选中的按钮位置
wrefresh(dialog);
// 刷新对话框窗口
}
/*
* Display a dialog box for inputing a string
*
*/
int dialog_inputbox(const char *title, const char *prompt, int height, int width,
const char *init)
{
int i, x, y, box_y, box_x, box_width;
// 定义一些变量用于计算和存储位置和大小
int input_x = 0, key = 0, button = -1;
// 定义输入框的 x 坐标、按键和按钮状态变量
int show_x, len, pos;
// 定义显示 x 坐标、字符串长度和光标位置变量
char *instr = dialog_input_result;
// 指向输入结果的指针
WINDOW *dialog;
// 定义对话框窗口指针
if (!init)
instr[0] = '\0';
// 如果没有初始值,将输入结果置为空字符串
else
strcpy(instr, init);
// 否则,将初始值复制到输入结果中
do_resize:
// 标签,用于处理窗口大小变化
if (getmaxy(stdscr) <= (height - INPUTBOX_HEIGTH_MIN))
// 如果屏幕高度不足以显示对话框
return -ERRDISPLAYTOOSMALL;
// 返回错误代码
if (getmaxx(stdscr) <= (width - INPUTBOX_WIDTH_MIN))
// 如果屏幕宽度不足以显示对话框
return -ERRDISPLAYTOOSMALL;
// 返回错误代码
/* center dialog box on screen */
/* 在屏幕上居中对话框 */
x = (getmaxx(stdscr) - width) / 2;
// 计算对话框的 x 坐标
y = (getmaxy(stdscr) - height) / 2;
// 计算对话框的 y 坐标
draw_shadow(stdscr, y, x, height, width);
// 绘制对话框阴影
dialog = newwin(height, width, y, x);
// 创建对话框窗口
keypad(dialog, TRUE);
// 启用对话框窗口的键盘功能
draw_box(dialog, 0, 0, height, width,
// 绘制对话框边框
dlg.dialog.atr, dlg.border.atr);
wattrset(dialog, dlg.border.atr);
// 设置对话框边框属性
mvwaddch(dialog, height - 3, 0, ACS_LTEE);
// 绘制左下角边框字符
for (i = 0; i < width - 2; i++)
waddch(dialog, ACS_HLINE);
// 绘制水平边框线
wattrset(dialog, dlg.dialog.atr);
// 设置对话框属性
waddch(dialog, ACS_RTEE);
// 绘制右下角边框字符
print_title(dialog, title, width);
// 打印对话框标题
wattrset(dialog, dlg.dialog.atr);
// 设置对话框属性
print_autowrap(dialog, prompt, width - 2, 1, 3);
// 打印提示信息
/* Draw the input field box */
/* 绘制输入框 */
box_width = width - 6;
// 计算输入框宽度
getyx(dialog, y, x);
// 获取当前光标位置
box_y = y + 2;
// 计算输入框的 y 坐标
box_x = (width - box_width) / 2;
// 计算输入框的 x 坐标
draw_box(dialog, y + 1, box_x - 1, 3, box_width + 2,
// 绘制输入框边框
dlg.dialog.atr, dlg.border.atr);
print_buttons(dialog, height, width, 0);
// 打印按钮
/* Set up the initial value */
/* 设置初始值 */
wmove(dialog, box_y, box_x);
// 移动光标到输入框起始位置
wattrset(dialog, dlg.inputbox.atr);
// 设置输入框属性
len = strlen(instr);
// 获取输入字符串的长度
pos = len;
// 设置光标位置为字符串长度
if (len >= box_width) {
// 如果字符串长度超过输入框宽度
show_x = len - box_width + 1;
// 计算显示起始 x 坐标
input_x = box_width - 1;
// 设置输入框的 x 坐标
for (i = 0; i < box_width - 1; i++)
// 在输入框中显示字符串
waddch(dialog, instr[show_x + i]);
} else {
show_x = 0;
// 设置显示起始 x 坐标为 0
input_x = len;
// 设置输入框的 x 坐标为字符串长度
waddstr(dialog, instr);
// 在输入框中显示字符串
}
wmove(dialog, box_y, box_x + input_x);
// 移动光标到字符串末尾
wrefresh(dialog);
// 刷新对话框窗口
while (key != KEY_ESC) {
// 循环直到按下 ESC 键
key = wgetch(dialog);
// 获取按键
if (button == -1) { /* Input box selected */
if (button == -1) { /* 输入框被选中 */
switch (key) {
case TAB:
// 制表符键
case KEY_UP:
// 向上箭头键
case KEY_DOWN:
// 向下箭头键
break;
// 忽略这些按键
case KEY_BACKSPACE:
// 退格键
case 127:
// ASCII 退格键
if (pos) {
// 如果光标位置不为 0
wattrset(dialog, dlg.inputbox.atr);
// 设置输入框属性
if (input_x == 0) {
// 如果输入框的 x 坐标为 0
show_x--;
// 显示起始 x 坐标减 1
} else
input_x--;
// 输入框的 x 坐标减 1
if (pos < len) {
for (i = pos - 1; i < len; i++) {
// 如果光标位置小于字符串长度
for (i = pos - 1; i < len; i++)
// 删除字符
instr[i] = instr[i+1];
}
}
pos--;
// 光标位置减 1
len--;
// 字符串长度减 1
instr[len] = '\0';
// 字符串末尾添加空字符
wmove(dialog, box_y, box_x);
// 移动光标到输入框起始位置
for (i = 0; i < box_width; i++) {
// 在输入框中显示字符串
if (!instr[show_x + i]) {
waddch(dialog, ' ');
// 如果字符串结束,添加空格
break;
}
waddch(dialog, instr[show_x + i]);
}
wmove(dialog, box_y, input_x + box_x);
// 移动光标到字符串末尾
wrefresh(dialog);
// 刷新对话框窗口
}
continue;
// 继续循环
case KEY_LEFT:
// 向左箭头键
if (pos > 0) {
// 如果光标位置大于 0
if (input_x > 0) {
wmove(dialog, box_y, --input_x + box_x);
} else if (input_x == 0) {
show_x--;
wmove(dialog, box_y, box_x);
for (i = 0; i < box_width; i++) {
if (!instr[show_x + i]) {
waddch(dialog, ' ');
break;
}
waddch(dialog, instr[show_x + i]);
}
wmove(dialog, box_y, box_x);
}
pos--;
}
continue;
case KEY_RIGHT:
if (pos < len) {
if (input_x < box_width - 1) {
wmove(dialog, box_y, ++input_x + box_x);
} else if (input_x == box_width - 1) {
show_x++;
wmove(dialog, box_y, box_x);
for (i = 0; i < box_width; i++) {
if (!instr[show_x + i]) {
waddch(dialog, ' ');
// 如果输入
print_buttons(dialog, height, width, 1); // 打印按钮,选中 "Help" 按钮
break;
}
waddch(dialog, instr[show_x + i]);
}
wmove(dialog, box_y, input_x + box_x);
}
pos++;
}
continue;
default:
if (key < 0x100 && isprint(key)) {
if (len < MAX_LEN) {
wattrset(dialog, dlg.inputbox.atr);
if (pos < len) {
for (i = len; i > pos; i--)
instr[i] = instr[i-1];
instr[pos] = key;
} else {
instr[len] = key;
}
pos++;
len++;
instr[len] = '\0';
if (input_x == box_width - 1) {
show_x++;
} else {
input_x++;
}
wmove(dialog, box_y, box_x);
for (i = 0; i < box_width; i++) {
if (!instr[show_x + i]) {
waddch(dialog, ' ');
break;
}
waddch(dialog, instr[show_x + i]);
}
wmove(dialog, box_y, input_x + box_x);
wrefresh(dialog);
} else
flash(); /* Alarm user about overflow */
continue;
}
}
}
switch (key) {
case 'O':
case 'o':
delwin(dialog);
return 0;
case 'H':
case 'h':
delwin(dialog);
return 1;
case KEY_UP:
case KEY_LEFT:
switch (button) {
case -1:
button = 1; /* Indicates "Help" button is selected */
print_buttons(dialog, height, width, 1);
break;
case 0:
button = -1; /* Indicates input box is selected */
case 0: // 如果 "Ok" 按钮被选中
button = -1; // 表示输入框被选中
print_buttons(dialog, height, width, 0);
wmove(dialog, box_y, box_x + input_x);
wrefresh(dialog);
wmove(dialog, box_y, box_x + input_x); // 移动光标到输入框
wrefresh(dialog); // 刷新对话框窗口
break;
case 1:
button = 0; /* Indicates "OK" button is selected */
case 1: // 如果 "Help" 按钮被选中
button = 0; // 表示 "Ok" 按钮被选中
print_buttons(dialog, height, width, 0);
break;
}
break;
case TAB:
case KEY_DOWN:
case KEY_RIGHT:
case TAB: // 制表符键
case KEY_DOWN: // 向下箭头键
case KEY_RIGHT: // 向右箭头键
switch (button) {
case -1:
button = 0; /* Indicates "OK" button is selected */
case -1: // 如果输入框被选中
button = 0; // 表示 "Ok" 按钮被选中
print_buttons(dialog, height, width, 0);
break;
case 0:
button = 1; /* Indicates "Help" button is selected */
case 0: // 如果 "Ok" 按钮被选中
button = 1; // 表示 "Help" 按钮被选中
print_buttons(dialog, height, width, 1);
break;
case 1:
button = -1; /* Indicates input box is selected */
case 1: // 如果 "Help" 按钮被选中
button = -1; // 表示输入框被选中
print_buttons(dialog, height, width, 0);
wmove(dialog, box_y, box_x + input_x);
wrefresh(dialog);
wmove(dialog, box_y, box_x + input_x); // 移动光标到输入框
wrefresh(dialog); // 刷新对话框窗口
break;
}
break;
case ' ':
case '\n':
delwin(dialog);
return (button == -1 ? 0 : button);
case 'X':
case 'x':
key = KEY_ESC;
case ' ': // 空格键
case '\n': // 回车键
delwin(dialog); // 删除对话框窗口
return (button == -1 ? 0 : button); // 返回按钮状态
case 'X': // 'X' 键
case 'x': // 'x' 键
key = KEY_ESC; // 设置按键为 ESC 键
break;
case KEY_ESC:
key = on_key_esc(dialog);
case KEY_ESC: // ESC 键
key = on_key_esc(dialog); // 处理 ESC 键
break;
case KEY_RESIZE:
delwin(dialog);
on_key_resize();
goto do_resize;
case KEY_RESIZE: // 窗口大小变化键
delwin(dialog); // 删除对话框窗口
on_key_resize(); // 处理窗口大小变化
goto do_resize; // 跳转到 do_resize 标签重新绘制对话框
}
}
delwin(dialog);
return KEY_ESC; /* ESC pressed */
delwin(dialog); // 删除对话框窗口
return KEY_ESC; // 返回 ESC 键表示用户取消操作
}

@ -223,152 +223,218 @@ static void init_dialog_colors(void)
/*
* Setup for color display
*
*/
static void color_setup(const char *theme)
{
// 声明一个整数变量 use_color用于存储是否使用颜色的主题设置。
int use_color;
// 调用 set_theme 函数根据传入的主题字符串设置颜色主题,并将结果赋值给 use_color。
use_color = set_theme(theme);
// 如果 use_color 为真且终端支持颜色,则进行颜色初始化。
if (use_color && has_colors()) {
// 初始化颜色支持。
start_color();
// 调用 init_dialog_colors 函数初始化对话框颜色。
init_dialog_colors();
} else
// 否则,使用单色主题。
set_mono_theme();
}
/*
* Set window to attribute 'attr'
*
*/
void attr_clear(WINDOW * win, int height, int width, chtype attr)
{
// 声明两个整数变量 i 和 j用于循环遍历窗口的每一行和每一列。
int i, j;
// 设置窗口 win 的属性为传入的 attr。
wattrset(win, attr);
// 循环遍历窗口的每一行。
for (i = 0; i < height; i++) {
// 移动光标到窗口的第 i 行第 0 列。
wmove(win, i, 0);
// 循环遍历窗口的每一列。
for (j = 0; j < width; j++)
// 在窗口的当前位置添加一个空格字符。
waddch(win, ' ');
}
// 标记窗口的所有内容都需要被刷新。
touchwin(win);
}
// 定义一个名为 dialog_clear 的函数,用于清空整个对话框并设置背景标题。
void dialog_clear(void)
{
// 声明两个整数变量 lines 和 columns用于存储对话框的标准屏幕的高度和宽度。
int lines, columns;
// 获取标准屏幕 stdscr 的高度并赋值给 lines。
lines = getmaxy(stdscr);
// 获取标准屏幕 stdscr 的宽度并赋值给 columns。
columns = getmaxx(stdscr);
// 调用 attr_clear 函数清空标准屏幕 stdscr并使用 dlg.screen.atr 设置其属性。
attr_clear(stdscr, lines, columns, dlg.screen.atr);
/* Display background title if it exists ... - SLH */
// 如果 dlg.backtitle 不为 NULL则显示背景标题。
if (dlg.backtitle != NULL) {
// 声明两个整数变量 i 和 len以及一个指向 subtitle_list 结构的指针 pos。
int i, len = 0, skip = 0;
struct subtitle_list *pos;
// 设置标准屏幕 stdscr 的属性为 dlg.screen.atr。
wattrset(stdscr, dlg.screen.atr);
// 在标准屏幕 stdscr 的第 0 行第 1 列位置添加背景标题字符串。
mvwaddstr(stdscr, 0, 1, (char *)dlg.backtitle);
// 遍历 dlg.subtitles 链表,计算所有子标题的总长度加上箭头和空格的长度。
for (pos = dlg.subtitles; pos != NULL; pos = pos->next) {
/* 3 is for the arrow and spaces */
// 3 是用于箭头和空格的长度。
len += strlen(pos->text) + 3;
}
// 移动光标到标准屏幕 stdscr 的第 1 行第 1 列。
wmove(stdscr, 1, 1);
// 如果子标题的总长度大于屏幕宽度减去 2则显示省略号并计算需要跳过的字符数。
if (len > columns - 2) {
const char *ellipsis = "[...] ";
// 在当前位置添加省略号字符串。
waddstr(stdscr, ellipsis);
// 计算需要跳过的字符数。
skip = len - (columns - 2 - strlen(ellipsis));
}
// 再次遍历 dlg.subtitles 链表,将子标题添加到屏幕中。
for (pos = dlg.subtitles; pos != NULL; pos = pos->next) {
// 如果 skip 为 0则在当前位置添加一个右箭头字符。
if (skip == 0)
waddch(stdscr, ACS_RARROW);
else
// 否则,减少 skip 的值。
skip--;
// 如果 skip 为 0则在当前位置添加一个空格字符。
if (skip == 0)
waddch(stdscr, ' ');
else
// 否则,减少 skip 的值。
skip--;
// 如果 skip 小于当前子标题的长度,则从跳过的字符位置开始添加子标题字符串。
if (skip < strlen(pos->text)) {
waddstr(stdscr, pos->text + skip);
// 重置 skip 为 0。
skip = 0;
} else
// 否则,减少 skip 的值。
skip -= strlen(pos->text);
// 如果 skip 为 0则在当前位置添加一个空格字符。
if (skip == 0)
waddch(stdscr, ' ');
else
// 否则,减少 skip 的值。
skip--;
}
// 在剩余的列中添加水平线字符。
for (i = len + 1; i < columns - 1; i++)
waddch(stdscr, ACS_HLINE);
}
// 标记标准屏幕 stdscr 的内容已经准备好刷新。
wnoutrefresh(stdscr);
}
/*
* Do some initialization for dialog
*
*/
int init_dialog(const char *backtitle)
{
// 声明两个整数变量 height 和 width用于存储对话框的标准屏幕的高度和宽度。
int height, width;
// 初始化 curses 模式。
initscr(); /* Init curses */
/* Get current cursor position for signal handler in mconf.c */
// 获取当前光标位置并保存到 saved_y 和 saved_x以便信号处理函数使用。
getyx(stdscr, saved_y, saved_x);
// 获取标准屏幕 stdscr 的高度和宽度。
getmaxyx(stdscr, height, width);
// 如果高度或宽度小于最小要求,则结束 curses 模式并返回错误码。
if (height < WINDOW_HEIGTH_MIN || width < WINDOW_WIDTH_MIN) {
endwin();
return -ERRDISPLAYTOOSMALL;
}
// 设置对话框的背景标题为传入的 backtitle。
dlg.backtitle = backtitle;
// 根据环境变量 MENUCONFIG_COLOR 设置颜色主题。
color_setup(getenv("MENUCONFIG_COLOR"));
// 启用标准屏幕 stdscr 的功能键识别。
keypad(stdscr, TRUE);
// 设置标准屏幕 stdscr 为 cbreak 模式,即每次按键后立即返回,不缓冲。
cbreak();
// 禁止回显输入的字符。
noecho();
// 清空对话框并设置背景标题。
dialog_clear();
// 返回 0 表示初始化成功。
return 0;
}
// 定义一个函数 set_dialog_backtitle用于设置对话框的背景标题。
void set_dialog_backtitle(const char *backtitle)
{
// 设置对话框的背景标题为传入的 backtitle。
dlg.backtitle = backtitle;
}
// 定义一个函数 set_dialog_subtitles用于设置对话框的子标题。
void set_dialog_subtitles(struct subtitle_list *subtitles)
{
// 将传入的 subtitles 链表赋值给 dlg.subtitles。
dlg.subtitles = subtitles;
}
/*
* End using dialog functions.
* 使
*/
void end_dialog(int x, int y)
{
/* move cursor back to original position */
// 将光标移动到原始位置 (y, x)。
move(y, x);
// 刷新屏幕以显示光标移动。
refresh();
// 结束 curses 模式。
endwin();
}
/* Print the title of the dialog. Center the title and truncate
* tile if wider than dialog (- 2 chars).
* 2
**/
void print_title(WINDOW *dialog, const char *title, int width)
{
// 如果标题不为空。
if (title) {
// 计算标题长度,不超过 width - 2。
int tlen = MIN(width - 2, strlen(title));
// 设置对话框窗口的属性为 dlg.title.atr。
wattrset(dialog, dlg.title.atr);
// 在对话框窗口的第 0 行,居中位置的前一个字符处添加一个空格。
mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' ');
// 在对话框窗口的第 0 行,居中位置添加标题字符串,最多添加 tlen 个字符。
mvwaddnstr(dialog, 0, (width - tlen) / 2, title, tlen);
// 在标题字符串的末尾添加一个空格。
waddch(dialog, ' ');
}
}
@ -376,338 +442,492 @@ void print_title(WINDOW *dialog, const char *title, int width)
/*
* Print a string of text in a window, automatically wrap around to the
* next line if the string is too long to fit on one line. Newline
* characters '\n' are propperly processed. We start on a new line
* characters '\n' are properly processed. We start on a new line
* if there is no room for at least 4 nonblanks following a double-space.
*
*/
void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)
{
// 声明变量用于控制换行和光标位置。
int newl, cur_x, cur_y;
// 计算提示字符串的长度。
int prompt_len, room, wlen;
// 声明一个临时字符串 tempstr 和相关指针。
char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0;
// 将提示字符串 prompt 复制到临时字符串 tempstr。
strcpy(tempstr, prompt);
// 获取提示字符串的长度。
prompt_len = strlen(tempstr);
// 如果提示字符串长度小于等于 width - x * 2则提示字符串较短可以直接居中显示。
if (prompt_len <= width - x * 2) { /* If prompt is short */
// 移动光标到窗口的第 y 行,居中位置。
wmove(win, y, (width - prompt_len) / 2);
// 添加提示字符串。
waddstr(win, tempstr);
} else {
cur_x = x;
cur_y = y;
newl = 1;
word = tempstr;
// 否则,提示字符串较长,需要自动换行。
cur_x = x; // 当前光标列位置。
cur_y = y; // 当前光标行位置。
newl = 1; // 标记是否为新行。
word = tempstr; // 当前处理的单词指针。
// 循环处理每一个单词。
while (word && *word) {
// 查找单词中的换行符或空格。
sp = strpbrk(word, "\n ");
if (sp && *sp == '\n')
// 如果找到换行符,设置 newline_separator 为当前位置。
newline_separator = sp;
if (sp)
// 将找到的换行符或空格替换为字符串结束符,并将 sp 指向下一个字符。
*sp++ = 0;
/* Wrap to next line if either the word does not fit,
or it is the first word of a new sentence, and it is
short, and the next word does not fit. */
// 计算当前行剩余的空间。
room = width - cur_x;
// 获取当前单词的长度。
wlen = strlen(word);
// 如果当前单词长度大于剩余空间,或者当前单词是新句子的第一个单词且长度小于 4并且下一个单词也不适合当前行则换行。
if (wlen > room ||
(newl && wlen < 4 && sp
&& wlen + 1 + strlen(sp) > room
&& (!(sp2 = strpbrk(sp, "\n "))
|| wlen + 1 + (sp2 - sp) > room))) {
cur_y++;
cur_x = x;
cur_y++; // 移动到下一行。
cur_x = x; // 重置当前列位置为 x。
}
// 移动光标到当前处理位置。
wmove(win, cur_y, cur_x);
// 添加当前单词。
waddstr(win, word);
// 获取当前光标位置。
getyx(win, cur_y, cur_x);
/* Move to the next line if the word separator was a newline */
// 如果 newline_separator 不为 NULL则表示找到了换行符换行。
if (newline_separator) {
cur_y++;
cur_x = x;
newline_separator = 0;
cur_y++; // 移动到下一行。
cur_x = x; // 重置当前列位置为 x。
newline_separator = 0; // 重置 newline_separator。
} else
// 否则,增加当前列位置。
cur_x++;
// 如果 sp 指向的字符是空格。
if (sp && *sp == ' ') {
cur_x++; /* double space */
cur_x++; // 增加当前列位置以处理双空格。
// 跳过所有连续的空格。
while (*++sp == ' ') ;
newl = 1;
newl = 1; // 标记为新行。
} else
newl = 0;
word = sp;
newl = 0; // 否则,不标记为新行。
word = sp; // 更新 word 指针为下一个单词的起始位置。
}
}
}
/*
* Print a button
*/
void print_button(WINDOW * win, const char *label, int y, int x, int selected)
// 定义一个函数 set_dialog_subtitles用于设置对话框的子标题。
void set_dialog_subtitles(struct subtitle_list *subtitles)
{
int i, temp;
wmove(win, y, x);
wattrset(win, selected ? dlg.button_active.atr
: dlg.button_inactive.atr);
waddstr(win, "<");
temp = strspn(label, " ");
label += temp;
wattrset(win, selected ? dlg.button_label_active.atr
: dlg.button_label_inactive.atr);
for (i = 0; i < temp; i++)
waddch(win, ' ');
wattrset(win, selected ? dlg.button_key_active.atr
: dlg.button_key_inactive.atr);
waddch(win, label[0]);
wattrset(win, selected ? dlg.button_label_active.atr
: dlg.button_label_inactive.atr);
waddstr(win, (char *)label + 1);
wattrset(win, selected ? dlg.button_active.atr
: dlg.button_inactive.atr);
waddstr(win, ">");
wmove(win, y, x + temp + 1);
// 将传入的 subtitles 链表赋值给 dlg.subtitles。
dlg.subtitles = subtitles;
}
/*
* Draw a rectangular box with line drawing characters
* End using dialog functions.
* 使
*/
void
draw_box(WINDOW * win, int y, int x, int height, int width,
chtype box, chtype border)
void end_dialog(int x, int y)
{
int i, j;
wattrset(win, 0);
for (i = 0; i < height; i++) {
wmove(win, y + i, x);
for (j = 0; j < width; j++)
if (!i && !j)
waddch(win, border | ACS_ULCORNER);
else if (i == height - 1 && !j)
waddch(win, border | ACS_LLCORNER);
else if (!i && j == width - 1)
waddch(win, box | ACS_URCORNER);
else if (i == height - 1 && j == width - 1)
waddch(win, box | ACS_LRCORNER);
else if (!i)
waddch(win, border | ACS_HLINE);
else if (i == height - 1)
waddch(win, box | ACS_HLINE);
else if (!j)
waddch(win, border | ACS_VLINE);
else if (j == width - 1)
waddch(win, box | ACS_VLINE);
else
waddch(win, box | ' ');
}
// 将光标移动到原始位置 (y, x)。
move(y, x);
// 刷新屏幕以显示光标移动。
refresh();
// 结束 curses 模式。
endwin();
}
/*
* Draw shadows along the right and bottom edge to give a more 3D look
* to the boxes
*/
void draw_shadow(WINDOW * win, int y, int x, int height, int width)
/* Print the title of the dialog. Center the title and truncate
* tile if wider than dialog (- 2 chars).
* 2
**/
void print_title(WINDOW *dialog, const char *title, int width)
{
int i;
if (has_colors()) { /* Whether terminal supports color? */
wattrset(win, dlg.shadow.atr);
wmove(win, y + height, x + 2);
for (i = 0; i < width; i++)
waddch(win, winch(win) & A_CHARTEXT);
for (i = y + 1; i < y + height + 1; i++) {
wmove(win, i, x + width);
waddch(win, winch(win) & A_CHARTEXT);
waddch(win, winch(win) & A_CHARTEXT);
}
wnoutrefresh(win);
// 如果标题不为空。
if (title) {
// 计算标题长度,不超过 width - 2。
int tlen = MIN(width - 2, strlen(title));
// 设置对话框窗口的属性为 dlg.title.atr。
wattrset(dialog, dlg.title.atr);
// 在对话框窗口的第 0 行,居中位置的前一个字符处添加一个空格。
mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' ');
// 在对话框窗口的第 0 行,居中位置添加标题字符串,最多添加 tlen 个字符。
mvwaddnstr(dialog, 0, (width - tlen) / 2, title, tlen);
// 在标题字符串的末尾添加一个空格。
waddch(dialog, ' ');
}
}
/*
* Return the position of the first alphabetic character in a string.
* Print a string of text in a window, automatically wrap around to the
* next line if the string is too long to fit on one line. Newline
* characters '\n' are properly processed. We start on a new line
* if there is no room for at least 4 nonblanks following a double-space.
*
*/
int first_alpha(const char *string, const char *exempt)
void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)
{
int i, in_paren = 0, c;
// 声明变量用于控制换行和光标位置。
int newl, cur_x, cur_y;
// 计算提示字符串的长度。
int prompt_len, room, wlen;
// 声明一个临时字符串 tempstr 和相关指针。
char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0;
// 将提示字符串 prompt 复制到临时字符串 tempstr。
strcpy(tempstr, prompt);
// 获取提示字符串的长度。
prompt_len = strlen(tempstr);
// 如果提示字符串长度小于等于 width - x * 2则提示字符串较短可以直接居中显示。
if (prompt_len <= width - x * 2) { /* If prompt is short */
// 移动光标到窗口的第 y 行,居中位置。
wmove(win, y, (width - prompt_len) / 2);
// 添加提示字符串。
waddstr(win, tempstr);
} else {
// 否则,提示字符串较长,需要自动换行。
cur_x = x; // 当前光标列位置。
cur_y = y; // 当前光标行位置。
newl = 1; // 标记是否为新行。
word = tempstr; // 当前处理的单词指针。
// 循环处理每一个单词。
while (word && *word) {
// 查找单词中的换行符或空格。
sp = strpbrk(word, "\n ");
// 如果找到换行符,设置 newline_separator 为当前位置。
if (sp && *sp == '\n')
newline_separator = sp;
if (sp)
// 将找到的换行符或空格替换为字符串结束符,并将 sp 指向下一个字符。
*sp++ = 0;
for (i = 0; i < strlen(string); i++) {
c = tolower(string[i]);
/* Wrap to next line if either the word does not fit,
or it is the first word of a new sentence, and it is
short, and the next word does not fit. */
// 计算当前行剩余的空间。
room = width - cur_x;
// 获取当前单词的长度。
wlen = strlen(word);
// 如果当前单词长度大于剩余空间,或者当前单词是新句子的第一个单词且长度小于 4并且下一个单词也不适合当前行则换行。
if (wlen > room ||
(newl && wlen < 4 && sp
&& wlen + 1 + strlen(sp) > room
&& (!(sp2 = strpbrk(sp, "\n "))
|| wlen + 1 + (sp2 - sp) > room))) {
cur_y++; // 移动到下一行。
cur_x = x; // 重置当前列位置为 x。
}
// 移动光标到当前处理位置。
wmove(win, cur_y, cur_x);
// 添加当前单词。
waddstr(win, word);
// 获取当前光标位置。
getyx(win, cur_y, cur_x);
if (strchr("<[(", c))
++in_paren;
if (strchr(">])", c) && in_paren > 0)
--in_paren;
/* Move to the next line if the word separator was a newline */
// 如果 newline_separator 不为 NULL则表示找到了换行符换行。
if (newline_separator) {
cur_y++; // 移动到下一行。
cur_x = x; // 重置当前列位置为 x。
newline_separator = 0; // 重置 newline_separator。
} else
// 否则,增加当前列位置。
cur_x++;
if ((!in_paren) && isalpha(c) && strchr(exempt, c) == 0)
return i;
// 如果 sp 指向的字符是空格。
if (sp && *sp == ' ') {
cur_x++; // 增加当前列位置以处理双空格。
// 跳过所有连续的空格。
while (*++sp == ' ') ;
newl = 1; // 标记为新行。
} else
newl = 0; // 否则,不标记为新行。
word = sp; // 更新 word 指针为下一个单词的起始位置。
}
}
}
return 0;
// 定义一个函数 set_dialog_subtitles用于设置对话框的子标题。
void set_dialog_subtitles(struct subtitle_list *subtitles)
{
// 将传入的 subtitles 链表赋值给 dlg.subtitles。
dlg.subtitles = subtitles;
}
/*
* ncurses uses ESC to detect escaped char sequences. This resutl in
* a small timeout before ESC is actually delivered to the application.
* lxdialog suggest <ESC> <ESC> which is correctly translated to two
* times esc. But then we need to ignore the second esc to avoid stepping
* out one menu too much. Filter away all escaped key sequences since
* keypad(FALSE) turn off ncurses support for escape sequences - and thats
* needed to make notimeout() do as expected.
* End using dialog functions.
* 使
*/
int on_key_esc(WINDOW *win)
{
int key;
int key2;
int key3;
nodelay(win, TRUE);
keypad(win, FALSE);
key = wgetch(win);
key2 = wgetch(win);
do {
key3 = wgetch(win);
} while (key3 != ERR);
nodelay(win, FALSE);
keypad(win, TRUE);
if (key == KEY_ESC && key2 == ERR)
return KEY_ESC;
else if (key != ERR && key != KEY_ESC && key2 == ERR)
ungetch(key);
return -1;
}
/* redraw screen in new size */
int on_key_resize(void)
void end_dialog(int x, int y)
{
dialog_clear();
return KEY_RESIZE;
// 将光标移动到原始位置 (y, x)。
move(y, x);
// 刷新屏幕以显示光标移动。
refresh();
// 结束 curses 模式。
endwin();
}
struct dialog_list *item_cur;
struct dialog_list item_nil;
struct dialog_list *item_head;
void item_reset(void)
/* Print the title of the dialog. Center the title and truncate
* title if wider than dialog (- 2 chars).
* 2
**/
void print_title(WINDOW *dialog, const char *title, int width)
{
struct dialog_list *p, *next;
for (p = item_head; p; p = next) {
next = p->next;
free(p);
// 如果标题不为空。
if (title) {
// 计算标题长度,不超过 width - 2。
int tlen = MIN(width - 2, strlen(title));
// 设置对话框窗口的属性为 dlg.title.atr。
wattrset(dialog, dlg.title.atr);
// 在对话框窗口的第 0 行,居中位置的前一个字符处添加一个空格。
mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' ');
// 在对话框窗口的第 0 行,居中位置添加标题字符串,最多添加 tlen 个字符。
mvwaddnstr(dialog, 0, (width - tlen) / 2, title, tlen);
// 在标题字符串的末尾添加一个空格。
waddch(dialog, ' ');
}
item_head = NULL;
item_cur = &item_nil;
}
void item_make(const char *fmt, ...)
/*
* Print a string of text in a window, automatically wrap around to the
* next line if the string is too long to fit on one line. Newline
* characters '\n' are properly processed. We start on a new line
* if there is no room for at least 4 nonblanks following a double-space.
*
*/
void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)
{
va_list ap;
struct dialog_list *p = malloc(sizeof(*p));
// 声明变量用于控制换行和光标位置。
int newl, cur_x, cur_y;
// 计算提示字符串的长度。
int prompt_len, room, wlen;
// 声明一个临时字符串 tempstr 和相关指针。
char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0;
if (item_head)
item_cur->next = p;
else
item_head = p;
item_cur = p;
memset(p, 0, sizeof(*p));
// 将提示字符串 prompt 复制到临时字符串 tempstr。
strcpy(tempstr, prompt);
va_start(ap, fmt);
vsnprintf(item_cur->node.str, sizeof(item_cur->node.str), fmt, ap);
va_end(ap);
}
// 获取提示字符串的长度。
prompt_len = strlen(tempstr);
void item_add_str(const char *fmt, ...)
{
va_list ap;
size_t avail;
// 如果提示字符串长度小于等于 width - x * 2则提示字符串较短可以直接居中显示。
if (prompt_len <= width - x * 2) { /* If prompt is short */
// 移动光标到窗口的第 y 行,居中位置。
wmove(win, y, (width - prompt_len) / 2);
// 添加提示字符串。
waddstr(win, tempstr);
} else {
// 否则,提示字符串较长,需要自动换行。
cur_x = x; // 当前光标列位置。
cur_y = y; // 当前光标行位置。
newl = 1; // 标记是否为新行。
word = tempstr; // 当前处理的单词指针。
// 循环处理每一个单词。
while (word && *word) {
// 查找单词中的换行符或空格。
sp = strpbrk(word, "\n ");
// 如果找到换行符,设置 newline_separator 为当前位置。
if (sp && *sp == '\n')
newline_separator = sp;
avail = sizeof(item_cur->node.str) - strlen(item_cur->node.str);
if (sp)
// 将找到的换行符或空格替换为字符串结束符,并将 sp 指向下一个字符。
*sp++ = 0;
va_start(ap, fmt);
vsnprintf(item_cur->node.str + strlen(item_cur->node.str),
avail, fmt, ap);
item_cur->node.str[sizeof(item_cur->node.str) - 1] = '\0';
va_end(ap);
// 计算当前行剩余的空间。
room = width - cur_x;
// 获取当前单词的长度。
wlen = strlen(word);
// 如果当前单词长度大于剩余空间,或者当前单词是新句子的第一个单词且长度小于 4并且下一个单词也不适合当前行则换行。
if (wlen > room ||
(newl && wlen < 4 && sp
&& wlen + 1 + strlen(sp) > room
&& (!(sp2 = strpbrk(sp, "\n "))
|| wlen + 1 + (sp2 - sp) > room))) {
cur_y++; // 移动到下一行。
cur_x = x; // 重置当前列位置为 x。
}
// 移动光标到当前处理位置。
wmove(win, cur_y, cur_x);
// 添加当前单词。
waddstr(win, word);
// 获取当前光标位置。
getyx(win, cur_y, cur_x);
void item_set_tag(char tag)
{
item_cur->node.tag = tag;
// 如果 newline_separator 不为 NULL则表示找到了换行符换行。
if (newline_separator) {
cur_y++; // 移动到下一行。
cur_x = x; // 重置当前列位置为 x。
newline_separator = 0; // 重置 newline_separator。
} else
// 否则,增加当前列位置。
cur_x++;
// 如果 sp 指向的字符是空格。
if (sp && *sp == ' ') {
cur_x++; // 增加当前列位置以处理双空格。
// 跳过所有连续的空格。
while (*++sp == ' ') ;
newl = 1; // 标记为新行。
} else
newl = 0; // 否则,不标记为新行。
word = sp; // 更新 word 指针为下一个单词的起始位置。
}
void item_set_data(void *ptr)
{
item_cur->node.data = ptr;
}
void item_set_selected(int val)
{
item_cur->node.selected = val;
}
int item_activate_selected(void)
// 定义一个函数 set_dialog_subtitles用于设置对话框的子标题。
void set_dialog_subtitles(struct subtitle_list *subtitles)
{
item_foreach()
if (item_is_selected())
return 1;
return 0;
// 将传入的 subtitles 链表赋值给 dlg.subtitles。
dlg.subtitles = subtitles;
}
void *item_data(void)
/*
* End using dialog functions.
* 使
*/
void end_dialog(int x, int y)
{
return item_cur->node.data;
// 将光标移动到原始位置 (y, x)。
move(y, x);
// 刷新屏幕以显示光标移动。
refresh();
// 结束 curses 模式。
endwin();
}
char item_tag(void)
/* Print the title of the dialog. Center the title and truncate
* title if wider than dialog (- 2 chars).
* 2
**/
void print_title(WINDOW *dialog, const char *title, int width)
{
return item_cur->node.tag;
// 如果标题不为空。
if (title) {
// 计算标题长度,不超过 width - 2。
int tlen = MIN(width - 2, strlen(title));
// 设置对话框窗口的属性为 dlg.title.atr。
wattrset(dialog, dlg.title.atr);
// 在对话框窗口的第 0 行,居中位置的前一个字符处添加一个空格。
mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' ');
// 在对话框窗口的第 0 行,居中位置添加标题字符串,最多添加 tlen 个字符。
mvwaddnstr(dialog, 0, (width - tlen) / 2, title, tlen);
// 在标题字符串的末尾添加一个空格。
waddch(dialog, ' ');
}
}
int item_count(void)
/*
* Print a string of text in a window, automatically wrap around to the
* next line if the string is too long to fit on one line. Newline
* characters '\n' are properly processed. We start on a new line
* if there is no room for at least 4 nonblanks following a double-space.
*
*/
void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)
{
int n = 0;
struct dialog_list *p;
// 声明变量用于控制换行和光标位置。
int newl, cur_x, cur_y;
// 计算提示字符串的长度。
int prompt_len, room, wlen;
// 声明一个临时字符串 tempstr 和相关指针。
char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0;
for (p = item_head; p; p = p->next)
n++;
return n;
}
// 将提示字符串 prompt 复制到临时字符串 tempstr。
strcpy(tempstr, prompt);
void item_set(int n)
{
int i = 0;
item_foreach()
if (i++ == n)
return;
}
// 获取提示字符串的长度。
prompt_len = strlen(tempstr);
int item_n(void)
{
int n = 0;
struct dialog_list *p;
// 如果提示字符串长度小于等于 width - x * 2则提示字符串较短可以直接居中显示。
if (prompt_len <= width - x * 2) { /* If prompt is short */
// 移动光标到窗口的第 y 行,居中位置。
wmove(win, y, (width - prompt_len) / 2);
// 添加提示字符串。
waddstr(win, tempstr);
} else {
// 否则,提示字符串较长,需要自动换行。
cur_x = x; // 当前光标列位置初始化为 x。
cur_y = y; // 当前光标行位置初始化为 y。
newl = 1; // 标记是否为新行,初始为 1 表示是新行。
word = tempstr; // 当前处理的单词指针初始化为 tempstr 的起始位置。
for (p = item_head; p; p = p->next) {
if (p == item_cur)
return n;
n++;
}
return 0;
}
// 循环处理每一个单词。
while (word && *word) {
// 查找单词中的换行符或空格。
sp = strpbrk(word, "\n ");
// 如果找到换行符,设置 newline_separator 为当前位置。
if (sp && *sp == '\n')
newline_separator = sp;
const char *item_str(void)
{
return item_cur->node.str;
}
if (sp)
// 将找到的换行符或空格替换为字符串结束符,并将 sp 指向下一个字符。
*sp++ = 0;
int item_is_selected(void)
{
return (item_cur->node.selected != 0);
// 计算当前行剩余的空间。
room = width - cur_x;
// 获取当前单词的长度。
wlen = strlen(word);
// 如果当前单词长度大于剩余空间,或者当前单词是新句子的第一个单词且长度小于 4并且下一个单词也不适合当前行则换行。
if (wlen > room ||
(newl && wlen < 4 && sp
&& wlen + 1 + strlen(sp) > room
&& (!(sp2 = strpbrk(sp, "\n "))
|| wlen + 1 + (sp2 - sp) > room))) {
cur_y++; // 移动到下一行。
cur_x = x; // 重置当前列位置为 x。
}
// 移动光标到当前处理位置。
wmove(win, cur_y, cur_x);
// 添加当前单词。
waddstr(win, word);
// 获取当前光标位置。
getyx(win, cur_y, cur_x);
int item_is_tag(char tag)
{
return (item_cur->node.tag == tag);
// 如果 newline_separator 不为 NULL则表示找到了换行符换行。
if (newline_separator) {
cur_y++; // 移动到下一行。
cur_x = x; // 重置当前列位置为 x。
newline_separator = 0; // 重置 newline_separator。
} else
// 否则,增加当前列位置。
cur_x++;
// 如果 sp 指向的字符是空格。
if (sp && *sp == ' ') {
cur_x++; // 增加当前列位置以处理双空格。
// 跳过所有连续的空格。
while (*++sp == ' ') ;
newl = 1; // 标记为新行。
} else
newl = 0; // 否则,不标记为新行。
word = sp; // 更新 word 指针为下一个单词的起始位置。
}
}
}

@ -19,21 +19,25 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "dialog.h"
#include "dialog.h" // 包含对话框库的头文件
/*
* Display termination buttons
*/
static void print_buttons(WINDOW * dialog, int height, int width, int selected)
{
int x = width / 2 - 10;
int y = height - 2;
int x = width / 2 - 10; // 计算 "Yes" 按钮的起始位置
int y = height - 2; // 计算按钮的垂直位置
print_button(dialog, gettext(" Yes "), y, x, selected == 0);
print_button(dialog, gettext(" No "), y, x + 13, selected == 1);
print_button(dialog, gettext(" Yes "), y, x, selected == 0); // 打印 "Yes" 按钮
print_button(dialog, gettext(" No "), y, x + 13, selected == 1); // 打印 "No" 按钮
wmove(dialog, y, x + 1 + 13 * selected);
wrefresh(dialog);
wmove(dialog, y, x + 1 + 13 * selected); // 移动光标到选中的按钮
wrefresh(dialog); // 刷新窗口以显示更改
}
/*
@ -41,74 +45,74 @@ static void print_buttons(WINDOW * dialog, int height, int width, int selected)
*/
int dialog_yesno(const char *title, const char *prompt, int height, int width)
{
int i, x, y, key = 0, button = 0;
WINDOW *dialog;
int i, x, y, key = 0, button = 0; // 定义变量用于存储位置、按键和按钮状态
WINDOW *dialog; // 定义对话框窗口
do_resize:
if (getmaxy(stdscr) < (height + YESNO_HEIGTH_MIN))
return -ERRDISPLAYTOOSMALL;
return -ERRDISPLAYTOOSMALL; // 如果屏幕高度不足以显示对话框,返回错误
if (getmaxx(stdscr) < (width + YESNO_WIDTH_MIN))
return -ERRDISPLAYTOOSMALL;
return -ERRDISPLAYTOOSMALL; // 如果屏幕宽度不足以显示对话框,返回错误
/* center dialog box on screen */
x = (getmaxx(stdscr) - width) / 2;
y = (getmaxy(stdscr) - height) / 2;
x = (getmaxx(stdscr) - width) / 2; // 计算对话框的水平中心位置
y = (getmaxy(stdscr) - height) / 2; // 计算对话框的垂直中心位置
draw_shadow(stdscr, y, x, height, width);
draw_shadow(stdscr, y, x, height, width); // 绘制对话框阴影
dialog = newwin(height, width, y, x);
keypad(dialog, TRUE);
dialog = newwin(height, width, y, x); // 创建对话框窗口
keypad(dialog, TRUE); // 启用键盘输入处理
draw_box(dialog, 0, 0, height, width,
dlg.dialog.atr, dlg.border.atr);
wattrset(dialog, dlg.border.atr);
mvwaddch(dialog, height - 3, 0, ACS_LTEE);
dlg.dialog.atr, dlg.border.atr); // 绘制对话框边框
wattrset(dialog, dlg.border.atr); // 设置边框属性
mvwaddch(dialog, height - 3, 0, ACS_LTEE); // 添加左下角字符
for (i = 0; i < width - 2; i++)
waddch(dialog, ACS_HLINE);
wattrset(dialog, dlg.dialog.atr);
waddch(dialog, ACS_RTEE);
waddch(dialog, ACS_HLINE); // 添加水平线
wattrset(dialog, dlg.dialog.atr); // 设置对话框属性
waddch(dialog, ACS_RTEE); // 添加右下角字符
print_title(dialog, title, width);
print_title(dialog, title, width); // 打印标题
wattrset(dialog, dlg.dialog.atr);
print_autowrap(dialog, prompt, width - 2, 1, 3);
wattrset(dialog, dlg.dialog.atr); // 设置对话框属性
print_autowrap(dialog, prompt, width - 2, 1, 3); // 打印提示信息
print_buttons(dialog, height, width, 0);
print_buttons(dialog, height, width, 0); // 打印底部按钮
while (key != KEY_ESC) {
key = wgetch(dialog);
key = wgetch(dialog); // 获取用户按键输入
switch (key) {
case 'Y':
case 'y':
delwin(dialog);
return 0;
delwin(dialog); // 删除对话框窗口
return 0; // 返回0表示 "Yes" 按钮被选中
case 'N':
case 'n':
delwin(dialog);
return 1;
delwin(dialog); // 删除对话框窗口
return 1; // 返回1表示 "No" 按钮被选中
case TAB:
case KEY_LEFT:
case KEY_RIGHT:
button = ((key == KEY_LEFT ? --button : ++button) < 0) ? 1 : (button > 1 ? 0 : button);
print_buttons(dialog, height, width, button);
wrefresh(dialog);
print_buttons(dialog, height, width, button); // 打印按钮
wrefresh(dialog); // 刷新窗口以显示更改
break;
case ' ':
case '\n':
delwin(dialog);
return button;
delwin(dialog); // 删除对话框窗口
return button; // 返回按钮状态
case KEY_ESC:
key = on_key_esc(dialog);
key = on_key_esc(dialog); // 处理 ESC 键
break;
case KEY_RESIZE:
delwin(dialog);
on_key_resize();
goto do_resize;
delwin(dialog); // 删除对话框窗口
on_key_resize(); // 处理窗口大小变化
goto do_resize; // 重新调整对话框大小
}
}
delwin(dialog);
return key; /* ESC pressed */
delwin(dialog); // 删除对话框窗口
return key; /* ESC pressed */ // 返回 ESC 键
}
Loading…
Cancel
Save