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