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