1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * textbox.c -- implements the text box |
4 | * |
5 | * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) |
6 | * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) |
7 | */ |
8 | |
9 | #include "dialog.h" |
10 | |
11 | static int hscroll; |
12 | static int begin_reached, end_reached, page_length; |
13 | static const char *buf, *page; |
14 | static size_t start, end; |
15 | |
16 | /* |
17 | * Go back 'n' lines in text. Called by dialog_textbox(). |
18 | * 'page' will be updated to point to the desired line in 'buf'. |
19 | */ |
20 | static void back_lines(int n) |
21 | { |
22 | int i; |
23 | |
24 | begin_reached = 0; |
25 | /* Go back 'n' lines */ |
26 | for (i = 0; i < n; i++) { |
27 | if (*page == '\0') { |
28 | if (end_reached) { |
29 | end_reached = 0; |
30 | continue; |
31 | } |
32 | } |
33 | if (page == buf) { |
34 | begin_reached = 1; |
35 | return; |
36 | } |
37 | page--; |
38 | do { |
39 | if (page == buf) { |
40 | begin_reached = 1; |
41 | return; |
42 | } |
43 | page--; |
44 | } while (*page != '\n'); |
45 | page++; |
46 | } |
47 | } |
48 | |
49 | /* |
50 | * Return current line of text. Called by dialog_textbox() and print_line(). |
51 | * 'page' should point to start of current line before calling, and will be |
52 | * updated to point to start of next line. |
53 | */ |
54 | static char *get_line(void) |
55 | { |
56 | int i = 0; |
57 | static char line[MAX_LEN + 1]; |
58 | |
59 | end_reached = 0; |
60 | while (*page != '\n') { |
61 | if (*page == '\0') { |
62 | end_reached = 1; |
63 | break; |
64 | } else if (i < MAX_LEN) |
65 | line[i++] = *(page++); |
66 | else { |
67 | /* Truncate lines longer than MAX_LEN characters */ |
68 | if (i == MAX_LEN) |
69 | line[i++] = '\0'; |
70 | page++; |
71 | } |
72 | } |
73 | if (i <= MAX_LEN) |
74 | line[i] = '\0'; |
75 | if (!end_reached) |
76 | page++; /* move past '\n' */ |
77 | |
78 | return line; |
79 | } |
80 | |
81 | /* |
82 | * Print a new line of text. |
83 | */ |
84 | static void print_line(WINDOW *win, int row, int width) |
85 | { |
86 | char *line; |
87 | |
88 | line = get_line(); |
89 | line += MIN(strlen(line), hscroll); /* Scroll horizontally */ |
90 | wmove(win, row, 0); /* move cursor to correct line */ |
91 | waddch(win, ' '); |
92 | waddnstr(win, line, MIN(strlen(line), width - 2)); |
93 | |
94 | /* Clear 'residue' of previous line */ |
95 | wclrtoeol(win); |
96 | } |
97 | |
98 | /* |
99 | * Print a new page of text. |
100 | */ |
101 | static void print_page(WINDOW *win, int height, int width) |
102 | { |
103 | int i, passed_end = 0; |
104 | |
105 | page_length = 0; |
106 | for (i = 0; i < height; i++) { |
107 | print_line(win, row: i, width); |
108 | if (!passed_end) |
109 | page_length++; |
110 | if (end_reached && !passed_end) |
111 | passed_end = 1; |
112 | } |
113 | wnoutrefresh(win); |
114 | } |
115 | |
116 | /* |
117 | * Print current position |
118 | */ |
119 | static void print_position(WINDOW *win) |
120 | { |
121 | int percent; |
122 | |
123 | wattrset(win, dlg.position_indicator.atr); |
124 | wbkgdset(win, dlg.position_indicator.atr & A_COLOR); |
125 | percent = (page - buf) * 100 / strlen(s: buf); |
126 | wmove(win, getmaxy(win) - 3, getmaxx(win) - 9); |
127 | wprintw(win, "(%3d%%)" , percent); |
128 | } |
129 | |
130 | /* |
131 | * refresh window content |
132 | */ |
133 | static void refresh_text_box(WINDOW *dialog, WINDOW *box, int boxh, int boxw, |
134 | int cur_y, int cur_x) |
135 | { |
136 | start = page - buf; |
137 | |
138 | print_page(win: box, height: boxh, width: boxw); |
139 | print_position(win: dialog); |
140 | wmove(dialog, cur_y, cur_x); /* Restore cursor position */ |
141 | wrefresh(dialog); |
142 | |
143 | end = page - buf; |
144 | } |
145 | |
146 | /* |
147 | * Display text from a file in a dialog box. |
148 | * |
149 | * keys is a null-terminated array |
150 | */ |
151 | int dialog_textbox(const char *title, const char *tbuf, int initial_height, |
152 | int initial_width, int *_vscroll, int *_hscroll, |
153 | int (*)(int, size_t, size_t, void *), void *data) |
154 | { |
155 | int i, x, y, cur_x, cur_y, key = 0; |
156 | int height, width, boxh, boxw; |
157 | WINDOW *dialog, *box; |
158 | bool done = false; |
159 | |
160 | begin_reached = 1; |
161 | end_reached = 0; |
162 | page_length = 0; |
163 | hscroll = 0; |
164 | buf = tbuf; |
165 | page = buf; /* page is pointer to start of page to be displayed */ |
166 | |
167 | if (_vscroll && *_vscroll) { |
168 | begin_reached = 0; |
169 | |
170 | for (i = 0; i < *_vscroll; i++) |
171 | get_line(); |
172 | } |
173 | if (_hscroll) |
174 | hscroll = *_hscroll; |
175 | |
176 | do_resize: |
177 | getmaxyx(stdscr, height, width); |
178 | if (height < TEXTBOX_HEIGHT_MIN || width < TEXTBOX_WIDTH_MIN) |
179 | return -ERRDISPLAYTOOSMALL; |
180 | if (initial_height != 0) |
181 | height = initial_height; |
182 | else |
183 | if (height > 4) |
184 | height -= 4; |
185 | else |
186 | height = 0; |
187 | if (initial_width != 0) |
188 | width = initial_width; |
189 | else |
190 | if (width > 5) |
191 | width -= 5; |
192 | else |
193 | width = 0; |
194 | |
195 | /* center dialog box on screen */ |
196 | x = (getmaxx(stdscr) - width) / 2; |
197 | y = (getmaxy(stdscr) - height) / 2; |
198 | |
199 | draw_shadow(win: stdscr, y, x, height, width); |
200 | |
201 | dialog = newwin(height, width, y, x); |
202 | keypad(dialog, TRUE); |
203 | |
204 | /* Create window for box region, used for scrolling text */ |
205 | boxh = height - 4; |
206 | boxw = width - 2; |
207 | box = subwin(dialog, boxh, boxw, y + 1, x + 1); |
208 | wattrset(box, dlg.dialog.atr); |
209 | wbkgdset(box, dlg.dialog.atr & A_COLOR); |
210 | |
211 | keypad(box, TRUE); |
212 | |
213 | /* register the new window, along with its borders */ |
214 | draw_box(win: dialog, y: 0, x: 0, height, width, |
215 | box: dlg.dialog.atr, border: dlg.border.atr); |
216 | |
217 | wattrset(dialog, dlg.border.atr); |
218 | mvwaddch(dialog, height - 3, 0, ACS_LTEE); |
219 | for (i = 0; i < width - 2; i++) |
220 | waddch(dialog, ACS_HLINE); |
221 | wattrset(dialog, dlg.dialog.atr); |
222 | wbkgdset(dialog, dlg.dialog.atr & A_COLOR); |
223 | waddch(dialog, ACS_RTEE); |
224 | |
225 | print_title(dialog, title, width); |
226 | |
227 | print_button(win: dialog, label: " Exit " , y: height - 2, x: width / 2 - 4, TRUE); |
228 | wnoutrefresh(dialog); |
229 | getyx(dialog, cur_y, cur_x); /* Save cursor position */ |
230 | |
231 | /* Print first page of text */ |
232 | attr_clear(win: box, height: boxh, width: boxw, attr: dlg.dialog.atr); |
233 | refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); |
234 | |
235 | while (!done) { |
236 | key = wgetch(dialog); |
237 | switch (key) { |
238 | case 'E': /* Exit */ |
239 | case 'e': |
240 | case 'X': |
241 | case 'x': |
242 | case 'q': |
243 | case '\n': |
244 | done = true; |
245 | break; |
246 | case 'g': /* First page */ |
247 | case KEY_HOME: |
248 | if (!begin_reached) { |
249 | begin_reached = 1; |
250 | page = buf; |
251 | refresh_text_box(dialog, box, boxh, boxw, |
252 | cur_y, cur_x); |
253 | } |
254 | break; |
255 | case 'G': /* Last page */ |
256 | case KEY_END: |
257 | |
258 | end_reached = 1; |
259 | /* point to last char in buf */ |
260 | page = buf + strlen(s: buf); |
261 | back_lines(n: boxh); |
262 | refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); |
263 | break; |
264 | case 'K': /* Previous line */ |
265 | case 'k': |
266 | case KEY_UP: |
267 | if (begin_reached) |
268 | break; |
269 | |
270 | back_lines(n: page_length + 1); |
271 | refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); |
272 | break; |
273 | case 'B': /* Previous page */ |
274 | case 'b': |
275 | case 'u': |
276 | case KEY_PPAGE: |
277 | if (begin_reached) |
278 | break; |
279 | back_lines(n: page_length + boxh); |
280 | refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); |
281 | break; |
282 | case 'J': /* Next line */ |
283 | case 'j': |
284 | case KEY_DOWN: |
285 | if (end_reached) |
286 | break; |
287 | |
288 | back_lines(n: page_length - 1); |
289 | refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); |
290 | break; |
291 | case KEY_NPAGE: /* Next page */ |
292 | case ' ': |
293 | case 'd': |
294 | if (end_reached) |
295 | break; |
296 | |
297 | begin_reached = 0; |
298 | refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); |
299 | break; |
300 | case '0': /* Beginning of line */ |
301 | case 'H': /* Scroll left */ |
302 | case 'h': |
303 | case KEY_LEFT: |
304 | if (hscroll <= 0) |
305 | break; |
306 | |
307 | if (key == '0') |
308 | hscroll = 0; |
309 | else |
310 | hscroll--; |
311 | /* Reprint current page to scroll horizontally */ |
312 | back_lines(n: page_length); |
313 | refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); |
314 | break; |
315 | case 'L': /* Scroll right */ |
316 | case 'l': |
317 | case KEY_RIGHT: |
318 | if (hscroll >= MAX_LEN) |
319 | break; |
320 | hscroll++; |
321 | /* Reprint current page to scroll horizontally */ |
322 | back_lines(n: page_length); |
323 | refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); |
324 | break; |
325 | case KEY_ESC: |
326 | if (on_key_esc(win: dialog) == KEY_ESC) |
327 | done = true; |
328 | break; |
329 | case KEY_RESIZE: |
330 | back_lines(n: height); |
331 | delwin(box); |
332 | delwin(dialog); |
333 | on_key_resize(); |
334 | goto do_resize; |
335 | default: |
336 | if (extra_key_cb && extra_key_cb(key, start, end, data)) { |
337 | done = true; |
338 | break; |
339 | } |
340 | } |
341 | } |
342 | delwin(box); |
343 | delwin(dialog); |
344 | if (_vscroll) { |
345 | const char *s; |
346 | |
347 | s = buf; |
348 | *_vscroll = 0; |
349 | back_lines(n: page_length); |
350 | while (s < page && (s = strchr(s: s, c: '\n'))) { |
351 | (*_vscroll)++; |
352 | s++; |
353 | } |
354 | } |
355 | if (_hscroll) |
356 | *_hscroll = hscroll; |
357 | return key; |
358 | } |
359 | |