more work in the lib-mime. begin to "rewr^H^Had" the code in rfc2231.c
[apps/madmutt.git] / curs_lib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 2004 g10 Code GmbH
5  *
6  * Parts were written/modified by:
7  * Nico Golde <nico@ngolde.de>
8  *
9  * This file is part of mutt-ng, see http://www.muttng.org/.
10  * It's licensed under the GNU General Public License,
11  * please see the file GPL in the top level source directory.
12  */
13
14 #if HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17
18 #include <lib-lib/mem.h>
19 #include <lib-lib/str.h>
20 #include <lib-lib/macros.h>
21 #include <lib-lib/file.h>
22
23 #include "mutt.h"
24 #include "enter.h"
25 #include "mutt_menu.h"
26 #include "mutt_curses.h"
27 #include "pager.h"
28 #include "mbyte.h"
29
30 #include "lib/debug.h"
31
32 #include <wchar.h>
33 #include <termios.h>
34 #include <sys/types.h>
35 #include <fcntl.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <ctype.h>
41
42 #ifdef HAVE_LANGINFO_YESEXPR
43 #include <langinfo.h>
44 #endif
45
46 /* not possible to unget more than one char under some curses libs, and it
47  * is impossible to unget function keys in SLang, so roll our own input
48  * buffering routines.
49  */
50 size_t UngetCount = 0;
51 static size_t UngetBufLen = 0;
52 static event_t *KeyEvent;
53
54 void mutt_refresh (void)
55 {
56   /* don't refresh when we are waiting for a child. */
57   if (option (OPTKEEPQUIET))
58     return;
59
60   /* don't refresh in the middle of macros unless necessary */
61   if (UngetCount && !option (OPTFORCEREFRESH))
62     return;
63
64   /* else */
65   refresh ();
66 }
67
68 /* Make sure that the next refresh does a full refresh.  This could be
69    optmized by not doing it at all if DISPLAY is set as this might
70    indicate that a GUI based pinentry was used.  Having an option to
71    customize this is of course the Mutt way.  */
72 void mutt_need_hard_redraw (void)
73 {
74   if (!getenv ("DISPLAY")) {
75     keypad (stdscr, TRUE);
76     clearok (stdscr, TRUE);
77     set_option (OPTNEEDREDRAW);
78   }
79 }
80
81 event_t mutt_getch (void)
82 {
83   int ch;
84   event_t err = { -1, OP_NULL }, ret;
85
86   if (!option (OPTUNBUFFEREDINPUT) && UngetCount)
87     return (KeyEvent[--UngetCount]);
88
89   SigInt = 0;
90
91   mutt_allow_interrupt (1);
92 #ifdef KEY_RESIZE
93   /* ncurses 4.2 sends this when the screen is resized */
94   ch = KEY_RESIZE;
95   while (ch == KEY_RESIZE)
96 #endif /* KEY_RESIZE */
97     ch = getch ();
98   mutt_allow_interrupt (0);
99
100   if (SigInt)
101     mutt_query_exit ();
102
103   if (ch == ERR)
104     return err;
105
106   if ((ch & 0x80) && option (OPTMETAKEY)) {
107     /* send ALT-x as ESC-x */
108     ch &= ~0x80;
109     mutt_ungetch (ch, 0);
110     ret.ch = '\033';
111     ret.op = 0;
112     return ret;
113   }
114
115   ret.ch = ch;
116   ret.op = 0;
117   return (ch == ctrl ('G') ? err : ret);
118 }
119
120 int _mutt_get_field ( const char *field, char *buf, size_t buflen,
121                      int complete, int multiple, char ***files, int *numfiles)
122 {
123   int ret;
124   int x, y;
125
126   ENTER_STATE *es = mutt_new_enter_state ();
127
128   do {
129     CLEARLINE (LINES - 1);
130     addstr (field);
131     mutt_refresh ();
132     getyx (stdscr, y, x);
133     ret =
134       _mutt_enter_string (buf, buflen, y, x, complete, multiple, files,
135                           numfiles, es);
136   }
137   while (ret == 1);
138   CLEARLINE (LINES - 1);
139   mutt_free_enter_state (&es);
140
141   return (ret);
142 }
143
144 int mutt_get_field_unbuffered (char *msg, char *buf, size_t buflen, int flags)
145 {
146   int rc;
147
148   set_option (OPTUNBUFFEREDINPUT);
149   rc = mutt_get_field (msg, buf, buflen, flags);
150   unset_option (OPTUNBUFFEREDINPUT);
151
152   return (rc);
153 }
154
155 void mutt_clear_error (void)
156 {
157   Errorbuf[0] = 0;
158   if (!option (OPTNOCURSES))
159     CLEARLINE (LINES - 1);
160 }
161
162 void mutt_edit_file (const char *editor, const char *data)
163 {
164   char cmd[LONG_STRING];
165
166   mutt_endwin (NULL);
167   mutt_expand_file_fmt (cmd, sizeof (cmd), editor, data);
168   if (mutt_system (cmd) == -1)
169     mutt_error (_("Error running \"%s\"!"), cmd);
170   keypad (stdscr, TRUE);
171   clearok (stdscr, TRUE);
172 }
173
174 int mutt_yesorno (const char *msg, int def)
175 {
176   event_t ch;
177   char *yes = _("yes");
178   char *no = _("no");
179   char *answer_string;
180   size_t answer_string_len;
181
182 #ifdef HAVE_LANGINFO_YESEXPR
183   char *expr;
184   regex_t reyes;
185   regex_t reno;
186   int reyes_ok;
187   int reno_ok;
188   char answer[2];
189
190   answer[1] = 0;
191
192   reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
193     !regcomp (&reyes, expr, REG_NOSUB | REG_EXTENDED);
194   reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
195     !regcomp (&reno, expr, REG_NOSUB | REG_EXTENDED);
196 #endif
197
198   CLEARLINE (LINES - 1);
199
200   /*
201    * In order to prevent the default answer to the question to wrapped
202    * around the screen in the even the question is wider than the screen,
203    * ensure there is enough room for the answer and truncate the question
204    * to fit.
205    */
206   answer_string = p_new(char, COLS + 1);
207   snprintf (answer_string, COLS + 1, " ([%s]/%s): ", def == M_YES ? yes : no,
208             def == M_YES ? no : yes);
209   answer_string_len = m_strlen(answer_string);
210   printw ("%.*s%s", COLS - answer_string_len, msg, answer_string);
211   p_delete(&answer_string);
212
213   for (;;) {
214     mutt_refresh ();
215     ch = mutt_getch ();
216     if (CI_is_return (ch.ch))
217       break;
218     if (ch.ch == -1) {
219       def = -1;
220       break;
221     }
222
223 #ifdef HAVE_LANGINFO_YESEXPR
224     answer[0] = ch.ch;
225     if (reyes_ok ? (regexec (&reyes, answer, 0, 0, 0) == 0) :
226 #else
227     if (
228 #endif
229          (tolower (ch.ch) == *yes)) {
230       def = M_YES;
231       break;
232     }
233     else if (
234 #ifdef HAVE_LANGINFO_YESEXPR
235               reno_ok ? (regexec (&reno, answer, 0, 0, 0) == 0) :
236 #endif
237               (tolower (ch.ch) == *no)) {
238       def = M_NO;
239       break;
240     }
241     else {
242       BEEP ();
243     }
244   }
245
246 #ifdef HAVE_LANGINFO_YESEXPR
247   if (reyes_ok)
248     regfree (&reyes);
249   if (reno_ok)
250     regfree (&reno);
251 #endif
252
253   if (def != -1) {
254     addstr ((char *) (def == M_YES ? yes : no));
255     mutt_refresh ();
256   }
257   return (def);
258 }
259
260 /* this function is called when the user presses the abort key */
261 void mutt_query_exit (void)
262 {
263   mutt_flushinp ();
264   curs_set (1);
265   if (Timeout)
266     timeout (-1);               /* restore blocking operation */
267   if (mutt_yesorno (_("Exit Mutt-ng?"), M_YES) == M_YES) {
268     mutt_endwin (NULL);
269     exit (1);
270   }
271   mutt_clear_error ();
272   mutt_curs_set (-1);
273   SigInt = 0;
274 }
275
276 void mutt_curses_error (const char *fmt, ...)
277 {
278   char TmpErrorbuf[STRING];
279   va_list ap;
280
281   va_start (ap, fmt);
282   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
283   va_end (ap);
284
285   debug_print (1, ("%s\n", Errorbuf));
286   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
287                       0, COLS - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
288   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
289
290   if (!option (OPTKEEPQUIET)) {
291     BEEP ();
292     SETCOLOR (MT_COLOR_ERROR);
293     mvaddstr (LINES - 1, 0, Errorbuf);
294     clrtoeol ();
295     SETCOLOR (MT_COLOR_NORMAL);
296     mutt_refresh ();
297   }
298
299   set_option (OPTMSGERR);
300 }
301
302 #ifdef USE_SOCKET
303 void mutt_progress_bar (progress_t* progress, long pos) {
304   char posstr[SHORT_STRING];
305
306   if (!pos) {
307     if (!NetInc)
308       mutt_message (progress->msg);
309     else {
310       if (progress->size)
311         mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
312                           progress->size);
313       progress->pos = 0;
314     }
315   }
316
317   if (!NetInc)
318     return;
319
320   if (pos >= progress->pos + (NetInc << 10)) {
321     progress->pos = pos;
322     pos = pos / (NetInc << 10) * (NetInc << 10);
323     mutt_pretty_size (posstr, sizeof (posstr), pos);
324     if (progress->size)
325       mutt_message ("%s %s/%s", progress->msg, posstr, progress->sizestr);
326     else
327       mutt_message ("%s %s", progress->msg, posstr);
328   }
329 }
330 #endif
331
332 void mutt_curses_message (const char *fmt, ...)
333 {
334   char TmpErrorbuf[STRING];
335   va_list ap;
336
337   va_start (ap, fmt);
338   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
339   va_end (ap);
340
341   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
342                       0, COLS - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
343   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
344
345   if (!option (OPTKEEPQUIET)) {
346     SETCOLOR (MT_COLOR_MESSAGE);
347     mvaddstr (LINES - 1, 0, Errorbuf);
348     clrtoeol ();
349     SETCOLOR (MT_COLOR_NORMAL);
350     mutt_refresh ();
351   }
352
353   unset_option (OPTMSGERR);
354 }
355
356 void mutt_show_error (void)
357 {
358   if (option (OPTKEEPQUIET))
359     return;
360
361   SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
362   CLEARLINE (LINES - 1);
363   addstr (Errorbuf);
364   SETCOLOR (MT_COLOR_NORMAL);
365 }
366
367 void mutt_endwin (const char *msg)
368 {
369   if (!option (OPTNOCURSES)) {
370     CLEARLINE (LINES - 1);
371
372     attrset (A_NORMAL);
373     mutt_refresh ();
374     endwin ();
375   }
376
377   if (msg && *msg) {
378     puts (msg);
379     fflush (stdout);
380   }
381 }
382
383 void _mutt_perror (const char *s, const char* filename, int line)
384 {
385   char *p = strerror (errno);
386
387   debug_print (1, ("%s: %s (errno = %d)\n", s, p ? p : "unknown error", errno));
388   mutt_error ("%s: %s (errno = %d) from %s:%i", s, p ? p : _("unknown error"), errno, filename, line);
389 }
390
391 int mutt_any_key_to_continue (const char *s)
392 {
393   struct termios t;
394   struct termios old;
395   int f, ch;
396
397   f = open ("/dev/tty", O_RDONLY);
398   tcgetattr (f, &t);
399   memcpy ((void *) &old, (void *) &t, sizeof (struct termios)); /* save original state */
400   t.c_lflag &= ~(ICANON | ECHO);
401   t.c_cc[VMIN] = 1;
402   t.c_cc[VTIME] = 0;
403   tcsetattr (f, TCSADRAIN, &t);
404   fflush (stdout);
405   if (s)
406     fputs (s, stdout);
407   else
408     fputs (_("Press any key to continue..."), stdout);
409   fflush (stdout);
410   ch = fgetc (stdin);
411   fflush (stdin);
412   tcsetattr (f, TCSADRAIN, &old);
413   close (f);
414   fputs ("\r\n", stdout);
415   mutt_clear_error ();
416   return (ch);
417 }
418
419 int mutt_do_pager (const char *banner,
420                    const char *tempfile, int do_color, pager_t * info)
421 {
422   int rc;
423
424   if (!Pager || m_strcmp(Pager, "builtin") == 0)
425     rc = mutt_pager (banner, tempfile, do_color, info);
426   else {
427     char cmd[STRING];
428
429     mutt_endwin (NULL);
430     mutt_expand_file_fmt (cmd, sizeof (cmd), Pager, tempfile);
431     if (mutt_system (cmd) == -1) {
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,
444                        int *redraw, int buffy, int multiple, char ***files,
445                        int *numfiles)
446 {
447   event_t ch;
448
449   mvaddstr (LINES - 1, 0, (char *) prompt);
450   addstr (_(" ('?' for list): "));
451   if (buf[0])
452     addstr (buf);
453   clrtoeol ();
454   mutt_refresh ();
455
456   ch = mutt_getch ();
457   if (ch.ch == -1) {
458     CLEARLINE (LINES - 1);
459     return (-1);
460   }
461   else if (ch.ch == '?') {
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     char *pc = p_new(char, m_strlen(prompt) + 3);
470
471     sprintf (pc, "%s: ", prompt);       /* __SPRINTF_CHECKED__ */
472     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
473     if (_mutt_get_field
474         (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files,
475          numfiles)
476         != 0)
477       buf[0] = 0;
478     MAYBE_REDRAW (*redraw);
479     p_delete(&pc);
480   }
481
482   return 0;
483 }
484
485 void mutt_ungetch (int ch, int op)
486 {
487   event_t tmp;
488
489   tmp.ch = ch;
490   tmp.op = op;
491
492   if (UngetCount >= UngetBufLen)
493     p_realloc(&KeyEvent, UngetBufLen += 128);
494
495   KeyEvent[UngetCount++] = tmp;
496 }
497
498 void mutt_flushinp (void)
499 {
500   UngetCount = 0;
501   flushinp ();
502 }
503
504 #if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
505 /* The argument can take 3 values:
506  * -1: restore the value of the last call
507  *  0: make the cursor invisible
508  *  1: make the cursor visible
509  */
510 void mutt_curs_set (int cursor)
511 {
512   static int SavedCursor = 1;
513
514   if (cursor < 0)
515     cursor = SavedCursor;
516   else
517     SavedCursor = cursor;
518
519   if (curs_set (cursor) == ERR) {
520     if (cursor == 1)            /* cnorm */
521       curs_set (2);             /* cvvis */
522   }
523 }
524 #endif
525
526 int mutt_multi_choice (char *prompt, char *letters)
527 {
528   event_t ch;
529   int choice;
530   char *p;
531
532   mvaddstr (LINES - 1, 0, prompt);
533   clrtoeol ();
534   for (;;) {
535     mutt_refresh ();
536     ch = mutt_getch ();
537     if (ch.ch == -1 || CI_is_return (ch.ch)) {
538       choice = -1;
539       break;
540     }
541     else {
542       p = strchr (letters, ch.ch);
543       if (p) {
544         choice = p - letters + 1;
545         break;
546       }
547       else if (ch.ch <= '9' && ch.ch > '0') {
548         choice = ch.ch - '0';
549         if (choice <= m_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   p_clear(&mbstate, 1);
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, int arboreal)
590 {
591   char *p;
592   wchar_t wc;
593   int w;
594   size_t k, k2;
595   char scratch[MB_LEN_MAX];
596   mbstate_t mbstate1, mbstate2;
597
598   p_clear(&mbstate1, 1);
599   p_clear(&mbstate2, 1);
600   --destlen;
601   p = dest;
602   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k) {
603     if (k == (size_t) (-1) || k == (size_t) (-2)) {
604       k = (k == (size_t) (-1)) ? 1 : n;
605       wc = replacement_char ();
606     }
607     if (arboreal && wc < M_TREE_MAX)
608       w = 1;                    /* hack */
609     else {
610       if (!IsWPrint (wc))
611         wc = '?';
612       w = wcwidth (wc);
613     }
614     if (w >= 0) {
615       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
616         break;
617       min_width -= w;
618       max_width -= w;
619       strncpy (p, scratch, k2);
620       p += k2;
621       destlen -= k2;
622     }
623   }
624   w = (int) destlen < min_width ? destlen : min_width;
625   if (w <= 0)
626     *p = '\0';
627   else if (right_justify) {
628     p[w] = '\0';
629     while (--p >= dest)
630       p[w] = *p;
631     while (--w >= 0)
632       dest[w] = m_pad_char;
633   }
634   else {
635     while (--w >= 0)
636       *p++ = m_pad_char;
637     *p = '\0';
638   }
639 }
640
641 /*
642  * This formats a string rather like
643  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
644  *   snprintf (dest, destlen, fmt, s);
645  * except that the numbers in the conversion specification refer to
646  * the number of character cells when printed.
647  */
648
649 static void mutt_format_s_x (char *dest,
650                              size_t destlen,
651                              const char *prefix, const char *s, int arboreal)
652 {
653   int right_justify = 1;
654   char *p;
655   int min_width;
656   int max_width = INT_MAX;
657
658   if (*prefix == '-')
659     ++prefix, right_justify = 0;
660   min_width = strtol (prefix, &p, 10);
661   if (*p == '.') {
662     prefix = p + 1;
663     max_width = strtol (prefix, &p, 10);
664     if (p <= prefix)
665       max_width = INT_MAX;
666   }
667
668   mutt_format_string (dest, destlen, min_width, max_width,
669                       right_justify, ' ', s, m_strlen(s), arboreal);
670 }
671
672 void mutt_format_s (char *dest,
673                     size_t destlen, const char *prefix, const char *s)
674 {
675   mutt_format_s_x (dest, destlen, prefix, s, 0);
676 }
677
678 void mutt_format_s_tree (char *dest,
679                          size_t destlen, const char *prefix, const char *s)
680 {
681   mutt_format_s_x (dest, destlen, prefix, s, 1);
682 }
683
684 /*
685  * mutt_paddstr (n, s) is almost equivalent to
686  * mutt_format_string (bigbuf, big, n, n, 0, ' ', s, big, 0), addstr (bigbuf)
687  */
688
689 void mutt_paddstr (int n, const char *s)
690 {
691   wchar_t wc;
692   int w;
693   size_t k;
694   size_t len = m_strlen(s);
695   mbstate_t mbstate;
696
697   p_clear(&mbstate, 1);
698   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k) {
699     if (k == (size_t) (-1) || k == (size_t) (-2)) {
700       k = (k == (size_t) (-1)) ? 1 : len;
701       wc = replacement_char ();
702     }
703     if (!IsWPrint (wc))
704       wc = '?';
705     w = wcwidth (wc);
706     if (w >= 0) {
707       if (w > n)
708         break;
709       addnstr ((char *) s, k);
710       n -= w;
711     }
712   }
713   while (n-- > 0)
714     addch (' ');
715 }
716
717 /*
718  * mutt_strwidth is like m_strlenexcept that it returns the width
719  * refering to the number of characters cells.
720  */
721
722 int mutt_strwidth (const char *s)
723 {
724   wchar_t wc;
725   int w;
726   size_t k, n;
727   mbstate_t mbstate;
728
729   if (!s)
730     return 0;
731
732   n = m_strlen(s);
733
734   p_clear(&mbstate, 1);
735   for (w = 0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k) {
736     if (k == (size_t) (-1) || k == (size_t) (-2)) {
737       k = (k == (size_t) (-1)) ? 1 : n;
738       wc = replacement_char ();
739     }
740     if (!IsWPrint (wc))
741       wc = '?';
742     w += wcwidth (wc);
743   }
744   return w;
745 }