1/*
2 *  $Id: textbox.c,v 1.110 2012/12/01 01:48:08 tom Exp $
3 *
4 *  textbox.c -- implements the text box
5 *
6 *  Copyright 2000-2011,2012	Thomas E.  Dickey
7 *
8 *  This program is free software; you can redistribute it and/or modify
9 *  it under the terms of the GNU Lesser General Public License, version 2.1
10 *  as published by the Free Software Foundation.
11 *
12 *  This program is distributed in the hope that it will be useful, but
13 *  WITHOUT ANY WARRANTY; without even the implied warranty of
14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 *  Lesser General Public License for more details.
16 *
17 *  You should have received a copy of the GNU Lesser General Public
18 *  License along with this program; if not, write to
19 *	Free Software Foundation, Inc.
20 *	51 Franklin St., Fifth Floor
21 *	Boston, MA 02110, USA.
22 *
23 *  An earlier version of this program lists as authors:
24 *	Savio Lam (lam836@cs.cuhk.hk)
25 */
26
27#include <dialog.h>
28#include <dlg_keys.h>
29
30#define PAGE_LENGTH	(height - 4)
31#define PAGE_WIDTH	(width - 2)
32
33typedef struct {
34    DIALOG_CALLBACK obj;
35    WINDOW *text;
36    const char **buttons;
37    int hscroll;
38    char line[MAX_LEN + 1];
39    int fd;
40    long file_size;
41    long fd_bytes_read;
42    long bytes_read;
43    long buffer_len;
44    bool begin_reached;
45    bool buffer_first;
46    bool end_reached;
47    long page_length;		/* lines on the page which is shown */
48    long in_buf;		/* ending index into buf[] for page */
49    char *buf;
50} MY_OBJ;
51
52static long
53lseek_obj(MY_OBJ * obj, long offset, int mode)
54{
55    long fpos;
56    if ((fpos = (long) lseek(obj->fd, (off_t) offset, mode)) == -1) {
57	switch (mode) {
58	default:
59	case SEEK_CUR:
60	    dlg_exiterr("Cannot get file position");
61	    break;
62	case SEEK_END:
63	    dlg_exiterr("Cannot seek to end of file");
64	    break;
65	case SEEK_SET:
66	    dlg_exiterr("Cannot set file position to %ld", offset);
67	    break;
68	}
69    }
70    return fpos;
71}
72
73static long
74ftell_obj(MY_OBJ * obj)
75{
76    return lseek_obj(obj, 0L, SEEK_CUR);
77}
78
79static void
80lseek_set(MY_OBJ * obj, long offset)
81{
82    long actual = lseek_obj(obj, offset, SEEK_SET);
83
84    if (actual != offset) {
85	dlg_exiterr("Cannot set file position to %ld (actual %ld)\n",
86		    offset, actual);
87    }
88}
89
90static void
91lseek_end(MY_OBJ * obj, long offset)
92{
93    long actual = lseek_obj(obj, offset, SEEK_END);
94
95    if (actual > offset) {
96	obj->file_size = actual;
97    }
98}
99
100static void
101lseek_cur(MY_OBJ * obj, long offset)
102{
103    long actual = lseek_obj(obj, offset, SEEK_CUR);
104
105    if (actual != offset) {
106	dlg_trace_msg("Lseek returned %ld, expected %ld\n", actual, offset);
107    }
108}
109
110static char *
111xalloc(size_t size)
112{
113    char *result = dlg_malloc(char, size);
114    assert_ptr(result, "xalloc");
115    return result;
116}
117
118/*
119 * read_high() substitutes read() for tab->spaces conversion
120 *
121 * buffer_len, fd_bytes_read, bytes_read are modified
122 * buf is allocated
123 *
124 * fd_bytes_read is the effective number of bytes read from file
125 * bytes_read is the length of buf, that can be different if tab_correct
126 */
127static void
128read_high(MY_OBJ * obj, size_t size_read)
129{
130    char *buftab, ch;
131    int i = 0, j, n, tmpint;
132    long begin_line;
133
134    /* Allocate space for read buffer */
135    buftab = xalloc(size_read + 1);
136
137    if ((obj->fd_bytes_read = read(obj->fd, buftab, size_read)) != -1) {
138
139	buftab[obj->fd_bytes_read] = '\0';	/* mark end of valid data */
140
141	if (dialog_vars.tab_correct) {
142
143	    /* calculate bytes_read by buftab and fd_bytes_read */
144	    obj->bytes_read = begin_line = 0;
145	    for (j = 0; j < obj->fd_bytes_read; j++)
146		if (buftab[j] == TAB)
147		    obj->bytes_read += dialog_state.tab_len
148			- ((obj->bytes_read - begin_line)
149			   % dialog_state.tab_len);
150		else if (buftab[j] == '\n') {
151		    obj->bytes_read++;
152		    begin_line = obj->bytes_read;
153		} else
154		    obj->bytes_read++;
155
156	    if (obj->bytes_read > obj->buffer_len) {
157		if (obj->buffer_first)
158		    obj->buffer_first = FALSE;	/* disp = 0 */
159		else {
160		    free(obj->buf);
161		}
162
163		obj->buffer_len = obj->bytes_read;
164
165		/* Allocate space for read buffer */
166		obj->buf = xalloc((size_t) obj->buffer_len + 1);
167	    }
168
169	} else {
170	    if (obj->buffer_first) {
171		obj->buffer_first = FALSE;
172
173		/* Allocate space for read buffer */
174		obj->buf = xalloc(size_read + 1);
175	    }
176
177	    obj->bytes_read = obj->fd_bytes_read;
178	}
179
180	j = 0;
181	begin_line = 0;
182	while (j < obj->fd_bytes_read)
183	    if (((ch = buftab[j++]) == TAB) && (dialog_vars.tab_correct != 0)) {
184		tmpint = (dialog_state.tab_len
185			  - ((int) ((long) i - begin_line) % dialog_state.tab_len));
186		for (n = 0; n < tmpint; n++)
187		    obj->buf[i++] = ' ';
188	    } else {
189		if (ch == '\n')
190		    begin_line = i + 1;
191		obj->buf[i++] = ch;
192	    }
193
194	obj->buf[i] = '\0';	/* mark end of valid data */
195
196    }
197    if (obj->bytes_read == -1)
198	dlg_exiterr("Error reading file");
199    free(buftab);
200}
201
202static long
203find_first(MY_OBJ * obj, char *buffer, long length)
204{
205    long recount = obj->page_length;
206    long result = 0;
207
208    while (length > 0) {
209	if (buffer[length] == '\n') {
210	    if (--recount < 0) {
211		result = length;
212		break;
213	    }
214	}
215	--length;
216    }
217    return result;
218}
219
220static long
221tabize(MY_OBJ * obj, long val, long *first_pos)
222{
223    long fpos;
224    long i, count, begin_line;
225    char *buftab;
226
227    if (!dialog_vars.tab_correct)
228	return val;
229
230    fpos = ftell_obj(obj);
231
232    lseek_set(obj, fpos - obj->fd_bytes_read);
233
234    /* Allocate space for read buffer */
235    buftab = xalloc((size_t) val + 1);
236
237    if ((read(obj->fd, buftab, (size_t) val)) == -1)
238	dlg_exiterr("Error reading file in tabize().");
239
240    begin_line = count = 0;
241    if (first_pos != 0)
242	*first_pos = 0;
243
244    for (i = 0; i < val; i++) {
245	if ((first_pos != 0) && (count >= val)) {
246	    *first_pos = find_first(obj, buftab, i);
247	    break;
248	}
249	if (buftab[i] == TAB)
250	    count += dialog_state.tab_len
251		- ((count - begin_line) % dialog_state.tab_len);
252	else if (buftab[i] == '\n') {
253	    count++;
254	    begin_line = count;
255	} else
256	    count++;
257    }
258
259    lseek_set(obj, fpos);
260    free(buftab);
261    return count;
262}
263/*
264 * Return current line of text.
265 * 'page' should point to start of current line before calling, and will be
266 * updated to point to start of next line.
267 */
268static char *
269get_line(MY_OBJ * obj)
270{
271    int i = 0;
272    long fpos;
273
274    obj->end_reached = FALSE;
275    while (obj->buf[obj->in_buf] != '\n') {
276	if (obj->buf[obj->in_buf] == '\0') {	/* Either end of file or end of buffer reached */
277	    fpos = ftell_obj(obj);
278
279	    if (fpos < obj->file_size) {	/* Not end of file yet */
280		/* We've reached end of buffer, but not end of file yet, so
281		 * read next part of file into buffer
282		 */
283		read_high(obj, BUF_SIZE);
284		obj->in_buf = 0;
285	    } else {
286		if (!obj->end_reached)
287		    obj->end_reached = TRUE;
288		break;
289	    }
290	} else if (i < MAX_LEN)
291	    obj->line[i++] = obj->buf[obj->in_buf++];
292	else {
293	    if (i == MAX_LEN)	/* Truncate lines longer than MAX_LEN characters */
294		obj->line[i++] = '\0';
295	    obj->in_buf++;
296	}
297    }
298    if (i <= MAX_LEN)
299	obj->line[i] = '\0';
300    if (!obj->end_reached)
301	obj->in_buf++;		/* move past '\n' */
302
303    return obj->line;
304}
305
306static bool
307match_string(MY_OBJ * obj, char *string)
308{
309    char *match = get_line(obj);
310    return strstr(match, string) != 0;
311}
312
313/*
314 * Go back 'n' lines in text file. Called by dialog_textbox().
315 * 'in_buf' will be updated to point to the desired line in 'buf'.
316 */
317static void
318back_lines(MY_OBJ * obj, long n)
319{
320    int i;
321    long fpos;
322    long val_to_tabize;
323
324    obj->begin_reached = FALSE;
325    /* We have to distinguish between end_reached and !end_reached since at end
326       * of file, the line is not ended by a '\n'.  The code inside 'if'
327       * basically does a '--in_buf' to move one character backward so as to
328       * skip '\n' of the previous line */
329    if (!obj->end_reached) {
330	/* Either beginning of buffer or beginning of file reached? */
331
332	if (obj->in_buf == 0) {
333	    fpos = ftell_obj(obj);
334
335	    if (fpos > obj->fd_bytes_read) {	/* Not beginning of file yet */
336		/* We've reached beginning of buffer, but not beginning of file
337		 * yet, so read previous part of file into buffer.  Note that
338		 * we only move backward for BUF_SIZE/2 bytes, but not BUF_SIZE
339		 * bytes to avoid re-reading again in print_page() later
340		 */
341		/* Really possible to move backward BUF_SIZE/2 bytes? */
342		if (fpos < BUF_SIZE / 2 + obj->fd_bytes_read) {
343		    /* No, move less than */
344		    lseek_set(obj, 0L);
345		    val_to_tabize = fpos - obj->fd_bytes_read;
346		} else {	/* Move backward BUF_SIZE/2 bytes */
347		    lseek_cur(obj, -(BUF_SIZE / 2 + obj->fd_bytes_read));
348		    val_to_tabize = BUF_SIZE / 2;
349		}
350		read_high(obj, BUF_SIZE);
351
352		obj->in_buf = tabize(obj, val_to_tabize, (long *) 0);
353
354	    } else {		/* Beginning of file reached */
355		obj->begin_reached = TRUE;
356		return;
357	    }
358	}
359	obj->in_buf--;
360	if (obj->buf[obj->in_buf] != '\n')
361	    /* Something's wrong... */
362	    dlg_exiterr("Internal error in back_lines().");
363    }
364
365    /* Go back 'n' lines */
366    for (i = 0; i < n; i++) {
367	do {
368	    if (obj->in_buf == 0) {
369		fpos = ftell_obj(obj);
370
371		if (fpos > obj->fd_bytes_read) {
372		    /* Really possible to move backward BUF_SIZE/2 bytes? */
373		    if (fpos < BUF_SIZE / 2 + obj->fd_bytes_read) {
374			/* No, move less than */
375			lseek_set(obj, 0L);
376			val_to_tabize = fpos - obj->fd_bytes_read;
377		    } else {	/* Move backward BUF_SIZE/2 bytes */
378			lseek_cur(obj, -(BUF_SIZE / 2 + obj->fd_bytes_read));
379			val_to_tabize = BUF_SIZE / 2;
380		    }
381		    read_high(obj, BUF_SIZE);
382
383		    obj->in_buf = tabize(obj, val_to_tabize, (long *) 0);
384
385		} else {	/* Beginning of file reached */
386		    obj->begin_reached = TRUE;
387		    return;
388		}
389	    }
390	} while (obj->buf[--(obj->in_buf)] != '\n');
391    }
392    obj->in_buf++;
393}
394
395/*
396 * Print a new line of text.
397 */
398static void
399print_line(MY_OBJ * obj, int row, int width)
400{
401    if (wmove(obj->text, row, 0) != ERR) {
402	int i, y, x;
403	char *line = get_line(obj);
404	const int *cols = dlg_index_columns(line);
405	const int *indx = dlg_index_wchars(line);
406	int limit = dlg_count_wchars(line);
407	int first = 0;
408	int last = limit;
409
410	if (width > getmaxx(obj->text))
411	    width = getmaxx(obj->text);
412	--width;		/* for the leading ' ' */
413
414	for (i = 0; i <= limit && cols[i] < obj->hscroll; ++i)
415	    first = i;
416
417	for (i = first; (i <= limit) && ((cols[i] - cols[first]) < width); ++i)
418	    last = i;
419
420	(void) waddch(obj->text, ' ');
421	(void) waddnstr(obj->text, line + indx[first], indx[last] - indx[first]);
422
423	getyx(obj->text, y, x);
424	if (y == row) {		/* Clear 'residue' of previous line */
425	    for (i = 0; i <= width - x; i++) {
426		(void) waddch(obj->text, ' ');
427	    }
428	}
429    }
430}
431
432/*
433 * Print a new page of text.
434 */
435static void
436print_page(MY_OBJ * obj, int height, int width)
437{
438    int i, passed_end = 0;
439
440    obj->page_length = 0;
441    for (i = 0; i < height; i++) {
442	print_line(obj, i, width);
443	if (!passed_end)
444	    obj->page_length++;
445	if (obj->end_reached && !passed_end)
446	    passed_end = 1;
447    }
448    (void) wnoutrefresh(obj->text);
449    dlg_trace_win(obj->text);
450}
451
452/*
453 * Print current position
454 */
455static void
456print_position(MY_OBJ * obj, WINDOW *win, int height, int width)
457{
458    long fpos;
459    long size;
460    long first = -1;
461
462    fpos = ftell_obj(obj);
463    if (dialog_vars.tab_correct)
464	size = tabize(obj, obj->in_buf, &first);
465    else
466	first = find_first(obj, obj->buf, size = obj->in_buf);
467
468    dlg_draw_scrollbar(win,
469		       first,
470		       fpos - obj->fd_bytes_read + size,
471		       fpos - obj->fd_bytes_read + size,
472		       obj->file_size,
473		       0, PAGE_WIDTH,
474		       0, PAGE_LENGTH + 1,
475		       border_attr,
476		       border_attr);
477}
478
479/*
480 * Display a dialog box and get the search term from user.
481 */
482static int
483get_search_term(WINDOW *dialog, char *input, int height, int width)
484{
485    /* *INDENT-OFF* */
486    static DLG_KEYS_BINDING binding[] = {
487	INPUTSTR_BINDINGS,
488	HELPKEY_BINDINGS,
489	ENTERKEY_BINDINGS,
490	END_KEYS_BINDING
491    };
492    /* *INDENT-ON* */
493
494    int old_x, old_y;
495    int box_x, box_y;
496    int box_height, box_width;
497    int offset = 0;
498    int key = 0;
499    int fkey = 0;
500    bool first = TRUE;
501    int result = DLG_EXIT_UNKNOWN;
502    const char *caption = _("Search");
503    int len_caption = dlg_count_columns(caption);
504    const int *indx;
505    int limit;
506    WINDOW *widget;
507
508    getbegyx(dialog, old_y, old_x);
509
510    box_height = 1 + (2 * MARGIN);
511    box_width = len_caption + (2 * (MARGIN + 2));
512    box_width = MAX(box_width, 30);
513    box_width = MIN(box_width, getmaxx(dialog) - 2 * MARGIN);
514    len_caption = MIN(len_caption, box_width - (2 * (MARGIN + 1)));
515
516    box_x = (width - box_width) / 2;
517    box_y = (height - box_height) / 2;
518    widget = dlg_new_modal_window(dialog,
519				  box_height, box_width,
520				  old_y + box_y, old_x + box_x);
521    keypad(widget, TRUE);
522    dlg_register_window(widget, "searchbox", binding);
523
524    dlg_draw_box2(widget, 0, 0, box_height, box_width,
525		  searchbox_attr,
526		  searchbox_border_attr,
527		  searchbox_border2_attr);
528    (void) wattrset(widget, searchbox_title_attr);
529    (void) wmove(widget, 0, (box_width - len_caption) / 2);
530
531    indx = dlg_index_wchars(caption);
532    limit = dlg_limit_columns(caption, len_caption, 0);
533    (void) waddnstr(widget, caption + indx[0], indx[limit] - indx[0]);
534
535    box_width -= 2;
536    offset = dlg_count_columns(input);
537
538    while (result == DLG_EXIT_UNKNOWN) {
539	if (!first) {
540	    key = dlg_getc(widget, &fkey);
541	    if (fkey) {
542		switch (fkey) {
543#ifdef KEY_RESIZE
544		case KEY_RESIZE:
545		    result = DLG_EXIT_CANCEL;
546		    continue;
547#endif
548		case DLGK_ENTER:
549		    result = DLG_EXIT_OK;
550		    continue;
551		}
552	    } else if (key == ESC) {
553		result = DLG_EXIT_ESC;
554		continue;
555	    } else if (key == ERR) {
556		napms(50);
557		continue;
558	    }
559	}
560	if (dlg_edit_string(input, &offset, key, fkey, first)) {
561	    dlg_show_string(widget, input, offset, searchbox_attr,
562			    1, 1, box_width, FALSE, first);
563	    first = FALSE;
564	}
565    }
566    dlg_del_window(widget);
567    return result;
568}
569
570static bool
571perform_search(MY_OBJ * obj, int height, int width, int key, char *search_term)
572{
573    int dir;
574    long tempinx;
575    long fpos;
576    int result;
577    bool found;
578    bool temp, temp1;
579    bool moved = FALSE;
580
581    /* set search direction */
582    dir = (key == '/' || key == 'n') ? 1 : 0;
583    if (dir ? !obj->end_reached : !obj->begin_reached) {
584	if (key == 'n' || key == 'N') {
585	    if (search_term[0] == '\0') {	/* No search term yet */
586		(void) beep();
587		return FALSE;
588	    }
589	    /* Get search term from user */
590	} else if ((result = get_search_term(obj->text, search_term,
591					     PAGE_LENGTH,
592					     PAGE_WIDTH)) != DLG_EXIT_OK
593		   || search_term[0] == '\0') {
594#ifdef KEY_RESIZE
595	    if (result == DLG_EXIT_CANCEL) {
596		ungetch(key);
597		ungetch(KEY_RESIZE);
598		/* FALLTHRU */
599	    }
600#endif
601	    /* ESC pressed, or no search term, reprint page to clear box */
602	    (void) wattrset(obj->text, dialog_attr);
603	    back_lines(obj, obj->page_length);
604	    return TRUE;
605	}
606	/* Save variables for restoring in case search term can't be found */
607	tempinx = obj->in_buf;
608	temp = obj->begin_reached;
609	temp1 = obj->end_reached;
610	fpos = ftell_obj(obj) - obj->fd_bytes_read;
611	/* update 'in_buf' to point to next (previous) line before
612	   forward (backward) searching */
613	back_lines(obj, (dir
614			 ? obj->page_length - 1
615			 : obj->page_length + 1));
616	if (dir) {		/* Forward search */
617	    while ((found = match_string(obj, search_term)) == FALSE) {
618		if (obj->end_reached)
619		    break;
620	    }
621	} else {		/* Backward search */
622	    while ((found = match_string(obj, search_term)) == FALSE) {
623		if (obj->begin_reached)
624		    break;
625		back_lines(obj, 2L);
626	    }
627	}
628	if (found == FALSE) {	/* not found */
629	    (void) beep();
630	    /* Restore program state to that before searching */
631	    lseek_set(obj, fpos);
632
633	    read_high(obj, BUF_SIZE);
634
635	    obj->in_buf = tempinx;
636	    obj->begin_reached = temp;
637	    obj->end_reached = temp1;
638	    /* move 'in_buf' to point to start of current page to
639	     * re-print current page.  Note that 'in_buf' always points
640	     * to start of next page, so this is necessary
641	     */
642	    back_lines(obj, obj->page_length);
643	} else {		/* Search term found */
644	    back_lines(obj, 1L);
645	}
646	/* Reprint page */
647	(void) wattrset(obj->text, dialog_attr);
648	moved = TRUE;
649    } else {			/* no need to find */
650	(void) beep();
651    }
652    return moved;
653}
654
655/*
656 * Display text from a file in a dialog box.
657 */
658int
659dialog_textbox(const char *title, const char *file, int height, int width)
660{
661    /* *INDENT-OFF* */
662    static DLG_KEYS_BINDING binding[] = {
663	HELPKEY_BINDINGS,
664	ENTERKEY_BINDINGS,
665	DLG_KEYS_DATA( DLGK_GRID_DOWN,  'J' ),
666	DLG_KEYS_DATA( DLGK_GRID_DOWN,  'j' ),
667	DLG_KEYS_DATA( DLGK_GRID_DOWN,  KEY_DOWN ),
668	DLG_KEYS_DATA( DLGK_GRID_LEFT,  'H' ),
669	DLG_KEYS_DATA( DLGK_GRID_LEFT,  'h' ),
670	DLG_KEYS_DATA( DLGK_GRID_LEFT,  KEY_LEFT ),
671	DLG_KEYS_DATA( DLGK_GRID_RIGHT, 'L' ),
672	DLG_KEYS_DATA( DLGK_GRID_RIGHT, 'l' ),
673	DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ),
674	DLG_KEYS_DATA( DLGK_GRID_UP,    'K' ),
675	DLG_KEYS_DATA( DLGK_GRID_UP,    'k' ),
676	DLG_KEYS_DATA( DLGK_GRID_UP,    KEY_UP ),
677	DLG_KEYS_DATA( DLGK_PAGE_FIRST, 'g' ),
678	DLG_KEYS_DATA( DLGK_PAGE_FIRST, KEY_HOME ),
679	DLG_KEYS_DATA( DLGK_PAGE_LAST,  'G' ),
680	DLG_KEYS_DATA( DLGK_PAGE_LAST,  KEY_END ),
681	DLG_KEYS_DATA( DLGK_PAGE_LAST,  KEY_LL ),
682	DLG_KEYS_DATA( DLGK_PAGE_NEXT,  ' ' ),
683	DLG_KEYS_DATA( DLGK_PAGE_NEXT,  KEY_NPAGE ),
684	DLG_KEYS_DATA( DLGK_PAGE_PREV,  'B' ),
685	DLG_KEYS_DATA( DLGK_PAGE_PREV,  'b' ),
686	DLG_KEYS_DATA( DLGK_PAGE_PREV,  KEY_PPAGE ),
687	DLG_KEYS_DATA( DLGK_BEGIN,	'0' ),
688	DLG_KEYS_DATA( DLGK_BEGIN,	KEY_BEG ),
689	DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ),
690	DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ),
691	END_KEYS_BINDING
692    };
693    /* *INDENT-ON* */
694
695#ifdef KEY_RESIZE
696    int old_height = height;
697    int old_width = width;
698#endif
699    long fpos;
700    int x, y, cur_x, cur_y;
701    int key = 0, fkey;
702    int next = 0;
703    int i, code, passed_end;
704    char search_term[MAX_LEN + 1];
705    MY_OBJ obj;
706    WINDOW *dialog;
707    bool moved;
708    int result = DLG_EXIT_UNKNOWN;
709    int button = dlg_default_button();
710    int min_width = 12;
711
712    search_term[0] = '\0';	/* no search term entered yet */
713
714    memset(&obj, 0, sizeof(obj));
715
716    obj.begin_reached = TRUE;
717    obj.buffer_first = TRUE;
718    obj.end_reached = FALSE;
719    obj.buttons = dlg_exit_label();
720
721    /* Open input file for reading */
722    if ((obj.fd = open(file, O_RDONLY)) == -1)
723	dlg_exiterr("Can't open input file %s", file);
724
725    /* Get file size. Actually, 'file_size' is the real file size - 1,
726       since it's only the last byte offset from the beginning */
727    lseek_end(&obj, 0L);
728
729    /* Restore file pointer to beginning of file after getting file size */
730    lseek_set(&obj, 0L);
731
732    read_high(&obj, BUF_SIZE);
733
734    dlg_button_layout(obj.buttons, &min_width);
735
736#ifdef KEY_RESIZE
737  retry:
738#endif
739    moved = TRUE;
740
741    dlg_auto_sizefile(title, file, &height, &width, 2, min_width);
742    dlg_print_size(height, width);
743    dlg_ctl_size(height, width);
744
745    x = dlg_box_x_ordinate(width);
746    y = dlg_box_y_ordinate(height);
747
748    dialog = dlg_new_window(height, width, y, x);
749    dlg_register_window(dialog, "textbox", binding);
750    dlg_register_buttons(dialog, "textbox", obj.buttons);
751
752    dlg_mouse_setbase(x, y);
753
754    /* Create window for text region, used for scrolling text */
755    obj.text = dlg_sub_window(dialog, PAGE_LENGTH, PAGE_WIDTH, y + 1, x + 1);
756
757    /* register the new window, along with its borders */
758    dlg_mouse_mkbigregion(0, 0, PAGE_LENGTH + 2, width, KEY_MAX, 1, 1, 1 /* lines */ );
759    dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
760    dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
761    dlg_draw_title(dialog, title);
762
763    dlg_draw_buttons(dialog, PAGE_LENGTH + 2, 0, obj.buttons, button, FALSE, width);
764    (void) wnoutrefresh(dialog);
765    getyx(dialog, cur_y, cur_x);	/* Save cursor position */
766
767    dlg_attr_clear(obj.text, PAGE_LENGTH, PAGE_WIDTH, dialog_attr);
768
769    while (result == DLG_EXIT_UNKNOWN) {
770
771	/*
772	 * Update the screen according to whether we shifted up/down by a line
773	 * or not.
774	 */
775	if (moved) {
776	    if (next < 0) {
777		(void) scrollok(obj.text, TRUE);
778		(void) scroll(obj.text);	/* Scroll text region up one line */
779		(void) scrollok(obj.text, FALSE);
780		print_line(&obj, PAGE_LENGTH - 1, PAGE_WIDTH);
781		(void) wnoutrefresh(obj.text);
782	    } else if (next > 0) {
783		/*
784		 * We don't call print_page() here but use scrolling to ensure
785		 * faster screen update.  However, 'end_reached' and
786		 * 'page_length' should still be updated, and 'in_buf' should
787		 * point to start of next page.  This is done by calling
788		 * get_line() in the following 'for' loop.
789		 */
790		(void) scrollok(obj.text, TRUE);
791		(void) wscrl(obj.text, -1);	/* Scroll text region down one line */
792		(void) scrollok(obj.text, FALSE);
793		obj.page_length = 0;
794		passed_end = 0;
795		for (i = 0; i < PAGE_LENGTH; i++) {
796		    if (!i) {
797			print_line(&obj, 0, PAGE_WIDTH);	/* print first line of page */
798			(void) wnoutrefresh(obj.text);
799		    } else
800			(void) get_line(&obj);	/* Called to update 'end_reached' and 'in_buf' */
801		    if (!passed_end)
802			obj.page_length++;
803		    if (obj.end_reached && !passed_end)
804			passed_end = 1;
805		}
806	    } else {
807		print_page(&obj, PAGE_LENGTH, PAGE_WIDTH);
808	    }
809	    print_position(&obj, dialog, height, width);
810	    (void) wmove(dialog, cur_y, cur_x);		/* Restore cursor position */
811	    wrefresh(dialog);
812	}
813	moved = FALSE;		/* assume we'll not move */
814	next = 0;		/* ...but not scroll by a line */
815
816	key = dlg_mouse_wgetch(dialog, &fkey);
817	if (dlg_result_key(key, fkey, &result))
818	    break;
819
820	if (!fkey && (code = dlg_char_to_button(key, obj.buttons)) >= 0) {
821	    result = dlg_ok_buttoncode(code);
822	    break;
823	}
824
825	if (fkey) {
826	    switch (key) {
827	    default:
828		if (is_DLGK_MOUSE(key)) {
829		    result = dlg_exit_buttoncode(key - M_EVENT);
830		    if (result < 0)
831			result = DLG_EXIT_OK;
832		} else {
833		    beep();
834		}
835		break;
836	    case DLGK_FIELD_NEXT:
837		button = dlg_next_button(obj.buttons, button);
838		if (button < 0)
839		    button = 0;
840		dlg_draw_buttons(dialog,
841				 height - 2, 0,
842				 obj.buttons, button,
843				 FALSE, width);
844		break;
845	    case DLGK_FIELD_PREV:
846		button = dlg_prev_button(obj.buttons, button);
847		if (button < 0)
848		    button = 0;
849		dlg_draw_buttons(dialog,
850				 height - 2, 0,
851				 obj.buttons, button,
852				 FALSE, width);
853		break;
854	    case DLGK_ENTER:
855		if (dialog_vars.nook)
856		    result = DLG_EXIT_OK;
857		else
858		    result = dlg_exit_buttoncode(button);
859		break;
860	    case DLGK_PAGE_FIRST:
861		if (!obj.begin_reached) {
862		    obj.begin_reached = 1;
863		    /* First page not in buffer? */
864		    fpos = ftell_obj(&obj);
865
866		    if (fpos > obj.fd_bytes_read) {
867			/* Yes, we have to read it in */
868			lseek_set(&obj, 0L);
869
870			read_high(&obj, BUF_SIZE);
871		    }
872		    obj.in_buf = 0;
873		    moved = TRUE;
874		}
875		break;
876	    case DLGK_PAGE_LAST:
877		obj.end_reached = TRUE;
878		/* Last page not in buffer? */
879		fpos = ftell_obj(&obj);
880
881		if (fpos < obj.file_size) {
882		    /* Yes, we have to read it in */
883		    lseek_end(&obj, -BUF_SIZE);
884
885		    read_high(&obj, BUF_SIZE);
886		}
887		obj.in_buf = obj.bytes_read;
888		back_lines(&obj, (long) PAGE_LENGTH);
889		moved = TRUE;
890		break;
891	    case DLGK_GRID_UP:	/* Previous line */
892		if (!obj.begin_reached) {
893		    back_lines(&obj, obj.page_length + 1);
894		    next = 1;
895		    moved = TRUE;
896		}
897		break;
898	    case DLGK_PAGE_PREV:	/* Previous page */
899	    case DLGK_MOUSE(KEY_PPAGE):
900		if (!obj.begin_reached) {
901		    back_lines(&obj, obj.page_length + PAGE_LENGTH);
902		    moved = TRUE;
903		}
904		break;
905	    case DLGK_GRID_DOWN:	/* Next line */
906		if (!obj.end_reached) {
907		    obj.begin_reached = 0;
908		    next = -1;
909		    moved = TRUE;
910		}
911		break;
912	    case DLGK_PAGE_NEXT:	/* Next page */
913	    case DLGK_MOUSE(KEY_NPAGE):
914		if (!obj.end_reached) {
915		    obj.begin_reached = 0;
916		    moved = TRUE;
917		}
918		break;
919	    case DLGK_BEGIN:	/* Beginning of line */
920		if (obj.hscroll > 0) {
921		    obj.hscroll = 0;
922		    /* Reprint current page to scroll horizontally */
923		    back_lines(&obj, obj.page_length);
924		    moved = TRUE;
925		}
926		break;
927	    case DLGK_GRID_LEFT:	/* Scroll left */
928		if (obj.hscroll > 0) {
929		    obj.hscroll--;
930		    /* Reprint current page to scroll horizontally */
931		    back_lines(&obj, obj.page_length);
932		    moved = TRUE;
933		}
934		break;
935	    case DLGK_GRID_RIGHT:	/* Scroll right */
936		if (obj.hscroll < MAX_LEN) {
937		    obj.hscroll++;
938		    /* Reprint current page to scroll horizontally */
939		    back_lines(&obj, obj.page_length);
940		    moved = TRUE;
941		}
942		break;
943#ifdef KEY_RESIZE
944	    case KEY_RESIZE:
945		/* reset data */
946		height = old_height;
947		width = old_width;
948		back_lines(&obj, obj.page_length);
949		/* repaint */
950		dlg_clear();
951		dlg_del_window(dialog);
952		refresh();
953		dlg_mouse_free_regions();
954		goto retry;
955#endif
956	    }
957	} else {
958	    switch (key) {
959	    case '/':		/* Forward search */
960	    case 'n':		/* Repeat forward search */
961	    case '?':		/* Backward search */
962	    case 'N':		/* Repeat backward search */
963		moved = perform_search(&obj, height, width, key, search_term);
964		fkey = FALSE;
965		break;
966	    default:
967		beep();
968		break;
969	    }
970	}
971    }
972
973    dlg_del_window(dialog);
974    free(obj.buf);
975    (void) close(obj.fd);
976    dlg_mouse_free_regions();
977    return result;
978}
979