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