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