ncurses 5.5
[ncurses.git] / test / view.c
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.62 2005/05/28 21:40:25 tom Exp $
27  */
28
29 #include <test.priv.h>
30
31 #include <time.h>
32
33 #undef CTRL                     /* conflict on AIX 5.2 with <sys/ioctl.h> */
34
35 #if HAVE_TERMIOS_H
36 # include <termios.h>
37 #else
38 # include <sgtty.h>
39 #endif
40
41 #if !defined(sun) || !HAVE_TERMIOS_H
42 # if HAVE_SYS_IOCTL_H
43 #  include <sys/ioctl.h>
44 # endif
45 #endif
46
47 #define my_pair 1
48
49 /* This is needed to compile 'struct winsize' */
50 #if NEED_PTEM_H
51 #include <sys/stream.h>
52 #include <sys/ptem.h>
53 #endif
54
55 static RETSIGTYPE finish(int sig) GCC_NORETURN;
56 static void show_all(const char *tag);
57
58 #if defined(SIGWINCH) && defined(TIOCGWINSZ) && HAVE_RESIZE_TERM
59 #define CAN_RESIZE 1
60 #else
61 #define CAN_RESIZE 0
62 #endif
63
64 #if CAN_RESIZE
65 static RETSIGTYPE adjust(int sig);
66 static int interrupted;
67 #endif
68
69 static bool waiting = FALSE;
70 static int shift = 0;
71 static bool try_color = FALSE;
72
73 static char *fname;
74 static NCURSES_CH_T **vec_lines;
75 static NCURSES_CH_T **lptr;
76 static int num_lines;
77
78 static void
79 usage(void)
80 {
81     static const char *msg[] =
82     {
83         "Usage: view [options] file"
84         ,""
85         ,"Options:"
86         ," -c       use color if terminal supports it"
87         ," -i       ignore INT, QUIT, TERM signals"
88         ," -n NUM   specify maximum number of lines (default 1000)"
89 #if defined(KEY_RESIZE)
90         ," -r       use old-style sigwinch handler rather than KEY_RESIZE"
91 #endif
92 #ifdef TRACE
93         ," -t       trace screen updates"
94         ," -T NUM   specify trace mask"
95 #endif
96     };
97     size_t n;
98     for (n = 0; n < SIZEOF(msg); n++)
99         fprintf(stderr, "%s\n", msg[n]);
100     ExitProgram(EXIT_FAILURE);
101 }
102
103 static int
104 ch_len(NCURSES_CH_T * src)
105 {
106     int result = 0;
107 #if USE_WIDEC_SUPPORT
108 #endif
109
110 #if USE_WIDEC_SUPPORT
111     while (getcchar(src++, NULL, NULL, NULL, NULL) > 0)
112         result++;
113 #else
114     while (*src++)
115         result++;
116 #endif
117     return result;
118 }
119
120 /*
121  * Allocate a string into an array of chtype's.  If UTF-8 mode is
122  * active, translate the string accordingly.
123  */
124 static NCURSES_CH_T *
125 ch_dup(char *src)
126 {
127     unsigned len = strlen(src);
128     NCURSES_CH_T *dst = typeMalloc(NCURSES_CH_T, len + 1);
129     unsigned j, k;
130 #if USE_WIDEC_SUPPORT
131     wchar_t wstr[CCHARW_MAX + 1];
132     wchar_t wch;
133     int l = 0;
134     mbstate_t state;
135     size_t rc;
136     int width;
137 #endif
138
139 #if USE_WIDEC_SUPPORT
140     memset(&state, 0, sizeof(state));
141 #endif
142     for (j = k = 0; j < len; j++) {
143 #if USE_WIDEC_SUPPORT
144         rc = mbrtowc(&wch, src + j, len - j, &state);
145         if (rc == (size_t) -1 || rc == (size_t) -2)
146             break;
147         j += rc - 1;
148         if ((width = wcwidth(wch)) < 0)
149             break;
150         if ((width > 0 && l > 0) || l == CCHARW_MAX) {
151             wstr[l] = L'\0';
152             l = 0;
153             if (setcchar(dst + k, wstr, 0, 0, NULL) != OK)
154                 break;
155             ++k;
156         }
157         if (width == 0 && l == 0)
158             wstr[l++] = L' ';
159         wstr[l++] = wch;
160 #else
161         dst[k++] = src[j];
162 #endif
163     }
164 #if USE_WIDEC_SUPPORT
165     if (l > 0) {
166         wstr[l] = L'\0';
167         if (setcchar(dst + k, wstr, 0, 0, NULL) == OK)
168             ++k;
169     }
170     wstr[0] = L'\0';
171     setcchar(dst + k, wstr, 0, 0, NULL);
172 #else
173     dst[k] = 0;
174 #endif
175     return dst;
176 }
177
178 int
179 main(int argc, char *argv[])
180 {
181     int MAXLINES = 1000;
182     FILE *fp;
183     char buf[BUFSIZ];
184     int i;
185     int my_delay = 0;
186     NCURSES_CH_T **olptr;
187     int value = 0;
188     bool done = FALSE;
189     bool got_number = FALSE;
190 #if CAN_RESIZE
191     bool nonposix_resize = FALSE;
192 #endif
193     const char *my_label = "Input";
194
195     setlocale(LC_ALL, "");
196
197 #ifndef NCURSES_VERSION
198     /*
199      * We know ncurses will catch SIGINT if we don't establish our own handler.
200      * Other versions of curses may/may not catch it.
201      */
202     (void) signal(SIGINT, finish);      /* arrange interrupts to terminate */
203 #endif
204
205     while ((i = getopt(argc, argv, "cin:rtT:")) != EOF) {
206         switch (i) {
207         case 'c':
208             try_color = TRUE;
209             break;
210         case 'i':
211             signal(SIGINT, SIG_IGN);
212             signal(SIGQUIT, SIG_IGN);
213             signal(SIGTERM, SIG_IGN);
214             break;
215         case 'n':
216             if ((MAXLINES = atoi(optarg)) < 1)
217                 usage();
218             break;
219 #if CAN_RESIZE
220         case 'r':
221             nonposix_resize = TRUE;
222             break;
223 #endif
224 #ifdef TRACE
225         case 'T':
226             trace((unsigned) atoi(optarg));
227             break;
228         case 't':
229             trace(TRACE_CALLS);
230             break;
231 #endif
232         default:
233             usage();
234         }
235     }
236     if (optind + 1 != argc)
237         usage();
238
239     if ((vec_lines = typeMalloc(NCURSES_CH_T *, MAXLINES + 2)) == 0)
240         usage();
241
242     fname = argv[optind];
243     if ((fp = fopen(fname, "r")) == 0) {
244         perror(fname);
245         ExitProgram(EXIT_FAILURE);
246     }
247 #if CAN_RESIZE
248     if (nonposix_resize)
249         (void) signal(SIGWINCH, adjust);        /* arrange interrupts to resize */
250 #endif
251
252     /* slurp the file */
253     for (lptr = &vec_lines[0]; (lptr - vec_lines) < MAXLINES; lptr++) {
254         char temp[BUFSIZ], *s, *d;
255         int col;
256
257         if (fgets(buf, sizeof(buf), fp) == 0)
258             break;
259
260         /* convert tabs so that shift will work properly */
261         for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) {
262             if (*d == '\n') {
263                 *d = '\0';
264                 break;
265             } else if (*d == '\t') {
266                 col = (col | 7) + 1;
267                 while ((d - temp) != col)
268                     *d++ = ' ';
269             } else
270 #if USE_WIDEC_SUPPORT
271                 col++, d++;
272 #else
273             if (isprint(UChar(*d))) {
274                 col++;
275                 d++;
276             } else {
277                 sprintf(d, "\\%03o", UChar(*s));
278                 d += strlen(d);
279                 col = (d - temp);
280             }
281 #endif
282         }
283         *lptr = ch_dup(temp);
284     }
285     (void) fclose(fp);
286     num_lines = lptr - vec_lines;
287
288     (void) initscr();           /* initialize the curses library */
289     keypad(stdscr, TRUE);       /* enable keyboard mapping */
290     (void) nonl();              /* tell curses not to do NL->CR/NL on output */
291     (void) cbreak();            /* take input chars one at a time, no wait for \n */
292     (void) noecho();            /* don't echo input */
293     nodelay(stdscr, TRUE);
294     idlok(stdscr, TRUE);        /* allow use of insert/delete line */
295
296     if (try_color) {
297         if (has_colors()) {
298             start_color();
299             init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
300             bkgd(COLOR_PAIR(my_pair));
301         } else {
302             try_color = FALSE;
303         }
304     }
305
306     lptr = vec_lines;
307     while (!done) {
308         int n, c;
309
310         if (!got_number)
311             show_all(my_label);
312
313         n = 0;
314         for (;;) {
315 #if CAN_RESIZE
316             if (interrupted) {
317                 adjust(0);
318                 my_label = "interrupt";
319             }
320 #endif
321             waiting = TRUE;
322             c = getch();
323             waiting = FALSE;
324             if ((c < 127) && isdigit(c)) {
325                 if (!got_number) {
326                     mvprintw(0, 0, "Count: ");
327                     clrtoeol();
328                 }
329                 addch(UChar(c));
330                 value = 10 * value + (c - '0');
331                 got_number = TRUE;
332             } else
333                 break;
334         }
335         if (got_number && value) {
336             n = value;
337         } else {
338             n = 1;
339         }
340
341         if (c != ERR)
342             my_label = keyname(c);
343         switch (c) {
344         case KEY_DOWN:
345         case 'n':
346             olptr = lptr;
347             for (i = 0; i < n; i++)
348                 if ((lptr - vec_lines) < (num_lines - LINES + 1))
349                     lptr++;
350                 else
351                     break;
352             wscrl(stdscr, lptr - olptr);
353             break;
354
355         case KEY_UP:
356         case 'p':
357             olptr = lptr;
358             for (i = 0; i < n; i++)
359                 if (lptr > vec_lines)
360                     lptr--;
361                 else
362                     break;
363             wscrl(stdscr, lptr - olptr);
364             break;
365
366         case 'h':
367         case KEY_HOME:
368             lptr = vec_lines;
369             break;
370
371         case 'e':
372         case KEY_END:
373             if (num_lines > LINES)
374                 lptr = vec_lines + num_lines - LINES + 1;
375             else
376                 lptr = vec_lines;
377             break;
378
379         case 'r':
380         case KEY_RIGHT:
381             shift += n;
382             break;
383
384         case 'l':
385         case KEY_LEFT:
386             shift -= n;
387             if (shift < 0) {
388                 shift = 0;
389                 beep();
390             }
391             break;
392
393         case 'q':
394             done = TRUE;
395             break;
396
397 #ifdef KEY_RESIZE
398         case KEY_RESIZE:        /* ignore this; ncurses will repaint */
399             break;
400 #endif
401         case 's':
402             if (got_number) {
403                 halfdelay(my_delay = n);
404             } else {
405                 nodelay(stdscr, FALSE);
406                 my_delay = -1;
407             }
408             break;
409         case ' ':
410             nodelay(stdscr, TRUE);
411             my_delay = 0;
412             break;
413         case ERR:
414             if (!my_delay)
415                 napms(50);
416             break;
417         default:
418             beep();
419             break;
420         }
421         if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
422             got_number = FALSE;
423             value = 0;
424         }
425     }
426
427     finish(0);                  /* we're done */
428 }
429
430 static RETSIGTYPE
431 finish(int sig)
432 {
433     endwin();
434 #if NO_LEAKS
435     if (vec_lines != 0) {
436         int n;
437         for (n = 0; n < num_lines; ++n) {
438             free(vec_lines[n]);
439         }
440         free(vec_lines);
441     }
442 #endif
443     ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
444 }
445
446 #if CAN_RESIZE
447 /*
448  * This uses functions that are "unsafe", but it seems to work on SunOS and
449  * Linux.  Usually:  the "unsafe" refers to the functions that POSIX lists
450  * which may be called from a signal handler.  Those do not include buffered
451  * I/O, which is used for instance in wrefresh().  To be really portable, you
452  * should use the KEY_RESIZE return (which relies on ncurses' sigwinch
453  * handler).
454  *
455  * The 'wrefresh(curscr)' is needed to force the refresh to start from the top
456  * of the screen -- some xterms mangle the bitmap while resizing.
457  */
458 static RETSIGTYPE
459 adjust(int sig)
460 {
461     if (waiting || sig == 0) {
462         struct winsize size;
463
464         if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
465             resize_term(size.ws_row, size.ws_col);
466             wrefresh(curscr);   /* Linux needs this */
467             show_all(sig ? "SIGWINCH" : "interrupt");
468         }
469         interrupted = FALSE;
470     } else {
471         interrupted = TRUE;
472     }
473     (void) signal(SIGWINCH, adjust);    /* some systems need this */
474 }
475 #endif /* CAN_RESIZE */
476
477 static void
478 show_all(const char *tag)
479 {
480     int i;
481     char temp[BUFSIZ];
482     NCURSES_CH_T *s;
483     time_t this_time;
484
485 #if CAN_RESIZE
486     sprintf(temp, "%s (%3dx%3d) col %d ", tag, LINES, COLS, shift);
487     i = strlen(temp);
488     sprintf(temp + i, "view %.*s", (int) (sizeof(temp) - 7 - i), fname);
489 #else
490     sprintf(temp, "view %.*s", (int) sizeof(temp) - 7, fname);
491 #endif
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 - vec_lines));
507         clrtoeol();
508         if ((s = lptr[i - 1]) != 0) {
509             int len = ch_len(s);
510             if (len > shift) {
511 #if USE_WIDEC_SUPPORT
512                 add_wchstr(s + shift);
513 #else
514                 addchstr(s + shift);
515 #endif
516             }
517 #if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT)
518             if (try_color)
519                 wchgat(stdscr, -1, A_NORMAL, my_pair, NULL);
520 #endif
521         }
522     }
523     setscrreg(1, LINES - 1);
524     scrollok(stdscr, TRUE);
525     refresh();
526 }