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