Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Paint the Menu |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2021 Richard Russon <rich@flatcap.org> |
7 | | * |
8 | | * @copyright |
9 | | * This program is free software: you can redistribute it and/or modify it under |
10 | | * the terms of the GNU General Public License as published by the Free Software |
11 | | * Foundation, either version 2 of the License, or (at your option) any later |
12 | | * version. |
13 | | * |
14 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
15 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
16 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
17 | | * details. |
18 | | * |
19 | | * You should have received a copy of the GNU General Public License along with |
20 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
21 | | */ |
22 | | |
23 | | /** |
24 | | * @page menu_draw Paint the Menu |
25 | | * |
26 | | * Paint the Menu |
27 | | */ |
28 | | |
29 | | #include "config.h" |
30 | | #include <stdbool.h> |
31 | | #include <stdio.h> |
32 | | #include <string.h> |
33 | | #include <wchar.h> |
34 | | #include "mutt/lib.h" |
35 | | #include "config/lib.h" |
36 | | #include "email/lib.h" |
37 | | #include "gui/lib.h" |
38 | | #include "color/lib.h" |
39 | | #include "index/lib.h" |
40 | | #include "menu/lib.h" |
41 | | #include "pattern/lib.h" |
42 | | #include "mutt_thread.h" |
43 | | #include "mview.h" |
44 | | #include "opcodes.h" |
45 | | |
46 | | /** |
47 | | * get_color - Choose a colour for a line of the index |
48 | | * @param index Index number |
49 | | * @param s String of embedded colour codes |
50 | | * @retval num Colour pair in an integer |
51 | | * |
52 | | * Text is coloured by inserting special characters into the string, e.g. |
53 | | * #MT_COLOR_INDEX_AUTHOR |
54 | | */ |
55 | | static struct AttrColor *get_color(int index, unsigned char *s) |
56 | 0 | { |
57 | 0 | const int type = *s; |
58 | 0 | struct RegexColorList *rcl = regex_colors_get_list(type); |
59 | 0 | struct Mailbox *m_cur = get_current_mailbox(); |
60 | 0 | struct Email *e = mutt_get_virt_email(m_cur, index); |
61 | 0 | if ((rcl == NULL) || (e == NULL)) |
62 | 0 | { |
63 | 0 | return simple_color_get(type); |
64 | 0 | } |
65 | | |
66 | 0 | struct RegexColor *np = NULL; |
67 | |
|
68 | 0 | if (type == MT_COLOR_INDEX_TAG) |
69 | 0 | { |
70 | 0 | struct AttrColor *ac_merge = NULL; |
71 | 0 | STAILQ_FOREACH(np, rcl, entries) |
72 | 0 | { |
73 | 0 | if (mutt_strn_equal((const char *) (s + 1), np->pattern, strlen(np->pattern))) |
74 | 0 | { |
75 | 0 | ac_merge = merged_color_overlay(ac_merge, &np->attr_color); |
76 | 0 | continue; |
77 | 0 | } |
78 | 0 | const char *transform = mutt_hash_find(TagTransforms, np->pattern); |
79 | 0 | if (transform && mutt_strn_equal((const char *) (s + 1), transform, strlen(transform))) |
80 | 0 | { |
81 | 0 | ac_merge = merged_color_overlay(ac_merge, &np->attr_color); |
82 | 0 | } |
83 | 0 | } |
84 | 0 | return ac_merge; |
85 | 0 | } |
86 | | |
87 | 0 | struct AttrColor *ac_merge = NULL; |
88 | 0 | STAILQ_FOREACH(np, rcl, entries) |
89 | 0 | { |
90 | 0 | if (mutt_pattern_exec(SLIST_FIRST(np->color_pattern), |
91 | 0 | MUTT_MATCH_FULL_ADDRESS, m_cur, e, NULL)) |
92 | 0 | { |
93 | 0 | ac_merge = merged_color_overlay(ac_merge, &np->attr_color); |
94 | 0 | } |
95 | 0 | } |
96 | |
|
97 | 0 | return ac_merge; |
98 | 0 | } |
99 | | |
100 | | /** |
101 | | * print_enriched_string - Display a string with embedded colours and graphics |
102 | | * @param win Window |
103 | | * @param index Index number |
104 | | * @param ac_def Default colour for the line |
105 | | * @param ac_ind Indicator colour for the line |
106 | | * @param s String of embedded colour codes |
107 | | * @param sub Config items |
108 | | */ |
109 | | static void print_enriched_string(struct MuttWindow *win, int index, |
110 | | struct AttrColor *ac_def, struct AttrColor *ac_ind, |
111 | | unsigned char *s, struct ConfigSubset *sub) |
112 | 0 | { |
113 | 0 | wchar_t wc = 0; |
114 | 0 | size_t k; |
115 | 0 | size_t n = mutt_str_len((char *) s); |
116 | 0 | mbstate_t mbstate = { 0 }; |
117 | |
|
118 | 0 | const bool c_ascii_chars = cs_subset_bool(sub, "ascii_chars"); |
119 | 0 | while (*s) |
120 | 0 | { |
121 | 0 | if (*s < MUTT_TREE_MAX) |
122 | 0 | { |
123 | 0 | struct AttrColor *ac_merge = merged_color_overlay(ac_def, simple_color_get(MT_COLOR_TREE)); |
124 | 0 | ac_merge = merged_color_overlay(ac_merge, ac_ind); |
125 | | |
126 | | /* Combining tree fg color and another bg color requires having |
127 | | * use_default_colors, because the other bg color may be undefined. */ |
128 | 0 | mutt_curses_set_color(ac_merge); |
129 | |
|
130 | 0 | while (*s && (*s < MUTT_TREE_MAX)) |
131 | 0 | { |
132 | 0 | switch (*s) |
133 | 0 | { |
134 | 0 | case MUTT_TREE_LLCORNER: |
135 | 0 | if (c_ascii_chars) |
136 | 0 | mutt_window_addch(win, '`'); |
137 | 0 | #ifdef WACS_LLCORNER |
138 | 0 | else |
139 | 0 | add_wch(WACS_LLCORNER); |
140 | | #else |
141 | | else if (CharsetIsUtf8) |
142 | | mutt_window_addstr(win, "\342\224\224"); /* WACS_LLCORNER */ |
143 | | else |
144 | | mutt_window_addch(win, ACS_LLCORNER); |
145 | | #endif |
146 | 0 | break; |
147 | 0 | case MUTT_TREE_ULCORNER: |
148 | 0 | if (c_ascii_chars) |
149 | 0 | mutt_window_addch(win, ','); |
150 | 0 | #ifdef WACS_ULCORNER |
151 | 0 | else |
152 | 0 | add_wch(WACS_ULCORNER); |
153 | | #else |
154 | | else if (CharsetIsUtf8) |
155 | | mutt_window_addstr(win, "\342\224\214"); /* WACS_ULCORNER */ |
156 | | else |
157 | | mutt_window_addch(win, ACS_ULCORNER); |
158 | | #endif |
159 | 0 | break; |
160 | 0 | case MUTT_TREE_LTEE: |
161 | 0 | if (c_ascii_chars) |
162 | 0 | mutt_window_addch(win, '|'); |
163 | 0 | #ifdef WACS_LTEE |
164 | 0 | else |
165 | 0 | add_wch(WACS_LTEE); |
166 | | #else |
167 | | else if (CharsetIsUtf8) |
168 | | mutt_window_addstr(win, "\342\224\234"); /* WACS_LTEE */ |
169 | | else |
170 | | mutt_window_addch(win, ACS_LTEE); |
171 | | #endif |
172 | 0 | break; |
173 | 0 | case MUTT_TREE_HLINE: |
174 | 0 | if (c_ascii_chars) |
175 | 0 | mutt_window_addch(win, '-'); |
176 | 0 | #ifdef WACS_HLINE |
177 | 0 | else |
178 | 0 | add_wch(WACS_HLINE); |
179 | | #else |
180 | | else if (CharsetIsUtf8) |
181 | | mutt_window_addstr(win, "\342\224\200"); /* WACS_HLINE */ |
182 | | else |
183 | | mutt_window_addch(win, ACS_HLINE); |
184 | | #endif |
185 | 0 | break; |
186 | 0 | case MUTT_TREE_VLINE: |
187 | 0 | if (c_ascii_chars) |
188 | 0 | mutt_window_addch(win, '|'); |
189 | 0 | #ifdef WACS_VLINE |
190 | 0 | else |
191 | 0 | add_wch(WACS_VLINE); |
192 | | #else |
193 | | else if (CharsetIsUtf8) |
194 | | mutt_window_addstr(win, "\342\224\202"); /* WACS_VLINE */ |
195 | | else |
196 | | mutt_window_addch(win, ACS_VLINE); |
197 | | #endif |
198 | 0 | break; |
199 | 0 | case MUTT_TREE_TTEE: |
200 | 0 | if (c_ascii_chars) |
201 | 0 | mutt_window_addch(win, '-'); |
202 | 0 | #ifdef WACS_TTEE |
203 | 0 | else |
204 | 0 | add_wch(WACS_TTEE); |
205 | | #else |
206 | | else if (CharsetIsUtf8) |
207 | | mutt_window_addstr(win, "\342\224\254"); /* WACS_TTEE */ |
208 | | else |
209 | | mutt_window_addch(win, ACS_TTEE); |
210 | | #endif |
211 | 0 | break; |
212 | 0 | case MUTT_TREE_BTEE: |
213 | 0 | if (c_ascii_chars) |
214 | 0 | mutt_window_addch(win, '-'); |
215 | 0 | #ifdef WACS_BTEE |
216 | 0 | else |
217 | 0 | add_wch(WACS_BTEE); |
218 | | #else |
219 | | else if (CharsetIsUtf8) |
220 | | mutt_window_addstr(win, "\342\224\264"); /* WACS_BTEE */ |
221 | | else |
222 | | mutt_window_addch(win, ACS_BTEE); |
223 | | #endif |
224 | 0 | break; |
225 | 0 | case MUTT_TREE_SPACE: |
226 | 0 | mutt_window_addch(win, ' '); |
227 | 0 | break; |
228 | 0 | case MUTT_TREE_RARROW: |
229 | 0 | mutt_window_addch(win, '>'); |
230 | 0 | break; |
231 | 0 | case MUTT_TREE_STAR: |
232 | 0 | mutt_window_addch(win, '*'); /* fake thread indicator */ |
233 | 0 | break; |
234 | 0 | case MUTT_TREE_HIDDEN: |
235 | 0 | mutt_window_addch(win, '&'); |
236 | 0 | break; |
237 | 0 | case MUTT_TREE_EQUALS: |
238 | 0 | mutt_window_addch(win, '='); |
239 | 0 | break; |
240 | 0 | case MUTT_TREE_MISSING: |
241 | 0 | mutt_window_addch(win, '?'); |
242 | 0 | break; |
243 | 0 | } |
244 | 0 | s++; |
245 | 0 | n--; |
246 | 0 | } |
247 | 0 | ac_merge = merged_color_overlay(ac_def, ac_ind); |
248 | 0 | mutt_curses_set_color(ac_merge); |
249 | 0 | } |
250 | 0 | else if (*s == MUTT_SPECIAL_INDEX) |
251 | 0 | { |
252 | 0 | s++; |
253 | 0 | if (*s == MT_COLOR_INDEX) |
254 | 0 | { |
255 | 0 | struct AttrColor *ac_merge = merged_color_overlay(ac_def, ac_ind); |
256 | 0 | mutt_curses_set_color(ac_merge); |
257 | 0 | } |
258 | 0 | else |
259 | 0 | { |
260 | 0 | struct AttrColor *color = get_color(index, s); |
261 | 0 | struct AttrColor *ac_merge = merged_color_overlay(ac_def, color); |
262 | 0 | ac_merge = merged_color_overlay(ac_merge, ac_ind); |
263 | |
|
264 | 0 | mutt_curses_set_color(ac_merge); |
265 | 0 | } |
266 | 0 | s++; |
267 | 0 | n -= 2; |
268 | 0 | } |
269 | 0 | else if ((k = mbrtowc(&wc, (char *) s, n, &mbstate)) > 0) |
270 | 0 | { |
271 | 0 | mutt_window_addnstr(win, (char *) s, k); |
272 | 0 | s += k; |
273 | 0 | n -= k; |
274 | 0 | } |
275 | 0 | else |
276 | 0 | { |
277 | 0 | break; |
278 | 0 | } |
279 | 0 | } |
280 | 0 | } |
281 | | |
282 | | /** |
283 | | * menu_pad_string - Pad a string with spaces for display in the Menu |
284 | | * @param menu Current Menu |
285 | | * @param buf Buffer containing the string |
286 | | * @param buflen Length of the buffer |
287 | | * |
288 | | * @note The string is padded in-place. |
289 | | */ |
290 | | static void menu_pad_string(struct Menu *menu, char *buf, size_t buflen) |
291 | 0 | { |
292 | 0 | char *scratch = mutt_str_dup(buf); |
293 | 0 | const bool c_arrow_cursor = cs_subset_bool(menu->sub, "arrow_cursor"); |
294 | 0 | const char *const c_arrow_string = cs_subset_string(menu->sub, "arrow_string"); |
295 | 0 | int shift = c_arrow_cursor ? mutt_strwidth(c_arrow_string) + 1 : 0; |
296 | 0 | int cols = menu->win->state.cols - shift; |
297 | |
|
298 | 0 | mutt_simple_format(buf, buflen, cols, cols, JUSTIFY_LEFT, ' ', scratch, |
299 | 0 | mutt_str_len(scratch), true); |
300 | 0 | buf[buflen - 1] = '\0'; |
301 | 0 | FREE(&scratch); |
302 | 0 | } |
303 | | |
304 | | /** |
305 | | * menu_redraw_full - Force the redraw of the Menu |
306 | | * @param menu Current Menu |
307 | | */ |
308 | | void menu_redraw_full(struct Menu *menu) |
309 | 0 | { |
310 | 0 | mutt_curses_set_color_by_id(MT_COLOR_NORMAL); |
311 | 0 | mutt_window_clear(menu->win); |
312 | |
|
313 | 0 | menu->page_len = menu->win->state.rows; |
314 | |
|
315 | 0 | menu->redraw = MENU_REDRAW_INDEX; |
316 | 0 | } |
317 | | |
318 | | /** |
319 | | * menu_redraw_index - Force the redraw of the index |
320 | | * @param menu Current Menu |
321 | | */ |
322 | | void menu_redraw_index(struct Menu *menu) |
323 | 0 | { |
324 | 0 | char buf[1024] = { 0 }; |
325 | 0 | struct AttrColor *ac = NULL; |
326 | |
|
327 | 0 | const bool c_arrow_cursor = cs_subset_bool(menu->sub, "arrow_cursor"); |
328 | 0 | const char *const c_arrow_string = cs_subset_string(menu->sub, "arrow_string"); |
329 | 0 | struct AttrColor *ac_ind = simple_color_get(MT_COLOR_INDICATOR); |
330 | 0 | for (int i = menu->top; i < (menu->top + menu->page_len); i++) |
331 | 0 | { |
332 | 0 | if (i < menu->max) |
333 | 0 | { |
334 | 0 | ac = menu->color(menu, i); |
335 | |
|
336 | 0 | menu->make_entry(menu, buf, sizeof(buf), i); |
337 | 0 | menu_pad_string(menu, buf, sizeof(buf)); |
338 | |
|
339 | 0 | mutt_curses_set_color(ac); |
340 | 0 | mutt_window_move(menu->win, 0, i - menu->top); |
341 | |
|
342 | 0 | if (i == menu->current) |
343 | 0 | mutt_curses_set_color(ac_ind); |
344 | |
|
345 | 0 | if (c_arrow_cursor) |
346 | 0 | { |
347 | 0 | if (i == menu->current) |
348 | 0 | { |
349 | 0 | mutt_window_addstr(menu->win, c_arrow_string); |
350 | 0 | mutt_curses_set_color(ac); |
351 | 0 | mutt_window_addch(menu->win, ' '); |
352 | 0 | } |
353 | 0 | else |
354 | 0 | { |
355 | | /* Print space chars to match the screen width of `$arrow_string` */ |
356 | 0 | mutt_window_printf(menu->win, "%*s", mutt_strwidth(c_arrow_string) + 1, ""); |
357 | 0 | } |
358 | 0 | } |
359 | |
|
360 | 0 | if ((i == menu->current) && !c_arrow_cursor) |
361 | 0 | print_enriched_string(menu->win, i, ac, ac_ind, (unsigned char *) buf, menu->sub); |
362 | 0 | else |
363 | 0 | print_enriched_string(menu->win, i, ac, NULL, (unsigned char *) buf, menu->sub); |
364 | 0 | } |
365 | 0 | else |
366 | 0 | { |
367 | 0 | mutt_curses_set_color_by_id(MT_COLOR_NORMAL); |
368 | 0 | mutt_window_clearline(menu->win, i - menu->top); |
369 | 0 | } |
370 | 0 | } |
371 | 0 | mutt_curses_set_color_by_id(MT_COLOR_NORMAL); |
372 | 0 | menu->redraw = MENU_REDRAW_NO_FLAGS; |
373 | 0 | } |
374 | | |
375 | | /** |
376 | | * menu_redraw_motion - Force the redraw of the list part of the menu |
377 | | * @param menu Current Menu |
378 | | */ |
379 | | void menu_redraw_motion(struct Menu *menu) |
380 | 0 | { |
381 | 0 | char buf[1024] = { 0 }; |
382 | | |
383 | | /* Note: menu->color() for the index can end up retrieving a message |
384 | | * over imap (if matching against ~h for instance). This can |
385 | | * generate status messages. So we want to call it *before* we |
386 | | * position the cursor for drawing. */ |
387 | 0 | struct AttrColor *old_color = menu->color(menu, menu->old_current); |
388 | 0 | mutt_window_move(menu->win, 0, menu->old_current - menu->top); |
389 | 0 | mutt_curses_set_color(old_color); |
390 | |
|
391 | 0 | const bool c_arrow_cursor = cs_subset_bool(menu->sub, "arrow_cursor"); |
392 | 0 | const char *const c_arrow_string = cs_subset_string(menu->sub, "arrow_string"); |
393 | 0 | struct AttrColor *ac_ind = simple_color_get(MT_COLOR_INDICATOR); |
394 | 0 | if (c_arrow_cursor) |
395 | 0 | { |
396 | | /* clear the arrow */ |
397 | | /* Print space chars to match the screen width of `$arrow_string` */ |
398 | 0 | mutt_window_printf(menu->win, "%*s", mutt_strwidth(c_arrow_string) + 1, ""); |
399 | 0 | mutt_curses_set_color_by_id(MT_COLOR_NORMAL); |
400 | |
|
401 | 0 | menu->make_entry(menu, buf, sizeof(buf), menu->old_current); |
402 | 0 | menu_pad_string(menu, buf, sizeof(buf)); |
403 | 0 | mutt_window_move(menu->win, mutt_strwidth(c_arrow_string) + 1, |
404 | 0 | menu->old_current - menu->top); |
405 | 0 | print_enriched_string(menu->win, menu->old_current, old_color, NULL, |
406 | 0 | (unsigned char *) buf, menu->sub); |
407 | | |
408 | | /* now draw it in the new location */ |
409 | 0 | mutt_curses_set_color(ac_ind); |
410 | 0 | mutt_window_mvaddstr(menu->win, 0, menu->current - menu->top, c_arrow_string); |
411 | 0 | } |
412 | 0 | else |
413 | 0 | { |
414 | 0 | mutt_curses_set_color_by_id(MT_COLOR_NORMAL); |
415 | | /* erase the current indicator */ |
416 | 0 | menu->make_entry(menu, buf, sizeof(buf), menu->old_current); |
417 | 0 | menu_pad_string(menu, buf, sizeof(buf)); |
418 | 0 | print_enriched_string(menu->win, menu->old_current, old_color, NULL, |
419 | 0 | (unsigned char *) buf, menu->sub); |
420 | | |
421 | | /* now draw the new one to reflect the change */ |
422 | 0 | struct AttrColor *cur_color = menu->color(menu, menu->current); |
423 | 0 | cur_color = merged_color_overlay(cur_color, ac_ind); |
424 | 0 | menu->make_entry(menu, buf, sizeof(buf), menu->current); |
425 | 0 | menu_pad_string(menu, buf, sizeof(buf)); |
426 | 0 | mutt_window_move(menu->win, 0, menu->current - menu->top); |
427 | 0 | mutt_curses_set_color(cur_color); |
428 | 0 | print_enriched_string(menu->win, menu->current, cur_color, ac_ind, |
429 | 0 | (unsigned char *) buf, menu->sub); |
430 | 0 | } |
431 | 0 | mutt_curses_set_color_by_id(MT_COLOR_NORMAL); |
432 | 0 | } |
433 | | |
434 | | /** |
435 | | * menu_redraw_current - Redraw the current menu |
436 | | * @param menu Current Menu |
437 | | */ |
438 | | void menu_redraw_current(struct Menu *menu) |
439 | 0 | { |
440 | 0 | char buf[1024] = { 0 }; |
441 | 0 | struct AttrColor *ac = menu->color(menu, menu->current); |
442 | |
|
443 | 0 | mutt_window_move(menu->win, 0, menu->current - menu->top); |
444 | 0 | menu->make_entry(menu, buf, sizeof(buf), menu->current); |
445 | 0 | menu_pad_string(menu, buf, sizeof(buf)); |
446 | |
|
447 | 0 | struct AttrColor *ac_ind = simple_color_get(MT_COLOR_INDICATOR); |
448 | 0 | const bool c_arrow_cursor = cs_subset_bool(menu->sub, "arrow_cursor"); |
449 | 0 | const char *const c_arrow_string = cs_subset_string(menu->sub, "arrow_string"); |
450 | 0 | if (c_arrow_cursor) |
451 | 0 | { |
452 | 0 | mutt_curses_set_color(ac_ind); |
453 | 0 | mutt_window_addstr(menu->win, c_arrow_string); |
454 | 0 | mutt_curses_set_color(ac); |
455 | 0 | mutt_window_addch(menu->win, ' '); |
456 | 0 | menu_pad_string(menu, buf, sizeof(buf)); |
457 | 0 | print_enriched_string(menu->win, menu->current, ac, NULL, |
458 | 0 | (unsigned char *) buf, menu->sub); |
459 | 0 | } |
460 | 0 | else |
461 | 0 | { |
462 | 0 | print_enriched_string(menu->win, menu->current, ac, ac_ind, |
463 | 0 | (unsigned char *) buf, menu->sub); |
464 | 0 | } |
465 | 0 | mutt_curses_set_color_by_id(MT_COLOR_NORMAL); |
466 | 0 | } |
467 | | |
468 | | /** |
469 | | * menu_redraw - Redraw the parts of the screen that have been flagged to be redrawn |
470 | | * @param menu Menu to redraw |
471 | | * @retval OP_NULL Menu was redrawn |
472 | | * @retval OP_REDRAW Full redraw required |
473 | | */ |
474 | | int menu_redraw(struct Menu *menu) |
475 | 0 | { |
476 | | /* See if all or part of the screen needs to be updated. */ |
477 | 0 | if (menu->redraw & MENU_REDRAW_FULL) |
478 | 0 | menu_redraw_full(menu); |
479 | |
|
480 | 0 | if (menu->redraw & MENU_REDRAW_INDEX) |
481 | 0 | menu_redraw_index(menu); |
482 | 0 | else if (menu->redraw & MENU_REDRAW_MOTION) |
483 | 0 | menu_redraw_motion(menu); |
484 | 0 | else if (menu->redraw == MENU_REDRAW_CURRENT) |
485 | 0 | menu_redraw_current(menu); |
486 | |
|
487 | 0 | return OP_NULL; |
488 | 0 | } |