9d88079f94c3a5f8719db7be7b3d161465f1521d
[ncurses.git] / test / savescreen.c
1 /****************************************************************************
2  * Copyright (c) 2007-2017,2018 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  * $Id: savescreen.c,v 1.53 2018/05/12 15:11:16 tom Exp $
30  *
31  * Demonstrate save/restore functions from the curses library.
32  * Thomas Dickey - 2007/7/14
33  */
34
35 #include <test.priv.h>
36 #include <popup_msg.h>
37 #include <parse_rgb.h>
38
39 #if HAVE_SCR_DUMP
40
41 #include <sys/types.h>
42 #include <sys/stat.h>
43
44 #if TIME_WITH_SYS_TIME
45 # include <sys/time.h>
46 # include <time.h>
47 #else
48 # if HAVE_SYS_TIME_H
49 #  include <sys/time.h>
50 # else
51 #  include <time.h>
52 # endif
53 #endif
54
55 #if defined(__hpux)
56 #define MyMarker 'X'
57 #else
58 #define MyMarker ACS_DIAMOND
59 #endif
60
61 #define MAX_ANSI 8
62
63 static bool use_init = FALSE;
64 static bool keep_dumps = FALSE;
65
66 #if USE_WIDEC_SUPPORT
67 /* In HPUX curses, cchar_t is opaque; other implementations are not */
68 static wchar_t
69 BaseChar(cchar_t data)
70 {
71     wchar_t my_wchar[CCHARW_MAX];
72     wchar_t result = 0;
73     attr_t my_attr;
74     short my_pair;
75     if (getcchar(&data, my_wchar, &my_attr, &my_pair, NULL) == OK)
76         result = my_wchar[0];
77     return result;
78 }
79 #endif
80
81 static int
82 fexists(const char *name)
83 {
84     struct stat sb;
85     return (stat(name, &sb) == 0 && (sb.st_mode & S_IFMT) == S_IFREG);
86 }
87
88 static void
89 setup_next(void)
90 {
91     curs_set(1);
92     reset_shell_mode();
93 }
94
95 static void
96 cleanup(char *files[])
97 {
98     int n;
99
100     if (!keep_dumps) {
101         for (n = 0; files[n] != 0; ++n) {
102             unlink(files[n]);
103         }
104     }
105 }
106
107 static int
108 load_screen(char *filename)
109 {
110     int result;
111
112     if (use_init) {
113         if ((result = scr_init(filename)) != ERR)
114             result = scr_restore(filename);
115     } else {
116         result = scr_set(filename);
117     }
118     return result;
119 }
120
121 /*
122  * scr_restore() or scr_set() operates on curscr.  If we read a character using
123  * getch() that will refresh stdscr, wiping out the result.  To avoid that,
124  * copy the data back from curscr to stdscr.
125  */
126 static void
127 after_load(void)
128 {
129     overwrite(curscr, stdscr);
130     doupdate();
131 }
132
133 static void
134 show_what(int color, int which, int last)
135 {
136     int y, x, n;
137     time_t now;
138     char *mytime;
139
140     getyx(stdscr, y, x);
141
142     move(0, 0);
143     printw("Color %d.  Saved %d of %d (? for help)", color, which, last + 1);
144
145     now = time((time_t *) 0);
146     mytime = ctime(&now);
147     for (n = (int) strlen(mytime) - 1; n >= 0; --n) {
148         if (isspace(UChar(mytime[n]))) {
149             mytime[n] = '\0';
150         } else {
151             break;
152         }
153     }
154     mvprintw(0, (COLS - n - 2), " %s", mytime);
155
156     move(y, x);
157
158     refresh();
159 }
160
161 static int
162 get_command(int color, int which, int last)
163 {
164     int ch;
165
166     timeout(50);
167
168     do {
169         show_what(color, which, last);
170         ch = getch();
171     } while (ch == ERR);
172
173     return ch;
174 }
175
176 static int
177 dump_screen(char **files, int color, int which, int last, bool use_colors)
178 {
179 #if USE_WIDEC_SUPPORT
180     cchar_t mycc;
181     int myxx;
182 #endif
183     char *filename = files[which];
184     bool dumped = FALSE;
185
186     if (filename != 0) {
187         dumped = TRUE;
188         show_what(color, ++which, last);
189         if (scr_dump(filename) == ERR) {
190             endwin();
191             printf("Cannot write screen-dump %s\n", filename);
192             cleanup(files);
193             ExitProgram(EXIT_SUCCESS);
194         }
195         if (use_colors) {
196             int cx, cy;
197             int pair = 1 + (which % MAX_ANSI);
198             /*
199              * Change the background color, to make it more obvious.  But that
200              * changes the existing text-color.  Copy the old values from the
201              * currently displayed screen.
202              */
203             bkgd((chtype) COLOR_PAIR(pair));
204             for (cy = 1; cy < LINES; ++cy) {
205                 for (cx = 0; cx < COLS; ++cx) {
206                     wmove(curscr, cy, cx);
207                     wmove(stdscr, cy, cx);
208 #if USE_WIDEC_SUPPORT
209                     if (win_wch(curscr, &mycc) != ERR) {
210                         myxx = wcwidth(BaseChar(mycc));
211                         if (myxx > 0) {
212                             wadd_wchnstr(stdscr, &mycc, 1);
213                             cx += (myxx - 1);
214                         }
215                     }
216 #else
217                     waddch(stdscr, winch(curscr));
218 #endif
219                 }
220             }
221         }
222     }
223     return dumped;
224 }
225
226 static void
227 editor_help(void)
228 {
229     static const char *msgs[] =
230     {
231         "You are now in the screen-editor, which allows you to make some",
232         "lines on the screen, as well as save copies of the screen to a",
233         "temporary file",
234         "",
235         "Keys:",
236         "   q           quit",
237         "   n           run the screen-loader to show the saved screens",
238         "   <space>     dump a screen",
239         "",
240         "   a           toggle between '#' and graphic symbol for drawing",
241         "   c           change color drawn by line to next in palette",
242         "   h,j,k,l or arrows to move around the screen, drawing",
243         0
244     };
245     popup_msg(stdscr, msgs);
246 }
247
248 static void
249 replay_help(void)
250 {
251     static const char *msgs[] =
252     {
253         "You are now in the screen-loader, which allows you to view",
254         "the dumped/restored screens.",
255         "",
256         "Keys:",
257         "   q           quit",
258         "   <space>     load the next screen",
259         "   <backspace> load the previous screen",
260         0
261     };
262     popup_msg(stdscr, msgs);
263 }
264
265 static void
266 usage(void)
267 {
268     static const char *msg[] =
269     {
270         "Usage: savescreen [-r] files",
271         "",
272         "Options:",
273         " -f file  fill/initialize screen using text from this file",
274         " -i       use scr_init/scr_restore rather than scr_set",
275         " -k       keep the restored dump-files rather than removing them",
276         " -r       replay the screen-dump files"
277     };
278     unsigned n;
279     for (n = 0; n < SIZEOF(msg); ++n) {
280         fprintf(stderr, "%s\n", msg[n]);
281     }
282     ExitProgram(EXIT_FAILURE);
283 }
284
285 int
286 main(int argc, char *argv[])
287 {
288     int ch;
289     int which = 0;
290     int last;
291     bool use_colors = FALSE;
292     bool replaying = FALSE;
293     bool done = FALSE;
294     char **files;
295     char *fill_by = 0;
296 #if USE_WIDEC_SUPPORT
297     int r, g, b;
298     int cube = 0;
299     int cube0 = 16;
300     int cube1;
301     cchar_t mycc;
302     static const wchar_t mywc[2] =
303     {L'#', 0};
304     bool using_rgb = FALSE;
305 #endif
306
307     setlocale(LC_ALL, "");
308
309     while ((ch = getopt(argc, argv, "f:ikr")) != -1) {
310         switch (ch) {
311         case 'f':
312             fill_by = optarg;
313             break;
314         case 'i':
315             use_init = TRUE;
316             break;
317         case 'k':
318             keep_dumps = TRUE;
319             break;
320         case 'r':
321             replaying = TRUE;
322             break;
323         default:
324             usage();
325             break;
326         }
327     }
328
329     files = argv + optind;
330     last = argc - optind - 1;
331
332     if (replaying) {
333         while (last >= 0 && !fexists(files[last]))
334             --last;
335     }
336
337     initscr();
338     cbreak();
339     noecho();
340     keypad(stdscr, TRUE);
341     curs_set(0);
342
343     if (has_colors() && (start_color() == OK) && COLORS >= MAX_ANSI) {
344         static const struct {
345             int fg, bg;
346         } table[MAX_ANSI] = {
347 #define DATA(fg,bg) { COLOR_##fg, COLOR_##bg }
348             DATA(RED, WHITE),
349                 DATA(GREEN, WHITE),
350                 DATA(YELLOW, BLACK),
351                 DATA(BLUE, WHITE),
352                 DATA(MAGENTA, WHITE),
353                 DATA(MAGENTA, BLACK),
354                 DATA(CYAN, WHITE),
355                 DATA(CYAN, BLACK),
356 #undef DATA
357         };
358         int n;
359         int pair = 1;
360
361         use_colors = TRUE;
362         /*
363          * Discounting color-pair 0 (no color), make the next 8 color pairs
364          * useful for leaving a visually distinct trail of characters on the
365          * screen.
366          */
367         for (n = 0; n < MAX_ANSI; ++n) {
368             init_pair((short) pair++, (short) table[n].fg, (short) table[n].bg);
369         }
370         /*
371          * After that, use color pairs for constructing a test-pattern, e.g.,
372          * imitating xterm's scripts.
373          */
374         if (fill_by == 0) {
375             if (COLORS <= 256) {
376                 for (n = 0; n < COLORS; ++n)
377                     init_pair((short) (n + MAX_ANSI), (short) n, (short) n);
378             }
379 #if HAVE_TIGETSTR && USE_WIDEC_SUPPORT
380             else {
381                 int r_max, g_max, b_max;
382
383                 if (parse_rgb(&r_max, &g_max, &b_max) > 0) {
384                     int rows = LINES - 1;
385                     int cols = COLS - 1;
386                     int b_delta = (b_max / rows);
387                     int r_delta = (r_max / cols);
388                     int g_delta = (g_max / cols);
389                     int row = 0;
390
391                     b = 0;
392                     using_rgb = TRUE;
393                     while (row++ < rows) {
394                         int col = 0;
395                         r = 0;
396                         g = g_max;
397                         while (col++ < cols) {
398                             int color = (((r * (g_max + 1)) + g) * (b_max + 1)
399                                          + b + MAX_ANSI);
400 #if HAVE_INIT_EXTENDED_COLOR
401                             init_extended_pair(pair, color, color);
402 #else
403                             init_pair(pair, color, color);
404 #endif
405                             pair++;
406                             r += r_delta;
407                             g -= g_delta;
408                         }
409                         b += b_delta;
410                     }
411                 }
412             }
413 #endif
414         }
415         if ((fill_by == 0) && !replaying) {
416             /*
417              * Originally (before wide-characters) ncurses supported 16 colors.
418              */
419             if (COLORS >= 16 && COLORS <= 256) {
420                 mvprintw(2, 0, "System colors:\n");
421                 for (n = 0; n < 16; ++n) {
422                     pair = n + MAX_ANSI;
423                     addch((chtype) (' ' | COLOR_PAIR(pair)));
424                     addch((chtype) (' ' | COLOR_PAIR(pair)));
425                     if (((n + 1) % 8) == 0)
426                         addch('\n');
427                 }
428             }
429             /*
430              * Even with ncurses, you need wide-character support to have more
431              * than 16 colors.
432              */
433 #if USE_WIDEC_SUPPORT
434             if (COLORS == 88) {
435                 cube = 4;
436             } else if (COLORS == 256) {
437                 cube = 6;
438             }
439             if (cube != 0) {
440                 cube0 = 16;
441                 cube1 = cube0 + (cube * cube * cube);
442
443                 addch('\n');
444                 printw("Color cube, %dx%dx%d:\n", cube, cube, cube);
445                 for (g = 0; g < cube; g++) {
446                     for (r = 0; r < cube; r++) {
447                         for (b = 0; b < cube; b++) {
448                             pair = MAX_ANSI
449                                 + 16
450                                 + (r * cube * cube) + (g * cube) + b;
451                             setcchar(&mycc, mywc, 0, (short) pair, NULL);
452                             add_wch(&mycc);
453                             add_wch(&mycc);
454                         }
455                         addch(' ');
456                     }
457                     addch('\n');
458                 }
459                 addch('\n');
460                 printw("Grayscale ramp:\n");
461                 for (n = cube1; n < COLORS; ++n) {
462                     pair = n + MAX_ANSI;
463                     setcchar(&mycc, mywc, 0, (short) pair, NULL);
464                     add_wch(&mycc);
465                     add_wch(&mycc);
466                 }
467             } else if ((COLORS > 256) && using_rgb) {
468                 int rows = LINES - 1;
469                 int cols = COLS - 1;
470                 int row = 0;
471
472                 b = 0;
473                 pair = MAX_ANSI;
474                 while (row++ < rows) {
475                     int col = 0;
476                     while (col++ < cols) {
477                         setcchar(&mycc, mywc, 0, (short) pair, &pair);
478                         add_wch(&mycc);
479                         ++pair;
480                     }
481                     addch('\n');
482                 }
483                 addch('\n');
484             }
485 #endif
486         }
487     }
488
489     if (fill_by != 0) {
490         FILE *fp = fopen(fill_by, "r");
491         if (fp != 0) {
492             bool filled = FALSE;
493             move(1, 0);
494             while ((ch = fgetc(fp)) != EOF) {
495                 if (addch(UChar(ch)) == ERR) {
496                     filled = TRUE;
497                     break;
498                 }
499             }
500             fclose(fp);
501             if (!filled) {
502                 while (addch(' ') != ERR) {
503                     ;
504                 }
505             }
506             move(0, 0);
507         } else {
508             exit_curses();
509             fprintf(stderr, "Cannot open \"%s\"\n", fill_by);
510             ExitProgram(EXIT_FAILURE);
511         }
512     }
513
514     if (replaying) {
515
516         /*
517          * Use the last file as the initial/current screen.
518          */
519         if (last < 0) {
520             exit_curses();
521             printf("No screen-dumps given\n");
522             ExitProgram(EXIT_FAILURE);
523         }
524
525         which = last;
526         if (load_screen(files[which]) == ERR) {
527             exit_curses();
528             printf("Cannot load screen-dump %s\n", files[which]);
529             ExitProgram(EXIT_FAILURE);
530         }
531         after_load();
532
533         while (!done && (ch = getch()) != ERR) {
534             switch (ch) {
535             case 'n':
536                 /*
537                  * If we got a "next" here, skip to the final screen before
538                  * moving to the next process.
539                  */
540                 setup_next();
541                 which = last;
542                 done = TRUE;
543                 break;
544             case 'q':
545                 cleanup(files);
546                 done = TRUE;
547                 break;
548             case KEY_BACKSPACE:
549             case '\b':
550                 if (--which < 0)
551                     which = last;
552                 break;
553             case ' ':
554                 if (++which > last)
555                     which = 0;
556                 break;
557             case HELP_KEY_1:
558                 replay_help();
559                 break;
560             default:
561                 beep();
562                 continue;
563             }
564
565             if (ch == 'q') {
566                 ;
567             } else if (scr_restore(files[which]) == ERR) {
568                 endwin();
569                 printf("Cannot load screen-dump %s\n", files[which]);
570                 cleanup(files);
571                 ExitProgram(EXIT_FAILURE);
572             } else {
573                 wrefresh(curscr);
574             }
575         }
576         endwin();
577     } else {
578         int y = 0;
579         int x = 0;
580         int color = 0;
581         int altchars = 0;
582         bool dirty = use_colors || (fill_by != 0);
583
584         while (!done) {
585             switch (get_command(color, which, last)) {
586             case 'n':
587                 if (dirty && files[which]) {
588                     dump_screen(files, color, which, last, use_colors);
589                 }
590                 setup_next();
591                 done = TRUE;
592                 break;
593             case 'q':
594                 cleanup(files);
595                 done = TRUE;
596                 break;
597             case ' ':
598                 if (dump_screen(files, color, which, last, use_colors)) {
599                     which = (which + 1) % MAX_ANSI;
600                     dirty = FALSE;
601                 } else {
602                     setup_next();
603                     done = TRUE;
604                 }
605                 break;
606             case KEY_LEFT:
607             case 'h':
608                 if (--x < 0)
609                     x = COLS - 1;
610                 break;
611             case KEY_DOWN:
612             case 'j':
613                 if (++y >= LINES)
614                     y = 1;
615                 break;
616             case KEY_UP:
617             case 'k':
618                 if (--y < 1)
619                     y = LINES - 1;
620                 break;
621             case KEY_RIGHT:
622             case 'l':
623                 if (++x >= COLS)
624                     x = 0;
625                 break;
626             case 'a':
627                 altchars = !altchars;
628                 break;
629             case 'c':
630                 if (use_colors) {
631                     color = (color + 1) % MAX_ANSI;
632                 }
633                 break;
634             case HELP_KEY_1:
635                 editor_help();
636                 break;
637             default:
638                 beep();
639                 continue;
640             }
641             if (!done) {
642                 chtype attr = A_REVERSE;
643                 chtype ch2 = (altchars ? MyMarker : '#');
644                 if (use_colors) {
645                     attr |= (chtype) COLOR_PAIR(color);
646                 }
647                 move(y, x);
648                 AddCh(ch2 | attr);
649                 move(y, x);
650                 dirty = TRUE;
651             }
652         }
653         endwin();
654     }
655     ExitProgram(EXIT_SUCCESS);
656 }
657
658 #else
659 int
660 main(int argc GCC_UNUSED, char *argv[]GCC_UNUSED)
661 {
662     printf("This program requires the screen-dump functions\n");
663     ExitProgram(EXIT_FAILURE);
664 }
665 #endif