ncurses 6.0 - patch 20170909
[ncurses.git] / test / gdc.c
1 /****************************************************************************
2  * Copyright (c) 1998-2016,2017 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  * Grand digital clock for curses compatible terminals
30  * Usage: gdc [-s] [-t hh:mm:ss] [n] -- run for n seconds (default infinity)
31  * Flags: -s: scroll
32  *
33  * modified 10-18-89 for curses (jrl)
34  * 10-18-89 added signal handling
35  *
36  * $Id: gdc.c,v 1.50 2017/09/09 20:23:09 tom Exp $
37  */
38
39 #include <test.priv.h>
40
41 #include <time.h>
42
43 #define YBASE   10
44 #define XBASE   10
45 #define XLENGTH 54
46 #define YDEPTH  5
47
48 #define PAIR_DIGITS 1
49 #define PAIR_OTHERS 2
50 #define PAIR_FRAMES 3
51
52 static short disp[11] =
53 {
54     075557, 011111, 071747, 071717, 055711,
55     074717, 074757, 071111, 075757, 075717, 002020
56 };
57 static long older[6], next[6], newer[6], mask;
58
59 static int sigtermed = 0;
60 static bool redirected = FALSE;
61 static bool hascolor = FALSE;
62
63 static void
64 sighndl(int signo)
65 {
66     signal(signo, sighndl);
67     sigtermed = signo;
68     if (redirected) {
69         exit_curses();
70         ExitProgram(EXIT_FAILURE);
71     }
72 }
73
74 static void
75 check_term(void)
76 {
77     if (sigtermed) {
78         (void) standend();
79         exit_curses();
80         fprintf(stderr, "gdc terminated by signal %d\n", sigtermed);
81         ExitProgram(EXIT_FAILURE);
82     }
83 }
84
85 static void
86 drawbox(bool scrolling)
87 {
88     chtype bottom[XLENGTH + 1];
89     int n;
90
91     if (hascolor)
92         (void) attrset(AttrArg(COLOR_PAIR(PAIR_FRAMES), 0));
93
94     MvAddCh(YBASE - 1, XBASE - 1, ACS_ULCORNER);
95     hline(ACS_HLINE, XLENGTH);
96     MvAddCh(YBASE - 1, XBASE + XLENGTH, ACS_URCORNER);
97
98     MvAddCh(YBASE + YDEPTH, XBASE - 1, ACS_LLCORNER);
99     if ((mvinchnstr(YBASE + YDEPTH, XBASE, bottom, XLENGTH)) != ERR) {
100         for (n = 0; n < XLENGTH; n++) {
101             if (!scrolling)
102                 bottom[n] &= ~A_COLOR;
103             bottom[n] = ACS_HLINE | (bottom[n] & (A_ATTRIBUTES | A_COLOR));
104         }
105         (void) mvaddchnstr(YBASE + YDEPTH, XBASE, bottom, XLENGTH);
106     }
107     MvAddCh(YBASE + YDEPTH, XBASE + XLENGTH, ACS_LRCORNER);
108
109     move(YBASE, XBASE - 1);
110     vline(ACS_VLINE, YDEPTH);
111
112     move(YBASE, XBASE + XLENGTH);
113     vline(ACS_VLINE, YDEPTH);
114
115     if (hascolor)
116         (void) attrset(AttrArg(COLOR_PAIR(PAIR_OTHERS), 0));
117 }
118
119 static void
120 standt(int on)
121 {
122     if (on) {
123         if (hascolor) {
124             attron(COLOR_PAIR(PAIR_DIGITS));
125         } else {
126             attron(A_STANDOUT);
127         }
128     } else {
129         if (hascolor) {
130             attron(COLOR_PAIR(PAIR_OTHERS));
131         } else {
132             attroff(A_STANDOUT);
133         }
134     }
135 }
136
137 static void
138 set(int t, int n)
139 {
140     int i, m;
141
142     m = 7 << n;
143     for (i = 0; i < 5; i++) {
144         next[i] |= ((disp[t] >> ((4 - i) * 3)) & 07) << n;
145         mask |= (next[i] ^ older[i]) & m;
146     }
147     if (mask & m)
148         mask |= m;
149 }
150
151 static void
152 usage(void)
153 {
154     static const char *msg[] =
155     {
156         "Usage: gdc [options] [count]"
157         ,""
158         ,"Options:"
159 #if HAVE_USE_DEFAULT_COLORS
160         ,"  -d       invoke use_default_colors"
161 #endif
162         ,"  -n       redirect input to /dev/null"
163         ,"  -s       scroll each number into place, rather than flipping"
164         ,"  -t hh:mm:ss specify starting time (default is ``now'')"
165         ,""
166         ,"If you specify a count, gdc runs for that number of seconds"
167     };
168     unsigned j;
169     for (j = 0; j < SIZEOF(msg); j++)
170         fprintf(stderr, "%s\n", msg[j]);
171     ExitProgram(EXIT_FAILURE);
172 }
173
174 static time_t
175 parse_time(const char *value)
176 {
177     int hh, mm, ss;
178     int check;
179     time_t result;
180     char c;
181     struct tm *tm;
182
183     if (sscanf(value, "%d:%d:%d%c", &hh, &mm, &ss, &c) != 3) {
184         if (sscanf(value, "%02d%02d%02d%c", &hh, &mm, &ss, &c) != 3) {
185             usage();
186         }
187     }
188
189     if ((hh < 0) || (hh >= 24) ||
190         (mm < 0) || (mm >= 60) ||
191         (ss < 0) || (ss >= 60)) {
192         usage();
193     }
194
195     /* adjust so that the localtime in the main loop will give usable time */
196     result = (hh * 3600) + ((mm * 60) + ss);
197     for (check = 0; check < 24; ++check) {
198         tm = localtime(&result);
199         if (tm->tm_hour == hh)
200             break;
201         result += 3600;
202     }
203
204     if (tm->tm_hour != hh) {
205         fprintf(stderr, "Cannot find local time for %s!\n", value);
206         usage();
207     }
208     return result;
209 }
210
211 int
212 main(int argc, char *argv[])
213 {
214     time_t now;
215     struct tm *tm;
216     long t, a;
217     int i, j, s, k;
218     int count = 0;
219     FILE *ofp = stdout;
220     FILE *ifp = stdin;
221     bool smooth = FALSE;
222     bool stages = FALSE;
223     time_t starts = 0;
224 #if HAVE_USE_DEFAULT_COLORS
225     bool d_option = FALSE;
226 #endif
227
228     setlocale(LC_ALL, "");
229
230     CATCHALL(sighndl);
231
232     while ((k = getopt(argc, argv, "dnst:")) != -1) {
233         switch (k) {
234 #if HAVE_USE_DEFAULT_COLORS
235         case 'd':
236             d_option = TRUE;
237             break;
238 #endif
239         case 'n':
240             ifp = fopen("/dev/null", "r");
241             redirected = TRUE;
242             break;
243         case 's':
244             smooth = TRUE;
245             break;
246         case 't':
247             starts = parse_time(optarg);
248             break;
249         default:
250             usage();
251         }
252     }
253     if (optind < argc) {
254         count = atoi(argv[optind++]);
255         assert(count >= 0);
256     }
257     if (optind < argc)
258         usage();
259
260     if (redirected) {
261         char *name = getenv("TERM");
262         if (name == 0
263             || newterm(name, ofp, ifp) == 0) {
264             fprintf(stderr, "cannot open terminal\n");
265             ExitProgram(EXIT_FAILURE);
266         }
267
268     } else {
269         initscr();
270     }
271     cbreak();
272     noecho();
273     nodelay(stdscr, 1);
274     curs_set(0);
275
276     hascolor = has_colors();
277
278     if (hascolor) {
279         short bg = COLOR_BLACK;
280         start_color();
281 #if HAVE_USE_DEFAULT_COLORS
282         if (d_option && (use_default_colors() == OK))
283             bg = -1;
284 #endif
285         init_pair(PAIR_DIGITS, COLOR_BLACK, COLOR_RED);
286         init_pair(PAIR_OTHERS, COLOR_RED, bg);
287         init_pair(PAIR_FRAMES, COLOR_WHITE, bg);
288         (void) attrset(AttrArg(COLOR_PAIR(PAIR_OTHERS), 0));
289     }
290
291   restart:
292     for (j = 0; j < 5; j++)
293         older[j] = newer[j] = next[j] = 0;
294
295     clear();
296     drawbox(FALSE);
297
298     do {
299         char buf[40];
300
301         if (starts != 0) {
302             now = ++starts;
303         } else {
304             time(&now);
305         }
306         tm = localtime(&now);
307
308         mask = 0;
309         set(tm->tm_sec % 10, 0);
310         set(tm->tm_sec / 10, 4);
311         set(tm->tm_min % 10, 10);
312         set(tm->tm_min / 10, 14);
313         set(tm->tm_hour % 10, 20);
314         set(tm->tm_hour / 10, 24);
315         set(10, 7);
316         set(10, 17);
317
318         for (k = 0; k < 6; k++) {
319             if (smooth) {
320                 for (i = 0; i < 5; i++)
321                     newer[i] = (newer[i] & ~mask) | (newer[i + 1] & mask);
322                 newer[5] = (newer[5] & ~mask) | (next[k] & mask);
323             } else {
324                 newer[k] = (newer[k] & ~mask) | (next[k] & mask);
325             }
326             next[k] = 0;
327             for (s = 1; s >= 0; s--) {
328                 standt(s);
329                 for (i = 0; i < 6; i++) {
330                     if ((a = (newer[i] ^ older[i]) & (s ? newer : older)[i])
331                         != 0) {
332                         for (j = 0, t = 1 << 26; t; t >>= 1, j++) {
333                             if (a & t) {
334                                 if (!(a & (t << 1))) {
335                                     move(YBASE + i, XBASE + 2 * j);
336                                 }
337                                 addstr("  ");
338                             }
339                         }
340                     }
341                     if (!s) {
342                         older[i] = newer[i];
343                     }
344                 }
345                 if (!s) {
346                     if (smooth)
347                         drawbox(TRUE);
348                     refresh();
349                     /*
350                      * If we're scrolling, space out the refreshes to fake
351                      * movement.  That's 7 frames, or 6 intervals, which would
352                      * be 166 msec if we spread it out over a second.  It looks
353                      * better (but will work on a slow terminal, e.g., less
354                      * than 9600bd) to squeeze that into a half-second, and use
355                      * half of 170 msec to ensure that the program doesn't eat
356                      * a lot of time when asking what time it is, at the top of
357                      * this loop -T.Dickey
358                      */
359                     if (smooth)
360                         napms(85);
361                     if (stages) {
362                         stages = FALSE;
363                         switch (wgetch(stdscr)) {
364                         case 'q':
365                             count = 1;
366                             break;
367                         case 'S':
368                             stages = TRUE;
369                             /* FALLTHRU */
370                         case 's':
371                             nodelay(stdscr, FALSE);
372                             break;
373                         case ' ':
374                             nodelay(stdscr, TRUE);
375                             break;
376 #ifdef KEY_RESIZE
377                         case KEY_RESIZE:
378 #endif
379                         case '?':
380                             goto restart;
381                         case ERR:
382                             check_term();
383                             /* FALLTHRU */
384                         default:
385                             continue;
386                         }
387                     }
388                 }
389             }
390         }
391
392         /* this depends on the detailed format of ctime(3) */
393         _nc_STRNCPY(buf, ctime(&now), (size_t) 30);
394         {
395             char *d2 = buf + 10;
396             char *s2 = buf + 19;
397             while ((*d2++ = *s2++) != '\0') ;
398         }
399         MvAddStr(16, 30, buf);
400
401         move(6, 0);
402         drawbox(FALSE);
403         refresh();
404
405         /*
406          * If we're not smooth-scrolling, wait 1000 msec (1 sec).  Use napms()
407          * rather than sleep() because the latter does odd things on some
408          * systems, e.g., suspending output as well.
409          */
410         if (smooth)
411             napms(500);
412         else
413             napms(1000);
414
415         /*
416          * This is a safe way to check if we're interrupted - making the signal
417          * handler set a flag that we can check.  Since we're running
418          * nodelay(), the wgetch() call returns immediately, and in particular
419          * will return an error if interrupted.  This works only if we can
420          * read from the input, of course.
421          */
422         stages = FALSE;
423         switch (wgetch(stdscr)) {
424         case 'q':
425             count = 1;
426             break;
427         case 'S':
428             stages = TRUE;
429             /* FALLTHRU */
430         case 's':
431             nodelay(stdscr, FALSE);
432             break;
433         case ' ':
434             nodelay(stdscr, TRUE);
435             break;
436 #ifdef KEY_RESIZE
437         case KEY_RESIZE:
438 #endif
439         case '?':
440             goto restart;
441         case ERR:
442             check_term();
443             /* FALLTHRU */
444         default:
445             continue;
446         }
447     } while (--count);
448     (void) standend();
449     exit_curses();
450     ExitProgram(EXIT_SUCCESS);
451 }