1 /*
2  *  textbox.c -- implements the text box
3  *
4  *  ORIGINAL AUTHOR: Savio Lam ([email protected])
5  *  MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap ([email protected])
6  *
7  *  This program is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU General Public License
9  *  as published by the Free Software Foundation; either version 2
10  *  of the License, or (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21 
22 #include "dialog.h"
23 
24 static void back_lines(int n);
25 static void print_page(WINDOW * win, int height, int width);
26 static void print_line(WINDOW * win, int row, int width);
27 static char *get_line(void);
28 static void print_position(WINDOW * win, int height, int width);
29 
30 static int hscroll, fd, file_size, bytes_read;
31 static int begin_reached = 1, end_reached, page_length;
32 static char *buf, *page;
33 
34 /*
35  * Display text from a file in a dialog box.
36  */
37 int dialog_textbox(const char *title, const char *file, int height, int width)
38 {
39 	int i, x, y, cur_x, cur_y, fpos, key = 0;
40 	int passed_end;
41 	char search_term[MAX_LEN + 1];
42 	WINDOW *dialog, *text;
43 
44 	search_term[0] = '\0';	/* no search term entered yet */
45 
46 	/* Open input file for reading */
47 	if ((fd = open(file, O_RDONLY)) == -1) {
48 		endwin();
49 		fprintf(stderr, "\nCan't open input file in dialog_textbox().\n");
50 		exit(-1);
51 	}
52 	/* Get file size. Actually, 'file_size' is the real file size - 1,
53 	   since it's only the last byte offset from the beginning */
54 	if ((file_size = lseek(fd, 0, SEEK_END)) == -1) {
55 		endwin();
56 		fprintf(stderr, "\nError getting file size in dialog_textbox().\n");
57 		exit(-1);
58 	}
59 	/* Restore file pointer to beginning of file after getting file size */
60 	if (lseek(fd, 0, SEEK_SET) == -1) {
61 		endwin();
62 		fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
63 		exit(-1);
64 	}
65 	/* Allocate space for read buffer */
66 	if ((buf = malloc(BUF_SIZE + 1)) == NULL) {
67 		endwin();
68 		fprintf(stderr, "\nCan't allocate memory in dialog_textbox().\n");
69 		exit(-1);
70 	}
71 	if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
72 		endwin();
73 		fprintf(stderr, "\nError reading file in dialog_textbox().\n");
74 		exit(-1);
75 	}
76 	buf[bytes_read] = '\0';	/* mark end of valid data */
77 	page = buf;		/* page is pointer to start of page to be displayed */
78 
79 	/* center dialog box on screen */
80 	x = (COLS - width) / 2;
81 	y = (LINES - height) / 2;
82 
83 	draw_shadow(stdscr, y, x, height, width);
84 
85 	dialog = newwin(height, width, y, x);
86 	keypad(dialog, TRUE);
87 
88 	/* Create window for text region, used for scrolling text */
89 	text = subwin(dialog, height - 4, width - 2, y + 1, x + 1);
90 	wattrset(text, dlg.dialog.atr);
91 	wbkgdset(text, dlg.dialog.atr & A_COLOR);
92 
93 	keypad(text, TRUE);
94 
95 	/* register the new window, along with its borders */
96 	draw_box(dialog, 0, 0, height, width,
97 		 dlg.dialog.atr, dlg.border.atr);
98 
99 	wattrset(dialog, dlg.border.atr);
100 	mvwaddch(dialog, height - 3, 0, ACS_LTEE);
101 	for (i = 0; i < width - 2; i++)
102 		waddch(dialog, ACS_HLINE);
103 	wattrset(dialog, dlg.dialog.atr);
104 	wbkgdset(dialog, dlg.dialog.atr & A_COLOR);
105 	waddch(dialog, ACS_RTEE);
106 
107 	print_title(dialog, title, width);
108 
109 	print_button(dialog, " Exit ", height - 2, width / 2 - 4, TRUE);
110 	wnoutrefresh(dialog);
111 	getyx(dialog, cur_y, cur_x);	/* Save cursor position */
112 
113 	/* Print first page of text */
114 	attr_clear(text, height - 4, width - 2, dlg.dialog.atr);
115 	print_page(text, height - 4, width - 2);
116 	print_position(dialog, height, width);
117 	wmove(dialog, cur_y, cur_x);	/* Restore cursor position */
118 	wrefresh(dialog);
119 
120 	while ((key != ESC) && (key != '\n')) {
121 		key = wgetch(dialog);
122 		switch (key) {
123 		case 'E':	/* Exit */
124 		case 'e':
125 		case 'X':
126 		case 'x':
127 			delwin(dialog);
128 			free(buf);
129 			close(fd);
130 			return 0;
131 		case 'g':	/* First page */
132 		case KEY_HOME:
133 			if (!begin_reached) {
134 				begin_reached = 1;
135 				/* First page not in buffer? */
136 				if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
137 					endwin();
138 					fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
139 					exit(-1);
140 				}
141 				if (fpos > bytes_read) {	/* Yes, we have to read it in */
142 					if (lseek(fd, 0, SEEK_SET) == -1) {
143 						endwin();
144 						fprintf(stderr, "\nError moving file pointer in "
145 							        "dialog_textbox().\n");
146 						exit(-1);
147 					}
148 					if ((bytes_read =
149 					     read(fd, buf, BUF_SIZE)) == -1) {
150 						endwin();
151 						fprintf(stderr, "\nError reading file in dialog_textbox().\n");
152 						exit(-1);
153 					}
154 					buf[bytes_read] = '\0';
155 				}
156 				page = buf;
157 				print_page(text, height - 4, width - 2);
158 				print_position(dialog, height, width);
159 				wmove(dialog, cur_y, cur_x);	/* Restore cursor position */
160 				wrefresh(dialog);
161 			}
162 			break;
163 		case 'G':	/* Last page */
164 		case KEY_END:
165 
166 			end_reached = 1;
167 			/* Last page not in buffer? */
168 			if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
169 				endwin();
170 				fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
171 				exit(-1);
172 			}
173 			if (fpos < file_size) {	/* Yes, we have to read it in */
174 				if (lseek(fd, -BUF_SIZE, SEEK_END) == -1) {
175 					endwin();
176 					fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n");
177 					exit(-1);
178 				}
179 				if ((bytes_read =
180 				     read(fd, buf, BUF_SIZE)) == -1) {
181 					endwin();
182 					fprintf(stderr, "\nError reading file in dialog_textbox().\n");
183 					exit(-1);
184 				}
185 				buf[bytes_read] = '\0';
186 			}
187 			page = buf + bytes_read;
188 			back_lines(height - 4);
189 			print_page(text, height - 4, width - 2);
190 			print_position(dialog, height, width);
191 			wmove(dialog, cur_y, cur_x);	/* Restore cursor position */
192 			wrefresh(dialog);
193 			break;
194 		case 'K':	/* Previous line */
195 		case 'k':
196 		case KEY_UP:
197 			if (!begin_reached) {
198 				back_lines(page_length + 1);
199 
200 				/* We don't call print_page() here but use scrolling to ensure
201 				   faster screen update. However, 'end_reached' and
202 				   'page_length' should still be updated, and 'page' should
203 				   point to start of next page. This is done by calling
204 				   get_line() in the following 'for' loop. */
205 				scrollok(text, TRUE);
206 				wscrl(text, -1);	/* Scroll text region down one line */
207 				scrollok(text, FALSE);
208 				page_length = 0;
209 				passed_end = 0;
210 				for (i = 0; i < height - 4; i++) {
211 					if (!i) {
212 						/* print first line of page */
213 						print_line(text, 0, width - 2);
214 						wnoutrefresh(text);
215 					} else
216 						/* Called to update 'end_reached' and 'page' */
217 						get_line();
218 					if (!passed_end)
219 						page_length++;
220 					if (end_reached && !passed_end)
221 						passed_end = 1;
222 				}
223 
224 				print_position(dialog, height, width);
225 				wmove(dialog, cur_y, cur_x);	/* Restore cursor position */
226 				wrefresh(dialog);
227 			}
228 			break;
229 		case 'B':	/* Previous page */
230 		case 'b':
231 		case KEY_PPAGE:
232 			if (begin_reached)
233 				break;
234 			back_lines(page_length + height - 4);
235 			print_page(text, height - 4, width - 2);
236 			print_position(dialog, height, width);
237 			wmove(dialog, cur_y, cur_x);
238 			wrefresh(dialog);
239 			break;
240 		case 'J':	/* Next line */
241 		case 'j':
242 		case KEY_DOWN:
243 			if (!end_reached) {
244 				begin_reached = 0;
245 				scrollok(text, TRUE);
246 				scroll(text);	/* Scroll text region up one line */
247 				scrollok(text, FALSE);
248 				print_line(text, height - 5, width - 2);
249 				wnoutrefresh(text);
250 				print_position(dialog, height, width);
251 				wmove(dialog, cur_y, cur_x);	/* Restore cursor position */
252 				wrefresh(dialog);
253 			}
254 			break;
255 		case KEY_NPAGE:	/* Next page */
256 		case ' ':
257 			if (end_reached)
258 				break;
259 
260 			begin_reached = 0;
261 			print_page(text, height - 4, width - 2);
262 			print_position(dialog, height, width);
263 			wmove(dialog, cur_y, cur_x);
264 			wrefresh(dialog);
265 			break;
266 		case '0':	/* Beginning of line */
267 		case 'H':	/* Scroll left */
268 		case 'h':
269 		case KEY_LEFT:
270 			if (hscroll <= 0)
271 				break;
272 
273 			if (key == '0')
274 				hscroll = 0;
275 			else
276 				hscroll--;
277 			/* Reprint current page to scroll horizontally */
278 			back_lines(page_length);
279 			print_page(text, height - 4, width - 2);
280 			wmove(dialog, cur_y, cur_x);
281 			wrefresh(dialog);
282 			break;
283 		case 'L':	/* Scroll right */
284 		case 'l':
285 		case KEY_RIGHT:
286 			if (hscroll >= MAX_LEN)
287 				break;
288 			hscroll++;
289 			/* Reprint current page to scroll horizontally */
290 			back_lines(page_length);
291 			print_page(text, height - 4, width - 2);
292 			wmove(dialog, cur_y, cur_x);
293 			wrefresh(dialog);
294 			break;
295 		case ESC:
296 			break;
297 		}
298 	}
299 
300 	delwin(dialog);
301 	free(buf);
302 	close(fd);
303 	return -1;		/* ESC pressed */
304 }
305 
306 /*
307  * Go back 'n' lines in text file. Called by dialog_textbox().
308  * 'page' will be updated to point to the desired line in 'buf'.
309  */
310 static void back_lines(int n)
311 {
312 	int i, fpos;
313 
314 	begin_reached = 0;
315 	/* We have to distinguish between end_reached and !end_reached
316 	   since at end of file, the line is not ended by a '\n'.
317 	   The code inside 'if' basically does a '--page' to move one
318 	   character backward so as to skip '\n' of the previous line */
319 	if (!end_reached) {
320 		/* Either beginning of buffer or beginning of file reached? */
321 		if (page == buf) {
322 			if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
323 				endwin();
324 				fprintf(stderr, "\nError moving file pointer in "
325 					        "back_lines().\n");
326 				exit(-1);
327 			}
328 			if (fpos > bytes_read) {	/* Not beginning of file yet */
329 				/* We've reached beginning of buffer, but not beginning of
330 				   file yet, so read previous part of file into buffer.
331 				   Note that we only move backward for BUF_SIZE/2 bytes,
332 				   but not BUF_SIZE bytes to avoid re-reading again in
333 				   print_page() later */
334 				/* Really possible to move backward BUF_SIZE/2 bytes? */
335 				if (fpos < BUF_SIZE / 2 + bytes_read) {
336 					/* No, move less then */
337 					if (lseek(fd, 0, SEEK_SET) == -1) {
338 						endwin();
339 						fprintf(stderr, "\nError moving file pointer in "
340 						                "back_lines().\n");
341 						exit(-1);
342 					}
343 					page = buf + fpos - bytes_read;
344 				} else {	/* Move backward BUF_SIZE/2 bytes */
345 					if (lseek (fd, -(BUF_SIZE / 2 + bytes_read), SEEK_CUR) == -1) {
346 						endwin();
347 						fprintf(stderr, "\nError moving file pointer "
348 						                "in back_lines().\n");
349 						exit(-1);
350 					}
351 					page = buf + BUF_SIZE / 2;
352 				}
353 				if ((bytes_read =
354 				     read(fd, buf, BUF_SIZE)) == -1) {
355 					endwin();
356 					fprintf(stderr, "\nError reading file in back_lines().\n");
357 					exit(-1);
358 				}
359 				buf[bytes_read] = '\0';
360 			} else {	/* Beginning of file reached */
361 				begin_reached = 1;
362 				return;
363 			}
364 		}
365 		if (*(--page) != '\n') {	/* '--page' here */
366 			/* Something's wrong... */
367 			endwin();
368 			fprintf(stderr, "\nInternal error in back_lines().\n");
369 			exit(-1);
370 		}
371 	}
372 	/* Go back 'n' lines */
373 	for (i = 0; i < n; i++)
374 		do {
375 			if (page == buf) {
376 				if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
377 					endwin();
378 					fprintf(stderr, "\nError moving file pointer in back_lines().\n");
379 					exit(-1);
380 				}
381 				if (fpos > bytes_read) {
382 					/* Really possible to move backward BUF_SIZE/2 bytes? */
383 					if (fpos < BUF_SIZE / 2 + bytes_read) {
384 						/* No, move less then */
385 						if (lseek(fd, 0, SEEK_SET) == -1) {
386 							endwin();
387 							fprintf(stderr, "\nError moving file pointer "
388 							                "in back_lines().\n");
389 							exit(-1);
390 						}
391 						page = buf + fpos - bytes_read;
392 					} else {	/* Move backward BUF_SIZE/2 bytes */
393 						if (lseek (fd, -(BUF_SIZE / 2 + bytes_read), SEEK_CUR) == -1) {
394 							endwin();
395 							fprintf(stderr, "\nError moving file pointer"
396 							                " in back_lines().\n");
397 							exit(-1);
398 						}
399 						page = buf + BUF_SIZE / 2;
400 					}
401 					if ((bytes_read =
402 					     read(fd, buf, BUF_SIZE)) == -1) {
403 						endwin();
404 						fprintf(stderr, "\nError reading file in "
405 						                "back_lines().\n");
406 						exit(-1);
407 					}
408 					buf[bytes_read] = '\0';
409 				} else {	/* Beginning of file reached */
410 					begin_reached = 1;
411 					return;
412 				}
413 			}
414 		} while (*(--page) != '\n');
415 	page++;
416 }
417 
418 /*
419  * Print a new page of text. Called by dialog_textbox().
420  */
421 static void print_page(WINDOW * win, int height, int width)
422 {
423 	int i, passed_end = 0;
424 
425 	page_length = 0;
426 	for (i = 0; i < height; i++) {
427 		print_line(win, i, width);
428 		if (!passed_end)
429 			page_length++;
430 		if (end_reached && !passed_end)
431 			passed_end = 1;
432 	}
433 	wnoutrefresh(win);
434 }
435 
436 /*
437  * Print a new line of text. Called by dialog_textbox() and print_page().
438  */
439 static void print_line(WINDOW * win, int row, int width)
440 {
441 	int y, x;
442 	char *line;
443 
444 	line = get_line();
445 	line += MIN(strlen(line), hscroll);	/* Scroll horizontally */
446 	wmove(win, row, 0);	/* move cursor to correct line */
447 	waddch(win, ' ');
448 	waddnstr(win, line, MIN(strlen(line), width - 2));
449 
450 	getyx(win, y, x);
451 	/* Clear 'residue' of previous line */
452 #if OLD_NCURSES
453 	{
454 		int i;
455 		for (i = 0; i < width - x; i++)
456 			waddch(win, ' ');
457 	}
458 #else
459 	wclrtoeol(win);
460 #endif
461 }
462 
463 /*
464  * Return current line of text. Called by dialog_textbox() and print_line().
465  * 'page' should point to start of current line before calling, and will be
466  * updated to point to start of next line.
467  */
468 static char *get_line(void)
469 {
470 	int i = 0, fpos;
471 	static char line[MAX_LEN + 1];
472 
473 	end_reached = 0;
474 	while (*page != '\n') {
475 		if (*page == '\0') {
476 			/* Either end of file or end of buffer reached */
477 			if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
478 				endwin();
479 				fprintf(stderr, "\nError moving file pointer in "
480 				                "get_line().\n");
481 				exit(-1);
482 			}
483 			if (fpos < file_size) {	/* Not end of file yet */
484 				/* We've reached end of buffer, but not end of file yet,
485 				   so read next part of file into buffer */
486 				if ((bytes_read =
487 				     read(fd, buf, BUF_SIZE)) == -1) {
488 					endwin();
489 					fprintf(stderr, "\nError reading file in get_line().\n");
490 					exit(-1);
491 				}
492 				buf[bytes_read] = '\0';
493 				page = buf;
494 			} else {
495 				if (!end_reached)
496 					end_reached = 1;
497 				break;
498 			}
499 		} else if (i < MAX_LEN)
500 			line[i++] = *(page++);
501 		else {
502 			/* Truncate lines longer than MAX_LEN characters */
503 			if (i == MAX_LEN)
504 				line[i++] = '\0';
505 			page++;
506 		}
507 	}
508 	if (i <= MAX_LEN)
509 		line[i] = '\0';
510 	if (!end_reached)
511 		page++;		/* move pass '\n' */
512 
513 	return line;
514 }
515 
516 /*
517  * Print current position
518  */
519 static void print_position(WINDOW * win, int height, int width)
520 {
521 	int fpos, percent;
522 
523 	if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
524 		endwin();
525 		fprintf(stderr, "\nError moving file pointer in print_position().\n");
526 		exit(-1);
527 	}
528 	wattrset(win, dlg.position_indicator.atr);
529 	wbkgdset(win, dlg.position_indicator.atr & A_COLOR);
530 	percent = !file_size ?
531 	    100 : ((fpos - bytes_read + page - buf) * 100) / file_size;
532 	wmove(win, height - 3, width - 9);
533 	wprintw(win, "(%3d%%)", percent);
534 }
535