Fix sidebar to always show ^.
[apps/madmutt.git] / lib-ui / sidebar.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) ????-2004 Justin Hibbits <jrh29@po.cwru.edu>
4  * Copyright (C) 2004 Thomer M. Gil <mutt@thomer.com>
5  *
6  * Parts were written/modified by:
7  * Rocco Rutte <pdmef@cs.tu-berlin.de>
8  * Nico Golde <nico@ngolde.de>
9  *
10  * This file is part of mutt-ng, see http://www.muttng.org/.
11  * It's licensed under the GNU General Public License,
12  * please see the file GPL in the top level source directory.
13  */
14
15 #include <lib-ui/lib-ui.h>
16 #include <libgen.h>
17
18 #include <lib-ui/menu.h>
19 #include <lib-ui/sidebar.h>
20
21 #include "mutt.h"
22 #include "charset.h"
23 #include "buffy.h"
24 #include "keymap.h"
25
26 static int CurBuffy = 0;
27
28 /* computes first entry to be shown */
29 static int calc_boundaries(void)
30 {
31     int TopBuffy = 0;
32
33     if (CurBuffy < 0 || CurBuffy >= Incoming.len)
34         CurBuffy = 0;
35
36     if (option(OPTSIDEBARNEWMAILONLY)) {
37         int n = 0;
38
39         for (int i = 0; i < CurBuffy; i++) {
40             n += Incoming.arr[i]->new > 0;
41         }
42
43         n %= (LINES - 3);
44         if (!n)
45             return CurBuffy;
46
47         for (int i = CurBuffy - 1; i >= 0; i--) {
48             if (Incoming.arr[i]->new > 0 && --n <= 0) {
49                 return i;
50             }
51         }
52     } else {
53         TopBuffy = CurBuffy - (CurBuffy % (LINES - 3));
54     }
55
56     return TopBuffy < 0 ? 0 : TopBuffy;
57 }
58
59 static char *shortened_hierarchy (char *hbox, int maxlen)
60 {
61   int dots = 0;
62   char *last_dot = NULL;
63   int i, j, len = m_strlen(hbox);
64   char *new_box;
65
66   if (!SidebarBoundary || !*SidebarBoundary)
67     return (m_strdup(hbox));
68
69   for (i = 0; i < len; ++i) {
70     if (strchr (SidebarBoundary, hbox[i])) {
71       ++dots;
72       last_dot = &hbox[i];
73     }
74   }
75
76   if (last_dot) {
77     ++last_dot;
78     new_box = p_new(char, maxlen + 1);
79     new_box[0] = hbox[0];
80     for (i = 1, j = 1; j < maxlen && i < len; ++i) {
81       if (strchr (SidebarBoundary, hbox[i])) {
82         new_box[j++] = hbox[i];
83         new_box[j] = 0;
84         if (&hbox[i + 1] != last_dot || j + m_strlen(last_dot) > maxlen) {
85           new_box[j++] = hbox[i + 1];
86           new_box[j] = 0;
87         } else {
88           m_strcat(&new_box[j], maxlen + 1, last_dot);
89           break;
90         }
91       }
92     }
93     return new_box;
94   }
95   return m_strdup(hbox);
96 }
97
98 static const char *
99 sidebar_number_format(char* dest, ssize_t destlen,
100                       char op, const char* src, const char* fmt,
101                       const char* ifstr, const char* elstr,
102                       anytype data, format_flag flags)
103 {
104   char tmp[STRING];
105   BUFFY* b = Incoming.arr[data.i];
106   int opt = flags & M_FORMAT_OPTIONAL;
107   int c = Context && !m_strcmp(Context->path, b->path);
108
109   switch (op) {
110     /* deleted */
111     case 'd':
112       if (!opt) {
113         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
114         snprintf (dest, destlen, tmp, c ? Context->deleted : 0);
115       } else if ((c && Context->deleted == 0) || !c)
116         opt = 0;
117       break;
118     /* flagged */
119     case 'F':
120     case 'f':                   /* for compatibility */
121       if (!opt) {
122         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
123         snprintf (dest, destlen, tmp, c ? Context->flagged : b->msg_flagged);
124       } else if ((c && Context->flagged == 0) || (!c && b->msg_flagged == 0))
125         opt = 0;
126       break;
127     /* total */
128     case 'c':                   /* for compatibility */
129     case 'm':
130       if (!opt) {
131         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
132         snprintf (dest, destlen, tmp, c ? Context->msgcount : b->msgcount);
133       } else if ((c && Context->msgcount == 0) || (!c && b->msgcount == 0))
134         opt = 0;
135       break;
136     /* total shown, i.e. not hidden by limit */
137     case 'M':
138       if (!opt) {
139         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
140         snprintf (dest, destlen, tmp, c ? Context->vcount : 0);
141       } else if ((c && Context->vcount == 0) || !c)
142         opt = 0;
143       break;
144     /* new */
145     case 'n':
146       if (!opt) {
147         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
148         snprintf (dest, destlen, tmp, c ? Context->new : b->new);
149       } else if ((c && Context->new == 0) || (!c && b->new == 0))
150         opt = 0;
151       break;
152     /* unread */
153     case 'u':
154       if (!opt) {
155         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
156         snprintf (dest, destlen, tmp, c ? Context->unread : b->msg_unread);
157       } else if ((c && Context->unread == 0) || (!c && b->msg_unread == 0))
158         opt = 0;
159       break;
160     /* tagged */
161     case 't':
162       if (!opt) {
163         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
164         snprintf (dest, destlen, tmp, c ? Context->tagged : 0);
165       } else if ((c && Context->tagged == 0) || !c)
166         opt = 0;
167       break;
168   }
169
170   if (flags & M_FORMAT_OPTIONAL)
171     m_strformat(dest, destlen, 0, opt ? ifstr : elstr,
172                 sidebar_number_format, data, flags);
173   return src;
174 }
175
176 int sidebar_need_count(void)
177 {
178     return ui_layout_sidebar_w() && !m_strisempty(SidebarNumberFormat);
179 }
180
181 /* print single item
182  * returns:
183  *      0       item was not printed ('cause of $sidebar_newmail_only)
184  *      1       item was printed
185  */
186 static int make_sidebar_entry(WINDOW *sw, char *sbox, int idx, ssize_t len)
187 {
188     int shortened = 0, lencnt = 0;
189     char no[STRING], entry[STRING];
190     int l_m = m_strlen(Maildir);
191
192     if (option(OPTSIDEBARNEWMAILONLY) && sbox && Context && Context->path
193     &&  m_strcmp(Context->path, sbox) && Incoming.arr[idx]->new == 0
194     &&  idx != CurBuffy)
195         return 0;
196
197     m_strformat(no, sizeof(no), len, SidebarNumberFormat,
198                 sidebar_number_format, idx, 0);
199     lencnt = m_strlen(no);
200
201     if (l_m > 0 && m_strncmp(sbox, Maildir, l_m) == 0 && m_strlen(sbox) > l_m)
202     {
203         sbox += l_m;
204         if (Maildir[strlen(Maildir) - 1] != '/') {
205             sbox += 1;
206         }
207     } else {
208         sbox = basename(sbox);
209     }
210
211     if (option(OPTSHORTENHIERARCHY) && m_strlen(sbox) > len - lencnt - 1) {
212         sbox = shortened_hierarchy(sbox, len - lencnt - 1);
213         shortened = 1;
214     }
215
216     snprintf(entry, sizeof(entry), "%*s", (int)len, no);
217     memcpy(entry, sbox, MIN(len, m_strlen(sbox)));
218     waddnstr(sw, entry, len);
219
220     if (shortened)
221         p_delete(&sbox);
222
223     return 1;
224 }
225
226 /* returns folder name of currently 
227  * selected folder for <sidebar-open>
228  */
229 const char *sidebar_get_current(void)
230 {
231     if (0 <= CurBuffy && CurBuffy < Incoming.len)
232         return Incoming.arr[CurBuffy]->path;
233     return NULL;
234 }
235
236 /* internally sets item to buf */
237 void sidebar_set_current(const char* buf)
238 {
239     int i = buffy_lookup(buf);
240     if (i >= 0) {
241         CurBuffy = i;
242     }
243 }
244
245 /* fix counters for a context
246  * FIXME since ctx must not be of our business, move it elsewhere
247  */
248 void sidebar_set_buffystats(CONTEXT *curContext)
249 {
250     int i = 0;
251     BUFFY *tmp;
252
253     if (!curContext || (i = buffy_lookup(curContext->path)) < 0)
254         return;
255     tmp = Incoming.arr[i];
256     tmp->new         = curContext->new;
257     tmp->msg_unread  = curContext->unread;
258     tmp->msgcount    = curContext->msgcount;
259     tmp->msg_flagged = curContext->flagged;
260 }
261
262 int sidebar_draw(void)
263 {
264     static short initialized = false, prev_show_value = 0;
265     int x, y, i, line;
266     char blank[STRING];
267     WINDOW *sw;
268
269     /* initialize first time */
270     if (!initialized) {
271         prev_show_value = option(OPTMBOXPANE);
272         initialized = 1;
273     }
274
275     /* save or restore the value SidebarWidth */
276     if (prev_show_value != option(OPTMBOXPANE)) {
277         if (!prev_show_value && option(OPTMBOXPANE)) {
278             /* after toggle: force recounting of all mail */
279             buffy_check (2);
280         }
281         prev_show_value = option(OPTMBOXPANE);
282     }
283
284     sw = ui_layout_sidebar_w();
285     if (!sw)
286         return 0;
287     getmaxyx(sw, y, x);
288
289     memset(&blank, ' ', sizeof(blank));
290
291     wmove(sw, 0, 0);
292     WSETCOLOR(sw, MT_COLOR_STATUS);
293     waddnstr(sw, blank, x);
294
295     line = 1;
296     for (i = calc_boundaries(); i < Incoming.len && line < y - 1; i++) {
297         BUFFY *tmp = Incoming.arr[i];
298
299         if (i == CurBuffy)
300             WSETCOLOR(sw, MT_COLOR_INDICATOR);
301         else if (tmp->new > 0)
302             WSETCOLOR(sw, MT_COLOR_NEW);
303         else if (tmp->msg_flagged > 0)
304             WSETCOLOR(sw, MT_COLOR_FLAGGED);
305         else
306             WSETCOLOR(sw, MT_COLOR_NORMAL);
307
308         if (make_sidebar_entry(sw, tmp->path, i, x - 1)) {
309             WSETCOLOR(sw, MT_COLOR_SIDEBAR);
310             waddch(sw, ACS_VLINE);
311             line++;
312         }
313     }
314
315     while (line < y - 1) {
316         WSETCOLOR(sw, MT_COLOR_NORMAL);
317         waddnstr(sw, blank, x - 1);
318         WSETCOLOR(sw, MT_COLOR_SIDEBAR);
319         waddch(sw, ACS_VLINE);
320         line++;
321     }
322
323     WSETCOLOR(sw, MT_COLOR_STATUS);
324     waddnstr(sw, blank, x);
325     WSETCOLOR(sw, MT_COLOR_NORMAL);
326
327     return 0;
328 }
329
330 void sidebar_scroll(int op)
331 {
332     if (!Incoming.len)
333         return;
334
335     switch (op) {
336       case OP_SIDEBAR_NEXT:
337         if (!option(OPTSIDEBARNEWMAILONLY)) {
338             CurBuffy = (CurBuffy + 1) % Incoming.len;
339             break;
340         }
341         /* FALLTHROUGH */
342       case OP_SIDEBAR_NEXT_NEW:
343         for (int i = 1; i < Incoming.len; i++) {
344             if (Incoming.arr[(CurBuffy + i) % Incoming.len]->new > 0) {
345                 CurBuffy = (CurBuffy + i) % Incoming.len;
346                 break;
347             }
348         }
349         break;
350
351       case OP_SIDEBAR_PREV:
352         if (!option(OPTSIDEBARNEWMAILONLY)) {
353             if (!CurBuffy) {
354                 CurBuffy = Incoming.len;
355             }
356             CurBuffy--;
357             break;
358         }
359         /* FALLTHROUGH */
360       case OP_SIDEBAR_PREV_NEW:
361         for (int i = Incoming.len - 1; i > 0; i--) {
362             if (Incoming.arr[(CurBuffy + i) % Incoming.len]->new > 0) {
363                 CurBuffy = (CurBuffy + i) % Incoming.len;
364                 break;
365             }
366         }
367         break;
368
369       case OP_SIDEBAR_SCROLL_UP:
370         CurBuffy -= LINES - 3;
371         if (CurBuffy < 0)
372             CurBuffy = 0;
373         break;
374       case OP_SIDEBAR_SCROLL_DOWN:
375         CurBuffy += LINES - 3;
376         if (CurBuffy >= Incoming.len)
377             CurBuffy = Incoming.len - 1;
378         break;
379       default:
380         return;
381     }
382
383     sidebar_draw();
384 }