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