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