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