1 /**
2  * @file
3  * GUI display a user-configurable status line
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2007 Michael R. Elkins <me@mutt.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page neo_status GUI display a user-configurable status line
25  *
26  * GUI display a user-configurable status line
27  */
28 
29 #include "config.h"
30 #include <stdbool.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <sys/types.h>
34 #include "mutt/lib.h"
35 #include "config/lib.h"
36 #include "core/lib.h"
37 #include "status.h"
38 #include "index/lib.h"
39 #include "menu/lib.h"
40 #include "context.h"
41 #include "format_flags.h"
42 #include "mutt_globals.h"
43 #include "mutt_mailbox.h"
44 #include "mutt_thread.h"
45 #include "muttlib.h"
46 #include "options.h"
47 #include "protos.h"
48 
49 /**
50  * get_sort_str - Get the sort method as a string
51  * @param buf    Buffer for the sort string
52  * @param buflen Length of the buffer
53  * @param method Sort method, see #SortType
54  * @retval ptr Buffer pointer
55  */
get_sort_str(char * buf,size_t buflen,enum SortType method)56 static char *get_sort_str(char *buf, size_t buflen, enum SortType method)
57 {
58   snprintf(buf, buflen, "%s%s%s", (method & SORT_REVERSE) ? "reverse-" : "",
59            (method & SORT_LAST) ? "last-" : "",
60            mutt_map_get_name(method & SORT_MASK, SortMethods));
61   return buf;
62 }
63 
64 /**
65  * struct MenuStatusLineData - Data for creating a Menu line
66  */
67 struct MenuStatusLineData
68 {
69   struct IndexSharedData *shared; ///< Data shared between Index, Pager and Sidebar
70   struct Menu *menu;              ///< Current Menu
71 };
72 
73 /**
74  * status_format_str - Create the status bar string - Implements ::format_t - @ingroup expando_api
75  *
76  * | Expando | Description
77  * |:--------|:--------------------------------------------------------
78  * | \%b     | Number of incoming folders with unread messages
79  * | \%D     | Description of the mailbox
80  * | \%d     | Number of deleted messages
81  * | \%f     | Full mailbox path
82  * | \%F     | Number of flagged messages
83  * | \%h     | Hostname
84  * | \%l     | Length of mailbox (in bytes)
85  * | \%L     | Size (in bytes) of the messages shown (or limited)
86  * | \%M     | Number of messages shown (virtual message count when limiting)
87  * | \%m     | Total number of messages
88  * | \%n     | Number of new messages
89  * | \%o     | Number of old unread messages
90  * | \%p     | Number of postponed messages
91  * | \%P     | Percent of way through index
92  * | \%R     | Number of read messages
93  * | \%r     | Readonly/wontwrite/changed flag
94  * | \%S     | Current aux sorting method (`$sort_aux`)
95  * | \%s     | Current sorting method (`$sort`)
96  * | \%T     | Current threading view (`$use_threads`)
97  * | \%t     | Number of tagged messages
98  * | \%u     | Number of unread messages
99  * | \%V     | Currently active limit pattern
100  * | \%v     | NeoMutt version
101  */
status_format_str(char * buf,size_t buflen,size_t col,int cols,char op,const char * src,const char * prec,const char * if_str,const char * else_str,intptr_t data,MuttFormatFlags flags)102 static const char *status_format_str(char *buf, size_t buflen, size_t col, int cols,
103                                      char op, const char *src, const char *prec,
104                                      const char *if_str, const char *else_str,
105                                      intptr_t data, MuttFormatFlags flags)
106 {
107   char fmt[128], tmp[128];
108   bool optional = (flags & MUTT_FORMAT_OPTIONAL);
109   struct MenuStatusLineData *msld = (struct MenuStatusLineData *) data;
110   struct IndexSharedData *shared = msld->shared;
111   struct Context *ctx = shared->ctx;
112   struct Mailbox *m = shared->mailbox;
113   struct Menu *menu = msld->menu;
114 
115   *buf = '\0';
116   switch (op)
117   {
118     case 'b':
119     {
120       const int num = mutt_mailbox_check(m, 0);
121       if (!optional)
122       {
123         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
124         snprintf(buf, buflen, fmt, num);
125       }
126       else if (num == 0)
127         optional = false;
128       break;
129     }
130 
131     case 'd':
132     {
133       const int num = m ? m->msg_deleted : 0;
134       if (!optional)
135       {
136         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
137         snprintf(buf, buflen, fmt, num);
138       }
139       else if (num == 0)
140         optional = false;
141       break;
142     }
143 
144     case 'D':
145       // If there's a descriptive name, use it. Otherwise, fall-through
146       if (m && m->name)
147       {
148         mutt_str_copy(tmp, m->name, sizeof(tmp));
149         snprintf(fmt, sizeof(fmt), "%%%ss", prec);
150         snprintf(buf, buflen, fmt, tmp);
151         break;
152       }
153     /* fallthrough */
154     case 'f':
155 #ifdef USE_COMP_MBOX
156       if (m && m->compress_info && (m->realpath[0] != '\0'))
157       {
158         mutt_str_copy(tmp, m->realpath, sizeof(tmp));
159         mutt_pretty_mailbox(tmp, sizeof(tmp));
160       }
161       else
162 #endif
163           if (m && (m->type == MUTT_NOTMUCH) && m->name)
164       {
165         mutt_str_copy(tmp, m->name, sizeof(tmp));
166       }
167       else if (m && !mutt_buffer_is_empty(&m->pathbuf))
168       {
169         mutt_str_copy(tmp, mailbox_path(m), sizeof(tmp));
170         mutt_pretty_mailbox(tmp, sizeof(tmp));
171       }
172       else
173         mutt_str_copy(tmp, _("(no mailbox)"), sizeof(tmp));
174 
175       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
176       snprintf(buf, buflen, fmt, tmp);
177       break;
178     case 'F':
179     {
180       const int num = m ? m->msg_flagged : 0;
181       if (!optional)
182       {
183         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
184         snprintf(buf, buflen, fmt, num);
185       }
186       else if (num == 0)
187         optional = false;
188       break;
189     }
190 
191     case 'h':
192       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
193       snprintf(buf, buflen, fmt, NONULL(ShortHostname));
194       break;
195 
196     case 'l':
197     {
198       const off_t num = m ? m->size : 0;
199       if (!optional)
200       {
201         snprintf(fmt, sizeof(fmt), "%%%ss", prec);
202         mutt_str_pretty_size(tmp, sizeof(tmp), num);
203         snprintf(buf, buflen, fmt, tmp);
204       }
205       else if (num == 0)
206         optional = false;
207       break;
208     }
209 
210     case 'L':
211       if (!optional)
212       {
213         snprintf(fmt, sizeof(fmt), "%%%ss", prec);
214         mutt_str_pretty_size(tmp, sizeof(tmp), ctx ? ctx->vsize : 0);
215         snprintf(buf, buflen, fmt, tmp);
216       }
217       else if (!ctx_has_limit(ctx))
218         optional = false;
219       break;
220 
221     case 'm':
222     {
223       const int num = m ? m->msg_count : 0;
224       if (!optional)
225       {
226         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
227         snprintf(buf, buflen, fmt, num);
228       }
229       else if (num == 0)
230         optional = false;
231       break;
232     }
233 
234     case 'M':
235       if (!optional)
236       {
237         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
238         snprintf(buf, buflen, fmt, m ? m->vcount : 0);
239       }
240       else if (!ctx_has_limit(ctx))
241         optional = false;
242       break;
243 
244     case 'n':
245     {
246       const int num = m ? m->msg_new : 0;
247       if (!optional)
248       {
249         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
250         snprintf(buf, buflen, fmt, num);
251       }
252       else if (num == 0)
253         optional = false;
254       break;
255     }
256 
257     case 'o':
258     {
259       const int num = m ? (m->msg_unread - m->msg_new) : 0;
260       if (!optional)
261       {
262         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
263         snprintf(buf, buflen, fmt, num);
264       }
265       else if (num == 0)
266         optional = false;
267       break;
268     }
269 
270     case 'p':
271     {
272       const int count = mutt_num_postponed(m, false);
273       if (!optional)
274       {
275         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
276         snprintf(buf, buflen, fmt, count);
277       }
278       else if (count == 0)
279         optional = false;
280       break;
281     }
282 
283     case 'P':
284     {
285       if (!menu)
286         break;
287       char *cp = NULL;
288       if (menu->top + menu->pagelen >= menu->max)
289       {
290         cp = menu->top ?
291                  /* L10N: Status bar message: the end of the list emails is visible in the index */
292                  _("end") :
293                  /* L10N: Status bar message: all the emails are visible in the index */
294                  _("all");
295       }
296       else
297       {
298         int count = (100 * (menu->top + menu->pagelen)) / menu->max;
299         snprintf(tmp, sizeof(tmp), "%d%%", count);
300         cp = tmp;
301       }
302       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
303       snprintf(buf, buflen, fmt, cp);
304       break;
305     }
306 
307     case 'r':
308     {
309       size_t i = 0;
310 
311       if (m)
312       {
313         i = OptAttachMsg ? 3 :
314                            ((m->readonly || m->dontwrite) ? 2 :
315                             (m->changed ||
316                              /* deleted doesn't necessarily mean changed in IMAP */
317                              (m->type != MUTT_IMAP && m->msg_deleted)) ?
318                                                             1 :
319                                                             0);
320       }
321 
322       const struct MbTable *c_status_chars =
323           cs_subset_mbtable(NeoMutt->sub, "status_chars");
324       if (!c_status_chars || !c_status_chars->len)
325         buf[0] = '\0';
326       else if (i >= c_status_chars->len)
327         snprintf(buf, buflen, "%s", c_status_chars->chars[0]);
328       else
329         snprintf(buf, buflen, "%s", c_status_chars->chars[i]);
330       break;
331     }
332 
333     case 'R':
334     {
335       const int read = m ? (m->msg_count - m->msg_unread) : 0;
336       if (!optional)
337       {
338         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
339         snprintf(buf, buflen, fmt, read);
340       }
341       else if (read == 0)
342         optional = false;
343       break;
344     }
345 
346     case 's':
347     {
348       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
349       const short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
350       snprintf(buf, buflen, fmt, get_sort_str(tmp, sizeof(tmp), c_sort));
351       break;
352     }
353 
354     case 'S':
355     {
356       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
357       const short c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
358       snprintf(buf, buflen, fmt, get_sort_str(tmp, sizeof(tmp), c_sort_aux));
359       break;
360     }
361 
362     case 't':
363     {
364       const int num = m ? m->msg_tagged : 0;
365       if (!optional)
366       {
367         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
368         snprintf(buf, buflen, fmt, num);
369       }
370       else if (num == 0)
371         optional = false;
372       break;
373     }
374 
375     case 'T':
376     {
377       const enum UseThreads c_use_threads = mutt_thread_style();
378       if (!optional)
379       {
380         snprintf(fmt, sizeof(fmt), "%%%ss", prec);
381         snprintf(buf, buflen, fmt, get_use_threads_str(c_use_threads));
382       }
383       else if (c_use_threads == UT_FLAT)
384         optional = false;
385       break;
386     }
387 
388     case 'u':
389       if (!optional)
390       {
391         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
392         snprintf(buf, buflen, fmt, m ? m->msg_unread : 0);
393       }
394       else if (!m || (m->msg_unread == 0))
395         optional = false;
396       break;
397 
398     case 'v':
399       snprintf(buf, buflen, "%s", mutt_make_version());
400       break;
401 
402     case 'V':
403       if (!optional)
404       {
405         snprintf(fmt, sizeof(fmt), "%%%ss", prec);
406         snprintf(buf, buflen, fmt, ctx_has_limit(ctx) ? ctx->pattern : "");
407       }
408       else if (!ctx_has_limit(ctx))
409         optional = false;
410       break;
411 
412     case 0:
413       *buf = '\0';
414       return src;
415 
416     default:
417       snprintf(buf, buflen, "%%%s%c", prec, op);
418       break;
419   }
420 
421   if (optional)
422   {
423     mutt_expando_format(buf, buflen, col, cols, if_str, status_format_str, data,
424                         MUTT_FORMAT_NO_FLAGS);
425   }
426   else if (flags & MUTT_FORMAT_OPTIONAL)
427   {
428     mutt_expando_format(buf, buflen, col, cols, else_str, status_format_str,
429                         data, MUTT_FORMAT_NO_FLAGS);
430   }
431 
432   /* We return the format string, unchanged */
433   return src;
434 }
435 
436 /**
437  * menu_status_line - Create the status line
438  * @param[out] buf      Buffer in which to save string
439  * @param[in]  buflen   Buffer length
440  * @param[in]  shared   Shared Index data
441  * @param[in]  menu     Current menu
442  * @param[in]  cols     Maximum number of columns to use
443  * @param[in]  fmt      Format string
444  */
menu_status_line(char * buf,size_t buflen,struct IndexSharedData * shared,struct Menu * menu,int cols,const char * fmt)445 void menu_status_line(char *buf, size_t buflen, struct IndexSharedData *shared,
446                       struct Menu *menu, int cols, const char *fmt)
447 {
448   struct MenuStatusLineData data = { shared, menu };
449 
450   mutt_expando_format(buf, buflen, 0, cols, fmt, status_format_str,
451                       (intptr_t) &data, MUTT_FORMAT_NO_FLAGS);
452 }
453