ncurses 6.0 - patch 20171028
[ncurses.git] / test / padview.c
1 /****************************************************************************
2  * Copyright (c) 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  * clone of view.c, using pads
30  *
31  * $Id: padview.c,v 1.12 2017/10/23 00:37:21 tom Exp $
32  */
33
34 #include <test.priv.h>
35 #include <widechars.h>
36 #include <popup_msg.h>
37
38 #include <sys/stat.h>
39 #include <time.h>
40
41 #if HAVE_NEWPAD
42
43 static void finish(int sig) GCC_NORETURN;
44
45 #define my_pair 1
46
47 static int shift = 0;
48 static bool try_color = FALSE;
49
50 static char *fname;
51 static int num_lines;
52
53 #if USE_WIDEC_SUPPORT
54 static bool n_option = FALSE;
55 #endif
56
57 static void usage(void) GCC_NORETURN;
58
59 static void
60 failed(const char *msg)
61 {
62     endwin();
63     fprintf(stderr, "%s\n", msg);
64     ExitProgram(EXIT_FAILURE);
65 }
66
67 static void
68 finish(int sig)
69 {
70     endwin();
71     ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
72 }
73
74 static void
75 show_all(const char *tag, WINDOW *my_pad, int my_row)
76 {
77     int i;
78     int digits;
79     char temp[BUFSIZ];
80     time_t this_time;
81
82     for (digits = 1, i = num_lines; i > 0; i /= 10) {
83         ++digits;
84     }
85
86     wattrset(stdscr, COLOR_PAIR(my_pair));
87     clear();
88
89     _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
90                 "view %.*s", (int) strlen(tag), tag);
91     i = (int) strlen(temp);
92     _nc_SPRINTF(temp + i, _nc_SLIMIT(sizeof(temp) - i)
93                 " %.*s", (int) sizeof(temp) - i - 2, fname);
94     mvprintw(0, 0, "%.*s", COLS, temp);
95     this_time = time((time_t *) 0);
96     _nc_STRNCPY(temp, ctime(&this_time), (size_t) 30);
97     if ((i = (int) strlen(temp)) != 0) {
98         temp[--i] = 0;
99         mvprintw(0, COLS - i - 2, "  %s", temp);
100     }
101
102     for (i = 1; i < LINES; i++) {
103         int actual = my_row + i;
104         if (actual > num_lines) {
105             break;
106         }
107         mvprintw(i, 0, "%*d:", digits, actual);
108     }
109     wnoutrefresh(stdscr);
110     pnoutrefresh(my_pad, my_row, shift, 1, digits + 1, LINES - 1, COLS - 1);
111     doupdate();
112 }
113
114 static WINDOW *
115 read_file(const char *filename)
116 {
117     FILE *fp;
118     int pass;
119     int k;
120     int height, width;
121     size_t j;
122     size_t len;
123     struct stat sb;
124     char *my_blob;
125     char **my_vec = 0;
126     WINDOW *my_pad;
127
128     if (stat(filename, &sb) != 0
129         || (sb.st_mode & S_IFMT) != S_IFREG) {
130         failed("input is not a file");
131     }
132
133     if (sb.st_size == 0) {
134         failed("input is empty");
135     }
136
137     if ((fp = fopen(filename, "r")) == 0) {
138         failed("cannot open input-file");
139     }
140
141     if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0) {
142         failed("cannot allocate memory for input-file");
143     }
144
145     len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
146     my_blob[sb.st_size] = '\0';
147     fclose(fp);
148
149     for (pass = 0; pass < 2; ++pass) {
150         char *base = my_blob;
151         k = 0;
152         for (j = 0; j < len; ++j) {
153             if (my_blob[j] == '\n') {
154                 if (pass) {
155                     my_vec[k] = base;
156                     my_blob[j] = '\0';
157                 }
158                 base = my_blob + j + 1;
159                 ++k;
160             }
161         }
162         num_lines = k;
163         if (base != (my_blob + j))
164             ++num_lines;
165         if (!pass &&
166             ((my_vec = typeCalloc(char *, (size_t) k + 2)) == 0)) {
167             failed("cannot allocate line-vector #1");
168         }
169     }
170
171 #if USE_WIDEC_SUPPORT
172     if (!memcmp("\357\273\277", my_blob, 3)) {
173         char *s = my_blob + 3;
174         char *d = my_blob;
175         Trace(("trim BOM"));
176         do {
177         } while ((*d++ = *s++) != '\0');
178     }
179 #endif
180
181     height = num_lines;
182     width = (int) strlen(my_vec[0]);
183     for (k = 1; my_vec[k]; ++k) {
184         int check = (int) (my_vec[k] - my_vec[k - 1]);
185         if (width < check)
186             width = check;
187     }
188     width = (width + 1) * 5;
189     my_pad = newpad(height, width);
190     if (my_pad == 0)
191         failed("cannot allocate pad workspace");
192     if (try_color) {
193         wattrset(my_pad, COLOR_PAIR(my_pair));
194         wbkgd(my_pad, (chtype) COLOR_PAIR(my_pair));
195     }
196
197     /*
198      * Use the curses library for rendering, including tab-conversion.
199      */
200     Trace(("slurp the file"));
201     for (k = 0; my_vec[k]; ++k) {
202         char *s;
203 #if USE_WIDEC_SUPPORT
204         char *last = my_vec[k] + (int) strlen(my_vec[k]);
205         wchar_t wch[2];
206         size_t rc;
207 #ifndef state_unused
208         mbstate_t state;
209 #endif
210 #endif /* USE_WIDEC_SUPPORT */
211
212         wmove(my_pad, k, 0);
213 #if USE_WIDEC_SUPPORT
214         wch[1] = 0;
215         reset_mbytes(state);
216 #endif
217         for (s = my_vec[k]; *s != '\0'; ++s) {
218 #if USE_WIDEC_SUPPORT
219             if (!n_option) {
220                 rc = (size_t) check_mbytes(wch[0], s, (size_t) (last - s), state);
221                 if ((long) rc == -1 || (long) rc == -2) {
222                     break;
223                 }
224                 s += rc - 1;
225                 waddwstr(my_pad, wch);
226             } else
227 #endif
228                 waddch(my_pad, *s & 0xff);
229         }
230     }
231
232     free(my_vec);
233     free(my_blob);
234
235     return my_pad;
236 }
237
238 static void
239 usage(void)
240 {
241     static const char *msg[] =
242     {
243         "Usage: view [options] file"
244         ,""
245         ,"Options:"
246         ," -c       use color if terminal supports it"
247         ," -i       ignore INT, QUIT, TERM signals"
248 #if USE_WIDEC_SUPPORT
249         ," -n       use waddch (bytes) rather then wadd_wch (wide-chars)"
250 #endif
251         ," -s       start in single-step mode, waiting for input"
252 #ifdef TRACE
253         ," -t       trace screen updates"
254         ," -T NUM   specify trace mask"
255 #endif
256     };
257     size_t n;
258     for (n = 0; n < SIZEOF(msg); n++)
259         fprintf(stderr, "%s\n", msg[n]);
260     ExitProgram(EXIT_FAILURE);
261 }
262
263 int
264 main(int argc, char *argv[])
265 {
266     static const char *help[] =
267     {
268         "Commands:",
269         "  q,^Q,ESC       - quit this program",
270         "",
271         "  p,<Up>         - scroll the viewport up by one row",
272         "  n,<Down>       - scroll the viewport down by one row",
273         "  l,<Left>       - scroll the viewport left by one column",
274         "  r,<Right>      - scroll the viewport right by one column",
275         "  <,>            - scroll the viewport left/right by 8 columns",
276         "",
277         "  h,<Home>       - scroll the viewport to top of file",
278         "  ^F,<PageDn>    - scroll to the next page",
279         "  ^B,<PageUp>    - scroll to the previous page",
280         "  e,<End>        - scroll the viewport to end of file",
281         "",
282         "  ^L             - repaint using redrawwin()",
283         "",
284         "  0 through 9    - enter digits for count",
285         "  s              - use entered count for halfdelay() parameter",
286         "                 - if no entered count, stop nodelay()",
287         "  <space>        - begin nodelay()",
288         0
289     };
290
291     int i;
292     int my_delay = 0;
293     WINDOW *my_pad;
294     int my_row = 0;
295     int value = 0;
296     bool done = FALSE;
297     bool got_number = FALSE;
298     bool ignore_sigs = FALSE;
299     bool single_step = FALSE;
300     const char *my_label = "Input";
301
302     setlocale(LC_ALL, "");
303
304     while ((i = getopt(argc, argv, "cinstT:")) != -1) {
305         switch (i) {
306         case 'c':
307             try_color = TRUE;
308             break;
309         case 'i':
310             ignore_sigs = TRUE;
311             break;
312 #if USE_WIDEC_SUPPORT
313         case 'n':
314             n_option = TRUE;
315             break;
316 #endif
317         case 's':
318             single_step = TRUE;
319             break;
320 #ifdef TRACE
321         case 'T':
322             {
323                 char *next = 0;
324                 int tvalue = (int) strtol(optarg, &next, 0);
325                 if (tvalue < 0 || (next != 0 && *next != 0))
326                     usage();
327                 trace((unsigned) tvalue);
328             }
329             break;
330         case 't':
331             trace(TRACE_CALLS);
332             break;
333 #endif
334         default:
335             usage();
336         }
337     }
338     if (optind + 1 != argc)
339         usage();
340
341     InitAndCatch(initscr(), ignore_sigs ? SIG_IGN : finish);
342     keypad(stdscr, TRUE);       /* enable keyboard mapping */
343     (void) nonl();              /* tell curses not to do NL->CR/NL on output */
344     (void) cbreak();            /* take input chars one at a time, no wait for \n */
345     (void) noecho();            /* don't echo input */
346     if (!single_step)
347         nodelay(stdscr, TRUE);
348     idlok(stdscr, TRUE);        /* allow use of insert/delete line */
349
350     my_pad = read_file(fname = argv[optind]);
351
352     if (try_color) {
353         if (has_colors()) {
354             start_color();
355             init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
356             bkgd((chtype) COLOR_PAIR(my_pair));
357         } else {
358             try_color = FALSE;
359         }
360     }
361
362     my_row = 0;
363     while (!done) {
364         int n, c;
365
366         if (!got_number)
367             show_all(my_label, my_pad, my_row);
368
369         for (;;) {
370             c = getch();
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             for (i = 0; i < n; i++)
394                 if (my_row < (num_lines - LINES + 1))
395                     my_row++;
396                 else
397                     break;
398             break;
399
400         case KEY_UP:
401         case 'p':
402             for (i = 0; i < n; i++)
403                 if (my_row > 0)
404                     my_row--;
405                 else
406                     break;
407             break;
408
409         case 'h':
410             /* FALLTHRU */
411         case KEY_HOME:
412             my_row = 0;
413             break;
414
415         case '<':
416             if ((shift -= 8) < 0)
417                 shift = 0;
418             break;
419         case '>':
420             shift += 8;
421             break;
422
423         case 'e':
424             /* FALLTHRU */
425         case KEY_END:
426             if (num_lines > LINES)
427                 my_row = (num_lines - LINES + 1);
428             else
429                 my_row = (num_lines - 2);
430             break;
431
432         case CTRL('F'):
433             /* FALLTHRU */
434         case KEY_NPAGE:
435             for (i = 0; i < n; i++) {
436                 if (my_row < (num_lines - 5))
437                     my_row += (LINES - 1);
438                 else
439                     my_row = (num_lines - 2);
440             }
441             break;
442
443         case CTRL('B'):
444             /* FALLTHRU */
445         case KEY_PPAGE:
446             for (i = 0; i < n; i++) {
447                 if (my_row >= LINES)
448                     my_row -= (LINES - 1);
449                 else
450                     my_row = 0;
451             }
452             break;
453
454         case 'r':
455         case KEY_RIGHT:
456             shift += n;
457             break;
458
459         case 'l':
460         case KEY_LEFT:
461             shift -= n;
462             if (shift < 0) {
463                 shift = 0;
464                 beep();
465             }
466             break;
467
468         case 'q':
469         case QUIT:
470         case ESCAPE:
471             done = TRUE;
472             break;
473
474 #ifdef KEY_RESIZE
475         case KEY_RESIZE:        /* ignore this; ncurses will repaint */
476             break;
477 #endif
478         case 's':
479 #if HAVE_HALFDELAY
480             if (got_number) {
481                 halfdelay(my_delay = n);
482             } else {
483                 nodelay(stdscr, FALSE);
484                 my_delay = -1;
485             }
486 #else
487             nodelay(stdscr, FALSE);
488             my_delay = -1;
489 #endif
490             break;
491         case ' ':
492             nodelay(stdscr, TRUE);
493             my_delay = 0;
494             break;
495         case CTRL('L'):
496             redrawwin(stdscr);
497             break;
498         case ERR:
499             if (!my_delay)
500                 napms(50);
501             break;
502         case HELP_KEY_1:
503             popup_msg(stdscr, help);
504             break;
505         default:
506             beep();
507             break;
508         }
509         if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
510             got_number = FALSE;
511             value = 0;
512         }
513     }
514
515     finish(0);                  /* we're done */
516 }
517 #else
518 int
519 main(void)
520 {
521     printf("This program requires the curses pad functions\n");
522     ExitProgram(EXIT_FAILURE);
523 }
524 #endif