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