xref: /netbsd/lib/libcurses/EXAMPLES/view.c (revision 17c16404)
1 /*
2  * view.c -- a silly little viewer program
3  *
4  * written by Eric S. Raymond <esr@snark.thyrsus.com> December 1994
5  * to test the scrolling code in ncurses.
6  *
7  * modified by Thomas Dickey <dickey@clark.net> July 1995 to demonstrate
8  * the use of 'resizeterm()', and May 2000 to illustrate wide-character
9  * handling.
10  *
11  * Takes a filename argument.  It's a simple file-viewer with various
12  * scroll-up and scroll-down commands.
13  *
14  * n	-- scroll one line forward
15  * p	-- scroll one line back
16  *
17  * Either command accepts a numeric prefix interpreted as a repeat count.
18  * Thus, typing `5n' should scroll forward 5 lines in the file.
19  *
20  * The way you can tell this is working OK is that, in the trace file,
21  * there should be one scroll operation plus a small number of line
22  * updates, as opposed to a whole-page update.  This means the physical
23  * scroll operation worked, and the refresh() code only had to do a
24  * partial repaint.
25  *
26  * $Id: view.c,v 1.1 2007/01/21 11:38:59 blymn Exp $
27  */
28 
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <signal.h>
33 #ifdef NCURSES
34 #define _XOPEN_SOURCE_EXTENDED
35 #include <ncurses.h>
36 #include <term.h>
37 #else
38 #include <curses.h>
39 #endif /* NCURSES */
40 #include <locale.h>
41 #include <assert.h>
42 #include <ctype.h>
43 #include <termios.h>
44 #include <util.h>
45 #include <unistd.h>
46 #ifdef HAVE_WCHAR
47 #include <wchar.h>
48 #endif /* HAVE_WCHAR */
49 #ifdef DEBUG
50 #include <syslog.h>
51 #endif /* DEBUG */
52 
53 #define UChar(c)    ((unsigned char)(c))
54 #define SIZEOF(table)	(sizeof(table)/sizeof(table[0]))
55 #define typeMalloc(type,n) (type *) malloc((n) * sizeof(type))
56 
57 #define my_pair 1
58 
59 #undef CURSES_CH_T
60 #ifdef HAVE_WCHAR
61 #define CURSES_CH_T cchar_t
62 #else
63 #define CURSES_CH_T chtype
64 #endif /* HAVE_WCHAR */
65 
66 static void finish(int sig);
67 static void show_all(const char *tag);
68 
69 static int shift = 0;
70 static bool try_color = FALSE;
71 
72 static char *fname;
73 static CURSES_CH_T **my_lines;
74 static CURSES_CH_T **lptr;
75 
76 static void usage(void)
77 {
78     static const char *msg[] = {
79 	    "Usage: view [options] file"
80 	    ,""
81 	    ,"Options:"
82 	    ," -c       use color if terminal supports it"
83 	    ," -i       ignore INT, QUIT, TERM signals"
84 	    ," -n NUM   specify maximum number of lines (default 1000)"
85 #if defined(KEY_RESIZE)
86 	    ," -r       use old-style sigwinch handler rather than KEY_RESIZE"
87 #endif
88 #ifdef TRACE
89 	    ," -t       trace screen updates"
90 	    ," -T NUM   specify trace mask"
91 #endif
92     };
93     size_t n;
94     for (n = 0; n < SIZEOF(msg); n++)
95 	    fprintf(stderr, "%s\n", msg[n]);
96     exit( 1 );
97 }
98 
99 static int ch_len(CURSES_CH_T * src)
100 {
101     int result = 0;
102 
103 #ifdef HAVE_WCHAR
104     while (getcchar(src++, NULL, NULL, NULL, NULL) > 0)
105 	    result++;
106 #else
107     while (*src++)
108 	result++;
109 #endif
110     return result;
111 }
112 
113 /*
114  * Allocate a string into an array of chtype's.  If UTF-8 mode is
115  * active, translate the string accordingly.
116  */
117 static CURSES_CH_T * ch_dup(char *src)
118 {
119     unsigned len = strlen(src);
120     CURSES_CH_T *dst = typeMalloc(CURSES_CH_T, len + 1);
121     unsigned j, k;
122 #ifdef HAVE_WCHAR
123     wchar_t wstr[CCHARW_MAX + 1];
124     wchar_t wch;
125     int l = 0;
126     mbstate_t state;
127     size_t rc;
128     int width;
129 #endif
130 
131 #ifdef HAVE_WCHAR
132     mbrtowc( NULL, NULL, 1, &state );
133 #endif
134     for (j = k = 0; j < len; j++) {
135 #ifdef HAVE_WCHAR
136 	    rc = mbrtowc(&wch, src + j, len - j, &state);
137 #ifdef DEBUG
138         syslog( LOG_INFO, "[ch_dup]mbrtowc() returns %d", rc );
139 #endif /* DEBUG */
140 	    if (rc == (size_t) -1 || rc == (size_t) -2)
141 	        break;
142 	    j += rc - 1;
143 	    if ((width = wcwidth(wch)) < 0)
144 	        break;
145 	    if ((width > 0 && l > 0) || l == CCHARW_MAX) {
146 	        wstr[l] = L'\0';
147 	        l = 0;
148 	        if (setcchar(dst + k, wstr, 0, 0, NULL) != OK)
149 		        break;
150 	        ++k;
151 	    }
152 	    if (width == 0 && l == 0)
153 	        wstr[l++] = L' ';
154 	    wstr[l++] = wch;
155 #ifdef DEBUG
156         syslog( LOG_INFO, "[ch_dup]wch=%x", wch );
157 #endif /* DEBUG */
158 #else
159 	    dst[k++] = src[j];
160 #endif
161     }
162 #ifdef HAVE_WCHAR
163     if (l > 0) {
164 	    wstr[l] = L'\0';
165 	    if (setcchar(dst + k, wstr, 0, 0, NULL) == OK)
166 	        ++k;
167     }
168     setcchar(dst + k, L"", 0, 0, NULL);
169 #else
170     dst[k] = 0;
171 #endif
172     return dst;
173 }
174 
175 int main(int argc, char *argv[])
176 {
177     int MAXLINES = 1000;
178     FILE *fp;
179     char buf[BUFSIZ];
180     int i;
181     int my_delay = 0;
182     CURSES_CH_T **olptr;
183     int length = 0;
184     int value = 0;
185     bool done = FALSE;
186     bool got_number = FALSE;
187     const char *my_label = "Input";
188 #ifdef HAVE_WCHAR
189     cchar_t icc;
190 #endif /* HAVE_WCHAR */
191 
192     setlocale(LC_ALL, "");
193 
194     (void) signal(SIGINT, finish);	/* arrange interrupts to terminate */
195 
196     while ((i = getopt(argc, argv, "cin:rtT:")) != EOF) {
197 	    switch (i) {
198 	        case 'c':
199 	            try_color = TRUE;
200 	            break;
201 	        case 'i':
202 	            signal(SIGINT, SIG_IGN);
203 	            signal(SIGQUIT, SIG_IGN);
204 	            signal(SIGTERM, SIG_IGN);
205 	            break;
206 	        case 'n':
207 	            if ((MAXLINES = atoi(optarg)) < 1)
208 		        usage();
209 	            break;
210 #ifdef TRACE
211 	        case 'T':
212 	            trace(atoi(optarg));
213 	            break;
214 	        case 't':
215 	            trace(TRACE_CALLS);
216 	            break;
217 #endif
218 	        default:
219 	            usage();
220 	    }
221     }
222     if (optind + 1 != argc)
223 	    usage();
224 
225     if ((my_lines = typeMalloc(CURSES_CH_T *, MAXLINES + 2)) == 0)
226 	    usage();
227 
228     fname = argv[optind];
229     if ((fp = fopen(fname, "r")) == 0) {
230 	    perror(fname);
231 	    exit( 1 );
232     }
233 
234     /* slurp the file */
235     for (lptr = &my_lines[0]; (lptr - my_lines) < MAXLINES; lptr++) {
236 	    char temp[BUFSIZ], *s, *d;
237 	    int col;
238 
239 	    if (fgets(buf, sizeof(buf), fp) == 0)
240 	        break;
241 
242 	    /* convert tabs so that shift will work properly */
243 	    for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) {
244 	        if (*d == '\n') {
245 		        *d = '\0';
246 		        break;
247 	        } else if (*d == '\t') {
248 		        col = (col | 7) + 1;
249 		        while ((d - temp) != col)
250 		            *d++ = ' ';
251 	        } else
252 #ifdef HAVE_WCHAR
253 		        col++, d++;
254 #else
255 	            if (isprint(UChar(*d))) {
256 		            col++;
257 		            d++;
258 	            } else {
259 		            sprintf(d, "\\%03o", UChar(*s));
260 		            d += strlen(d);
261 		            col = (d - temp);
262 	            }
263 #endif
264 	    }
265 	    *lptr = ch_dup(temp);
266     }
267     (void) fclose(fp);
268     length = lptr - my_lines;
269 
270     (void) initscr();		/* initialize the curses library */
271     keypad(stdscr, TRUE);	/* enable keyboard mapping */
272     (void) nonl();	 /* tell curses not to do NL->CR/NL on output */
273     (void) cbreak(); /* take input chars one at a time, no wait for \n */
274     (void) noecho();		/* don't echo input */
275     nodelay(stdscr, TRUE);
276     idlok(stdscr, TRUE);	/* allow use of insert/delete line */
277 
278     if (try_color) {
279 	    if (has_colors()) {
280 	        start_color();
281 	        init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
282 	        bkgd(COLOR_PAIR(my_pair));
283 	    } else {
284 	        try_color = FALSE;
285 	    }
286     }
287 
288     lptr = my_lines;
289     while (!done) {
290 	    int n;
291 #ifdef HAVE_WCHAR
292         wint_t c = 0;
293         int ret;
294 #else
295         int c = 0;
296 #endif /* HAVE_WCHAR */
297 
298 	    if (!got_number)
299 	        show_all(my_label);
300 
301 	    n = 0;
302 	    for (;;) {
303             c = 0;
304 #ifdef HAVE_WCHAR
305             ret = get_wch( &c );
306             if ( ret == ERR ) {
307 	            if (!my_delay)
308 		            napms(50);
309                 continue;
310             }
311 #ifdef DEBUG
312             else if ( ret == KEY_CODE_YES )
313                 syslog( LOG_INFO, "[main]Func key(%x)", c );
314             else
315                 syslog( LOG_INFO, "[main]c=%x", c );
316 #endif /* DEBUG */
317 #else
318 	        c = getch();
319 #ifdef DEBUG
320             syslog( LOG_INFO, "[main]c='%c'", c );
321 #endif /* DEBUG */
322 #endif /* HAVE_WCHAR */
323 	        if ((c < 127) && isdigit(c)) {
324 		        if (!got_number) {
325 		            mvprintw(0, 0, "Count: ");
326 		            clrtoeol();
327 		        }
328 		        addch(c);
329 		        value = 10 * value + (c - '0');
330 		        got_number = TRUE;
331 	        } else
332 		        break;
333 	    }
334 	    if (got_number && value) {
335 	        n = value;
336 	    } else {
337 	        n = 1;
338 	    }
339 
340 #ifdef HAVE_WCHAR
341 	    if (ret != ERR)
342             my_label = key_name( c );
343         else
344 	        if (!my_delay)
345 		        napms(50);
346 #else
347 	    if (c != ERR)
348 	        my_label = keyname(c);
349 #endif /* HAVE_WCHAR */
350 	    switch (c) {
351 	        case KEY_DOWN:
352 #ifdef HAVE_WCHAR
353             case L'n':
354 #else
355 	        case 'n':
356 #endif /* HAVE_WCHAR */
357 	            olptr = lptr;
358 	            for (i = 0; i < n; i++)
359 		            if ((lptr - my_lines) < (length - LINES + 1))
360 		                lptr++;
361 		            else
362 		                break;
363 	            wscrl(stdscr, lptr - olptr);
364 	            break;
365 
366 	        case KEY_UP:
367 #ifdef HAVE_WCHAR
368             case L'p':
369 #else
370 	        case 'p':
371 #endif /* HAVE_WCHAR */
372 	            olptr = lptr;
373 	            for (i = 0; i < n; i++)
374 		            if (lptr > my_lines)
375 		                lptr--;
376 		            else
377 		                break;
378 	            wscrl(stdscr, lptr - olptr);
379 	            break;
380 
381 #ifdef HAVE_WCHAR
382             case L'h':
383 #else
384 	        case 'h':
385 #endif /* HAVE_WCHAR */
386 	        case KEY_HOME:
387 	            lptr = my_lines;
388 	            break;
389 
390 #ifdef HAVE_WCHAR
391             case L'e':
392 #else
393 	        case 'e':
394 #endif /* HAVE_WCHAR */
395 	        case KEY_END:
396 	            if (length > LINES)
397 		            lptr = my_lines + length - LINES + 1;
398 	            else
399 		            lptr = my_lines;
400 	            break;
401 
402 #ifdef HAVE_WCHAR
403             case L'r':
404 #else
405 	        case 'r':
406 #endif /* HAVE_WCHAR */
407 	        case KEY_RIGHT:
408 	            shift += n;
409 	            break;
410 
411 #ifdef HAVE_WCHAR
412             case L'l':
413 #else
414 	        case 'l':
415 #endif /* HAVE_WCHAR */
416 	        case KEY_LEFT:
417 	            shift -= n;
418 	            if (shift < 0) {
419 		            shift = 0;
420 		            beep();
421 	            }
422 	            break;
423 
424 #ifdef HAVE_WCHAR
425             case L'q':
426 #else
427 	        case 'q':
428 #endif /* HAVE_WCHAR */
429 	            done = TRUE;
430 	            break;
431 
432 #ifdef KEY_RESIZE
433 	        case KEY_RESIZE:
434                 //refresh();
435 	            break;
436 #endif
437 #ifdef HAVE_WCHAR
438 	        case L's':
439 #else
440             case 's':
441 #endif /* HAVE_WCHAR */
442 	            if (got_number) {
443 		            halfdelay(my_delay = n);
444 	            } else {
445 		            nodelay(stdscr, FALSE);
446 		            my_delay = -1;
447 	            }
448 	            break;
449 #ifdef HAVE_WCHAR
450             case L' ':
451 #else
452 	        case ' ':
453 #endif /* HAVE_WCHAR */
454 	            nodelay(stdscr, TRUE);
455 	            my_delay = 0;
456 	            break;
457 #ifndef HAVE_WCHAR
458 	        case ERR:
459 	            if (!my_delay)
460 		            napms(50);
461 	            break;
462 #endif /* HAVE_WCHAR */
463 	        default:
464 	            beep();
465 	            break;
466 	    }
467 	    if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
468 	        got_number = FALSE;
469 	        value = 0;
470 	    }
471     }
472 
473     finish(0);			/* we're done */
474 }
475 
476 static void finish(int sig)
477 {
478     endwin();
479     exit(sig != 0 ?  1 : 0 );
480 }
481 
482 static void show_all(const char *tag)
483 {
484     int i;
485     char temp[BUFSIZ];
486     CURSES_CH_T *s;
487     time_t this_time;
488 
489     sprintf(temp, "%s (%3dx%3d) col %d ", tag, LINES, COLS, shift);
490     i = strlen(temp);
491     sprintf(temp + i, "view %.*s", (int) (sizeof(temp) - 7 - i), fname);
492     move(0, 0);
493     printw("%.*s", COLS, temp);
494     clrtoeol();
495     this_time = time((time_t *) 0);
496     strcpy(temp, ctime(&this_time));
497     if ((i = strlen(temp)) != 0) {
498 	    temp[--i] = 0;
499 	    if (move(0, COLS - i - 2) != ERR)
500 	        printw("  %s", temp);
501     }
502 
503     scrollok(stdscr, FALSE);	/* prevent screen from moving */
504     for (i = 1; i < LINES; i++) {
505 	    move(i, 0);
506 	    printw("%3ld:", (long) (lptr + i - my_lines));
507 	    clrtoeol();
508 	    if ((s = lptr[i - 1]) != 0) {
509 	        int len = ch_len(s);
510 	        if (len > shift) {
511 #ifdef HAVE_WCHAR
512 		        add_wchstr(s + shift);
513 #else
514 		        addchstr(s + shift);
515 #endif
516 	        }
517 	    }
518     }
519     setscrreg(1, LINES - 1);
520     scrollok(stdscr, TRUE);
521     refresh();
522 }
523