1/****************************************************************************
2 * Copyright (c) 1998-2007,2008 Free Software Foundation, Inc.              *
3 *                                                                          *
4 * Permission is hereby granted, free of charge, to any person obtaining a  *
5 * copy of this software and associated documentation files (the            *
6 * "Software"), to deal in the Software without restriction, including      *
7 * without limitation the rights to use, copy, modify, merge, publish,      *
8 * distribute, distribute with modifications, sublicense, and/or sell       *
9 * copies of the Software, and to permit persons to whom the Software is    *
10 * furnished to do so, subject to the following conditions:                 *
11 *                                                                          *
12 * The above copyright notice and this permission notice shall be included  *
13 * in all copies or substantial portions of the Software.                   *
14 *                                                                          *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22 *                                                                          *
23 * Except as contained in this notice, the name(s) of the above copyright   *
24 * holders shall not be used in advertising or otherwise to promote the     *
25 * sale, use or other dealings in this Software without prior written       *
26 * authorization.                                                           *
27 ****************************************************************************/
28/*
29 * view.c -- a silly little viewer program
30 *
31 * written by Eric S. Raymond <esr@snark.thyrsus.com> December 1994
32 * to test the scrolling code in ncurses.
33 *
34 * modified by Thomas Dickey <dickey@clark.net> July 1995 to demonstrate
35 * the use of 'resizeterm()', and May 2000 to illustrate wide-character
36 * handling.
37 *
38 * Takes a filename argument.  It's a simple file-viewer with various
39 * scroll-up and scroll-down commands.
40 *
41 * n	-- scroll one line forward
42 * p	-- scroll one line back
43 *
44 * Either command accepts a numeric prefix interpreted as a repeat count.
45 * Thus, typing `5n' should scroll forward 5 lines in the file.
46 *
47 * The way you can tell this is working OK is that, in the trace file,
48 * there should be one scroll operation plus a small number of line
49 * updates, as opposed to a whole-page update.  This means the physical
50 * scroll operation worked, and the refresh() code only had to do a
51 * partial repaint.
52 *
53 * $Id: view.c,v 1.69 2008/09/06 22:10:50 tom Exp $
54 */
55
56#include <test.priv.h>
57
58#include <time.h>
59
60#undef CTRL			/* conflict on AIX 5.2 with <sys/ioctl.h> */
61
62#if HAVE_TERMIOS_H
63# include <termios.h>
64#else
65# include <sgtty.h>
66#endif
67
68#if !defined(sun) || !HAVE_TERMIOS_H
69# if HAVE_SYS_IOCTL_H
70#  include <sys/ioctl.h>
71# endif
72#endif
73
74#define my_pair 1
75
76/* This is needed to compile 'struct winsize' */
77#if NEED_PTEM_H
78#include <sys/stream.h>
79#include <sys/ptem.h>
80#endif
81
82#if USE_WIDEC_SUPPORT
83#if HAVE_MBTOWC && HAVE_MBLEN
84#define reset_mbytes(state) mblen(NULL, 0), mbtowc(NULL, NULL, 0)
85#define count_mbytes(buffer,length,state) mblen(buffer,length)
86#define check_mbytes(wch,buffer,length,state) \
87	(int) mbtowc(&wch, buffer, length)
88#define state_unused
89#elif HAVE_MBRTOWC && HAVE_MBRLEN
90#define reset_mbytes(state) init_mb(state)
91#define count_mbytes(buffer,length,state) mbrlen(buffer,length,&state)
92#define check_mbytes(wch,buffer,length,state) \
93	(int) mbrtowc(&wch, buffer, length, &state)
94#else
95make an error
96#endif
97#endif				/* USE_WIDEC_SUPPORT */
98
99static RETSIGTYPE finish(int sig) GCC_NORETURN;
100static void show_all(const char *tag);
101
102#if defined(SIGWINCH) && defined(TIOCGWINSZ) && HAVE_RESIZE_TERM
103#define CAN_RESIZE 1
104#else
105#define CAN_RESIZE 0
106#endif
107
108#if CAN_RESIZE
109static RETSIGTYPE adjust(int sig);
110static int interrupted;
111#endif
112
113static bool waiting = FALSE;
114static int shift = 0;
115static bool try_color = FALSE;
116
117static char *fname;
118static NCURSES_CH_T **vec_lines;
119static NCURSES_CH_T **lptr;
120static int num_lines;
121
122static void
123usage(void)
124{
125    static const char *msg[] =
126    {
127	"Usage: view [options] file"
128	,""
129	,"Options:"
130	," -c       use color if terminal supports it"
131	," -i       ignore INT, QUIT, TERM signals"
132	," -n NUM   specify maximum number of lines (default 1000)"
133#if defined(KEY_RESIZE)
134	," -r       use old-style sigwinch handler rather than KEY_RESIZE"
135#endif
136#ifdef TRACE
137	," -t       trace screen updates"
138	," -T NUM   specify trace mask"
139#endif
140    };
141    size_t n;
142    for (n = 0; n < SIZEOF(msg); n++)
143	fprintf(stderr, "%s\n", msg[n]);
144    ExitProgram(EXIT_FAILURE);
145}
146
147static int
148ch_len(NCURSES_CH_T * src)
149{
150    int result = 0;
151#if USE_WIDEC_SUPPORT
152#endif
153
154#if USE_WIDEC_SUPPORT
155    while (getcchar(src++, NULL, NULL, NULL, NULL) > 0)
156	result++;
157#else
158    while (*src++)
159	result++;
160#endif
161    return result;
162}
163
164/*
165 * Allocate a string into an array of chtype's.  If UTF-8 mode is
166 * active, translate the string accordingly.
167 */
168static NCURSES_CH_T *
169ch_dup(char *src)
170{
171    unsigned len = strlen(src);
172    NCURSES_CH_T *dst = typeMalloc(NCURSES_CH_T, len + 1);
173    unsigned j, k;
174#if USE_WIDEC_SUPPORT
175    wchar_t wstr[CCHARW_MAX + 1];
176    wchar_t wch;
177    int l = 0;
178    size_t rc;
179    int width;
180#ifndef state_unused
181    mbstate_t state;
182#endif
183#endif /* USE_WIDEC_SUPPORT */
184
185#if USE_WIDEC_SUPPORT
186    reset_mbytes(state);
187#endif
188    for (j = k = 0; j < len; j++) {
189#if USE_WIDEC_SUPPORT
190	rc = check_mbytes(wch, src + j, len - j, state);
191	if (rc == (size_t) -1 || rc == (size_t) -2)
192	    break;
193	j += rc - 1;
194	if ((width = wcwidth(wch)) < 0)
195	    break;
196	if ((width > 0 && l > 0) || l == CCHARW_MAX) {
197	    wstr[l] = L'\0';
198	    l = 0;
199	    if (setcchar(dst + k, wstr, 0, 0, NULL) != OK)
200		break;
201	    ++k;
202	}
203	if (width == 0 && l == 0)
204	    wstr[l++] = L' ';
205	wstr[l++] = wch;
206#else
207	dst[k++] = src[j];
208#endif
209    }
210#if USE_WIDEC_SUPPORT
211    if (l > 0) {
212	wstr[l] = L'\0';
213	if (setcchar(dst + k, wstr, 0, 0, NULL) == OK)
214	    ++k;
215    }
216    wstr[0] = L'\0';
217    setcchar(dst + k, wstr, 0, 0, NULL);
218#else
219    dst[k] = 0;
220#endif
221    return dst;
222}
223
224int
225main(int argc, char *argv[])
226{
227    int MAXLINES = 1000;
228    FILE *fp;
229    char buf[BUFSIZ];
230    int i;
231    int my_delay = 0;
232    NCURSES_CH_T **olptr;
233    int value = 0;
234    bool done = FALSE;
235    bool got_number = FALSE;
236#if CAN_RESIZE
237    bool nonposix_resize = FALSE;
238#endif
239    const char *my_label = "Input";
240
241    setlocale(LC_ALL, "");
242
243#ifndef NCURSES_VERSION
244    /*
245     * We know ncurses will catch SIGINT if we don't establish our own handler.
246     * Other versions of curses may/may not catch it.
247     */
248    (void) signal(SIGINT, finish);	/* arrange interrupts to terminate */
249#endif
250
251    while ((i = getopt(argc, argv, "cin:rtT:")) != -1) {
252	switch (i) {
253	case 'c':
254	    try_color = TRUE;
255	    break;
256	case 'i':
257	    CATCHALL(SIG_IGN);
258	    break;
259	case 'n':
260	    if ((MAXLINES = atoi(optarg)) < 1 ||
261		(MAXLINES + 2) <= 1)
262		usage();
263	    break;
264#if CAN_RESIZE
265	case 'r':
266	    nonposix_resize = TRUE;
267	    break;
268#endif
269#ifdef TRACE
270	case 'T':
271	    trace((unsigned) atoi(optarg));
272	    break;
273	case 't':
274	    trace(TRACE_CALLS);
275	    break;
276#endif
277	default:
278	    usage();
279	}
280    }
281    if (optind + 1 != argc)
282	usage();
283
284    if ((vec_lines = typeMalloc(NCURSES_CH_T *, MAXLINES + 2)) == 0)
285	usage();
286
287    fname = argv[optind];
288    if ((fp = fopen(fname, "r")) == 0) {
289	perror(fname);
290	ExitProgram(EXIT_FAILURE);
291    }
292#if CAN_RESIZE
293    if (nonposix_resize)
294	(void) signal(SIGWINCH, adjust);	/* arrange interrupts to resize */
295#endif
296
297    /* slurp the file */
298    for (lptr = &vec_lines[0]; (lptr - vec_lines) < MAXLINES; lptr++) {
299	char temp[BUFSIZ], *s, *d;
300	int col;
301
302	if (fgets(buf, sizeof(buf), fp) == 0)
303	    break;
304
305	/* convert tabs so that shift will work properly */
306	for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) {
307	    if (*d == '\n') {
308		*d = '\0';
309		break;
310	    } else if (*d == '\t') {
311		col = (col | 7) + 1;
312		while ((d - temp) != col)
313		    *d++ = ' ';
314	    } else
315#if USE_WIDEC_SUPPORT
316		col++, d++;
317#else
318	    if (isprint(UChar(*d))) {
319		col++;
320		d++;
321	    } else {
322		sprintf(d, "\\%03o", UChar(*s));
323		d += strlen(d);
324		col = (d - temp);
325	    }
326#endif
327	}
328	*lptr = ch_dup(temp);
329    }
330    (void) fclose(fp);
331    num_lines = lptr - vec_lines;
332
333    (void) initscr();		/* initialize the curses library */
334    keypad(stdscr, TRUE);	/* enable keyboard mapping */
335    (void) nonl();		/* tell curses not to do NL->CR/NL on output */
336    (void) cbreak();		/* take input chars one at a time, no wait for \n */
337    (void) noecho();		/* don't echo input */
338    nodelay(stdscr, TRUE);
339    idlok(stdscr, TRUE);	/* allow use of insert/delete line */
340
341    if (try_color) {
342	if (has_colors()) {
343	    start_color();
344	    init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
345	    bkgd(COLOR_PAIR(my_pair));
346	} else {
347	    try_color = FALSE;
348	}
349    }
350
351    lptr = vec_lines;
352    while (!done) {
353	int n, c;
354
355	if (!got_number)
356	    show_all(my_label);
357
358	n = 0;
359	for (;;) {
360#if CAN_RESIZE
361	    if (interrupted) {
362		adjust(0);
363		my_label = "interrupt";
364	    }
365#endif
366	    waiting = TRUE;
367	    c = getch();
368	    waiting = FALSE;
369	    if ((c < 127) && isdigit(c)) {
370		if (!got_number) {
371		    mvprintw(0, 0, "Count: ");
372		    clrtoeol();
373		}
374		addch(UChar(c));
375		value = 10 * value + (c - '0');
376		got_number = TRUE;
377	    } else
378		break;
379	}
380	if (got_number && value) {
381	    n = value;
382	} else {
383	    n = 1;
384	}
385
386	if (c != ERR)
387	    my_label = keyname(c);
388	switch (c) {
389	case KEY_DOWN:
390	case 'n':
391	    olptr = lptr;
392	    for (i = 0; i < n; i++)
393		if ((lptr - vec_lines) < (num_lines - LINES + 1))
394		    lptr++;
395		else
396		    break;
397	    scrl(lptr - olptr);
398	    break;
399
400	case KEY_UP:
401	case 'p':
402	    olptr = lptr;
403	    for (i = 0; i < n; i++)
404		if (lptr > vec_lines)
405		    lptr--;
406		else
407		    break;
408	    scrl(lptr - olptr);
409	    break;
410
411	case 'h':
412	case KEY_HOME:
413	    lptr = vec_lines;
414	    break;
415
416	case 'e':
417	case KEY_END:
418	    if (num_lines > LINES)
419		lptr = vec_lines + num_lines - LINES + 1;
420	    else
421		lptr = vec_lines;
422	    break;
423
424	case 'r':
425	case KEY_RIGHT:
426	    shift += n;
427	    break;
428
429	case 'l':
430	case KEY_LEFT:
431	    shift -= n;
432	    if (shift < 0) {
433		shift = 0;
434		beep();
435	    }
436	    break;
437
438	case 'q':
439	    done = TRUE;
440	    break;
441
442#ifdef KEY_RESIZE
443	case KEY_RESIZE:	/* ignore this; ncurses will repaint */
444	    break;
445#endif
446	case 's':
447	    if (got_number) {
448		halfdelay(my_delay = n);
449	    } else {
450		nodelay(stdscr, FALSE);
451		my_delay = -1;
452	    }
453	    break;
454	case ' ':
455	    nodelay(stdscr, TRUE);
456	    my_delay = 0;
457	    break;
458	case ERR:
459	    if (!my_delay)
460		napms(50);
461	    break;
462	default:
463	    beep();
464	    break;
465	}
466	if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
467	    got_number = FALSE;
468	    value = 0;
469	}
470    }
471
472    finish(0);			/* we're done */
473}
474
475static RETSIGTYPE
476finish(int sig)
477{
478    endwin();
479#if NO_LEAKS
480    if (vec_lines != 0) {
481	int n;
482	for (n = 0; n < num_lines; ++n) {
483	    free(vec_lines[n]);
484	}
485	free(vec_lines);
486    }
487#endif
488    ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
489}
490
491#if CAN_RESIZE
492/*
493 * This uses functions that are "unsafe", but it seems to work on SunOS and
494 * Linux.  Usually:  the "unsafe" refers to the functions that POSIX lists
495 * which may be called from a signal handler.  Those do not include buffered
496 * I/O, which is used for instance in wrefresh().  To be really portable, you
497 * should use the KEY_RESIZE return (which relies on ncurses' sigwinch
498 * handler).
499 *
500 * The 'wrefresh(curscr)' is needed to force the refresh to start from the top
501 * of the screen -- some xterms mangle the bitmap while resizing.
502 */
503static RETSIGTYPE
504adjust(int sig)
505{
506    if (waiting || sig == 0) {
507	struct winsize size;
508
509	if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
510	    resize_term(size.ws_row, size.ws_col);
511	    wrefresh(curscr);	/* Linux needs this */
512	    show_all(sig ? "SIGWINCH" : "interrupt");
513	}
514	interrupted = FALSE;
515    } else {
516	interrupted = TRUE;
517    }
518    (void) signal(SIGWINCH, adjust);	/* some systems need this */
519}
520#endif /* CAN_RESIZE */
521
522static void
523show_all(const char *tag)
524{
525    int i;
526    char temp[BUFSIZ];
527    NCURSES_CH_T *s;
528    time_t this_time;
529
530#if CAN_RESIZE
531    sprintf(temp, "%.20s (%3dx%3d) col %d ", tag, LINES, COLS, shift);
532    i = strlen(temp);
533    if ((i + 7) < (int) sizeof(temp))
534	sprintf(temp + i, "view %.*s", (int) (sizeof(temp) - 7 - i), fname);
535#else
536    (void) tag;
537    sprintf(temp, "view %.*s", (int) sizeof(temp) - 7, fname);
538#endif
539    move(0, 0);
540    printw("%.*s", COLS, temp);
541    clrtoeol();
542    this_time = time((time_t *) 0);
543    strcpy(temp, ctime(&this_time));
544    if ((i = strlen(temp)) != 0) {
545	temp[--i] = 0;
546	if (move(0, COLS - i - 2) != ERR)
547	    printw("  %s", temp);
548    }
549
550    scrollok(stdscr, FALSE);	/* prevent screen from moving */
551    for (i = 1; i < LINES; i++) {
552	move(i, 0);
553	printw("%3ld:", (long) (lptr + i - vec_lines));
554	clrtoeol();
555	if ((s = lptr[i - 1]) != 0) {
556	    int len = ch_len(s);
557	    if (len > shift) {
558#if USE_WIDEC_SUPPORT
559		add_wchstr(s + shift);
560#else
561		addchstr(s + shift);
562#endif
563	    }
564#if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT)
565	    if (try_color)
566		wchgat(stdscr, -1, A_NORMAL, my_pair, NULL);
567#endif
568	}
569    }
570    setscrreg(1, LINES - 1);
571    scrollok(stdscr, TRUE);
572    refresh();
573}
574