76a88a3096aeea8ac99f8b4721e3198048f724ae
[ncurses.git] / test / view.c
1 /****************************************************************************
2  * Copyright (c) 1998-2011,2012 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28 /*
29  * view.c -- a silly little viewer program
30  *
31  * written by Eric S. Raymond <esr@snark.thyrsus.com> December 1994
32  * to test the scrolling code in ncurses.
33  *
34  * modified by Thomas Dickey <dickey@clark.net> July 1995 to demonstrate
35  * the use of 'resizeterm()', and May 2000 to illustrate wide-character
36  * handling.
37  *
38  * Takes a filename argument.  It's a simple file-viewer with various
39  * scroll-up and scroll-down commands.
40  *
41  * n    -- scroll one line forward
42  * p    -- scroll one line back
43  *
44  * Either command accepts a numeric prefix interpreted as a repeat count.
45  * Thus, typing `5n' should scroll forward 5 lines in the file.
46  *
47  * The way you can tell this is working OK is that, in the trace file,
48  * there should be one scroll operation plus a small number of line
49  * updates, as opposed to a whole-page update.  This means the physical
50  * scroll operation worked, and the refresh() code only had to do a
51  * partial repaint.
52  *
53  * $Id: view.c,v 1.89 2012/12/29 22:38:28 tom Exp $
54  */
55
56 #include <test.priv.h>
57 #include <widechars.h>
58
59 #include <time.h>
60
61 #undef CTRL                     /* conflict on AIX 5.2 with <sys/ioctl.h> */
62
63 #if HAVE_TERMIOS_H
64 # include <termios.h>
65 #else
66 #if !defined(__MINGW32__)
67 # include <sgtty.h>
68 #endif
69 #endif
70
71 #if !defined(sun) || !HAVE_TERMIOS_H
72 # if HAVE_SYS_IOCTL_H
73 #  include <sys/ioctl.h>
74 # endif
75 #endif
76
77 #define my_pair 1
78
79 /* This is needed to compile 'struct winsize' */
80 #if NEED_PTEM_H
81 #include <sys/stream.h>
82 #include <sys/ptem.h>
83 #endif
84
85 static RETSIGTYPE finish(int sig) GCC_NORETURN;
86 static void show_all(const char *tag);
87
88 #if defined(SIGWINCH) && defined(TIOCGWINSZ) && HAVE_RESIZE_TERM
89 #define CAN_RESIZE 1
90 #else
91 #define CAN_RESIZE 0
92 #endif
93
94 #if CAN_RESIZE
95 static RETSIGTYPE adjust(int sig);
96 static int interrupted;
97 static bool waiting = FALSE;
98 #endif
99
100 static int shift = 0;
101 static bool try_color = FALSE;
102
103 static char *fname;
104 static NCURSES_CH_T **vec_lines;
105 static NCURSES_CH_T **lptr;
106 static int num_lines;
107
108 static void usage(void) GCC_NORETURN;
109
110 static void
111 usage(void)
112 {
113     static const char *msg[] =
114     {
115         "Usage: view [options] file"
116         ,""
117         ,"Options:"
118         ," -c       use color if terminal supports it"
119         ," -i       ignore INT, QUIT, TERM signals"
120         ," -n NUM   specify maximum number of lines (default 1000)"
121 #if defined(KEY_RESIZE)
122         ," -r       use old-style sigwinch handler rather than KEY_RESIZE"
123 #endif
124 #ifdef TRACE
125         ," -t       trace screen updates"
126         ," -T NUM   specify trace mask"
127 #endif
128     };
129     size_t n;
130     for (n = 0; n < SIZEOF(msg); n++)
131         fprintf(stderr, "%s\n", msg[n]);
132     ExitProgram(EXIT_FAILURE);
133 }
134
135 static int
136 ch_len(NCURSES_CH_T * src)
137 {
138     int result = 0;
139 #if USE_WIDEC_SUPPORT
140     int count;
141 #endif
142
143 #if USE_WIDEC_SUPPORT
144     for (;;) {
145         TEST_CCHAR(src, count, {
146             ++result;
147             ++src;
148         }
149         , {
150             break;
151         })
152     }
153 #else
154     while (*src++)
155         result++;
156 #endif
157     return result;
158 }
159
160 /*
161  * Allocate a string into an array of chtype's.  If UTF-8 mode is
162  * active, translate the string accordingly.
163  */
164 static NCURSES_CH_T *
165 ch_dup(char *src)
166 {
167     unsigned len = (unsigned) strlen(src);
168     NCURSES_CH_T *dst = typeMalloc(NCURSES_CH_T, len + 1);
169     size_t j, k;
170 #if USE_WIDEC_SUPPORT
171     wchar_t wstr[CCHARW_MAX + 1];
172     wchar_t wch;
173     int l = 0;
174     size_t rc;
175     int width;
176 #ifndef state_unused
177     mbstate_t state;
178 #endif
179 #endif /* USE_WIDEC_SUPPORT */
180
181 #if USE_WIDEC_SUPPORT
182     reset_mbytes(state);
183 #endif
184     for (j = k = 0; j < len; j++) {
185 #if USE_WIDEC_SUPPORT
186         rc = (size_t) check_mbytes(wch, src + j, len - j, state);
187         if (rc == (size_t) -1 || rc == (size_t) -2)
188             break;
189         j += rc - 1;
190         if ((width = wcwidth(wch)) < 0)
191             break;
192         if ((width > 0 && l > 0) || l == CCHARW_MAX) {
193             wstr[l] = L'\0';
194             l = 0;
195             if (setcchar(dst + k, wstr, 0, 0, NULL) != OK)
196                 break;
197             ++k;
198         }
199         if (width == 0 && l == 0)
200             wstr[l++] = L' ';
201         wstr[l++] = wch;
202 #else
203         dst[k++] = (chtype) UChar(src[j]);
204 #endif
205     }
206 #if USE_WIDEC_SUPPORT
207     if (l > 0) {
208         wstr[l] = L'\0';
209         if (setcchar(dst + k, wstr, 0, 0, NULL) == OK)
210             ++k;
211     }
212     wstr[0] = L'\0';
213     setcchar(dst + k, wstr, 0, 0, NULL);
214 #else
215     dst[k] = 0;
216 #endif
217     return dst;
218 }
219
220 int
221 main(int argc, char *argv[])
222 {
223     int MAXLINES = 1000;
224     FILE *fp;
225     char buf[BUFSIZ];
226     int i;
227     int my_delay = 0;
228     NCURSES_CH_T **olptr;
229     int value = 0;
230     bool done = FALSE;
231     bool got_number = FALSE;
232 #if CAN_RESIZE
233     bool nonposix_resize = FALSE;
234 #endif
235     const char *my_label = "Input";
236
237     setlocale(LC_ALL, "");
238
239 #ifndef NCURSES_VERSION
240     /*
241      * We know ncurses will catch SIGINT if we don't establish our own handler.
242      * Other versions of curses may/may not catch it.
243      */
244     (void) signal(SIGINT, finish);      /* arrange interrupts to terminate */
245 #endif
246
247     while ((i = getopt(argc, argv, "cin:rtT:")) != -1) {
248         switch (i) {
249         case 'c':
250             try_color = TRUE;
251             break;
252         case 'i':
253             CATCHALL(SIG_IGN);
254             break;
255         case 'n':
256             if ((MAXLINES = atoi(optarg)) < 1 ||
257                 (MAXLINES + 2) <= 1)
258                 usage();
259             break;
260 #if CAN_RESIZE
261         case 'r':
262             nonposix_resize = TRUE;
263             break;
264 #endif
265 #ifdef TRACE
266         case 'T':
267             {
268                 char *next = 0;
269                 int tvalue = strtol(optarg, &next, 0);
270                 if (tvalue < 0 || (next != 0 && *next != 0))
271                     usage();
272                 trace((unsigned) tvalue);
273             }
274             break;
275         case 't':
276             trace(TRACE_CALLS);
277             break;
278 #endif
279         default:
280             usage();
281         }
282     }
283     if (optind + 1 != argc)
284         usage();
285
286     if ((vec_lines = typeCalloc(NCURSES_CH_T *, (size_t) MAXLINES + 2)) == 0)
287         usage();
288
289     assert(vec_lines != 0);
290
291     fname = argv[optind];
292     if ((fp = fopen(fname, "r")) == 0) {
293         perror(fname);
294         ExitProgram(EXIT_FAILURE);
295     }
296 #if CAN_RESIZE
297     if (nonposix_resize)
298         (void) signal(SIGWINCH, adjust);        /* arrange interrupts to resize */
299 #endif
300
301     Trace(("slurp the file"));
302     for (lptr = &vec_lines[0]; (lptr - vec_lines) < MAXLINES; lptr++) {
303         char temp[BUFSIZ], *s, *d;
304         int col;
305
306         if (fgets(buf, sizeof(buf), fp) == 0)
307             break;
308
309 #if USE_WIDEC_SUPPORT
310         if (lptr == vec_lines) {
311             if (!memcmp("", buf, 3)) {
312                 Trace(("trim BOM"));
313                 s = buf + 3;
314                 d = buf;
315                 do {
316                 } while ((*d++ = *s++) != '\0');
317             }
318         }
319 #endif
320
321         /* convert tabs and nonprinting chars so that shift will work properly */
322         for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) {
323             if (*d == '\r') {
324                 if (s[1] == '\n')
325                     continue;
326                 else
327                     break;
328             }
329             if (*d == '\n') {
330                 *d = '\0';
331                 break;
332             } else if (*d == '\t') {
333                 col = (col | 7) + 1;
334                 while ((d - temp) != col)
335                     *d++ = ' ';
336             } else
337 #if USE_WIDEC_SUPPORT
338                 col++, d++;
339 #else
340             if (isprint(UChar(*d))) {
341                 col++;
342                 d++;
343             } else {
344                 sprintf(d, "\\%03o", UChar(*s));
345                 d += strlen(d);
346                 col = (int) (d - temp);
347             }
348 #endif
349         }
350         *lptr = ch_dup(temp);
351     }
352     (void) fclose(fp);
353     num_lines = (int) (lptr - vec_lines);
354
355     (void) initscr();           /* initialize the curses library */
356     keypad(stdscr, TRUE);       /* enable keyboard mapping */
357     (void) nonl();              /* tell curses not to do NL->CR/NL on output */
358     (void) cbreak();            /* take input chars one at a time, no wait for \n */
359     (void) noecho();            /* don't echo input */
360     nodelay(stdscr, TRUE);
361     idlok(stdscr, TRUE);        /* allow use of insert/delete line */
362
363     if (try_color) {
364         if (has_colors()) {
365             start_color();
366             init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
367             bkgd((chtype) COLOR_PAIR(my_pair));
368         } else {
369             try_color = FALSE;
370         }
371     }
372
373     lptr = vec_lines;
374     while (!done) {
375         int n, c;
376
377         if (!got_number)
378             show_all(my_label);
379
380         for (;;) {
381 #if CAN_RESIZE
382             if (interrupted) {
383                 adjust(0);
384                 my_label = "interrupt";
385             }
386             waiting = TRUE;
387             c = getch();
388             waiting = FALSE;
389 #else
390             c = getch();
391 #endif
392             if ((c < 127) && isdigit(c)) {
393                 if (!got_number) {
394                     MvPrintw(0, 0, "Count: ");
395                     clrtoeol();
396                 }
397                 addch(UChar(c));
398                 value = 10 * value + (c - '0');
399                 got_number = TRUE;
400             } else
401                 break;
402         }
403         if (got_number && value) {
404             n = value;
405         } else {
406             n = 1;
407         }
408
409         if (c != ERR)
410             my_label = keyname(c);
411         switch (c) {
412         case KEY_DOWN:
413         case 'n':
414             olptr = lptr;
415             for (i = 0; i < n; i++)
416                 if ((lptr - vec_lines) < (num_lines - LINES + 1))
417                     lptr++;
418                 else
419                     break;
420             scrl((int) (lptr - olptr));
421             break;
422
423         case KEY_UP:
424         case 'p':
425             olptr = lptr;
426             for (i = 0; i < n; i++)
427                 if (lptr > vec_lines)
428                     lptr--;
429                 else
430                     break;
431             scrl((int) (lptr - olptr));
432             break;
433
434         case 'h':
435         case KEY_HOME:
436             lptr = vec_lines;
437             break;
438
439         case 'e':
440         case KEY_END:
441             if (num_lines > LINES)
442                 lptr = vec_lines + num_lines - LINES + 1;
443             else
444                 lptr = vec_lines;
445             break;
446
447         case 'r':
448         case KEY_RIGHT:
449             shift += n;
450             break;
451
452         case 'l':
453         case KEY_LEFT:
454             shift -= n;
455             if (shift < 0) {
456                 shift = 0;
457                 beep();
458             }
459             break;
460
461         case 'q':
462             done = TRUE;
463             break;
464
465 #ifdef KEY_RESIZE
466         case KEY_RESIZE:        /* ignore this; ncurses will repaint */
467             break;
468 #endif
469         case 's':
470             if (got_number) {
471                 halfdelay(my_delay = n);
472             } else {
473                 nodelay(stdscr, FALSE);
474                 my_delay = -1;
475             }
476             break;
477         case ' ':
478             nodelay(stdscr, TRUE);
479             my_delay = 0;
480             break;
481         case ERR:
482             if (!my_delay)
483                 napms(50);
484             break;
485         default:
486             beep();
487             break;
488         }
489         if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
490             got_number = FALSE;
491             value = 0;
492         }
493     }
494
495     finish(0);                  /* we're done */
496 }
497
498 static RETSIGTYPE
499 finish(int sig)
500 {
501     endwin();
502 #if NO_LEAKS
503     if (vec_lines != 0) {
504         int n;
505         for (n = 0; n < num_lines; ++n) {
506             free(vec_lines[n]);
507         }
508         free(vec_lines);
509     }
510 #endif
511     ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
512 }
513
514 #if CAN_RESIZE
515 /*
516  * This uses functions that are "unsafe", but it seems to work on SunOS. 
517  * Usually: the "unsafe" refers to the functions that POSIX lists which may be
518  * called from a signal handler.  Those do not include buffered I/O, which is
519  * used for instance in wrefresh().  To be really portable, you should use the
520  * KEY_RESIZE return (which relies on ncurses' sigwinch handler).
521  *
522  * The 'wrefresh(curscr)' is needed to force the refresh to start from the top
523  * of the screen -- some xterms mangle the bitmap while resizing.
524  */
525 static RETSIGTYPE
526 adjust(int sig)
527 {
528     if (waiting || sig == 0) {
529         struct winsize size;
530
531         if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
532             resize_term(size.ws_row, size.ws_col);
533             wrefresh(curscr);
534             show_all(sig ? "SIGWINCH" : "interrupt");
535         }
536         interrupted = FALSE;
537     } else {
538         interrupted = TRUE;
539     }
540     (void) signal(SIGWINCH, adjust);    /* some systems need this */
541 }
542 #endif /* CAN_RESIZE */
543
544 static void
545 show_all(const char *tag)
546 {
547     int i;
548     char temp[BUFSIZ];
549     NCURSES_CH_T *s;
550     time_t this_time;
551
552 #if CAN_RESIZE
553     sprintf(temp, "%.20s (%3dx%3d) col %d ", tag, LINES, COLS, shift);
554     i = (int) strlen(temp);
555     if ((i + 7) < (int) sizeof(temp)) {
556         sprintf(temp + i, "view %.*s",
557                 (int) (sizeof(temp) - 7 - (size_t) i),
558                 fname);
559     }
560 #else
561     (void) tag;
562     sprintf(temp, "view %.*s", (int) sizeof(temp) - 7, fname);
563 #endif
564     move(0, 0);
565     printw("%.*s", COLS, temp);
566     clrtoeol();
567     this_time = time((time_t *) 0);
568     strncpy(temp, ctime(&this_time), 30);
569     if ((i = (int) strlen(temp)) != 0) {
570         temp[--i] = 0;
571         if (move(0, COLS - i - 2) != ERR)
572             printw("  %s", temp);
573     }
574
575     scrollok(stdscr, FALSE);    /* prevent screen from moving */
576     for (i = 1; i < LINES; i++) {
577         move(i, 0);
578         printw("%3ld:", (long) (lptr + i - vec_lines));
579         clrtoeol();
580         if ((s = lptr[i - 1]) != 0) {
581             int len = ch_len(s);
582             if (len > shift) {
583 #if USE_WIDEC_SUPPORT
584                 add_wchstr(s + shift);
585 #else
586                 addchstr(s + shift);
587 #endif
588             }
589 #if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT)
590             if (try_color)
591                 wchgat(stdscr, -1, A_NORMAL, my_pair, NULL);
592 #endif
593         }
594     }
595     setscrreg(1, LINES - 1);
596     scrollok(stdscr, TRUE);
597     refresh();
598 }