]> git.llucax.com Git - software/mutt-debian.git/blob - curs_lib.c
adding a missing bug number
[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     if (progress->flags & M_PROGRESS_SIZE)
424     {
425       pos = pos / (progress->inc << 10) * (progress->inc << 10);
426       mutt_pretty_size (posstr, sizeof (posstr), pos);
427     }
428     else
429       snprintf (posstr, sizeof (posstr), "%ld", pos);
430
431     dprint (5, (debugfile, "updating progress: %s\n", posstr));
432
433     progress->pos = pos;
434     if (now)
435       progress->timestamp = now;
436
437     if (progress->size > 0)
438     {
439       mutt_message ("%s %s/%s (%d%%)", progress->msg, posstr, progress->sizestr,
440                     percent > 0 ? percent :
441                         (int) (100.0 * (double) progress->pos / progress->size));
442     }
443     else
444     {
445       if (percent > 0)
446         mutt_message ("%s %s (%d%%)", progress->msg, posstr, percent);
447       else
448         mutt_message ("%s %s", progress->msg, posstr);
449     }
450   }
451
452 out:
453   if (pos >= progress->size)
454     mutt_clear_error ();
455 }
456
457 void mutt_show_error (void)
458 {
459   if (option (OPTKEEPQUIET))
460     return;
461   
462   SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
463   CLEARLINE (LINES-1);
464   addstr (Errorbuf);
465   SETCOLOR (MT_COLOR_NORMAL);
466 }
467
468 void mutt_endwin (const char *msg)
469 {
470   int e = errno;
471
472   if (!option (OPTNOCURSES))
473   {
474     CLEARLINE (LINES - 1);
475     
476     attrset (A_NORMAL);
477     mutt_refresh ();
478     endwin ();
479   }
480   
481   if (msg && *msg)
482   {
483     puts (msg);
484     fflush (stdout);
485   }
486
487   errno = e;
488 }
489
490 void mutt_perror (const char *s)
491 {
492   char *p = strerror (errno);
493
494   dprint (1, (debugfile, "%s: %s (errno = %d)\n", s, 
495       p ? p : "unknown error", errno));
496   mutt_error ("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
497 }
498
499 int mutt_any_key_to_continue (const char *s)
500 {
501   struct termios t;
502   struct termios old;
503   int f, ch;
504
505   f = open ("/dev/tty", O_RDONLY);
506   tcgetattr (f, &t);
507   memcpy ((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */
508   t.c_lflag &= ~(ICANON | ECHO);
509   t.c_cc[VMIN] = 1;
510   t.c_cc[VTIME] = 0;
511   tcsetattr (f, TCSADRAIN, &t);
512   fflush (stdout);
513   if (s)
514     fputs (s, stdout);
515   else
516     fputs (_("Press any key to continue..."), stdout);
517   fflush (stdout);
518   ch = fgetc (stdin);
519   fflush (stdin);
520   tcsetattr (f, TCSADRAIN, &old);
521   close (f);
522   fputs ("\r\n", stdout);
523   mutt_clear_error ();
524   return (ch);
525 }
526
527 int mutt_do_pager (const char *banner,
528                    const char *tempfile,
529                    int do_color,
530                    pager_t *info)
531 {
532   int rc;
533   
534   if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
535     rc = mutt_pager (banner, tempfile, do_color, info);
536   else
537   {
538     char cmd[STRING];
539     
540     mutt_endwin (NULL);
541     mutt_expand_file_fmt (cmd, sizeof(cmd), Pager, tempfile);
542     if (mutt_system (cmd) == -1)
543     {
544       mutt_error (_("Error running \"%s\"!"), cmd);
545       rc = -1;
546     }
547     else
548       rc = 0;
549     mutt_unlink (tempfile);
550   }
551
552   return rc;
553 }
554
555 int _mutt_enter_fname (const char *prompt, char *buf, size_t blen, int *redraw, int buffy, int multiple, char ***files, int *numfiles)
556 {
557   event_t ch;
558
559   mvaddstr (LINES-1, 0, (char *) prompt);
560   addstr (_(" ('?' for list): "));
561   if (buf[0])
562     addstr (buf);
563   clrtoeol ();
564   mutt_refresh ();
565
566   ch = mutt_getch();
567   if (ch.ch < 0)
568   {
569     CLEARLINE (LINES-1);
570     return (-1);
571   }
572   else if (ch.ch == '?')
573   {
574     mutt_refresh ();
575     buf[0] = 0;
576     _mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0), 
577                        files, numfiles);
578     *redraw = REDRAW_FULL;
579   }
580   else
581   {
582     char *pc = safe_malloc (mutt_strlen (prompt) + 3);
583
584     sprintf (pc, "%s: ", prompt);       /* __SPRINTF_CHECKED__ */
585     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
586     if (_mutt_get_field (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files, numfiles)
587         != 0)
588       buf[0] = 0;
589     MAYBE_REDRAW (*redraw);
590     FREE (&pc);
591   }
592
593   return 0;
594 }
595
596 void mutt_ungetch (int ch, int op)
597 {
598   event_t tmp;
599
600   tmp.ch = ch;
601   tmp.op = op;
602
603   if (UngetCount >= UngetBufLen)
604     safe_realloc (&KeyEvent, (UngetBufLen += 128) * sizeof(event_t));
605
606   KeyEvent[UngetCount++] = tmp;
607 }
608
609 void mutt_flushinp (void)
610 {
611   UngetCount = 0;
612   flushinp ();
613 }
614
615 #if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
616 /* The argument can take 3 values:
617  * -1: restore the value of the last call
618  *  0: make the cursor invisible
619  *  1: make the cursor visible
620  */
621 void mutt_curs_set (int cursor)
622 {
623   static int SavedCursor = 1;
624   
625   if (cursor < 0)
626     cursor = SavedCursor;
627   else
628     SavedCursor = cursor;
629   
630   if (curs_set (cursor) == ERR) {
631     if (cursor == 1)    /* cnorm */
632       curs_set (2);     /* cvvis */
633   }
634 }
635 #endif
636
637 int mutt_multi_choice (char *prompt, char *letters)
638 {
639   event_t ch;
640   int choice;
641   char *p;
642
643   mvaddstr (LINES - 1, 0, prompt);
644   clrtoeol ();
645   FOREVER
646   {
647     mutt_refresh ();
648     ch  = mutt_getch ();
649     if (ch.ch < 0 || CI_is_return (ch.ch))
650     {
651       choice = -1;
652       break;
653     }
654     else
655     {
656       p = strchr (letters, ch.ch);
657       if (p)
658       {
659         choice = p - letters + 1;
660         break;
661       }
662       else if (ch.ch <= '9' && ch.ch > '0')
663       {
664         choice = ch.ch - '0';
665         if (choice <= mutt_strlen (letters))
666           break;
667       }
668     }
669     BEEP ();
670   }
671   CLEARLINE (LINES - 1);
672   mutt_refresh ();
673   return choice;
674 }
675
676 /*
677  * addwch would be provided by an up-to-date curses library
678  */
679
680 int mutt_addwch (wchar_t wc)
681 {
682   char buf[MB_LEN_MAX*2];
683   mbstate_t mbstate;
684   size_t n1, n2;
685
686   memset (&mbstate, 0, sizeof (mbstate));
687   if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t)(-1) ||
688       (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t)(-1))
689     return -1; /* ERR */
690   else
691     return addstr (buf);
692 }
693
694
695 /*
696  * This formats a string, a bit like
697  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
698  * except that the widths refer to the number of character cells
699  * when printed.
700  */
701
702 void mutt_format_string (char *dest, size_t destlen,
703                          int min_width, int max_width,
704                          int justify, char m_pad_char,
705                          const char *s, size_t n,
706                          int arboreal)
707 {
708   char *p;
709   wchar_t wc;
710   int w;
711   size_t k, k2;
712   char scratch[MB_LEN_MAX];
713   mbstate_t mbstate1, mbstate2;
714
715   memset(&mbstate1, 0, sizeof (mbstate1));
716   memset(&mbstate2, 0, sizeof (mbstate2));
717   --destlen;
718   p = dest;
719   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k)
720   {
721     if (k == (size_t)(-1) || k == (size_t)(-2))
722     {
723       if (k == (size_t)(-1) && errno == EILSEQ)
724         memset (&mbstate1, 0, sizeof (mbstate1));
725
726       k = (k == (size_t)(-1)) ? 1 : n;
727       wc = replacement_char ();
728     }
729     if (arboreal && wc < M_TREE_MAX)
730       w = 1; /* hack */
731     else
732     {
733       if (!IsWPrint (wc))
734         wc = '?';
735       w = wcwidth (wc);
736     }
737     if (w >= 0)
738     {
739       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
740         break;
741       min_width -= w;
742       max_width -= w;
743       strncpy (p, scratch, k2);
744       p += k2;            
745       destlen -= k2;
746     }
747   }
748   w = (int)destlen < min_width ? destlen : min_width;
749   if (w <= 0)
750     *p = '\0';
751   else if (justify == FMT_RIGHT)        /* right justify */
752   {
753     p[w] = '\0';
754     while (--p >= dest)
755       p[w] = *p;
756     while (--w >= 0)
757       dest[w] = m_pad_char;
758   }
759   else if (justify == FMT_CENTER)       /* center */
760   {
761     char *savedp = p;
762     int half = (w+1) / 2; /* half of cushion space */
763
764     p[w] = '\0';
765
766     /* move str to center of buffer */
767     while (--p >= dest)
768       p[half] = *p;
769
770     /* fill rhs */
771     p = savedp + half;
772     while (--w >= half)
773       *p++ = m_pad_char;
774
775     /* fill lhs */
776     while (half--)
777       dest[half] = m_pad_char;
778   }
779   else                                  /* left justify */
780   {
781     while (--w >= 0)
782       *p++ = m_pad_char;
783     *p = '\0';
784   }
785 }
786
787 /*
788  * This formats a string rather like
789  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
790  *   snprintf (dest, destlen, fmt, s);
791  * except that the numbers in the conversion specification refer to
792  * the number of character cells when printed.
793  */
794
795 static void mutt_format_s_x (char *dest,
796                              size_t destlen,
797                              const char *prefix,
798                              const char *s,
799                              int arboreal)
800 {
801   int justify = FMT_RIGHT;
802   char *p;
803   int min_width;
804   int max_width = INT_MAX;
805
806   if (*prefix == '-')
807     ++prefix, justify = FMT_LEFT;
808   else if (*prefix == '=')
809     ++prefix, justify = FMT_CENTER;
810   min_width = strtol (prefix, &p, 10);
811   if (*p == '.')
812   {
813     prefix = p + 1;
814     max_width = strtol (prefix, &p, 10);
815     if (p <= prefix)
816       max_width = INT_MAX;
817   }
818
819   mutt_format_string (dest, destlen, min_width, max_width,
820                       justify, ' ', s, mutt_strlen (s), arboreal);
821 }
822
823 void mutt_format_s (char *dest,
824                     size_t destlen,
825                     const char *prefix,
826                     const char *s)
827 {
828   mutt_format_s_x (dest, destlen, prefix, s, 0);
829 }
830
831 void mutt_format_s_tree (char *dest,
832                          size_t destlen,
833                          const char *prefix,
834                          const char *s)
835 {
836   mutt_format_s_x (dest, destlen, prefix, s, 1);
837 }
838
839 /*
840  * mutt_paddstr (n, s) is almost equivalent to
841  * mutt_format_string (bigbuf, big, n, n, FMT_LEFT, ' ', s, big, 0), addstr (bigbuf)
842  */
843
844 void mutt_paddstr (int n, const char *s)
845 {
846   wchar_t wc;
847   int w;
848   size_t k;
849   size_t len = mutt_strlen (s);
850   mbstate_t mbstate;
851
852   memset (&mbstate, 0, sizeof (mbstate));
853   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k)
854   {
855     if (k == (size_t)(-1) || k == (size_t)(-2))
856     {
857       if (k == (size_t) (-1))
858         memset (&mbstate, 0, sizeof (mbstate));
859       k = (k == (size_t)(-1)) ? 1 : len;
860       wc = replacement_char ();
861     }
862     if (!IsWPrint (wc))
863       wc = '?';
864     w = wcwidth (wc);
865     if (w >= 0)
866     {
867       if (w > n)
868         break;
869       addnstr ((char *)s, k);
870       n -= w;
871     }
872   }
873   while (n-- > 0)
874     addch (' ');
875 }
876
877 /* See how many bytes to copy from string so its at most maxlen bytes
878  * long and maxwid columns wide */
879 int mutt_wstr_trunc (const char *src, size_t maxlen, size_t maxwid, size_t *width)
880 {
881   wchar_t wc;
882   int w = 0, l = 0, cl;
883   size_t cw, n;
884   mbstate_t mbstate;
885
886   if (!src)
887     goto out;
888
889   n = mutt_strlen (src);
890
891   memset (&mbstate, 0, sizeof (mbstate));
892   for (w = 0; n && (cl = mbrtowc (&wc, src, n, &mbstate)); src += cl, n -= cl)
893   {
894     if (cl == (size_t)(-1) || cl == (size_t)(-2))
895       cw = cl = 1;
896     else
897       cw = wcwidth (wc);
898     if (cl + l > maxlen || cw + w > maxwid)
899       break;
900     l += cl;
901     w += cw;
902   }
903 out:
904   if (width)
905     *width = w;
906   return l;
907 }
908
909 /*
910  * returns the number of bytes the first (multibyte) character
911  * of input consumes:
912  *      < 0 ... conversion error
913  *      = 0 ... end of input
914  *      > 0 ... length (bytes)
915  */
916 int mutt_charlen (const char *s, int *width)
917 {
918   wchar_t wc;
919   mbstate_t mbstate;
920   size_t k, n;
921
922   if (!s || !*s)
923     return 0;
924
925   n = mutt_strlen (s);
926   memset (&mbstate, 0, sizeof (mbstate));
927   k = mbrtowc (&wc, s, n, &mbstate);
928   if (width)
929     *width = wcwidth (wc);
930   return (k == (size_t)(-1) || k == (size_t)(-2)) ? -1 : k;
931 }
932
933 /*
934  * mutt_strwidth is like mutt_strlen except that it returns the width
935  * refering to the number of characters cells.
936  */
937
938 int mutt_strwidth (const char *s)
939 {
940   wchar_t wc;
941   int w;
942   size_t k, n;
943   mbstate_t mbstate;
944
945   if (!s) return 0;
946
947   n = mutt_strlen (s);
948
949   memset (&mbstate, 0, sizeof (mbstate));
950   for (w=0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k)
951   {
952     if (k == (size_t)(-1) || k == (size_t)(-2))
953     {
954       k = (k == (size_t)(-1)) ? 1 : n;
955       wc = replacement_char ();
956     }
957     if (!IsWPrint (wc))
958       wc = '?';
959     w += wcwidth (wc);
960   }
961   return w;
962 }