move signal.c into lib/sys
[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 <lib-sys/mutt_signal.h>
39
40 #include "curses.h"
41 #include "menu.h"
42 #include "enter.h"
43
44 #include "mutt.h"
45 #include "pager.h"
46 #include "mbyte.h"
47
48 /* not possible to unget more than one char under some curses libs, and it
49  * is impossible to unget function keys in SLang, so roll our own input
50  * buffering routines.
51  */
52 size_t UngetCount = 0;
53 static size_t UngetBufLen = 0;
54 static event_t *KeyEvent;
55
56 void mutt_refresh (void)
57 {
58   /* don't refresh when we are waiting for a child. */
59   if (option (OPTKEEPQUIET))
60     return;
61
62   /* don't refresh in the middle of macros unless necessary */
63   if (UngetCount && !option (OPTFORCEREFRESH))
64     return;
65
66   /* else */
67   refresh ();
68 }
69
70 /* Make sure that the next refresh does a full refresh.  This could be
71    optmized by not doing it at all if DISPLAY is set as this might
72    indicate that a GUI based pinentry was used.  Having an option to
73    customize this is of course the Mutt way.  */
74 void mutt_need_hard_redraw (void)
75 {
76   if (!getenv ("DISPLAY")) {
77     keypad (stdscr, TRUE);
78     clearok (stdscr, TRUE);
79     set_option (OPTNEEDREDRAW);
80   }
81 }
82
83 event_t mutt_getch (void)
84 {
85   int ch;
86   event_t err = { -1, OP_NULL }, ret;
87
88   if (!option (OPTUNBUFFEREDINPUT) && UngetCount)
89     return (KeyEvent[--UngetCount]);
90
91   SigInt = 0;
92
93   mutt_allow_interrupt (1);
94   ch = getch ();
95   mutt_allow_interrupt (0);
96
97   if (SigInt)
98     mutt_query_exit ();
99
100   if (ch == ERR)
101     return err;
102
103   if ((ch & 0x80) && option (OPTMETAKEY)) {
104     /* send ALT-x as ESC-x */
105     ch &= ~0x80;
106     mutt_ungetch (ch, 0);
107     ret.ch = '\033';
108     ret.op = 0;
109     return ret;
110   }
111
112   ret.ch = ch;
113   ret.op = 0;
114   return (ch == ctrl ('G') ? err : ret);
115 }
116
117 int _mutt_get_field ( const char *field, char *buf, size_t buflen,
118                      int complete, int multiple, char ***files, int *numfiles)
119 {
120   int ret;
121   int x, y;
122
123   ENTER_STATE *es = mutt_new_enter_state ();
124
125   do {
126     CLEARLINE (LINES - 1);
127     addstr (field);
128     mutt_refresh ();
129     getyx (stdscr, y, x);
130     ret =
131       _mutt_enter_string (buf, buflen, y, x, complete, multiple, files,
132                           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_field_unbuffered (char *msg, char *buf, size_t buflen, int flags)
142 {
143   int rc;
144
145   set_option (OPTUNBUFFEREDINPUT);
146   rc = mutt_get_field (msg, buf, buflen, flags);
147   unset_option (OPTUNBUFFEREDINPUT);
148
149   return (rc);
150 }
151
152 void mutt_clear_error (void)
153 {
154   Errorbuf[0] = 0;
155   if (!option (OPTNOCURSES))
156     CLEARLINE (LINES - 1);
157 }
158
159 void mutt_edit_file (const char *editor, const char *data)
160 {
161   char cmd[LONG_STRING];
162
163   mutt_endwin (NULL);
164   mutt_expand_file_fmt (cmd, sizeof (cmd), editor, data);
165   if (mutt_system (cmd) == -1)
166     mutt_error (_("Error running \"%s\"!"), cmd);
167   keypad (stdscr, TRUE);
168   clearok (stdscr, TRUE);
169 }
170
171 int mutt_yesorno (const char *msg, int def)
172 {
173   event_t ch;
174   char *yes = _("yes");
175   char *no = _("no");
176   char *answer_string;
177   size_t answer_string_len;
178
179 #ifdef HAVE_LANGINFO_YESEXPR
180   char *expr;
181   regex_t reyes;
182   regex_t reno;
183   int reyes_ok;
184   int reno_ok;
185   char answer[2];
186
187   answer[1] = 0;
188
189   reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
190     !regcomp (&reyes, expr, REG_NOSUB | REG_EXTENDED);
191   reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
192     !regcomp (&reno, expr, REG_NOSUB | REG_EXTENDED);
193 #endif
194
195   CLEARLINE (LINES - 1);
196
197   /*
198    * In order to prevent the default answer to the question to wrapped
199    * around the screen in the even the question is wider than the screen,
200    * ensure there is enough room for the answer and truncate the question
201    * to fit.
202    */
203   answer_string = p_new(char, COLS + 1);
204   snprintf (answer_string, COLS + 1, " ([%s]/%s): ", def == M_YES ? yes : no,
205             def == M_YES ? no : yes);
206   answer_string_len = m_strlen(answer_string);
207   printw ("%.*s%s", COLS - answer_string_len, msg, answer_string);
208   p_delete(&answer_string);
209
210   for (;;) {
211     mutt_refresh ();
212     ch = mutt_getch ();
213     if (CI_is_return (ch.ch))
214       break;
215     if (ch.ch == -1) {
216       def = -1;
217       break;
218     }
219
220 #ifdef HAVE_LANGINFO_YESEXPR
221     answer[0] = ch.ch;
222     if (reyes_ok ? (regexec (&reyes, answer, 0, 0, 0) == 0) :
223 #else
224     if (
225 #endif
226          (tolower (ch.ch) == *yes)) {
227       def = M_YES;
228       break;
229     }
230     else if (
231 #ifdef HAVE_LANGINFO_YESEXPR
232               reno_ok ? (regexec (&reno, answer, 0, 0, 0) == 0) :
233 #endif
234               (tolower (ch.ch) == *no)) {
235       def = M_NO;
236       break;
237     }
238     else {
239       BEEP ();
240     }
241   }
242
243 #ifdef HAVE_LANGINFO_YESEXPR
244   if (reyes_ok)
245     regfree (&reyes);
246   if (reno_ok)
247     regfree (&reno);
248 #endif
249
250   if (def != -1) {
251     addstr ((char *) (def == M_YES ? yes : no));
252     mutt_refresh ();
253   }
254   return (def);
255 }
256
257 /* this function is called when the user presses the abort key */
258 void mutt_query_exit (void)
259 {
260   mutt_flushinp ();
261   curs_set (1);
262   if (Timeout)
263     timeout (-1);               /* restore blocking operation */
264   if (mutt_yesorno (_("Exit Madmutt?"), M_YES) == M_YES) {
265     mutt_endwin (NULL);
266     exit (1);
267   }
268   mutt_clear_error ();
269   mutt_curs_set (-1);
270   SigInt = 0;
271 }
272
273 void mutt_curses_error (const char *fmt, ...)
274 {
275   char TmpErrorbuf[STRING];
276   va_list ap;
277
278   va_start (ap, fmt);
279   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
280   va_end (ap);
281
282   debug_print (1, ("%s\n", Errorbuf));
283   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
284                       0, COLS - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
285   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
286
287   if (!option (OPTKEEPQUIET)) {
288     BEEP ();
289     SETCOLOR (MT_COLOR_ERROR);
290     mvaddstr (LINES - 1, 0, Errorbuf);
291     clrtoeol ();
292     SETCOLOR (MT_COLOR_NORMAL);
293     mutt_refresh ();
294   }
295
296   set_option (OPTMSGERR);
297 }
298
299 void mutt_progress_bar (progress_t* progress, long pos) {
300   char posstr[SHORT_STRING];
301
302   if (!pos) {
303     if (!NetInc)
304       mutt_message (progress->msg);
305     else {
306       if (progress->size)
307         mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
308                           progress->size);
309       progress->pos = 0;
310     }
311   }
312
313   if (!NetInc)
314     return;
315
316   if (pos >= progress->pos + (NetInc << 10)) {
317     progress->pos = pos;
318     pos = pos / (NetInc << 10) * (NetInc << 10);
319     mutt_pretty_size (posstr, sizeof (posstr), pos);
320     if (progress->size)
321       mutt_message ("%s %s/%s", progress->msg, posstr, progress->sizestr);
322     else
323       mutt_message ("%s %s", progress->msg, posstr);
324   }
325 }
326
327 void mutt_curses_message (const char *fmt, ...)
328 {
329   char TmpErrorbuf[STRING];
330   va_list ap;
331
332   va_start (ap, fmt);
333   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
334   va_end (ap);
335
336   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
337                       0, COLS - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
338   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
339
340   if (!option (OPTKEEPQUIET)) {
341     SETCOLOR (MT_COLOR_MESSAGE);
342     mvaddstr (LINES - 1, 0, Errorbuf);
343     clrtoeol ();
344     SETCOLOR (MT_COLOR_NORMAL);
345     mutt_refresh ();
346   }
347
348   unset_option (OPTMSGERR);
349 }
350
351 void mutt_show_error (void)
352 {
353   if (option (OPTKEEPQUIET))
354     return;
355
356   SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
357   CLEARLINE (LINES - 1);
358   addstr (Errorbuf);
359   SETCOLOR (MT_COLOR_NORMAL);
360 }
361
362 void mutt_endwin (const char *msg)
363 {
364   if (!option (OPTNOCURSES)) {
365     CLEARLINE (LINES - 1);
366
367     attrset (A_NORMAL);
368     mutt_refresh ();
369     endwin ();
370   }
371
372   if (msg && *msg) {
373     puts (msg);
374     fflush (stdout);
375   }
376 }
377
378 void _mutt_perror (const char *s, const char* filename, int line)
379 {
380   char *p = strerror (errno);
381
382   debug_print (1, ("%s: %s (errno = %d)\n", s, p ? p : "unknown error", errno));
383   mutt_error ("%s: %s (errno = %d) from %s:%i", s, p ? p : _("unknown error"), errno, filename, line);
384 }
385
386 int mutt_any_key_to_continue (const char *s)
387 {
388   struct termios t;
389   struct termios old;
390   int f, ch;
391
392   f = open ("/dev/tty", O_RDONLY);
393   tcgetattr (f, &t);
394   memcpy ((void *) &old, (void *) &t, sizeof (struct termios)); /* save original state */
395   t.c_lflag &= ~(ICANON | ECHO);
396   t.c_cc[VMIN] = 1;
397   t.c_cc[VTIME] = 0;
398   tcsetattr (f, TCSADRAIN, &t);
399   fflush (stdout);
400   if (s)
401     fputs (s, stdout);
402   else
403     fputs (_("Press any key to continue..."), stdout);
404   fflush (stdout);
405   ch = fgetc (stdin);
406   fflush (stdin);
407   tcsetattr (f, TCSADRAIN, &old);
408   close (f);
409   fputs ("\r\n", stdout);
410   mutt_clear_error ();
411   return (ch);
412 }
413
414 int mutt_do_pager (const char *banner,
415                    const char *tempfile, int do_color, pager_t * info)
416 {
417   int rc;
418
419   if (!Pager || m_strcmp(Pager, "builtin") == 0)
420     rc = mutt_pager (banner, tempfile, do_color, info);
421   else {
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       mutt_error (_("Error running \"%s\"!"), cmd);
428       rc = -1;
429     }
430     else
431       rc = 0;
432     mutt_unlink (tempfile);
433   }
434
435   return rc;
436 }
437
438 int _mutt_enter_fname (const char *prompt, char *buf, size_t blen,
439                        int *redraw, int buffy, int multiple, char ***files,
440                        int *numfiles)
441 {
442   event_t ch;
443
444   mvaddstr (LINES - 1, 0, (char *) prompt);
445   addstr (_(" ('?' for list): "));
446   if (buf[0])
447     addstr (buf);
448   clrtoeol ();
449   mutt_refresh ();
450
451   ch = mutt_getch ();
452   if (ch.ch == -1) {
453     CLEARLINE (LINES - 1);
454     return (-1);
455   }
456   else if (ch.ch == '?') {
457     mutt_refresh ();
458     buf[0] = 0;
459     _mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0),
460                        files, numfiles);
461     *redraw = REDRAW_FULL;
462   }
463   else {
464     char *pc = p_new(char, m_strlen(prompt) + 3);
465
466     sprintf (pc, "%s: ", prompt);       /* __SPRINTF_CHECKED__ */
467     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
468     if (_mutt_get_field
469         (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files,
470          numfiles)
471         != 0)
472       buf[0] = 0;
473     MAYBE_REDRAW (*redraw);
474     p_delete(&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     p_realloc(&KeyEvent, UngetBufLen += 128);
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   for (;;) {
530     mutt_refresh ();
531     ch = mutt_getch ();
532     if (ch.ch == -1 || CI_is_return (ch.ch)) {
533       choice = -1;
534       break;
535     }
536     else {
537       p = strchr (letters, ch.ch);
538       if (p) {
539         choice = p - letters + 1;
540         break;
541       }
542       else if (ch.ch <= '9' && ch.ch > '0') {
543         choice = ch.ch - '0';
544         if (choice <= m_strlen(letters))
545           break;
546       }
547     }
548     BEEP ();
549   }
550   CLEARLINE (LINES - 1);
551   mutt_refresh ();
552   return choice;
553 }
554
555 /*
556  * addwch would be provided by an up-to-date curses library
557  */
558
559 int mutt_addwch (wchar_t wc)
560 {
561   char buf[MB_LEN_MAX * 2];
562   mbstate_t mbstate;
563   size_t n1, n2;
564
565   p_clear(&mbstate, 1);
566   if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t) (-1) ||
567       (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t) (-1))
568     return -1;                  /* ERR */
569   else
570     return addstr (buf);
571 }
572
573
574 /*
575  * This formats a string, a bit like
576  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
577  * except that the widths refer to the number of character cells
578  * when printed.
579  */
580
581 void mutt_format_string (char *dest, ssize_t destlen,
582                          int min_width, int max_width,
583                          int right_justify, char m_pad_char,
584                          const char *s, ssize_t n, int arboreal)
585 {
586   char *p;
587   wchar_t wc;
588   int w;
589   ssize_t k, k2;
590   char scratch[MB_LEN_MAX];
591   mbstate_t mbstate1, mbstate2;
592
593   p_clear(&mbstate1, 1);
594   p_clear(&mbstate2, 1);
595   --destlen;
596   p = dest;
597   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k) {
598     if (k == -1 || k == -2) {
599       k = (k == -1) ? 1 : n;
600       wc = replacement_char ();
601     }
602     if (arboreal && wc < M_TREE_MAX)
603       w = 1;                    /* hack */
604     else {
605       if (!IsWPrint (wc))
606         wc = '?';
607       w = wcwidth (wc);
608     }
609     if (w >= 0) {
610       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
611         break;
612       min_width -= w;
613       max_width -= w;
614       strncpy (p, scratch, k2);
615       p += k2;
616       destlen -= k2;
617     }
618   }
619   w = destlen < min_width ? destlen : min_width;
620   if (w <= 0)
621     *p = '\0';
622   else if (right_justify) {
623     p[w] = '\0';
624     while (--p >= dest)
625       p[w] = *p;
626     while (--w >= 0)
627       dest[w] = m_pad_char;
628   }
629   else {
630     while (--w >= 0)
631       *p++ = m_pad_char;
632     *p = '\0';
633   }
634 }
635
636 /*
637  * This formats a string rather like
638  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
639  *   snprintf (dest, destlen, fmt, s);
640  * except that the numbers in the conversion specification refer to
641  * the number of character cells when printed.
642  */
643
644 static void mutt_format_s_x (char *dest,
645                              size_t destlen,
646                              const char *prefix, const char *s, int arboreal)
647 {
648   int right_justify = 1;
649   char *p;
650   int min_width;
651   int max_width = INT_MAX;
652
653   if (*prefix == '-')
654     ++prefix, right_justify = 0;
655   min_width = strtol (prefix, &p, 10);
656   if (*p == '.') {
657     prefix = p + 1;
658     max_width = strtol (prefix, &p, 10);
659     if (p <= prefix)
660       max_width = INT_MAX;
661   }
662
663   mutt_format_string (dest, destlen, min_width, max_width,
664                       right_justify, ' ', s, m_strlen(s), arboreal);
665 }
666
667 void mutt_format_s (char *dest,
668                     size_t destlen, const char *prefix, const char *s)
669 {
670   mutt_format_s_x (dest, destlen, prefix, s, 0);
671 }
672
673 void mutt_format_s_tree (char *dest,
674                          size_t destlen, const char *prefix, const char *s)
675 {
676   mutt_format_s_x (dest, destlen, prefix, s, 1);
677 }
678
679 /*
680  * mutt_paddstr (n, s) is almost equivalent to
681  * mutt_format_string (bigbuf, big, n, n, 0, ' ', s, big, 0), addstr (bigbuf)
682  */
683
684 void mutt_paddstr (int n, const char *s)
685 {
686   wchar_t wc;
687   int w;
688   size_t k;
689   size_t len = m_strlen(s);
690   mbstate_t mbstate;
691
692   p_clear(&mbstate, 1);
693   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k) {
694     if (k == (size_t) (-1) || k == (size_t) (-2)) {
695       k = (k == (size_t) (-1)) ? 1 : len;
696       wc = replacement_char ();
697     }
698     if (!IsWPrint (wc))
699       wc = '?';
700     w = wcwidth (wc);
701     if (w >= 0) {
702       if (w > n)
703         break;
704       addnstr ((char *) s, k);
705       n -= w;
706     }
707   }
708   while (n-- > 0)
709     addch (' ');
710 }
711
712 /*
713  * mutt_strwidth is like m_strlenexcept that it returns the width
714  * refering to the number of characters cells.
715  */
716
717 int mutt_strwidth (const char *s)
718 {
719   wchar_t wc;
720   int w;
721   size_t k, n;
722   mbstate_t mbstate;
723
724   if (!s)
725     return 0;
726
727   n = m_strlen(s);
728
729   p_clear(&mbstate, 1);
730   for (w = 0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k) {
731     if (k == (size_t) (-1) || k == (size_t) (-2)) {
732       k = (k == (size_t) (-1)) ? 1 : n;
733       wc = replacement_char ();
734     }
735     if (!IsWPrint (wc))
736       wc = '?';
737     w += wcwidth (wc);
738   }
739   return w;
740 }