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