1 /**
2 * @file
3 * Index Dialog
4 *
5 * @authors
6 * Copyright (C) 1996-2000,2002,2010,2012-2013 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2020 R Primus <rprimus@gmail.com>
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 index_dialog Index Dialog
26 *
27 * The Index Dialog is the main screen within NeoMutt. It contains @ref
28 * index_index (a list of emails), @ref pager_dialog (a view of an email) and
29 * @ref sidebar_window (a list of mailboxes).
30 *
31 * ## Windows
32 *
33 * | Name | Type | See Also |
34 * | :----------- | :----------- | :---------------- |
35 * | Index Dialog | WT_DLG_INDEX | mutt_index_menu() |
36 *
37 * **Parent**
38 * - @ref gui_dialog
39 *
40 * **Children**
41 * - See: @ref index_ipanel
42 * - See: @ref pager_ppanel
43 * - See: @ref sidebar_window
44 *
45 * ## Data
46 * - #IndexSharedData
47 *
48 * ## Events
49 *
50 * None.
51 *
52 * Some other events are handled by the dialog's children.
53 */
54
55 #include "config.h"
56 #include <assert.h>
57 #include <stdbool.h>
58 #include <stdio.h>
59 #include "private.h"
60 #include "mutt/lib.h"
61 #include "config/lib.h"
62 #include "email/lib.h"
63 #include "core/lib.h"
64 #include "conn/lib.h"
65 #include "gui/lib.h"
66 #include "lib.h"
67 #include "color/lib.h"
68 #include "menu/lib.h"
69 #include "pager/lib.h"
70 #include "pattern/lib.h"
71 #include "context.h"
72 #include "format_flags.h"
73 #include "functions.h"
74 #include "hdrline.h"
75 #include "hook.h"
76 #include "keymap.h"
77 #include "mutt_globals.h"
78 #include "mutt_logging.h"
79 #include "mutt_mailbox.h"
80 #include "mutt_thread.h"
81 #include "mx.h"
82 #include "opcodes.h"
83 #include "options.h"
84 #include "private_data.h"
85 #include "protos.h"
86 #include "shared_data.h"
87 #include "sort.h"
88 #include "status.h"
89 #ifdef USE_NOTMUCH
90 #include "notmuch/lib.h"
91 #endif
92 #ifdef USE_NNTP
93 #include "nntp/lib.h"
94 #include "nntp/adata.h" // IWYU pragma: keep
95 #include "nntp/mdata.h" // IWYU pragma: keep
96 #endif
97 #ifdef USE_INOTIFY
98 #include "monitor.h"
99 #endif
100
101 /// Help Bar for the Index dialog
102 static const struct Mapping IndexHelp[] = {
103 // clang-format off
104 { N_("Quit"), OP_QUIT },
105 { N_("Del"), OP_DELETE },
106 { N_("Undel"), OP_UNDELETE },
107 { N_("Save"), OP_SAVE },
108 { N_("Mail"), OP_MAIL },
109 { N_("Reply"), OP_REPLY },
110 { N_("Group"), OP_GROUP_REPLY },
111 { N_("Help"), OP_HELP },
112 { NULL, 0 },
113 // clang-format on
114 };
115
116 #ifdef USE_NNTP
117 /// Help Bar for the News Index dialog
118 const struct Mapping IndexNewsHelp[] = {
119 // clang-format off
120 { N_("Quit"), OP_QUIT },
121 { N_("Del"), OP_DELETE },
122 { N_("Undel"), OP_UNDELETE },
123 { N_("Save"), OP_SAVE },
124 { N_("Post"), OP_POST },
125 { N_("Followup"), OP_FOLLOWUP },
126 { N_("Catchup"), OP_CATCHUP },
127 { N_("Help"), OP_HELP },
128 { NULL, 0 },
129 // clang-format on
130 };
131 #endif
132
133 /**
134 * check_acl - Check the ACLs for a function
135 * @param m Mailbox
136 * @param acl ACL, see #AclFlags
137 * @param msg Error message for failure
138 * @retval true The function is permitted
139 */
check_acl(struct Mailbox * m,AclFlags acl,const char * msg)140 bool check_acl(struct Mailbox *m, AclFlags acl, const char *msg)
141 {
142 if (!m)
143 return false;
144
145 if (!(m->rights & acl))
146 {
147 /* L10N: %s is one of the CHECK_ACL entries below. */
148 mutt_error(_("%s: Operation not permitted by ACL"), msg);
149 return false;
150 }
151
152 return true;
153 }
154
155 /**
156 * collapse_all - Collapse/uncollapse all threads
157 * @param ctx Context
158 * @param menu current menu
159 * @param toggle toggle collapsed state
160 *
161 * This function is called by the OP_MAIN_COLLAPSE_ALL command and on folder
162 * enter if the `$collapse_all` option is set. In the first case, the @a toggle
163 * parameter is 1 to actually toggle collapsed/uncollapsed state on all
164 * threads. In the second case, the @a toggle parameter is 0, actually turning
165 * this function into a one-way collapse.
166 */
collapse_all(struct Context * ctx,struct Menu * menu,int toggle)167 void collapse_all(struct Context *ctx, struct Menu *menu, int toggle)
168 {
169 if (!ctx || !ctx->mailbox || (ctx->mailbox->msg_count == 0) || !menu)
170 return;
171
172 struct Email *e_cur = mutt_get_virt_email(ctx->mailbox, menu_get_index(menu));
173 if (!e_cur)
174 return;
175
176 int final;
177
178 /* Figure out what the current message would be after folding / unfolding,
179 * so that we can restore the cursor in a sane way afterwards. */
180 if (e_cur->collapsed && toggle)
181 final = mutt_uncollapse_thread(e_cur);
182 else if (mutt_thread_can_collapse(e_cur))
183 final = mutt_collapse_thread(e_cur);
184 else
185 final = e_cur->vnum;
186
187 if (final == -1)
188 return;
189
190 struct Email *base = mutt_get_virt_email(ctx->mailbox, final);
191 if (!base)
192 return;
193
194 /* Iterate all threads, perform collapse/uncollapse as needed */
195 ctx->collapsed = toggle ? !ctx->collapsed : true;
196 mutt_thread_collapse(ctx->threads, ctx->collapsed);
197
198 /* Restore the cursor */
199 mutt_set_vnum(ctx->mailbox);
200 menu->max = ctx->mailbox->vcount;
201 for (int i = 0; i < ctx->mailbox->vcount; i++)
202 {
203 struct Email *e = mutt_get_virt_email(ctx->mailbox, i);
204 if (!e)
205 break;
206 if (e->index == base->index)
207 {
208 menu_set_index(menu, i);
209 break;
210 }
211 }
212
213 menu_queue_redraw(menu, MENU_REDRAW_INDEX);
214 }
215
216 /**
217 * ci_next_undeleted - Find the next undeleted email
218 * @param m Mailbox
219 * @param msgno Message number to start at
220 * @retval >=0 Message number of next undeleted email
221 * @retval -1 No more undeleted messages
222 */
ci_next_undeleted(struct Mailbox * m,int msgno)223 int ci_next_undeleted(struct Mailbox *m, int msgno)
224 {
225 if (!m)
226 return -1;
227
228 for (int i = msgno + 1; i < m->vcount; i++)
229 {
230 struct Email *e = mutt_get_virt_email(m, i);
231 if (!e)
232 continue;
233 if (!e->deleted)
234 return i;
235 }
236 return -1;
237 }
238
239 /**
240 * ci_previous_undeleted - Find the previous undeleted email
241 * @param m Mailbox
242 * @param msgno Message number to start at
243 * @retval >=0 Message number of next undeleted email
244 * @retval -1 No more undeleted messages
245 */
ci_previous_undeleted(struct Mailbox * m,int msgno)246 int ci_previous_undeleted(struct Mailbox *m, int msgno)
247 {
248 if (!m)
249 return -1;
250
251 for (int i = msgno - 1; i >= 0; i--)
252 {
253 struct Email *e = mutt_get_virt_email(m, i);
254 if (!e)
255 continue;
256 if (!e->deleted)
257 return i;
258 }
259 return -1;
260 }
261
262 /**
263 * ci_first_message - Get index of first new message
264 * @param m Mailbox
265 * @retval num Index of first new message
266 *
267 * Return the index of the first new message, or failing that, the first
268 * unread message.
269 */
ci_first_message(struct Mailbox * m)270 int ci_first_message(struct Mailbox *m)
271 {
272 if (!m || (m->msg_count == 0))
273 return 0;
274
275 int old = -1;
276 for (int i = 0; i < m->vcount; i++)
277 {
278 struct Email *e = mutt_get_virt_email(m, i);
279 if (!e)
280 continue;
281 if (!e->read && !e->deleted)
282 {
283 if (!e->old)
284 return i;
285 if (old == -1)
286 old = i;
287 }
288 }
289 if (old != -1)
290 return old;
291
292 /* If `$use_threads` is not threaded and `$sort` is reverse, the latest
293 * message is first. Otherwise, the latest message is first if exactly
294 * one of `$use_threads` and `$sort` are reverse.
295 */
296 short c_sort = cs_subset_sort(m->sub, "sort");
297 if ((c_sort & SORT_MASK) == SORT_THREADS)
298 c_sort = cs_subset_sort(m->sub, "sort_aux");
299 bool reverse = false;
300 switch (mutt_thread_style())
301 {
302 case UT_FLAT:
303 reverse = c_sort & SORT_REVERSE;
304 break;
305 case UT_THREADS:
306 reverse = c_sort & SORT_REVERSE;
307 break;
308 case UT_REVERSE:
309 reverse = !(c_sort & SORT_REVERSE);
310 break;
311 default:
312 assert(false);
313 }
314
315 if (reverse || (m->vcount == 0))
316 return 0;
317
318 return m->vcount - 1;
319 }
320
321 /**
322 * resort_index - Resort the index
323 * @param ctx Context
324 * @param menu Current Menu
325 */
resort_index(struct Context * ctx,struct Menu * menu)326 void resort_index(struct Context *ctx, struct Menu *menu)
327 {
328 if (!ctx || !ctx->mailbox || !menu)
329 return;
330
331 struct Mailbox *m = ctx->mailbox;
332 const int old_index = menu_get_index(menu);
333 struct Email *e_cur = mutt_get_virt_email(m, old_index);
334
335 int new_index = -1;
336 mutt_sort_headers(m, ctx->threads, false, &ctx->vsize);
337 /* Restore the current message */
338
339 for (int i = 0; i < m->vcount; i++)
340 {
341 struct Email *e = mutt_get_virt_email(m, i);
342 if (!e)
343 continue;
344 if (e == e_cur)
345 {
346 new_index = i;
347 break;
348 }
349 }
350
351 if (mutt_using_threads() && (old_index < 0))
352 new_index = mutt_parent_message(e_cur, false);
353
354 if (old_index < 0)
355 new_index = ci_first_message(m);
356
357 menu_set_index(menu, new_index);
358 menu_queue_redraw(menu, MENU_REDRAW_INDEX);
359 }
360
361 /**
362 * update_index_threaded - Update the index (if threaded)
363 * @param ctx Mailbox
364 * @param check Flags, e.g. #MX_STATUS_REOPENED
365 * @param oldcount How many items are currently in the index
366 */
update_index_threaded(struct Context * ctx,enum MxStatus check,int oldcount)367 static void update_index_threaded(struct Context *ctx, enum MxStatus check, int oldcount)
368 {
369 struct Email **save_new = NULL;
370 const bool lmt = ctx_has_limit(ctx);
371
372 struct Mailbox *m = ctx->mailbox;
373 int num_new = MAX(0, m->msg_count - oldcount);
374
375 const bool c_uncollapse_new = cs_subset_bool(m->sub, "uncollapse_new");
376 /* save the list of new messages */
377 if ((check != MX_STATUS_REOPENED) && (oldcount > 0) &&
378 (lmt || c_uncollapse_new) && (num_new > 0))
379 {
380 save_new = mutt_mem_malloc(num_new * sizeof(struct Email *));
381 for (int i = oldcount; i < m->msg_count; i++)
382 save_new[i - oldcount] = m->emails[i];
383 }
384
385 /* Sort first to thread the new messages, because some patterns
386 * require the threading information.
387 *
388 * If the mailbox was reopened, need to rethread from scratch. */
389 mutt_sort_headers(m, ctx->threads, (check == MX_STATUS_REOPENED), &ctx->vsize);
390
391 if (lmt)
392 {
393 /* Because threading changes the order in m->emails, we don't
394 * know which emails are new. Hence, we need to re-apply the limit to the
395 * whole set.
396 */
397 for (int i = 0; i < m->msg_count; i++)
398 {
399 struct Email *e = m->emails[i];
400 if ((e->vnum != -1) || mutt_pattern_exec(SLIST_FIRST(ctx->limit_pattern),
401 MUTT_MATCH_FULL_ADDRESS, m, e, NULL))
402 {
403 /* vnum will get properly set by mutt_set_vnum(), which
404 * is called by mutt_sort_headers() just below. */
405 e->vnum = 1;
406 e->visible = true;
407 }
408 else
409 {
410 e->vnum = -1;
411 e->visible = false;
412 }
413 }
414 /* Need a second sort to set virtual numbers and redraw the tree */
415 mutt_sort_headers(m, ctx->threads, false, &ctx->vsize);
416 }
417
418 /* uncollapse threads with new mail */
419 if (c_uncollapse_new)
420 {
421 if (check == MX_STATUS_REOPENED)
422 {
423 ctx->collapsed = false;
424 mutt_thread_collapse(ctx->threads, ctx->collapsed);
425 mutt_set_vnum(m);
426 }
427 else if (oldcount > 0)
428 {
429 for (int j = 0; j < num_new; j++)
430 {
431 if (save_new[j]->visible)
432 {
433 mutt_uncollapse_thread(save_new[j]);
434 }
435 }
436 mutt_set_vnum(m);
437 }
438 }
439
440 FREE(&save_new);
441 }
442
443 /**
444 * update_index_unthreaded - Update the index (if unthreaded)
445 * @param ctx Mailbox
446 * @param check Flags, e.g. #MX_STATUS_REOPENED
447 */
update_index_unthreaded(struct Context * ctx,enum MxStatus check)448 static void update_index_unthreaded(struct Context *ctx, enum MxStatus check)
449 {
450 /* We are in a limited view. Check if the new message(s) satisfy
451 * the limit criteria. If they do, set their virtual msgno so that
452 * they will be visible in the limited view */
453 if (ctx_has_limit(ctx))
454 {
455 int padding = mx_msg_padding_size(ctx->mailbox);
456 ctx->mailbox->vcount = ctx->vsize = 0;
457 for (int i = 0; i < ctx->mailbox->msg_count; i++)
458 {
459 struct Email *e = ctx->mailbox->emails[i];
460 if (!e)
461 break;
462 if (mutt_pattern_exec(SLIST_FIRST(ctx->limit_pattern),
463 MUTT_MATCH_FULL_ADDRESS, ctx->mailbox, e, NULL))
464 {
465 assert(ctx->mailbox->vcount < ctx->mailbox->msg_count);
466 e->vnum = ctx->mailbox->vcount;
467 ctx->mailbox->v2r[ctx->mailbox->vcount] = i;
468 e->visible = true;
469 ctx->mailbox->vcount++;
470 struct Body *b = e->body;
471 ctx->vsize += b->length + b->offset - b->hdr_offset + padding;
472 }
473 else
474 {
475 e->visible = false;
476 }
477 }
478 }
479
480 /* if the mailbox was reopened, need to rethread from scratch */
481 mutt_sort_headers(ctx->mailbox, ctx->threads, (check == MX_STATUS_REOPENED), &ctx->vsize);
482 }
483
484 /**
485 * update_index - Update the index
486 * @param menu Current Menu
487 * @param ctx Mailbox
488 * @param check Flags, e.g. #MX_STATUS_REOPENED
489 * @param oldcount How many items are currently in the index
490 * @param shared Shared Index data
491 */
update_index(struct Menu * menu,struct Context * ctx,enum MxStatus check,int oldcount,const struct IndexSharedData * shared)492 void update_index(struct Menu *menu, struct Context *ctx, enum MxStatus check,
493 int oldcount, const struct IndexSharedData *shared)
494 {
495 if (!menu || !ctx)
496 return;
497
498 struct Mailbox *m = ctx->mailbox;
499 if (mutt_using_threads())
500 update_index_threaded(ctx, check, oldcount);
501 else
502 update_index_unthreaded(ctx, check);
503
504 const int old_index = menu_get_index(menu);
505 int index = -1;
506 if (oldcount)
507 {
508 /* restore the current message to the message it was pointing to */
509 for (int i = 0; i < m->vcount; i++)
510 {
511 struct Email *e = mutt_get_virt_email(m, i);
512 if (!e)
513 continue;
514 if (index_shared_data_is_cur_email(shared, e))
515 {
516 index = i;
517 break;
518 }
519 }
520 }
521
522 if (index < 0)
523 {
524 index = (old_index < m->vcount) ? old_index : ci_first_message(m);
525 }
526 menu_set_index(menu, index);
527 }
528
529 /**
530 * mutt_update_index - Update the index
531 * @param menu Current Menu
532 * @param ctx Mailbox
533 * @param check Flags, e.g. #MX_STATUS_REOPENED
534 * @param oldcount How many items are currently in the index
535 * @param shared Shared Index data
536 */
mutt_update_index(struct Menu * menu,struct Context * ctx,enum MxStatus check,int oldcount,struct IndexSharedData * shared)537 void mutt_update_index(struct Menu *menu, struct Context *ctx, enum MxStatus check,
538 int oldcount, struct IndexSharedData *shared)
539 {
540 update_index(menu, ctx, check, oldcount, shared);
541 }
542
543 /**
544 * index_mailbox_observer - Notification that a Mailbox has changed - Implements ::observer_t - @ingroup observer_api
545 *
546 * If a Mailbox is closed, then set a pointer to NULL.
547 */
index_mailbox_observer(struct NotifyCallback * nc)548 static int index_mailbox_observer(struct NotifyCallback *nc)
549 {
550 if ((nc->event_type != NT_MAILBOX) || !nc->global_data)
551 return -1;
552
553 if (nc->event_subtype != NT_MAILBOX_DELETE)
554 return 0;
555
556 struct Mailbox **ptr = nc->global_data;
557 if (!ptr || !*ptr)
558 return 0;
559
560 *ptr = NULL;
561 mutt_debug(LL_DEBUG5, "mailbox done\n");
562 return 0;
563 }
564
565 /**
566 * change_folder_mailbox - Change to a different Mailbox by pointer
567 * @param menu Current Menu
568 * @param m Mailbox
569 * @param oldcount How many items are currently in the index
570 * @param shared Shared Index data
571 * @param read_only Open Mailbox in read-only mode
572 */
change_folder_mailbox(struct Menu * menu,struct Mailbox * m,int * oldcount,struct IndexSharedData * shared,bool read_only)573 void change_folder_mailbox(struct Menu *menu, struct Mailbox *m, int *oldcount,
574 struct IndexSharedData *shared, bool read_only)
575 {
576 if (!m)
577 return;
578
579 /* keepalive failure in mutt_enter_fname may kill connection. */
580 if (shared->mailbox && (mutt_buffer_is_empty(&shared->mailbox->pathbuf)))
581 {
582 ctx_free(&shared->ctx);
583 if (shared->mailbox->flags == MB_HIDDEN)
584 mailbox_free(&shared->mailbox);
585 }
586
587 if (shared->mailbox)
588 {
589 char *new_last_folder = NULL;
590 #ifdef USE_INOTIFY
591 int monitor_remove_rc = mutt_monitor_remove(NULL);
592 #endif
593 #ifdef USE_COMP_MBOX
594 if (shared->mailbox->compress_info && (shared->mailbox->realpath[0] != '\0'))
595 new_last_folder = mutt_str_dup(shared->mailbox->realpath);
596 else
597 #endif
598 new_last_folder = mutt_str_dup(mailbox_path(shared->mailbox));
599 *oldcount = shared->mailbox->msg_count;
600
601 const enum MxStatus check = mx_mbox_close(shared->mailbox);
602 if (check == MX_STATUS_OK)
603 {
604 ctx_free(&shared->ctx);
605 if ((shared->mailbox != m) && (shared->mailbox->flags == MB_HIDDEN))
606 mailbox_free(&shared->mailbox);
607 }
608 else
609 {
610 #ifdef USE_INOTIFY
611 if (monitor_remove_rc == 0)
612 mutt_monitor_add(NULL);
613 #endif
614 if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED))
615 update_index(menu, shared->ctx, check, *oldcount, shared);
616
617 FREE(&new_last_folder);
618 OptSearchInvalid = true;
619 menu_queue_redraw(menu, MENU_REDRAW_INDEX);
620 return;
621 }
622 FREE(&LastFolder);
623 LastFolder = new_last_folder;
624 }
625 mutt_str_replace(&CurrentFolder, mailbox_path(m));
626
627 /* If the `folder-hook` were to call `unmailboxes`, then the Mailbox (`m`)
628 * could be deleted, leaving `m` dangling. */
629 // TODO: Refactor this function to avoid the need for an observer
630 notify_observer_add(m->notify, NT_MAILBOX, index_mailbox_observer, &m);
631 char *dup_path = mutt_str_dup(mailbox_path(m));
632 char *dup_name = mutt_str_dup(m->name);
633
634 mutt_folder_hook(dup_path, dup_name);
635 if (m)
636 {
637 /* `m` is still valid, but we won't need the observer again before the end
638 * of the function. */
639 notify_observer_remove(m->notify, index_mailbox_observer, &m);
640 }
641 else
642 {
643 // Recreate the Mailbox as the folder-hook might have invoked `mailboxes`
644 // and/or `unmailboxes`.
645 m = mx_path_resolve(dup_path);
646 }
647
648 FREE(&dup_path);
649 FREE(&dup_name);
650
651 if (!m)
652 return;
653
654 const OpenMailboxFlags flags = read_only ? MUTT_READONLY : MUTT_OPEN_NO_FLAGS;
655 if (mx_mbox_open(m, flags))
656 {
657 struct Context *ctx = ctx_new(m);
658 index_shared_data_set_context(shared, ctx);
659
660 menu->max = m->msg_count;
661 menu_set_index(menu, ci_first_message(shared->mailbox));
662 #ifdef USE_INOTIFY
663 mutt_monitor_add(NULL);
664 #endif
665 }
666 else
667 {
668 index_shared_data_set_context(shared, NULL);
669 menu_set_index(menu, 0);
670 }
671
672 const bool c_collapse_all = cs_subset_bool(shared->sub, "collapse_all");
673 if (mutt_using_threads() && c_collapse_all)
674 collapse_all(shared->ctx, menu, 0);
675
676 struct MuttWindow *dlg = dialog_find(menu->win);
677 struct EventMailbox ev_m = { shared->mailbox };
678 mutt_debug(LL_NOTIFY, "NT_MAILBOX_SWITCH: %p\n", shared->mailbox);
679 notify_send(dlg->notify, NT_MAILBOX, NT_MAILBOX_SWITCH, &ev_m);
680
681 mutt_clear_error();
682 /* force the mailbox check after we have changed the folder */
683 mutt_mailbox_check(ev_m.mailbox, MUTT_MAILBOX_CHECK_FORCE);
684 menu_queue_redraw(menu, MENU_REDRAW_FULL);
685 OptSearchInvalid = true;
686 }
687
688 #ifdef USE_NOTMUCH
689 /**
690 * change_folder_notmuch - Change to a different Notmuch Mailbox by string
691 * @param menu Current Menu
692 * @param buf Folder to change to
693 * @param buflen Length of buffer
694 * @param oldcount How many items are currently in the index
695 * @param shared Shared Index data
696 * @param read_only Open Mailbox in read-only mode
697 */
change_folder_notmuch(struct Menu * menu,char * buf,int buflen,int * oldcount,struct IndexSharedData * shared,bool read_only)698 struct Mailbox *change_folder_notmuch(struct Menu *menu, char *buf, int buflen, int *oldcount,
699 struct IndexSharedData *shared, bool read_only)
700 {
701 if (!nm_url_from_query(NULL, buf, buflen))
702 {
703 mutt_message(_("Failed to create query, aborting"));
704 return NULL;
705 }
706
707 struct Mailbox *m_query = mx_path_resolve(buf);
708 change_folder_mailbox(menu, m_query, oldcount, shared, read_only);
709 return m_query;
710 }
711 #endif
712
713 /**
714 * change_folder_string - Change to a different Mailbox by string
715 * @param menu Current Menu
716 * @param buf Folder to change to
717 * @param buflen Length of buffer
718 * @param oldcount How many items are currently in the index
719 * @param shared Shared Index data
720 * @param pager_return Return to the pager afterwards
721 * @param read_only Open Mailbox in read-only mode
722 */
change_folder_string(struct Menu * menu,char * buf,size_t buflen,int * oldcount,struct IndexSharedData * shared,bool * pager_return,bool read_only)723 void change_folder_string(struct Menu *menu, char *buf, size_t buflen, int *oldcount,
724 struct IndexSharedData *shared, bool *pager_return, bool read_only)
725 {
726 #ifdef USE_NNTP
727 if (OptNews)
728 {
729 OptNews = false;
730 nntp_expand_path(buf, buflen, &CurrentNewsSrv->conn->account);
731 }
732 else
733 #endif
734 {
735 const char *const c_folder = cs_subset_string(shared->sub, "folder");
736 mx_path_canon(buf, buflen, c_folder, NULL);
737 }
738
739 enum MailboxType type = mx_path_probe(buf);
740 if ((type == MUTT_MAILBOX_ERROR) || (type == MUTT_UNKNOWN))
741 {
742 // Look for a Mailbox by its description, before failing
743 struct Mailbox *m = mailbox_find_name(buf);
744 if (m)
745 {
746 change_folder_mailbox(menu, m, oldcount, shared, read_only);
747 *pager_return = false;
748 }
749 else
750 mutt_error(_("%s is not a mailbox"), buf);
751 return;
752 }
753
754 /* past this point, we don't return to the pager on error */
755 *pager_return = false;
756
757 struct Mailbox *m = mx_path_resolve(buf);
758 change_folder_mailbox(menu, m, oldcount, shared, read_only);
759 }
760
761 /**
762 * index_make_entry - Format a menu item for the index list - Implements Menu::make_entry() - @ingroup menu_make_entry
763 */
index_make_entry(struct Menu * menu,char * buf,size_t buflen,int line)764 void index_make_entry(struct Menu *menu, char *buf, size_t buflen, int line)
765 {
766 buf[0] = '\0';
767
768 if (!menu || !menu->mdata)
769 return;
770
771 struct IndexPrivateData *priv = menu->mdata;
772 struct IndexSharedData *shared = priv->shared;
773 struct Mailbox *m = shared->mailbox;
774
775 if (!m || (line < 0) || (line >= m->email_max))
776 return;
777
778 struct Email *e = mutt_get_virt_email(m, line);
779 if (!e)
780 return;
781
782 MuttFormatFlags flags = MUTT_FORMAT_ARROWCURSOR | MUTT_FORMAT_INDEX;
783 struct MuttThread *tmp = NULL;
784
785 const enum UseThreads c_threads = mutt_thread_style();
786 if ((c_threads > UT_FLAT) && e->tree)
787 {
788 flags |= MUTT_FORMAT_TREE; /* display the thread tree */
789 if (e->display_subject)
790 flags |= MUTT_FORMAT_FORCESUBJ;
791 else
792 {
793 const bool reverse = c_threads == UT_REVERSE;
794 int edgemsgno;
795 if (reverse)
796 {
797 if (menu->top + menu->pagelen > menu->max)
798 edgemsgno = m->v2r[menu->max - 1];
799 else
800 edgemsgno = m->v2r[menu->top + menu->pagelen - 1];
801 }
802 else
803 edgemsgno = m->v2r[menu->top];
804
805 for (tmp = e->thread->parent; tmp; tmp = tmp->parent)
806 {
807 if (!tmp->message)
808 continue;
809
810 /* if no ancestor is visible on current screen, provisionally force
811 * subject... */
812 if (reverse ? (tmp->message->msgno > edgemsgno) : (tmp->message->msgno < edgemsgno))
813 {
814 flags |= MUTT_FORMAT_FORCESUBJ;
815 break;
816 }
817 else if (tmp->message->vnum >= 0)
818 break;
819 }
820 if (flags & MUTT_FORMAT_FORCESUBJ)
821 {
822 for (tmp = e->thread->prev; tmp; tmp = tmp->prev)
823 {
824 if (!tmp->message)
825 continue;
826
827 /* ...but if a previous sibling is available, don't force it */
828 if (reverse ? (tmp->message->msgno > edgemsgno) : (tmp->message->msgno < edgemsgno))
829 break;
830 else if (tmp->message->vnum >= 0)
831 {
832 flags &= ~MUTT_FORMAT_FORCESUBJ;
833 break;
834 }
835 }
836 }
837 }
838 }
839
840 const char *const c_index_format =
841 cs_subset_string(shared->sub, "index_format");
842 mutt_make_string(buf, buflen, menu->win->state.cols, NONULL(c_index_format),
843 m, shared->ctx->msg_in_pager, e, flags, NULL);
844 }
845
846 /**
847 * index_color - Calculate the colour for a line of the index - Implements Menu::color() - @ingroup menu_color
848 */
index_color(struct Menu * menu,int line)849 int index_color(struct Menu *menu, int line)
850 {
851 struct IndexPrivateData *priv = menu->mdata;
852 struct IndexSharedData *shared = priv->shared;
853 struct Mailbox *m = shared->mailbox;
854 if (!m || (line < 0))
855 return 0;
856
857 struct Email *e = mutt_get_virt_email(m, line);
858 if (!e)
859 return 0;
860
861 if (e->pair)
862 return e->pair;
863
864 mutt_set_header_color(m, e);
865 return e->pair;
866 }
867
868 /**
869 * mutt_draw_statusline - Draw a highlighted status bar
870 * @param win Window
871 * @param cols Maximum number of screen columns
872 * @param buf Message to be displayed
873 * @param buflen Length of the buffer
874 *
875 * Users configure the highlighting of the status bar, e.g.
876 * color status red default "[0-9][0-9]:[0-9][0-9]"
877 *
878 * Where regexes overlap, the one nearest the start will be used.
879 * If two regexes start at the same place, the longer match will be used.
880 */
mutt_draw_statusline(struct MuttWindow * win,int cols,const char * buf,size_t buflen)881 void mutt_draw_statusline(struct MuttWindow *win, int cols, const char *buf, size_t buflen)
882 {
883 if (!buf || !stdscr)
884 return;
885
886 size_t i = 0;
887 size_t offset = 0;
888 bool found = false;
889 size_t chunks = 0;
890 size_t len = 0;
891
892 /**
893 * struct StatusSyntax - Colours of the status bar
894 */
895 struct StatusSyntax
896 {
897 int color; ///< Colour pair
898 int first; ///< First character of that colour
899 int last; ///< Last character of that colour
900 } *syntax = NULL;
901
902 do
903 {
904 struct RegexColor *cl = NULL;
905 found = false;
906
907 if (!buf[offset])
908 break;
909
910 /* loop through each "color status regex" */
911 STAILQ_FOREACH(cl, regex_colors_get_list(MT_COLOR_STATUS), entries)
912 {
913 regmatch_t pmatch[cl->match + 1];
914
915 if (regexec(&cl->regex, buf + offset, cl->match + 1, pmatch, 0) != 0)
916 continue; /* regex doesn't match the status bar */
917
918 int first = pmatch[cl->match].rm_so + offset;
919 int last = pmatch[cl->match].rm_eo + offset;
920
921 if (first == last)
922 continue; /* ignore an empty regex */
923
924 if (!found)
925 {
926 chunks++;
927 mutt_mem_realloc(&syntax, chunks * sizeof(struct StatusSyntax));
928 }
929
930 i = chunks - 1;
931 if (!found || (first < syntax[i].first) ||
932 ((first == syntax[i].first) && (last > syntax[i].last)))
933 {
934 syntax[i].color = cl->pair;
935 syntax[i].first = first;
936 syntax[i].last = last;
937 }
938 found = true;
939 }
940
941 if (syntax)
942 {
943 offset = syntax[i].last;
944 }
945 } while (found);
946
947 /* Only 'len' bytes will fit into 'cols' screen columns */
948 len = mutt_wstr_trunc(buf, buflen, cols, NULL);
949
950 offset = 0;
951
952 if ((chunks > 0) && (syntax[0].first > 0))
953 {
954 /* Text before the first highlight */
955 mutt_window_addnstr(win, buf, MIN(len, syntax[0].first));
956 mutt_curses_set_color_by_id(MT_COLOR_STATUS);
957 if (len <= syntax[0].first)
958 goto dsl_finish; /* no more room */
959
960 offset = syntax[0].first;
961 }
962
963 for (i = 0; i < chunks; i++)
964 {
965 /* Highlighted text */
966 mutt_curses_set_attr(syntax[i].color);
967 mutt_window_addnstr(win, buf + offset, MIN(len, syntax[i].last) - offset);
968 if (len <= syntax[i].last)
969 goto dsl_finish; /* no more room */
970
971 size_t next;
972 if ((i + 1) == chunks)
973 {
974 next = len;
975 }
976 else
977 {
978 next = MIN(len, syntax[i + 1].first);
979 }
980
981 mutt_curses_set_color_by_id(MT_COLOR_STATUS);
982 offset = syntax[i].last;
983 mutt_window_addnstr(win, buf + offset, next - offset);
984
985 offset = next;
986 if (offset >= len)
987 goto dsl_finish; /* no more room */
988 }
989
990 mutt_curses_set_color_by_id(MT_COLOR_STATUS);
991 if (offset < len)
992 {
993 /* Text after the last highlight */
994 mutt_window_addnstr(win, buf + offset, len - offset);
995 }
996
997 int width = mutt_strwidth(buf);
998 if (width < cols)
999 {
1000 /* Pad the rest of the line with whitespace */
1001 mutt_paddstr(win, cols - width, "");
1002 }
1003 dsl_finish:
1004 FREE(&syntax);
1005 }
1006
1007 /**
1008 * index_custom_redraw - Redraw the index - Implements Menu::custom_redraw() - @ingroup menu_custom_redraw
1009 */
index_custom_redraw(struct Menu * menu)1010 static void index_custom_redraw(struct Menu *menu)
1011 {
1012 if (menu->redraw & MENU_REDRAW_FULL)
1013 menu_redraw_full(menu);
1014
1015 struct IndexPrivateData *priv = menu->mdata;
1016 struct IndexSharedData *shared = priv->shared;
1017 struct Mailbox *m = shared->mailbox;
1018 const int index = menu_get_index(menu);
1019 if (m && m->emails && (index < m->vcount))
1020 {
1021 if (menu->redraw & MENU_REDRAW_INDEX)
1022 {
1023 menu_redraw_index(menu);
1024 }
1025 else if (menu->redraw & MENU_REDRAW_MOTION)
1026 menu_redraw_motion(menu);
1027 else if (menu->redraw & MENU_REDRAW_CURRENT)
1028 menu_redraw_current(menu);
1029 }
1030
1031 menu->redraw = MENU_REDRAW_NO_FLAGS;
1032 mutt_debug(LL_DEBUG5, "repaint done\n");
1033 }
1034
1035 /**
1036 * mutt_index_menu - Display a list of emails
1037 * @param dlg Dialog containing Windows to draw on
1038 * @param m_init Initial mailbox
1039 * @retval Mailbox open in the index
1040 *
1041 * This function handles the message index window as well as commands returned
1042 * from the pager (MENU_PAGER).
1043 */
mutt_index_menu(struct MuttWindow * dlg,struct Mailbox * m_init)1044 struct Mailbox *mutt_index_menu(struct MuttWindow *dlg, struct Mailbox *m_init)
1045 {
1046 struct Context *ctx_old = Context;
1047 struct IndexSharedData *shared = dlg->wdata;
1048 index_shared_data_set_context(shared, ctx_new(m_init));
1049
1050 struct MuttWindow *panel_index = window_find_child(dlg, WT_INDEX);
1051 struct MuttWindow *panel_pager = window_find_child(dlg, WT_PAGER);
1052
1053 struct IndexPrivateData *priv = panel_index->wdata;
1054 priv->attach_msg = OptAttachMsg;
1055 priv->win_index = window_find_child(panel_index, WT_MENU);
1056 priv->win_ibar = window_find_child(panel_index, WT_STATUS_BAR);
1057 priv->win_pager = window_find_child(panel_pager, WT_CUSTOM);
1058 priv->win_pbar = window_find_child(panel_pager, WT_STATUS_BAR);
1059
1060 int op = OP_NULL;
1061
1062 #ifdef USE_NNTP
1063 if (shared->mailbox && (shared->mailbox->type == MUTT_NNTP))
1064 dlg->help_data = IndexNewsHelp;
1065 else
1066 #endif
1067 dlg->help_data = IndexHelp;
1068 dlg->help_menu = MENU_MAIN;
1069
1070 priv->menu = priv->win_index->wdata;
1071 priv->menu->make_entry = index_make_entry;
1072 priv->menu->color = index_color;
1073 priv->menu->custom_redraw = index_custom_redraw;
1074 priv->menu->max = shared->mailbox ? shared->mailbox->vcount : 0;
1075 menu_set_index(priv->menu, ci_first_message(shared->mailbox));
1076 mutt_window_reflow(NULL);
1077
1078 if (!priv->attach_msg)
1079 {
1080 /* force the mailbox check after we enter the folder */
1081 mutt_mailbox_check(shared->mailbox, MUTT_MAILBOX_CHECK_FORCE);
1082 }
1083 #ifdef USE_INOTIFY
1084 mutt_monitor_add(NULL);
1085 #endif
1086
1087 {
1088 const bool c_collapse_all = cs_subset_bool(shared->sub, "collapse_all");
1089 if (mutt_using_threads() && c_collapse_all)
1090 {
1091 collapse_all(shared->ctx, priv->menu, 0);
1092 menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1093 }
1094 }
1095
1096 while (true)
1097 {
1098 /* Clear the tag prefix unless we just started it. Don't clear
1099 * the prefix on a timeout (op==-2), but do clear on an abort (op==-1) */
1100 if (priv->tag && (op != OP_TAG_PREFIX) && (op != OP_TAG_PREFIX_COND) && (op != -2))
1101 priv->tag = false;
1102
1103 /* check if we need to resort the index because just about
1104 * any 'op' below could do mutt_enter_command(), either here or
1105 * from any new priv->menu launched, and change $sort/$sort_aux */
1106 if (OptNeedResort && shared->mailbox && (shared->mailbox->msg_count != 0) &&
1107 (menu_get_index(priv->menu) >= 0))
1108 {
1109 resort_index(shared->ctx, priv->menu);
1110 }
1111
1112 priv->menu->max = shared->mailbox ? shared->mailbox->vcount : 0;
1113 priv->oldcount = shared->mailbox ? shared->mailbox->msg_count : 0;
1114
1115 {
1116 if (OptRedrawTree && shared->mailbox &&
1117 (shared->mailbox->msg_count != 0) && mutt_using_threads())
1118 {
1119 mutt_draw_tree(shared->ctx->threads);
1120 OptRedrawTree = false;
1121 }
1122 }
1123
1124 if (shared->mailbox)
1125 {
1126 mailbox_gc_run();
1127
1128 shared->ctx->menu = priv->menu;
1129 /* check for new mail in the mailbox. If nonzero, then something has
1130 * changed about the file (either we got new mail or the file was
1131 * modified underneath us.) */
1132 enum MxStatus check = mx_mbox_check(shared->mailbox);
1133
1134 if (check == MX_STATUS_ERROR)
1135 {
1136 if (mutt_buffer_is_empty(&shared->mailbox->pathbuf))
1137 {
1138 /* fatal error occurred */
1139 ctx_free(&shared->ctx);
1140 menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1141 }
1142
1143 OptSearchInvalid = true;
1144 }
1145 else if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED) ||
1146 (check == MX_STATUS_FLAGS))
1147 {
1148 /* notify the user of new mail */
1149 if (check == MX_STATUS_REOPENED)
1150 {
1151 mutt_error(
1152 _("Mailbox was externally modified. Flags may be wrong."));
1153 }
1154 else if (check == MX_STATUS_NEW_MAIL)
1155 {
1156 for (size_t i = 0; i < shared->mailbox->msg_count; i++)
1157 {
1158 const struct Email *e = shared->mailbox->emails[i];
1159 if (e && !e->read && !e->old)
1160 {
1161 mutt_message(_("New mail in this mailbox"));
1162 const bool c_beep_new = cs_subset_bool(shared->sub, "beep_new");
1163 if (c_beep_new)
1164 mutt_beep(true);
1165 const char *const c_new_mail_command =
1166 cs_subset_string(shared->sub, "new_mail_command");
1167 if (c_new_mail_command)
1168 {
1169 char cmd[1024];
1170 menu_status_line(cmd, sizeof(cmd), shared, priv->menu,
1171 sizeof(cmd), NONULL(c_new_mail_command));
1172 if (mutt_system(cmd) != 0)
1173 mutt_error(_("Error running \"%s\""), cmd);
1174 }
1175 break;
1176 }
1177 }
1178 }
1179 else if (check == MX_STATUS_FLAGS)
1180 {
1181 mutt_message(_("Mailbox was externally modified"));
1182 }
1183
1184 /* avoid the message being overwritten by mailbox */
1185 priv->do_mailbox_notify = false;
1186
1187 bool verbose = shared->mailbox->verbose;
1188 shared->mailbox->verbose = false;
1189 update_index(priv->menu, shared->ctx, check, priv->oldcount, shared);
1190 shared->mailbox->verbose = verbose;
1191 priv->menu->max = shared->mailbox->vcount;
1192 menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1193 OptSearchInvalid = true;
1194 }
1195
1196 index_shared_data_set_email(
1197 shared, mutt_get_virt_email(shared->mailbox, menu_get_index(priv->menu)));
1198 }
1199
1200 if (!priv->attach_msg)
1201 {
1202 /* check for new mail in the incoming folders */
1203 priv->oldcount = priv->newcount;
1204 priv->newcount = mutt_mailbox_check(shared->mailbox, 0);
1205 if (priv->do_mailbox_notify)
1206 {
1207 if (mutt_mailbox_notify(shared->mailbox))
1208 {
1209 const bool c_beep_new = cs_subset_bool(shared->sub, "beep_new");
1210 if (c_beep_new)
1211 mutt_beep(true);
1212 const char *const c_new_mail_command =
1213 cs_subset_string(shared->sub, "new_mail_command");
1214 if (c_new_mail_command)
1215 {
1216 char cmd[1024];
1217 menu_status_line(cmd, sizeof(cmd), shared, priv->menu, sizeof(cmd),
1218 NONULL(c_new_mail_command));
1219 if (mutt_system(cmd) != 0)
1220 mutt_error(_("Error running \"%s\""), cmd);
1221 }
1222 }
1223 }
1224 else
1225 priv->do_mailbox_notify = true;
1226 }
1227
1228 if (op >= 0)
1229 mutt_curses_set_cursor(MUTT_CURSOR_INVISIBLE);
1230
1231 if (priv->in_pager)
1232 {
1233 mutt_curses_set_cursor(MUTT_CURSOR_VISIBLE); /* fallback from the pager */
1234 }
1235 else
1236 {
1237 index_custom_redraw(priv->menu);
1238 window_redraw(NULL);
1239
1240 /* give visual indication that the next command is a tag- command */
1241 if (priv->tag)
1242 msgwin_set_text(MT_COLOR_NORMAL, "tag-");
1243
1244 const bool c_arrow_cursor = cs_subset_bool(shared->sub, "arrow_cursor");
1245 const bool c_braille_friendly =
1246 cs_subset_bool(shared->sub, "braille_friendly");
1247 const int index = menu_get_index(priv->menu);
1248 if (c_arrow_cursor)
1249 {
1250 mutt_window_move(priv->menu->win, 2, index - priv->menu->top);
1251 }
1252 else if (c_braille_friendly)
1253 {
1254 mutt_window_move(priv->menu->win, 0, index - priv->menu->top);
1255 }
1256 else
1257 {
1258 mutt_window_move(priv->menu->win, priv->menu->win->state.cols - 1,
1259 index - priv->menu->top);
1260 }
1261 mutt_refresh();
1262
1263 if (SigWinch)
1264 {
1265 SigWinch = false;
1266 mutt_resize_screen();
1267 priv->menu->top = 0; /* so we scroll the right amount */
1268 /* force a real complete redraw. clrtobot() doesn't seem to be able
1269 * to handle every case without this. */
1270 clearok(stdscr, true);
1271 msgwin_clear_text();
1272 continue;
1273 }
1274
1275 window_redraw(NULL);
1276 op = km_dokey(MENU_MAIN);
1277
1278 /* either user abort or timeout */
1279 if (op < 0)
1280 {
1281 mutt_timeout_hook();
1282 if (priv->tag)
1283 msgwin_clear_text();
1284 continue;
1285 }
1286
1287 mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", OpStrings[op][0], op);
1288
1289 mutt_curses_set_cursor(MUTT_CURSOR_VISIBLE);
1290
1291 /* special handling for the priv->tag-prefix function */
1292 const bool c_auto_tag = cs_subset_bool(shared->sub, "auto_tag");
1293 if ((op == OP_TAG_PREFIX) || (op == OP_TAG_PREFIX_COND))
1294 {
1295 /* A second priv->tag-prefix command aborts */
1296 if (priv->tag)
1297 {
1298 priv->tag = false;
1299 msgwin_clear_text();
1300 continue;
1301 }
1302
1303 if (!shared->mailbox)
1304 {
1305 mutt_error(_("No mailbox is open"));
1306 continue;
1307 }
1308
1309 if (shared->mailbox->msg_tagged == 0)
1310 {
1311 if (op == OP_TAG_PREFIX)
1312 mutt_error(_("No tagged messages"));
1313 else if (op == OP_TAG_PREFIX_COND)
1314 {
1315 mutt_flush_macro_to_endcond();
1316 mutt_message(_("Nothing to do"));
1317 }
1318 continue;
1319 }
1320
1321 /* get the real command */
1322 priv->tag = true;
1323 continue;
1324 }
1325 else if (c_auto_tag && shared->mailbox && (shared->mailbox->msg_tagged != 0))
1326 {
1327 priv->tag = true;
1328 }
1329
1330 mutt_clear_error();
1331 }
1332
1333 #ifdef USE_NNTP
1334 OptNews = false; /* for any case */
1335 #endif
1336
1337 #ifdef USE_NOTMUCH
1338 nm_db_debug_check(shared->mailbox);
1339 #endif
1340
1341 int rc = index_function_dispatcher(priv->win_index, op);
1342
1343 if (rc == IR_CONTINUE)
1344 {
1345 op = OP_DISPLAY_MESSAGE;
1346 continue;
1347 }
1348
1349 if (rc > 0)
1350 {
1351 op = rc;
1352 continue;
1353 }
1354
1355 if ((rc == IR_UNKNOWN) && !priv->in_pager)
1356 km_error_key(MENU_MAIN);
1357
1358 #ifdef USE_NOTMUCH
1359 nm_db_debug_check(shared->mailbox);
1360 #endif
1361
1362 if (priv->in_pager)
1363 {
1364 mutt_clear_pager_position();
1365 priv->in_pager = false;
1366 menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1367 }
1368
1369 if (rc == IR_DONE)
1370 break;
1371 }
1372
1373 ctx_free(&shared->ctx);
1374 Context = ctx_old;
1375
1376 return shared->mailbox;
1377 }
1378
1379 /**
1380 * mutt_set_header_color - Select a colour for a message
1381 * @param m Mailbox
1382 * @param e Current Email
1383 */
mutt_set_header_color(struct Mailbox * m,struct Email * e)1384 void mutt_set_header_color(struct Mailbox *m, struct Email *e)
1385 {
1386 if (!e)
1387 return;
1388
1389 struct RegexColor *color = NULL;
1390 struct PatternCache cache = { 0 };
1391
1392 STAILQ_FOREACH(color, regex_colors_get_list(MT_COLOR_INDEX), entries)
1393 {
1394 if (mutt_pattern_exec(SLIST_FIRST(color->color_pattern),
1395 MUTT_MATCH_FULL_ADDRESS, m, e, &cache))
1396 {
1397 e->pair = color->pair;
1398 return;
1399 }
1400 }
1401 e->pair = simple_colors_get(MT_COLOR_NORMAL);
1402 }
1403
1404 /**
1405 * index_pager_init - Allocate the Windows for the Index/Pager
1406 * @retval ptr Dialog containing nested Windows
1407 */
index_pager_init(void)1408 struct MuttWindow *index_pager_init(void)
1409 {
1410 struct MuttWindow *dlg =
1411 mutt_window_new(WT_DLG_INDEX, MUTT_WIN_ORIENT_HORIZONTAL, MUTT_WIN_SIZE_MAXIMISE,
1412 MUTT_WIN_SIZE_UNLIMITED, MUTT_WIN_SIZE_UNLIMITED);
1413
1414 struct IndexSharedData *shared = index_shared_data_new();
1415 notify_set_parent(shared->notify, dlg->notify);
1416
1417 dlg->wdata = shared;
1418 dlg->wdata_free = index_shared_data_free;
1419
1420 const bool c_status_on_top = cs_subset_bool(NeoMutt->sub, "status_on_top");
1421
1422 struct MuttWindow *panel_index = ipanel_new(c_status_on_top, shared);
1423 struct MuttWindow *panel_pager = ppanel_new(c_status_on_top, shared);
1424
1425 mutt_window_add_child(dlg, panel_index);
1426 mutt_window_add_child(dlg, panel_pager);
1427
1428 dlg->focus = panel_index;
1429
1430 return dlg;
1431 }
1432