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