git-svn-id: svn://svn.berlios.de/mutt-ng/trunk@59 e385b8ad-14ed-0310-8656-cc95a2468c6d
[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   va_list ap;
300
301   va_start (ap, fmt);
302   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
303   va_end (ap);
304   
305   dprint (1, (debugfile, "%s\n", Errorbuf));
306   mutt_format_string (Errorbuf, sizeof (Errorbuf),
307                       0, COLS-2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
308
309   if (!option (OPTKEEPQUIET))
310   {
311     BEEP ();
312     SETCOLOR (MT_COLOR_ERROR);
313     mvaddstr (LINES-1, 0, Errorbuf);
314     clrtoeol ();
315     SETCOLOR (MT_COLOR_NORMAL);
316     mutt_refresh ();
317   }
318
319   set_option (OPTMSGERR);
320 }
321
322 void mutt_curses_message (const char *fmt, ...)
323 {
324   va_list ap;
325
326   va_start (ap, fmt);
327   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
328   va_end (ap);
329
330   mutt_format_string (Errorbuf, sizeof (Errorbuf),
331                       0, COLS-2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
332
333   if (!option (OPTKEEPQUIET))
334   {
335     SETCOLOR (MT_COLOR_MESSAGE);
336     mvaddstr (LINES - 1, 0, Errorbuf);
337     clrtoeol ();
338     SETCOLOR (MT_COLOR_NORMAL);
339     mutt_refresh ();
340   }
341
342   unset_option (OPTMSGERR);
343 }
344
345 void mutt_show_error (void)
346 {
347   if (option (OPTKEEPQUIET))
348     return;
349   
350   SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
351   CLEARLINE (LINES-1);
352   addstr (Errorbuf);
353   SETCOLOR (MT_COLOR_NORMAL);
354 }
355
356 void mutt_endwin (const char *msg)
357 {
358   if (!option (OPTNOCURSES))
359   {
360     CLEARLINE (LINES - 1);
361     
362     attrset (A_NORMAL);
363     mutt_refresh ();
364     endwin ();
365   }
366   
367   if (msg && *msg)
368   {
369     puts (msg);
370     fflush (stdout);
371   }
372 }
373
374 void mutt_perror (const char *s)
375 {
376   char *p = strerror (errno);
377
378   dprint (1, (debugfile, "%s: %s (errno = %d)\n", s, 
379       p ? p : "unknown error", errno));
380   mutt_error ("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
381 }
382
383 int mutt_any_key_to_continue (const char *s)
384 {
385   struct termios t;
386   struct termios old;
387   int f, ch;
388
389   f = open ("/dev/tty", O_RDONLY);
390   tcgetattr (f, &t);
391   memcpy ((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */
392   t.c_lflag &= ~(ICANON | ECHO);
393   t.c_cc[VMIN] = 1;
394   t.c_cc[VTIME] = 0;
395   tcsetattr (f, TCSADRAIN, &t);
396   fflush (stdout);
397   if (s)
398     fputs (s, stdout);
399   else
400     fputs (_("Press any key to continue..."), stdout);
401   fflush (stdout);
402   ch = fgetc (stdin);
403   fflush (stdin);
404   tcsetattr (f, TCSADRAIN, &old);
405   close (f);
406   fputs ("\r\n", stdout);
407   mutt_clear_error ();
408   return (ch);
409 }
410
411 int mutt_do_pager (const char *banner,
412                    const char *tempfile,
413                    int do_color,
414                    pager_t *info)
415 {
416   int rc;
417   
418   if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
419     rc = mutt_pager (banner, tempfile, do_color, info);
420   else
421   {
422     char cmd[STRING];
423     
424     mutt_endwin (NULL);
425     mutt_expand_file_fmt (cmd, sizeof(cmd), Pager, tempfile);
426     if (mutt_system (cmd) == -1)
427     {
428       mutt_error (_("Error running \"%s\"!"), cmd);
429       rc = -1;
430     }
431     else
432       rc = 0;
433     mutt_unlink (tempfile);
434   }
435
436   return rc;
437 }
438
439 int _mutt_enter_fname (const char *prompt, char *buf, size_t blen, int *redraw, int buffy, int multiple, char ***files, int *numfiles)
440 {
441   event_t ch;
442
443   mvaddstr (LINES-1, 0, (char *) prompt);
444   addstr (_(" ('?' for list): "));
445   if (buf[0])
446     addstr (buf);
447   clrtoeol ();
448   mutt_refresh ();
449
450   ch = mutt_getch();
451   if (ch.ch == -1)
452   {
453     CLEARLINE (LINES-1);
454     return (-1);
455   }
456   else if (ch.ch == '?')
457   {
458     mutt_refresh ();
459     buf[0] = 0;
460     _mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0), 
461                        files, numfiles);
462     *redraw = REDRAW_FULL;
463   }
464   else
465   {
466     char *pc = safe_malloc (mutt_strlen (prompt) + 3);
467
468     sprintf (pc, "%s: ", prompt);        /* __SPRINTF_CHECKED__ */
469     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
470     if (_mutt_get_field (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files, numfiles)
471         != 0)
472       buf[0] = 0;
473     MAYBE_REDRAW (*redraw);
474     FREE (&pc);
475   }
476
477   return 0;
478 }
479
480 void mutt_ungetch (int ch, int op)
481 {
482   event_t tmp;
483
484   tmp.ch = ch;
485   tmp.op = op;
486
487   if (UngetCount >= UngetBufLen)
488     safe_realloc (&KeyEvent, (UngetBufLen += 128) * sizeof(event_t));
489
490   KeyEvent[UngetCount++] = tmp;
491 }
492
493 void mutt_flushinp (void)
494 {
495   UngetCount = 0;
496   flushinp ();
497 }
498
499 #if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
500 /* The argument can take 3 values:
501  * -1: restore the value of the last call
502  *  0: make the cursor invisible
503  *  1: make the cursor visible
504  */
505 void mutt_curs_set (int cursor)
506 {
507   static int SavedCursor = 1;
508   
509   if (cursor < 0)
510     cursor = SavedCursor;
511   else
512     SavedCursor = cursor;
513   
514   if (curs_set (cursor) == ERR) {
515     if (cursor == 1)        /* cnorm */
516       curs_set (2);        /* cvvis */
517   }
518 }
519 #endif
520
521 int mutt_multi_choice (char *prompt, char *letters)
522 {
523   event_t ch;
524   int choice;
525   char *p;
526
527   mvaddstr (LINES - 1, 0, prompt);
528   clrtoeol ();
529   FOREVER
530   {
531     mutt_refresh ();
532     ch  = mutt_getch ();
533     if (ch.ch == -1 || CI_is_return (ch.ch))
534     {
535       choice = -1;
536       break;
537     }
538     else
539     {
540       p = strchr (letters, ch.ch);
541       if (p)
542       {
543         choice = p - letters + 1;
544         break;
545       }
546       else if (ch.ch <= '9' && ch.ch > '0')
547       {
548         choice = ch.ch - '0';
549         if (choice <= mutt_strlen (letters))
550           break;
551       }
552     }
553     BEEP ();
554   }
555   CLEARLINE (LINES - 1);
556   mutt_refresh ();
557   return choice;
558 }
559
560 /*
561  * addwch would be provided by an up-to-date curses library
562  */
563
564 int mutt_addwch (wchar_t wc)
565 {
566   char buf[MB_LEN_MAX*2];
567   mbstate_t mbstate;
568   size_t n1, n2;
569
570   memset (&mbstate, 0, sizeof (mbstate));
571   if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t)(-1) ||
572       (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t)(-1))
573     return -1; /* ERR */
574   else
575     return addstr (buf);
576 }
577
578
579 /*
580  * This formats a string, a bit like
581  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
582  * except that the widths refer to the number of character cells
583  * when printed.
584  */
585
586 void mutt_format_string (char *dest, size_t destlen,
587                          int min_width, int max_width,
588                          int right_justify, char m_pad_char,
589                          const char *s, size_t n,
590                          int arboreal)
591 {
592   char *p;
593   int w;
594   size_t k, k2;
595   char scratch[MB_LEN_MAX];
596
597   --destlen;
598   p = dest;
599   for (; n; s+=1, n-=1)
600   {
601     w = 1;
602     k2 = 1;
603     if (w > max_width)
604         break;
605     min_width -= w;
606     max_width -= w;
607     strncpy (p, s, k2);
608     p += k2;            
609     destlen -= k2;
610   }
611   w = (int)destlen < min_width ? destlen : min_width;
612   if (w <= 0)
613     *p = '\0';
614   else if (right_justify)
615   {
616     p[w] = '\0';
617     while (--p >= dest)
618       p[w] = *p;
619     while (--w >= 0)
620       dest[w] = m_pad_char;
621   }
622   else
623   {
624     while (--w >= 0)
625       *p++ = m_pad_char;
626     *p = '\0';
627   }
628 }
629
630 /*
631  * This formats a string rather like
632  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
633  *   snprintf (dest, destlen, fmt, s);
634  * except that the numbers in the conversion specification refer to
635  * the number of character cells when printed.
636  */
637
638 static void mutt_format_s_x (char *dest,
639                              size_t destlen,
640                              const char *prefix,
641                              const char *s,
642                              int arboreal)
643 {
644   int right_justify = 1;
645   char *p;
646   int min_width;
647   int max_width = INT_MAX;
648
649   if (*prefix == '-')
650     ++prefix, right_justify = 0;
651   min_width = strtol (prefix, &p, 10);
652   if (*p == '.')
653   {
654     prefix = p + 1;
655     max_width = strtol (prefix, &p, 10);
656     if (p <= prefix)
657       max_width = INT_MAX;
658   }
659
660   mutt_format_string (dest, destlen, min_width, max_width,
661                       right_justify, ' ', s, mutt_strlen (s), arboreal);
662 }
663
664 void mutt_format_s (char *dest,
665                     size_t destlen,
666                     const char *prefix,
667                     const char *s)
668 {
669   mutt_format_s_x (dest, destlen, prefix, s, 0);
670 }
671
672 void mutt_format_s_tree (char *dest,
673                          size_t destlen,
674                          const char *prefix,
675                          const char *s)
676 {
677   mutt_format_s_x (dest, destlen, prefix, s, 1);
678 }
679
680 /*
681  * mutt_paddstr (n, s) is almost equivalent to
682  * mutt_format_string (bigbuf, big, n, n, 0, ' ', s, big, 0), addstr (bigbuf)
683  */
684
685 void mutt_paddstr (int n, const char *s)
686 {
687   size_t len = mutt_strlen (s);
688
689   for (; len && *s; s += 1, len -= 1)
690   {
691     if (1 > n)
692       break;
693     addnstr ((char *)s, 1);
694     n -= 1;
695   }
696   while (n-- > 0)
697     addch (' ');
698 }
699
700 /*
701  * mutt_strwidth is like mutt_strlen except that it returns the width
702  * refering to the number of characters cells.
703  * AK: since we remove all that multibyte-character-stuff, it is equal to mutt_strlen
704  */
705
706 int mutt_strwidth (const char *s)
707 {
708   return mutt_strlen(s);
709 }