NeoMutt  2025-12-11-1039-g550ac6
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
msgwin.c
Go to the documentation of this file.
1
22
84
85#include "config.h"
86#include <stdbool.h>
87#include <string.h>
88#include <wchar.h>
89#include "mutt/lib.h"
90#include "core/lib.h"
91#include "msgwin.h"
92#include "color/lib.h"
93#include "module_data.h"
94#include "msgcont.h"
95#include "msgwin_wdata.h"
96#include "mutt_curses.h"
97#include "mutt_window.h"
98
107void measure(struct MwCharArray *chars, const char *str, const struct AttrColor *ac_color)
108{
109 if (!str || !*str)
110 return;
111
112 mbstate_t mbstate = { 0 };
113 struct MwChar mwc = { 0 };
114
115 size_t str_len = mutt_str_len(str);
116
117 while (*str && (str_len > 0))
118 {
119 wchar_t wc = L'\0';
120 size_t consumed = mbrtowc(&wc, str, str_len, &mbstate);
121 if (consumed == 0)
122 break;
123
124 if (consumed == ICONV_ILLEGAL_SEQ)
125 {
126 memset(&mbstate, 0, sizeof(mbstate));
127 wc = ReplacementChar;
128 consumed = 1;
129 }
130 else if (consumed == ICONV_BUF_TOO_SMALL)
131 {
132 wc = ReplacementChar;
133 consumed = str_len;
134 }
135
136 int wchar_width = wcwidth(wc);
137 if (wchar_width < 0)
138 wchar_width = 1;
139
140 if (wc == 0xfe0f) // Emoji variation
141 {
142 int size = ARRAY_SIZE(chars);
143 if (size > 0)
144 {
145 struct MwChar *es_prev = ARRAY_GET(chars, size - 1);
146 if (es_prev->width == 1)
147 es_prev->width = 2;
148 }
149 }
150
151 mwc = (struct MwChar) { wchar_width, consumed, ac_color };
152 ARRAY_ADD(chars, mwc);
153
154 str += consumed;
155 str_len -= consumed;
156 }
157}
158
162static int msgwin_recalc(struct MuttWindow *win)
163{
164 win->actions |= WA_REPAINT;
165 mutt_debug(LL_DEBUG5, "recalc done, request WA_REPAINT\n");
166 return 0;
167}
168
179int msgwin_calc_rows(struct MsgWinWindowData *wdata, int cols, const char *str)
180{
181 if (!wdata)
182 return 0;
183
184 for (int i = 0; i < MSGWIN_MAX_ROWS; i++)
185 {
186 ARRAY_FREE(&wdata->rows[i]);
187 }
188
189 if (!str || !*str)
190 return 0;
191
192 int width = 0;
193 int offset = 0;
194 int row = 0;
195 bool new_row = false;
196
197 struct MwChunk *chunk = NULL;
198 struct MwChar *mwc = NULL;
199 ARRAY_FOREACH(mwc, &wdata->chars)
200 {
201 const bool nl = (mwc->bytes == 1) && (str[offset] == '\n');
202 if (nl)
203 {
204 new_row = true;
205 offset += mwc->bytes;
206 continue;
207 }
208
209 if (((width + mwc->width) > cols) || new_row)
210 {
211 // ROW IS FULL
212 new_row = false;
213
214 row++;
215 if (row >= MSGWIN_MAX_ROWS)
216 {
217 // NO MORE ROOM
218 break;
219 }
220
221 // Start a new row
222 struct MwChunk tmp = { offset, mwc->bytes, mwc->width, mwc->ac_color };
223
224 mutt_debug(LL_DEBUG5, "row = %d\n", row);
225 ARRAY_ADD(&wdata->rows[row], tmp);
226 chunk = ARRAY_LAST(&wdata->rows[row]);
227
228 width = 0;
229 }
230 else if (!chunk || (mwc->ac_color != chunk->ac_color))
231 {
232 // CHANGE OF COLOUR
233 struct MwChunk tmp = { offset, mwc->bytes, mwc->width, mwc->ac_color };
234 ARRAY_ADD(&wdata->rows[row], tmp);
235 chunk = ARRAY_LAST(&wdata->rows[row]);
236 }
237 else
238 {
239 // MORE OF THE SAME
240 chunk->bytes += mwc->bytes;
241 chunk->width += mwc->width;
242 }
243
244 offset += mwc->bytes;
245 width += mwc->width;
246 }
247
248 mutt_debug(LL_DEBUG5, "msgwin_calc_rows() => %d\n", row + 1);
249 return row + 1;
250}
251
255static int msgwin_repaint(struct MuttWindow *win)
256{
257 struct MsgWinWindowData *wdata = win->wdata;
258
259 const char *str = buf_string(wdata->text);
260 const int max_rows = MIN(MSGWIN_MAX_ROWS, win->state.rows);
261 for (int i = 0; i < max_rows; i++)
262 {
263 mutt_window_move(win, i, 0);
264 if (ARRAY_EMPTY(&wdata->rows[i]))
265 {
268 break;
269 }
270
271 struct MwChunk *chunk = NULL;
272 ARRAY_FOREACH(chunk, &wdata->rows[i])
273 {
275 mutt_window_addnstr(win, str + chunk->offset, chunk->bytes);
276 }
279 }
280
281 mutt_window_get_coords(win, &wdata->row, &wdata->col);
282
283 mutt_debug(LL_DEBUG5, "msgwin repaint done\n");
284 return 0;
285}
286
290static bool msgwin_recursor(struct MuttWindow *win)
291{
292 struct MsgWinWindowData *wdata = win->wdata;
293
294 mutt_window_move(win, wdata->row, wdata->col);
296
297 mutt_debug(LL_DEBUG5, "msgwin recursor done\n");
298 return true;
299}
300
310void msgwin_set_rows(struct MuttWindow *win, short rows)
311{
312 if (!win)
313 win = msgcont_get_msgwin();
314 if (!win)
315 return;
316
318
319 if (rows != win->req_rows)
320 {
321 win->req_rows = rows;
322
324
325 mutt_window_reflow(NULL);
326 }
327}
328
338{
339 if (nc->event_type != NT_WINDOW)
340 return 0;
341 if (!nc->global_data || !nc->event_data)
342 return -1;
343
344 struct MuttWindow *win = nc->global_data;
345 struct EventWindow *ev_w = nc->event_data;
346 if (ev_w->win != win)
347 return 0;
348
350 {
351 if (ev_w->flags & WN_HIDDEN)
352 {
354 }
355
356 if (ev_w->flags & (WN_NARROWER | WN_WIDER))
357 {
358 struct MsgWinWindowData *wdata = win->wdata;
360 buf_string(wdata->text)));
361 win->actions |= WA_RECALC;
362 }
363 else
364 {
365 win->actions |= WA_REPAINT;
366 }
367 mutt_debug(LL_DEBUG5, "window state done, request WA_RECALC\n");
368 }
369 else if (nc->event_subtype == NT_WINDOW_DELETE)
370 {
372 mutt_debug(LL_DEBUG5, "window delete done\n");
373 }
374 return 0;
375}
376
381struct MuttWindow *msgwin_new(bool interactive)
382{
385
386 struct MsgWinWindowData *wdata = msgwin_wdata_new();
387
388 win->wdata = wdata;
390 win->recalc = msgwin_recalc;
391 win->repaint = msgwin_repaint;
392
393 if (interactive)
395
396 // Copy the container's dimensions
398 if (mod_data && mod_data->message_container)
399 win->state = mod_data->message_container->state;
400
402
403 return win;
404}
405
413const char *msgwin_get_text(struct MuttWindow *win)
414{
415 if (!win)
416 win = msgcont_get_msgwin();
417 if (!win)
418 return NULL;
419
420 struct MsgWinWindowData *wdata = win->wdata;
421
422 return buf_string(wdata->text);
423}
424
431void msgwin_add_text(struct MuttWindow *win, const char *text, const struct AttrColor *ac_color)
432{
433 if (!win)
434 win = msgcont_get_msgwin();
435 if (!win)
436 return;
437
438 struct MsgWinWindowData *wdata = win->wdata;
439
440 if (text)
441 {
442 buf_addstr(wdata->text, text);
443 measure(&wdata->chars, text, ac_color);
444 mutt_debug(LL_DEBUG5, "MW ADD: %zu, %s\n", buf_len(wdata->text),
445 buf_string(wdata->text));
446 }
447 else
448 {
449 int rows = msgwin_calc_rows(wdata, win->state.cols, buf_string(wdata->text));
450 msgwin_set_rows(win, rows);
451 win->actions |= WA_RECALC;
452 }
453}
454
462void msgwin_add_text_n(struct MuttWindow *win, const char *text, int bytes,
463 const struct AttrColor *ac_color)
464{
465 if (!win)
466 win = msgcont_get_msgwin();
467 if (!win)
468 return;
469
470 struct MsgWinWindowData *wdata = win->wdata;
471
472 if (text)
473 {
474 // Track the length of the text so far
475 const size_t len = buf_len(wdata->text);
476 buf_addstr_n(wdata->text, text, bytes);
477 // and only measure the newly added text
478 measure(&wdata->chars, wdata->text->data + len, ac_color);
479 mutt_debug(LL_DEBUG5, "MW ADD: %zu, %s\n", buf_len(wdata->text),
480 buf_string(wdata->text));
481 }
482 else
483 {
484 int rows = msgwin_calc_rows(wdata, win->state.cols, buf_string(wdata->text));
485 msgwin_set_rows(win, rows);
486 win->actions |= WA_RECALC;
487 }
488}
489
502void msgwin_set_text(struct MuttWindow *win, const char *text, enum ColorId color)
503{
504 if (!win)
505 win = msgcont_get_msgwin();
506 if (!win)
507 return;
508
509 const bool hidden = !win->state.visible;
510
511 struct MsgWinWindowData *wdata = win->wdata;
512
513 // If the text is unchanged: nothing to do, except reveal the window if it
514 // was hidden so the user actually sees it.
515 if (mutt_str_equal(buf_string(wdata->text), text) && !hidden)
516 return;
517
518 buf_strcpy(wdata->text, text);
519 ARRAY_FREE(&wdata->chars);
520 if (wdata->text)
521 {
522 const struct AttrColor *ac_normal = simple_color_get(MT_COLOR_NORMAL);
523 const struct AttrColor *ac_color = simple_color_get(color);
524 const struct AttrColor *ac_merge = merged_color_overlay(ac_normal, ac_color);
525
526 measure(&wdata->chars, buf_string(wdata->text), ac_merge);
527 }
528
529 mutt_debug(LL_DEBUG5, "MW SET: %zu, %s\n", buf_len(wdata->text),
530 buf_string(wdata->text));
531
532 // If the window was hidden, reveal it and reflow so the message is seen.
533 if (!win->state.visible)
534 {
535 window_set_visible(win, true);
536 int rows = msgwin_calc_rows(wdata, win->state.cols, buf_string(wdata->text));
537 win->req_rows = CLAMP(rows, 1, MSGWIN_MAX_ROWS);
539 win->actions |= WA_RECALC;
540 window_redraw(NULL);
541 return;
542 }
543
544 int rows = msgwin_calc_rows(wdata, win->state.cols, buf_string(wdata->text));
545 msgwin_set_rows(win, rows);
546 win->actions |= WA_RECALC;
547}
548
557{
558 if (!win)
559 win = msgcont_get_msgwin();
560 if (!win)
561 return;
562
563 struct MsgWinWindowData *wdata = win->wdata;
564
565 // Force-clear even if the window was hidden (msgwin_set_text would skip it).
566 if (!buf_is_empty(wdata->text))
567 {
568 buf_reset(wdata->text);
569 ARRAY_FREE(&wdata->chars);
570 for (int i = 0; i < MSGWIN_MAX_ROWS; i++)
571 {
572 ARRAY_FREE(&wdata->rows[i]);
573 }
574
575 if (win->state.visible)
576 {
577 int rows = msgwin_calc_rows(wdata, win->state.cols, buf_string(wdata->text));
578 msgwin_set_rows(win, rows);
579 win->actions |= WA_RECALC;
580 }
581 }
582
583 // If the Message Window isn't alone in the stack, hide it again so the
584 // Window beneath us regains the space.
585 if (win->state.visible && (msgcont_num_windows() > 1))
586 {
587 window_set_visible(win, false);
589 window_redraw(NULL);
590 }
591}
592
600{
601 return msgcont_get_msgwin();
602}
603
612{
613 struct MuttWindow *win = msgcont_get_msgwin();
614 if (!win || !win->wdata)
615 return false;
616
617 const struct MsgWinWindowData *wdata = win->wdata;
618 return !buf_is_empty(wdata->text);
619}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition array.h:157
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:223
#define ARRAY_LAST(head)
Convenience method to get the last element.
Definition array.h:145
#define ARRAY_EMPTY(head)
Check if an array is empty.
Definition array.h:74
#define ARRAY_SIZE(head)
The number of elements stored.
Definition array.h:87
#define ARRAY_FREE(head)
Release all memory.
Definition array.h:209
#define ARRAY_GET(head, idx)
Return the element at index.
Definition array.h:109
size_t buf_addstr_n(struct Buffer *buf, const char *s, size_t len)
Add a string to a Buffer, expanding it if necessary.
Definition buffer.c:109
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition buffer.c:497
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition buffer.c:89
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition buffer.c:298
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:233
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:401
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
Color and attribute parsing.
struct AttrColor * simple_color_get(enum ColorId cid)
Get the colour of an object by its ID.
Definition simple.c:98
ColorId
List of all coloured objects.
Definition color.h:35
@ MT_COLOR_NORMAL
Plain text.
Definition color.h:53
Convenience wrapper for the core headers.
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
static int msgwin_window_observer(struct NotifyCallback *nc)
Notification that a Window has changed - Implements observer_t -.
Definition msgwin.c:337
static int msgwin_recalc(struct MuttWindow *win)
Recalculate the display of the Message Window - Implements MuttWindow::recalc() -.
Definition msgwin.c:162
static bool msgwin_recursor(struct MuttWindow *win)
Recursor the Message Window - Implements MuttWindow::recursor() -.
Definition msgwin.c:290
static int msgwin_repaint(struct MuttWindow *win)
Redraw the Message Window - Implements MuttWindow::repaint() -.
Definition msgwin.c:255
void msgwin_wdata_free(struct MuttWindow *win, void **ptr)
Free the private data - Implements MuttWindow::wdata_free() -.
Gui private Module data.
@ LL_DEBUG5
Log at debug level 5.
Definition logging2.h:49
#define MIN(a, b)
Return the minimum of two values.
Definition memory.h:40
#define CLAMP(val, lo, hi)
Clamp a value between a lower and upper bound.
Definition memory.h:42
const struct AttrColor * merged_color_overlay(const struct AttrColor *base, const struct AttrColor *over)
Combine two colours.
Definition merged.c:110
@ MODULE_ID_GUI
ModuleGui, Graphical code
Definition module_api.h:45
int msgcont_num_windows(void)
Count the Windows in the Message Container.
Definition msgcont.c:90
void msgcont_recalc_rows(void)
Recalculate the Bottom Bar height.
Definition msgcont.c:64
struct MuttWindow * msgcont_get_msgwin(void)
Get the Message Window.
Definition msgcont.c:190
Message Window.
bool msgwin_has_text(void)
Does the Message Window currently hold any text?
Definition msgwin.c:611
struct MuttWindow * msgwin_new(bool interactive)
Create the Message Window.
Definition msgwin.c:381
void msgwin_clear_text(struct MuttWindow *win)
Clear the text in the Message Window.
Definition msgwin.c:556
void msgwin_add_text(struct MuttWindow *win, const char *text, const struct AttrColor *ac_color)
Add text to the Message Window.
Definition msgwin.c:431
int msgwin_calc_rows(struct MsgWinWindowData *wdata, int cols, const char *str)
How many rows will a string need?
Definition msgwin.c:179
struct MuttWindow * msgwin_get_window(void)
Get the Message Window pointer.
Definition msgwin.c:599
void msgwin_add_text_n(struct MuttWindow *win, const char *text, int bytes, const struct AttrColor *ac_color)
Add some text to the Message Window.
Definition msgwin.c:462
void measure(struct MwCharArray *chars, const char *str, const struct AttrColor *ac_color)
Measure a string in bytes and cells.
Definition msgwin.c:107
void msgwin_set_text(struct MuttWindow *win, const char *text, enum ColorId color)
Set the text for the Message Window.
Definition msgwin.c:502
const char * msgwin_get_text(struct MuttWindow *win)
Get the text from the Message Window.
Definition msgwin.c:413
void msgwin_set_rows(struct MuttWindow *win, short rows)
Resize the Message Window.
Definition msgwin.c:310
Message Window.
struct MsgWinWindowData * msgwin_wdata_new(void)
Create new private data for the Message Window.
Message Window private data.
#define MSGWIN_MAX_ROWS
Maximum number of rows to show in the message window.
wchar_t ReplacementChar
When a Unicode character can't be displayed, use this instead.
Definition charset.c:62
#define ICONV_BUF_TOO_SMALL
Error value for iconv() - Buffer too small.
Definition charset.h:116
#define ICONV_ILLEGAL_SEQ
Error value for iconv() - Illegal sequence.
Definition charset.h:114
Convenience wrapper for the library headers.
bool notify_observer_remove(struct Notify *notify, const observer_t callback, const void *global_data)
Remove an observer from an object.
Definition notify.c:230
bool notify_observer_add(struct Notify *notify, enum NotifyType type, observer_t callback, void *global_data)
Add an observer to an object.
Definition notify.c:191
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:666
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
enum MuttCursorState mutt_curses_set_cursor(enum MuttCursorState state)
Set the cursor state.
Definition mutt_curses.c:94
const struct AttrColor * mutt_curses_set_color_by_id(enum ColorId cid)
Set the colour and attributes by the Colour ID.
Definition mutt_curses.c:79
void mutt_curses_set_color(const struct AttrColor *ac)
Set the colour and attributes for text.
Definition mutt_curses.c:38
Define wrapper functions around Curses.
@ MUTT_CURSOR_VISIBLE
Display a normal cursor.
Definition mutt_curses.h:66
void window_redraw(struct MuttWindow *win)
Reflow, recalc and repaint a tree of Windows.
void mutt_window_reflow(struct MuttWindow *win)
Resize a Window and its children.
struct MuttWindow * mutt_window_new(enum WindowType type, enum MuttWindowOrientation orient, enum MuttWindowSize size, int cols, int rows)
Create a new Window.
int mutt_window_move(struct MuttWindow *win, int row, int col)
Move the cursor in a Window.
void window_set_visible(struct MuttWindow *win, bool visible)
Set a Window visible or hidden.
void mutt_window_get_coords(struct MuttWindow *win, int *row, int *col)
Get the cursor position in the Window.
int mutt_window_addnstr(struct MuttWindow *win, const char *str, int num)
Write a partial string to a Window.
void mutt_window_clrtoeol(struct MuttWindow *win)
Clear to the end of the line.
Window management.
@ WT_MESSAGE
Window for messages/errors.
Definition mutt_window.h:99
@ MUTT_WIN_ORIENT_VERTICAL
Window uses all available vertical space.
Definition mutt_window.h:38
@ WA_REPAINT
Redraw the contents of the Window.
@ WA_RECALC
Recalculate the contents of the Window.
@ NT_WINDOW_STATE
Window state has changed, e.g. WN_VISIBLE.
@ NT_WINDOW_DELETE
Window is about to be deleted.
@ WN_WIDER
Window became wider.
@ WN_HIDDEN
Window became hidden.
@ WN_NARROWER
Window became narrower.
#define MUTT_WIN_SIZE_UNLIMITED
Use as much space as possible.
Definition mutt_window.h:52
@ MUTT_WIN_SIZE_FIXED
Window has a fixed size.
Definition mutt_window.h:47
void * neomutt_get_module_data(struct NeoMutt *n, enum ModuleId id)
Get the private data for a Module.
Definition neomutt.c:666
@ NT_WINDOW
MuttWindow has changed, NotifyWindow, EventWindow.
Definition notify_type.h:58
A curses colour and its attributes.
Definition attr.h:65
char * data
Pointer to data.
Definition buffer.h:37
An Event that happened to a Window.
struct MuttWindow * win
Window that changed.
WindowNotifyFlags flags
Attributes of Window that changed.
Gui private Module data.
Definition module_data.h:32
struct MuttWindow * message_container
Message Container Window.
Definition module_data.h:38
Message Window private Window data.
struct Buffer * text
Cached display string.
struct MwCharArray chars
Text: Breakdown of bytes and widths.
struct MwChunkArray rows[MSGWIN_MAX_ROWS]
String byte counts for each row.
int row
Cursor row.
int col
Cursor column.
int(* repaint)(struct MuttWindow *win)
struct WindowState state
Current state of the Window.
void * wdata
Private data.
struct Notify * notify
Notifications: NotifyWindow, EventWindow.
short req_rows
Number of rows required.
int(* recalc)(struct MuttWindow *win)
void(* wdata_free)(struct MuttWindow *win, void **ptr)
WindowActionFlags actions
Actions to be performed, e.g. WA_RECALC.
bool(* recursor)(struct MuttWindow *win)
Description of a single character.
const struct AttrColor * ac_color
Colour to use.
unsigned char width
Width in screen cells.
unsigned char bytes
Number of bytes to represent.
A block of characters of one colour.
unsigned short bytes
Number of bytes in the row.
unsigned short width
Width of row in screen cells.
unsigned short offset
Offset into MsgWinWindowData.text.
const struct AttrColor * ac_color
Colour to use.
Container for Accounts, Notifications.
Definition neomutt.h:41
Data passed to a notification function.
Definition observer.h:34
void * event_data
Data from notify_send().
Definition observer.h:38
enum NotifyType event_type
Send: Event type, e.g. NT_ACCOUNT.
Definition observer.h:36
int event_subtype
Send: Event subtype, e.g. NT_ACCOUNT_ADD.
Definition observer.h:37
void * global_data
Data from notify_observer_add().
Definition observer.h:39
bool visible
Window is visible.
Definition mutt_window.h:59
short cols
Number of columns, can be MUTT_WIN_SIZE_UNLIMITED.
Definition mutt_window.h:60
short rows
Number of rows, can be MUTT_WIN_SIZE_UNLIMITED.
Definition mutt_window.h:61