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