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