Have a lib-ui/lib-ui.h
[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 TopBuffy = 0;
27 static int CurBuffy = 0;
28 static int known_lines = 0;
29 static short initialized = 0;
30 static short prev_show_value;
31
32 /* computes first entry to be shown */
33 static void calc_boundaries (void) {
34   if (!Incoming.len)
35     return;
36   if (CurBuffy < 0 || CurBuffy >= Incoming.len)
37     CurBuffy = 0;
38   if (TopBuffy < 0 || TopBuffy >= Incoming.len)
39     TopBuffy = 0;
40
41   if (option (OPTSIDEBARNEWMAILONLY)) {
42     int i = CurBuffy;
43     TopBuffy = CurBuffy - 1;
44     while (i >= 0) {
45       if (Incoming.arr[i]->new > 0)
46         TopBuffy = i;
47       i--;
48     }
49   } else if (known_lines>0)
50     TopBuffy = CurBuffy - (CurBuffy % known_lines);
51   if (TopBuffy < 0)
52     TopBuffy = 0;
53 }
54
55 static char *shortened_hierarchy (char *hbox, int maxlen)
56 {
57   int dots = 0;
58   char *last_dot = NULL;
59   int i, j, len = m_strlen(hbox);
60   char *new_box;
61
62   if (!SidebarBoundary || !*SidebarBoundary)
63     return (m_strdup(hbox));
64
65   for (i = 0; i < len; ++i) {
66     if (strchr (SidebarBoundary, hbox[i])) {
67       ++dots;
68       last_dot = &hbox[i];
69     }
70   }
71
72   if (last_dot) {
73     ++last_dot;
74     new_box = p_new(char, maxlen + 1);
75     new_box[0] = hbox[0];
76     for (i = 1, j = 1; j < maxlen && i < len; ++i) {
77       if (strchr (SidebarBoundary, hbox[i])) {
78         new_box[j++] = hbox[i];
79         new_box[j] = 0;
80         if (&hbox[i + 1] != last_dot || j + m_strlen(last_dot) > maxlen) {
81           new_box[j++] = hbox[i + 1];
82           new_box[j] = 0;
83         } else {
84           m_strcat(&new_box[j], maxlen + 1, last_dot);
85           break;
86         }
87       }
88     }
89     return new_box;
90   }
91   return m_strdup(hbox);
92 }
93
94 static const char *
95 sidebar_number_format(char* dest, ssize_t destlen,
96                       char op, const char* src, const char* fmt,
97                       const char* ifstr, const char* elstr,
98                       anytype data, format_flag flags)
99 {
100   char tmp[STRING];
101   BUFFY* b = Incoming.arr[data.i];
102   int opt = flags & M_FORMAT_OPTIONAL;
103   int c = Context && !m_strcmp(Context->path, b->path);
104
105   switch (op) {
106     /* deleted */
107     case 'd':
108       if (!opt) {
109         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
110         snprintf (dest, destlen, tmp, c ? Context->deleted : 0);
111       } else if ((c && Context->deleted == 0) || !c)
112         opt = 0;
113       break;
114     /* flagged */
115     case 'F':
116     case 'f':                   /* for compatibility */
117       if (!opt) {
118         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
119         snprintf (dest, destlen, tmp, c ? Context->flagged : b->msg_flagged);
120       } else if ((c && Context->flagged == 0) || (!c && b->msg_flagged == 0))
121         opt = 0;
122       break;
123     /* total */
124     case 'c':                   /* for compatibility */
125     case 'm':
126       if (!opt) {
127         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
128         snprintf (dest, destlen, tmp, c ? Context->msgcount : b->msgcount);
129       } else if ((c && Context->msgcount == 0) || (!c && b->msgcount == 0))
130         opt = 0;
131       break;
132     /* total shown, i.e. not hidden by limit */
133     case 'M':
134       if (!opt) {
135         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
136         snprintf (dest, destlen, tmp, c ? Context->vcount : 0);
137       } else if ((c && Context->vcount == 0) || !c)
138         opt = 0;
139       break;
140     /* new */
141     case 'n':
142       if (!opt) {
143         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
144         snprintf (dest, destlen, tmp, c ? Context->new : b->new);
145       } else if ((c && Context->new == 0) || (!c && b->new == 0))
146         opt = 0;
147       break;
148     /* unread */
149     case 'u':
150       if (!opt) {
151         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
152         snprintf (dest, destlen, tmp, c ? Context->unread : b->msg_unread);
153       } else if ((c && Context->unread == 0) || (!c && b->msg_unread == 0))
154         opt = 0;
155       break;
156     /* tagged */
157     case 't':
158       if (!opt) {
159         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
160         snprintf (dest, destlen, tmp, c ? Context->tagged : 0);
161       } else if ((c && Context->tagged == 0) || !c)
162         opt = 0;
163       break;
164   }
165
166   if (flags & M_FORMAT_OPTIONAL)
167     m_strformat(dest, destlen, 0, opt ? ifstr : elstr,
168                 sidebar_number_format, data, flags);
169   return src;
170 }
171
172 int sidebar_need_count (void) {
173   if (!option (OPTMBOXPANE) || SidebarWidth == 0 ||
174       !SidebarNumberFormat || !*SidebarNumberFormat)
175     return (0);
176   return (1);
177 }
178
179 /* print single item
180  * returns:
181  *      0       item was not printed ('cause of $sidebar_newmail_only)
182  *      1       item was printed
183  */
184 static int make_sidebar_entry (char* sbox, int idx, ssize_t len)
185 {
186   int shortened = 0, lencnt = 0;
187   char no[STRING], entry[STRING];
188   int l_m = m_strlen(Maildir);
189
190   if (SidebarWidth > COLS)
191     SidebarWidth = COLS;
192
193   if (option (OPTSIDEBARNEWMAILONLY) && sbox && Context && Context->path && 
194       m_strcmp(Context->path, sbox) && Incoming.arr[idx]->new == 0)
195     /* if $sidebar_newmail_only is set, don't display the
196      * box only if it's not the currently opened
197      * (i.e. always display the currently opened) */
198     return 0;
199
200   m_strformat(no, len, SidebarWidth, SidebarNumberFormat, sidebar_number_format, idx, 0);
201   lencnt = m_strlen(no);
202
203   if (l_m > 0 && m_strncmp(sbox, Maildir, l_m) == 0 && 
204       m_strlen(sbox) > l_m) {
205     sbox += l_m;
206     if (Maildir[strlen(Maildir)-1]!='/') {
207       sbox += 1;
208     }
209   } else
210     sbox = basename (sbox);
211
212   if (option(OPTSHORTENHIERARCHY) && m_strlen(sbox) > len-lencnt-1) {
213     sbox = shortened_hierarchy (sbox, len-lencnt-1);
214     shortened = 1;
215   }
216
217   snprintf(entry, sizeof(entry), "%*s", (int)len, no);
218   memcpy(entry, sbox, MIN(len - 1, m_strlen(sbox)));
219   waddnstr(stdscr, entry, len);
220
221   if (shortened)
222     p_delete(&sbox);
223
224   return 1;
225 }
226
227 /* returns folder name of currently 
228  * selected folder for <sidebar-open>
229  */
230 const char* sidebar_get_current (void) {
231   if (!Incoming.len)
232     return (NULL);
233   return Incoming.arr[CurBuffy]->path;
234 }
235
236 /* internally sets item to buf */
237 void sidebar_set_current (const char* buf) {
238   int i = buffy_lookup (buf);
239   if (i >= 0) {
240     CurBuffy = i;
241     calc_boundaries();
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   int i = 0;
250   BUFFY* tmp = NULL;
251   if (!curContext || !Incoming.len || (i = buffy_lookup (curContext->path)) < 0)
252     return;
253   tmp = Incoming.arr[i];
254   tmp->new = curContext->new;
255   tmp->msg_unread = curContext->unread;
256   tmp->msgcount = curContext->msgcount;
257   tmp->msg_flagged = curContext->flagged;
258 }
259
260 void sidebar_draw_frames (void) {
261   ssize_t i,delim_len;
262
263   if (!option(OPTMBOXPANE) || SidebarWidth==0) 
264     return;
265
266   delim_len=m_strlen(NONULL(SidebarDelim));
267
268   /* draw vertical delimiter */
269   SETCOLOR (MT_COLOR_SIDEBAR);
270   for (i = 0; i < LINES-1; i++) {
271     wmove (stdscr, i, SidebarWidth - delim_len);
272     if (!m_strcmp(SidebarDelim, "|"))
273       waddch (stdscr, ACS_VLINE);
274     else
275       waddstr (stdscr, NONULL (SidebarDelim));
276   }
277
278   /* fill "gaps" at top+bottom */
279   SETCOLOR(MT_COLOR_STATUS);
280   for (i=0; i<SidebarWidth; i++) {
281     /*
282      * if we don't have $status_on_top and have $help, fill top
283      * gap with spaces to get bg color
284      */
285     if (option(OPTSTATUSONTOP) || option(OPTHELP)) {
286       wmove(stdscr, 0,i);
287       waddch(stdscr, ' ');
288     }
289     /*
290       * if we don't have $status_on_top or we have $help, fill bottom
291       * gap with spaces to get bg color
292       */
293     if (!option(OPTSTATUSONTOP) || option(OPTHELP)) {
294       wmove(stdscr, LINES-2,i);
295       waddch(stdscr, ' ');
296     }
297   }
298   SETCOLOR (MT_COLOR_NORMAL);
299 }
300
301 /* actually draws something
302  * FIXME this needs some clue when to do it
303  */
304 int sidebar_draw (void) {
305   int first_line = option (OPTSTATUSONTOP) ? 1 : option (OPTHELP) ? 1 : 0,
306       last_line = LINES - 2 + (option (OPTSTATUSONTOP) && !option (OPTHELP) ? 1 : 0),
307       i = 0,line;
308   BUFFY *tmp;
309   ssize_t delim_len = m_strlen(SidebarDelim);
310   char blank[STRING];
311
312   known_lines=last_line-first_line;
313
314   /* initialize first time */
315   if (!initialized) {
316     prev_show_value = option (OPTMBOXPANE);
317     initialized = 1;
318   }
319
320   if (TopBuffy==0 || CurBuffy==0)
321     calc_boundaries();
322
323   /* save or restore the value SidebarWidth */
324   if (prev_show_value != option (OPTMBOXPANE)) {
325     if (!prev_show_value && option (OPTMBOXPANE)) {
326       /* after toggle: force recounting of all mail */
327       buffy_check (2);
328     }
329     prev_show_value = option (OPTMBOXPANE);
330   }
331
332   if (SidebarWidth > 0 && option (OPTMBOXPANE)
333       && m_strlen(SidebarDelim) >= SidebarWidth) {
334     mutt_error (_("Value for sidebar_delim is too long. Disabling sidebar."));
335     sleep (2);
336     unset_option (OPTMBOXPANE);
337     return (0);
338   }
339
340   if (SidebarWidth == 0 || !option (OPTMBOXPANE))
341     return 0;
342
343   sidebar_draw_frames();
344
345   if (!Incoming.len)
346     return 0;
347
348   /* actually print items */
349   for (i = TopBuffy, line=first_line; i < Incoming.len && line < last_line; i++) {
350     tmp = Incoming.arr[i];
351
352     if (i == CurBuffy)
353       SETCOLOR (MT_COLOR_INDICATOR);
354     else if (tmp->new > 0)
355       SETCOLOR (MT_COLOR_NEW);
356     else if (tmp->msg_flagged > 0)
357       SETCOLOR (MT_COLOR_FLAGGED);
358     else
359       SETCOLOR (MT_COLOR_NORMAL);
360
361     wmove (stdscr, line, 0);
362     line += make_sidebar_entry (tmp->path, i, SidebarWidth-delim_len);
363   }
364
365   SETCOLOR (MT_COLOR_NORMAL);
366
367   /* fill with blanks to bottom */
368   memset(&blank, ' ', sizeof(blank));
369   for (; line < last_line; line++) {
370     wmove (stdscr, line, 0);
371     waddnstr (stdscr, blank, SidebarWidth-delim_len);
372   }
373   return 0;
374 }
375
376 /* returns index of new item with new mail or -1 */
377 static int exist_next_new () {
378   int i = 0;
379   if (!Incoming.len)
380     return (-1);
381   i = CurBuffy + 1;
382   while (i < Incoming.len)
383     if (Incoming.arr[i++]->new > 0)
384       return (i-1);
385   return (-1);
386 }
387
388 /* returns index of prev item with new mail or -1 */
389 static int exist_prev_new () {
390   int i = 0;
391   if (!Incoming.len)
392     return (-1);
393   i = CurBuffy - 1;
394   while (i >= 0)
395     if (Incoming.arr[i--]->new > 0)
396       return (i+1);
397   return (-1);
398 }
399
400 void sidebar_scroll (int op) {
401   int i = 0;
402
403   if (!SidebarWidth || !Incoming.len)
404     return;
405
406   switch (op) {
407   case OP_SIDEBAR_NEXT:
408     if (!option (OPTSIDEBARNEWMAILONLY)) {
409       if (CurBuffy + 1 == Incoming.len) {
410         mutt_error (_("You are on the last mailbox."));
411         return;
412       }
413       CurBuffy++;
414       break;
415     }                           /* the fall-through is intentional */
416   case OP_SIDEBAR_NEXT_NEW:
417     if ((i = exist_next_new ()) < 0) {
418       mutt_error (_("No next mailboxes with new mail."));
419       return;
420     }
421     else
422       CurBuffy = i;
423     break;
424   case OP_SIDEBAR_PREV:
425     if (!option (OPTSIDEBARNEWMAILONLY)) {
426       if (CurBuffy == 0) {
427         mutt_error (_("You are on the first mailbox."));
428         return;
429       }
430       CurBuffy--;
431       break;
432     }                           /* the fall-through is intentional */
433   case OP_SIDEBAR_PREV_NEW:
434     if ((i = exist_prev_new ()) < 0) {
435       mutt_error (_("No previous mailbox with new mail."));
436       return;
437     }
438     else
439       CurBuffy = i;
440     break;
441
442   case OP_SIDEBAR_SCROLL_UP:
443     if (CurBuffy == 0) {
444       mutt_error (_("You are on the first mailbox."));
445       return;
446     }
447     CurBuffy -= known_lines;
448     if (CurBuffy < 0)
449       CurBuffy = 0;
450     break;
451   case OP_SIDEBAR_SCROLL_DOWN:
452     if (CurBuffy + 1 == Incoming.len) {
453       mutt_error (_("You are on the last mailbox."));
454       return;
455     }
456     CurBuffy += known_lines;
457     if (CurBuffy >= Incoming.len)
458       CurBuffy = Incoming.len - 1;
459     break;
460   default:
461     return;
462   }
463   calc_boundaries ();
464   sidebar_draw ();
465 }