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