Rocco Rutte:
[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   {
73     keypad (stdscr, TRUE);
74     clearok (stdscr, TRUE);
75     set_option (OPTNEEDREDRAW);
76   }
77 }
78
79 event_t mutt_getch (void)
80 {
81   int ch;
82   event_t err = {-1, OP_NULL }, ret;
83
84   if (!option(OPTUNBUFFEREDINPUT) && UngetCount)
85     return (KeyEvent[--UngetCount]);
86
87   SigInt = 0;
88
89   mutt_allow_interrupt (1);
90 #ifdef KEY_RESIZE
91   /* ncurses 4.2 sends this when the screen is resized */
92   ch = KEY_RESIZE;
93   while (ch == KEY_RESIZE)
94 #endif /* KEY_RESIZE */
95     ch = getch ();
96   mutt_allow_interrupt (0);
97
98   if (SigInt)
99     mutt_query_exit ();
100
101   if(ch == ERR)
102     return err;
103   
104   if ((ch & 0x80) && option (OPTMETAKEY))
105   {
106     /* send ALT-x as ESC-x */
107     ch &= ~0x80;
108     mutt_ungetch (ch, 0);
109     ret.ch = '\033';
110     ret.op = 0;
111     return ret;
112   }
113
114   ret.ch = ch;
115   ret.op = 0;
116   return (ch == ctrl ('G') ? err : ret);
117 }
118
119 int _mutt_get_field (/* const */ char *field, char *buf, size_t buflen, int complete, int multiple, char ***files, int *numfiles)
120 {
121   int ret;
122   int x, y;
123
124   ENTER_STATE *es = mutt_new_enter_state();
125   
126   do
127   {
128     CLEARLINE (LINES-1);
129     addstr (field);
130     mutt_refresh ();
131     getyx (stdscr, y, x);
132     ret = _mutt_enter_string (buf, buflen, y, x, complete, multiple, files, 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, def == M_YES ? no : yes);
221   answer_string_len = strlen (answer_string);
222   printw ("%.*s%s", COLS - answer_string_len, msg, answer_string);
223   FREE (&answer_string);
224
225   FOREVER
226   {
227     mutt_refresh ();
228     ch = mutt_getch ();
229     if (CI_is_return (ch.ch))
230       break;
231     if (ch.ch == -1)
232     {
233       def = -1;
234       break;
235     }
236
237 #ifdef HAVE_LANGINFO_YESEXPR
238     answer[0] = ch.ch;
239     if (reyes_ok ? 
240         (regexec (& reyes, answer, 0, 0, 0) == 0) :
241 #else
242     if (
243 #endif
244         (tolower (ch.ch) == 'y'))
245     {
246       def = M_YES;
247       break;
248     }
249     else if (
250 #ifdef HAVE_LANGINFO_YESEXPR
251              reno_ok ?
252              (regexec (& reno, answer, 0, 0, 0) == 0) :
253 #endif
254              (tolower (ch.ch) == 'n'))
255     {
256       def = M_NO;
257       break;
258     }
259     else
260     {
261       BEEP();
262     }
263   }
264
265 #ifdef HAVE_LANGINFO_YESEXPR    
266   if (reyes_ok)
267     regfree (& reyes);
268   if (reno_ok)
269     regfree (& reno);
270 #endif
271
272   if (def != -1)
273   {
274     addstr ((char *) (def == M_YES ? yes : no));
275     mutt_refresh ();
276   }
277   return (def);
278 }
279
280 /* this function is called when the user presses the abort key */
281 void mutt_query_exit (void)
282 {
283   mutt_flushinp ();
284   curs_set (1);
285   if (Timeout)
286     timeout (-1); /* restore blocking operation */
287   if (mutt_yesorno (_("Exit Mutt?"), M_YES) == M_YES)
288   {
289     endwin ();
290     exit (1);
291   }
292   mutt_clear_error();
293   mutt_curs_set (-1);
294   SigInt = 0;
295 }
296
297 void mutt_curses_error (const char *fmt, ...)
298 {
299   char TmpErrorbuf[STRING];
300   va_list ap;
301
302   va_start (ap, fmt);
303   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
304   va_end (ap);
305   
306   dprint (1, (debugfile, "%s\n", Errorbuf));
307   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
308                       0, COLS-2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
309   snprintf(Errorbuf,sizeof(Errorbuf),"%s",TmpErrorbuf); /* overkill */
310
311   if (!option (OPTKEEPQUIET))
312   {
313     BEEP ();
314     SETCOLOR (MT_COLOR_ERROR);
315     mvaddstr (LINES-1, 0, Errorbuf);
316     clrtoeol ();
317     SETCOLOR (MT_COLOR_NORMAL);
318     mutt_refresh ();
319   }
320
321   set_option (OPTMSGERR);
322 }
323
324 void mutt_curses_message (const char *fmt, ...)
325 {
326   char TmpErrorbuf[STRING];
327   va_list ap;
328
329   va_start (ap, fmt);
330   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
331   va_end (ap);
332
333   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
334                       0, COLS-2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
335   snprintf(Errorbuf,sizeof(Errorbuf),"%s",TmpErrorbuf); /* overkill */
336
337   if (!option (OPTKEEPQUIET))
338   {
339     SETCOLOR (MT_COLOR_MESSAGE);
340     mvaddstr (LINES - 1, 0, Errorbuf);
341     clrtoeol ();
342     SETCOLOR (MT_COLOR_NORMAL);
343     mutt_refresh ();
344   }
345
346   unset_option (OPTMSGERR);
347 }
348
349 void mutt_show_error (void)
350 {
351   if (option (OPTKEEPQUIET))
352     return;
353   
354   SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
355   CLEARLINE (LINES-1);
356   addstr (Errorbuf);
357   SETCOLOR (MT_COLOR_NORMAL);
358 }
359
360 void mutt_endwin (const char *msg)
361 {
362   if (!option (OPTNOCURSES))
363   {
364     CLEARLINE (LINES - 1);
365     
366     attrset (A_NORMAL);
367     mutt_refresh ();
368     endwin ();
369   }
370   
371   if (msg && *msg)
372   {
373     puts (msg);
374     fflush (stdout);
375   }
376 }
377
378 void mutt_perror (const char *s)
379 {
380   char *p = strerror (errno);
381
382   dprint (1, (debugfile, "%s: %s (errno = %d)\n", s, 
383       p ? p : "unknown error", errno));
384   mutt_error ("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
385 }
386
387 int mutt_any_key_to_continue (const char *s)
388 {
389   struct termios t;
390   struct termios old;
391   int f, ch;
392
393   f = open ("/dev/tty", O_RDONLY);
394   tcgetattr (f, &t);
395   memcpy ((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */
396   t.c_lflag &= ~(ICANON | ECHO);
397   t.c_cc[VMIN] = 1;
398   t.c_cc[VTIME] = 0;
399   tcsetattr (f, TCSADRAIN, &t);
400   fflush (stdout);
401   if (s)
402     fputs (s, stdout);
403   else
404     fputs (_("Press any key to continue..."), stdout);
405   fflush (stdout);
406   ch = fgetc (stdin);
407   fflush (stdin);
408   tcsetattr (f, TCSADRAIN, &old);
409   close (f);
410   fputs ("\r\n", stdout);
411   mutt_clear_error ();
412   return (ch);
413 }
414
415 int mutt_do_pager (const char *banner,
416                    const char *tempfile,
417                    int do_color,
418                    pager_t *info)
419 {
420   int rc;
421   
422   if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
423     rc = mutt_pager (banner, tempfile, do_color, info);
424   else
425   {
426     char cmd[STRING];
427     
428     mutt_endwin (NULL);
429     mutt_expand_file_fmt (cmd, sizeof(cmd), Pager, tempfile);
430     if (mutt_system (cmd) == -1)
431     {
432       mutt_error (_("Error running \"%s\"!"), cmd);
433       rc = -1;
434     }
435     else
436       rc = 0;
437     mutt_unlink (tempfile);
438   }
439
440   return rc;
441 }
442
443 int _mutt_enter_fname (const char *prompt, char *buf, size_t blen, int *redraw, int buffy, int multiple, char ***files, int *numfiles)
444 {
445   event_t ch;
446
447   mvaddstr (LINES-1, 0, (char *) prompt);
448   addstr (_(" ('?' for list): "));
449   if (buf[0])
450     addstr (buf);
451   clrtoeol ();
452   mutt_refresh ();
453
454   ch = mutt_getch();
455   if (ch.ch == -1)
456   {
457     CLEARLINE (LINES-1);
458     return (-1);
459   }
460   else if (ch.ch == '?')
461   {
462     mutt_refresh ();
463     buf[0] = 0;
464     _mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0), 
465                        files, numfiles);
466     *redraw = REDRAW_FULL;
467   }
468   else
469   {
470     char *pc = safe_malloc (mutt_strlen (prompt) + 3);
471
472     sprintf (pc, "%s: ", prompt);        /* __SPRINTF_CHECKED__ */
473     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
474     if (_mutt_get_field (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files, numfiles)
475         != 0)
476       buf[0] = 0;
477     MAYBE_REDRAW (*redraw);
478     FREE (&pc);
479   }
480
481   return 0;
482 }
483
484 void mutt_ungetch (int ch, int op)
485 {
486   event_t tmp;
487
488   tmp.ch = ch;
489   tmp.op = op;
490
491   if (UngetCount >= UngetBufLen)
492     safe_realloc (&KeyEvent, (UngetBufLen += 128) * sizeof(event_t));
493
494   KeyEvent[UngetCount++] = tmp;
495 }
496
497 void mutt_flushinp (void)
498 {
499   UngetCount = 0;
500   flushinp ();
501 }
502
503 #if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
504 /* The argument can take 3 values:
505  * -1: restore the value of the last call
506  *  0: make the cursor invisible
507  *  1: make the cursor visible
508  */
509 void mutt_curs_set (int cursor)
510 {
511   static int SavedCursor = 1;
512   
513   if (cursor < 0)
514     cursor = SavedCursor;
515   else
516     SavedCursor = cursor;
517   
518   if (curs_set (cursor) == ERR) {
519     if (cursor == 1)        /* cnorm */
520       curs_set (2);        /* cvvis */
521   }
522 }
523 #endif
524
525 int mutt_multi_choice (char *prompt, char *letters)
526 {
527   event_t ch;
528   int choice;
529   char *p;
530
531   mvaddstr (LINES - 1, 0, prompt);
532   clrtoeol ();
533   FOREVER
534   {
535     mutt_refresh ();
536     ch  = mutt_getch ();
537     if (ch.ch == -1 || CI_is_return (ch.ch))
538     {
539       choice = -1;
540       break;
541     }
542     else
543     {
544       p = strchr (letters, ch.ch);
545       if (p)
546       {
547         choice = p - letters + 1;
548         break;
549       }
550       else if (ch.ch <= '9' && ch.ch > '0')
551       {
552         choice = ch.ch - '0';
553         if (choice <= mutt_strlen (letters))
554           break;
555       }
556     }
557     BEEP ();
558   }
559   CLEARLINE (LINES - 1);
560   mutt_refresh ();
561   return choice;
562 }
563
564 /*
565  * addwch would be provided by an up-to-date curses library
566  */
567
568 int mutt_addwch (wchar_t wc)
569 {
570   char buf[MB_LEN_MAX*2];
571   mbstate_t mbstate;
572   size_t n1, n2;
573
574   memset (&mbstate, 0, sizeof (mbstate));
575   if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t)(-1) ||
576       (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t)(-1))
577     return -1; /* ERR */
578   else
579     return addstr (buf);
580 }
581
582
583 /*
584  * This formats a string, a bit like
585  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
586  * except that the widths refer to the number of character cells
587  * when printed.
588  */
589
590 void mutt_format_string (char *dest, size_t destlen,
591                          int min_width, int max_width,
592                          int right_justify, char m_pad_char,
593                          const char *s, size_t n,
594                          int arboreal)
595 {
596   char *p;
597   wchar_t wc;
598   int w;
599   size_t k, k2;
600   char scratch[MB_LEN_MAX];
601   mbstate_t mbstate1, mbstate2;
602
603   memset(&mbstate1, 0, sizeof (mbstate1));
604   memset(&mbstate2, 0, sizeof (mbstate2));
605   --destlen;
606   p = dest;
607   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k)
608   {
609     if (k == (size_t)(-1) || k == (size_t)(-2))
610     {
611       k = (k == (size_t)(-1)) ? 1 : n;
612       wc = replacement_char ();
613     }
614     if (arboreal && wc < M_TREE_MAX)
615       w = 1; /* hack */
616     else
617     {
618       if (!IsWPrint (wc))
619         wc = '?';
620       w = wcwidth (wc);
621     }
622     if (w >= 0)
623     {
624       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
625         break;
626       min_width -= w;
627       max_width -= w;
628       strncpy (p, scratch, k2);
629       p += k2;            
630       destlen -= k2;
631     }
632   }
633   w = (int)destlen < min_width ? destlen : min_width;
634   if (w <= 0)
635     *p = '\0';
636   else if (right_justify)
637   {
638     p[w] = '\0';
639     while (--p >= dest)
640       p[w] = *p;
641     while (--w >= 0)
642       dest[w] = m_pad_char;
643   }
644   else
645   {
646     while (--w >= 0)
647       *p++ = m_pad_char;
648     *p = '\0';
649   }
650 }
651
652 /*
653  * This formats a string rather like
654  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
655  *   snprintf (dest, destlen, fmt, s);
656  * except that the numbers in the conversion specification refer to
657  * the number of character cells when printed.
658  */
659
660 static void mutt_format_s_x (char *dest,
661                              size_t destlen,
662                              const char *prefix,
663                              const char *s,
664                              int arboreal)
665 {
666   int right_justify = 1;
667   char *p;
668   int min_width;
669   int max_width = INT_MAX;
670
671   if (*prefix == '-')
672     ++prefix, right_justify = 0;
673   min_width = strtol (prefix, &p, 10);
674   if (*p == '.')
675   {
676     prefix = p + 1;
677     max_width = strtol (prefix, &p, 10);
678     if (p <= prefix)
679       max_width = INT_MAX;
680   }
681
682   mutt_format_string (dest, destlen, min_width, max_width,
683                       right_justify, ' ', s, mutt_strlen (s), arboreal);
684 }
685
686 void mutt_format_s (char *dest,
687                     size_t destlen,
688                     const char *prefix,
689                     const char *s)
690 {
691   mutt_format_s_x (dest, destlen, prefix, s, 0);
692 }
693
694 void mutt_format_s_tree (char *dest,
695                          size_t destlen,
696                          const char *prefix,
697                          const char *s)
698 {
699   mutt_format_s_x (dest, destlen, prefix, s, 1);
700 }
701
702 /*
703  * mutt_paddstr (n, s) is almost equivalent to
704  * mutt_format_string (bigbuf, big, n, n, 0, ' ', s, big, 0), addstr (bigbuf)
705  */
706
707 void mutt_paddstr (int n, const char *s)
708 {
709   wchar_t wc;
710   int w;
711   size_t k;
712   size_t len = mutt_strlen (s);
713   mbstate_t mbstate;
714
715   memset (&mbstate, 0, sizeof (mbstate));
716   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k)
717   {
718     if (k == (size_t)(-1) || k == (size_t)(-2))
719     {
720       k = (k == (size_t)(-1)) ? 1 : len;
721       wc = replacement_char ();
722     }
723     if (!IsWPrint (wc))
724       wc = '?';
725     w = wcwidth (wc);
726     if (w >= 0)
727     {
728       if (w > n)
729         break;
730       addnstr ((char *)s, 1);
731       n -= w;
732     }
733   }
734   while (n-- > 0)
735     addch (' ');
736 }
737
738 /*
739  * mutt_strwidth is like mutt_strlen except that it returns the width
740  * refering to the number of characters cells.
741  */
742
743 int mutt_strwidth (const char *s)
744 {
745   wchar_t wc;
746   int w;
747   size_t k, n;
748   mbstate_t mbstate;
749
750   if (!s) return 0;
751
752   n = mutt_strlen (s);
753
754   memset (&mbstate, 0, sizeof (mbstate));
755   for (w=0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k)
756   {
757     if (k == (size_t)(-1) || k == (size_t)(-2))
758     {
759       k = (k == (size_t)(-1)) ? 1 : n;
760       wc = replacement_char ();
761     }
762     if (!IsWPrint (wc))
763       wc = '?';
764     w += wcwidth (wc);
765   }
766   return w;
767 }