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