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