ncurses 5.6 - patch 20061223
[ncurses.git] / tack / output.c
1 /*
2 ** Copyright (C) 1991, 1997 Free Software Foundation, Inc.
3 ** 
4 ** This file is part of TACK.
5 ** 
6 ** TACK is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU General Public License as published by
8 ** the Free Software Foundation; either version 2, or (at your option)
9 ** any later version.
10 ** 
11 ** TACK is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 ** GNU General Public License for more details.
15 ** 
16 ** You should have received a copy of the GNU General Public License
17 ** along with TACK; see the file COPYING.  If not, write to
18 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 ** Boston, MA 02110-1301, USA
20 */
21 /* screen formatting and I/O utility functions */
22
23 #include <tack.h>
24 #include <time.h>
25
26 MODULE_ID("$Id: output.c,v 1.11 2006/11/26 00:16:49 tom Exp $")
27
28 /* globals */
29 long char_sent;                 /* number of characters sent */
30 int char_count;                 /* counts characters */
31 int line_count;                 /* counts line feeds */
32 int expand_chars;               /* length of expand() string */
33 int replace_mode;               /* used to output replace mode padding */
34 int can_go_home;                /* TRUE if we can fashion a home command */
35 int can_clear_screen;           /* TRUE if we can somehow clear the screen */
36 int raw_characters_sent;        /* Total output characters */
37 static int log_count;           /* Number of characters on a log line */
38
39 /* translate mode default strings */
40 #define TM_carriage_return      TM_string[0].value
41 #define TM_cursor_down          TM_string[1].value
42 #define TM_scroll_forward       TM_string[2].value
43 #define TM_newline              TM_string[3].value
44 #define TM_cursor_left          TM_string[4].value
45 #define TM_bell                 TM_string[5].value
46 #define TM_form_feed            TM_string[6].value
47 #define TM_tab                  TM_string[7].value
48
49 struct default_string_list TM_string[TM_last] = {
50         {"cr", "\r", 0},
51         {"cud1", "\n", 0},
52         {"ind", "\n", 0},
53         {"nel", "\r\n", 0},
54         {"cub1", "\b", 0},
55         {"bel", "\007", 0},
56         {"ff", "\f", 0},
57         {"ht", "\t", 0}
58 };
59
60 static const char *c0[32] = {
61         "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK",
62         "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
63         "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
64         "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
65 };
66
67 static const char *c1[32] = {
68         "", "", "", "", "IND", "NEL", "SSA", "ESA",
69         "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3",
70         "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA",
71         "", "", "", "CSI", "ST", "OSC", "PM", "APC"
72 };
73
74 int
75 getnext(int mask)
76 {                               /* get the next character without scan mode
77                                    conversion */
78         int ch;
79         unsigned char buf;
80
81         tc_putp(req_for_input);
82         fflush(stdout);
83         if (nodelay_read)
84                 while (1) {
85                         ch = read(fileno(stdin), &buf, 1);
86                         if (ch == -1)
87                                 return EOF;
88                         if (ch == 1)
89                                 return buf;
90                 }
91         ch = getchar();
92         if (ch == EOF)
93                 return EOF;
94         return ch & mask;
95 }
96
97
98 int
99 getchp(int mask)
100 {                               /* read a character with scan mode conversion */
101         if (scan_mode) {
102                 tc_putp(req_for_input);
103                 fflush(stdout);
104                 return scan_key();
105         } else
106                 return getnext(mask);
107 }
108
109 /*
110 **      tc_putch(c)
111 **
112 **      Output one character
113 */
114 int
115 tc_putch(int c)
116 {
117         char_sent++;
118         raw_characters_sent++;
119         putchar(c);
120         if ((raw_characters_sent & 31) == 31) {
121                 fflush(stdout);
122         }
123         if (log_fp) {
124                 /* terminal output logging */
125                 c = UChar(c);
126                 if (c < 32) {
127                         fprintf(log_fp, "<%s>", c0[c]);
128                         log_count += 5;
129                 } else
130                 if (c < 127) {
131                         fprintf(log_fp, "%c", c);
132                         log_count += 1;
133                 } else {
134                         fprintf(log_fp, "<%02x>", c);
135                         log_count += 4;
136                 }
137                 if (c == '\n' || log_count >= 80) {
138                         fprintf(log_fp, "\n");
139                         log_count = 0;
140                 }
141         }
142         return (c);
143 }
144
145 /*
146 **      tt_tputs(string, reps)
147 **
148 **      Output a string with tputs() translation.
149 **      Use this function inside timing tests.
150 */
151 void
152 tt_tputs(const char *string, int reps)
153 {
154         int i;
155
156         if (string) {
157                 for (i = 0; i < TT_MAX; i++) {
158                         if (i >= ttp) {
159                                 tt_cap[i] = string;
160                                 tt_affected[i] = reps;
161                                 tt_count[i] = 1;
162                                 tt_delay[i] = msec_cost(string, reps);
163                                 ttp++;
164                                 break;
165                         }
166                         if (string == tt_cap[i] && reps == tt_affected[i]) {
167                                 tt_count[i]++;
168                                 tt_delay_used += tt_delay[i];
169                                 break;
170                         }
171                 }
172                 (void) tputs(string, reps, tc_putch);
173         }
174 }
175
176 /*
177 **      tt_putp(string)
178 **
179 **      Output a string with tputs() translation.
180 **      Use this function inside timing tests.
181 */
182 void
183 tt_putp(const char *string)
184 {
185         tt_tputs(string, 1);
186 }
187
188 /*
189 **      tt_putparm(string, reps, arg1, arg2)
190 **
191 **      Send tt_tputs(tparm(string, args1, arg2), reps)
192 **      Use this function inside timing tests.
193 */
194 void
195 tt_putparm(
196         NCURSES_CONST char *string,
197         int reps,
198         int arg1,
199         int arg2)
200 {
201         int i;
202
203         if (string) {
204                 for (i = 0; i < TT_MAX; i++) {
205                         if (i >= ttp) {
206                                 tt_cap[i] = string;
207                                 tt_affected[i] = reps;
208                                 tt_count[i] = 1;
209                                 tt_delay[i] = msec_cost(string, reps);
210                                 ttp++;
211                                 break;
212                         }
213                         if (string == tt_cap[i] && reps == tt_affected[i]) {
214                                 tt_count[i]++;
215                                 tt_delay_used += tt_delay[i];
216                                 break;
217                         }
218                 }
219                 (void) tputs(TPARM_2((NCURSES_CONST char *)string, arg1, arg2), reps, tc_putch);
220         }
221 }
222
223 /*
224 **      tc_putp(string)
225 **
226 **      Output a string with tputs() translation.
227 **      Use this function instead of putp() so we can track
228 **      the actual number of characters sent.
229 */
230 int
231 tc_putp(const char *string)
232 {
233         return tputs(string, 1, tc_putch);
234 }
235
236
237 void
238 put_this(int c)
239 {                               /* output one character (with padding) */
240         tc_putch(c);
241         if (char_padding && replace_mode)
242                 tt_putp(char_padding);
243 }
244
245
246 void
247 put_cr(void)
248 {
249         if (translate_mode && carriage_return) {
250                 tt_putp(carriage_return);
251         } else {
252                 tt_putp(TM_carriage_return);
253         }
254         char_count = 0;
255 }
256
257
258 void
259 put_lf(void)
260 {                               /* send a linefeed (only works in RAW or
261                                    CBREAK mode) */
262         if (translate_mode && cursor_down) {
263                 tt_putp(cursor_down);
264         } else {
265                 tt_putp(TM_cursor_down);
266         }
267         line_count++;
268 }
269
270
271 void
272 put_ind(void)
273 {                               /* scroll forward (only works in RAW or
274                                    CBREAK mode) */
275         if (translate_mode && scroll_forward) {
276                 tt_putp(scroll_forward);
277         } else {
278                 tt_putp(TM_scroll_forward);
279         }
280         line_count++;
281 }
282
283 /*
284 **      put_crlf()
285 **
286 **      Send (nel)  or <cr> <lf>
287 */
288 void
289 put_crlf(void)
290 {
291         if (translate_mode && newline) {
292                 tt_putp(newline);
293         } else {
294                 tt_putp(TM_newline);
295         }
296         char_count = 0;
297         line_count++;
298 }
299
300 /*
301 **      put_new_lines(count)
302 **
303 **      Send a number of newlines. (nel)
304 */
305 void
306 put_newlines(int n)
307 {
308         while (n-- > 0) {
309                 put_crlf();
310         }
311 }
312
313 /*
314 **      putchp(character)
315 **
316 **      Send one character to the terminal.
317 **      This function does translation of control characters.
318 */
319 void
320 putchp(int c)
321 {
322         switch (c) {
323         case '\b':
324                 if (translate_mode && cursor_left) {
325                         tt_putp(cursor_left);
326                 } else {
327                         tt_putp(TM_cursor_left);
328                 }
329                 char_count--;
330                 break;
331         case 7:
332                 if (translate_mode && bell) {
333                         tt_putp(bell);
334                 } else {
335                         tt_putp(TM_bell);
336                 }
337                 break;
338         case '\f':
339                 if (translate_mode && form_feed) {
340                         tt_putp(form_feed);
341                 } else {
342                         tt_putp(TM_form_feed);
343                 }
344                 char_count = 0;
345                 line_count++;
346                 break;
347         case '\n':
348                 put_crlf();
349                 break;
350         case '\r':
351                 put_cr();
352                 break;
353         case '\t':
354                 if (translate_mode && tab) {
355                         tt_putp(tab);
356                 } else {
357                         tt_putp(TM_tab);
358                 }
359                 char_count = ((char_count / 8) + 1) * 8;
360                 break;
361         default:
362                 put_this(c);
363                 char_count++;
364                 break;
365         }
366 }
367
368
369 void
370 put_str(const char *s)
371 {                               /* send the string to the terminal */
372         for (; *s; putchp(*s++));
373 }
374
375
376 void
377 putln(const char *s)
378 {                               /* output a string followed by a CR LF */
379         for (; *s; putchp(*s++));
380         put_crlf();
381 }
382
383
384 void
385 put_columns(const char *s, int len, int w)
386 {                               /* put out s in column format */
387         int l;
388
389         if (char_count + w > columns) {
390                 put_crlf();
391         }
392         l = char_count % w;
393         if (l) {
394                 while (l < w) {
395                         putchp(' ');
396                         l++;
397                 }
398         }
399         if (char_count && char_count + len >= columns) {
400                 put_crlf();
401         }
402         l = char_count;
403         put_str(s);
404         char_count = l + len;
405 }
406
407
408 /*
409 **      ptext(string)
410 **
411 **      Output a string but do not assume the terminal will wrap to a
412 **      new line.  Break the line at a word boundary then send a CR LF.
413 **      This is more esthetic on 40 column terminals.
414 */
415 void
416 ptext(const char *s)
417 {
418         const char *t;
419
420         while (*s) {
421                 for (t = s + 1; *t > ' '; t++);
422                 if ((char_count != 0) && ((t - s) + char_count >= columns)) {
423                         put_crlf();
424                         while (*s == ' ')
425                                 s++;
426                 }
427                 while (s < t) {
428                         putchp(*s++);
429                 }
430         }
431 }
432
433
434 void
435 put_dec(char *f, int i)
436 {                               /* print a line with a decimal number in it */
437         char tm[128];
438
439         sprintf(tm, f, i / 10, i % 10);
440         ptext(tm);
441 }
442
443
444 void
445 three_digit(char *tx, int i)
446 {                               /* convert the decimal number to a string of
447                                    at least 3 digits */
448         if (i < 1000)
449                 sprintf(tx, "%d.%d", i / 10, i % 10);
450         else
451                 sprintf(tx, "%d", i / 10);
452 }
453
454
455 void
456 ptextln(const char *s)
457 {                               /* print the text using ptext() then add a CR
458                                    LF */
459         ptext(s);
460         put_crlf();
461 }
462
463
464 static void
465 expand_one(int ch, char **v)
466 {                               /* expand one character */
467         char *t = *v;
468
469         if (ch & 0x80) {        /* dump it in octal (yuck) */
470                 *t++ = '\\';
471                 *t++ = '0' + ((ch >> 6) & 3);
472                 *t++ = '0' + ((ch >> 3) & 7);
473                 *t++ = '0' + (ch & 7);
474                 expand_chars += 4;
475         } else if (ch == 127) { /* DEL */
476                 *t++ = '^';
477                 *t++ = '?';
478                 expand_chars += 2;
479         } else if (ch >= ' ') {
480                 *t++ = ch;
481                 expand_chars++;
482         } else {        /* control characters */
483                 *t++ = '^';
484                 *t++ = ch + '@';
485                 expand_chars += 2;
486         }
487         *v = t;
488 }
489
490
491 char *
492 expand(const char *s)
493 {                               /* convert the string to printable form */
494         static char buf[4096];
495         char *t, *v;
496         int ch;
497
498         if (magic_cookie_glitch <= 0 && exit_attribute_mode) {
499                 v = enter_reverse_mode;
500         } else {
501                 v = NULL;
502         }
503         expand_chars = 0;
504         t = buf;
505         if (s) {
506                 for (; (ch = *s); s++) {
507                         if ((ch & 0x80) && v) { /* print it in reverse video
508                                                    mode */
509                                 strcpy(t, liberated(TPARM_0(v)));
510                                 for (; *t; t++);
511                                 expand_one(ch & 0x7f, &t);
512                                 strcpy(t, liberated(TPARM_0(exit_attribute_mode)));
513                                 for (; *t; t++);
514                         } else {
515                                 expand_one(ch, &t);
516                         }
517                 }
518         }
519         *t = '\0';
520         return buf;
521 }
522
523
524 char *
525 print_expand(char *s)
526 {                               /* convert the string to 7-bit printable form */
527         static char buf[4096];
528         char *t;
529         int ch;
530
531         expand_chars = 0;
532         t = buf;
533         if (s) {
534                 for (; (ch = *s); s++) {
535                         expand_one(ch, &t);
536                 }
537         }
538         *t = '\0';
539         return buf;
540 }
541
542
543 char *
544 expand_to(char *s, int l)
545 {                               /* expand s to length l */
546         char *t;
547
548         for (s = t = expand(s); *t; t++);
549         for (; expand_chars < l; expand_chars++) {
550                 *t++ = ' ';
551         }
552         *t = '\0';
553         return s;
554 }
555
556
557 char *
558 hex_expand_to(char *s, int l)
559 {                               /* expand s to length l in hex */
560         static char buf[4096];
561         char *t;
562
563         for (t = buf; *s; s++) {
564                 sprintf(t, "%02X ", UChar(*s));
565                 t += 3;
566                 if (t - buf > (int) sizeof(buf) - 4) {
567                         break;
568                 }
569         }
570         for (; t - buf < l;) {
571                 *t++ = ' ';
572         }
573         *t = '\0';
574         expand_chars = t - buf;
575         return buf;
576 }
577
578
579 char *
580 expand_command(const char *c)
581 {                               /* expand an ANSI escape sequence */
582         static char buf[256];
583         int i, j, ch;
584         char *s;
585
586         s = buf;
587         for (i = FALSE; (ch = UChar(*c)) != 0; c++) {
588                 if (i) {
589                         *s++ = ' ';
590                 }
591                 i = TRUE;
592                 if (ch < 32) {
593                         j = UChar(c[1]);
594                         if (ch == '\033' && j >= '@' && j <= '_') {
595                                 ch = j - '@';
596                                 c++;
597                                 for (j = 0; (*s = c1[ch][j++]); s++);
598                         } else
599                                 for (j = 0; (*s = c0[ch][j++]); s++);
600                 } else {
601                         *s++ = ch;
602                         j = UChar(c[1]);
603                         if (ch >= '0' && ch <= '9' &&
604                                 j >= '0' && j <= '9') {
605                                 i = FALSE;
606                         }
607                 }
608         }
609         *s = '\0';
610         return buf;
611 }
612
613 /*
614 **      go_home()
615 **
616 **      Move the cursor to the home position
617 */
618 void
619 go_home(void)
620 {
621         int i;
622
623         if (cursor_home)
624                 tt_putp(cursor_home);
625         else if (cursor_address)
626                 tt_putparm(cursor_address, lines, 0, 0);
627         else if (row_address) { /* use (vpa) */
628                 put_cr();
629                 tt_putparm(row_address, 1, 0, 0);
630         } else if (cursor_up && cursor_to_ll) {
631                 tt_putp(cursor_to_ll);
632                 for (i = 1; i < lines; i++) {
633                         tt_putp(cursor_up);
634                 }
635         } else {
636                 can_go_home = FALSE;
637                 return;
638         }
639         char_count = line_count = 0;
640         can_go_home = TRUE;
641 }
642
643
644 void
645 home_down(void)
646 {                               /* move the cursor to the lower left hand
647                                    corner */
648         int i;
649
650         if (cursor_to_ll)
651                 tt_putp(cursor_to_ll);
652         else if (cursor_address)
653                 tt_putparm(cursor_address, lines, lines - 1, 0);
654         else if (row_address) { /* use (vpa) */
655                 put_cr();
656                 tt_putparm(row_address, 1, lines - 1, 0);
657         } else if (cursor_down && cursor_home) {
658                 tt_putp(cursor_home);
659                 for (i = 1; i < lines; i++)
660                         tt_putp(cursor_down);
661         } else
662                 return;
663         char_count = 0;
664         line_count = lines - 1;
665 }
666
667
668 void
669 put_clear(void)
670 {                               /* clear the screen */
671         int i;
672
673         if (clear_screen)
674                 tt_tputs(clear_screen, lines);
675         else if (clr_eos && can_go_home) {
676                 go_home();
677                 tt_tputs(clr_eos, lines);
678         } else if (scroll_forward && !over_strike && (can_go_home || cursor_up)) {
679                 /* clear the screen by scrolling */
680                 put_cr();
681                 if (cursor_to_ll) {
682                         tt_putp(cursor_to_ll);
683                 } else if (cursor_address) {
684                         tt_putparm(cursor_address, lines, lines - 1, 0);
685                 } else if (row_address) {
686                         tt_putparm(row_address, 1, lines - 1, 0);
687                 } else {
688                         for (i = 1; i < lines; i++) {
689                                 tt_putp(scroll_forward);
690                         }
691                 }
692                 for (i = 1; i < lines; i++) {
693                         tt_putp(scroll_forward);
694                 }
695                 if (can_go_home) {
696                         go_home();
697                 } else {
698                         for (i = 1; i < lines; i++) {
699                                 tt_putp(cursor_up);
700                         }
701                 }
702         } else {
703                 can_clear_screen = FALSE;
704                 return;
705         }
706         char_count = line_count = 0;
707         can_clear_screen = TRUE;
708 }
709
710 /*
711 **      wait_here()
712 **
713 **      read one character from the input stream
714 **      If the terminal is not in RAW mode then this function will
715 **      wait for a <cr> or <lf>.
716 */
717 int
718 wait_here(void)
719 {
720         char ch, cc[64];
721         char message[16];
722         int i, j;
723
724         for (i = 0; i < (int) sizeof(cc); i++) {
725                 cc[i] = ch = getchp(STRIP_PARITY);
726                 if (ch == '\r' || ch == '\n') {
727                         put_crlf();
728                         char_sent = 0;
729                         return cc[i ? i - 1 : 0];
730                 }
731                 if (ch >= ' ') {
732                         if (stty_query(TTY_CHAR_MODE)) {
733                                 put_crlf();
734                                 char_sent = 0;
735                                 return ch;
736                         }
737                         continue;
738                 }
739                 if (ch == 023) {        /* Control S */
740                         /* ignore control S, but tell me about it */
741                         while (ch == 023 || ch == 021) {
742                                 ch = getchp(STRIP_PARITY);
743                                 if (i < (int) sizeof(cc))
744                                         cc[++i] = ch;
745                         }
746                         put_str("\nThe terminal sent a ^S -");
747                         for (j = 0; j <= i; j++) {
748                                 sprintf(message, " %02X", cc[j] & 0xFF);
749                                 put_str(message);
750                         }
751                         put_crlf();
752                         i = -1;
753                 } else if (ch != 021) { /* Not Control Q */
754                         /* could be abort character */
755                         spin_flush();
756                         if (tty_can_sync == SYNC_TESTED) {
757                                 (void) tty_sync_error();
758                         } else {
759                                 put_str("\n? ");
760                         }
761                 }
762         }
763         return '?';
764 }
765
766
767 /*
768 **      read_string(buffer, length)
769 **
770 **      Read a string of characters from the input stream.
771 */
772 void
773 read_string(
774         char *buf,
775         int length)
776 {
777         int ch, i;
778
779         for (i = 0; i < length - 1; ) {
780                 ch = getchp(STRIP_PARITY);
781                 if (ch == '\r' || ch == '\n') {
782                         break;
783                 }
784                 if (ch == '\b' || ch == 127) {
785                         if (i) {
786                                 putchp('\b');
787                                 putchp(' ');
788                                 putchp('\b');
789                                 i--;
790                         }
791                 } else {
792                         buf[i++] = ch;
793                         putchp(ch);
794                 }
795         }
796         buf[i] = '\0';
797         put_crlf();
798         char_sent = 0;
799 }
800
801 /*
802 **      maybe_wait(lines)
803 **
804 **      wait if near the end of the screen, then clear screen
805 */
806 void 
807 maybe_wait(int n)
808 {
809         if (line_count + n >= lines) {
810                 if (char_sent != 0) {
811                         ptext("Go? ");
812                         (void) wait_here();
813                 }
814                 put_clear();
815         } else {
816                 put_crlf();
817         }
818 }