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