NeoMutt  2021-02-05-666-ge300cd
Teaching an old dog new tricks
DOXYGEN
menu.c
Go to the documentation of this file.
1 
30 #include "config.h"
31 #include <stdbool.h>
32 #include <stdint.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include "private.h"
36 #include "mutt/lib.h"
37 #include "config/lib.h"
38 #include "gui/lib.h"
39 #include "mutt.h"
40 #include "menu/lib.h"
41 #include "commands.h"
42 #include "context.h"
43 #include "keymap.h"
44 #include "mutt_globals.h"
45 #include "mutt_logging.h"
46 #include "mutt_mailbox.h"
47 #include "opcodes.h"
48 #include "protos.h"
49 #include "type.h"
50 
52 
53 #define MUTT_SEARCH_UP 1
54 #define MUTT_SEARCH_DOWN 2
55 
62 static void menu_jump(struct Menu *menu)
63 {
64  if (menu->max == 0)
65  {
66  mutt_error(_("No entries"));
67  return;
68  }
69 
71  char buf[128] = { 0 };
72  if ((mutt_get_field(_("Jump to: "), buf, sizeof(buf), MUTT_COMP_NO_FLAGS,
73  false, NULL, NULL) == 0) &&
74  (buf[0] != '\0'))
75  {
76  int n = 0;
77  if ((mutt_str_atoi(buf, &n) == 0) && (n > 0) && (n < (menu->max + 1)))
78  {
79  menu_set_index(menu, n - 1); // msg numbers are 0-based
80  }
81  else
82  mutt_error(_("Invalid index number"));
83  }
84 }
85 
89 static int default_color(struct Menu *menu, int line)
90 {
92 }
93 
97 static int generic_search(struct Menu *menu, regex_t *rx, int line)
98 {
99  char buf[1024];
100 
101  menu_make_entry(menu, buf, sizeof(buf), line);
102  return regexec(rx, buf, 0, NULL, 0);
103 }
104 
108 void menu_cleanup(void)
109 {
110  for (int i = 0; i < MENU_MAX; i++)
111  FREE(&SearchBuffers[i]);
112 }
113 
117 void menu_init(void)
118 {
119  for (int i = 0; i < MENU_MAX; i++)
120  SearchBuffers[i] = NULL;
121 }
122 
128 void menu_add_dialog_row(struct Menu *menu, const char *row)
129 {
130  ARRAY_SET(&menu->dialog, menu->max, mutt_str_dup(row));
131  menu->max++;
132 }
133 
141 static int search(struct Menu *menu, int op)
142 {
143  int rc = 0, wrap = 0;
144  int search_dir;
145  regex_t re;
146  char buf[128];
147  char *search_buf = ((menu->type < MENU_MAX)) ? SearchBuffers[menu->type] : NULL;
148 
149  if (!(search_buf && *search_buf) || ((op != OP_SEARCH_NEXT) && (op != OP_SEARCH_OPPOSITE)))
150  {
151  mutt_str_copy(buf, search_buf && (search_buf[0] != '\0') ? search_buf : "",
152  sizeof(buf));
153  if ((mutt_get_field(((op == OP_SEARCH) || (op == OP_SEARCH_NEXT)) ?
154  _("Search for: ") :
155  _("Reverse search for: "),
156  buf, sizeof(buf), MUTT_CLEAR, false, NULL, NULL) != 0) ||
157  (buf[0] == '\0'))
158  {
159  return -1;
160  }
161  if (menu->type < MENU_MAX)
162  {
163  mutt_str_replace(&SearchBuffers[menu->type], buf);
164  search_buf = SearchBuffers[menu->type];
165  }
166  menu->search_dir =
167  ((op == OP_SEARCH) || (op == OP_SEARCH_NEXT)) ? MUTT_SEARCH_DOWN : MUTT_SEARCH_UP;
168  }
169 
170  search_dir = (menu->search_dir == MUTT_SEARCH_UP) ? -1 : 1;
171  if (op == OP_SEARCH_OPPOSITE)
172  search_dir = -search_dir;
173 
174  if (search_buf)
175  {
176  uint16_t flags = mutt_mb_is_lower(search_buf) ? REG_ICASE : 0;
177  rc = REG_COMP(&re, search_buf, REG_NOSUB | flags);
178  }
179 
180  if (rc != 0)
181  {
182  regerror(rc, &re, buf, sizeof(buf));
183  mutt_error("%s", buf);
184  return -1;
185  }
186 
187  rc = menu->current + search_dir;
188 search_next:
189  if (wrap)
190  mutt_message(_("Search wrapped to top"));
191  while ((rc >= 0) && (rc < menu->max))
192  {
193  if (menu->search(menu, &re, rc) == 0)
194  {
195  regfree(&re);
196  return rc;
197  }
198 
199  rc += search_dir;
200  }
201 
202  const bool c_wrap_search = cs_subset_bool(menu->sub, "wrap_search");
203  if (c_wrap_search && (wrap++ == 0))
204  {
205  rc = (search_dir == 1) ? 0 : menu->max - 1;
206  goto search_next;
207  }
208  regfree(&re);
209  mutt_error(_("Not found"));
210  return -1;
211 }
212 
218 static int menu_dialog_translate_op(int i)
219 {
220  switch (i)
221  {
222  case OP_NEXT_ENTRY:
223  return OP_NEXT_LINE;
224  case OP_PREV_ENTRY:
225  return OP_PREV_LINE;
226  case OP_CURRENT_TOP:
227  case OP_FIRST_ENTRY:
228  return OP_TOP_PAGE;
229  case OP_CURRENT_BOTTOM:
230  case OP_LAST_ENTRY:
231  return OP_BOTTOM_PAGE;
232  case OP_CURRENT_MIDDLE:
233  return OP_MIDDLE_PAGE;
234  }
235 
236  return i;
237 }
238 
246 static int menu_dialog_dokey(struct Menu *menu, int *ip)
247 {
248  struct KeyEvent ch;
249  char *p = NULL;
250 
251  do
252  {
253  ch = mutt_getch();
254  } while (ch.ch == -2); // Timeout
255 
256  if (ch.ch < 0)
257  {
258  *ip = -1;
259  return 0;
260  }
261 
262  if (ch.ch && (p = strchr(menu->keys, ch.ch)))
263  {
264  *ip = OP_MAX + (p - menu->keys + 1);
265  return 0;
266  }
267  else
268  {
269  if (ch.op == OP_NULL)
270  mutt_unget_event(ch.ch, 0);
271  else
272  mutt_unget_event(0, ch.op);
273  return -1;
274  }
275 }
276 
282 int menu_loop(struct Menu *menu)
283 {
284  int op = OP_NULL;
285 
286  while (true)
287  {
288  /* Clear the tag prefix unless we just started it. Don't clear
289  * the prefix on a timeout (op==-2), but do clear on an abort (op==-1) */
290  if (menu->tagprefix && (op != OP_TAG_PREFIX) &&
291  (op != OP_TAG_PREFIX_COND) && (op != -2))
292  {
293  menu->tagprefix = false;
294  }
295 
297 
298  if (menu_redraw(menu) == OP_REDRAW)
299  return OP_REDRAW;
300 
301  /* give visual indication that the next command is a tag- command */
302  if (menu->tagprefix)
304 
305  const bool c_arrow_cursor = cs_subset_bool(menu->sub, "arrow_cursor");
306  const bool c_braille_friendly =
307  cs_subset_bool(menu->sub, "braille_friendly");
308 
309  /* move the cursor out of the way */
310  if (c_arrow_cursor)
311  mutt_window_move(menu->win, 2, menu->current - menu->top);
312  else if (c_braille_friendly)
313  mutt_window_move(menu->win, 0, menu->current - menu->top);
314  else
315  {
316  mutt_window_move(menu->win, menu->win->state.cols - 1, menu->current - menu->top);
317  }
318 
319  mutt_refresh();
320 
321  /* try to catch dialog keys before ops */
322  if (!ARRAY_EMPTY(&menu->dialog) && (menu_dialog_dokey(menu, &op) == 0))
323  return op;
324 
325  const bool c_auto_tag = cs_subset_bool(menu->sub, "auto_tag");
326  op = km_dokey(menu->type);
327  if ((op == OP_TAG_PREFIX) || (op == OP_TAG_PREFIX_COND))
328  {
329  if (menu->tagprefix)
330  {
331  menu->tagprefix = false;
333  continue;
334  }
335 
336  if (menu->tagged)
337  {
338  menu->tagprefix = true;
339  continue;
340  }
341  else if (op == OP_TAG_PREFIX)
342  {
343  mutt_error(_("No tagged entries"));
344  op = -1;
345  }
346  else /* None tagged, OP_TAG_PREFIX_COND */
347  {
349  mutt_message(_("Nothing to do"));
350  op = -1;
351  }
352  }
353  else if (menu->tagged && c_auto_tag)
354  menu->tagprefix = true;
355 
357 
358  if (SigWinch)
359  {
360  SigWinch = false;
362  clearok(stdscr, true); /* force complete redraw */
363  }
364 
365  if (op < 0)
366  {
367  if (menu->tagprefix)
369  continue;
370  }
371 
372  if (ARRAY_EMPTY(&menu->dialog))
374 
375  /* Convert menubar movement to scrolling */
376  if (!ARRAY_EMPTY(&menu->dialog))
377  op = menu_dialog_translate_op(op);
378 
379  switch (op)
380  {
381  case OP_NEXT_ENTRY:
382  menu_next_entry(menu);
383  break;
384  case OP_PREV_ENTRY:
385  menu_prev_entry(menu);
386  break;
387  case OP_HALF_DOWN:
388  menu_half_down(menu);
389  break;
390  case OP_HALF_UP:
391  menu_half_up(menu);
392  break;
393  case OP_NEXT_PAGE:
394  menu_next_page(menu);
395  break;
396  case OP_PREV_PAGE:
397  menu_prev_page(menu);
398  break;
399  case OP_NEXT_LINE:
400  menu_next_line(menu);
401  break;
402  case OP_PREV_LINE:
403  menu_prev_line(menu);
404  break;
405  case OP_FIRST_ENTRY:
406  menu_first_entry(menu);
407  break;
408  case OP_LAST_ENTRY:
409  menu_last_entry(menu);
410  break;
411  case OP_TOP_PAGE:
412  menu_top_page(menu);
413  break;
414  case OP_MIDDLE_PAGE:
415  menu_middle_page(menu);
416  break;
417  case OP_BOTTOM_PAGE:
418  menu_bottom_page(menu);
419  break;
420  case OP_CURRENT_TOP:
421  menu_current_top(menu);
422  break;
423  case OP_CURRENT_MIDDLE:
424  menu_current_middle(menu);
425  break;
426  case OP_CURRENT_BOTTOM:
427  menu_current_bottom(menu);
428  break;
429  case OP_SEARCH:
430  case OP_SEARCH_REVERSE:
431  case OP_SEARCH_NEXT:
432  case OP_SEARCH_OPPOSITE:
433  if (menu->custom_search)
434  return op;
435  else if (menu->search && ARRAY_EMPTY(&menu->dialog)) /* Searching dialogs won't work */
436  {
437  int index = search(menu, op);
438  if (index != -1)
439  menu_set_index(menu, index);
440  }
441  else
442  mutt_error(_("Search is not implemented for this menu"));
443  break;
444 
445  case OP_JUMP:
446  if (!ARRAY_EMPTY(&menu->dialog))
447  mutt_error(_("Jumping is not implemented for dialogs"));
448  else
449  menu_jump(menu);
450  break;
451 
452  case OP_ENTER_COMMAND:
454  window_redraw(NULL);
455  break;
456 
457  case OP_TAG:
458  if (menu->tag && ARRAY_EMPTY(&menu->dialog))
459  {
460  const bool c_resolve = cs_subset_bool(menu->sub, "resolve");
461 
462  if (menu->tagprefix && !c_auto_tag)
463  {
464  for (int i = 0; i < menu->max; i++)
465  menu->tagged += menu->tag(menu, i, 0);
466  menu->redraw |= MENU_REDRAW_INDEX;
467  }
468  else if (menu->max != 0)
469  {
470  int j = menu->tag(menu, menu->current, -1);
471  menu->tagged += j;
472  if (j && c_resolve && (menu->current < (menu->max - 1)))
473  {
474  menu_set_index(menu, menu->current + 1);
475  }
476  else
477  menu->redraw |= MENU_REDRAW_CURRENT;
478  }
479  else
480  mutt_error(_("No entries"));
481  }
482  else
483  mutt_error(_("Tagging is not supported"));
484  break;
485 
486  case OP_SHELL_ESCAPE:
487  if (mutt_shell_escape())
488  {
490  }
491  break;
492 
493  case OP_WHAT_KEY:
494  mutt_what_key();
495  break;
496 
497  case OP_CHECK_STATS:
499  break;
500 
501  case OP_REDRAW:
502  clearok(stdscr, true);
503  menu->redraw = MENU_REDRAW_FULL;
504  break;
505 
506  case OP_HELP:
507  mutt_help(menu->type);
508  menu->redraw = MENU_REDRAW_FULL;
509  break;
510 
511  case OP_NULL:
512  km_error_key(menu->type);
513  break;
514 
515  case OP_END_COND:
516  break;
517 
518  default:
519  return op;
520  }
521  }
522  /* not reached */
523 }
524 
530 {
531  struct MuttWindow *win = alldialogs_get_current();
532  while (win && win->focus)
533  win = win->focus;
534 
535  if (!win)
536  return MENU_GENERIC;
537 
538  if ((win->type == WT_CUSTOM) && (win->parent->type == WT_PAGER))
539  return MENU_PAGER;
540 
541  if (win->type != WT_MENU)
542  return MENU_GENERIC;
543 
544  struct Menu *menu = win->wdata;
545  if (!menu)
546  return MENU_GENERIC;
547 
548  return menu->type;
549 }
550 
555 void menu_free(struct Menu **ptr)
556 {
557  struct Menu *menu = *ptr;
558 
559  notify_free(&menu->notify);
560 
561  if (menu->mdata_free && menu->mdata)
562  menu->mdata_free(menu, &menu->mdata); // Custom function to free private data
563 
564  char **line = NULL;
565  ARRAY_FOREACH(line, &menu->dialog)
566  {
567  FREE(line);
568  }
569  ARRAY_FREE(&menu->dialog);
570 
571  FREE(ptr);
572 }
573 
581 struct Menu *menu_new(enum MenuType type, struct MuttWindow *win, struct ConfigSubset *sub)
582 {
583  struct Menu *menu = mutt_mem_calloc(1, sizeof(struct Menu));
584 
585  menu->type = type;
586  menu->redraw = MENU_REDRAW_FULL;
587  menu->color = default_color;
588  menu->search = generic_search;
589  menu->notify = notify_new();
590  menu->win = win;
591  menu->pagelen = win->state.rows;
592  menu->sub = sub;
593 
594  notify_set_parent(menu->notify, win->notify);
595  menu_add_observers(menu);
596 
597  return menu;
598 }
599 
605 int menu_get_index(struct Menu *menu)
606 {
607  if (!menu)
608  return -1;
609 
610  return menu->current;
611 }
612 
619 MenuRedrawFlags menu_set_index(struct Menu *menu, int index)
620 {
621  return menu_move_selection(menu, index);
622 }
623 
630 {
631  if (!menu)
632  return;
633 
634  menu->redraw |= redraw;
635  menu->win->actions |= WA_RECALC;
636 }
Convenience wrapper for the gui headers.
The "current" mailbox.
Definition: context.h:37
Manage keymappings.
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
MenuType
Types of GUI selections.
Definition: type.h:35
int mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: string.c:252
#define MUTT_CLEAR
Clear input if printable character is pressed.
Definition: mutt.h:58
Definition: lib.h:67
void menu_make_entry(struct Menu *menu, char *buf, size_t buflen, int i)
Create string to display in a Menu (the index)
Definition: draw.c:299
struct Mailbox * ctx_mailbox(struct Context *ctx)
wrapper to get the mailbox in a Context, or NULL
Definition: context.c:444
#define mutt_error(...)
Definition: logging.h:88
The "currently-open" mailbox.
Generic selection list.
Definition: type.h:45
int mutt_get_field(const char *field, char *buf, size_t buflen, CompletionFlags complete, bool multiple, char ***files, int *numfiles)
Ask the user for a string.
Definition: curs_lib.c:335
void notify_free(struct Notify **ptr)
Free a notification handler.
Definition: notify.c:73
bool mutt_shell_escape(void)
invoke a command in a subshell
Definition: commands.c:943
#define ARRAY_FREE(head)
Release all memory.
Definition: array.h:198
void mutt_resize_screen(void)
Update NeoMutt&#39;s opinion about the window size (CURSES)
Definition: resize.c:101
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:206
NeoMutt Logging.
MenuRedrawFlags redraw
When to redraw the screen.
Definition: lib.h:72
void mutt_enter_command(void)
enter a neomutt command
Definition: commands.c:978
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
void mutt_unget_event(int ch, int op)
Return a keystroke to the input buffer.
Definition: curs_lib.c:583
#define _(a)
Definition: message.h:28
#define REG_COMP(preg, regex, cflags)
Compile a regular expression.
Definition: regex3.h:54
int menu_redraw(struct Menu *menu)
Redraw the parts of the screen that have been flagged to be redrawn.
Definition: draw.c:546
void window_redraw(struct MuttWindow *win)
Reflow, recalc and repaint a tree of Windows.
Definition: mutt_window.c:632
int op
function op
Definition: keymap.h:67
int ch
raw key pressed
Definition: keymap.h:66
All user-callable functions.
void mutt_help(enum MenuType menu)
Display the help menu.
Definition: help.c:389
void msgwin_clear_text(void)
Clear the text in the Message Window.
Definition: msgwin.c:242
Convenience wrapper for the config headers.
int mutt_window_move(struct MuttWindow *win, int col, int row)
Move the cursor in a Window.
Definition: mutt_window.c:310
struct MuttWindow * focus
Focussed Window.
Definition: mutt_window.h:140
WindowActionFlags actions
Actions to be performed, e.g. WA_RECALC.
Definition: mutt_window.h:132
Definition: type.h:59
Pager pager (email viewer)
Definition: type.h:54
void mutt_curses_set_cursor(enum MuttCursorState state)
Set the cursor state.
Definition: mutt_curses.c:71
struct Notify * notify
Notifications: NotifyWindow, EventWindow.
Definition: mutt_window.h:138
enum MenuType type
Menu definition for keymap entries.
Definition: lib.h:73
Many unsorted constants and some structs.
void mutt_flush_macro_to_endcond(void)
Drop a macro from the input buffer.
Definition: curs_lib.c:639
#define MUTT_COMP_NO_FLAGS
No flags are set.
Definition: mutt.h:52
static int generic_search(struct Menu *menu, regex_t *rx, int line)
Search a menu for a item matching a regex - Implements Menu::search() -.
Definition: menu.c:97
struct MuttWindow * win
Window holding the Menu.
Definition: lib.h:76
void mutt_what_key(void)
Ask the user to press a key.
Definition: keymap.c:1718
Display a normal cursor.
Definition: mutt_curses.h:81
int LastKey
contains the last key the user pressed
Definition: keymap.c:123
void km_error_key(enum MenuType mtype)
Handle an unbound key sequence.
Definition: keymap.c:1144
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:112
Window with a custom drawing function.
Definition: mutt_window.h:95
Plain text.
Definition: color.h:58
short cols
Number of columns, can be MUTT_WIN_SIZE_UNLIMITED.
Definition: mutt_window.h:60
void mutt_refresh(void)
Force a refresh of the screen.
Definition: curs_lib.c:115
struct Notify * notify_new(void)
Create a new notifications handler.
Definition: notify.c:60
Prototypes for many functions.
A set of inherited config items.
Definition: subset.h:46
struct WindowState state
Current state of the Window.
Definition: mutt_window.h:127
void * mdata
Private data.
Definition: lib.h:155
int mutt_mailbox_check(struct Mailbox *m_cur, int force)
Check all all Mailboxes for new mail.
Definition: mutt_mailbox.c:137
short rows
Number of rows, can be MUTT_WIN_SIZE_UNLIMITED.
Definition: mutt_window.h:61
int top
Entry that is the top of the current page.
Definition: lib.h:89
bool tagprefix
User has pressed <tag-prefix>
Definition: lib.h:75
#define ARRAY_EMPTY(head)
Check if an array is empty.
Definition: array.h:70
Manage where the email is piped to external commands.
struct MuttWindow * alldialogs_get_current(void)
Get the currently active Dialog.
Definition: dialog.c:224
int search_dir
Direction of search.
Definition: lib.h:91
An event such as a keypress.
Definition: keymap.h:64
struct Notify * notify
Notifications.
Definition: lib.h:153
int(* tag)(struct Menu *menu, int sel, int act)
Definition: lib.h:130
bool custom_search
The menu implements its own non-Menusearch()-compatible search, trickle OP_SEARCH*.
Definition: lib.h:93
struct KeyEvent mutt_getch(void)
Read a character from the input buffer.
Definition: curs_lib.c:196
int pagelen
Number of entries per screen.
Definition: lib.h:74
char * keys
Keys used in the prompt.
Definition: lib.h:86
int mutt_color(enum ColorId id)
Return the color of an object.
Definition: color.c:1427
int tagged
Number of tagged entries.
Definition: lib.h:92
#define MUTT_MAILBOX_CHECK_FORCE
Definition: mutt_mailbox.h:32
int max
Number of entries in the menu.
Definition: lib.h:71
#define ARRAY_SET(head, idx, elem)
Set an element in the array.
Definition: array.h:119
A panel containing the Pager Window.
Definition: mutt_window.h:100
void(* mdata_free)(struct Menu *menu, void **ptr)
Definition: lib.h:170
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:749
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:446
struct MuttWindow * parent
Parent Window.
Definition: mutt_window.h:135
struct ConfigSubset * sub
Inherited config items.
Definition: lib.h:78
void mutt_check_stats(struct Mailbox *m)
Forcibly update mailbox stats.
Definition: commands.c:1590
#define mutt_message(...)
Definition: logging.h:87
#define FREE(x)
Definition: memory.h:40
Hide the cursor.
Definition: mutt_curses.h:80
void notify_set_parent(struct Notify *notify, struct Notify *parent)
Set the parent notification handler.
Definition: notify.c:93
Hundreds of global variables to back the user variables.
int current
Current entry.
Definition: lib.h:70
int km_dokey(enum MenuType mtype)
Determine what a keypress should do.
Definition: keymap.c:635
Convenience wrapper for the library headers.
void * wdata
Private data.
Definition: mutt_window.h:145
bool mutt_mb_is_lower(const char *s)
Does a multi-byte string contain only lowercase characters?
Definition: mbyte.c:357
WHERE SIG_ATOMIC_VOLATILE_T SigWinch
true after SIGWINCH is received
Definition: mutt_globals.h:68
enum WindowType type
Window type, e.g. WT_SIDEBAR.
Definition: mutt_window.h:144
int(* search)(struct Menu *menu, regex_t *rx, int line)
Definition: lib.h:118
static int default_color(struct Menu *menu, int line)
Get the default colour for a line of the menu - Implements Menu::color() -.
Definition: menu.c:89
An Window containing a Menu.
Definition: mutt_window.h:98
Mailbox helper functions.
int(* color)(struct Menu *menu, int line)
Definition: lib.h:142
Menu types.
void msgwin_set_text(enum ColorId color, const char *text)
Set the text for the Message Window.
Definition: msgwin.c:223
#define WA_RECALC
Recalculate the contents of the Window.
Definition: mutt_window.h:110