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