mutt_enter_string is only used for _mutt_get_field for real.
[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-ui/lib-ui.h>
15
16 #include <langinfo.h>
17 #include <termios.h>
18
19 #include <lib-lua/lib-lua.h>
20 #include <lib-sys/unix.h>
21 #include <lib-sys/mutt_signal.h>
22
23 #include "menu.h"
24
25 #include "mutt.h"
26 #include "pager.h"
27 #include "charset.h"
28 #include "madtty.h"
29
30 /* not possible to unget more than one char under some curses libs, and it
31  * is impossible to unget function keys in SLang, so roll our own input
32  * buffering routines.
33  */
34 ssize_t UngetCount = 0;
35 static ssize_t UngetBufLen = 0;
36 static event_t *KeyEvent;
37
38 event_t mutt_getch (void)
39 {
40   int ch;
41   event_t err = { -1, OP_NULL }, ret;
42
43   if (!option (OPTUNBUFFEREDINPUT) && UngetCount)
44     return (KeyEvent[--UngetCount]);
45
46   SigInt = 0;
47
48   mutt_allow_interrupt (1);
49   ch = getch();
50   mutt_allow_interrupt (0);
51
52   if (SigInt)
53     mutt_query_exit ();
54
55   if (ch == ERR)
56     return err;
57
58   ret.ch = ch;
59   ret.op = 0;
60   return (ch == ctrl ('G') ? err : ret);
61 }
62
63 int mutt_get_field_unbuffered (char *msg, char *buf, ssize_t buflen, int flags)
64 {
65   int rc;
66
67   set_option (OPTUNBUFFEREDINPUT);
68   rc = mutt_get_field (msg, buf, buflen, flags);
69   unset_option (OPTUNBUFFEREDINPUT);
70
71   return (rc);
72 }
73
74 void mutt_clear_error (void)
75 {
76   Errorbuf[0] = 0;
77   if (!option (OPTNOCURSES))
78     CLEARLINE(stdscr, LINES - 1);
79 }
80
81 static struct timeval const slice = { 0, 1000 * 1000 / 100 };
82 static struct timeval timeval_add(struct timeval a, struct timeval b)
83 {
84     int usec = a.tv_usec + b.tv_usec;
85     a.tv_sec += b.tv_sec;
86     while (usec > 1000 * 1000) {
87         a.tv_sec += 1;
88         usec -= 1000 * 1000;
89     }
90     a.tv_usec = usec;
91     return a;
92 }
93
94 static int is_expired(struct timeval now, struct timeval expiry)
95 {
96     return now.tv_sec > expiry.tv_sec
97         || (now.tv_sec == expiry.tv_sec && now.tv_usec > expiry.tv_usec);
98 }
99
100 void mutt_edit_file(const char *data)
101 {
102     char cmd[STRING];
103     const char *args[] = { "/bin/sh", "-c", cmd, NULL };
104     int dirty = 0, ch, res, mh, mw, pty, pid;
105     struct timeval next;
106     madtty_t *rt;
107
108     m_quotefile_fmt(cmd, sizeof(cmd), mod_core.editor, data);
109     getmaxyx(main_w, mh, mw);
110     SigChild = 0;
111
112     rt = madtty_create(mh - 2, mw);
113     pid = madtty_forkpty(rt, args[0], args, &pty);
114     if (pid < 0) {
115         madtty_destroy(rt);
116         mutt_error(_("unable to start editor"));
117         return;
118     }
119
120     SETCOLOR(main_w, MT_COLOR_SIDEBAR);
121     mvwhline(main_w, 0, 0, ACS_HLINE, mw);
122
123     nodelay(stdscr, true);
124     gettimeofday(&next, NULL);
125     while (!SigChild) {
126         struct timeval tv = { 0, 1000 * 1000 / 100 };
127         fd_set rfds;
128
129         FD_ZERO(&rfds);
130         FD_SET(0, &rfds);
131         FD_SET(pty, &rfds);
132
133         if (select(pty + 1, &rfds, NULL, NULL, &tv) < 0)
134             break;
135
136         if (FD_ISSET(pty, &rfds)) {
137             madtty_process(rt);
138             dirty = 1;
139         }
140
141         while ((ch = getch()) != ERR) {
142             madtty_keypress(rt, ch); /* pass the keypress for handling */
143             dirty = 1;
144         }
145
146         gettimeofday(&tv, NULL);
147         if (dirty && is_expired(tv, next)) {
148             madtty_draw(rt, main_w, 1, 0);
149             wrefresh(main_w);
150             dirty = 0;
151             next = timeval_add(tv, slice);
152         }
153     }
154     while (waitpid(pid, &res, 0) < 0 && errno == EINTR);
155     nodelay(stdscr, false);
156     close(pty);
157     madtty_destroy(rt);
158 }
159
160 int mutt_yesorno (const char *msg, int def)
161 {
162   event_t ch;
163   const char *yes = _("yes");
164   const char *no = _("no");
165   char *answer_string;
166   ssize_t answer_string_len;
167   char *expr;
168   regex_t reyes;
169   regex_t reno;
170   int reyes_ok;
171   int reno_ok;
172   char answer[2];
173
174   answer[1] = 0;
175
176   reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
177     !regcomp (&reyes, expr, REG_NOSUB | REG_EXTENDED);
178   reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
179     !regcomp (&reno, expr, REG_NOSUB | REG_EXTENDED);
180
181   CLEARLINE(stdscr, LINES - 1);
182
183   /*
184    * In order to prevent the default answer to the question to wrapped
185    * around the screen in the even the question is wider than the screen,
186    * ensure there is enough room for the answer and truncate the question
187    * to fit.
188    */
189   answer_string = p_new(char, getmaxx(stdscr) + 1);
190   snprintf (answer_string, getmaxx(stdscr) + 1, " ([%s]/%s): ", def == M_YES ? yes : no,
191             def == M_YES ? no : yes);
192   answer_string_len = m_strlen(answer_string);
193   wprintw (stdscr, "%.*s%s", getmaxx(stdscr) - answer_string_len, msg, answer_string);
194   p_delete(&answer_string);
195
196   for (;;) {
197     mutt_refresh ();
198     ch = mutt_getch ();
199     if (CI_is_return (ch.ch))
200       break;
201     if (ch.ch == -1) {
202       def = -1;
203       break;
204     }
205
206     answer[0] = ch.ch;
207     if (reyes_ok ? (regexec (&reyes, answer, 0, 0, 0) == 0) : tolower (ch.ch) == *yes)
208     {
209       def = M_YES;
210       break;
211     }
212     else if (
213               reno_ok ? (regexec (&reno, answer, 0, 0, 0) == 0) :
214               (tolower (ch.ch) == *no)) {
215       def = M_NO;
216       break;
217     } else {
218       BEEP ();
219     }
220   }
221
222   if (reyes_ok)
223     regfree (&reyes);
224   if (reno_ok)
225     regfree (&reno);
226
227   if (def != -1) {
228     waddstr (stdscr, (char *) (def == M_YES ? yes : no));
229     mutt_refresh ();
230   }
231   CLEARLINE(stdscr, LINES - 1);
232   return (def);
233 }
234
235 /* this function is called when the user presses the abort key */
236 void mutt_query_exit (void)
237 {
238   mutt_flushinp ();
239   curs_set (1);
240   if (Timeout)
241     wtimeout (stdscr, -1);               /* restore blocking operation */
242   if (mutt_yesorno (_("Exit Madmutt?"), M_YES) == M_YES) {
243     mutt_endwin (NULL);
244     mutt_exit(1);
245   }
246   mutt_clear_error ();
247   mutt_curs_set (-1);
248   SigInt = 0;
249 }
250
251 void mutt_curses_error (const char *fmt, ...)
252 {
253   char TmpErrorbuf[STRING];
254   va_list ap;
255
256   va_start (ap, fmt);
257   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
258   va_end (ap);
259
260   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
261                       0, getmaxy(stdscr) - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
262   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
263
264   if (!option (OPTKEEPQUIET)) {
265     BEEP ();
266     SETCOLOR(stdscr, MT_COLOR_ERROR);
267     mvwaddstr (stdscr, LINES - 1, 0, Errorbuf);
268     wclrtoeol (stdscr);
269     SETCOLOR(stdscr, MT_COLOR_NORMAL);
270     mutt_refresh ();
271   }
272
273   set_option (OPTMSGERR);
274 }
275
276 void mutt_progress_bar (progress_t* progress, long pos) {
277   char posstr[STRING];
278
279   if (!pos) {
280     if (!NetInc)
281       mutt_message (progress->msg);
282     else {
283       if (progress->size)
284         mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
285                           progress->size);
286       progress->pos = 0;
287     }
288   }
289
290   if (!NetInc)
291     return;
292
293   if (pos >= progress->pos + (NetInc << 10)) {
294     progress->pos = pos;
295     pos = pos / (NetInc << 10) * (NetInc << 10);
296     mutt_pretty_size (posstr, sizeof (posstr), pos);
297     if (progress->size)
298       mutt_message ("%s %s/%s", progress->msg, posstr, progress->sizestr);
299     else
300       mutt_message ("%s %s", progress->msg, posstr);
301   }
302 }
303
304 void mutt_curses_message (const char *fmt, ...)
305 {
306   char TmpErrorbuf[STRING];
307   va_list ap;
308
309   va_start (ap, fmt);
310   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
311   va_end (ap);
312
313   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
314                       0, getmaxx(stdscr) - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
315   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
316
317   if (!option (OPTKEEPQUIET)) {
318     SETCOLOR(stdscr, MT_COLOR_MESSAGE);
319     mvwaddstr (stdscr, LINES - 1, 0, Errorbuf);
320     wclrtoeol (stdscr);
321     SETCOLOR(stdscr, MT_COLOR_NORMAL);
322     mutt_refresh ();
323   }
324
325   unset_option (OPTMSGERR);
326 }
327
328 void mutt_show_error (void)
329 {
330   if (option (OPTKEEPQUIET))
331     return;
332
333   SETCOLOR(stdscr, option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
334   CLEARLINE(stdscr, LINES - 1);
335   waddstr (stdscr, Errorbuf);
336   SETCOLOR(stdscr, MT_COLOR_NORMAL);
337 }
338
339 void curses_initialize(void)
340 {
341     initscr();
342     if (start_color() == ERR || !has_colors() || COLORS < 8)
343         mutt_exit(-1);
344     madtty_init_colors();
345     ci_start_color();
346     noecho();
347     raw();
348     keypad(stdscr, true);
349     typeahead(-1);
350     meta(stdscr, true);
351     curs_set(0);
352     ESCDELAY = 50;
353 }
354
355 int mutt_any_key_to_continue (const char *s)
356 {
357   struct termios t;
358   struct termios old;
359   int f, ch;
360
361   f = open ("/dev/tty", O_RDONLY);
362   tcgetattr (f, &t);
363   memcpy ((void *) &old, (void *) &t, sizeof (struct termios)); /* save original state */
364   t.c_lflag &= ~(ICANON | ECHO);
365   t.c_cc[VMIN] = 1;
366   t.c_cc[VTIME] = 0;
367   tcsetattr (f, TCSADRAIN, &t);
368   fflush (stdout);
369   if (s)
370     fputs (s, stdout);
371   else
372     fputs (_("Press any key to continue..."), stdout);
373   fflush (stdout);
374   ch = fgetc (stdin);
375   fflush (stdin);
376   tcsetattr (f, TCSADRAIN, &old);
377   close (f);
378   fputs ("\r\n", stdout);
379   mutt_clear_error ();
380   return (ch);
381 }
382
383 int _mutt_enter_fname (const char *prompt, char *buf, ssize_t blen,
384                        int *redraw, int buffy, int multiple, char ***files,
385                        int *numfiles)
386 {
387   event_t ch;
388
389   mvwaddstr(stdscr, LINES - 1, 0, (char *) prompt);
390   waddstr(stdscr, _(" ('?' for list): "));
391   if (buf[0])
392     waddstr (stdscr, buf);
393   wclrtoeol (stdscr);
394   mutt_refresh ();
395
396   ch = mutt_getch ();
397   if (ch.ch == -1) {
398     CLEARLINE(stdscr, LINES - 1);
399     return (-1);
400   }
401   else if (ch.ch == '?') {
402     mutt_refresh ();
403     buf[0] = 0;
404     mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0),
405                       files, numfiles);
406     *redraw = REDRAW_FULL;
407   }
408   else {
409     char *pc = p_new(char, m_strlen(prompt) + 3);
410
411     sprintf(pc, "%s: ", prompt);
412     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
413     if (_mutt_get_field
414         (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files,
415          numfiles)
416         != 0)
417       buf[0] = 0;
418     MAYBE_REDRAW (*redraw);
419     p_delete(&pc);
420   }
421
422   return 0;
423 }
424
425 void mutt_ungetch (int ch, int op)
426 {
427   event_t tmp;
428
429   tmp.ch = ch;
430   tmp.op = op;
431
432   if (UngetCount >= UngetBufLen)
433     p_realloc(&KeyEvent, UngetBufLen += 128);
434
435   KeyEvent[UngetCount++] = tmp;
436 }
437
438 void mutt_flushinp (void)
439 {
440   UngetCount = 0;
441   flushinp ();
442 }
443
444 /* The argument can take 3 values:
445  * -1: restore the value of the last call
446  *  0: make the cursor invisible
447  *  1: make the cursor visible
448  */
449 void mutt_curs_set (int cursor)
450 {
451   static int SavedCursor = 1;
452
453   if (cursor < 0)
454     cursor = SavedCursor;
455   else
456     SavedCursor = cursor;
457
458   if (curs_set (cursor) == ERR) {
459     if (cursor == 1)            /* cnorm */
460       curs_set (2);             /* cvvis */
461   }
462 }
463
464 int mutt_multi_choice (const char *prompt, const char *letters)
465 {
466   event_t ch;
467   int choice;
468   char *p;
469
470   mvwaddstr (stdscr, LINES - 1, 0, prompt);
471   wclrtoeol (stdscr);
472   for (;;) {
473     mutt_refresh ();
474     ch = mutt_getch ();
475     if (ch.ch == -1 || CI_is_return (ch.ch)) {
476       choice = -1;
477       break;
478     }
479     else {
480       p = strchr (letters, ch.ch);
481       if (p) {
482         choice = p - letters + 1;
483         break;
484       }
485       else if (ch.ch <= '9' && ch.ch > '0') {
486         choice = ch.ch - '0';
487         if (choice <= m_strlen(letters))
488           break;
489       }
490     }
491     BEEP ();
492   }
493   CLEARLINE(stdscr, LINES - 1);
494   mutt_refresh ();
495   return choice;
496 }
497
498 ssize_t mutt_pretty_size(char *s, ssize_t len, ssize_t n)
499 {
500     if (n == 0)
501         return m_strcpy(s, len, "0K");
502
503     if (n < 10189)           /* 0.1K - 9.9K */
504         return snprintf(s, len, "%3.1fK", (n < 103) ? 0.1 : n / 1024.0);
505
506     if (n < 1023949)         /* 10K - 999K */
507         /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
508         return snprintf(s, len, "%ldK", (n + 51) / 1024);
509
510     if (n < 10433332)        /* 1.0M - 9.9M */
511         return snprintf(s, len, "%3.1fM", n / 1048576.0);
512
513     /* (10433332 + 52428) / 1048576 = 10 */
514     return snprintf (s, len, "%ldM", (n + 52428) / 1048576);
515 }
516
517 /*
518  * This formats a string, a bit like
519  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
520  * except that the widths refer to the number of character cells
521  * when printed.
522  */
523
524 void mutt_format_string (char *dest, ssize_t destlen,
525                          int min_width, int max_width,
526                          int right_justify, char m_pad_char,
527                          const char *s, ssize_t n, int arboreal)
528 {
529   char *p;
530   wchar_t wc;
531   int w;
532   ssize_t k, k2;
533   char scratch[MB_LEN_MAX];
534   mbstate_t mbstate1, mbstate2;
535
536   p_clear(&mbstate1, 1);
537   p_clear(&mbstate2, 1);
538   --destlen;
539   p = dest;
540   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k) {
541     if (k == -1 || k == -2) {
542       k = (k == -1) ? 1 : n;
543       wc = CharsetReplacement;
544     }
545     if (arboreal && wc < M_TREE_MAX)
546       w = 1;                    /* hack */
547     else {
548       if (!iswprint(wc))
549         wc = '?';
550       w = wcwidth (wc);
551     }
552     if (w >= 0) {
553       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
554         break;
555       min_width -= w;
556       max_width -= w;
557       m_strncpy(p, destlen, scratch, k2);
558       p += k2;
559       destlen -= k2;
560     }
561   }
562   w = destlen < min_width ? destlen : min_width;
563   if (w <= 0)
564     *p = '\0';
565   else if (right_justify) {
566     p[w] = '\0';
567     while (--p >= dest)
568       p[w] = *p;
569     while (--w >= 0)
570       dest[w] = m_pad_char;
571   }
572   else {
573     while (--w >= 0)
574       *p++ = m_pad_char;
575     *p = '\0';
576   }
577 }
578
579 /*
580  * This formats a string rather like
581  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
582  *   snprintf (dest, destlen, fmt, s);
583  * except that the numbers in the conversion specification refer to
584  * the number of character cells when printed.
585  */
586
587 static void mutt_format_s_x (char *dest, ssize_t destlen,
588                              const char *prefix, const char *s, int arboreal)
589 {
590   int right_justify = 1;
591   char *p;
592   int min_width;
593   int max_width = INT_MAX;
594
595   if (*prefix == '-')
596     ++prefix, right_justify = 0;
597   min_width = strtol (prefix, &p, 10);
598   if (*p == '.') {
599     prefix = p + 1;
600     max_width = strtol (prefix, &p, 10);
601     if (p <= prefix)
602       max_width = INT_MAX;
603   }
604
605   mutt_format_string (dest, destlen, min_width, max_width,
606                       right_justify, ' ', s, m_strlen(s), arboreal);
607 }
608
609 void mutt_format_s (char *dest, ssize_t destlen,
610                     const char *prefix, const char *s)
611 {
612   mutt_format_s_x (dest, destlen, prefix, s, 0);
613 }
614
615 void mutt_format_s_tree (char *dest, ssize_t destlen,
616                          const char *prefix, const char *s)
617 {
618   mutt_format_s_x (dest, destlen, prefix, s, 1);
619 }
620
621 /*
622  * mutt_paddstr (n, s) is almost equivalent to
623  * mutt_format_string (bigbuf, big, n, n, 0, ' ', s, big, 0), waddstr (main_w, bigbuf)
624  */
625
626 void mutt_paddstr(WINDOW *win, int n, const char *s)
627 {
628   wchar_t wc;
629   int w;
630   ssize_t k;
631   ssize_t len = m_strlen(s);
632   mbstate_t mbstate;
633
634   p_clear(&mbstate, 1);
635   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k) {
636     if (k == -1 || k == -2) {
637       k = (k == -1) ? 1 : len;
638       wc = CharsetReplacement;
639     }
640     if (!iswprint(wc))
641       wc = '?';
642     w = wcwidth (wc);
643     if (w >= 0) {
644       if (w > n)
645         break;
646       waddnstr(win, (char *) s, k);
647       n -= w;
648     }
649   }
650   while (n-- > 0)
651     waddch(win, ' ');
652 }