]> granicus.if.org Git - mutt/blob - sidebar.c
Convert smime_invoke_import() and helpers to use buffer pool.
[mutt] / sidebar.c
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 */
33 static short PreviousSort = SORT_ORDER;  /* sidebar_sort_method */
34
35 /**
36  * struct sidebar_entry - Info about folders in the sidebar
37  */
38 typedef struct sidebar_entry
39 {
40   char         box[STRING];     /* formatted mailbox name */
41   BUFFY       *buffy;
42   short        is_hidden;
43 } SBENTRY;
44
45 static int EntryCount = 0;
46 static int EntryLen   = 0;
47 static SBENTRY **Entries = NULL;
48
49 static int TopIndex = -1;    /* First mailbox visible in sidebar */
50 static int OpnIndex = -1;    /* Current (open) mailbox */
51 static int HilIndex = -1;    /* Highlighted mailbox */
52 static int BotIndex = -1;    /* Last mailbox visible in sidebar */
53
54 static 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  */
78 static 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  */
211 static 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  */
249 static 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  */
291 static 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  */
325 static 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  */
360 static 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  */
389 static 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  */
477 static 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  */
508 static 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  */
529 static 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  */
617 static void draw_sidebar (int num_rows, int num_cols, int div_width)
618 {
619   int entryidx;
620   SBENTRY *entry;
621   BUFFY *b;
622   int indent_width = -1;
623   int indent_depths[SIDEBAR_MAX_INDENT];
624   const char *sidebar_folder_name;
625   BUFFER *pretty_folder_name, *last_folder_name, *indent_folder_name;
626
627   if (TopIndex < 0)
628     return;
629
630   pretty_folder_name = mutt_buffer_pool_get ();
631   last_folder_name = mutt_buffer_pool_get ();
632   indent_folder_name = mutt_buffer_pool_get ();
633
634   int w = MIN(num_cols, (SidebarWidth - div_width));
635   int row = 0;
636   for (entryidx = TopIndex; (entryidx < EntryCount) && (row < num_rows); entryidx++)
637   {
638     entry = Entries[entryidx];
639     if (entry->is_hidden)
640       continue;
641     b = entry->buffy;
642
643     if (entryidx == OpnIndex)
644     {
645       if ((ColorDefs[MT_COLOR_SB_INDICATOR] != 0))
646         SETCOLOR(MT_COLOR_SB_INDICATOR);
647       else
648         SETCOLOR(MT_COLOR_INDICATOR);
649     }
650     else if (entryidx == HilIndex)
651       SETCOLOR(MT_COLOR_HIGHLIGHT);
652     else if ((b->msg_unread > 0) || (b->new))
653       SETCOLOR(MT_COLOR_NEW);
654     else if (b->msg_flagged > 0)
655       SETCOLOR(MT_COLOR_FLAGGED);
656     else if ((ColorDefs[MT_COLOR_SB_SPOOLFILE] != 0) &&
657              (mutt_strcmp (mutt_b2s (b->pathbuf), Spoolfile) == 0))
658       SETCOLOR(MT_COLOR_SB_SPOOLFILE);
659     else
660       SETCOLOR(MT_COLOR_NORMAL);
661
662     mutt_window_move (MuttSidebarWindow, row, 0);
663     if (Context && Context->realpath &&
664         !mutt_strcmp (b->realpath, Context->realpath))
665     {
666       b->msg_unread  = Context->unread;
667       b->msg_count   = Context->msgcount;
668       b->msg_flagged = Context->flagged;
669     }
670
671     mutt_buffer_strcpy (pretty_folder_name, mutt_b2s (b->pathbuf));
672     mutt_buffer_pretty_mailbox (pretty_folder_name);
673     sidebar_folder_name = mutt_b2s (pretty_folder_name);
674
675     if (SidebarDelimChars)
676     {
677       int parent_depth = 0;
678       int i;
679
680       if (option (OPTSIDEBARSHORTPATH) || option (OPTSIDEBARFOLDERINDENT))
681       {
682         int depth = 0, common_depth = 0;
683
684         calculate_depth (sidebar_folder_name, mutt_b2s (last_folder_name),
685                          &depth, &common_depth);
686         mutt_buffer_strcpy (last_folder_name, sidebar_folder_name);
687
688         if (indent_width < SIDEBAR_MAX_INDENT)
689           indent_width++;
690
691         /* indent_depths[] hold the path depths at each level of indentation.
692          * Indent based off the longest path that we share in common.
693          *
694          * The 'indent_depths[] >= depth' test below is for a corner case:
695          *
696          * path       depth    common_depth    indent_width
697          * /a           2            0              0
698          * /a/b         3            2              1
699          * /a/b/        3            3              1
700          *
701          * Because the common_depth of /a/b/ matches the depth of
702          * /a/b, we need the additional test to continue popping the
703          * indent_depths[] stack.
704          */
705         while (indent_width &&
706                ((indent_depths[indent_width - 1] > common_depth) ||
707                 (indent_depths[indent_width - 1] >= depth)))
708           indent_width--;
709
710         if (indent_width < SIDEBAR_MAX_INDENT)
711           indent_depths[indent_width] = depth;
712         if (indent_width)
713           parent_depth = indent_depths[indent_width - 1];
714       }
715
716       if (option (OPTSIDEBARSHORTPATH) && parent_depth)
717       {
718         for (i = 0; parent_depth && sidebar_folder_name[i]; i++)
719           if (strchr (SidebarDelimChars, sidebar_folder_name[i]))
720             parent_depth--;
721         sidebar_folder_name += i;
722       }
723
724       if (option (OPTSIDEBARFOLDERINDENT) && indent_width)
725       {
726         mutt_buffer_clear (indent_folder_name);
727         for (i = 0; i < indent_width; i++)
728           mutt_buffer_addstr (indent_folder_name, NONULL(SidebarIndentString));
729         mutt_buffer_addstr (indent_folder_name, sidebar_folder_name);
730         sidebar_folder_name = mutt_b2s (indent_folder_name);
731       }
732     }
733
734     char str[STRING];
735     make_sidebar_entry (str, sizeof (str), w, sidebar_folder_name, entry);
736     printw ("%s", str);
737     row++;
738   }
739
740   mutt_buffer_pool_release (&pretty_folder_name);
741   mutt_buffer_pool_release (&last_folder_name);
742   mutt_buffer_pool_release (&indent_folder_name);
743
744   fill_empty_space (row, num_rows - row, w);
745 }
746
747
748 /**
749  * mutt_sb_draw - Completely redraw the sidebar
750  *
751  * Completely refresh the sidebar region.  First draw the divider; then, for
752  * each BUFFY, call make_sidebar_entry; finally blank out any remaining space.
753  */
754 void mutt_sb_draw (void)
755 {
756   if (!option (OPTSIDEBAR))
757     return;
758
759   int num_rows  = MuttSidebarWindow->rows;
760   int num_cols  = MuttSidebarWindow->cols;
761
762   int div_width = draw_divider (num_rows, num_cols);
763   if (div_width < 0)
764     return;
765
766   if (!Incoming)
767   {
768     fill_empty_space (0, num_rows, SidebarWidth - div_width);
769     return;
770   }
771
772   if (!prepare_sidebar (num_rows))
773     return;
774
775   draw_sidebar (num_rows, num_cols, div_width);
776 }
777
778 /**
779  * select_next - Selects the next unhidden mailbox
780  *
781  * Returns:
782  *      1: Success
783  *      0: Failure
784  */
785 static int select_next (void)
786 {
787   int entry = HilIndex;
788
789   if (!EntryCount || HilIndex < 0)
790     return 0;
791
792   do
793   {
794     entry++;
795     if (entry == EntryCount)
796       return 0;
797   } while (Entries[entry]->is_hidden);
798
799   HilIndex = entry;
800   return 1;
801 }
802
803 /**
804  * select_next_new - Selects the next new mailbox
805  *
806  * Search down the list of mail folders for one containing new mail.
807  *
808  * Returns:
809  *      1: Success
810  *      0: Failure
811  */
812 static int select_next_new (void)
813 {
814   int entry = HilIndex;
815
816   if (!EntryCount || HilIndex < 0)
817     return 0;
818
819   do
820   {
821     entry++;
822     if (entry == EntryCount)
823     {
824       if (option (OPTSIDEBARNEXTNEWWRAP))
825         entry = 0;
826       else
827         return 0;
828     }
829     if (entry == HilIndex)
830       return 0;
831   } while (!Entries[entry]->buffy->new &&
832            !Entries[entry]->buffy->msg_unread);
833
834   HilIndex = entry;
835   return 1;
836 }
837
838 /**
839  * select_prev - Selects the previous unhidden mailbox
840  *
841  * Returns:
842  *      1: Success
843  *      0: Failure
844  */
845 static int select_prev (void)
846 {
847   int entry = HilIndex;
848
849   if (!EntryCount || HilIndex < 0)
850     return 0;
851
852   do
853   {
854     entry--;
855     if (entry < 0)
856       return 0;
857   } while (Entries[entry]->is_hidden);
858
859   HilIndex = entry;
860   return 1;
861 }
862
863 /**
864  * select_prev_new - Selects the previous new mailbox
865  *
866  * Search up the list of mail folders for one containing new mail.
867  *
868  * Returns:
869  *      1: Success
870  *      0: Failure
871  */
872 static int select_prev_new (void)
873 {
874   int entry = HilIndex;
875
876   if (!EntryCount || HilIndex < 0)
877     return 0;
878
879   do
880   {
881     entry--;
882     if (entry < 0)
883     {
884       if (option (OPTSIDEBARNEXTNEWWRAP))
885         entry = EntryCount - 1;
886       else
887         return 0;
888     }
889     if (entry == HilIndex)
890       return 0;
891   } while (!Entries[entry]->buffy->new &&
892            !Entries[entry]->buffy->msg_unread);
893
894   HilIndex = entry;
895   return 1;
896 }
897
898 /**
899  * select_page_down - Selects the first entry in the next page of mailboxes
900  *
901  * Returns:
902  *      1: Success
903  *      0: Failure
904  */
905 static int select_page_down (void)
906 {
907   int orig_hil_index = HilIndex;
908
909   if (!EntryCount || BotIndex < 0)
910     return 0;
911
912   HilIndex = BotIndex;
913   select_next ();
914   /* If the rest of the entries are hidden, go up to the last unhidden one */
915   if (Entries[HilIndex]->is_hidden)
916     select_prev ();
917
918   return (orig_hil_index != HilIndex);
919 }
920
921 /**
922  * select_page_up - Selects the last entry in the previous page of mailboxes
923  *
924  * Returns:
925  *      1: Success
926  *      0: Failure
927  */
928 static int select_page_up (void)
929 {
930   int orig_hil_index = HilIndex;
931
932   if (!EntryCount || TopIndex < 0)
933     return 0;
934
935   HilIndex = TopIndex;
936   select_prev ();
937   /* If the rest of the entries are hidden, go down to the last unhidden one */
938   if (Entries[HilIndex]->is_hidden)
939     select_next ();
940
941   return (orig_hil_index != HilIndex);
942 }
943
944 /**
945  * mutt_sb_change_mailbox - Change the selected mailbox
946  * @op: Operation code
947  *
948  * Change the selected mailbox, e.g. "Next mailbox", "Previous Mailbox
949  * with new mail". The operations are listed OPS.SIDEBAR which is built
950  * into an enum in keymap_defs.h.
951  *
952  * If the operation is successful, HilBuffy will be set to the new mailbox.
953  * This function only *selects* the mailbox, doesn't *open* it.
954  *
955  * Allowed values are: OP_SIDEBAR_NEXT, OP_SIDEBAR_NEXT_NEW,
956  * OP_SIDEBAR_PAGE_DOWN, OP_SIDEBAR_PAGE_UP, OP_SIDEBAR_PREV,
957  * OP_SIDEBAR_PREV_NEW.
958  */
959 void mutt_sb_change_mailbox (int op)
960 {
961   if (!option (OPTSIDEBAR))
962     return;
963
964   if (HilIndex < 0)     /* It'll get reset on the next draw */
965     return;
966
967   switch (op)
968   {
969     case OP_SIDEBAR_NEXT:
970       if (! select_next ())
971         return;
972       break;
973     case OP_SIDEBAR_NEXT_NEW:
974       if (! select_next_new ())
975         return;
976       break;
977     case OP_SIDEBAR_PAGE_DOWN:
978       if (! select_page_down ())
979         return;
980       break;
981     case OP_SIDEBAR_PAGE_UP:
982       if (! select_page_up ())
983         return;
984       break;
985     case OP_SIDEBAR_PREV:
986       if (! select_prev ())
987         return;
988       break;
989     case OP_SIDEBAR_PREV_NEW:
990       if (! select_prev_new ())
991         return;
992       break;
993     default:
994       return;
995   }
996   mutt_set_current_menu_redraw (REDRAW_SIDEBAR);
997 }
998
999 /**
1000  * mutt_sb_set_buffystats - Update the BUFFY's message counts from the CONTEXT
1001  * @ctx:  A mailbox CONTEXT
1002  *
1003  * Given a mailbox CONTEXT, find a matching mailbox BUFFY and copy the message
1004  * counts into it.
1005  */
1006 void mutt_sb_set_buffystats (const CONTEXT *ctx)
1007 {
1008   /* Even if the sidebar's hidden,
1009    * we should take note of the new data. */
1010   BUFFY *b = Incoming;
1011   if (!ctx || !b)
1012     return;
1013
1014   for (; b; b = b->next)
1015   {
1016     if (!mutt_strcmp (b->realpath, ctx->realpath))
1017     {
1018       b->msg_unread  = ctx->unread;
1019       b->msg_count   = ctx->msgcount;
1020       b->msg_flagged = ctx->flagged;
1021       break;
1022     }
1023   }
1024 }
1025
1026 /**
1027  * mutt_sb_get_highlight - Get the BUFFY that's highlighted in the sidebar
1028  *
1029  * Get the path of the mailbox that's highlighted in the sidebar.
1030  *
1031  * Returns:
1032  *      Mailbox path
1033  */
1034 const char *mutt_sb_get_highlight (void)
1035 {
1036   if (!option (OPTSIDEBAR))
1037     return NULL;
1038
1039   if (!EntryCount || HilIndex < 0)
1040     return NULL;
1041
1042   return mutt_b2s (Entries[HilIndex]->buffy->pathbuf);
1043 }
1044
1045 /**
1046  * mutt_sb_set_open_buffy - Set the OpnBuffy based on the global Context
1047  *
1048  * Search through the list of mailboxes.  If a BUFFY has a matching path, set
1049  * OpnBuffy to it.
1050  */
1051 void mutt_sb_set_open_buffy (void)
1052 {
1053   int entry;
1054
1055   OpnIndex = -1;
1056
1057   if (!Context)
1058     return;
1059
1060   for (entry = 0; entry < EntryCount; entry++)
1061   {
1062     if (!mutt_strcmp (Entries[entry]->buffy->realpath, Context->realpath))
1063     {
1064       OpnIndex = entry;
1065       HilIndex = entry;
1066       break;
1067     }
1068   }
1069 }
1070
1071 /**
1072  * mutt_sb_notify_mailbox - The state of a BUFFY is about to change
1073  *
1074  * We receive a notification:
1075  *      After a new BUFFY has been created
1076  *      Before a BUFFY is deleted
1077  *
1078  * Before a deletion, check that our pointers won't be invalidated.
1079  */
1080 void mutt_sb_notify_mailbox (BUFFY *b, int created)
1081 {
1082   int del_index;
1083
1084   if (!b)
1085     return;
1086
1087   /* Any new/deleted mailboxes will cause a refresh.  As long as
1088    * they're valid, our pointers will be updated in prepare_sidebar() */
1089
1090   if (created)
1091   {
1092     if (EntryCount >= EntryLen)
1093     {
1094       EntryLen += 10;
1095       safe_realloc (&Entries, EntryLen * sizeof (SBENTRY *));
1096     }
1097     Entries[EntryCount] = safe_calloc (1, sizeof(SBENTRY));
1098     Entries[EntryCount]->buffy = b;
1099
1100     if (TopIndex < 0)
1101       TopIndex = EntryCount;
1102     if (BotIndex < 0)
1103       BotIndex = EntryCount;
1104     if ((OpnIndex < 0) && Context &&
1105         (mutt_strcmp (b->realpath, Context->realpath) == 0))
1106       OpnIndex = EntryCount;
1107
1108     EntryCount++;
1109   }
1110   else
1111   {
1112     for (del_index = 0; del_index < EntryCount; del_index++)
1113       if (Entries[del_index]->buffy == b)
1114         break;
1115     if (del_index == EntryCount)
1116       return;
1117     FREE (&Entries[del_index]);
1118     EntryCount--;
1119
1120     if (TopIndex > del_index || TopIndex == EntryCount)
1121       TopIndex--;
1122     if (OpnIndex == del_index)
1123       OpnIndex = -1;
1124     else if (OpnIndex > del_index)
1125       OpnIndex--;
1126     if (HilIndex > del_index || HilIndex == EntryCount)
1127       HilIndex--;
1128     if (BotIndex > del_index || BotIndex == EntryCount)
1129       BotIndex--;
1130
1131     for (; del_index < EntryCount; del_index++)
1132       Entries[del_index] = Entries[del_index + 1];
1133   }
1134
1135   mutt_set_current_menu_redraw (REDRAW_SIDEBAR);
1136 }