ncurses 6.1 - patch 20181027
[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.51 2017/09/30 18:10:05 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     while ((k = getopt(argc, argv, "dnst:")) != -1) {
231         switch (k) {
232 #if HAVE_USE_DEFAULT_COLORS
233         case 'd':
234             d_option = TRUE;
235             break;
236 #endif
237         case 'n':
238             ifp = fopen("/dev/null", "r");
239             redirected = TRUE;
240             break;
241         case 's':
242             smooth = TRUE;
243             break;
244         case 't':
245             starts = parse_time(optarg);
246             break;
247         default:
248             usage();
249         }
250     }
251     if (optind < argc) {
252         count = atoi(argv[optind++]);
253         assert(count >= 0);
254     }
255     if (optind < argc)
256         usage();
257
258     InitAndCatch({
259         if (redirected) {
260             char *name = getenv("TERM");
261             if (name == 0
262                 || newterm(name, ofp, ifp) == 0) {
263                 fprintf(stderr, "cannot open terminal\n");
264                 ExitProgram(EXIT_FAILURE);
265             }
266         } else {
267             initscr();
268         }
269     }
270     ,sighndl);
271
272     cbreak();
273     noecho();
274     nodelay(stdscr, 1);
275     curs_set(0);
276
277     hascolor = has_colors();
278
279     if (hascolor) {
280         short bg = COLOR_BLACK;
281         start_color();
282 #if HAVE_USE_DEFAULT_COLORS
283         if (d_option && (use_default_colors() == OK))
284             bg = -1;
285 #endif
286         init_pair(PAIR_DIGITS, COLOR_BLACK, COLOR_RED);
287         init_pair(PAIR_OTHERS, COLOR_RED, bg);
288         init_pair(PAIR_FRAMES, COLOR_WHITE, bg);
289         (void) attrset(AttrArg(COLOR_PAIR(PAIR_OTHERS), 0));
290     }
291
292   restart:
293     for (j = 0; j < 5; j++)
294         older[j] = newer[j] = next[j] = 0;
295
296     clear();
297     drawbox(FALSE);
298
299     do {
300         char buf[40];
301
302         if (starts != 0) {
303             now = ++starts;
304         } else {
305             time(&now);
306         }
307         tm = localtime(&now);
308
309         mask = 0;
310         set(tm->tm_sec % 10, 0);
311         set(tm->tm_sec / 10, 4);
312         set(tm->tm_min % 10, 10);
313         set(tm->tm_min / 10, 14);
314         set(tm->tm_hour % 10, 20);
315         set(tm->tm_hour / 10, 24);
316         set(10, 7);
317         set(10, 17);
318
319         for (k = 0; k < 6; k++) {
320             if (smooth) {
321                 for (i = 0; i < 5; i++)
322                     newer[i] = (newer[i] & ~mask) | (newer[i + 1] & mask);
323                 newer[5] = (newer[5] & ~mask) | (next[k] & mask);
324             } else {
325                 newer[k] = (newer[k] & ~mask) | (next[k] & mask);
326             }
327             next[k] = 0;
328             for (s = 1; s >= 0; s--) {
329                 standt(s);
330                 for (i = 0; i < 6; i++) {
331                     if ((a = (newer[i] ^ older[i]) & (s ? newer : older)[i])
332                         != 0) {
333                         for (j = 0, t = 1 << 26; t; t >>= 1, j++) {
334                             if (a & t) {
335                                 if (!(a & (t << 1))) {
336                                     move(YBASE + i, XBASE + 2 * j);
337                                 }
338                                 addstr("  ");
339                             }
340                         }
341                     }
342                     if (!s) {
343                         older[i] = newer[i];
344                     }
345                 }
346                 if (!s) {
347                     if (smooth)
348                         drawbox(TRUE);
349                     refresh();
350                     /*
351                      * If we're scrolling, space out the refreshes to fake
352                      * movement.  That's 7 frames, or 6 intervals, which would
353                      * be 166 msec if we spread it out over a second.  It looks
354                      * better (but will work on a slow terminal, e.g., less
355                      * than 9600bd) to squeeze that into a half-second, and use
356                      * half of 170 msec to ensure that the program doesn't eat
357                      * a lot of time when asking what time it is, at the top of
358                      * this loop -T.Dickey
359                      */
360                     if (smooth)
361                         napms(85);
362                     if (stages) {
363                         stages = FALSE;
364                         switch (wgetch(stdscr)) {
365                         case 'q':
366                             count = 1;
367                             break;
368                         case 'S':
369                             stages = TRUE;
370                             /* FALLTHRU */
371                         case 's':
372                             nodelay(stdscr, FALSE);
373                             break;
374                         case ' ':
375                             nodelay(stdscr, TRUE);
376                             break;
377 #ifdef KEY_RESIZE
378                         case KEY_RESIZE:
379 #endif
380                         case '?':
381                             goto restart;
382                         case ERR:
383                             check_term();
384                             /* FALLTHRU */
385                         default:
386                             continue;
387                         }
388                     }
389                 }
390             }
391         }
392
393         /* this depends on the detailed format of ctime(3) */
394         _nc_STRNCPY(buf, ctime(&now), (size_t) 30);
395         {
396             char *d2 = buf + 10;
397             char *s2 = buf + 19;
398             while ((*d2++ = *s2++) != '\0') ;
399         }
400         MvAddStr(16, 30, buf);
401
402         move(6, 0);
403         drawbox(FALSE);
404         refresh();
405
406         /*
407          * If we're not smooth-scrolling, wait 1000 msec (1 sec).  Use napms()
408          * rather than sleep() because the latter does odd things on some
409          * systems, e.g., suspending output as well.
410          */
411         if (smooth)
412             napms(500);
413         else
414             napms(1000);
415
416         /*
417          * This is a safe way to check if we're interrupted - making the signal
418          * handler set a flag that we can check.  Since we're running
419          * nodelay(), the wgetch() call returns immediately, and in particular
420          * will return an error if interrupted.  This works only if we can
421          * read from the input, of course.
422          */
423         stages = FALSE;
424         switch (wgetch(stdscr)) {
425         case 'q':
426             count = 1;
427             break;
428         case 'S':
429             stages = TRUE;
430             /* FALLTHRU */
431         case 's':
432             nodelay(stdscr, FALSE);
433             break;
434         case ' ':
435             nodelay(stdscr, TRUE);
436             break;
437 #ifdef KEY_RESIZE
438         case KEY_RESIZE:
439 #endif
440         case '?':
441             goto restart;
442         case ERR:
443             check_term();
444             /* FALLTHRU */
445         default:
446             continue;
447         }
448     } while (--count);
449     (void) standend();
450     exit_curses();
451     ExitProgram(EXIT_SUCCESS);
452 }