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