]> git.llucax.com Git - software/mutt-debian.git/blob - curs_lib.c
Imported Upstream version 1.5.19
[software/mutt-debian.git] / curs_lib.c
1 /*
2  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
3  * Copyright (C) 2004 g10 Code GmbH
4  * 
5  *     This program is free software; you can redistribute it and/or modify
6  *     it under the terms of the GNU General Public License as published by
7  *     the Free Software Foundation; either version 2 of the License, or
8  *     (at your option) any later version.
9  * 
10  *     This program is distributed in the hope that it will be useful,
11  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *     GNU General Public License for more details.
14  * 
15  *     You should have received a copy of the GNU General Public License
16  *     along with this program; if not, write to the Free Software
17  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */ 
19
20 #if HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include "mutt.h"
25 #include "mutt_menu.h"
26 #include "mutt_curses.h"
27 #include "pager.h"
28 #include "mbyte.h"
29
30 #include <termios.h>
31 #include <sys/types.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <ctype.h>
38 #ifdef HAVE_SYS_TIME_H
39 # include <sys/time.h>
40 #endif
41 #include <time.h>
42
43 #ifdef HAVE_LANGINFO_YESEXPR
44 #include <langinfo.h>
45 #endif
46
47 /* not possible to unget more than one char under some curses libs, and it
48  * is impossible to unget function keys in SLang, so roll our own input
49  * buffering routines.
50  */
51 size_t UngetCount = 0;
52 static size_t UngetBufLen = 0;
53 static event_t *KeyEvent;
54
55 void mutt_refresh (void)
56 {
57   /* don't refresh when we are waiting for a child. */
58   if (option (OPTKEEPQUIET))
59     return;
60
61   /* don't refresh in the middle of macros unless necessary */
62   if (UngetCount && !option (OPTFORCEREFRESH))
63     return;
64
65   /* else */
66   refresh ();
67 }
68
69 /* Make sure that the next refresh does a full refresh.  This could be
70    optmized by not doing it at all if DISPLAY is set as this might
71    indicate that a GUI based pinentry was used.  Having an option to
72    customize this is of course the Mutt way.  */
73 void mutt_need_hard_redraw (void)
74 {
75   keypad (stdscr, TRUE);
76   clearok (stdscr, TRUE);
77   set_option (OPTNEEDREDRAW);
78 }
79
80 event_t mutt_getch (void)
81 {
82   int ch;
83   event_t err = {-1, OP_NULL }, ret;
84   event_t timeout = {-2, OP_NULL};
85
86   if (!option(OPTUNBUFFEREDINPUT) && UngetCount)
87     return (KeyEvent[--UngetCount]);
88
89   SigInt = 0;
90
91   mutt_allow_interrupt (1);
92 #ifdef KEY_RESIZE
93   /* ncurses 4.2 sends this when the screen is resized */
94   ch = KEY_RESIZE;
95   while (ch == KEY_RESIZE)
96 #endif /* KEY_RESIZE */
97     ch = getch ();
98   mutt_allow_interrupt (0);
99
100   if (SigInt)
101     mutt_query_exit ();
102
103   if(ch == ERR)
104   {
105     /* either timeout or the terminal has been lost */
106     if (!isatty (0))
107     {
108       endwin ();
109       exit (1);
110     }
111     return timeout;
112   }
113
114   if ((ch & 0x80) && option (OPTMETAKEY))
115   {
116     /* send ALT-x as ESC-x */
117     ch &= ~0x80;
118     mutt_ungetch (ch, 0);
119     ret.ch = '\033';
120     ret.op = 0;
121     return ret;
122   }
123
124   ret.ch = ch;
125   ret.op = 0;
126   return (ch == ctrl ('G') ? err : ret);
127 }
128
129 int _mutt_get_field (/* const */ char *field, char *buf, size_t buflen, int complete, int multiple, char ***files, int *numfiles)
130 {
131   int ret;
132   int x, y;
133
134   ENTER_STATE *es = mutt_new_enter_state();
135   
136   do
137   {
138     CLEARLINE (LINES-1);
139     addstr (field);
140     mutt_refresh ();
141     getyx (stdscr, y, x);
142     ret = _mutt_enter_string (buf, buflen, y, x, complete, multiple, files, numfiles, es);
143   }
144   while (ret == 1);
145   CLEARLINE (LINES-1);
146   mutt_free_enter_state (&es);
147   
148   return (ret);
149 }
150
151 int mutt_get_field_unbuffered (char *msg, char *buf, size_t buflen, int flags)
152 {
153   int rc;
154
155   set_option (OPTUNBUFFEREDINPUT);
156   rc = mutt_get_field (msg, buf, buflen, flags);
157   unset_option (OPTUNBUFFEREDINPUT);
158
159   return (rc);
160 }
161
162 void mutt_clear_error (void)
163 {
164   Errorbuf[0] = 0;
165   if (!option(OPTNOCURSES))
166     CLEARLINE (LINES-1);
167 }
168
169 void mutt_edit_file (const char *editor, const char *data)
170 {
171   char cmd[LONG_STRING];
172   
173   mutt_endwin (NULL);
174   mutt_expand_file_fmt (cmd, sizeof (cmd), editor, data);
175   if (mutt_system (cmd))
176   {
177     mutt_error (_("Error running \"%s\"!"), cmd);
178     mutt_sleep (2);
179   }
180 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
181   /* the terminal may have been resized while the editor owned it */
182   mutt_resize_screen ();
183 #endif
184   keypad (stdscr, TRUE);
185   clearok (stdscr, TRUE);
186 }
187
188 int mutt_yesorno (const char *msg, int def)
189 {
190   event_t ch;
191   char *yes = _("yes");
192   char *no = _("no");
193   char *answer_string;
194   size_t answer_string_len;
195
196 #ifdef HAVE_LANGINFO_YESEXPR
197   char *expr;
198   regex_t reyes;
199   regex_t reno;
200   int reyes_ok;
201   int reno_ok;
202   char answer[2];
203
204   answer[1] = 0;
205   
206   reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
207              !regcomp (&reyes, expr, REG_NOSUB|REG_EXTENDED);
208   reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
209             !regcomp (&reno, expr, REG_NOSUB|REG_EXTENDED);
210 #endif
211
212   CLEARLINE(LINES-1);
213
214   /*
215    * In order to prevent the default answer to the question to wrapped
216    * around the screen in the even the question is wider than the screen,
217    * ensure there is enough room for the answer and truncate the question
218    * to fit.
219    */
220   answer_string = safe_malloc (COLS + 1);
221   snprintf (answer_string, COLS + 1, " ([%s]/%s): ", def == M_YES ? yes : no, def == M_YES ? no : yes);
222   answer_string_len = strlen (answer_string);
223   printw ("%.*s%s", COLS - answer_string_len, msg, answer_string);
224   FREE (&answer_string);
225
226   FOREVER
227   {
228     mutt_refresh ();
229     ch = mutt_getch ();
230     if (CI_is_return (ch.ch))
231       break;
232     if (ch.ch < 0)
233     {
234       def = -1;
235       break;
236     }
237
238 #ifdef HAVE_LANGINFO_YESEXPR
239     answer[0] = ch.ch;
240     if (reyes_ok ? 
241         (regexec (& reyes, answer, 0, 0, 0) == 0) :
242 #else
243     if (
244 #endif
245         (tolower (ch.ch) == 'y'))
246     {
247       def = M_YES;
248       break;
249     }
250     else if (
251 #ifdef HAVE_LANGINFO_YESEXPR
252              reno_ok ?
253              (regexec (& reno, answer, 0, 0, 0) == 0) :
254 #endif
255              (tolower (ch.ch) == 'n'))
256     {
257       def = M_NO;
258       break;
259     }
260     else
261     {
262       BEEP();
263     }
264   }
265
266 #ifdef HAVE_LANGINFO_YESEXPR    
267   if (reyes_ok)
268     regfree (& reyes);
269   if (reno_ok)
270     regfree (& reno);
271 #endif
272
273   if (def != -1)
274   {
275     addstr ((char *) (def == M_YES ? yes : no));
276     mutt_refresh ();
277   }
278   return (def);
279 }
280
281 /* this function is called when the user presses the abort key */
282 void mutt_query_exit (void)
283 {
284   mutt_flushinp ();
285   curs_set (1);
286   if (Timeout)
287     timeout (-1); /* restore blocking operation */
288   if (mutt_yesorno (_("Exit Mutt?"), M_YES) == M_YES)
289   {
290     endwin ();
291     exit (1);
292   }
293   mutt_clear_error();
294   mutt_curs_set (-1);
295   SigInt = 0;
296 }
297
298 void mutt_curses_error (const char *fmt, ...)
299 {
300   va_list ap;
301   char scratch[LONG_STRING];
302
303   va_start (ap, fmt);
304   vsnprintf (scratch, sizeof (scratch), fmt, ap);
305   va_end (ap);
306   
307   dprint (1, (debugfile, "%s\n", scratch));
308   mutt_format_string (Errorbuf, sizeof (Errorbuf),
309                       0, COLS-2, FMT_LEFT, 0, scratch, sizeof (scratch), 0);
310
311   if (!option (OPTKEEPQUIET))
312   {
313     BEEP ();
314     SETCOLOR (MT_COLOR_ERROR);
315     mvaddstr (LINES-1, 0, Errorbuf);
316     clrtoeol ();
317     SETCOLOR (MT_COLOR_NORMAL);
318     mutt_refresh ();
319   }
320
321   set_option (OPTMSGERR);
322 }
323
324 void mutt_curses_message (const char *fmt, ...)
325 {
326   va_list ap;
327   char scratch[LONG_STRING];
328
329   va_start (ap, fmt);
330   vsnprintf (scratch, sizeof (scratch), fmt, ap);
331   va_end (ap);
332
333   mutt_format_string (Errorbuf, sizeof (Errorbuf),
334                       0, COLS-2, FMT_LEFT, 0, scratch, sizeof (scratch), 0);
335
336   if (!option (OPTKEEPQUIET))
337   {
338     SETCOLOR (MT_COLOR_MESSAGE);
339     mvaddstr (LINES - 1, 0, Errorbuf);
340     clrtoeol ();
341     SETCOLOR (MT_COLOR_NORMAL);
342     mutt_refresh ();
343   }
344
345   unset_option (OPTMSGERR);
346 }
347
348 void mutt_progress_init (progress_t* progress, const char *msg,
349                          unsigned short flags, unsigned short inc,
350                          long size)
351 {
352   struct timeval tv = { 0, 0 };
353
354   if (!progress)
355     return;
356   if (option(OPTNOCURSES))
357     return;
358
359   memset (progress, 0, sizeof (progress_t));
360   progress->inc = inc;
361   progress->flags = flags;
362   progress->msg = msg;
363   progress->size = size;
364   if (progress->size) {
365     if (progress->flags & M_PROGRESS_SIZE)
366       mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
367                         progress->size);
368     else
369       snprintf (progress->sizestr, sizeof (progress->sizestr), "%ld",
370                 progress->size);
371   }
372   if (!inc)
373   {
374     if (size)
375       mutt_message ("%s (%s)", msg, progress->sizestr);
376     else
377       mutt_message (msg);
378     return;
379   }
380   if (gettimeofday (&tv, NULL) < 0)
381     dprint (1, (debugfile, "gettimeofday failed: %d\n", errno));
382   /* if timestamp is 0 no time-based suppression is done */
383   if (TimeInc)
384     progress->timestamp = ((unsigned int) tv.tv_sec * 1000)
385         + (unsigned int) (tv.tv_usec / 1000);
386   mutt_progress_update (progress, 0, 0);
387 }
388
389 void mutt_progress_update (progress_t* progress, long pos, int percent)
390 {
391   char posstr[SHORT_STRING];
392   short update = 0;
393   struct timeval tv = { 0, 0 };
394   unsigned int now = 0;
395
396   if (option(OPTNOCURSES))
397     return;
398
399   if (!progress->inc)
400     goto out;
401
402   /* refresh if size > inc */
403   if (progress->flags & M_PROGRESS_SIZE &&
404       (pos >= progress->pos + (progress->inc << 10)))
405     update = 1;
406   else if (pos >= progress->pos + progress->inc)
407     update = 1;
408
409   /* skip refresh if not enough time has passed */
410   if (update && progress->timestamp && !gettimeofday (&tv, NULL)) {
411     now = ((unsigned int) tv.tv_sec * 1000)
412           + (unsigned int) (tv.tv_usec / 1000);
413     if (now && now - progress->timestamp < TimeInc)
414       update = 0;
415   }
416
417   /* always show the first update */
418   if (!pos)
419     update = 1;
420
421   if (update)
422   {
423     dprint (1, (debugfile, "Updating progress: %ld\n", pos));
424     if (progress->flags & M_PROGRESS_SIZE)
425     {
426       pos = pos / (progress->inc << 10) * (progress->inc << 10);
427       mutt_pretty_size (posstr, sizeof (posstr), pos);
428     }
429     else
430       snprintf (posstr, sizeof (posstr), "%ld", pos);
431     
432     progress->pos = pos;
433     if (now)
434       progress->timestamp = now;
435
436     if (progress->size > 0)
437     {
438       mutt_message ("%s %s/%s (%d%%)", progress->msg, posstr, progress->sizestr,
439                     percent > 0 ? percent :
440                         (int) (100.0 * (double) progress->pos / progress->size));
441     }
442     else
443     {
444       if (percent > 0)
445         mutt_message ("%s %s (%d%%)", progress->msg, posstr, percent);
446       else
447         mutt_message ("%s %s", progress->msg, posstr);
448     }
449   }
450
451 out:
452   if (pos >= progress->size)
453     mutt_clear_error ();
454 }
455
456 void mutt_show_error (void)
457 {
458   if (option (OPTKEEPQUIET))
459     return;
460   
461   SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
462   CLEARLINE (LINES-1);
463   addstr (Errorbuf);
464   SETCOLOR (MT_COLOR_NORMAL);
465 }
466
467 void mutt_endwin (const char *msg)
468 {
469   int e = errno;
470
471   if (!option (OPTNOCURSES))
472   {
473     CLEARLINE (LINES - 1);
474     
475     attrset (A_NORMAL);
476     mutt_refresh ();
477     endwin ();
478   }
479   
480   if (msg && *msg)
481   {
482     puts (msg);
483     fflush (stdout);
484   }
485
486   errno = e;
487 }
488
489 void mutt_perror (const char *s)
490 {
491   char *p = strerror (errno);
492
493   dprint (1, (debugfile, "%s: %s (errno = %d)\n", s, 
494       p ? p : "unknown error", errno));
495   mutt_error ("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
496 }
497
498 int mutt_any_key_to_continue (const char *s)
499 {
500   struct termios t;
501   struct termios old;
502   int f, ch;
503
504   f = open ("/dev/tty", O_RDONLY);
505   tcgetattr (f, &t);
506   memcpy ((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */
507   t.c_lflag &= ~(ICANON | ECHO);
508   t.c_cc[VMIN] = 1;
509   t.c_cc[VTIME] = 0;
510   tcsetattr (f, TCSADRAIN, &t);
511   fflush (stdout);
512   if (s)
513     fputs (s, stdout);
514   else
515     fputs (_("Press any key to continue..."), stdout);
516   fflush (stdout);
517   ch = fgetc (stdin);
518   fflush (stdin);
519   tcsetattr (f, TCSADRAIN, &old);
520   close (f);
521   fputs ("\r\n", stdout);
522   mutt_clear_error ();
523   return (ch);
524 }
525
526 int mutt_do_pager (const char *banner,
527                    const char *tempfile,
528                    int do_color,
529                    pager_t *info)
530 {
531   int rc;
532   
533   if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
534     rc = mutt_pager (banner, tempfile, do_color, info);
535   else
536   {
537     char cmd[STRING];
538     
539     mutt_endwin (NULL);
540     mutt_expand_file_fmt (cmd, sizeof(cmd), Pager, tempfile);
541     if (mutt_system (cmd) == -1)
542     {
543       mutt_error (_("Error running \"%s\"!"), cmd);
544       rc = -1;
545     }
546     else
547       rc = 0;
548     mutt_unlink (tempfile);
549   }
550
551   return rc;
552 }
553
554 int _mutt_enter_fname (const char *prompt, char *buf, size_t blen, int *redraw, int buffy, int multiple, char ***files, int *numfiles)
555 {
556   event_t ch;
557
558   mvaddstr (LINES-1, 0, (char *) prompt);
559   addstr (_(" ('?' for list): "));
560   if (buf[0])
561     addstr (buf);
562   clrtoeol ();
563   mutt_refresh ();
564
565   ch = mutt_getch();
566   if (ch.ch < 0)
567   {
568     CLEARLINE (LINES-1);
569     return (-1);
570   }
571   else if (ch.ch == '?')
572   {
573     mutt_refresh ();
574     buf[0] = 0;
575     _mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0), 
576                        files, numfiles);
577     *redraw = REDRAW_FULL;
578   }
579   else
580   {
581     char *pc = safe_malloc (mutt_strlen (prompt) + 3);
582
583     sprintf (pc, "%s: ", prompt);       /* __SPRINTF_CHECKED__ */
584     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
585     if (_mutt_get_field (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files, numfiles)
586         != 0)
587       buf[0] = 0;
588     MAYBE_REDRAW (*redraw);
589     FREE (&pc);
590   }
591
592   return 0;
593 }
594
595 void mutt_ungetch (int ch, int op)
596 {
597   event_t tmp;
598
599   tmp.ch = ch;
600   tmp.op = op;
601
602   if (UngetCount >= UngetBufLen)
603     safe_realloc (&KeyEvent, (UngetBufLen += 128) * sizeof(event_t));
604
605   KeyEvent[UngetCount++] = tmp;
606 }
607
608 void mutt_flushinp (void)
609 {
610   UngetCount = 0;
611   flushinp ();
612 }
613
614 #if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
615 /* The argument can take 3 values:
616  * -1: restore the value of the last call
617  *  0: make the cursor invisible
618  *  1: make the cursor visible
619  */
620 void mutt_curs_set (int cursor)
621 {
622   static int SavedCursor = 1;
623   
624   if (cursor < 0)
625     cursor = SavedCursor;
626   else
627     SavedCursor = cursor;
628   
629   if (curs_set (cursor) == ERR) {
630     if (cursor == 1)    /* cnorm */
631       curs_set (2);     /* cvvis */
632   }
633 }
634 #endif
635
636 int mutt_multi_choice (char *prompt, char *letters)
637 {
638   event_t ch;
639   int choice;
640   char *p;
641
642   mvaddstr (LINES - 1, 0, prompt);
643   clrtoeol ();
644   FOREVER
645   {
646     mutt_refresh ();
647     ch  = mutt_getch ();
648     if (ch.ch < 0 || CI_is_return (ch.ch))
649     {
650       choice = -1;
651       break;
652     }
653     else
654     {
655       p = strchr (letters, ch.ch);
656       if (p)
657       {
658         choice = p - letters + 1;
659         break;
660       }
661       else if (ch.ch <= '9' && ch.ch > '0')
662       {
663         choice = ch.ch - '0';
664         if (choice <= mutt_strlen (letters))
665           break;
666       }
667     }
668     BEEP ();
669   }
670   CLEARLINE (LINES - 1);
671   mutt_refresh ();
672   return choice;
673 }
674
675 /*
676  * addwch would be provided by an up-to-date curses library
677  */
678
679 int mutt_addwch (wchar_t wc)
680 {
681   char buf[MB_LEN_MAX*2];
682   mbstate_t mbstate;
683   size_t n1, n2;
684
685   memset (&mbstate, 0, sizeof (mbstate));
686   if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t)(-1) ||
687       (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t)(-1))
688     return -1; /* ERR */
689   else
690     return addstr (buf);
691 }
692
693
694 /*
695  * This formats a string, a bit like
696  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
697  * except that the widths refer to the number of character cells
698  * when printed.
699  */
700
701 void mutt_format_string (char *dest, size_t destlen,
702                          int min_width, int max_width,
703                          int justify, char m_pad_char,
704                          const char *s, size_t n,
705                          int arboreal)
706 {
707   char *p;
708   wchar_t wc;
709   int w;
710   size_t k, k2;
711   char scratch[MB_LEN_MAX];
712   mbstate_t mbstate1, mbstate2;
713
714   memset(&mbstate1, 0, sizeof (mbstate1));
715   memset(&mbstate2, 0, sizeof (mbstate2));
716   --destlen;
717   p = dest;
718   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k)
719   {
720     if (k == (size_t)(-1) || k == (size_t)(-2))
721     {
722       if (k == (size_t)(-1) && errno == EILSEQ)
723         memset (&mbstate1, 0, sizeof (mbstate1));
724
725       k = (k == (size_t)(-1)) ? 1 : n;
726       wc = replacement_char ();
727     }
728     if (arboreal && wc < M_TREE_MAX)
729       w = 1; /* hack */
730     else
731     {
732       if (!IsWPrint (wc))
733         wc = '?';
734       w = wcwidth (wc);
735     }
736     if (w >= 0)
737     {
738       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
739         break;
740       min_width -= w;
741       max_width -= w;
742       strncpy (p, scratch, k2);
743       p += k2;            
744       destlen -= k2;
745     }
746   }
747   w = (int)destlen < min_width ? destlen : min_width;
748   if (w <= 0)
749     *p = '\0';
750   else if (justify == FMT_RIGHT)        /* right justify */
751   {
752     p[w] = '\0';
753     while (--p >= dest)
754       p[w] = *p;
755     while (--w >= 0)
756       dest[w] = m_pad_char;
757   }
758   else if (justify == FMT_CENTER)       /* center */
759   {
760     char *savedp = p;
761     int half = (w+1) / 2; /* half of cushion space */
762
763     p[w] = '\0';
764
765     /* move str to center of buffer */
766     while (--p >= dest)
767       p[half] = *p;
768
769     /* fill rhs */
770     p = savedp + half;
771     while (--w >= half)
772       *p++ = m_pad_char;
773
774     /* fill lhs */
775     while (half--)
776       dest[half] = m_pad_char;
777   }
778   else                                  /* left justify */
779   {
780     while (--w >= 0)
781       *p++ = m_pad_char;
782     *p = '\0';
783   }
784 }
785
786 /*
787  * This formats a string rather like
788  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
789  *   snprintf (dest, destlen, fmt, s);
790  * except that the numbers in the conversion specification refer to
791  * the number of character cells when printed.
792  */
793
794 static void mutt_format_s_x (char *dest,
795                              size_t destlen,
796                              const char *prefix,
797                              const char *s,
798                              int arboreal)
799 {
800   int justify = FMT_RIGHT;
801   char *p;
802   int min_width;
803   int max_width = INT_MAX;
804
805   if (*prefix == '-')
806     ++prefix, justify = FMT_LEFT;
807   else if (*prefix == '=')
808     ++prefix, justify = FMT_CENTER;
809   min_width = strtol (prefix, &p, 10);
810   if (*p == '.')
811   {
812     prefix = p + 1;
813     max_width = strtol (prefix, &p, 10);
814     if (p <= prefix)
815       max_width = INT_MAX;
816   }
817
818   mutt_format_string (dest, destlen, min_width, max_width,
819                       justify, ' ', s, mutt_strlen (s), arboreal);
820 }
821
822 void mutt_format_s (char *dest,
823                     size_t destlen,
824                     const char *prefix,
825                     const char *s)
826 {
827   mutt_format_s_x (dest, destlen, prefix, s, 0);
828 }
829
830 void mutt_format_s_tree (char *dest,
831                          size_t destlen,
832                          const char *prefix,
833                          const char *s)
834 {
835   mutt_format_s_x (dest, destlen, prefix, s, 1);
836 }
837
838 /*
839  * mutt_paddstr (n, s) is almost equivalent to
840  * mutt_format_string (bigbuf, big, n, n, FMT_LEFT, ' ', s, big, 0), addstr (bigbuf)
841  */
842
843 void mutt_paddstr (int n, const char *s)
844 {
845   wchar_t wc;
846   int w;
847   size_t k;
848   size_t len = mutt_strlen (s);
849   mbstate_t mbstate;
850
851   memset (&mbstate, 0, sizeof (mbstate));
852   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k)
853   {
854     if (k == (size_t)(-1) || k == (size_t)(-2))
855     {
856       if (k == (size_t) (-1))
857         memset (&mbstate, 0, sizeof (mbstate));
858       k = (k == (size_t)(-1)) ? 1 : len;
859       wc = replacement_char ();
860     }
861     if (!IsWPrint (wc))
862       wc = '?';
863     w = wcwidth (wc);
864     if (w >= 0)
865     {
866       if (w > n)
867         break;
868       addnstr ((char *)s, k);
869       n -= w;
870     }
871   }
872   while (n-- > 0)
873     addch (' ');
874 }
875
876 /* See how many bytes to copy from string so its at most maxlen bytes
877  * long and maxwid columns wide */
878 int mutt_wstr_trunc (const char *src, size_t maxlen, size_t maxwid, size_t *width)
879 {
880   wchar_t wc;
881   int w = 0, l = 0, cl;
882   size_t cw, n;
883   mbstate_t mbstate;
884
885   if (!src)
886     goto out;
887
888   n = mutt_strlen (src);
889
890   memset (&mbstate, 0, sizeof (mbstate));
891   for (w = 0; n && (cl = mbrtowc (&wc, src, n, &mbstate)); src += cl, n -= cl)
892   {
893     if (cl == (size_t)(-1) || cl == (size_t)(-2))
894       cw = cl = 1;
895     else
896       cw = wcwidth (wc);
897     if (cl + l > maxlen || cw + w > maxwid)
898       break;
899     l += cl;
900     w += cw;
901   }
902 out:
903   if (width)
904     *width = w;
905   return l;
906 }
907
908 /*
909  * returns the number of bytes the first (multibyte) character
910  * of input consumes:
911  *      < 0 ... conversion error
912  *      = 0 ... end of input
913  *      > 0 ... length (bytes)
914  */
915 int mutt_charlen (const char *s, int *width)
916 {
917   wchar_t wc;
918   mbstate_t mbstate;
919   size_t k, n;
920
921   if (!s || !*s)
922     return 0;
923
924   n = mutt_strlen (s);
925   memset (&mbstate, 0, sizeof (mbstate));
926   k = mbrtowc (&wc, s, n, &mbstate);
927   if (width)
928     *width = wcwidth (wc);
929   return (k == (size_t)(-1) || k == (size_t)(-2)) ? -1 : k;
930 }
931
932 /*
933  * mutt_strwidth is like mutt_strlen except that it returns the width
934  * refering to the number of characters cells.
935  */
936
937 int mutt_strwidth (const char *s)
938 {
939   wchar_t wc;
940   int w;
941   size_t k, n;
942   mbstate_t mbstate;
943
944   if (!s) return 0;
945
946   n = mutt_strlen (s);
947
948   memset (&mbstate, 0, sizeof (mbstate));
949   for (w=0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k)
950   {
951     if (k == (size_t)(-1) || k == (size_t)(-2))
952     {
953       k = (k == (size_t)(-1)) ? 1 : n;
954       wc = replacement_char ();
955     }
956     if (!IsWPrint (wc))
957       wc = '?';
958     w += wcwidth (wc);
959   }
960   return w;
961 }