/src/neomutt/gui/curs_lib.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * GUI miscellaneous curses (window drawing) routines |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 1996-2002,2010,2012-2013 Michael R. Elkins <me@mutt.org> |
7 | | * Copyright (C) 2004 g10 Code GmbH |
8 | | * |
9 | | * @copyright |
10 | | * This program is free software: you can redistribute it and/or modify it under |
11 | | * the terms of the GNU General Public License as published by the Free Software |
12 | | * Foundation, either version 2 of the License, or (at your option) any later |
13 | | * version. |
14 | | * |
15 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
16 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
17 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
18 | | * details. |
19 | | * |
20 | | * You should have received a copy of the GNU General Public License along with |
21 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
22 | | */ |
23 | | |
24 | | /** |
25 | | * @page gui_curs_lib Window drawing code |
26 | | * |
27 | | * GUI miscellaneous curses (window drawing) routines |
28 | | */ |
29 | | |
30 | | #include "config.h" |
31 | | #include <errno.h> |
32 | | #include <fcntl.h> |
33 | | #include <limits.h> |
34 | | #include <stdbool.h> |
35 | | #include <stdio.h> |
36 | | #include <stdlib.h> |
37 | | #include <string.h> |
38 | | #include <termios.h> |
39 | | #include <unistd.h> |
40 | | #include <wchar.h> |
41 | | #include "mutt/lib.h" |
42 | | #include "config/lib.h" |
43 | | #include "core/lib.h" |
44 | | #include "mutt.h" |
45 | | #include "curs_lib.h" |
46 | | #include "browser/lib.h" |
47 | | #include "color/lib.h" |
48 | | #include "editor/lib.h" |
49 | | #include "history/lib.h" |
50 | | #include "key/lib.h" |
51 | | #include "question/lib.h" |
52 | | #include "globals.h" |
53 | | #include "msgcont.h" |
54 | | #include "msgwin.h" |
55 | | #include "mutt_curses.h" |
56 | | #include "mutt_logging.h" |
57 | | #include "mutt_thread.h" |
58 | | #include "mutt_window.h" |
59 | | #include "opcodes.h" |
60 | | #include "protos.h" |
61 | | #ifdef HAVE_ISWBLANK |
62 | | #include <wctype.h> |
63 | | #endif |
64 | | |
65 | | /** |
66 | | * mutt_beep - Irritate the user |
67 | | * @param force If true, ignore the "$beep" config variable |
68 | | */ |
69 | | void mutt_beep(bool force) |
70 | 0 | { |
71 | 0 | const bool c_beep = cs_subset_bool(NeoMutt->sub, "beep"); |
72 | 0 | if (force || c_beep) |
73 | 0 | beep(); |
74 | 0 | } |
75 | | |
76 | | /** |
77 | | * mutt_refresh - Force a refresh of the screen |
78 | | */ |
79 | | void mutt_refresh(void) |
80 | 0 | { |
81 | | /* don't refresh when we are waiting for a child. */ |
82 | 0 | if (OptKeepQuiet) |
83 | 0 | return; |
84 | | |
85 | | /* don't refresh in the middle of macros unless necessary */ |
86 | 0 | if (!ARRAY_EMPTY(&MacroEvents) && !OptForceRefresh) |
87 | 0 | return; |
88 | | |
89 | | /* else */ |
90 | 0 | refresh(); |
91 | 0 | } |
92 | | |
93 | | /** |
94 | | * mutt_need_hard_redraw - Force a hard refresh |
95 | | * |
96 | | * Make sure that the next refresh does a full refresh. This could be |
97 | | * optimized by not doing it at all if DISPLAY is set as this might indicate |
98 | | * that a GUI based pinentry was used. Having an option to customize this is |
99 | | * of course the NeoMutt way. |
100 | | */ |
101 | | void mutt_need_hard_redraw(void) |
102 | 0 | { |
103 | | // Forcibly switch to the alternate screen. |
104 | | // Using encryption can leave ncurses confused about which mode it's in. |
105 | 0 | fputs("\033[?1049h", stdout); |
106 | 0 | fflush(stdout); |
107 | 0 | keypad(stdscr, true); |
108 | 0 | clearok(stdscr, true); |
109 | 0 | window_redraw(NULL); |
110 | 0 | } |
111 | | |
112 | | /** |
113 | | * mutt_edit_file - Let the user edit a file |
114 | | * @param editor User's editor config |
115 | | * @param file File to edit |
116 | | */ |
117 | | void mutt_edit_file(const char *editor, const char *file) |
118 | 0 | { |
119 | 0 | struct Buffer *cmd = buf_pool_get(); |
120 | |
|
121 | 0 | mutt_endwin(); |
122 | 0 | buf_file_expand_fmt_quote(cmd, editor, file); |
123 | 0 | if (mutt_system(buf_string(cmd)) != 0) |
124 | 0 | { |
125 | 0 | mutt_error(_("Error running \"%s\""), buf_string(cmd)); |
126 | 0 | } |
127 | | /* the terminal may have been resized while the editor owned it */ |
128 | 0 | mutt_resize_screen(); |
129 | 0 | keypad(stdscr, true); |
130 | 0 | clearok(stdscr, true); |
131 | |
|
132 | 0 | buf_pool_release(&cmd); |
133 | 0 | } |
134 | | |
135 | | /** |
136 | | * mutt_query_exit - Ask the user if they want to leave NeoMutt |
137 | | * |
138 | | * This function is called when the user presses the abort key. |
139 | | */ |
140 | | void mutt_query_exit(void) |
141 | 0 | { |
142 | 0 | mutt_flushinp(); |
143 | 0 | if (query_yesorno(_("Exit NeoMutt without saving?"), MUTT_YES) == MUTT_YES) |
144 | 0 | { |
145 | 0 | mutt_exit(0); /* This call never returns */ |
146 | 0 | } |
147 | 0 | mutt_clear_error(); |
148 | 0 | SigInt = false; |
149 | 0 | } |
150 | | |
151 | | /** |
152 | | * mutt_endwin - Shutdown curses |
153 | | */ |
154 | | void mutt_endwin(void) |
155 | 0 | { |
156 | 0 | if (OptNoCurses) |
157 | 0 | return; |
158 | | |
159 | 0 | int e = errno; |
160 | | |
161 | | /* at least in some situations (screen + xterm under SuSE11/12) endwin() |
162 | | * doesn't properly flush the screen without an explicit call. */ |
163 | 0 | mutt_refresh(); |
164 | 0 | endwin(); |
165 | 0 | SigWinch = true; |
166 | |
|
167 | 0 | errno = e; |
168 | 0 | } |
169 | | |
170 | | /** |
171 | | * mutt_perror_debug - Show the user an 'errno' message |
172 | | * @param s Additional text to show |
173 | | */ |
174 | | void mutt_perror_debug(const char *s) |
175 | 0 | { |
176 | 0 | char *p = strerror(errno); |
177 | |
|
178 | 0 | mutt_debug(LL_DEBUG1, "%s: %s (errno = %d)\n", s, p ? p : "unknown error", errno); |
179 | 0 | mutt_error("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno); |
180 | 0 | } |
181 | | |
182 | | /** |
183 | | * mutt_any_key_to_continue - Prompt the user to 'press any key' and wait |
184 | | * @param s Message prompt |
185 | | * @retval num Key pressed |
186 | | * @retval EOF Error, or prompt aborted |
187 | | */ |
188 | | int mutt_any_key_to_continue(const char *s) |
189 | 0 | { |
190 | 0 | struct termios term = { 0 }; |
191 | 0 | struct termios old = { 0 }; |
192 | |
|
193 | 0 | int fd = open("/dev/tty", O_RDONLY); |
194 | 0 | if (fd < 0) |
195 | 0 | return EOF; |
196 | | |
197 | 0 | tcgetattr(fd, &old); // Save the current tty settings |
198 | |
|
199 | 0 | term = old; |
200 | 0 | term.c_lflag &= ~(ICANON | ECHO); // Canonical (not line-buffered), don't echo the characters |
201 | 0 | term.c_cc[VMIN] = 1; // Wait for at least one character |
202 | 0 | term.c_cc[VTIME] = 255; // Wait for 25.5s |
203 | 0 | tcsetattr(fd, TCSANOW, &term); |
204 | |
|
205 | 0 | if (s) |
206 | 0 | fputs(s, stdout); |
207 | 0 | else |
208 | 0 | fputs(_("Press any key to continue..."), stdout); |
209 | 0 | fflush(stdout); |
210 | |
|
211 | 0 | char ch = '\0'; |
212 | | // Wait for a character. This might timeout, so loop. |
213 | 0 | while (read(fd, &ch, 1) == 0) |
214 | 0 | ; // do nothing |
215 | | |
216 | | // Change the tty settings to be non-blocking |
217 | 0 | term.c_cc[VMIN] = 0; // Returning with zero characters is acceptable |
218 | 0 | term.c_cc[VTIME] = 0; // Don't wait |
219 | 0 | tcsetattr(fd, TCSANOW, &term); |
220 | |
|
221 | 0 | char buf[64] = { 0 }; |
222 | 0 | while (read(fd, buf, sizeof(buf)) > 0) |
223 | 0 | ; // Mop up any remaining chars |
224 | |
|
225 | 0 | tcsetattr(fd, TCSANOW, &old); // Restore the previous tty settings |
226 | 0 | close(fd); |
227 | |
|
228 | 0 | fputs("\r\n", stdout); |
229 | 0 | mutt_clear_error(); |
230 | 0 | return (ch >= 0) ? ch : EOF; |
231 | 0 | } |
232 | | |
233 | | /** |
234 | | * mw_enter_fname - Ask the user to select a file - @ingroup gui_mw |
235 | | * @param[in] prompt Prompt |
236 | | * @param[in] fname Buffer for the result |
237 | | * @param[in] mailbox If true, select mailboxes |
238 | | * @param[in] multiple Allow multiple selections |
239 | | * @param[in] m Mailbox |
240 | | * @param[out] files List of files selected |
241 | | * @param[out] numfiles Number of files selected |
242 | | * @param[in] flags Flags, see #SelectFileFlags |
243 | | * @retval 0 Success |
244 | | * @retval -1 Error |
245 | | * |
246 | | * This function uses the message window. |
247 | | * |
248 | | * Allow the user to enter a filename. |
249 | | * If they hit '?' then the browser will be started. See: dlg_browser() |
250 | | */ |
251 | | int mw_enter_fname(const char *prompt, struct Buffer *fname, bool mailbox, |
252 | | struct Mailbox *m, bool multiple, char ***files, |
253 | | int *numfiles, SelectFileFlags flags) |
254 | 0 | { |
255 | 0 | struct MuttWindow *win = msgwin_new(true); |
256 | 0 | if (!win) |
257 | 0 | return -1; |
258 | | |
259 | 0 | int rc = -1; |
260 | |
|
261 | 0 | struct Buffer *text = buf_pool_get(); |
262 | 0 | const struct AttrColor *ac_normal = simple_color_get(MT_COLOR_NORMAL); |
263 | 0 | const struct AttrColor *ac_prompt = simple_color_get(MT_COLOR_PROMPT); |
264 | |
|
265 | 0 | msgwin_add_text(win, prompt, ac_prompt); |
266 | 0 | msgwin_add_text(win, _(" ('?' for list): "), ac_prompt); |
267 | 0 | if (!buf_is_empty(fname)) |
268 | 0 | msgwin_add_text(win, buf_string(fname), ac_normal); |
269 | |
|
270 | 0 | msgcont_push_window(win); |
271 | 0 | struct MuttWindow *old_focus = window_set_focus(win); |
272 | |
|
273 | 0 | struct KeyEvent event = { 0, OP_NULL }; |
274 | 0 | do |
275 | 0 | { |
276 | 0 | window_redraw(NULL); |
277 | 0 | event = mutt_getch(GETCH_NO_FLAGS); |
278 | 0 | } while ((event.op == OP_TIMEOUT) || (event.op == OP_REPAINT)); |
279 | |
|
280 | 0 | mutt_refresh(); |
281 | 0 | win = msgcont_pop_window(); |
282 | 0 | window_set_focus(old_focus); |
283 | 0 | mutt_window_free(&win); |
284 | |
|
285 | 0 | if (event.ch < 0) |
286 | 0 | goto done; |
287 | | |
288 | 0 | if (event.ch == '?') |
289 | 0 | { |
290 | 0 | buf_reset(fname); |
291 | |
|
292 | 0 | if (flags == MUTT_SEL_NO_FLAGS) |
293 | 0 | flags = MUTT_SEL_FOLDER; |
294 | 0 | if (multiple) |
295 | 0 | flags |= MUTT_SEL_MULTI; |
296 | 0 | if (mailbox) |
297 | 0 | flags |= MUTT_SEL_MAILBOX; |
298 | 0 | dlg_browser(fname, flags, m, files, numfiles); |
299 | 0 | } |
300 | 0 | else |
301 | 0 | { |
302 | 0 | char *pc = NULL; |
303 | 0 | mutt_str_asprintf(&pc, "%s: ", prompt); |
304 | 0 | if (event.op == OP_NULL) |
305 | 0 | mutt_unget_ch(event.ch); |
306 | 0 | else |
307 | 0 | mutt_unget_op(event.op); |
308 | |
|
309 | 0 | buf_alloc(fname, 1024); |
310 | 0 | struct FileCompletionData cdata = { multiple, m, files, numfiles }; |
311 | 0 | enum HistoryClass hclass = mailbox ? HC_MBOX : HC_FILE; |
312 | 0 | if (mw_get_field(pc, fname, MUTT_COMP_CLEAR, hclass, &CompleteMailboxOps, &cdata) != 0) |
313 | 0 | { |
314 | 0 | buf_reset(fname); |
315 | 0 | } |
316 | 0 | FREE(&pc); |
317 | 0 | } |
318 | |
|
319 | 0 | rc = 0; |
320 | |
|
321 | 0 | done: |
322 | 0 | buf_pool_release(&text); |
323 | 0 | return rc; |
324 | 0 | } |
325 | | |
326 | | /** |
327 | | * mutt_addwch - Addwch would be provided by an up-to-date curses library |
328 | | * @param win Window |
329 | | * @param wc Wide char to display |
330 | | * @retval 0 Success |
331 | | * @retval -1 Error |
332 | | */ |
333 | | int mutt_addwch(struct MuttWindow *win, wchar_t wc) |
334 | 0 | { |
335 | 0 | char buf[MB_LEN_MAX * 2]; |
336 | 0 | mbstate_t mbstate = { 0 }; |
337 | 0 | size_t n1, n2; |
338 | |
|
339 | 0 | if (((n1 = wcrtomb(buf, wc, &mbstate)) == ICONV_ILLEGAL_SEQ) || |
340 | 0 | ((n2 = wcrtomb(buf + n1, 0, &mbstate)) == ICONV_ILLEGAL_SEQ)) |
341 | 0 | { |
342 | 0 | return -1; /* ERR */ |
343 | 0 | } |
344 | 0 | else |
345 | 0 | { |
346 | 0 | return mutt_window_addstr(win, buf); |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | /** |
351 | | * mutt_simple_format - Format a string, like snprintf() |
352 | | * @param[out] buf Buffer in which to save string |
353 | | * @param[in] buflen Buffer length |
354 | | * @param[in] min_width Minimum width |
355 | | * @param[in] max_width Maximum width |
356 | | * @param[in] justify Justification, e.g. #JUSTIFY_RIGHT |
357 | | * @param[in] pad_char Padding character |
358 | | * @param[in] s String to format |
359 | | * @param[in] n Number of bytes of string to format |
360 | | * @param[in] arboreal If true, string contains graphical tree characters |
361 | | * |
362 | | * This formats a string, a bit like snprintf(buf, buflen, "%-*.*s", |
363 | | * min_width, max_width, s), except that the widths refer to the number of |
364 | | * character cells when printed. |
365 | | */ |
366 | | void mutt_simple_format(char *buf, size_t buflen, int min_width, int max_width, |
367 | | enum FormatJustify justify, char pad_char, |
368 | | const char *s, size_t n, bool arboreal) |
369 | 0 | { |
370 | 0 | wchar_t wc = 0; |
371 | 0 | int w; |
372 | 0 | size_t k; |
373 | 0 | char scratch[MB_LEN_MAX] = { 0 }; |
374 | 0 | mbstate_t mbstate1 = { 0 }; |
375 | 0 | mbstate_t mbstate2 = { 0 }; |
376 | 0 | bool escaped = false; |
377 | |
|
378 | 0 | buflen--; |
379 | 0 | char *p = buf; |
380 | 0 | for (; n && (k = mbrtowc(&wc, s, n, &mbstate1)); s += k, n -= k) |
381 | 0 | { |
382 | 0 | if ((k == ICONV_ILLEGAL_SEQ) || (k == ICONV_BUF_TOO_SMALL)) |
383 | 0 | { |
384 | 0 | if ((k == ICONV_ILLEGAL_SEQ) && (errno == EILSEQ)) |
385 | 0 | memset(&mbstate1, 0, sizeof(mbstate1)); |
386 | |
|
387 | 0 | k = (k == ICONV_ILLEGAL_SEQ) ? 1 : n; |
388 | 0 | wc = ReplacementChar; |
389 | 0 | } |
390 | 0 | if (escaped) |
391 | 0 | { |
392 | 0 | escaped = false; |
393 | 0 | w = 0; |
394 | 0 | } |
395 | 0 | else if (arboreal && (wc == MUTT_SPECIAL_INDEX)) |
396 | 0 | { |
397 | 0 | escaped = true; |
398 | 0 | w = 0; |
399 | 0 | } |
400 | 0 | else if (arboreal && (wc < MUTT_TREE_MAX)) |
401 | 0 | { |
402 | 0 | w = 1; /* hack */ |
403 | 0 | } |
404 | 0 | else |
405 | 0 | { |
406 | 0 | #ifdef HAVE_ISWBLANK |
407 | 0 | if (iswblank(wc)) |
408 | 0 | wc = ' '; |
409 | 0 | else |
410 | 0 | #endif |
411 | 0 | if (!IsWPrint(wc)) |
412 | 0 | wc = '?'; |
413 | 0 | w = wcwidth(wc); |
414 | 0 | } |
415 | 0 | if (w >= 0) |
416 | 0 | { |
417 | 0 | if (w > max_width) |
418 | 0 | continue; |
419 | 0 | size_t k2 = wcrtomb(scratch, wc, &mbstate2); |
420 | 0 | if ((k2 == ICONV_ILLEGAL_SEQ) || (k2 > buflen)) |
421 | 0 | continue; |
422 | | |
423 | 0 | min_width -= w; |
424 | 0 | max_width -= w; |
425 | 0 | strncpy(p, scratch, k2); |
426 | 0 | p += k2; |
427 | 0 | buflen -= k2; |
428 | 0 | } |
429 | 0 | } |
430 | 0 | w = ((int) buflen < min_width) ? buflen : min_width; |
431 | 0 | if (w <= 0) |
432 | 0 | { |
433 | 0 | *p = '\0'; |
434 | 0 | } |
435 | 0 | else if (justify == JUSTIFY_RIGHT) /* right justify */ |
436 | 0 | { |
437 | 0 | p[w] = '\0'; |
438 | 0 | while (--p >= buf) |
439 | 0 | p[w] = *p; |
440 | 0 | while (--w >= 0) |
441 | 0 | buf[w] = pad_char; |
442 | 0 | } |
443 | 0 | else if (justify == JUSTIFY_CENTER) /* center */ |
444 | 0 | { |
445 | 0 | char *savedp = p; |
446 | 0 | int half = (w + 1) / 2; /* half of cushion space */ |
447 | |
|
448 | 0 | p[w] = '\0'; |
449 | | |
450 | | /* move str to center of buffer */ |
451 | 0 | while (--p >= buf) |
452 | 0 | p[half] = *p; |
453 | | |
454 | | /* fill rhs */ |
455 | 0 | p = savedp + half; |
456 | 0 | while (--w >= half) |
457 | 0 | *p++ = pad_char; |
458 | | |
459 | | /* fill lhs */ |
460 | 0 | while (half--) |
461 | 0 | buf[half] = pad_char; |
462 | 0 | } |
463 | 0 | else /* left justify */ |
464 | 0 | { |
465 | 0 | while (--w >= 0) |
466 | 0 | *p++ = pad_char; |
467 | 0 | *p = '\0'; |
468 | 0 | } |
469 | 0 | } |
470 | | |
471 | | /** |
472 | | * mutt_format_s_x - Format a string like snprintf() |
473 | | * @param[out] buf Buffer in which to save string |
474 | | * @param[in] buflen Buffer length |
475 | | * @param[in] prec Field precision, e.g. "-3.4" |
476 | | * @param[in] s String to format |
477 | | * @param[in] arboreal If true, string contains graphical tree characters |
478 | | * |
479 | | * This formats a string rather like: |
480 | | * - snprintf(fmt, sizeof(fmt), "%%%ss", prec); |
481 | | * - snprintf(buf, buflen, fmt, s); |
482 | | * except that the numbers in the conversion specification refer to |
483 | | * the number of character cells when printed. |
484 | | */ |
485 | | void mutt_format_s_x(char *buf, size_t buflen, const char *prec, const char *s, bool arboreal) |
486 | 0 | { |
487 | 0 | enum FormatJustify justify = JUSTIFY_RIGHT; |
488 | 0 | char *p = NULL; |
489 | 0 | int min_width; |
490 | 0 | int max_width = INT_MAX; |
491 | |
|
492 | 0 | if (*prec == '-') |
493 | 0 | { |
494 | 0 | prec++; |
495 | 0 | justify = JUSTIFY_LEFT; |
496 | 0 | } |
497 | 0 | else if (*prec == '=') |
498 | 0 | { |
499 | 0 | prec++; |
500 | 0 | justify = JUSTIFY_CENTER; |
501 | 0 | } |
502 | 0 | min_width = strtol(prec, &p, 10); |
503 | 0 | if (*p == '.') |
504 | 0 | { |
505 | 0 | prec = p + 1; |
506 | 0 | max_width = strtol(prec, &p, 10); |
507 | 0 | if (p <= prec) |
508 | 0 | max_width = INT_MAX; |
509 | 0 | } |
510 | |
|
511 | 0 | mutt_simple_format(buf, buflen, min_width, max_width, justify, ' ', s, |
512 | 0 | mutt_str_len(s), arboreal); |
513 | 0 | } |
514 | | |
515 | | /** |
516 | | * mutt_format_s - Format a simple string |
517 | | * @param[out] buf Buffer in which to save string |
518 | | * @param[in] buflen Buffer length |
519 | | * @param[in] prec Field precision, e.g. "-3.4" |
520 | | * @param[in] s String to format |
521 | | */ |
522 | | void mutt_format_s(char *buf, size_t buflen, const char *prec, const char *s) |
523 | 0 | { |
524 | 0 | mutt_format_s_x(buf, buflen, prec, s, false); |
525 | 0 | } |
526 | | |
527 | | /** |
528 | | * mutt_format_s_tree - Format a simple string with tree characters |
529 | | * @param[out] buf Buffer in which to save string |
530 | | * @param[in] buflen Buffer length |
531 | | * @param[in] prec Field precision, e.g. "-3.4" |
532 | | * @param[in] s String to format |
533 | | */ |
534 | | void mutt_format_s_tree(char *buf, size_t buflen, const char *prec, const char *s) |
535 | 0 | { |
536 | 0 | mutt_format_s_x(buf, buflen, prec, s, true); |
537 | 0 | } |
538 | | |
539 | | /** |
540 | | * mutt_paddstr - Display a string on screen, padded if necessary |
541 | | * @param win Window |
542 | | * @param n Final width of field |
543 | | * @param s String to display |
544 | | */ |
545 | | void mutt_paddstr(struct MuttWindow *win, int n, const char *s) |
546 | 0 | { |
547 | 0 | wchar_t wc = 0; |
548 | 0 | size_t k; |
549 | 0 | size_t len = mutt_str_len(s); |
550 | 0 | mbstate_t mbstate = { 0 }; |
551 | |
|
552 | 0 | for (; len && (k = mbrtowc(&wc, s, len, &mbstate)); s += k, len -= k) |
553 | 0 | { |
554 | 0 | if ((k == ICONV_ILLEGAL_SEQ) || (k == ICONV_BUF_TOO_SMALL)) |
555 | 0 | { |
556 | 0 | if (k == ICONV_ILLEGAL_SEQ) |
557 | 0 | memset(&mbstate, 0, sizeof(mbstate)); |
558 | 0 | k = (k == ICONV_ILLEGAL_SEQ) ? 1 : len; |
559 | 0 | wc = ReplacementChar; |
560 | 0 | } |
561 | 0 | if (!IsWPrint(wc)) |
562 | 0 | wc = '?'; |
563 | 0 | const int w = wcwidth(wc); |
564 | 0 | if (w >= 0) |
565 | 0 | { |
566 | 0 | if (w > n) |
567 | 0 | break; |
568 | 0 | mutt_window_addnstr(win, (char *) s, k); |
569 | 0 | n -= w; |
570 | 0 | } |
571 | 0 | } |
572 | 0 | while (n-- > 0) |
573 | 0 | mutt_window_addch(win, ' '); |
574 | 0 | } |
575 | | |
576 | | /** |
577 | | * mutt_wstr_trunc - Work out how to truncate a widechar string |
578 | | * @param[in] src String to measure |
579 | | * @param[in] maxlen Maximum length of string in bytes |
580 | | * @param[in] maxwid Maximum width in screen columns |
581 | | * @param[out] width Save the truncated screen column width |
582 | | * @retval num Bytes to use |
583 | | * |
584 | | * See how many bytes to copy from string so it's at most maxlen bytes long and |
585 | | * maxwid columns wide |
586 | | */ |
587 | | size_t mutt_wstr_trunc(const char *src, size_t maxlen, size_t maxwid, size_t *width) |
588 | 0 | { |
589 | 0 | wchar_t wc = 0; |
590 | 0 | size_t n, w = 0, l = 0, cl; |
591 | 0 | int cw; |
592 | 0 | mbstate_t mbstate = { 0 }; |
593 | |
|
594 | 0 | if (!src) |
595 | 0 | goto out; |
596 | | |
597 | 0 | n = mutt_str_len(src); |
598 | |
|
599 | 0 | for (w = 0; n && (cl = mbrtowc(&wc, src, n, &mbstate)); src += cl, n -= cl) |
600 | 0 | { |
601 | 0 | if (cl == ICONV_ILLEGAL_SEQ) |
602 | 0 | { |
603 | 0 | memset(&mbstate, 0, sizeof(mbstate)); |
604 | 0 | cl = 1; |
605 | 0 | wc = ReplacementChar; |
606 | 0 | } |
607 | 0 | else if (cl == ICONV_BUF_TOO_SMALL) |
608 | 0 | { |
609 | 0 | cl = n; |
610 | 0 | wc = ReplacementChar; |
611 | 0 | } |
612 | |
|
613 | 0 | cw = wcwidth(wc); |
614 | | /* hack because MUTT_TREE symbols aren't turned into characters |
615 | | * until rendered by print_enriched_string() */ |
616 | 0 | if ((cw < 0) && (src[0] == MUTT_SPECIAL_INDEX)) |
617 | 0 | { |
618 | 0 | cl = 2; /* skip the index coloring sequence */ |
619 | 0 | cw = 0; |
620 | 0 | } |
621 | 0 | else if ((cw < 0) && (cl == 1) && (src[0] != '\0') && (src[0] < MUTT_TREE_MAX)) |
622 | 0 | { |
623 | 0 | cw = 1; |
624 | 0 | } |
625 | 0 | else if (cw < 0) |
626 | 0 | { |
627 | 0 | cw = 0; /* unprintable wchar */ |
628 | 0 | } |
629 | 0 | if (wc == '\n') |
630 | 0 | break; |
631 | 0 | if (((cl + l) > maxlen) || ((cw + w) > maxwid)) |
632 | 0 | break; |
633 | 0 | l += cl; |
634 | 0 | w += cw; |
635 | 0 | } |
636 | 0 | out: |
637 | 0 | if (width) |
638 | 0 | *width = w; |
639 | 0 | return l; |
640 | 0 | } |
641 | | |
642 | | /** |
643 | | * mutt_strwidth - Measure a string's width in screen cells |
644 | | * @param s String to be measured |
645 | | * @retval num Screen cells string would use |
646 | | */ |
647 | | size_t mutt_strwidth(const char *s) |
648 | 0 | { |
649 | 0 | if (!s) |
650 | 0 | return 0; |
651 | 0 | return mutt_strnwidth(s, mutt_str_len(s)); |
652 | 0 | } |
653 | | |
654 | | /** |
655 | | * mutt_strnwidth - Measure a string's width in screen cells |
656 | | * @param s String to be measured |
657 | | * @param n Length of string to be measured |
658 | | * @retval num Screen cells string would use |
659 | | */ |
660 | | size_t mutt_strnwidth(const char *s, size_t n) |
661 | 0 | { |
662 | 0 | if (!s) |
663 | 0 | return 0; |
664 | | |
665 | 0 | wchar_t wc = 0; |
666 | 0 | int w; |
667 | 0 | size_t k; |
668 | 0 | mbstate_t mbstate = { 0 }; |
669 | |
|
670 | 0 | for (w = 0; n && (k = mbrtowc(&wc, s, n, &mbstate)); s += k, n -= k) |
671 | 0 | { |
672 | 0 | if (*s == MUTT_SPECIAL_INDEX) |
673 | 0 | { |
674 | 0 | s += 2; /* skip the index coloring sequence */ |
675 | 0 | k = 0; |
676 | 0 | continue; |
677 | 0 | } |
678 | | |
679 | 0 | if ((k == ICONV_ILLEGAL_SEQ) || (k == ICONV_BUF_TOO_SMALL)) |
680 | 0 | { |
681 | 0 | if (k == ICONV_ILLEGAL_SEQ) |
682 | 0 | memset(&mbstate, 0, sizeof(mbstate)); |
683 | 0 | k = (k == ICONV_ILLEGAL_SEQ) ? 1 : n; |
684 | 0 | wc = ReplacementChar; |
685 | 0 | } |
686 | 0 | if (!IsWPrint(wc)) |
687 | 0 | wc = '?'; |
688 | 0 | w += wcwidth(wc); |
689 | 0 | } |
690 | 0 | return w; |
691 | 0 | } |
692 | | |
693 | | /** |
694 | | * mw_what_key - Display the value of a key - @ingroup gui_mw |
695 | | * |
696 | | * This function uses the message window. |
697 | | * |
698 | | * Displays the octal value back to the user. e.g. |
699 | | * `Char = h, Octal = 150, Decimal = 104` |
700 | | * |
701 | | * Press the $abort_key (default Ctrl-G) to exit. |
702 | | */ |
703 | | void mw_what_key(void) |
704 | 0 | { |
705 | 0 | struct MuttWindow *win = msgwin_new(true); |
706 | 0 | if (!win) |
707 | 0 | return; |
708 | | |
709 | 0 | char prompt[256] = { 0 }; |
710 | 0 | snprintf(prompt, sizeof(prompt), _("Enter keys (%s to abort): "), km_keyname(AbortKey)); |
711 | 0 | msgwin_set_text(win, prompt, MT_COLOR_PROMPT); |
712 | |
|
713 | 0 | msgcont_push_window(win); |
714 | 0 | struct MuttWindow *old_focus = window_set_focus(win); |
715 | 0 | window_redraw(win); |
716 | |
|
717 | 0 | char keys[256] = { 0 }; |
718 | 0 | const struct AttrColor *ac_normal = simple_color_get(MT_COLOR_NORMAL); |
719 | 0 | const struct AttrColor *ac_prompt = simple_color_get(MT_COLOR_PROMPT); |
720 | | |
721 | | // --------------------------------------------------------------------------- |
722 | | // Event Loop |
723 | 0 | timeout(1000); // 1 second |
724 | 0 | while (true) |
725 | 0 | { |
726 | 0 | int ch = getch(); |
727 | 0 | if (ch == AbortKey) |
728 | 0 | break; |
729 | | |
730 | 0 | if (ch == KEY_RESIZE) |
731 | 0 | { |
732 | 0 | timeout(0); |
733 | 0 | while ((ch = getch()) == KEY_RESIZE) |
734 | 0 | { |
735 | | // do nothing |
736 | 0 | } |
737 | 0 | } |
738 | |
|
739 | 0 | if (ch == ERR) |
740 | 0 | { |
741 | 0 | if (!isatty(0)) // terminal was lost |
742 | 0 | mutt_exit(1); |
743 | |
|
744 | 0 | if (SigWinch) |
745 | 0 | { |
746 | 0 | SigWinch = false; |
747 | 0 | notify_send(NeoMutt->notify_resize, NT_RESIZE, 0, NULL); |
748 | 0 | window_redraw(NULL); |
749 | 0 | } |
750 | 0 | else |
751 | 0 | { |
752 | 0 | notify_send(NeoMutt->notify_timeout, NT_TIMEOUT, 0, NULL); |
753 | 0 | } |
754 | |
|
755 | 0 | continue; |
756 | 0 | } |
757 | | |
758 | 0 | msgwin_clear_text(win); |
759 | 0 | snprintf(keys, sizeof(keys), _("Char = %s, Octal = %o, Decimal = %d\n"), |
760 | 0 | km_keyname(ch), ch, ch); |
761 | 0 | msgwin_add_text(win, keys, ac_normal); |
762 | 0 | msgwin_add_text(win, prompt, ac_prompt); |
763 | 0 | msgwin_add_text(win, NULL, NULL); |
764 | 0 | window_redraw(NULL); |
765 | 0 | } |
766 | | // --------------------------------------------------------------------------- |
767 | |
|
768 | 0 | win = msgcont_pop_window(); |
769 | 0 | window_set_focus(old_focus); |
770 | 0 | mutt_window_free(&win); |
771 | 0 | } |