Andreas Krennmair:
[apps/madmutt.git] / curs_lib.c
1 /*
2  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
3  * Copyright (C) 2004 g10 Code GmbH
4  * 
5  *     This program is free software; you can redistribute it and/or modify
6  *     it under the terms of the GNU General Public License as published by
7  *     the Free Software Foundation; either version 2 of the License, or
8  *     (at your option) any later version.
9  * 
10  *     This program is distributed in the hope that it will be useful,
11  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *     GNU General Public License for more details.
14  * 
15  *     You should have received a copy of the GNU General Public License
16  *     along with this program; if not, write to the Free Software
17  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
18  */
19
20 #if HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include "mutt.h"
25 #include "mutt_menu.h"
26 #include "mutt_curses.h"
27 #include "pager.h"
28 #include "mbyte.h"
29
30 #include <termios.h>
31 #include <sys/types.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <ctype.h>
38
39 #ifdef HAVE_LANGINFO_YESEXPR
40 #include <langinfo.h>
41 #endif
42
43 /* not possible to unget more than one char under some curses libs, and it
44  * is impossible to unget function keys in SLang, so roll our own input
45  * buffering routines.
46  */
47 size_t UngetCount = 0;
48 static size_t UngetBufLen = 0;
49 static event_t *KeyEvent;
50
51 void mutt_refresh (void)
52 {
53   /* don't refresh when we are waiting for a child. */
54   if (option (OPTKEEPQUIET))
55     return;
56
57   /* don't refresh in the middle of macros unless necessary */
58   if (UngetCount && !option (OPTFORCEREFRESH))
59     return;
60
61   /* else */
62   refresh ();
63 }
64
65 /* Make sure that the next refresh does a full refresh.  This could be
66    optmized by not doing it at all if DISPLAY is set as this might
67    indicate that a GUI based pinentry was used.  Having an option to
68    customize this is of course the Mutt way.  */
69 void mutt_need_hard_redraw (void)
70 {
71   if (!getenv ("DISPLAY")) {
72     keypad (stdscr, TRUE);
73     clearok (stdscr, TRUE);
74     set_option (OPTNEEDREDRAW);
75   }
76 }
77
78 event_t mutt_getch (void)
79 {
80   int ch;
81   event_t err = { -1, OP_NULL }, ret;
82
83   if (!option (OPTUNBUFFEREDINPUT) && UngetCount)
84     return (KeyEvent[--UngetCount]);
85
86   SigInt = 0;
87
88   mutt_allow_interrupt (1);
89 #ifdef KEY_RESIZE
90   /* ncurses 4.2 sends this when the screen is resized */
91   ch = KEY_RESIZE;
92   while (ch == KEY_RESIZE)
93 #endif /* KEY_RESIZE */
94     ch = getch ();
95   mutt_allow_interrupt (0);
96
97   if (SigInt)
98     mutt_query_exit ();
99
100   if (ch == ERR)
101     return err;
102
103   if ((ch & 0x80) && option (OPTMETAKEY)) {
104     /* send ALT-x as ESC-x */
105     ch &= ~0x80;
106     mutt_ungetch (ch, 0);
107     ret.ch = '\033';
108     ret.op = 0;
109     return ret;
110   }
111
112   ret.ch = ch;
113   ret.op = 0;
114   return (ch == ctrl ('G') ? err : ret);
115 }
116
117 int _mutt_get_field ( /* const */ char *field, char *buf, size_t buflen,
118                      int complete, int multiple, char ***files, int *numfiles)
119 {
120   int ret;
121   int x, y;
122
123   ENTER_STATE *es = mutt_new_enter_state ();
124
125   do {
126     CLEARLINE (LINES - 1);
127     addstr (field);
128     mutt_refresh ();
129     getyx (stdscr, y, x);
130     ret =
131       _mutt_enter_string (buf, buflen, y, x, complete, multiple, files,
132                           numfiles, es);
133   }
134   while (ret == 1);
135   CLEARLINE (LINES - 1);
136   mutt_free_enter_state (&es);
137
138   return (ret);
139 }
140
141 int mutt_get_password (char *msg, char *buf, size_t buflen)
142 {
143   int rc;
144
145   CLEARLINE (LINES - 1);
146   addstr (msg);
147   set_option (OPTUNBUFFEREDINPUT);
148   rc = mutt_enter_string (buf, buflen, LINES - 1, mutt_strlen (msg), M_PASS);
149   unset_option (OPTUNBUFFEREDINPUT);
150   CLEARLINE (LINES - 1);
151   return (rc);
152 }
153
154 void mutt_clear_error (void)
155 {
156   Errorbuf[0] = 0;
157   if (!option (OPTNOCURSES))
158     CLEARLINE (LINES - 1);
159 }
160
161 static void fix_end_of_file (const char *data)
162 {
163   FILE *fp;
164   int c;
165
166   if ((fp = safe_fopen (data, "a+")) == NULL)
167     return;
168   fseek (fp, -1, SEEK_END);
169   if ((c = fgetc (fp)) != '\n')
170     fputc ('\n', fp);
171   safe_fclose (&fp);
172 }
173
174 void mutt_edit_file (const char *editor, const char *data)
175 {
176   char cmd[LONG_STRING];
177
178   mutt_endwin (NULL);
179   mutt_expand_file_fmt (cmd, sizeof (cmd), editor, data);
180   if (mutt_system (cmd) == -1)
181     mutt_error (_("Error running \"%s\"!"), cmd);
182   fix_end_of_file (data);
183   keypad (stdscr, TRUE);
184   clearok (stdscr, TRUE);
185 }
186
187 int mutt_yesorno (const char *msg, int def)
188 {
189   event_t ch;
190   char *yes = _("yes");
191   char *no = _("no");
192   char *answer_string;
193   size_t answer_string_len;
194
195 #ifdef HAVE_LANGINFO_YESEXPR
196   char *expr;
197   regex_t reyes;
198   regex_t reno;
199   int reyes_ok;
200   int reno_ok;
201   char answer[2];
202
203   answer[1] = 0;
204
205   reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
206     !regcomp (&reyes, expr, REG_NOSUB | REG_EXTENDED);
207   reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
208     !regcomp (&reno, expr, REG_NOSUB | REG_EXTENDED);
209 #endif
210
211   CLEARLINE (LINES - 1);
212
213   /*
214    * In order to prevent the default answer to the question to wrapped
215    * around the screen in the even the question is wider than the screen,
216    * ensure there is enough room for the answer and truncate the question
217    * to fit.
218    */
219   answer_string = safe_malloc (COLS + 1);
220   snprintf (answer_string, COLS + 1, " ([%s]/%s): ", def == M_YES ? yes : no,
221             def == M_YES ? no : yes);
222   answer_string_len = strlen (answer_string);
223   printw ("%.*s%s", COLS - answer_string_len, msg, answer_string);
224   FREE (&answer_string);
225
226   FOREVER {
227     mutt_refresh ();
228     ch = mutt_getch ();
229     if (CI_is_return (ch.ch))
230       break;
231     if (ch.ch == -1) {
232       def = -1;
233       break;
234     }
235
236 #ifdef HAVE_LANGINFO_YESEXPR
237     answer[0] = ch.ch;
238     if (reyes_ok ? (regexec (&reyes, answer, 0, 0, 0) == 0) :
239 #else
240     if (
241 #endif
242          (tolower (ch.ch) == 'y')) {
243       def = M_YES;
244       break;
245     }
246     else if (
247 #ifdef HAVE_LANGINFO_YESEXPR
248               reno_ok ? (regexec (&reno, answer, 0, 0, 0) == 0) :
249 #endif
250               (tolower (ch.ch) == 'n')) {
251       def = M_NO;
252       break;
253     }
254     else {
255       BEEP ();
256     }
257   }
258
259 #ifdef HAVE_LANGINFO_YESEXPR
260   if (reyes_ok)
261     regfree (&reyes);
262   if (reno_ok)
263     regfree (&reno);
264 #endif
265
266   if (def != -1) {
267     addstr ((char *) (def == M_YES ? yes : no));
268     mutt_refresh ();
269   }
270   return (def);
271 }
272
273 /* this function is called when the user presses the abort key */
274 void mutt_query_exit (void)
275 {
276   mutt_flushinp ();
277   curs_set (1);
278   if (Timeout)
279     timeout (-1);               /* restore blocking operation */
280   if (mutt_yesorno (_("Exit Mutt-ng?"), M_YES) == M_YES) {
281     endwin ();
282     exit (1);
283   }
284   mutt_clear_error ();
285   mutt_curs_set (-1);
286   SigInt = 0;
287 }
288
289 void mutt_curses_error (const char *fmt, ...)
290 {
291   char TmpErrorbuf[STRING];
292   va_list ap;
293
294   va_start (ap, fmt);
295   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
296   va_end (ap);
297
298   dprint (1, (debugfile, "%s\n", Errorbuf));
299   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
300                       0, COLS - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
301   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
302
303   if (!option (OPTKEEPQUIET)) {
304     BEEP ();
305     SETCOLOR (MT_COLOR_ERROR);
306     mvaddstr (LINES - 1, 0, Errorbuf);
307     clrtoeol ();
308     SETCOLOR (MT_COLOR_NORMAL);
309     mutt_refresh ();
310   }
311
312   set_option (OPTMSGERR);
313 }
314
315 void mutt_curses_message (const char *fmt, ...)
316 {
317   char TmpErrorbuf[STRING];
318   va_list ap;
319
320   va_start (ap, fmt);
321   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
322   va_end (ap);
323
324   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
325                       0, COLS - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
326   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
327
328   if (!option (OPTKEEPQUIET)) {
329     SETCOLOR (MT_COLOR_MESSAGE);
330     mvaddstr (LINES - 1, 0, Errorbuf);
331     clrtoeol ();
332     SETCOLOR (MT_COLOR_NORMAL);
333     mutt_refresh ();
334   }
335
336   unset_option (OPTMSGERR);
337 }
338
339 void mutt_show_error (void)
340 {
341   if (option (OPTKEEPQUIET))
342     return;
343
344   SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
345   CLEARLINE (LINES - 1);
346   addstr (Errorbuf);
347   SETCOLOR (MT_COLOR_NORMAL);
348 }
349
350 void mutt_endwin (const char *msg)
351 {
352   if (!option (OPTNOCURSES)) {
353     CLEARLINE (LINES - 1);
354
355     attrset (A_NORMAL);
356     mutt_refresh ();
357     endwin ();
358   }
359
360   if (msg && *msg) {
361     puts (msg);
362     fflush (stdout);
363   }
364 }
365
366 void mutt_perror (const char *s)
367 {
368   char *p = strerror (errno);
369
370   dprint (1, (debugfile, "%s: %s (errno = %d)\n", s,
371               p ? p : "unknown error", errno));
372   mutt_error ("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
373 }
374
375 int mutt_any_key_to_continue (const char *s)
376 {
377   struct termios t;
378   struct termios old;
379   int f, ch;
380
381   f = open ("/dev/tty", O_RDONLY);
382   tcgetattr (f, &t);
383   memcpy ((void *) &old, (void *) &t, sizeof (struct termios)); /* save original state */
384   t.c_lflag &= ~(ICANON | ECHO);
385   t.c_cc[VMIN] = 1;
386   t.c_cc[VTIME] = 0;
387   tcsetattr (f, TCSADRAIN, &t);
388   fflush (stdout);
389   if (s)
390     fputs (s, stdout);
391   else
392     fputs (_("Press any key to continue..."), stdout);
393   fflush (stdout);
394   ch = fgetc (stdin);
395   fflush (stdin);
396   tcsetattr (f, TCSADRAIN, &old);
397   close (f);
398   fputs ("\r\n", stdout);
399   mutt_clear_error ();
400   return (ch);
401 }
402
403 int mutt_do_pager (const char *banner,
404                    const char *tempfile, int do_color, pager_t * info)
405 {
406   int rc;
407
408   if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
409     rc = mutt_pager (banner, tempfile, do_color, info);
410   else {
411     char cmd[STRING];
412
413     mutt_endwin (NULL);
414     mutt_expand_file_fmt (cmd, sizeof (cmd), Pager, tempfile);
415     if (mutt_system (cmd) == -1) {
416       mutt_error (_("Error running \"%s\"!"), cmd);
417       rc = -1;
418     }
419     else
420       rc = 0;
421     mutt_unlink (tempfile);
422   }
423
424   return rc;
425 }
426
427 int _mutt_enter_fname (const char *prompt, char *buf, size_t blen,
428                        int *redraw, int buffy, int multiple, char ***files,
429                        int *numfiles)
430 {
431   event_t ch;
432
433   mvaddstr (LINES - 1, 0, (char *) prompt);
434   addstr (_(" ('?' for list): "));
435   if (buf[0])
436     addstr (buf);
437   clrtoeol ();
438   mutt_refresh ();
439
440   ch = mutt_getch ();
441   if (ch.ch == -1) {
442     CLEARLINE (LINES - 1);
443     return (-1);
444   }
445   else if (ch.ch == '?') {
446     mutt_refresh ();
447     buf[0] = 0;
448     _mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0),
449                        files, numfiles);
450     *redraw = REDRAW_FULL;
451   }
452   else {
453     char *pc = safe_malloc (mutt_strlen (prompt) + 3);
454
455     sprintf (pc, "%s: ", prompt);       /* __SPRINTF_CHECKED__ */
456     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
457     if (_mutt_get_field
458         (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files,
459          numfiles)
460         != 0)
461       buf[0] = 0;
462     MAYBE_REDRAW (*redraw);
463     FREE (&pc);
464   }
465
466   return 0;
467 }
468
469 void mutt_ungetch (int ch, int op)
470 {
471   event_t tmp;
472
473   tmp.ch = ch;
474   tmp.op = op;
475
476   if (UngetCount >= UngetBufLen)
477     safe_realloc (&KeyEvent, (UngetBufLen += 128) * sizeof (event_t));
478
479   KeyEvent[UngetCount++] = tmp;
480 }
481
482 void mutt_flushinp (void)
483 {
484   UngetCount = 0;
485   flushinp ();
486 }
487
488 #if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
489 /* The argument can take 3 values:
490  * -1: restore the value of the last call
491  *  0: make the cursor invisible
492  *  1: make the cursor visible
493  */
494 void mutt_curs_set (int cursor)
495 {
496   static int SavedCursor = 1;
497
498   if (cursor < 0)
499     cursor = SavedCursor;
500   else
501     SavedCursor = cursor;
502
503   if (curs_set (cursor) == ERR) {
504     if (cursor == 1)            /* cnorm */
505       curs_set (2);             /* cvvis */
506   }
507 }
508 #endif
509
510 int mutt_multi_choice (char *prompt, char *letters)
511 {
512   event_t ch;
513   int choice;
514   char *p;
515
516   mvaddstr (LINES - 1, 0, prompt);
517   clrtoeol ();
518   FOREVER {
519     mutt_refresh ();
520     ch = mutt_getch ();
521     if (ch.ch == -1 || CI_is_return (ch.ch)) {
522       choice = -1;
523       break;
524     }
525     else {
526       p = strchr (letters, ch.ch);
527       if (p) {
528         choice = p - letters + 1;
529         break;
530       }
531       else if (ch.ch <= '9' && ch.ch > '0') {
532         choice = ch.ch - '0';
533         if (choice <= mutt_strlen (letters))
534           break;
535       }
536     }
537     BEEP ();
538   }
539   CLEARLINE (LINES - 1);
540   mutt_refresh ();
541   return choice;
542 }
543
544 /*
545  * addwch would be provided by an up-to-date curses library
546  */
547
548 int mutt_addwch (wchar_t wc)
549 {
550   char buf[MB_LEN_MAX * 2];
551   mbstate_t mbstate;
552   size_t n1, n2;
553
554   memset (&mbstate, 0, sizeof (mbstate));
555   if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t) (-1) ||
556       (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t) (-1))
557     return -1;                  /* ERR */
558   else
559     return addstr (buf);
560 }
561
562
563 /*
564  * This formats a string, a bit like
565  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
566  * except that the widths refer to the number of character cells
567  * when printed.
568  */
569
570 void mutt_format_string (char *dest, size_t destlen,
571                          int min_width, int max_width,
572                          int right_justify, char m_pad_char,
573                          const char *s, size_t n, int arboreal)
574 {
575   char *p;
576   wchar_t wc;
577   int w;
578   size_t k, k2;
579   char scratch[MB_LEN_MAX];
580   mbstate_t mbstate1, mbstate2;
581
582   memset (&mbstate1, 0, sizeof (mbstate1));
583   memset (&mbstate2, 0, sizeof (mbstate2));
584   --destlen;
585   p = dest;
586   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k) {
587     if (k == (size_t) (-1) || k == (size_t) (-2)) {
588       k = (k == (size_t) (-1)) ? 1 : n;
589       wc = replacement_char ();
590     }
591     if (arboreal && wc < M_TREE_MAX)
592       w = 1;                    /* hack */
593     else {
594       if (!IsWPrint (wc))
595         wc = '?';
596       w = wcwidth (wc);
597     }
598     if (w >= 0) {
599       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
600         break;
601       min_width -= w;
602       max_width -= w;
603       strncpy (p, scratch, k2);
604       p += k2;
605       destlen -= k2;
606     }
607   }
608   w = (int) destlen < min_width ? destlen : min_width;
609   if (w <= 0)
610     *p = '\0';
611   else if (right_justify) {
612     p[w] = '\0';
613     while (--p >= dest)
614       p[w] = *p;
615     while (--w >= 0)
616       dest[w] = m_pad_char;
617   }
618   else {
619     while (--w >= 0)
620       *p++ = m_pad_char;
621     *p = '\0';
622   }
623 }
624
625 /*
626  * This formats a string rather like
627  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
628  *   snprintf (dest, destlen, fmt, s);
629  * except that the numbers in the conversion specification refer to
630  * the number of character cells when printed.
631  */
632
633 static void mutt_format_s_x (char *dest,
634                              size_t destlen,
635                              const char *prefix, const char *s, int arboreal)
636 {
637   int right_justify = 1;
638   char *p;
639   int min_width;
640   int max_width = INT_MAX;
641
642   if (*prefix == '-')
643     ++prefix, right_justify = 0;
644   min_width = strtol (prefix, &p, 10);
645   if (*p == '.') {
646     prefix = p + 1;
647     max_width = strtol (prefix, &p, 10);
648     if (p <= prefix)
649       max_width = INT_MAX;
650   }
651
652   mutt_format_string (dest, destlen, min_width, max_width,
653                       right_justify, ' ', s, mutt_strlen (s), arboreal);
654 }
655
656 void mutt_format_s (char *dest,
657                     size_t destlen, const char *prefix, const char *s)
658 {
659   mutt_format_s_x (dest, destlen, prefix, s, 0);
660 }
661
662 void mutt_format_s_tree (char *dest,
663                          size_t destlen, const char *prefix, const char *s)
664 {
665   mutt_format_s_x (dest, destlen, prefix, s, 1);
666 }
667
668 /*
669  * mutt_paddstr (n, s) is almost equivalent to
670  * mutt_format_string (bigbuf, big, n, n, 0, ' ', s, big, 0), addstr (bigbuf)
671  */
672
673 void mutt_paddstr (int n, const char *s)
674 {
675   wchar_t wc;
676   int w;
677   size_t k;
678   size_t len = mutt_strlen (s);
679   mbstate_t mbstate;
680
681   memset (&mbstate, 0, sizeof (mbstate));
682   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k) {
683     if (k == (size_t) (-1) || k == (size_t) (-2)) {
684       k = (k == (size_t) (-1)) ? 1 : len;
685       wc = replacement_char ();
686     }
687     if (!IsWPrint (wc))
688       wc = '?';
689     w = wcwidth (wc);
690     if (w >= 0) {
691       if (w > n)
692         break;
693       addnstr ((char *) s, k);
694       n -= w;
695     }
696   }
697   while (n-- > 0)
698     addch (' ');
699 }
700
701 /*
702  * mutt_strwidth is like mutt_strlen except that it returns the width
703  * refering to the number of characters cells.
704  */
705
706 int mutt_strwidth (const char *s)
707 {
708   wchar_t wc;
709   int w;
710   size_t k, n;
711   mbstate_t mbstate;
712
713   if (!s)
714     return 0;
715
716   n = mutt_strlen (s);
717
718   memset (&mbstate, 0, sizeof (mbstate));
719   for (w = 0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k) {
720     if (k == (size_t) (-1) || k == (size_t) (-2)) {
721       k = (k == (size_t) (-1)) ? 1 : n;
722       wc = replacement_char ();
723     }
724     if (!IsWPrint (wc))
725       wc = '?';
726     w += wcwidth (wc);
727   }
728   return w;
729 }