mutt stable branch with some hacks
1/* Copyright (C) 2004 Justin Hibbits <jrh29@po.cwru.edu>
2 * Copyright (C) 2004 Thomer M. Gil <mutt@thomer.com>
3 * Copyright (C) 2015-2016 Richard Russon <rich@flatcap.org>
4 * Copyright (C) 2016-2017 Kevin J. McCarthy <kevin@8t8.us>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
19 */
20
21#if HAVE_CONFIG_H
22# include "config.h"
23#endif
24
25#include "mutt.h"
26#include "buffy.h"
27#include "keymap.h"
28#include "mutt_curses.h"
29#include "mutt_menu.h"
30#include "sort.h"
31
32/* Previous values for some sidebar config */
33static short PreviousSort = SORT_ORDER; /* sidebar_sort_method */
34
35/**
36 * struct sidebar_entry - Info about folders in the sidebar
37 */
38typedef struct sidebar_entry
39{
40 char box[STRING]; /* formatted mailbox name */
41 BUFFY *buffy;
42 short is_hidden;
43} SBENTRY;
44
45static int EntryCount = 0;
46static int EntryLen = 0;
47static SBENTRY **Entries = NULL;
48
49static int TopIndex = -1; /* First mailbox visible in sidebar */
50static int OpnIndex = -1; /* Current (open) mailbox */
51static int HilIndex = -1; /* Highlighted mailbox */
52static int BotIndex = -1; /* Last mailbox visible in sidebar */
53
54static int select_next (void);
55
56
57/**
58 * cb_format_str - Create the string to show in the sidebar
59 * @dest: Buffer in which to save string
60 * @destlen: Buffer length
61 * @col: Starting column, UNUSED
62 * @op: printf-like operator, e.g. 'B'
63 * @src: printf-like format string
64 * @prefix: Field formatting string, UNUSED
65 * @ifstring: If condition is met, display this string
66 * @elsestring: Otherwise, display this string
67 * @data: Pointer to our sidebar_entry
68 * @flags: Format flags, e.g. MUTT_FORMAT_OPTIONAL
69 *
70 * cb_format_str is a callback function for mutt_FormatString. It understands
71 * six operators. '%B' : Mailbox name, '%F' : Number of flagged messages,
72 * '%N' : Number of new messages, '%S' : Size (total number of messages),
73 * '%!' : Icon denoting number of flagged messages.
74 * '%n' : N if folder has new mail, blank otherwise.
75 *
76 * Returns: src (unchanged)
77 */
78static const char *cb_format_str(char *dest, size_t destlen, size_t col, int cols, char op,
79 const char *src, const char *prefix, const char *ifstring,
80 const char *elsestring, unsigned long data, format_flag flags)
81{
82 SBENTRY *sbe = (SBENTRY *) data;
83 unsigned int optional;
84 char fmt[STRING];
85
86 if (!sbe || !dest)
87 return src;
88
89 dest[0] = 0; /* Just in case there's nothing to do */
90
91 BUFFY *b = sbe->buffy;
92 if (!b)
93 return src;
94
95 int c = Context && (mutt_strcmp (Context->realpath, b->realpath) == 0);
96
97 optional = flags & MUTT_FORMAT_OPTIONAL;
98
99 switch (op)
100 {
101 case 'B':
102 mutt_format_s (dest, destlen, prefix, sbe->box);
103 break;
104
105 case 'd':
106 if (!optional)
107 {
108 snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
109 snprintf (dest, destlen, fmt, c ? Context->deleted : 0);
110 }
111 else if ((c && Context->deleted == 0) || !c)
112 optional = 0;
113 break;
114
115 case 'F':
116 if (!optional)
117 {
118 snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
119 snprintf (dest, destlen, fmt, b->msg_flagged);
120 }
121 else if (b->msg_flagged == 0)
122 optional = 0;
123 break;
124
125 case 'L':
126 if (!optional)
127 {
128 snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
129 snprintf (dest, destlen, fmt, c ? Context->vcount : b->msg_count);
130 }
131 else if ((c && Context->vcount == b->msg_count) || !c)
132 optional = 0;
133 break;
134
135 case 'N':
136 if (!optional)
137 {
138 snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
139 snprintf (dest, destlen, fmt, b->msg_unread);
140 }
141 else if (b->msg_unread == 0)
142 optional = 0;
143 break;
144
145 case 'n':
146 if (!optional)
147 {
148 snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
149 snprintf (dest, destlen, fmt, b->new ? 'N' : ' ');
150 }
151 else if (b->new == 0)
152 optional = 0;
153 break;
154
155 case 'S':
156 if (!optional)
157 {
158 snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
159 snprintf (dest, destlen, fmt, b->msg_count);
160 }
161 else if (b->msg_count == 0)
162 optional = 0;
163 break;
164
165 case 't':
166 if (!optional)
167 {
168 snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
169 snprintf (dest, destlen, fmt, c ? Context->tagged : 0);
170 }
171 else if ((c && Context->tagged == 0) || !c)
172 optional = 0;
173 break;
174
175 case '!':
176 if (b->msg_flagged == 0)
177 mutt_format_s (dest, destlen, prefix, "");
178 else if (b->msg_flagged == 1)
179 mutt_format_s (dest, destlen, prefix, "!");
180 else if (b->msg_flagged == 2)
181 mutt_format_s (dest, destlen, prefix, "!!");
182 else
183 {
184 snprintf (fmt, sizeof (fmt), "%d!", b->msg_flagged);
185 mutt_format_s (dest, destlen, prefix, fmt);
186 }
187 break;
188 }
189
190 if (optional)
191 mutt_FormatString (dest, destlen, col, SidebarWidth, ifstring, cb_format_str, (unsigned long) sbe, flags);
192 else if (flags & MUTT_FORMAT_OPTIONAL)
193 mutt_FormatString (dest, destlen, col, SidebarWidth, elsestring, cb_format_str, (unsigned long) sbe, flags);
194
195 /* We return the format string, unchanged */
196 return src;
197}
198
199/**
200 * make_sidebar_entry - Turn mailbox data into a sidebar string
201 * @buf: Buffer in which to save string
202 * @buflen: Buffer length
203 * @width: Desired width in screen cells
204 * @box: Mailbox name
205 * @b: Mailbox object
206 *
207 * Take all the relevant mailbox data and the desired screen width and then get
208 * mutt_FormatString to do the actual work. mutt_FormatString will callback to
209 * us using cb_format_str() for the sidebar specific formatting characters.
210 */
211static void make_sidebar_entry (char *buf, unsigned int buflen, int width, const char *box,
212 SBENTRY *sbe)
213{
214 if (!buf || !box || !sbe)
215 return;
216
217 strfcpy (sbe->box, box, sizeof (sbe->box));
218
219 mutt_FormatString (buf, buflen, 0, width, NONULL(SidebarFormat), cb_format_str, (unsigned long) sbe, 0);
220
221 /* Force string to be exactly the right width */
222 int w = mutt_strwidth (buf);
223 int s = mutt_strlen (buf);
224 width = MIN(buflen, width);
225 if (w < width)
226 {
227 /* Pad with spaces */
228 memset (buf + s, ' ', width - w);
229 buf[s + width - w] = 0;
230 }
231 else if (w > width)
232 {
233 /* Truncate to fit */
234 int len = mutt_wstr_trunc (buf, buflen, width, NULL);
235 buf[len] = 0;
236 }
237}
238
239/**
240 * cb_qsort_sbe - qsort callback to sort SBENTRYs
241 * @a: First SBENTRY to compare
242 * @b: Second SBENTRY to compare
243 *
244 * Returns:
245 * -1: a precedes b
246 * 0: a and b are identical
247 * 1: b precedes a
248 */
249static int cb_qsort_sbe (const void *a, const void *b)
250{
251 const SBENTRY *sbe1 = *(const SBENTRY **) a;
252 const SBENTRY *sbe2 = *(const SBENTRY **) b;
253 BUFFY *b1 = sbe1->buffy;
254 BUFFY *b2 = sbe2->buffy;
255
256 int result = 0;
257
258 switch ((SidebarSortMethod & SORT_MASK))
259 {
260 case SORT_COUNT:
261 result = (b2->msg_count - b1->msg_count);
262 break;
263 case SORT_UNREAD:
264 result = (b2->msg_unread - b1->msg_unread);
265 break;
266 case SORT_FLAGGED:
267 result = (b2->msg_flagged - b1->msg_flagged);
268 break;
269 case SORT_PATH:
270 result = mutt_strcasecmp (mutt_b2s (b1->pathbuf), mutt_b2s (b2->pathbuf));
271 break;
272 }
273
274 if (SidebarSortMethod & SORT_REVERSE)
275 result = -result;
276
277 return result;
278}
279
280/**
281 * update_entries_visibility - Should a sidebar_entry be displayed in the sidebar
282 *
283 * For each SBENTRY in the Entries array, check whether we should display it.
284 * This is determined by several criteria. If the BUFFY:
285 * is the currently open mailbox
286 * is the currently highlighted mailbox
287 * has unread messages
288 * has flagged messages
289 * is whitelisted
290 */
291static void update_entries_visibility (void)
292{
293 short new_only = option (OPTSIDEBARNEWMAILONLY);
294 SBENTRY *sbe;
295 int i;
296
297 for (i = 0; i < EntryCount; i++)
298 {
299 sbe = Entries[i];
300
301 sbe->is_hidden = 0;
302
303 if (!new_only)
304 continue;
305
306 if ((i == OpnIndex) || (sbe->buffy->msg_unread > 0) || sbe->buffy->new ||
307 (sbe->buffy->msg_flagged > 0))
308 continue;
309
310 if (Context && (mutt_strcmp (sbe->buffy->realpath, Context->realpath) == 0))
311 /* Spool directory */
312 continue;
313
314 if (mutt_find_list (SidebarWhitelist, mutt_b2s (sbe->buffy->pathbuf)))
315 /* Explicitly asked to be visible */
316 continue;
317
318 sbe->is_hidden = 1;
319 }
320}
321
322/**
323 * unsort_entries - Restore Entries array order to match Buffy list order
324 */
325static void unsort_entries (void)
326{
327 BUFFY *cur = Incoming;
328 int i = 0, j;
329 SBENTRY *tmp;
330
331 while (cur && (i < EntryCount))
332 {
333 j = i;
334 while ((j < EntryCount) &&
335 (Entries[j]->buffy != cur))
336 j++;
337 if (j < EntryCount)
338 {
339 if (j != i)
340 {
341 tmp = Entries[i];
342 Entries[i] = Entries[j];
343 Entries[j] = tmp;
344 }
345 i++;
346 }
347 cur = cur->next;
348 }
349}
350
351/**
352 * sort_entries - Sort Entries array.
353 *
354 * Sort the Entries array according to the current sort config
355 * option "sidebar_sort_method". This calls qsort to do the work which calls our
356 * callback function "cb_qsort_sbe".
357 *
358 * Once sorted, the prev/next links will be reconstructed.
359 */
360static void sort_entries (void)
361{
362 short ssm = (SidebarSortMethod & SORT_MASK);
363
364 /* These are the only sort methods we understand */
365 if ((ssm == SORT_COUNT) ||
366 (ssm == SORT_UNREAD) ||
367 (ssm == SORT_FLAGGED) ||
368 (ssm == SORT_PATH))
369 qsort (Entries, EntryCount, sizeof (*Entries), cb_qsort_sbe);
370 else if ((ssm == SORT_ORDER) &&
371 (SidebarSortMethod != PreviousSort))
372 unsort_entries ();
373}
374
375/**
376 * prepare_sidebar - Prepare the list of SBENTRYs for the sidebar display
377 * @page_size: The number of lines on a page
378 *
379 * Before painting the sidebar, we determine which are visible, sort
380 * them and set up our page pointers.
381 *
382 * This is a lot of work to do each refresh, but there are many things that
383 * can change outside of the sidebar that we don't hear about.
384 *
385 * Returns:
386 * 0: No, don't draw the sidebar
387 * 1: Yes, draw the sidebar
388 */
389static int prepare_sidebar (int page_size)
390{
391 int i;
392 SBENTRY *opn_entry = NULL, *hil_entry = NULL;
393 int page_entries;
394
395 if (!EntryCount || (page_size <= 0))
396 return 0;
397
398 if (OpnIndex >= 0)
399 opn_entry = Entries[OpnIndex];
400 if (HilIndex >= 0)
401 hil_entry = Entries[HilIndex];
402
403 update_entries_visibility ();
404 sort_entries ();
405
406 for (i = 0; i < EntryCount; i++)
407 {
408 if (opn_entry == Entries[i])
409 OpnIndex = i;
410 if (hil_entry == Entries[i])
411 HilIndex = i;
412 }
413
414 if ((HilIndex < 0) || Entries[HilIndex]->is_hidden ||
415 (SidebarSortMethod != PreviousSort))
416 {
417 if (OpnIndex >= 0)
418 HilIndex = OpnIndex;
419 else
420 {
421 HilIndex = 0;
422 if (Entries[HilIndex]->is_hidden)
423 select_next ();
424 }
425 }
426
427 /* Set the Top and Bottom to frame the HilIndex in groups of page_size */
428
429 /* If OPTSIDEBARNEMAILONLY is set, some entries may be hidden so we
430 * need to scan for the framing interval */
431 if (option (OPTSIDEBARNEWMAILONLY))
432 {
433 TopIndex = BotIndex = -1;
434 while (BotIndex < HilIndex)
435 {
436 TopIndex = BotIndex + 1;
437 page_entries = 0;
438 while (page_entries < page_size)
439 {
440 BotIndex++;
441 if (BotIndex >= EntryCount)
442 break;
443 if (! Entries[BotIndex]->is_hidden)
444 page_entries++;
445 }
446 }
447 }
448 /* Otherwise we can just calculate the interval */
449 else
450 {
451 TopIndex = (HilIndex / page_size) * page_size;
452 BotIndex = TopIndex + page_size - 1;
453 }
454
455 if (BotIndex > (EntryCount - 1))
456 BotIndex = EntryCount - 1;
457
458 PreviousSort = SidebarSortMethod;
459 return 1;
460}
461
462/**
463 * draw_divider - Draw a line between the sidebar and the rest of mutt
464 * @num_rows: Height of the Sidebar
465 * @num_cols: Width of the Sidebar
466 *
467 * Draw a divider using characters from the config option "sidebar_divider_char".
468 * This can be an ASCII or Unicode character. First we calculate this
469 * characters' width in screen columns, then subtract that from the config
470 * option "sidebar_width".
471 *
472 * Returns:
473 * -1: Error: bad character, etc
474 * 0: Error: 0 width character
475 * n: Success: character occupies n screen columns
476 */
477static int draw_divider (int num_rows, int num_cols)
478{
479 /* Calculate the width of the delimiter in screen cells */
480 int delim_len = mutt_strwidth (SidebarDividerChar);
481
482 if (delim_len < 1)
483 return delim_len;
484
485 if (delim_len > num_cols)
486 return 0;
487
488 SETCOLOR(MT_COLOR_DIVIDER);
489
490 int i;
491 for (i = 0; i < num_rows; i++)
492 {
493 mutt_window_move (MuttSidebarWindow, i, SidebarWidth - delim_len); //RAR 0 for rhs
494 addstr (NONULL(SidebarDividerChar));
495 }
496
497 return delim_len;
498}
499
500/**
501 * fill_empty_space - Wipe the remaining Sidebar space
502 * @first_row: Window line to start (0-based)
503 * @num_rows: Number of rows to fill
504 * @width: Width of the Sidebar (minus the divider)
505 *
506 * Write spaces over the area the sidebar isn't using.
507 */
508static void fill_empty_space (int first_row, int num_rows, int width)
509{
510 /* Fill the remaining rows with blank space */
511 SETCOLOR(MT_COLOR_NORMAL);
512
513 int r;
514 for (r = 0; r < num_rows; r++)
515 {
516 mutt_window_move (MuttSidebarWindow, first_row + r, 0); //RAR rhs
517 int i;
518 for (i = 0; i < width; i++)
519 addch (' ');
520 }
521}
522
523/**
524 * calculate_depth - Calculate depth of path based on SidebarDelimChars.
525 *
526 * If lastpath is not NULL, common_depth is also calculated. These
527 * are used for indentation and short_path calculation.
528 */
529static void calculate_depth (const char *path, const char *lastpath,
530 int *depth, int *common_depth)
531{
532 int i, has_trailing_delim = 0;
533
534 *depth = *common_depth = 0;
535 if (!SidebarDelimChars || !path)
536 return;
537
538 for (i = 0; path[i]; i++)
539 {
540 if (strchr (SidebarDelimChars, path[i]))
541 {
542 (*depth)++;
543
544 /* /a/b/c and /a/b/c/ both are a depth of 3.
545 * Only count the final '\0' if the last character wasn't a separator.
546 */
547 if (!path[i+1])
548 has_trailing_delim = 1;
549 }
550
551 if (lastpath)
552 {
553 /* path /a/b/c/d
554 * lastpath /a/b
555 * lastpath /a/
556 * lastpath /a
557 * ^
558 */
559 if (strchr (SidebarDelimChars, path[i]) &&
560 (strchr (SidebarDelimChars, lastpath[i]) || !lastpath[i]))
561 {
562 (*common_depth)++;
563 if (!lastpath[i])
564 lastpath = NULL;
565 }
566
567 /* path /abc
568 * lastpath /ad
569 * lastpath /a/
570 * lastpath /a
571 * ^
572 */
573 else if (!lastpath[i] || path[i] != lastpath[i])
574 lastpath = NULL;
575 }
576 }
577
578 if (!has_trailing_delim)
579 {
580 (*depth)++;
581
582 /* path /a
583 * lastpath /a/b/c
584 * lastpath /a/
585 * lastpath /a
586 * ^
587 */
588 if (lastpath &&
589 (strchr (SidebarDelimChars, lastpath[i]) || !lastpath[i]))
590 (*common_depth)++;
591 }
592}
593
594#define SIDEBAR_MAX_INDENT 32
595
596/**
597 * draw_sidebar - Write out a list of mailboxes, on the left
598 * @num_rows: Height of the Sidebar
599 * @num_cols: Width of the Sidebar
600 * @div_width: Width in screen characters taken by the divider
601 *
602 * Display a list of mailboxes in a panel on the left. What's displayed will
603 * depend on our index markers: TopBuffy, OpnBuffy, HilBuffy, BotBuffy.
604 * On the first run they'll be NULL, so we display the top of Mutt's list
605 * (Incoming).
606 *
607 * TopBuffy - first visible mailbox
608 * BotBuffy - last visible mailbox
609 * OpnBuffy - mailbox shown in Mutt's Index Panel
610 * HilBuffy - Unselected mailbox (the paging follows this)
611 *
612 * The entries are formatted using "sidebar_format" and may be abbreviated:
613 * "sidebar_short_path", indented: "sidebar_folder_indent",
614 * "sidebar_indent_string" and sorted: "sidebar_sort_method". Finally, they're
615 * trimmed to fit the available space.
616 */
617static void draw_sidebar (int num_rows, int num_cols, int div_width)
618{
619 int entryidx;
620 SBENTRY *entry;
621 BUFFY *b;
622 int maildir_is_prefix;
623 int indent_width = -1;
624 int indent_depths[SIDEBAR_MAX_INDENT];
625 const char *sidebar_folder_name;
626 BUFFER *pretty_folder_name, *last_folder_name, *indent_folder_name;
627
628 if (TopIndex < 0)
629 return;
630
631 pretty_folder_name = mutt_buffer_pool_get ();
632 last_folder_name = mutt_buffer_pool_get ();
633 indent_folder_name = mutt_buffer_pool_get ();
634
635 int w = MIN(num_cols, (SidebarWidth - div_width));
636 int row = 0;
637 for (entryidx = TopIndex; (entryidx < EntryCount) && (row < num_rows); entryidx++)
638 {
639 entry = Entries[entryidx];
640 if (entry->is_hidden)
641 continue;
642 b = entry->buffy;
643
644 if (entryidx == OpnIndex)
645 {
646 if ((ColorDefs[MT_COLOR_SB_INDICATOR] != 0))
647 SETCOLOR(MT_COLOR_SB_INDICATOR);
648 else
649 SETCOLOR(MT_COLOR_INDICATOR);
650 }
651 else if (entryidx == HilIndex)
652 SETCOLOR(MT_COLOR_HIGHLIGHT);
653 else if ((b->msg_unread > 0) || (b->new))
654 SETCOLOR(MT_COLOR_NEW);
655 else if (b->msg_flagged > 0)
656 SETCOLOR(MT_COLOR_FLAGGED);
657 else if ((ColorDefs[MT_COLOR_SB_SPOOLFILE] != 0) &&
658 (mutt_strcmp (mutt_b2s (b->pathbuf), Spoolfile) == 0))
659 SETCOLOR(MT_COLOR_SB_SPOOLFILE);
660 else
661 SETCOLOR(MT_COLOR_NORMAL);
662
663 mutt_window_move (MuttSidebarWindow, row, 0);
664 if (Context && Context->realpath &&
665 !mutt_strcmp (b->realpath, Context->realpath))
666 {
667 b->msg_unread = Context->unread;
668 b->msg_count = Context->msgcount;
669 b->msg_flagged = Context->flagged;
670 }
671
672 maildir_is_prefix = 0;
673 if (option (OPTSIDEBARUSEMBSHORTCUTS))
674 {
675 mutt_buffer_strcpy (pretty_folder_name, mutt_b2s (b->pathbuf));
676 mutt_buffer_pretty_mailbox (pretty_folder_name);
677 sidebar_folder_name = mutt_b2s (pretty_folder_name);
678 if (sidebar_folder_name[0] == '=')
679 maildir_is_prefix = 1;
680 }
681 else
682 {
683 /* compute length of Maildir without trailing separator */
684 size_t maildirlen = mutt_strlen (Maildir);
685 if (maildirlen &&
686 SidebarDelimChars &&
687 strchr (SidebarDelimChars, Maildir[maildirlen - 1]))
688 maildirlen--;
689
690 /* check whether Maildir is a prefix of the current folder's path */
691 if ((mutt_buffer_len (b->pathbuf) > maildirlen) &&
692 (mutt_strncmp (Maildir, mutt_b2s (b->pathbuf), maildirlen) == 0) &&
693 SidebarDelimChars &&
694 strchr (SidebarDelimChars, mutt_b2s (b->pathbuf)[maildirlen]))
695 {
696 sidebar_folder_name = mutt_b2s (b->pathbuf) + (maildirlen + 1);
697 maildir_is_prefix = 1;
698 }
699 else
700 sidebar_folder_name = mutt_b2s (b->pathbuf);
701 }
702
703 if (SidebarDelimChars)
704 {
705 int parent_depth = 0;
706 int i;
707
708 if (option (OPTSIDEBARSHORTPATH) || option (OPTSIDEBARFOLDERINDENT))
709 {
710 int depth = 0, common_depth = 0;
711
712 calculate_depth (sidebar_folder_name, mutt_b2s (last_folder_name),
713 &depth, &common_depth);
714
715 if (option(OPTSIDEBARRELSPINDENT))
716 {
717 mutt_buffer_strcpy (last_folder_name, sidebar_folder_name);
718
719 if (indent_width < SIDEBAR_MAX_INDENT)
720 indent_width++;
721
722 /* indent_depths[] hold the path depths at each level of indentation.
723 * Indent based off the longest path that we share in common.
724 *
725 * The 'indent_depths[] >= depth' test below is for a corner case:
726 *
727 * path depth common_depth indent_width
728 * /a 2 0 0
729 * /a/b 3 2 1
730 * /a/b/ 3 3 1
731 *
732 * Because the common_depth of /a/b/ matches the depth of
733 * /a/b, we need the additional test to continue popping the
734 * indent_depths[] stack.
735 */
736 while (indent_width &&
737 ((indent_depths[indent_width - 1] > common_depth) ||
738 (indent_depths[indent_width - 1] >= depth)))
739 indent_width--;
740
741 if (indent_width < SIDEBAR_MAX_INDENT)
742 indent_depths[indent_width] = depth;
743 if (indent_width)
744 parent_depth = indent_depths[indent_width - 1];
745 }
746 else
747 {
748 parent_depth = depth - 1;
749 indent_width = maildir_is_prefix ? depth - 1 : 0;
750 }
751 }
752
753 if (option (OPTSIDEBARSHORTPATH) && (parent_depth > 0))
754 {
755 for (i = 0; parent_depth && sidebar_folder_name[i]; i++)
756 if (strchr (SidebarDelimChars, sidebar_folder_name[i]))
757 parent_depth--;
758 sidebar_folder_name += i;
759 }
760
761 if (option (OPTSIDEBARFOLDERINDENT) && (indent_width > 0))
762 {
763 mutt_buffer_clear (indent_folder_name);
764 for (i = 0; i < indent_width; i++)
765 mutt_buffer_addstr (indent_folder_name, NONULL(SidebarIndentString));
766 mutt_buffer_addstr (indent_folder_name, sidebar_folder_name);
767 sidebar_folder_name = mutt_b2s (indent_folder_name);
768 }
769 }
770
771 char str[STRING];
772 make_sidebar_entry (str, sizeof (str), w, sidebar_folder_name, entry);
773 printw ("%s", str);
774 row++;
775 }
776
777 mutt_buffer_pool_release (&pretty_folder_name);
778 mutt_buffer_pool_release (&last_folder_name);
779 mutt_buffer_pool_release (&indent_folder_name);
780
781 fill_empty_space (row, num_rows - row, w);
782}
783
784
785/**
786 * mutt_sb_draw - Completely redraw the sidebar
787 *
788 * Completely refresh the sidebar region. First draw the divider; then, for
789 * each BUFFY, call make_sidebar_entry; finally blank out any remaining space.
790 */
791void mutt_sb_draw (void)
792{
793 if (!option (OPTSIDEBAR))
794 return;
795
796 int num_rows = MuttSidebarWindow->rows;
797 int num_cols = MuttSidebarWindow->cols;
798
799 int div_width = draw_divider (num_rows, num_cols);
800 if (div_width < 0)
801 return;
802
803 if (!Incoming)
804 {
805 fill_empty_space (0, num_rows, SidebarWidth - div_width);
806 return;
807 }
808
809 if (!prepare_sidebar (num_rows))
810 return;
811
812 draw_sidebar (num_rows, num_cols, div_width);
813}
814
815/**
816 * select_next - Selects the next unhidden mailbox
817 *
818 * Returns:
819 * 1: Success
820 * 0: Failure
821 */
822static int select_next (void)
823{
824 int entry = HilIndex;
825
826 if (!EntryCount || HilIndex < 0)
827 return 0;
828
829 do
830 {
831 entry++;
832 if (entry == EntryCount)
833 return 0;
834 } while (Entries[entry]->is_hidden);
835
836 HilIndex = entry;
837 return 1;
838}
839
840/**
841 * select_next_new - Selects the next new mailbox
842 *
843 * Search down the list of mail folders for one containing new mail.
844 *
845 * Returns:
846 * 1: Success
847 * 0: Failure
848 */
849static int select_next_new (void)
850{
851 int entry = HilIndex;
852
853 if (!EntryCount || HilIndex < 0)
854 return 0;
855
856 do
857 {
858 entry++;
859 if (entry == EntryCount)
860 {
861 if (option (OPTSIDEBARNEXTNEWWRAP))
862 entry = 0;
863 else
864 return 0;
865 }
866 if (entry == HilIndex)
867 return 0;
868 } while (!Entries[entry]->buffy->new &&
869 !Entries[entry]->buffy->msg_unread);
870
871 HilIndex = entry;
872 return 1;
873}
874
875/**
876 * select_prev - Selects the previous unhidden mailbox
877 *
878 * Returns:
879 * 1: Success
880 * 0: Failure
881 */
882static int select_prev (void)
883{
884 int entry = HilIndex;
885
886 if (!EntryCount || HilIndex < 0)
887 return 0;
888
889 do
890 {
891 entry--;
892 if (entry < 0)
893 return 0;
894 } while (Entries[entry]->is_hidden);
895
896 HilIndex = entry;
897 return 1;
898}
899
900/**
901 * select_prev_new - Selects the previous new mailbox
902 *
903 * Search up the list of mail folders for one containing new mail.
904 *
905 * Returns:
906 * 1: Success
907 * 0: Failure
908 */
909static int select_prev_new (void)
910{
911 int entry = HilIndex;
912
913 if (!EntryCount || HilIndex < 0)
914 return 0;
915
916 do
917 {
918 entry--;
919 if (entry < 0)
920 {
921 if (option (OPTSIDEBARNEXTNEWWRAP))
922 entry = EntryCount - 1;
923 else
924 return 0;
925 }
926 if (entry == HilIndex)
927 return 0;
928 } while (!Entries[entry]->buffy->new &&
929 !Entries[entry]->buffy->msg_unread);
930
931 HilIndex = entry;
932 return 1;
933}
934
935/**
936 * select_page_down - Selects the first entry in the next page of mailboxes
937 *
938 * Returns:
939 * 1: Success
940 * 0: Failure
941 */
942static int select_page_down (void)
943{
944 int orig_hil_index = HilIndex;
945
946 if (!EntryCount || BotIndex < 0)
947 return 0;
948
949 HilIndex = BotIndex;
950 select_next ();
951 /* If the rest of the entries are hidden, go up to the last unhidden one */
952 if (Entries[HilIndex]->is_hidden)
953 select_prev ();
954
955 return (orig_hil_index != HilIndex);
956}
957
958/**
959 * select_page_up - Selects the last entry in the previous page of mailboxes
960 *
961 * Returns:
962 * 1: Success
963 * 0: Failure
964 */
965static int select_page_up (void)
966{
967 int orig_hil_index = HilIndex;
968
969 if (!EntryCount || TopIndex < 0)
970 return 0;
971
972 HilIndex = TopIndex;
973 select_prev ();
974 /* If the rest of the entries are hidden, go down to the last unhidden one */
975 if (Entries[HilIndex]->is_hidden)
976 select_next ();
977
978 return (orig_hil_index != HilIndex);
979}
980
981/**
982 * mutt_sb_change_mailbox - Change the selected mailbox
983 * @op: Operation code
984 *
985 * Change the selected mailbox, e.g. "Next mailbox", "Previous Mailbox
986 * with new mail". The operations are listed OPS.SIDEBAR which is built
987 * into an enum in keymap_defs.h.
988 *
989 * If the operation is successful, HilBuffy will be set to the new mailbox.
990 * This function only *selects* the mailbox, doesn't *open* it.
991 *
992 * Allowed values are: OP_SIDEBAR_NEXT, OP_SIDEBAR_NEXT_NEW,
993 * OP_SIDEBAR_PAGE_DOWN, OP_SIDEBAR_PAGE_UP, OP_SIDEBAR_PREV,
994 * OP_SIDEBAR_PREV_NEW.
995 */
996void mutt_sb_change_mailbox (int op)
997{
998 if (!option (OPTSIDEBAR))
999 return;
1000
1001 if (HilIndex < 0) /* It'll get reset on the next draw */
1002 return;
1003
1004 switch (op)
1005 {
1006 case OP_SIDEBAR_NEXT:
1007 if (! select_next ())
1008 return;
1009 break;
1010 case OP_SIDEBAR_NEXT_NEW:
1011 if (! select_next_new ())
1012 return;
1013 break;
1014 case OP_SIDEBAR_PAGE_DOWN:
1015 if (! select_page_down ())
1016 return;
1017 break;
1018 case OP_SIDEBAR_PAGE_UP:
1019 if (! select_page_up ())
1020 return;
1021 break;
1022 case OP_SIDEBAR_PREV:
1023 if (! select_prev ())
1024 return;
1025 break;
1026 case OP_SIDEBAR_PREV_NEW:
1027 if (! select_prev_new ())
1028 return;
1029 break;
1030 default:
1031 return;
1032 }
1033 mutt_set_current_menu_redraw (REDRAW_SIDEBAR);
1034}
1035
1036/**
1037 * mutt_sb_set_buffystats - Update the BUFFY's message counts from the CONTEXT
1038 * @ctx: A mailbox CONTEXT
1039 *
1040 * Given a mailbox CONTEXT, find a matching mailbox BUFFY and copy the message
1041 * counts into it.
1042 */
1043void mutt_sb_set_buffystats (const CONTEXT *ctx)
1044{
1045 /* Even if the sidebar's hidden,
1046 * we should take note of the new data. */
1047 BUFFY *b = Incoming;
1048 if (!ctx || !b)
1049 return;
1050
1051 for (; b; b = b->next)
1052 {
1053 if (!mutt_strcmp (b->realpath, ctx->realpath))
1054 {
1055 b->msg_unread = ctx->unread;
1056 b->msg_count = ctx->msgcount;
1057 b->msg_flagged = ctx->flagged;
1058 break;
1059 }
1060 }
1061}
1062
1063/**
1064 * mutt_sb_get_highlight - Get the BUFFY that's highlighted in the sidebar
1065 *
1066 * Get the path of the mailbox that's highlighted in the sidebar.
1067 *
1068 * Returns:
1069 * Mailbox path
1070 */
1071const char *mutt_sb_get_highlight (void)
1072{
1073 if (!option (OPTSIDEBAR))
1074 return NULL;
1075
1076 if (!EntryCount || HilIndex < 0)
1077 return NULL;
1078
1079 return mutt_b2s (Entries[HilIndex]->buffy->pathbuf);
1080}
1081
1082/**
1083 * mutt_sb_set_open_buffy - Set the OpnBuffy based on the global Context
1084 *
1085 * Search through the list of mailboxes. If a BUFFY has a matching path, set
1086 * OpnBuffy to it.
1087 */
1088void mutt_sb_set_open_buffy (void)
1089{
1090 int entry;
1091
1092 OpnIndex = -1;
1093
1094 if (!Context)
1095 return;
1096
1097 for (entry = 0; entry < EntryCount; entry++)
1098 {
1099 if (!mutt_strcmp (Entries[entry]->buffy->realpath, Context->realpath))
1100 {
1101 OpnIndex = entry;
1102 HilIndex = entry;
1103 break;
1104 }
1105 }
1106}
1107
1108/**
1109 * mutt_sb_notify_mailbox - The state of a BUFFY is about to change
1110 *
1111 * We receive a notification:
1112 * After a new BUFFY has been created
1113 * Before a BUFFY is deleted
1114 *
1115 * Before a deletion, check that our pointers won't be invalidated.
1116 */
1117void mutt_sb_notify_mailbox (BUFFY *b, int created)
1118{
1119 int del_index;
1120
1121 if (!b)
1122 return;
1123
1124 /* Any new/deleted mailboxes will cause a refresh. As long as
1125 * they're valid, our pointers will be updated in prepare_sidebar() */
1126
1127 if (created)
1128 {
1129 if (EntryCount >= EntryLen)
1130 {
1131 EntryLen += 10;
1132 safe_realloc (&Entries, EntryLen * sizeof (SBENTRY *));
1133 }
1134 Entries[EntryCount] = safe_calloc (1, sizeof(SBENTRY));
1135 Entries[EntryCount]->buffy = b;
1136
1137 if (TopIndex < 0)
1138 TopIndex = EntryCount;
1139 if (BotIndex < 0)
1140 BotIndex = EntryCount;
1141 if ((OpnIndex < 0) && Context &&
1142 (mutt_strcmp (b->realpath, Context->realpath) == 0))
1143 OpnIndex = EntryCount;
1144
1145 EntryCount++;
1146 }
1147 else
1148 {
1149 for (del_index = 0; del_index < EntryCount; del_index++)
1150 if (Entries[del_index]->buffy == b)
1151 break;
1152 if (del_index == EntryCount)
1153 return;
1154 FREE (&Entries[del_index]);
1155 EntryCount--;
1156
1157 if (TopIndex > del_index || TopIndex == EntryCount)
1158 TopIndex--;
1159 if (OpnIndex == del_index)
1160 OpnIndex = -1;
1161 else if (OpnIndex > del_index)
1162 OpnIndex--;
1163 if (HilIndex > del_index || HilIndex == EntryCount)
1164 HilIndex--;
1165 if (BotIndex > del_index || BotIndex == EntryCount)
1166 BotIndex--;
1167
1168 for (; del_index < EntryCount; del_index++)
1169 Entries[del_index] = Entries[del_index + 1];
1170 }
1171
1172 mutt_set_current_menu_redraw (REDRAW_SIDEBAR);
1173}