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