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