* Replace body.c by body.obj in pith/makefile.wnt to fix
[alpine.git] / pith / mailindx.c
blobf9ec76dcbbd530baecdfeeac95ec070fc1ae08b1
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mailindx.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /* ========================================================================
6 * Copyright 2013-2018 Eduardo Chappa
7 * Copyright 2006-2008 University of Washington
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * ========================================================================
18 #include "../pith/headers.h"
19 #include "../pith/mailindx.h"
20 #include "../pith/mailview.h"
21 #include "../pith/flag.h"
22 #include "../pith/icache.h"
23 #include "../pith/msgno.h"
24 #include "../pith/thread.h"
25 #include "../pith/strlst.h"
26 #include "../pith/status.h"
27 #include "../pith/mailcmd.h"
28 #include "../pith/search.h"
29 #include "../pith/charset.h"
30 #include "../pith/reply.h"
31 #include "../pith/bldaddr.h"
32 #include "../pith/addrstring.h"
33 #include "../pith/news.h"
34 #include "../pith/util.h"
35 #include "../pith/pattern.h"
36 #include "../pith/sequence.h"
37 #include "../pith/color.h"
38 #include "../pith/stream.h"
39 #include "../pith/string.h"
40 #include "../pith/send.h"
41 #include "../pith/options.h"
42 #include "../pith/ablookup.h"
43 #ifdef _WINDOWS
44 #include "../pico/osdep/mswin.h"
45 #endif
48 * pointers to formatting functions
50 ICE_S *(*format_index_line)(INDEXDATA_S *);
51 void (*setup_header_widths)(MAILSTREAM *);
54 * pointer to optional load_overview functionality
56 void (*pith_opt_paint_index_hline)(MAILSTREAM *, long, ICE_S *);
59 * pointer to hook for saving index format state
61 void (*pith_opt_save_index_state)(int);
64 * hook to allow caller to insert cue that indicates a condensed
65 * thread relationship cue
67 int (*pith_opt_condense_thread_cue)(PINETHRD_S *, ICE_S *, char **, size_t *, int, int);
68 int (*pith_opt_truncate_sfstr)(void);
72 * Internal prototypes
74 void setup_for_thread_index_screen(void);
75 ICE_S *format_index_index_line(INDEXDATA_S *);
76 ICE_S *format_thread_index_line(INDEXDATA_S *);
77 int set_index_addr(INDEXDATA_S *, char *, ADDRESS *, char *, int, char *);
78 int ctype_is_fixed_length(IndexColType);
79 void setup_index_header_widths(MAILSTREAM *);
80 void setup_thread_header_widths(MAILSTREAM *);
81 int parse_index_format(char *, INDEX_COL_S **);
82 int index_in_overview(MAILSTREAM *);
83 ADDRESS *fetch_from(INDEXDATA_S *);
84 ADDRESS *fetch_sender(INDEXDATA_S *);
85 char *fetch_newsgroups(INDEXDATA_S *);
86 char *fetch_subject(INDEXDATA_S *);
87 char *fetch_date(INDEXDATA_S *);
88 long fetch_size(INDEXDATA_S *);
89 BODY *fetch_body(INDEXDATA_S *);
90 char *fetch_firsttext(INDEXDATA_S *idata, int);
91 char *fetch_header(INDEXDATA_S *idata, char *hdrname);
92 void subj_str(INDEXDATA_S *, char *, size_t, SubjKW, int, int, ICE_S *);
93 void key_str(INDEXDATA_S *, SubjKW, ICE_S *);
94 void header_str(INDEXDATA_S *, HEADER_TOK_S *, ICE_S *);
95 void prio_str(INDEXDATA_S *, IndexColType, ICE_S *);
96 void from_str(IndexColType, INDEXDATA_S *, char *, size_t, ICE_S *);
97 int day_of_week(struct date *);
98 int day_of_year(struct date *);
99 unsigned long ice_hash(ICE_S *);
100 char *left_adjust(int);
101 char *right_adjust(int);
102 char *format_str(int, int);
103 char *copy_format_str(int, int, char *, int);
104 void set_print_format(IELEM_S *, int, int);
105 void set_ielem_widths_in_field(IFIELD_S *);
108 #define BIGWIDTH 2047
111 /*----------------------------------------------------------------------
112 Initialize the index_disp_format array in ps_global from this
113 format string.
115 Args: format -- the string containing the format tokens
116 answer -- put the answer here, free first if there was a previous
117 value here
118 ----*/
119 void
120 init_index_format(char *format, INDEX_COL_S **answer)
122 char *p;
123 int i, w, monabb_width = 0, column = 0;
126 * Record the fact that SCORE appears in some index format. This
127 * is a heavy-handed approach. It will stick at 1 if any format ever
128 * contains score during this session. This is ok since it will just
129 * cause recalculation if wrong and these things rarely change much.
131 if(!ps_global->a_format_contains_score && format
132 && strstr(format, "SCORE")){
133 ps_global->a_format_contains_score = 1;
134 /* recalculate need for scores */
135 scores_are_used(SCOREUSE_INVALID);
138 set_need_format_setup(ps_global->mail_stream);
139 /* if custom format is specified, try it, else go with default */
140 if(!(format && *format && parse_index_format(format, answer))){
141 static INDEX_COL_S answer_default[] = {
142 {iStatus, Fixed, 3},
143 {iMessNo, WeCalculate},
144 {iSDateTime24, WeCalculate},
145 {iFromTo, Percent, 33}, /* percent of rest */
146 {iSizeNarrow, WeCalculate},
147 {iSubjKey, Percent, 67},
148 {iNothing}
151 if(*answer)
152 free_index_format(answer);
154 *answer = (INDEX_COL_S *)fs_get(sizeof(answer_default));
155 memcpy(*answer, answer_default, sizeof(answer_default));
159 * Test to see how long the month abbreviations are.
161 for(i = 1; i <= 12; i++){
162 p = month_abbrev_locale(i);
163 monabb_width = MAX(utf8_width(p), monabb_width);
166 monabb_width = MIN(MAX(2, monabb_width), 5);
169 * Fill in req_width's for WeCalculate items.
171 for(column = 0; (*answer)[column].ctype != iNothing; column++){
173 /* don't use strftime if we're not trying to use the LC_TIME stuff */
174 if(F_ON(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
175 switch((*answer)[column].ctype){
176 case iSDate:
177 (*answer)[column].ctype = iS1Date;
178 break;
179 case iSDateTime:
180 (*answer)[column].ctype = iSDateTimeS1;
181 break;
182 case iSDateTime24:
183 (*answer)[column].ctype = iSDateTimeS124;
184 break;
185 default:
186 break;
190 if((*answer)[column].wtype == WeCalculate){
191 switch((*answer)[column].ctype){
192 case iPrio:
193 case iPrioBang:
194 case iAtt:
195 (*answer)[column].req_width = 1;
196 break;
197 case iYear2Digit:
198 case iDay:
199 case iMon:
200 case iDay2Digit:
201 case iMon2Digit:
202 case iArrow:
203 case iKeyInit:
204 (*answer)[column].req_width = 2;
205 break;
206 case iStatus:
207 case iMessNo:
208 case iInit:
209 (*answer)[column].req_width = 3;
210 break;
211 case iYear:
212 case iDayOrdinal:
213 case iSIStatus:
214 (*answer)[column].req_width = 4;
215 break;
216 case iTime24:
217 case iTimezone:
218 case iSizeNarrow:
219 case iKey:
220 (*answer)[column].req_width = 5;
221 break;
222 case iFStatus:
223 case iIStatus:
224 case iScore:
225 (*answer)[column].req_width = 6;
226 break;
227 case iTime12:
228 case iSTime:
229 case iKSize:
230 case iSize:
231 case iPrioAlpha:
232 (*answer)[column].req_width = 7;
233 break;
234 case iS1Date:
235 case iS2Date:
236 case iS3Date:
237 case iS4Date:
238 case iDateIsoS:
239 case iSizeComma:
240 (*answer)[column].req_width = 8;
241 break;
242 case iSTime24:
243 (*answer)[column].req_width = 9;
244 break;
245 case iMonAbb:
246 (*answer)[column].req_width = monabb_width;
247 (*answer)[column].monabb_width = monabb_width;
248 break;
249 case iDayOfWeekAbb:
251 w = 0;
254 * Test to see how long it is.
256 for(i = 0; i < 7; i++){
257 p = day_abbrev_locale(i);
258 w = MAX(utf8_width(p), w);
261 (*answer)[column].req_width = MIN(MAX(2, w), 5);
263 break;
264 case iDate:
265 (*answer)[column].req_width = monabb_width + 3;
266 (*answer)[column].monabb_width = monabb_width;
267 break;
268 case iMonLong:
270 w = 0;
273 * Test to see how long it is.
275 for(i = 1; i <= 12; i++){
276 p = month_name_locale(i);
277 w = MAX(utf8_width(p), w);
280 (*answer)[column].req_width = MIN(MAX(3, w), 12);
282 break;
283 case iDayOfWeek:
285 w = 0;
287 for(i = 0; i < 7; i++){
288 p = day_name_locale(i);
289 w = MAX(utf8_width(p), w);
292 (*answer)[column].req_width = MIN(MAX(3, w), 12);
294 break;
295 case iSDate:
296 case iSDateTime:
297 case iSDateTime24:
298 case iPrefDate:
299 case iPrefTime:
300 case iPrefDateTime:
303 * Format a date to see how long it is.
304 * Make it as least as long as "Yesterday".
305 * We should really use the width of the longest
306 * of the translated yesterdays and friends but...
308 struct tm tm;
309 int len = 20;
310 char ss[100];
312 memset(&tm, 0, sizeof(tm));
313 tm.tm_year = 106;
314 tm.tm_mon = 11;
315 tm.tm_mday = 31;
316 tm.tm_hour = 3;
317 tm.tm_min = 23;
318 tm.tm_wday = 3;
319 switch((*answer)[column].ctype){
320 case iPrefTime:
321 our_strftime(ss, sizeof(ss), "%X", &tm);
322 break;
323 case iPrefDateTime:
324 our_strftime(ss, sizeof(ss), "%c", &tm);
325 len = 32;
326 break;
327 default:
328 our_strftime(ss, sizeof(ss), "%x", &tm);
329 break;
331 (*answer)[column].req_width = MIN(MAX(9, utf8_width(ss)), len);
334 (*answer)[column].monabb_width = monabb_width;
335 break;
337 case iDescripSize:
338 case iSDateIsoS:
339 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
340 case iSDateTimeIsoS:
341 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
342 case iSDateTimeIsoS24:
343 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
345 * These SDates are 8 wide but they need to be 9 for "Yesterday".
347 (*answer)[column].req_width = 9;
348 break;
349 case iDateIso:
350 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
351 (*answer)[column].req_width = 10;
352 break;
353 case iLDate:
354 (*answer)[column].req_width = 12;
355 (*answer)[column].monabb_width = monabb_width;
356 break;
357 case iRDate:
358 (*answer)[column].req_width = 16;
359 break;
360 default:
361 break;
366 calc_extra_hdrs();
367 if(get_extra_hdrs())
368 (void) mail_parameters(NULL, SET_IMAPEXTRAHEADERS,
369 (void *) get_extra_hdrs());
373 void
374 reset_index_format(void)
376 long rflags = ROLE_DO_OTHER;
377 PAT_STATE pstate;
378 PAT_S *pat;
379 int we_set_it = 0;
381 if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){
382 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
383 if(match_pattern(pat->patgrp, ps_global->mail_stream, NULL,
384 NULL, NULL, SE_NOSERVER|SE_NOPREFETCH))
385 break;
388 if(pat && pat->action && !pat->action->bogus
389 && pat->action->index_format){
390 we_set_it++;
391 init_index_format(pat->action->index_format,
392 &ps_global->index_disp_format);
396 if(!we_set_it)
397 init_index_format(ps_global->VAR_INDEX_FORMAT,
398 &ps_global->index_disp_format);
402 void
403 free_index_format(INDEX_COL_S **disp_format)
405 INDEX_COL_S *cdesc = NULL;
407 if(disp_format && *disp_format){
408 for(cdesc = (*disp_format); cdesc->ctype != iNothing; cdesc++)
409 if(cdesc->hdrtok)
410 free_hdrtok(&cdesc->hdrtok);
412 fs_give((void **) disp_format);
417 HEADER_TOK_S *
418 new_hdrtok(char *hdrname)
420 HEADER_TOK_S *hdrtok;
422 hdrtok = (HEADER_TOK_S *) fs_get(sizeof(HEADER_TOK_S));
423 memset(hdrtok, 0, sizeof(HEADER_TOK_S));
424 hdrtok->hdrname = hdrname ? cpystr(hdrname) : NULL;
425 hdrtok->fieldnum = 0;
426 hdrtok->adjustment = Left;
427 hdrtok->fieldsepcnt = 1;
428 hdrtok->fieldseps = cpystr(" ");
430 return(hdrtok);
434 void
435 free_hdrtok(HEADER_TOK_S **hdrtok)
437 if(hdrtok && *hdrtok){
438 if((*hdrtok)->hdrname)
439 fs_give((void **) &(*hdrtok)->hdrname);
441 if((*hdrtok)->fieldseps)
442 fs_give((void **) &(*hdrtok)->fieldseps);
444 fs_give((void **) hdrtok);
449 /* popular ones first to make it slightly faster */
450 static INDEX_PARSE_T itokens[] = {
451 {"STATUS", iStatus, FOR_INDEX},
452 {"MSGNO", iMessNo, FOR_INDEX},
453 {"DATE", iDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
454 {"FROMORTO", iFromTo, FOR_INDEX},
455 {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX},
456 {"SIZE", iSize, FOR_INDEX},
457 {"SIZECOMMA", iSizeComma, FOR_INDEX},
458 {"SIZENARROW", iSizeNarrow, FOR_INDEX},
459 {"KSIZE", iKSize, FOR_INDEX},
460 {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
461 {"SHORTSUBJECT", iShortSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
462 {"FULLSTATUS", iFStatus, FOR_INDEX},
463 {"IMAPSTATUS", iIStatus, FOR_INDEX},
464 {"SHORTIMAPSTATUS", iSIStatus, FOR_INDEX},
465 {"SUBJKEY", iSubjKey, FOR_INDEX},
466 {"SHORTSUBJKEY", iShortSubjKey, FOR_INDEX},
467 {"SUBJKEYINIT", iSubjKeyInit, FOR_INDEX},
468 {"SHORTSUBJKEYINIT",iShortSubjKeyInit, FOR_INDEX},
469 {"SUBJECTTEXT", iSubjectText, FOR_INDEX},
470 {"SUBJKEYTEXT", iSubjKeyText, FOR_INDEX},
471 {"SUBJKEYINITTEXT", iSubjKeyInitText, FOR_INDEX},
472 {"OPENINGTEXT", iOpeningText, FOR_INDEX},
473 {"OPENINGTEXTNQ", iOpeningTextNQ, FOR_INDEX},
474 {"KEY", iKey, FOR_INDEX},
475 {"KEYINIT", iKeyInit, FOR_INDEX},
476 {"DESCRIPSIZE", iDescripSize, FOR_INDEX},
477 {"ATT", iAtt, FOR_INDEX},
478 {"SCORE", iScore, FOR_INDEX},
479 {"PRIORITY", iPrio, FOR_INDEX},
480 {"PRIORITYALPHA", iPrioAlpha, FOR_INDEX},
481 {"PRIORITY!", iPrioBang, FOR_INDEX},
482 {"LONGDATE", iLDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
483 {"SHORTDATE1", iS1Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
484 {"SHORTDATE2", iS2Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
485 {"SHORTDATE3", iS3Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
486 {"SHORTDATE4", iS4Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
487 {"DATEISO", iDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
488 {"SHORTDATEISO", iDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
489 {"SMARTDATE", iSDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
490 {"SMARTTIME", iSTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
491 {"SMARTTIME24", iSTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
492 {"SMARTDATEISO", iSDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
493 {"SMARTDATESHORTISO",iSDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
494 {"SMARTDATES1", iSDateS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
495 {"SMARTDATES2", iSDateS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
496 {"SMARTDATES3", iSDateS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
497 {"SMARTDATES4", iSDateS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
498 {"SMARTDATETIME", iSDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
499 {"SMARTDATETIMEISO",iSDateTimeIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
500 {"SMARTDATETIMESHORTISO",iSDateTimeIsoS,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
501 {"SMARTDATETIMES1", iSDateTimeS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
502 {"SMARTDATETIMES2", iSDateTimeS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
503 {"SMARTDATETIMES3", iSDateTimeS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
504 {"SMARTDATETIMES4", iSDateTimeS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
505 {"SMARTDATETIME24", iSDateTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
506 {"SMARTDATETIMEISO24", iSDateTimeIso24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
507 {"SMARTDATETIMESHORTISO24",iSDateTimeIsoS24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
508 {"SMARTDATETIMES124", iSDateTimeS124, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
509 {"SMARTDATETIMES224", iSDateTimeS224, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
510 {"SMARTDATETIMES324", iSDateTimeS324, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
511 {"SMARTDATETIMES424", iSDateTimeS424, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
512 {"TIME24", iTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
513 {"TIME12", iTime12, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
514 {"TIMEZONE", iTimezone, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
515 {"MONTHABBREV", iMonAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
516 {"DAYOFWEEKABBREV", iDayOfWeekAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
517 {"DAYOFWEEK", iDayOfWeek, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
518 {"FROM", iFrom, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
519 {"TO", iTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
520 {"SENDER", iSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
521 {"CC", iCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
522 {"RECIPS", iRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
523 {"NEWS", iNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
524 {"TOANDNEWS", iToAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
525 {"NEWSANDTO", iNewsAndTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
526 {"RECIPSANDNEWS", iRecipsAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
527 {"NEWSANDRECIPS", iNewsAndRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
528 {"MSGID", iMsgID, FOR_REPLY_INTRO|FOR_TEMPLATE},
529 {"CURNEWS", iCurNews, FOR_REPLY_INTRO|FOR_TEMPLATE},
530 {"DAYDATE", iRDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
531 {"PREFDATE", iPrefDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
532 {"PREFTIME", iPrefTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
533 {"PREFDATETIME", iPrefDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
534 {"DAY", iDay, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
535 {"DAYORDINAL", iDayOrdinal, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
536 {"DAY2DIGIT", iDay2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
537 {"MONTHLONG", iMonLong, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
538 {"MONTH", iMon, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
539 {"MONTH2DIGIT", iMon2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
540 {"YEAR", iYear, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
541 {"YEAR2DIGIT", iYear2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
542 {"ADDRESS", iAddress, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
543 {"MAILBOX", iMailbox, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
544 {"ROLENICK", iRoleNick, FOR_REPLY_INTRO|FOR_TEMPLATE},
545 {"INIT", iInit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
546 {"CURDATE", iCurDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
547 {"CURDATEISO", iCurDateIso, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
548 {"CURDATEISOS", iCurDateIsoS, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
549 {"CURTIME24", iCurTime24, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
550 {"CURTIME12", iCurTime12, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
551 {"CURDAY", iCurDay, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
552 {"CURDAY2DIGIT", iCurDay2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
553 {"CURDAYOFWEEK", iCurDayOfWeek, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
554 {"CURDAYOFWEEKABBREV", iCurDayOfWeekAbb,
555 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
556 {"CURMONTH", iCurMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
557 {"CURMONTH2DIGIT", iCurMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
558 {"CURMONTHLONG", iCurMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
559 {"CURMONTHABBREV", iCurMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
560 {"CURYEAR", iCurYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
561 {"CURYEAR2DIGIT", iCurYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
562 {"CURPREFDATE", iCurPrefDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
563 {"CURPREFTIME", iCurPrefTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
564 {"CURPREFDATETIME", iCurPrefDateTime,
565 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
566 {"LASTMONTH", iLstMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
567 {"LASTMONTH2DIGIT", iLstMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
568 {"LASTMONTHLONG", iLstMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
569 {"LASTMONTHABBREV", iLstMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
570 {"LASTMONTHYEAR", iLstMonYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
571 {"LASTMONTHYEAR2DIGIT", iLstMonYear2Digit,
572 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
573 {"LASTYEAR", iLstYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
574 {"LASTYEAR2DIGIT", iLstYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
575 {"HEADER", iHeader, FOR_INDEX},
576 {"TEXT", iText, FOR_INDEX},
577 {"ARROW", iArrow, FOR_INDEX},
578 {"NEWLINE", iNewLine, FOR_REPLY_INTRO},
579 {"CURSORPOS", iCursorPos, FOR_TEMPLATE},
580 {NULL, iNothing, FOR_NOTHING}
583 INDEX_PARSE_T itokensinv[sizeof(itokens)/sizeof(itokens[0])];
585 void
586 inverse_itokens(void)
588 INDEX_PARSE_T *pt;
589 for (pt = itokens; pt->name; pt++)
590 itokensinv[pt->ctype].ctype = pt - itokens;
594 INDEX_PARSE_T *
595 itoken(int i)
597 return((i < sizeof(itokens) && itokens[i].name) ? &itokens[i] : NULL);
602 * Args txt -- The token being checked begins at the beginning
603 * of txt. The end of the token is delimited by a null, or
604 * white space, or an underscore if DELIM_USCORE is set,
605 * or a left paren if DELIM_PAREN is set.
606 * flags -- Flags contains the what_for value, and DELIM_ values.
608 * Returns A ptr to an INDEX_PARSE_T from itokens above, else NULL.
610 INDEX_PARSE_T *
611 itoktype(char *txt, int flags)
613 INDEX_PARSE_T *pt;
614 char token[100 + 1];
615 char *v, *w;
618 * Separate a copy of the possible token out of txt.
620 v = txt;
621 w = token;
622 while(w < token+sizeof(token)-1 &&
623 *v &&
624 !(!(*v & 0x80) && isspace((unsigned char)*v)) &&
625 !(flags & DELIM_USCORE && *v == '_') &&
626 !(flags & DELIM_PAREN && *v == '(') &&
627 !(flags & DELIM_COLON && *v == ':'))
628 *w++ = *v++;
630 *w = '\0';
632 for(pt = itokens; pt->name; pt++)
633 if(pt->what_for & flags && !strucmp(pt->name, token))
634 return(pt);
636 return(NULL);
641 parse_index_format(char *format_str, INDEX_COL_S **answer)
643 int i, column = 0;
644 char *p, *q;
645 INDEX_PARSE_T *pt;
646 INDEX_COL_S cdesc[200]; /* plenty of temp storage for answer */
648 memset((void *)cdesc, 0, sizeof(cdesc));
650 p = format_str;
651 while(p && *p && column < 200-1){
652 /* skip leading white space for next word */
653 p = skip_white_space(p);
654 pt = itoktype(p, FOR_INDEX | DELIM_PAREN | DELIM_COLON);
656 /* ignore unrecognized word */
657 if(!pt){
658 char c;
659 for(q = p; *p && !isspace((unsigned char)*p); p++)
662 if((c = *p) != '\0')
663 *p++ = '\0';
665 dprint((1,
666 "parse_index_format: unrecognized token: %s\n",
667 q ? q : "?"));
668 q_status_message1(SM_ORDER | SM_DING, 0, 3,
669 _("Unrecognized word in index-format: %s"), q);
670 if(c != '\0')
671 *(p-1) = c;
672 continue;
675 cdesc[column].ctype = pt->ctype;
677 if(pt->ctype == iHeader || pt->ctype == iText){
679 * iHeader field has special syntax.
681 * HEADER:hdrname(width,fieldnum,field_separators,L_or_R)
683 * where width is the regular width or percentage width or
684 * left out for default width, fieldnum defaults to 0 for
685 * whole thing, 1 for first field, ...
686 * and field_separators is a list of characters which separate
687 * the fields. The whole parenthesized part is optional. If used
688 * the arguments can be dropped from the right, so
690 * HEADER:hdrname or
691 * HEADER:hdrname(10) or
692 * HEADER:hdrname(10%) or
693 * HEADER:hdrname(10,2) or
694 * HEADER:hdrname(,2) or
695 * HEADER:hdrname(10,2, ) or
696 * HEADER:hdrname(10,2,\,:) or
697 * HEADER:hdrname(10,2,\,:,R)
699 * iText field uses the hdrtok field for convenience. It has syntax
701 * TEXT:text or
702 * TEXT:text(10) or
703 * TEXT:text(10%)
705 * and the literal text goes into the index line. It is also special
706 * because there is no 1 column space after this field.
709 /* skip over name */
710 p += strlen(pt->name);
712 /* look for header name */
713 if(*p == ':'){
714 char *w, hdrname[200];
716 hdrname[0] = '\0';
717 w = hdrname;
718 p++;
719 if(*p == '\"'){ /* quoted name */
720 p++;
721 while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){
722 if(*p == '\\')
723 p++;
725 *w++ = *p++;
728 *w = '\0';
729 if(*p == '\"')
730 p++;
732 else{
733 while(w < hdrname + sizeof(hdrname)-1 &&
734 !(!(*p & 0x80) && isspace((unsigned char)*p)) &&
735 *p != '(')
736 *w++ = *p++;
738 *w = '\0';
741 if(hdrname[0]){
742 cdesc[column].hdrtok = new_hdrtok(hdrname);
744 else{
745 if(pt->ctype == iHeader){
746 dprint((1, "parse_index_token: HEADER should be followed by :hdrname\n"));
747 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname");
749 else{
750 dprint((1, "parse_index_token: TEXT should be followed by :text\n"));
751 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text");
755 else{
756 if(pt->ctype == iHeader){
757 dprint((1, "parse_index_token: HEADER should be followed by :hdrname, not %s\n", p));
758 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname");
760 else{
761 dprint((1, "parse_index_token: TEXT should be followed by :text, not %s\n", p));
762 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text");
765 /* skip over rest of bogus config */
766 while(!(!(*p & 0x80) && isspace((unsigned char)*p)) && *p != '(')
767 p++;
770 else{
771 /* skip over name and look for parens */
772 p += strlen(pt->name);
775 if(*p == '('){
776 p++;
777 q = p;
778 while(p && *p && isdigit((unsigned char) *p))
779 p++;
781 if(pt->ctype == iHeader){
782 /* first argument is width or width percentage, like for others */
783 if(p && *p && (*p == ')' || *p == ',')){
784 if(p > q){
785 cdesc[column].wtype = Fixed;
786 cdesc[column].req_width = atoi(q);
788 else{
789 cdesc[column].wtype = WeCalculate;
790 cdesc[column].req_width = 0;
793 else if(p && *p && *p == '%' && p > q){
794 cdesc[column].wtype = Percent;
795 cdesc[column].req_width = atoi(q);
796 p++;
798 else{
799 cdesc[column].wtype = WeCalculate;
800 cdesc[column].req_width = 0;
803 /* optional 2nd argument is field number, 0 whole thing, 1, 2, ... */
804 if(p && *p && *p == ','){
805 p++;
806 /* no space allowed between arguments */
807 if(*p && isdigit((unsigned char) *p)){
808 q = p;
809 while(*p && isdigit((unsigned char) *p))
810 p++;
812 cdesc[column].hdrtok->fieldnum = atoi(q);
815 * Optional 3rd argument is field separators.
816 * Comma is \, and backslash is \\.
818 if(*p == ','){
819 int j;
821 p++;
822 /* don't use default */
823 if(*p && *p != ')' && *p != ',' && cdesc[column].hdrtok->fieldseps)
824 cdesc[column].hdrtok->fieldseps[0] = '\0';
826 j = 0;
827 if(*p == '\"' && strchr(p+1, '\"')){
828 p++;
829 while(*p && *p != ')' && *p != '\"' && *p != ','){
830 if(cdesc[column].hdrtok->fieldseps)
831 fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2);
833 if(*p == '\\' && *(p+1))
834 p++;
836 if(cdesc[column].hdrtok->fieldseps){
837 cdesc[column].hdrtok->fieldseps[j++] = *p++;
838 cdesc[column].hdrtok->fieldseps[j] = '\0';
839 cdesc[column].hdrtok->fieldsepcnt = j;
843 if(*p == '\"')
844 p++;
846 else{
847 while(*p && *p != ')' && *p != ','){
848 if(cdesc[column].hdrtok->fieldseps)
849 fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2);
850 if(*p == '\\' && *(p+1))
851 p++;
853 if(cdesc[column].hdrtok->fieldseps){
854 cdesc[column].hdrtok->fieldseps[j++] = *p++;
855 cdesc[column].hdrtok->fieldseps[j] = '\0';
856 cdesc[column].hdrtok->fieldsepcnt = j;
861 /* optional 4th argument, left or right adjust */
862 if(*p == ','){
863 p++;
864 if(*p == 'L' || *p == 'l')
865 cdesc[column].hdrtok->adjustment = Left;
866 else if(*p == 'R' || *p == 'r')
867 cdesc[column].hdrtok->adjustment = Right;
868 else{
869 dprint((1, "parse_index_token: HEADER 4th argument should be L or R, not\n", *p ? p : "<null>"));
870 q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 4th argument should be L or R");
875 else{
876 dprint((1, "parse_index_token: HEADER 2nd argument should be field number, not\n", *p ? p : "<null>"));
877 q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 2nd argument should be field number, a non-negative digit");
881 else{
882 if(p && *p && *p == ')' && p > q){
883 cdesc[column].wtype = Fixed;
884 cdesc[column].req_width = atoi(q);
886 else if(p && *p && *p == '%' && p > q){
887 cdesc[column].wtype = Percent;
888 cdesc[column].req_width = atoi(q);
890 else{
891 cdesc[column].wtype = WeCalculate;
892 cdesc[column].req_width = 0;
896 else{
897 /* if they left out width for iText we can figure it out */
898 if(pt->ctype == iText && cdesc[column].hdrtok && cdesc[column].hdrtok->hdrname){
899 cdesc[column].wtype = Fixed;
900 cdesc[column].req_width = utf8_width(cdesc[column].hdrtok->hdrname);
902 else{
903 cdesc[column].wtype = WeCalculate;
904 cdesc[column].req_width = 0;
908 column++;
909 /* skip text at end of word */
910 while(p && *p && !isspace((unsigned char)*p))
911 p++;
914 /* if, after all that, we didn't find anything recognizable, bitch */
915 if(!column){
916 dprint((1, "Completely unrecognizable index-format\n"));
917 q_status_message(SM_ORDER | SM_DING, 0, 3,
918 _("Configured \"index-format\" unrecognizable. Using default."));
919 return(0);
922 /* Finish with Nothing column */
923 cdesc[column].ctype = iNothing;
925 /* free up old answer */
926 if(*answer)
927 free_index_format(answer);
929 /* allocate space for new answer */
930 *answer = (INDEX_COL_S *)fs_get((column+1)*sizeof(INDEX_COL_S));
931 memset((void *)(*answer), 0, (column+1)*sizeof(INDEX_COL_S));
932 /* copy answer to real place */
933 for(i = 0; i <= column; i++)
934 (*answer)[i] = cdesc[i];
936 return(1);
941 * These types are basically fixed in width.
942 * The order is slightly significant. The ones towards the front of the
943 * list get space allocated sooner than the ones at the end of the list.
945 static IndexColType fixed_ctypes[] = {
946 iMessNo, iStatus, iFStatus, iIStatus, iSIStatus,
947 iDate, iSDate, iSDateTime, iSDateTime24,
948 iSTime, iSTime24, iLDate,
949 iS1Date, iS2Date, iS3Date, iS4Date, iDateIso, iDateIsoS,
950 iSDateIso, iSDateIsoS,
951 iSDateS1, iSDateS2, iSDateS3, iSDateS4,
952 iSDateTimeIso, iSDateTimeIsoS,
953 iSDateTimeS1, iSDateTimeS2, iSDateTimeS3, iSDateTimeS4,
954 iSDateTimeIso24, iSDateTimeIsoS24,
955 iSDateTimeS124, iSDateTimeS224, iSDateTimeS324, iSDateTimeS424,
956 iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize,
957 iPrio, iPrioBang, iPrioAlpha, iInit,
958 iAtt, iTime24, iTime12, iTimezone, iMonAbb, iYear, iYear2Digit,
959 iDay2Digit, iMon2Digit, iDayOfWeekAbb, iScore, iMonLong, iDayOfWeek
964 ctype_is_fixed_length(IndexColType ctype)
966 int j;
968 for(j = 0; ; j++){
969 if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes))
970 break;
972 if(ctype == fixed_ctypes[j])
973 return 1;
976 return 0;
980 /*----------------------------------------------------------------------
981 Setup the widths of the various columns in the index display
982 ----*/
983 void
984 setup_index_header_widths(MAILSTREAM *stream)
986 int colspace; /* for reserving space between columns */
987 int j, some_to_calculate;
988 int space_left, screen_width, fix;
989 int keep_going, tot_pct, was_sl;
990 long max_msgno;
991 WidthType wtype;
992 INDEX_COL_S *cdesc;
994 max_msgno = mn_get_total(ps_global->msgmap);
996 dprint((8, "=== setup_index_header_widths() ===\n"));
998 clear_icache_flags(stream);
999 screen_width = ps_global->ttyo->screen_cols;
1000 space_left = screen_width;
1001 some_to_calculate = 0;
1002 colspace = -1;
1005 * Calculate how many fields there are so we know how many spaces
1006 * between columns to reserve. Fill in Fixed widths now. Reserve
1007 * special case WeCalculate with non-zero req_widths before doing
1008 * Percent cases below.
1010 for(cdesc = ps_global->index_disp_format;
1011 cdesc->ctype != iNothing;
1012 cdesc++){
1014 if(cdesc->wtype == Fixed){
1015 cdesc->width = cdesc->req_width;
1016 if(cdesc->width > 0)
1017 colspace++;
1019 else if(cdesc->wtype == Percent){
1020 cdesc->width = 0; /* calculated later */
1021 colspace++;
1023 else{ /* WeCalculate */
1024 cdesc->width = cdesc->req_width; /* reserve this for now */
1025 some_to_calculate++;
1026 colspace++;
1029 /* no space after iText */
1030 if(cdesc->ctype == iText)
1031 colspace--;
1033 space_left -= cdesc->width;
1036 colspace = MAX(colspace, 0);
1038 space_left -= colspace; /* space between columns */
1040 ps_global->display_keywords_in_subject = 0;
1041 ps_global->display_keywordinits_in_subject = 0;
1044 * Set the actual lengths for the fixed width fields and set up
1045 * the left or right adjustment for everything.
1046 * There should be a case setting actual_length for all of the types
1047 * in fixed_ctypes.
1049 for(cdesc = ps_global->index_disp_format;
1050 cdesc->ctype != iNothing;
1051 cdesc++){
1053 wtype = cdesc->wtype;
1055 if(cdesc->ctype == iSubjKey || cdesc->ctype == iSubjKeyText)
1056 ps_global->display_keywords_in_subject = 1;
1057 else if(cdesc->ctype == iSubjKeyInit || cdesc->ctype == iSubjKeyInitText)
1058 ps_global->display_keywordinits_in_subject = 1;
1060 if(wtype == WeCalculate || wtype == Percent || cdesc->width != 0){
1062 switch(cdesc->ctype){
1063 case iSDate: case iSDateIso: case iSDateIsoS:
1064 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1065 case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
1066 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1067 case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
1068 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1069 case iSTime: case iSTime24:
1070 set_format_includes_smartdate(stream);
1071 break;
1073 default:
1074 break;
1077 if(ctype_is_fixed_length(cdesc->ctype)){
1078 switch(cdesc->ctype){
1079 case iPrio:
1080 case iPrioBang:
1081 case iAtt:
1082 cdesc->actual_length = 1;
1083 cdesc->adjustment = Left;
1084 break;
1086 case iYear2Digit:
1087 case iDay2Digit:
1088 case iMon2Digit:
1089 cdesc->actual_length = 2;
1090 cdesc->adjustment = Left;
1091 break;
1093 case iArrow:
1094 cdesc->actual_length = 2;
1095 cdesc->adjustment = Right;
1096 break;
1098 case iStatus:
1099 case iInit:
1100 cdesc->actual_length = 3;
1101 cdesc->adjustment = Left;
1102 break;
1104 case iMessNo:
1105 set_format_includes_msgno(stream);
1106 if(max_msgno < 1000)
1107 cdesc->actual_length = 3;
1108 else if(max_msgno < 10000)
1109 cdesc->actual_length = 4;
1110 else if(max_msgno < 100000)
1111 cdesc->actual_length = 5;
1112 else
1113 cdesc->actual_length = 6;
1115 cdesc->adjustment = Right;
1116 break;
1118 case iYear:
1119 case iSIStatus:
1120 cdesc->actual_length = 4;
1121 cdesc->adjustment = Left;
1122 break;
1124 case iTime24:
1125 case iTimezone:
1126 cdesc->actual_length = 5;
1127 cdesc->adjustment = Left;
1128 break;
1130 case iSizeNarrow:
1131 cdesc->actual_length = 5;
1132 cdesc->adjustment = Right;
1133 break;
1135 case iFStatus:
1136 case iIStatus:
1137 cdesc->actual_length = 6;
1138 cdesc->adjustment = Left;
1139 break;
1141 case iScore:
1142 cdesc->actual_length = 6;
1143 cdesc->adjustment = Right;
1144 break;
1146 case iTime12:
1147 case iSize:
1148 case iKSize:
1149 cdesc->actual_length = 7;
1150 cdesc->adjustment = Right;
1151 break;
1153 case iSTime:
1154 cdesc->actual_length = 7;
1155 cdesc->adjustment = Left;
1156 break;
1158 case iPrioAlpha:
1159 cdesc->actual_length = 7;
1160 cdesc->adjustment = Left;
1161 break;
1164 case iS1Date:
1165 case iS2Date:
1166 case iS3Date:
1167 case iS4Date:
1168 case iDateIsoS:
1169 cdesc->actual_length = 8;
1170 cdesc->adjustment = Left;
1171 break;
1173 case iSizeComma:
1174 cdesc->actual_length = 8;
1175 cdesc->adjustment = Right;
1176 break;
1178 case iSDate:
1179 case iSDateTime:
1180 case iSDateTime24:
1181 case iMonAbb:
1182 case iDayOfWeekAbb:
1183 case iDayOfWeek:
1184 case iDate:
1185 case iMonLong:
1186 cdesc->actual_length = cdesc->req_width;
1187 cdesc->adjustment = Left;
1188 break;
1190 case iSTime24:
1191 case iSDateIsoS:
1192 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1193 case iSDateTimeIsoS:
1194 case iSDateTimeIsoS24:
1195 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1196 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1197 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
1198 if(cdesc->ctype == iSDateIso
1199 || cdesc->ctype == iSDateTimeIso
1200 || cdesc->ctype == iSDateTimeIso24)
1201 cdesc->actual_length = 10;
1202 else
1203 cdesc->actual_length = 9;
1205 cdesc->adjustment = Left;
1206 break;
1208 case iDescripSize:
1209 cdesc->actual_length = 9;
1210 cdesc->adjustment = Right;
1211 break;
1213 case iDateIso:
1214 cdesc->actual_length = 10;
1215 cdesc->adjustment = Left;
1216 break;
1218 case iLDate:
1219 cdesc->actual_length = 12;
1220 cdesc->adjustment = Left;
1221 break;
1223 default:
1224 alpine_panic("Unhandled fixed case in setup_index_header");
1225 break;
1228 else if(cdesc->ctype == iHeader)
1229 cdesc->adjustment = cdesc->hdrtok ? cdesc->hdrtok->adjustment : Left;
1230 else
1231 cdesc->adjustment = Left;
1235 if(ps_global->display_keywords_in_subject)
1236 ps_global->display_keywordinits_in_subject = 0;
1238 /* if have reserved unneeded space for size, give it back */
1239 for(cdesc = ps_global->index_disp_format;
1240 cdesc->ctype != iNothing;
1241 cdesc++)
1242 if(cdesc->ctype == iSize || cdesc->ctype == iKSize ||
1243 cdesc->ctype == iSizeNarrow ||
1244 cdesc->ctype == iSizeComma || cdesc->ctype == iDescripSize){
1245 if(cdesc->actual_length == 0){
1246 if((fix=cdesc->width) > 0){ /* had this reserved */
1247 cdesc->width = 0;
1248 space_left += fix;
1251 space_left++; /* +1 for space between columns */
1256 * Calculate the field widths that are basically fixed in width.
1257 * Do them in this order in case we don't have enough space to go around.
1258 * The set of fixed_ctypes here is the same as the set where we
1259 * set the actual_lengths above.
1261 for(j = 0; space_left > 0 && some_to_calculate; j++){
1263 if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes))
1264 break;
1266 for(cdesc = ps_global->index_disp_format;
1267 cdesc->ctype != iNothing && space_left > 0 && some_to_calculate;
1268 cdesc++)
1269 if(cdesc->ctype == fixed_ctypes[j] && cdesc->wtype == WeCalculate){
1270 some_to_calculate--;
1271 fix = MIN(cdesc->actual_length - cdesc->width, space_left);
1272 cdesc->width += fix;
1273 space_left -= fix;
1278 * Fill in widths for Percent cases. If there are no more to calculate,
1279 * use the percentages as relative numbers and use the rest of the space,
1280 * else treat them as absolute percentages of the original avail screen.
1282 if(space_left > 0){
1283 if(some_to_calculate){
1284 int tot_requested = 0;
1287 * Requests are treated as percent of screen width. See if they
1288 * will all fit. If not, trim them back proportionately.
1290 for(cdesc = ps_global->index_disp_format;
1291 cdesc->ctype != iNothing;
1292 cdesc++){
1293 if(cdesc->wtype == Percent){
1294 /* The 2, 200, and +100 are because we're rounding */
1295 fix = ((2*cdesc->req_width *
1296 (screen_width-colspace))+100) / 200;
1297 tot_requested += fix;
1301 if(tot_requested > space_left){
1302 int multiplier = (100 * space_left) / tot_requested;
1304 for(cdesc = ps_global->index_disp_format;
1305 cdesc->ctype != iNothing && space_left > 0;
1306 cdesc++){
1307 if(cdesc->wtype == Percent){
1308 /* The 2, 200, and +100 are because we're rounding */
1309 fix = ((2*cdesc->req_width *
1310 (screen_width-colspace))+100) / 200;
1311 fix = (2 * fix * multiplier + 100) / 200;
1312 fix = MIN(fix, space_left);
1313 cdesc->width += fix;
1314 space_left -= fix;
1318 else{
1319 for(cdesc = ps_global->index_disp_format;
1320 cdesc->ctype != iNothing && space_left > 0;
1321 cdesc++){
1322 if(cdesc->wtype == Percent){
1323 /* The 2, 200, and +100 are because we're rounding */
1324 fix = ((2*cdesc->req_width *
1325 (screen_width-colspace))+100) / 200;
1326 fix = MIN(fix, space_left);
1327 cdesc->width += fix;
1328 space_left -= fix;
1333 else{
1334 tot_pct = 0;
1335 was_sl = space_left;
1336 /* add up total percentages requested */
1337 for(cdesc = ps_global->index_disp_format;
1338 cdesc->ctype != iNothing;
1339 cdesc++)
1340 if(cdesc->wtype == Percent)
1341 tot_pct += cdesc->req_width;
1343 /* give relative weight to requests */
1344 for(cdesc = ps_global->index_disp_format;
1345 cdesc->ctype != iNothing && space_left > 0 && tot_pct > 0;
1346 cdesc++){
1347 if(cdesc->wtype == Percent){
1348 fix = ((2*cdesc->req_width*was_sl)+tot_pct) / (2*tot_pct);
1349 fix = MIN(fix, space_left);
1350 cdesc->width += fix;
1351 space_left -= fix;
1357 /* split up rest, give twice as much to Subject */
1358 keep_going = 1;
1359 while(space_left > 0 && keep_going){
1360 keep_going = 0;
1361 for(cdesc = ps_global->index_disp_format;
1362 cdesc->ctype != iNothing && space_left > 0;
1363 cdesc++){
1364 if(cdesc->wtype == WeCalculate && !ctype_is_fixed_length(cdesc->ctype)){
1365 keep_going++;
1366 cdesc->width++;
1367 space_left--;
1368 if(space_left > 0 && (cdesc->ctype == iSubject
1369 || cdesc->ctype == iShortSubject
1370 || cdesc->ctype == iSubjectText
1371 || cdesc->ctype == iSubjKey
1372 || cdesc->ctype == iShortSubjKey
1373 || cdesc->ctype == iSubjKeyText
1374 || cdesc->ctype == iSubjKeyInit
1375 || cdesc->ctype == iShortSubjKeyInit
1376 || cdesc->ctype == iSubjKeyInitText)){
1377 cdesc->width++;
1378 space_left--;
1384 /* if still more, pad out percent's */
1385 keep_going = 1;
1386 while(space_left > 0 && keep_going){
1387 keep_going = 0;
1388 for(cdesc = ps_global->index_disp_format;
1389 cdesc->ctype != iNothing && space_left > 0;
1390 cdesc++){
1391 if(cdesc->wtype == Percent && !ctype_is_fixed_length(cdesc->ctype)){
1392 keep_going++;
1393 cdesc->width++;
1394 space_left--;
1399 /* if user made Fixed fields too big, give back space */
1400 keep_going = 1;
1401 while(space_left < 0 && keep_going){
1402 keep_going = 0;
1403 for(cdesc = ps_global->index_disp_format;
1404 cdesc->ctype != iNothing && space_left < 0;
1405 cdesc++){
1406 if(cdesc->wtype == Fixed && cdesc->width > 0){
1407 keep_going++;
1408 cdesc->width--;
1409 space_left++;
1414 if(pith_opt_save_index_state)
1415 (*pith_opt_save_index_state)(FALSE);
1419 void
1420 setup_thread_header_widths(MAILSTREAM *stream)
1422 clear_icache_flags(stream);
1423 if(pith_opt_save_index_state)
1424 (*pith_opt_save_index_state)(TRUE);
1429 * load_overview - c-client call back to gather overview data
1431 * Note: if we never get called, UID represents a hole
1432 * if we're passed a zero UID, totally bogus overview data
1433 * if we're passed a zero obuf, mostly bogus overview data
1435 void
1436 load_overview(MAILSTREAM *stream, imapuid_t uid, OVERVIEW *obuf, long unsigned int rawno)
1438 if(obuf && rawno >= 1L && stream && rawno <= stream->nmsgs){
1439 INDEXDATA_S idata;
1440 ICE_S *ice;
1442 memset(&idata, 0, sizeof(INDEXDATA_S));
1443 idata.no_fetch = 1;
1446 * Only really load the thing if we've got an NNTP stream
1447 * otherwise we're just using mail_fetch_overview to load the
1448 * IMAP envelope cache with the specific set of messages
1449 * in a single RTT.
1451 idata.stream = stream;
1452 idata.rawno = rawno;
1453 idata.msgno = mn_raw2m(sp_msgmap(stream), idata.rawno);
1454 idata.size = obuf->optional.octets;
1455 idata.from = obuf->from;
1456 idata.date = obuf->date;
1457 idata.subject = obuf->subject;
1459 ice = (*format_index_line)(&idata);
1460 if(idata.bogus && ice){
1461 if(THRD_INDX()){
1462 if(ice->tice)
1463 clear_ice(&ice->tice);
1465 else
1466 clear_ice(&ice);
1468 else if(F_OFF(F_QUELL_NEWS_ENV_CB, ps_global)
1469 && (!THRD_INDX() || (ice && ice->tice))
1470 && !msgline_hidden(stream, sp_msgmap(stream), idata.msgno, 0)
1471 && pith_opt_paint_index_hline){
1472 (*pith_opt_paint_index_hline)(stream, idata.msgno, ice);
1478 ICE_S *
1479 build_header_work(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
1480 long int msgno, long int top_msgno, int msgcount, int *fetched)
1482 ICE_S *ice, *ic;
1483 MESSAGECACHE *mc;
1484 long n, i, cnt, rawno, visible, limit = -1L;
1486 rawno = mn_m2raw(msgmap, msgno);
1488 /* cache hit? */
1489 if(THRD_INDX()){
1490 ice = fetch_ice(stream, rawno);
1491 if(!ice)
1492 return(NULL);
1494 if(ice->tice && ice->tice->ifield
1495 && ice->tice->color_lookup_done && ice->tice->widths_done){
1496 #ifdef DEBUG
1497 char buf[MAX_SCREEN_COLS+1];
1498 simple_index_line(buf, sizeof(buf), ice->tice, msgno);
1499 #endif
1500 dprint((9, "Hitt: Returning %p -> <%s (%d)\n",
1501 ice->tice,
1502 buf[0] ? buf : "?",
1503 buf[0] ? strlen(buf) : 0));
1504 return(ice);
1507 else{
1508 ice = fetch_ice(stream, rawno);
1509 if(!ice)
1510 return(NULL);
1512 if(ice->ifield && ice->color_lookup_done && ice->widths_done){
1513 #ifdef DEBUG
1514 char buf[MAX_SCREEN_COLS+1];
1515 simple_index_line(buf, sizeof(buf), ice, msgno);
1516 #endif
1517 dprint((9, "Hit: Returning %p -> <%s (%d)\n",
1518 ice,
1519 buf[0] ? buf : "?",
1520 buf[0] ? strlen(buf) : 0));
1521 return(ice);
1526 * If we are in THRD_INDX() and the width changed we don't currently
1527 * have a method of fixing just the widths and print_format strings.
1528 * Instead, we clear the index cache entry and start over.
1530 if(THRD_INDX() && ice && ice->tice && ice->tice->ifield
1531 && !ice->tice->widths_done){
1532 clear_ice(&ice->tice);
1536 * Fetch everything we need to start filling in the index line
1537 * explicitly via mail_fetch_overview. On an nntp stream
1538 * this has the effect of building the index lines in the
1539 * load_overview callback. Under IMAP we're either getting
1540 * the envelope data via the imap_envelope callback or
1541 * preloading the cache. Either way, we're getting exactly
1542 * what we want rather than relying on linear lookahead sort
1543 * of prefetch...
1545 if(!(fetched && *fetched) && index_in_overview(stream)
1546 && ((THRD_INDX() && !(ice->tice && ice->tice->ifield))
1547 || (!THRD_INDX() && !ice->ifield))){
1548 char *seq;
1549 int count;
1550 MESSAGECACHE *mc;
1551 PINETHRD_S *thrd;
1553 if(fetched)
1554 (*fetched)++;
1556 /* clear sequence bits */
1557 for(n = 1L; n <= stream->nmsgs; n++)
1558 if((mc = mail_elt(stream, n)) != NULL)
1559 mc->sequence = 0;
1562 * Light interesting bits
1563 * NOTE: not set above because m2raw's cheaper
1564 * than raw2m for every message
1568 * Unfortunately, it is expensive to calculate visible pages
1569 * in thread index if we are zoomed, so we don't try.
1571 if(THRD_INDX() && any_lflagged(msgmap, MN_HIDE))
1572 visible = msgmap->visible_threads;
1573 else if(THREADING() && sp_viewing_a_thread(stream)){
1575 * We know that all visible messages in the thread are marked
1576 * with MN_CHID2.
1578 for(visible = 0L, n = top_msgno;
1579 visible < msgcount && n <= mn_get_total(msgmap);
1580 n++){
1582 if(!get_lflag(stream, msgmap, n, MN_CHID2))
1583 break;
1585 if(!msgline_hidden(stream, msgmap, n, 0))
1586 visible++;
1590 else
1591 visible = mn_get_total(msgmap)
1592 - any_lflagged(msgmap, MN_HIDE|MN_CHID);
1594 limit = MIN(visible, msgcount);
1596 if(THRD_INDX()){
1597 count = i = 0;
1600 * First add the msgno we're asking for in case it
1601 * isn't visible.
1603 thrd = fetch_thread(stream, mn_m2raw(msgmap, msgno));
1604 if(msgno <= mn_get_total(msgmap)
1605 && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){
1606 count += mark_msgs_in_thread(stream, thrd, msgmap);
1609 thrd = fetch_thread(stream, mn_m2raw(msgmap, top_msgno));
1612 * Loop through visible threads, marking them for fetching.
1613 * Stop at end of screen or sooner if we run out of visible
1614 * threads.
1616 while(thrd){
1617 n = mn_raw2m(msgmap, thrd->rawno);
1618 if(n >= msgno
1619 && n <= mn_get_total(msgmap)
1620 && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){
1621 count += mark_msgs_in_thread(stream, thrd, msgmap);
1624 if(++i >= limit)
1625 break;
1627 /* find next thread which is visible */
1629 if(mn_get_revsort(msgmap) && thrd->prevthd)
1630 thrd = fetch_thread(stream, thrd->prevthd);
1631 else if(!mn_get_revsort(msgmap) && thrd->nextthd)
1632 thrd = fetch_thread(stream, thrd->nextthd);
1633 else
1634 thrd = NULL;
1635 } while(thrd
1636 && msgline_hidden(stream, msgmap,
1637 mn_raw2m(msgmap, thrd->rawno), 0));
1640 else{
1641 count = i = 0;
1644 * First add the msgno we're asking for in case it
1645 * isn't visible.
1647 if(msgno > 0L && msgno <= mn_get_total(msgmap)
1648 && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,msgno)))) || !ic->ifield)){
1649 if((thrd = fetch_thread(stream, rawno)) != NULL){
1651 * If we're doing a MUTTLIKE display the index line
1652 * may depend on the thread parent, and grandparent,
1653 * and further back. So just fetch the whole thread
1654 * in that case.
1656 if(THREADING()
1657 && ps_global->thread_disp_style == THREAD_MUTTLIKE
1658 && thrd->top)
1659 thrd = fetch_thread(stream, thrd->top);
1661 count += mark_msgs_in_thread(stream, thrd, msgmap);
1663 else if(rawno > 0L && rawno <= stream->nmsgs
1664 && (mc = mail_elt(stream,rawno))
1665 && !mc->private.msg.env){
1666 mc->sequence = 1;
1667 count++;
1671 n = top_msgno;
1672 while(1){
1673 if(n >= msgno
1674 && n <= mn_get_total(msgmap)
1675 && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,n)))) || !ic->ifield)){
1676 if((thrd = fetch_thread(stream, rawno)) != NULL){
1678 * If we're doing a MUTTLIKE display the index line
1679 * may depend on the thread parent, and grandparent,
1680 * and further back. So just fetch the whole thread
1681 * in that case.
1683 if(THREADING()
1684 && ps_global->thread_disp_style == THREAD_MUTTLIKE
1685 && thrd->top)
1686 thrd = fetch_thread(stream, thrd->top);
1688 count += mark_msgs_in_thread(stream, thrd, msgmap);
1690 else if(rawno > 0L && rawno <= stream->nmsgs
1691 && (mc = mail_elt(stream,rawno))
1692 && !mc->private.msg.env){
1693 mc->sequence = 1;
1694 count++;
1698 if(++i >= limit)
1699 break;
1701 /* find next n which is visible */
1702 while(++n <= mn_get_total(msgmap)
1703 && msgline_hidden(stream, msgmap, n, 0))
1708 if(count){
1709 seq = build_sequence(stream, NULL, NULL);
1710 if(seq){
1711 ps_global->dont_count_flagchanges = 1;
1712 mail_fetch_overview_sequence(stream, seq,
1713 (stream->dtb && stream->dtb->name
1714 && !strcmp(stream->dtb->name, "imap"))
1715 ? NULL : load_overview);
1716 ps_global->dont_count_flagchanges = 0;
1717 fs_give((void **) &seq);
1722 * reassign ice from the cache as it may've been built
1723 * within the overview callback or it may have become stale
1724 * in the prior sequence bit setting loop ...
1726 rawno = mn_m2raw(msgmap, msgno);
1727 ice = fetch_ice(stream, rawno);
1728 if(!ice)
1729 return(NULL);
1732 if((THRD_INDX() && !(ice->tice && ice->tice->ifield))
1733 || (!THRD_INDX() && !ice->ifield)){
1734 INDEXDATA_S idata;
1737 * With pre-fetching/callback-formatting done and no success,
1738 * fall into formatting the requested line...
1740 memset(&idata, 0, sizeof(INDEXDATA_S));
1741 idata.stream = stream;
1742 idata.msgno = msgno;
1743 idata.rawno = mn_m2raw(msgmap, msgno);
1744 if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
1745 && (mc = mail_elt(stream, idata.rawno))){
1746 idata.size = mc->rfc822_size;
1747 index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
1749 else
1750 idata.bogus = 2;
1752 ice = (*format_index_line)(&idata);
1753 if(!ice)
1754 return(NULL);
1758 * If needed, reset the print_format strings so that they add up to
1759 * the right total width. The reset width functionality isn't implemented
1760 * for THRD_INDX() so we are just doing a complete rebuild in that
1761 * case. This is driven by the clear_ice() call in clear_index_cache_ent()
1762 * so it should never be the case that THRD_INDX() is true and only
1763 * widths_done needs to be fixed.
1765 if((!THRD_INDX() && ice->ifield && !ice->widths_done)){
1766 ICE_S *working_ice;
1767 IFIELD_S *ifield;
1768 INDEX_COL_S *cdesc;
1770 if(need_format_setup(stream))
1771 setup_header_widths(stream);
1773 if(THRD_INDX())
1774 working_ice = ice ? ice->tice : NULL;
1775 else
1776 working_ice = ice;
1778 if(working_ice){
1780 * First fix the ifield widths. The cdescs with nonzero widths
1781 * should correspond to the ifields that are defined.
1783 ifield = working_ice->ifield;
1784 for(cdesc = ps_global->index_disp_format;
1785 cdesc->ctype != iNothing && ifield; cdesc++){
1786 if(cdesc->width){
1787 if(cdesc->ctype != ifield->ctype){
1788 dprint((1, "build_header_work(%ld): cdesc->ctype=%d != ifield->ctype=%d NOT SUPPOSED TO HAPPEN!\n", msgno, (int) cdesc->ctype, (int) ifield->ctype));
1789 assert(0);
1792 ifield->width = cdesc->width;
1793 ifield = ifield->next;
1797 /* fix the print_format strings and widths */
1798 for(ifield = working_ice->ifield; ifield; ifield = ifield->next)
1799 set_ielem_widths_in_field(ifield);
1801 working_ice->widths_done = 1;
1805 if(THRD_INDX() && ice->tice)
1806 ice->tice->color_lookup_done = 1;
1809 * Look for a color for this line (and other lines in the current
1810 * view). This does a SEARCH for each role which has a color until
1811 * it finds a match. This will be satisfied by the c-client
1812 * cache created by the mail_fetch_overview above if it is a header
1813 * search.
1815 if(!THRD_INDX() && !ice->color_lookup_done){
1816 COLOR_PAIR *linecolor;
1817 SEARCHSET *ss, *s;
1818 ICE_S *ic;
1819 PAT_STATE *pstate = NULL;
1821 if(pico_usingcolor()){
1822 if(limit < 0L){
1823 if(THREADING() && sp_viewing_a_thread(stream)){
1824 for(visible = 0L, n = top_msgno;
1825 visible < msgcount && n <= mn_get_total(msgmap);
1826 n++){
1828 if(!get_lflag(stream, msgmap, n, MN_CHID2))
1829 break;
1831 if(!msgline_hidden(stream, msgmap, n, 0))
1832 visible++;
1836 else
1837 visible = mn_get_total(msgmap)
1838 - any_lflagged(msgmap, MN_HIDE|MN_CHID);
1840 limit = MIN(visible, msgcount);
1842 /* clear sequence bits */
1843 for(n = 1L; n <= stream->nmsgs; n++)
1844 if((mc = mail_elt(stream, n)) != NULL)
1845 mc->sequence = 0;
1847 cnt = i = 0;
1848 n = top_msgno;
1849 while(1){
1850 if(n >= msgno
1851 && n <= mn_get_total(msgmap)
1852 && (!(ic=fetch_ice(stream,(rawno = mn_m2raw(msgmap, n)))) || !ic->color_lookup_done)){
1854 if(rawno >= 1L && rawno <= stream->nmsgs
1855 && (mc = mail_elt(stream, rawno))){
1856 mc->sequence = 1;
1857 cnt++;
1861 if(++i >= limit)
1862 break;
1864 /* find next n which is visible */
1865 while(++n <= mn_get_total(msgmap)
1866 && msgline_hidden(stream, msgmap, n, 0))
1871 * Why is there a loop here? The first call to get_index_line_color
1872 * will return a set of messages which match one of the roles.
1873 * Then, we eliminate those messages from the search set and try
1874 * again. This time we'd get past that role and into a different
1875 * role. Because of that, we hang onto the state and don't reset
1876 * to the first_pattern on the second and subsequent times
1877 * through the loop, avoiding fruitless match_pattern calls in
1878 * get_index_line_color.
1879 * Before the first call, pstate should be set to NULL.
1881 while(cnt > 0L){
1882 ss = build_searchset(stream);
1883 if(ss){
1884 int colormatch;
1886 linecolor = NULL;
1887 colormatch = get_index_line_color(stream, ss, &pstate,
1888 &linecolor);
1891 * Assign this color to all matched msgno's and
1892 * turn off the sequence bit so we won't check
1893 * for them again.
1895 if(colormatch){
1896 for(s = ss; s; s = s->next){
1897 for(n = s->first; n <= s->last; n++){
1898 if(n >= 1L && n <= stream->nmsgs
1899 && (mc = mail_elt(stream, n))
1900 && mc->searched){
1901 cnt--;
1902 mc->sequence = 0;
1903 ic = fetch_ice(stream, n);
1904 if(ic){
1905 ic->color_lookup_done = 1;
1906 if(linecolor)
1907 ic->linecolor = new_color_pair(linecolor->fg,
1908 linecolor->bg);
1914 if(linecolor)
1915 free_color_pair(&linecolor);
1917 else{
1918 /* have to mark the rest of the lookups done */
1919 for(s = ss; s && cnt > 0; s = s->next){
1920 for(n = s->first; n <= s->last && cnt > 0; n++){
1921 if(n >= 1L && n <= stream->nmsgs
1922 && (mc = mail_elt(stream, n))
1923 && mc->sequence){
1924 cnt--;
1925 ic = fetch_ice(stream, n);
1926 if(ic)
1927 ic->color_lookup_done = 1;
1932 /* just making sure */
1933 cnt = 0L;
1936 mail_free_searchset(&ss);
1938 else
1939 cnt = 0L;
1942 ice = fetch_ice(stream, mn_m2raw(msgmap, msgno));
1944 else
1945 ice->color_lookup_done = 1;
1948 return(ice); /* Return formatted index data */
1953 day_of_week(struct date *d)
1955 int m, y;
1957 m = d->month;
1958 y = d->year;
1959 if(m <= 2){
1960 m += 9;
1961 y--;
1963 else
1964 m -= 3; /* March is month 0 */
1966 return((d->day+2+((7+31*m)/12)+y+(y/4)+(y/400)-(y/100))%7);
1970 static int daytab[2][13] = {
1971 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
1972 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
1976 day_of_year(struct date *d)
1978 int i, leap, doy;
1980 if(d->year <= 0 || d->month < 1 || d->month > 12)
1981 return(-1);
1983 doy = d->day;
1984 leap = (d->year%4 == 0 && d->year%100 != 0) || d->year%400 == 0;
1985 for(i = 1; i < d->month; i++)
1986 doy += daytab[leap][i];
1988 return(doy);
1993 /*----------------------------------------------------------------------
1994 Format a string summarizing the message header for index on screen
1996 Args: buffer -- buffer to place formatted line
1997 idata -- snot it takes to format the line
1999 Result: returns pointer given buffer IF entry formatted
2000 else NULL if there was a problem (but the buffer is
2001 still suitable for display)
2002 ----*/
2003 ICE_S *
2004 format_index_index_line(INDEXDATA_S *idata)
2006 char str[BIGWIDTH+1], to_us, status, *field,
2007 *p, *newsgroups;
2008 int i, collapsed = 0, start, fromfield;
2009 long l, score;
2010 BODY *body = NULL;
2011 MESSAGECACHE *mc;
2012 ADDRESS *addr, *toaddr, *ccaddr, *last_to;
2013 PINETHRD_S *thrd = NULL;
2014 INDEX_COL_S *cdesc = NULL;
2015 ICE_S *ice, **icep;
2016 IFIELD_S *ifield;
2017 IELEM_S *ielem;
2018 COLOR_PAIR *color = NULL;
2019 struct variable *vars = ps_global->vars;
2021 dprint((8, "=== format_index_line(msgno=%ld,rawno=%ld) ===\n",
2022 idata ? idata->msgno : -1, idata ? idata->rawno : -1));
2025 ice = fetch_ice(idata->stream, idata->rawno);
2026 if(!ice)
2027 return(NULL);
2029 free_ifield(&ice->ifield);
2032 * Operate on a temporary copy of ice. The reason for this
2033 * is that we may end up causing a pine_mail_fetchenvelope() call
2034 * (e.g., in to_us_symbol_for_thread()) that causes an mm_flags()
2035 * and mm_flags may do a clear_ice(), freeing the ice we are working
2036 * on out from under us. We try to fetch everything we need in
2037 * build_header_work() but c-client will short-circuit our request
2038 * if we already got the raw header for some reason. One possible
2039 * reason is a categorizer command in a filter. In that case
2040 * we still need a fetch fast to get the rest of the envelope data.
2042 ice = copy_ice(ice);
2044 /* is this a collapsed thread index line? */
2045 if(!idata->bogus && THREADING()){
2046 thrd = fetch_thread(idata->stream, idata->rawno);
2047 collapsed = thrd && thrd->next
2048 && get_lflag(idata->stream, NULL,
2049 idata->rawno, MN_COLL);
2052 /* calculate contents of the required fields */
2053 for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++)
2054 if(cdesc->width){
2055 memset(str, 0, sizeof(str));
2056 ifield = new_ifield(&ice->ifield);
2057 ifield->ctype = cdesc->ctype;
2058 ifield->width = cdesc->width;
2059 fromfield = 0;
2061 if(idata->bogus){
2062 if(cdesc->ctype == iMessNo)
2063 snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
2064 else if(idata->bogus < 2 && (cdesc->ctype == iSubject
2065 || cdesc->ctype == iShortSubject
2066 || cdesc->ctype == iSubjectText
2067 || cdesc->ctype == iSubjKey
2068 || cdesc->ctype == iShortSubjKey
2069 || cdesc->ctype == iSubjKeyText
2070 || cdesc->ctype == iSubjKeyInit
2071 || cdesc->ctype == iShortSubjKeyInit
2072 || cdesc->ctype == iSubjKeyInitText))
2073 snprintf(str, sizeof(str), "%s", _("[ No Message Text Available ]"));
2075 else
2076 switch(cdesc->ctype){
2077 case iStatus:
2078 to_us = status = ' ';
2079 if(collapsed){
2080 thrd = fetch_thread(idata->stream, idata->rawno);
2081 to_us = to_us_symbol_for_thread(idata->stream, thrd, 1);
2082 status = status_symbol_for_thread(idata->stream, thrd,
2083 cdesc->ctype);
2085 else{
2086 if(idata->rawno > 0L && idata->rawno <= idata->stream->nmsgs
2087 && (mc=mail_elt(idata->stream,idata->rawno)) && mc->flagged)
2088 to_us = '*'; /* simple */
2089 else if(!IS_NEWS(idata->stream)){
2090 for(addr = fetch_to(idata); addr; addr = addr->next)
2091 if(address_is_us(addr, ps_global)){
2092 ice->to_us = 1;
2093 if(to_us == ' ')
2094 to_us = '+';
2096 break;
2099 if(to_us != '+' && resent_to_us(idata)){
2100 ice->to_us = 1;
2101 if(to_us == ' ')
2102 to_us = '+';
2105 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
2106 for(addr = fetch_cc(idata); addr; addr = addr->next)
2107 if(address_is_us(addr, ps_global)){
2108 ice->cc_us = 1;
2109 to_us = '-';
2110 break;
2114 status = (!idata->stream || !IS_NEWS(idata->stream)
2115 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))
2116 ? 'N' : ' ';
2118 if(mc->seen)
2119 status = ' ';
2121 if(user_flag_is_set(idata->stream, idata->rawno, FORWARDED_FLAG))
2122 status = 'F';
2124 if(mc->answered)
2125 status = 'A';
2127 if(mc->deleted)
2128 status = 'D';
2131 snprintf(str, sizeof(str), "%c %c", to_us, status);
2133 ifield->leftadj = 1;
2134 for(i = 0; i < 3; i++){
2135 ielem = new_ielem(&ifield->ielem);
2136 ielem->freedata = 1;
2137 ielem->data = (char *) fs_get(2 * sizeof(char));
2138 ielem->data[0] = str[i];
2139 ielem->data[1] = '\0';
2140 ielem->datalen = 1;
2141 set_print_format(ielem, 1, ifield->leftadj);
2144 if(pico_usingcolor()){
2146 if(str[0] == '*'){
2147 if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
2148 ielem = ifield->ielem;
2149 ielem->freecolor = 1;
2150 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
2153 else if(str[0] == '+' || str[0] == '-'){
2154 if(VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
2155 ielem = ifield->ielem;
2156 ielem->freecolor = 1;
2157 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
2161 if(str[2] == 'D'){
2162 if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
2163 ielem = ifield->ielem->next->next;
2164 ielem->freecolor = 1;
2165 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
2168 else if(str[2] == 'A'){
2169 if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
2170 ielem = ifield->ielem->next->next;
2171 ielem->freecolor = 1;
2172 ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
2175 else if(str[2] == 'F'){
2176 if(VAR_IND_FWD_FORE_COLOR && VAR_IND_FWD_BACK_COLOR){
2177 ielem = ifield->ielem->next->next;
2178 ielem->freecolor = 1;
2179 ielem->color = new_color_pair(VAR_IND_FWD_FORE_COLOR, VAR_IND_FWD_BACK_COLOR);
2182 else if(str[2] == 'N'){
2183 if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
2184 ielem = ifield->ielem->next->next;
2185 ielem->freecolor = 1;
2186 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
2191 break;
2193 case iFStatus:
2194 case iIStatus:
2195 case iSIStatus:
2197 char new, answered, deleted, flagged;
2199 if(collapsed){
2200 thrd = fetch_thread(idata->stream, idata->rawno);
2201 to_us = to_us_symbol_for_thread(idata->stream, thrd, 0);
2203 else{
2204 to_us = ' ';
2205 if(!IS_NEWS(idata->stream)){
2206 for(addr = fetch_to(idata); addr; addr = addr->next)
2207 if(address_is_us(addr, ps_global)){
2208 to_us = '+';
2209 break;
2212 if(to_us == ' ' && resent_to_us(idata))
2213 to_us = '+';
2215 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
2216 for(addr = fetch_cc(idata); addr; addr = addr->next)
2217 if(address_is_us(addr, ps_global)){
2218 to_us = '-';
2219 break;
2224 new = answered = deleted = flagged = ' ';
2226 if(collapsed){
2227 unsigned long save_branch, cnt, tot_in_thrd;
2230 * Branch is a sibling, not part of the thread, so
2231 * don't consider it when displaying this line.
2233 save_branch = thrd->branch;
2234 thrd->branch = 0L;
2236 tot_in_thrd = count_flags_in_thread(idata->stream, thrd,
2237 F_NONE);
2239 cnt = count_flags_in_thread(idata->stream, thrd, F_DEL);
2240 if(cnt)
2241 deleted = (cnt == tot_in_thrd) ? 'D' : 'd';
2243 cnt = count_flags_in_thread(idata->stream, thrd, F_ANS);
2244 if(cnt)
2245 answered = (cnt == tot_in_thrd) ? 'A' : 'a';
2247 /* no lower case *, same thing for some or all */
2248 if(count_flags_in_thread(idata->stream, thrd, F_FLAG))
2249 flagged = '*';
2251 new = status_symbol_for_thread(idata->stream, thrd,
2252 cdesc->ctype);
2254 thrd->branch = save_branch;
2256 else{
2257 mc = (idata->rawno > 0L && idata->stream
2258 && idata->rawno <= idata->stream->nmsgs)
2259 ? mail_elt(idata->stream, idata->rawno) : NULL;
2260 if(mc && mc->valid){
2261 if(cdesc->ctype == iIStatus || cdesc->ctype == iSIStatus){
2262 if(mc->recent)
2263 new = mc->seen ? 'R' : 'N';
2264 else if (!mc->seen)
2265 new = 'U';
2267 else if(!mc->seen
2268 && (!IS_NEWS(idata->stream)
2269 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)))
2270 new = 'N';
2272 if(mc->answered)
2273 answered = 'A';
2275 if(mc->deleted)
2276 deleted = 'D';
2278 if(mc->flagged)
2279 flagged = '*';
2283 snprintf(str, sizeof(str), "%c %c%c%c%c", to_us, flagged, new,
2284 answered, deleted);
2286 if(cdesc->ctype == iSIStatus)
2287 start = 2;
2288 else
2289 start = 0;
2291 ifield->leftadj = 1;
2292 for(i = start; i < 6; i++){
2293 ielem = new_ielem(&ifield->ielem);
2294 ielem->freedata = 1;
2295 ielem->data = (char *) fs_get(2 * sizeof(char));
2296 ielem->data[0] = str[i];
2297 ielem->data[1] = '\0';
2298 ielem->datalen = 1;
2299 set_print_format(ielem, 1, ifield->leftadj);
2302 if(pico_usingcolor()){
2304 if(str[0] == '+' || str[0] == '-'){
2305 if(start == 0
2306 && VAR_IND_PLUS_FORE_COLOR
2307 && VAR_IND_PLUS_BACK_COLOR){
2308 ielem = ifield->ielem;
2309 ielem->freecolor = 1;
2310 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
2314 if(str[2] == '*'){
2315 if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
2316 if(start == 2)
2317 ielem = ifield->ielem;
2318 else
2319 ielem = ifield->ielem->next->next;
2321 ielem->freecolor = 1;
2322 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
2326 if(str[3] == 'N' || str[3] == 'n'){
2327 if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
2328 if(start == 2)
2329 ielem = ifield->ielem->next;
2330 else
2331 ielem = ifield->ielem->next->next->next;
2333 ielem->freecolor = 1;
2334 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
2337 else if(str[3] == 'R' || str[3] == 'r'){
2338 if(VAR_IND_REC_FORE_COLOR && VAR_IND_REC_BACK_COLOR){
2339 if(start == 2)
2340 ielem = ifield->ielem->next;
2341 else
2342 ielem = ifield->ielem->next->next->next;
2344 ielem->freecolor = 1;
2345 ielem->color = new_color_pair(VAR_IND_REC_FORE_COLOR, VAR_IND_REC_BACK_COLOR);
2348 else if(str[3] == 'U' || str[3] == 'u'){
2349 if(VAR_IND_UNS_FORE_COLOR && VAR_IND_UNS_BACK_COLOR){
2350 if(start == 2)
2351 ielem = ifield->ielem->next;
2352 else
2353 ielem = ifield->ielem->next->next->next;
2355 ielem->freecolor = 1;
2356 ielem->color = new_color_pair(VAR_IND_UNS_FORE_COLOR, VAR_IND_UNS_BACK_COLOR);
2360 if(str[4] == 'A' || str[4] == 'a'){
2361 if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
2362 if(start == 2)
2363 ielem = ifield->ielem->next->next;
2364 else
2365 ielem = ifield->ielem->next->next->next->next;
2367 ielem->freecolor = 1;
2368 ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
2372 if(str[5] == 'D' || str[5] == 'd'){
2373 if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
2374 if(start == 2)
2375 ielem = ifield->ielem->next->next->next;
2376 else
2377 ielem = ifield->ielem->next->next->next->next->next;
2379 ielem->freecolor = 1;
2380 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
2386 break;
2388 case iMessNo:
2390 * This is a special case. The message number is
2391 * generated on the fly in the painting routine.
2392 * But the data array is allocated here in case it
2393 * is useful for the paint routine.
2395 snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
2396 break;
2398 case iArrow:
2399 snprintf(str, sizeof(str), "%-*.*s", ifield->width, ifield->width, " ");
2400 if(VAR_IND_ARR_FORE_COLOR && VAR_IND_ARR_BACK_COLOR){
2401 ifield->leftadj = 1;
2402 ielem = new_ielem(&ifield->ielem);
2403 ielem->freedata = 1;
2404 ielem->data = cpystr(str);
2405 ielem->datalen = strlen(str);
2406 set_print_format(ielem, ifield->width, ifield->leftadj);
2407 ielem->freecolor = 1;
2408 ielem->color = new_color_pair(VAR_IND_ARR_FORE_COLOR,
2409 VAR_IND_ARR_BACK_COLOR);
2412 break;
2414 case iScore:
2415 score = get_msg_score(idata->stream, idata->rawno);
2416 if(score == SCORE_UNDEF){
2417 SEARCHSET *ss = NULL;
2419 ss = mail_newsearchset();
2420 ss->first = ss->last = (unsigned long) idata->rawno;
2421 if(ss){
2423 * This looks like it might be expensive to get the
2424 * score for each message when needed but it shouldn't
2425 * be too bad because we know we have the envelope
2426 * data cached. We can't calculate all of the scores
2427 * we need for the visible messages right here in
2428 * one fell swoop because we don't have the other
2429 * envelopes yet. And we can't get the other
2430 * envelopes at this point because we may be in
2431 * the middle of a c-client callback (pine_imap_env).
2432 * (Actually we could, because we know whether or
2433 * not we're in the callback because of the no_fetch
2434 * parameter.)
2435 * We have another problem if the score rules depend
2436 * on something other than envelope data. I guess they
2437 * only do that if they have an alltext (search the
2438 * text of the message) definition. So, we're going
2439 * to pass no_fetch to calculate_scores so that it
2440 * can return an error if we need the text data but
2441 * can't get it because of no_fetch. Setting bogus
2442 * will cause us to do the scores calculation later
2443 * when we are no longer in the callback.
2445 idata->bogus =
2446 (calculate_some_scores(idata->stream,
2447 ss, idata->no_fetch) == 0)
2448 ? 1 : 0;
2449 score = get_msg_score(idata->stream, idata->rawno);
2450 mail_free_searchset(&ss);
2454 snprintf(str, sizeof(str), "%ld", score != SCORE_UNDEF ? score : 0L);
2455 break;
2457 case iDate: case iMonAbb: case iLDate:
2458 case iSDate: case iSTime: case iSTime24:
2459 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
2460 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
2461 case iSDateIsoS: case iSDateIso:
2462 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
2463 case iSDateTime:
2464 case iSDateTimeIsoS: case iSDateTimeIso:
2465 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
2466 case iSDateTime24:
2467 case iSDateTimeIsoS24: case iSDateTimeIso24:
2468 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
2469 case iTimezone: case iYear: case iYear2Digit:
2470 case iRDate: case iDay: case iDay2Digit: case iMon2Digit:
2471 case iDayOrdinal: case iMon: case iMonLong:
2472 case iDayOfWeekAbb: case iDayOfWeek:
2473 case iPrefDate: case iPrefTime: case iPrefDateTime:
2474 date_str(fetch_date(idata), cdesc->ctype, 0, str, sizeof(str), cdesc->monabb_width);
2475 break;
2477 case iFromTo:
2478 case iFromToNotNews:
2479 case iFrom:
2480 case iAddress:
2481 case iMailbox:
2482 fromfield++;
2483 from_str(cdesc->ctype, idata, str, sizeof(str), ice);
2484 break;
2486 case iTo:
2487 if(((field = ((addr = fetch_to(idata))
2488 ? "To"
2489 : (addr = fetch_cc(idata))
2490 ? "Cc"
2491 : NULL))
2492 && !set_index_addr(idata, field, addr, NULL, BIGWIDTH, str))
2493 || !field)
2494 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2495 snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, newsgroups);
2497 break;
2499 case iCc:
2500 set_index_addr(idata, "Cc", fetch_cc(idata), NULL, BIGWIDTH, str);
2501 break;
2503 case iRecips:
2504 toaddr = fetch_to(idata);
2505 ccaddr = fetch_cc(idata);
2506 for(last_to = toaddr;
2507 last_to && last_to->next;
2508 last_to = last_to->next)
2511 /* point end of to list temporarily at cc list */
2512 if(last_to)
2513 last_to->next = ccaddr;
2515 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2517 if(last_to)
2518 last_to->next = NULL;
2520 break;
2522 case iSender:
2523 fromfield++;
2524 if((addr = fetch_sender(idata)) != NULL)
2525 set_index_addr(idata, "Sender", addr, NULL, BIGWIDTH, str);
2527 break;
2529 case iInit:
2530 {ADDRESS *addr;
2532 if((addr = fetch_from(idata)) && addr->personal){
2533 char *name, *initials = NULL;
2535 name = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
2536 SIZEOF_20KBUF, addr->personal);
2537 if(name == addr->personal){
2538 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
2539 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
2540 name = (char *) tmp_20k_buf;
2543 if(name && *name){
2544 initials = reply_quote_initials(name);
2545 snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, initials);
2550 break;
2552 case iSize:
2553 /* 0 ... 9999 */
2554 if((l = fetch_size(idata)) < 10*1000L)
2555 snprintf(str, sizeof(str), "(%lu)", l);
2556 /* 10K ... 999K */
2557 else if(l < 1000L*1000L - 1000L/2){
2558 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2559 snprintf(str, sizeof(str), "(%luK)", l);
2561 /* 1.0M ... 99.9M */
2562 else if(l < 1000L*100L*1000L - 100L*1000L/2){
2563 l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
2564 ? 1L : 0L);
2565 snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
2567 /* 100M ... 2000M */
2568 else if(l <= 2*1000L*1000L*1000L){
2569 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2570 ? 1L : 0L);
2571 snprintf(str, sizeof(str), "(%luM)", l);
2573 else
2574 snprintf(str, sizeof(str), "(HUGE!)");
2576 break;
2578 case iSizeComma:
2579 /* 0 ... 99,999 */
2580 if((l = fetch_size(idata)) < 100*1000L)
2581 snprintf(str, sizeof(str), "(%s)", comatose(l));
2582 /* 100K ... 9,999K */
2583 else if(l < 10L*1000L*1000L - 1000L/2){
2584 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2585 snprintf(str, sizeof(str), "(%sK)", comatose(l));
2587 /* 10.0M ... 999.9M */
2588 else if(l < 1000L*1000L*1000L - 100L*1000L/2){
2589 l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
2590 ? 1L : 0L);
2591 snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
2593 /* 1,000M ... 2,000M */
2594 else if(l <= 2*1000L*1000L*1000L){
2595 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2596 ? 1L : 0L);
2597 snprintf(str, sizeof(str), "(%sM)", comatose(l));
2599 else
2600 snprintf(str, sizeof(str), "(HUGE!)");
2602 break;
2604 case iSizeNarrow:
2605 /* 0 ... 999 */
2606 if((l = fetch_size(idata)) < 1000L)
2607 snprintf(str, sizeof(str), "(%lu)", l);
2608 /* 1K ... 99K */
2609 else if(l < 100L*1000L - 1000L/2){
2610 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2611 snprintf(str, sizeof(str), "(%luK)", l);
2613 /* .1M ... .9M */
2614 else if(l < 1000L*1000L - 100L*1000L/2){
2615 l = l/(100L*1000L) + (l%(100L*1000L) >= 100L*1000L/2
2616 ? 1L : 0L);
2617 snprintf(str, sizeof(str), "(.%luM)", l);
2619 /* 1M ... 99M */
2620 else if(l < 1000L*100L*1000L - 1000L*1000L/2){
2621 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2622 ? 1L : 0L);
2623 snprintf(str, sizeof(str), "(%luM)", l);
2625 /* .1G ... .9G */
2626 else if(l < 1000L*1000L*1000L - 100L*1000L*1000L/2){
2627 l = l/(100L*1000L*1000L) + (l%(100L*1000L*1000L) >=
2628 (100L*1000L*1000L/2) ? 1L : 0L);
2629 snprintf(str, sizeof(str), "(.%luG)", l);
2631 /* 1G ... 2G */
2632 else if(l <= 2*1000L*1000L*1000L){
2633 l = l/(1000L*1000L*1000L) + (l%(1000L*1000L*1000L) >=
2634 (1000L*1000L*1000L/2) ? 1L : 0L);
2635 snprintf(str, sizeof(str), "(%luG)", l);
2637 else
2638 snprintf(str, sizeof(str), "(HUGE!)");
2640 break;
2642 /* From Carl Jacobsen <carl@ucsd.edu> */
2643 case iKSize:
2644 l = fetch_size(idata);
2645 l = (l / 1024L) + (l % 1024L != 0 ? 1 : 0);
2647 if(l < 1024L) { /* 0k .. 1023k */
2648 snprintf(str, sizeof(str), "(%luk)", l);
2650 } else if (l < 100L * 1024L){ /* 1.0M .. 99.9M */
2651 snprintf(str, sizeof(str), "(%lu.M)", (l * 10L) / 1024L);
2652 if ((p = strchr(str, '.')) != NULL) {
2653 p--; p[1] = p[0]; p[0] = '.'; /* swap last digit & . */
2655 } else if (l <= 2L * 1024L * 1024L) { /* 100M .. 2048 */
2656 snprintf(str, sizeof(str), "(%luM)", l / 1024L);
2657 } else {
2658 snprintf(str, sizeof(str), "(HUGE!)");
2661 break;
2663 case iDescripSize:
2664 if((body = fetch_body(idata)) != NULL)
2665 switch(body->type){
2666 case TYPETEXT:
2668 mc = (idata->rawno > 0L && idata->stream
2669 && idata->rawno <= idata->stream->nmsgs)
2670 ? mail_elt(idata->stream, idata->rawno) : NULL;
2671 if(mc && mc->rfc822_size < 6000)
2672 snprintf(str, sizeof(str), "(short )");
2673 else if(mc && mc->rfc822_size < 25000)
2674 snprintf(str, sizeof(str), "(medium )");
2675 else if(mc && mc->rfc822_size < 100000)
2676 snprintf(str, sizeof(str), "(long )");
2677 else
2678 snprintf(str, sizeof(str), "(huge )");
2681 break;
2683 case TYPEMULTIPART:
2684 if(strucmp(body->subtype, "MIXED") == 0){
2685 int x;
2687 x = body->nested.part
2688 ? body->nested.part->body.type
2689 : TYPETEXT + 1000;
2690 switch(x){
2691 case TYPETEXT:
2692 if(body->nested.part->body.size.bytes < 6000)
2693 snprintf(str, sizeof(str), "(short+ )");
2694 else if(body->nested.part->body.size.bytes
2695 < 25000)
2696 snprintf(str, sizeof(str), "(medium+)");
2697 else if(body->nested.part->body.size.bytes
2698 < 100000)
2699 snprintf(str, sizeof(str), "(long+ )");
2700 else
2701 snprintf(str, sizeof(str), "(huge+ )");
2702 break;
2704 default:
2705 snprintf(str, sizeof(str), "(multi )");
2706 break;
2709 else if(strucmp(body->subtype, "DIGEST") == 0)
2710 snprintf(str, sizeof(str), "(digest )");
2711 else if(strucmp(body->subtype, "ALTERNATIVE") == 0)
2712 snprintf(str, sizeof(str), "(mul/alt)");
2713 else if(strucmp(body->subtype, "PARALLEL") == 0)
2714 snprintf(str, sizeof(str), "(mul/par)");
2715 else
2716 snprintf(str, sizeof(str), "(multi )");
2718 break;
2720 case TYPEMESSAGE:
2721 snprintf(str, sizeof(str), "(message)");
2722 break;
2724 case TYPEAPPLICATION:
2725 snprintf(str, sizeof(str), "(applica)");
2726 break;
2728 case TYPEAUDIO:
2729 snprintf(str, sizeof(str), "(audio )");
2730 break;
2732 case TYPEIMAGE:
2733 snprintf(str, sizeof(str), "(image )");
2734 break;
2736 case TYPEVIDEO:
2737 snprintf(str, sizeof(str), "(video )");
2738 break;
2740 default:
2741 snprintf(str, sizeof(str), "(other )");
2742 break;
2745 break;
2747 case iAtt:
2748 str[0] = SPACE;
2749 str[1] = '\0';
2750 if((body = fetch_body(idata)) &&
2751 body->type == TYPEMULTIPART &&
2752 strucmp(body->subtype, "ALTERNATIVE") != 0){
2753 PART *part;
2754 int atts = 0;
2756 part = body->nested.part; /* 1st part, don't count */
2757 while(part && part->next && atts < 10){
2758 atts++;
2759 part = part->next;
2762 if(atts > 9)
2763 str[0] = '*';
2764 else if(atts > 0)
2765 str[0] = '0' + atts;
2768 break;
2770 case iSubject:
2771 subj_str(idata, str, sizeof(str), NoKW, 0, 0, ice);
2772 break;
2774 case iShortSubject:
2775 subj_str(idata, str, sizeof(str), NoKW, 0, 1, ice);
2776 break;
2778 case iSubjectText:
2779 subj_str(idata, str, sizeof(str), NoKW, 1, 0, ice);
2780 break;
2782 case iSubjKey:
2783 subj_str(idata, str, sizeof(str), KW, 0, 0, ice);
2784 break;
2786 case iShortSubjKey:
2787 subj_str(idata, str, sizeof(str), KW, 0, 1, ice);
2788 break;
2790 case iSubjKeyText:
2791 subj_str(idata, str, sizeof(str), KW, 1, 0, ice);
2792 break;
2794 case iSubjKeyInit:
2795 subj_str(idata, str, sizeof(str), KWInit, 0, 0, ice);
2796 break;
2798 case iShortSubjKeyInit:
2799 subj_str(idata, str, sizeof(str), KWInit, 0, 1, ice);
2800 break;
2802 case iSubjKeyInitText:
2803 subj_str(idata, str, sizeof(str), KWInit, 1, 0, ice);
2804 break;
2806 case iOpeningText:
2807 case iOpeningTextNQ:
2808 if(idata->no_fetch)
2809 idata->bogus = 1;
2810 else{
2811 char *first_text;
2813 first_text = fetch_firsttext(idata, cdesc->ctype == iOpeningTextNQ);
2815 if(first_text){
2816 strncpy(str, first_text, BIGWIDTH);
2817 str[BIGWIDTH] = '\0';
2818 fs_give((void **) &first_text);
2822 break;
2824 case iKey:
2825 key_str(idata, KW, ice);
2826 break;
2828 case iKeyInit:
2829 key_str(idata, KWInit, ice);
2830 break;
2832 case iNews:
2833 if((newsgroups = fetch_newsgroups(idata)) != NULL){
2834 strncpy(str, newsgroups, BIGWIDTH);
2835 str[BIGWIDTH] = '\0';
2838 break;
2840 case iNewsAndTo:
2841 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2842 strncpy(str, newsgroups, sizeof(str));
2844 if((l = strlen(str)) < sizeof(str)){
2845 if(sizeof(str) - l < 6)
2846 strncpy(str+l, "...", sizeof(str)-l);
2847 else{
2848 if(l > 0){
2849 strncpy(str+l, " and ", sizeof(str)-l);
2850 set_index_addr(idata, "To", fetch_to(idata),
2851 NULL, BIGWIDTH-l-5, str+l+5);
2852 if(!str[l+5])
2853 str[l] = '\0';
2855 else
2856 set_index_addr(idata, "To", fetch_to(idata),
2857 NULL, BIGWIDTH, str);
2861 break;
2863 case iToAndNews:
2864 set_index_addr(idata, "To", fetch_to(idata),
2865 NULL, BIGWIDTH, str);
2866 if((l = strlen(str)) < sizeof(str) &&
2867 (newsgroups = fetch_newsgroups(idata))){
2868 if(sizeof(str) - l < 6)
2869 strncpy(str+l, "...", sizeof(str)-l);
2870 else{
2871 if(l > 0)
2872 strncpy(str+l, " and ", sizeof(str)-l);
2874 if(l > 0)
2875 strncpy(str+l+5, newsgroups, BIGWIDTH-l-5);
2876 else
2877 strncpy(str, newsgroups, BIGWIDTH);
2881 break;
2883 case iNewsAndRecips:
2884 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2885 strncpy(str, newsgroups, BIGWIDTH);
2887 if((l = strlen(str)) < BIGWIDTH){
2888 if(BIGWIDTH - l < 6)
2889 strncpy(str+l, "...", BIGWIDTH-l);
2890 else{
2891 toaddr = fetch_to(idata);
2892 ccaddr = fetch_cc(idata);
2893 for(last_to = toaddr;
2894 last_to && last_to->next;
2895 last_to = last_to->next)
2898 /* point end of to list temporarily at cc list */
2899 if(last_to)
2900 last_to->next = ccaddr;
2902 if(l > 0){
2903 strncpy(str+l, " and ", sizeof(str)-l);
2904 set_index_addr(idata, "To", toaddr,
2905 NULL, BIGWIDTH-l-5, str+l+5);
2906 if(!str[l+5])
2907 str[l] = '\0';
2909 else
2910 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2912 if(last_to)
2913 last_to->next = NULL;
2917 break;
2919 case iRecipsAndNews:
2920 toaddr = fetch_to(idata);
2921 ccaddr = fetch_cc(idata);
2922 for(last_to = toaddr;
2923 last_to && last_to->next;
2924 last_to = last_to->next)
2927 /* point end of to list temporarily at cc list */
2928 if(last_to)
2929 last_to->next = ccaddr;
2931 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2933 if(last_to)
2934 last_to->next = NULL;
2936 if((l = strlen(str)) < BIGWIDTH &&
2937 (newsgroups = fetch_newsgroups(idata))){
2938 if(BIGWIDTH - l < 6)
2939 strncpy(str+l, "...", BIGWIDTH-l);
2940 else{
2941 if(l > 0)
2942 strncpy(str+l, " and ", sizeof(str)-l);
2944 if(l > 0)
2945 strncpy(str+l+5, newsgroups, BIGWIDTH-l-5);
2946 else
2947 strncpy(str, newsgroups, BIGWIDTH);
2951 break;
2953 case iPrio:
2954 case iPrioAlpha:
2955 case iPrioBang:
2956 prio_str(idata, cdesc->ctype, ice);
2957 break;
2959 case iHeader:
2960 header_str(idata, cdesc->hdrtok, ice);
2961 break;
2963 case iText:
2964 strncpy(str, (cdesc->hdrtok && cdesc->hdrtok->hdrname) ? cdesc->hdrtok->hdrname : "", sizeof(str));
2965 str[sizeof(str)-1] = '\0';
2966 break;
2968 default:
2969 break;
2973 * If the element wasn't already filled in above, do it here.
2975 if(!ifield->ielem){
2976 ielem = new_ielem(&ifield->ielem);
2978 if((color = hdr_color(itokens[itokensinv[cdesc->ctype].ctype].name, NULL, ps_global->index_token_colors)) != NULL){
2979 if(pico_usingcolor()){
2980 ielem->color = new_color_pair(color->fg, color->bg);
2981 ielem->type = eTypeCol;
2983 free_color_pair(&color);
2986 ielem->freedata = 1;
2987 ielem->data = cpystr(str);
2988 ielem->datalen = strlen(str);
2990 if(fromfield && pico_usingcolor()
2991 && ps_global->VAR_IND_FROM_FORE_COLOR
2992 && ps_global->VAR_IND_FROM_BACK_COLOR){
2993 ielem->type = eTypeCol;
2994 ielem->freecolor = 1;
2995 ielem->color = new_color_pair(ps_global->VAR_IND_FROM_FORE_COLOR,
2996 ps_global->VAR_IND_FROM_BACK_COLOR);
2998 * This space is here so that if the text does
2999 * not extend all the way to the end of the field then
3000 * we'll switch the color back and paint the rest of the
3001 * field in the Normal color or the index line color.
3003 ielem = new_ielem(&ielem);
3004 ielem->freedata = 1;
3005 ielem->data = cpystr(" ");
3006 ielem->datalen = 1;
3008 else if((cdesc->ctype == iOpeningText || cdesc->ctype == iOpeningTextNQ)
3009 && pico_usingcolor()
3010 && ps_global->VAR_IND_OP_FORE_COLOR
3011 && ps_global->VAR_IND_OP_BACK_COLOR){
3012 ielem->type = eTypeCol;
3013 ielem->freecolor = 1;
3014 ielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR,
3015 ps_global->VAR_IND_OP_BACK_COLOR);
3017 * This space is here so that if the text does
3018 * not extend all the way to the end of the field then
3019 * we'll switch the color back and paint the rest of the
3020 * field in the Normal color or the index line color.
3022 ielem = new_ielem(&ielem);
3023 ielem->freedata = 1;
3024 ielem->data = cpystr(" ");
3025 ielem->datalen = 1;
3028 ifield->leftadj = (cdesc->adjustment == Left) ? 1 : 0;
3029 set_ielem_widths_in_field(ifield);
3033 ice->widths_done = 1;
3034 ice->id = ice_hash(ice);
3037 * Now we have to put the temporary copy of ice back as the
3038 * real thing.
3040 icep = fetch_ice_ptr(idata->stream, idata->rawno);
3041 if(icep){
3042 free_ice(icep); /* free what is already there */
3043 *icep = ice;
3046 return(ice);
3050 ICE_S *
3051 format_thread_index_line(INDEXDATA_S *idata)
3053 char *p, buffer[BIGWIDTH+1];
3054 int thdlen, space_left, i;
3055 PINETHRD_S *thrd = NULL;
3056 ICE_S *ice, *tice = NULL, **ticep = NULL;
3057 IFIELD_S *ifield;
3058 IELEM_S *ielem;
3059 int (*save_sfstr_func)(void);
3060 struct variable *vars = ps_global->vars;
3062 dprint((8, "=== format_thread_index_line(%ld,%ld) ===\n",
3063 idata ? idata->msgno : -1, idata ? idata->rawno : -1));
3065 space_left = ps_global->ttyo->screen_cols;
3067 if(ps_global->msgmap->max_thrdno < 1000)
3068 thdlen = 3;
3069 else if(ps_global->msgmap->max_thrdno < 10000)
3070 thdlen = 4;
3071 else if(ps_global->msgmap->max_thrdno < 100000)
3072 thdlen = 5;
3073 else
3074 thdlen = 6;
3076 ice = fetch_ice(idata->stream, idata->rawno);
3078 thrd = fetch_thread(idata->stream, idata->rawno);
3080 if(!thrd || !ice) /* can't happen? */
3081 return(ice);
3083 if(!ice->tice){
3084 tice = (ICE_S *) fs_get(sizeof(*tice));
3085 memset(tice, 0, sizeof(*tice));
3086 ice->tice = tice;
3089 tice = ice->tice;
3091 if(!tice)
3092 return(ice);
3094 free_ifield(&tice->ifield);
3096 ticep = &ice->tice;
3097 tice = copy_ice(tice);
3099 if(space_left >= 3){
3100 char to_us, status;
3102 p = buffer;
3103 to_us = to_us_symbol_for_thread(idata->stream, thrd, 1);
3104 status = status_symbol_for_thread(idata->stream, thrd, iStatus);
3106 if((p-buffer)+3 < sizeof(buffer)){
3107 p[0] = to_us;
3108 p[1] = ' ';
3109 p[2] = status;
3110 p[3] = '\0';;
3113 space_left -= 3;
3115 ifield = new_ifield(&tice->ifield);
3116 ifield->ctype = iStatus;
3117 ifield->width = 3;
3118 ifield->leftadj = 1;
3119 for(i = 0; i < 3; i++){
3120 ielem = new_ielem(&ifield->ielem);
3121 ielem->freedata = 1;
3122 ielem->data = (char *) fs_get(2 * sizeof(char));
3123 ielem->data[0] = p[i];
3124 ielem->data[1] = '\0';
3125 ielem->datalen = 1;
3126 set_print_format(ielem, 1, ifield->leftadj);
3129 if(pico_usingcolor()){
3130 if(to_us == '*'
3131 && VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
3132 ielem = ifield->ielem;
3133 ielem->freecolor = 1;
3134 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR,
3135 VAR_IND_IMP_BACK_COLOR);
3136 if(F_ON(F_COLOR_LINE_IMPORTANT, ps_global))
3137 tice->linecolor = new_color_pair(VAR_IND_IMP_FORE_COLOR,
3138 VAR_IND_IMP_BACK_COLOR);
3140 else if((to_us == '+' || to_us == '-')
3141 && VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
3142 ielem = ifield->ielem;
3143 ielem->freecolor = 1;
3144 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR,
3145 VAR_IND_PLUS_BACK_COLOR);
3148 if(status == 'D'
3149 && VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
3150 ielem = ifield->ielem->next->next;
3151 ielem->freecolor = 1;
3152 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR,
3153 VAR_IND_DEL_BACK_COLOR);
3155 else if(status == 'N'
3156 && VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
3157 ielem = ifield->ielem->next->next;
3158 ielem->freecolor = 1;
3159 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR,
3160 VAR_IND_NEW_BACK_COLOR);
3165 if(space_left >= thdlen+1){
3166 p = buffer;
3167 space_left--;
3169 snprintf(p, sizeof(buffer), "%*.*s", thdlen, thdlen, "");
3170 space_left -= thdlen;
3172 ifield = new_ifield(&tice->ifield);
3173 ifield->ctype = iMessNo;
3174 ifield->width = thdlen;
3175 ifield->leftadj = 0;
3176 ielem = new_ielem(&ifield->ielem);
3177 ielem->freedata = 1;
3178 ielem->data = cpystr(p);
3179 ielem->datalen = strlen(p);
3180 set_print_format(ielem, ifield->width, ifield->leftadj);
3183 if(space_left >= 7){
3185 p = buffer;
3186 space_left--;
3188 date_str(fetch_date(idata), iDate, 0, p, sizeof(buffer), 0);
3189 if(sizeof(buffer) > 6)
3190 p[6] = '\0';
3192 if(strlen(p) < 6 && (sizeof(buffer)) > 6){
3193 char *q;
3195 for(q = p + strlen(p); q < p + 6; q++)
3196 *q = ' ';
3199 space_left -= 6;
3201 ifield = new_ifield(&tice->ifield);
3202 ifield->ctype = iDate;
3203 ifield->width = 6;
3204 ifield->leftadj = 1;
3205 ielem = new_ielem(&ifield->ielem);
3206 ielem->freedata = 1;
3207 ielem->data = cpystr(p);
3208 ielem->datalen = ifield->width;
3209 set_print_format(ielem, ifield->width, ifield->leftadj);
3213 if(space_left > 3){
3214 int from_width, subj_width, bigthread_adjust;
3215 long in_thread;
3216 char from[BIGWIDTH+1];
3217 char tcnt[50];
3219 space_left--;
3221 in_thread = count_lflags_in_thread(idata->stream, thrd,
3222 ps_global->msgmap, MN_NONE);
3224 p = buffer;
3225 if(in_thread == 1 && THRD_AUTO_VIEW())
3226 snprintf(tcnt, sizeof(tcnt), " ");
3227 else
3228 snprintf(tcnt, sizeof(tcnt), "(%ld)", in_thread);
3230 bigthread_adjust = MAX(0, strlen(tcnt) - 3);
3232 /* third of the rest */
3233 from_width = MAX((space_left-1)/3 - bigthread_adjust, 1);
3235 /* the rest */
3236 subj_width = space_left - from_width - 1;
3238 if(strlen(tcnt) > subj_width)
3239 tcnt[subj_width] = '\0';
3241 from[0] = '\0';
3242 save_sfstr_func = pith_opt_truncate_sfstr;
3243 pith_opt_truncate_sfstr = NULL;
3244 from_str(iFromTo, idata, from, sizeof(from), tice);
3245 pith_opt_truncate_sfstr = save_sfstr_func;
3247 ifield = new_ifield(&tice->ifield);
3248 ifield->leftadj = 1;
3249 ielem = new_ielem(&ifield->ielem);
3250 ielem->freedata = 1;
3251 ielem->type = eTypeCol;
3252 ielem->data = cpystr(from);
3253 ielem->datalen = strlen(from);
3254 ifield->width = from_width;
3255 set_print_format(ielem, ifield->width, ifield->leftadj);
3256 ifield->ctype = iFrom;
3257 if(from_width > 0 && pico_usingcolor()
3258 && VAR_IND_FROM_FORE_COLOR && VAR_IND_FROM_BACK_COLOR){
3259 ielem->freecolor = 1;
3260 ielem->color = new_color_pair(VAR_IND_FROM_FORE_COLOR,
3261 VAR_IND_FROM_BACK_COLOR);
3264 ifield = new_ifield(&tice->ifield);
3265 ifield->leftadj = 0;
3266 ielem = new_ielem(&ifield->ielem);
3267 ielem->freedata = 1;
3268 ielem->data = cpystr(tcnt);
3269 ielem->datalen = strlen(tcnt);
3270 ifield->width = ielem->datalen;
3271 set_print_format(ielem, ifield->width, ifield->leftadj);
3272 ifield->ctype = iAtt; /* not used, except that it isn't special */
3274 subj_width -= strlen(tcnt);
3276 if(subj_width > 0)
3277 subj_width--;
3279 if(subj_width > 0){
3280 if(idata->bogus){
3281 if(idata->bogus < 2)
3282 snprintf(buffer, sizeof(buffer), "%-.*s", BIGWIDTH,
3283 _("[ No Message Text Available ]"));
3285 else{
3286 buffer[0] = '\0';
3287 save_sfstr_func = pith_opt_truncate_sfstr;
3288 pith_opt_truncate_sfstr = NULL;
3289 subj_str(idata, buffer, sizeof(buffer), NoKW, 0, 0, NULL);
3290 pith_opt_truncate_sfstr = save_sfstr_func;
3293 ifield = new_ifield(&tice->ifield);
3294 ifield->leftadj = 1;
3295 ielem = new_ielem(&ifield->ielem);
3296 ielem->freedata = 1;
3297 ielem->type = eTypeCol;
3298 ielem->data = cpystr(buffer);
3299 ielem->datalen = strlen(buffer);
3300 ifield->width = subj_width;
3301 set_print_format(ielem, ifield->width, ifield->leftadj);
3302 ifield->ctype = iSubject;
3303 if(pico_usingcolor() && VAR_IND_SUBJ_FORE_COLOR && VAR_IND_SUBJ_BACK_COLOR){
3304 ielem->freecolor = 1;
3305 ielem->color = new_color_pair(VAR_IND_SUBJ_FORE_COLOR,
3306 VAR_IND_SUBJ_BACK_COLOR);
3310 else if(space_left > 1){
3311 snprintf(p, sizeof(buffer)-(p-buffer), "%-.*s", space_left-1, " ");
3312 ifield = new_ifield(&tice->ifield);
3313 ifield->leftadj = 1;
3314 ielem = new_ielem(&ifield->ielem);
3315 ielem->freedata = 1;
3316 ielem->data = cpystr(p);
3317 ielem->datalen = strlen(p);
3318 ifield->width = space_left-1;
3319 set_print_format(ielem, ifield->width, ifield->leftadj);
3320 ifield->ctype = iSubject;
3323 tice->widths_done = 1;
3324 tice->id = ice_hash(tice);
3326 if(ticep){
3327 free_ice(ticep); /* free what is already there */
3328 *ticep = tice;
3331 return(ice);
3336 * Print the fields of ice in buf with a single space between fields.
3338 * Args buf -- place to put the line
3339 * ice -- the data for the line
3340 * msgno -- this is the msgno to be used, blanks if <= 0
3342 * Returns a pointer to buf.
3344 char *
3345 simple_index_line(char *buf, size_t buflen, ICE_S *ice, long int msgno)
3347 char *p;
3348 IFIELD_S *ifield, *previfield = NULL;
3349 IELEM_S *ielem;
3351 if(!buf)
3352 alpine_panic("NULL buf in simple_index_line()");
3354 if(buflen > 0)
3355 buf[0] = '\0';
3357 p = buf;
3359 if(ice){
3361 for(ifield = ice->ifield; ifield && p-buf < buflen; ifield = ifield->next){
3363 /* space between fields */
3364 if(ifield != ice->ifield && !(previfield && previfield->ctype == iText))
3365 *p++ = ' ';
3367 /* message number string is generated on the fly */
3368 if(ifield->ctype == iMessNo){
3369 ielem = ifield->ielem;
3370 if(ielem && ielem->datalen >= ifield->width){
3371 if(msgno > 0L)
3372 snprintf(ielem->data, ielem->datalen+1, "%*.ld", ifield->width, msgno);
3373 else
3374 snprintf(ielem->data, ielem->datalen+1, "%*.*s", ifield->width, ifield->width, "");
3378 for(ielem = ifield->ielem;
3379 ielem && ielem->print_format && p-buf < buflen;
3380 ielem = ielem->next){
3381 char *src;
3382 size_t bytes_added;
3384 src = ielem->data;
3385 bytes_added = utf8_pad_to_width(p, src,
3386 buflen-(p-buf) * sizeof(char),
3387 ielem->wid, ifield->leftadj);
3388 p += bytes_added;
3391 previfield = ifield;
3394 if(p-buf < buflen)
3395 *p = '\0';
3398 buf[buflen-1] = '\0';
3400 return(buf);
3405 * Look in current mail_stream for matches for messages in the searchset
3406 * which match a color rule pattern. Return the color.
3407 * The searched bit will be set for all of the messages which match the
3408 * first pattern which has a match.
3410 * Args stream -- the mail stream
3411 * searchset -- restrict attention to this set of messages
3412 * pstate -- The pattern state. On the first call it will be Null.
3413 * Null means start over with a new first_pattern.
3414 * After that it will be pointing to our local PAT_STATE
3415 * so that next_pattern goes to the next one after the
3416 * ones we've already checked.
3418 * Returns 0 if no match, 1 if a match.
3419 * The color that goes with the matched rule in returned_color.
3420 * It may be NULL, which indicates default.
3423 get_index_line_color(MAILSTREAM *stream, SEARCHSET *searchset,
3424 PAT_STATE **pstate, COLOR_PAIR **returned_color)
3426 PAT_S *pat = NULL;
3427 long rflags = ROLE_INCOL;
3428 COLOR_PAIR *color = NULL;
3429 int match = 0;
3430 static PAT_STATE localpstate;
3432 dprint((7, "get_index_line_color\n"));
3434 if(returned_color)
3435 *returned_color = NULL;
3437 if(*pstate)
3438 pat = next_pattern(*pstate);
3439 else{
3440 *pstate = &localpstate;
3441 if(!nonempty_patterns(rflags, *pstate))
3442 *pstate = NULL;
3444 if(*pstate)
3445 pat = first_pattern(*pstate);
3448 if(*pstate){
3450 /* Go through the possible roles one at a time until we get a match. */
3451 while(!match && pat){
3452 if(match_pattern(pat->patgrp, stream, searchset, NULL,
3453 get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
3454 if(!pat->action || pat->action->bogus)
3455 break;
3457 match++;
3458 if(pat->action && pat->action->incol)
3459 color = new_color_pair(pat->action->incol->fg,
3460 pat->action->incol->bg);
3462 else
3463 pat = next_pattern(*pstate);
3467 if(match && returned_color)
3468 *returned_color = color;
3470 return(match);
3478 index_in_overview(MAILSTREAM *stream)
3480 INDEX_COL_S *cdesc = NULL;
3482 if(!(stream->mailbox && IS_REMOTE(stream->mailbox)))
3483 return(FALSE); /* no point! */
3485 if(stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "nntp")){
3487 if(THRD_INDX())
3488 return(TRUE);
3490 for(cdesc = ps_global->index_disp_format;
3491 cdesc->ctype != iNothing;
3492 cdesc++)
3493 switch(cdesc->ctype){
3494 case iTo: /* can't be satisfied by XOVER */
3495 case iSender: /* ... or specifically handled */
3496 case iDescripSize: /* ... in news case */
3497 case iAtt:
3498 return(FALSE);
3500 default :
3501 break;
3505 return(TRUE);
3511 * fetch_from - called to get a the index entry's "From:" field
3514 resent_to_us(INDEXDATA_S *idata)
3516 if(!idata->valid_resent_to){
3517 static char *fields[] = {"Resent-To", NULL};
3518 char *h;
3520 if(idata->no_fetch){
3521 idata->bogus = 1; /* don't do this */
3522 return(FALSE);
3525 if((h = pine_fetchheader_lines(idata->stream,idata->rawno,NULL,fields)) != NULL){
3526 idata->resent_to_us = parsed_resent_to_us(h);
3527 fs_give((void **) &h);
3530 idata->valid_resent_to = 1;
3533 return(idata->resent_to_us);
3538 parsed_resent_to_us(char *h)
3540 char *p, *q;
3541 ADDRESS *addr = NULL;
3542 int rv = FALSE;
3544 if((p = strindex(h, ':')) != NULL){
3545 for(q = ++p; (q = strpbrk(q, "\015\012")) != NULL; q++)
3546 *q = ' '; /* quash junk */
3548 rfc822_parse_adrlist(&addr, p, ps_global->maildomain);
3549 if(addr){
3550 rv = address_is_us(addr, ps_global);
3551 mail_free_address(&addr);
3555 return(rv);
3561 * fetch_from - called to get a the index entry's "From:" field
3563 ADDRESS *
3564 fetch_from(INDEXDATA_S *idata)
3566 if(idata->no_fetch) /* implies from is valid */
3567 return(idata->from);
3568 else if(idata->bogus)
3569 idata->bogus = 2;
3570 else{
3571 ENVELOPE *env;
3573 /* c-client call's just cache access at this point */
3574 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3575 return(env->from);
3577 idata->bogus = 1;
3580 return(NULL);
3585 * fetch_to - called to get a the index entry's "To:" field
3587 ADDRESS *
3588 fetch_to(INDEXDATA_S *idata)
3590 if(idata->no_fetch){ /* check for specific validity */
3591 if(idata->valid_to)
3592 return(idata->to);
3593 else
3594 idata->bogus = 1; /* can't give 'em what they want */
3596 else if(idata->bogus){
3597 idata->bogus = 2; /* elevate bogosity */
3599 else{
3600 ENVELOPE *env;
3602 /* c-client call's just cache access at this point */
3603 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3604 return(env->to);
3606 idata->bogus = 1;
3609 return(NULL);
3614 * fetch_cc - called to get a the index entry's "Cc:" field
3616 ADDRESS *
3617 fetch_cc(INDEXDATA_S *idata)
3619 if(idata->no_fetch){ /* check for specific validity */
3620 if(idata->valid_cc)
3621 return(idata->cc);
3622 else
3623 idata->bogus = 1; /* can't give 'em what they want */
3625 else if(idata->bogus){
3626 idata->bogus = 2; /* elevate bogosity */
3628 else{
3629 ENVELOPE *env;
3631 /* c-client call's just cache access at this point */
3632 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3633 return(env->cc);
3635 idata->bogus = 1;
3638 return(NULL);
3644 * fetch_sender - called to get a the index entry's "Sender:" field
3646 ADDRESS *
3647 fetch_sender(INDEXDATA_S *idata)
3649 if(idata->no_fetch){ /* check for specific validity */
3650 if(idata->valid_sender)
3651 return(idata->sender);
3652 else
3653 idata->bogus = 1; /* can't give 'em what they want */
3655 else if(idata->bogus){
3656 idata->bogus = 2; /* elevate bogosity */
3658 else{
3659 ENVELOPE *env;
3661 /* c-client call's just cache access at this point */
3662 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3663 return(env->sender);
3665 idata->bogus = 1;
3668 return(NULL);
3673 * fetch_newsgroups - called to get a the index entry's "Newsgroups:" field
3675 char *
3676 fetch_newsgroups(INDEXDATA_S *idata)
3678 if(idata->no_fetch){ /* check for specific validity */
3679 if(idata->valid_news)
3680 return(idata->newsgroups);
3681 else
3682 idata->bogus = 1; /* can't give 'em what they want */
3684 else if(idata->bogus){
3685 idata->bogus = 2; /* elevate bogosity */
3687 else{
3688 ENVELOPE *env;
3690 /* c-client call's just cache access at this point */
3691 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3692 return(env->newsgroups);
3694 idata->bogus = 1;
3697 return(NULL);
3702 * fetch_subject - called to get at the index entry's "Subject:" field
3704 char *
3705 fetch_subject(INDEXDATA_S *idata)
3707 if(idata->no_fetch) /* implies subject is valid */
3708 return(idata->subject);
3709 else if(idata->bogus)
3710 idata->bogus = 2;
3711 else{
3712 ENVELOPE *env;
3714 /* c-client call's just cache access at this point */
3715 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3716 return(env->subject);
3718 idata->bogus = 1;
3721 return(NULL);
3726 * Return an allocated copy of the first few characters from the body
3727 * of the message for possible use in the index screen.
3729 * Maybe we could figure out some way to do aggregate calls to get
3730 * this info for all the lines in view instead of all the one at a
3731 * time calls we're doing now.
3733 char *
3734 fetch_firsttext(INDEXDATA_S *idata, int delete_quotes)
3736 ENVELOPE *env;
3737 BODY *body = NULL;
3738 char *firsttext = NULL;
3739 STORE_S *so;
3740 gf_io_t pc;
3741 long partial_fetch_len = 0L;
3742 SEARCHSET *ss, **sset;
3744 try_again:
3747 * Prevent wild prefetch, just get the one we're after.
3748 * Can we get this somehow in the overview call in build_header_work?
3750 ss = mail_newsearchset();
3751 ss->first = idata->rawno;
3752 sset = (SEARCHSET **) mail_parameters(idata->stream,
3753 GET_FETCHLOOKAHEAD,
3754 (void *) idata->stream);
3755 if(sset)
3756 *sset = ss;
3758 if((env = pine_mail_fetchstructure(idata->stream, idata->rawno, &body)) != NULL){
3759 if(body){
3760 char *subtype = NULL;
3761 char *partno;
3763 if((body->type == TYPETEXT
3764 && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
3766 (body->type == TYPEMULTIPART && body->nested.part
3767 && body->nested.part->body.type == TYPETEXT
3768 && (subtype=body->nested.part->body.subtype)
3769 && ALLOWED_SUBTYPE(subtype))
3771 (body->type == TYPEMULTIPART && body->nested.part
3772 && body->nested.part->body.type == TYPEMULTIPART
3773 && body->nested.part->body.nested.part
3774 && body->nested.part->body.nested.part->body.type == TYPETEXT
3775 && (subtype=body->nested.part->body.nested.part->body.subtype)
3776 && ALLOWED_SUBTYPE(subtype))){
3778 if((so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
3779 char buf[1025], *p;
3780 unsigned char c;
3781 int success;
3782 int one_space_done = 0;
3784 if(partial_fetch_len == 0L){
3785 if(subtype && !strucmp(subtype, "html"))
3786 partial_fetch_len = 1024L;
3787 else if(subtype && !strucmp(subtype, "plain"))
3788 partial_fetch_len = delete_quotes ? 128L : 64L;
3789 else
3790 partial_fetch_len = 256L;
3793 if((body->type == TYPETEXT
3794 && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
3796 (body->type == TYPEMULTIPART && body->nested.part
3797 && body->nested.part->body.type == TYPETEXT
3798 && (subtype=body->nested.part->body.subtype)
3799 && ALLOWED_SUBTYPE(subtype)))
3800 partno = "1";
3801 else
3802 partno = "1.1";
3804 gf_set_so_writec(&pc, so);
3805 success = get_body_part_text(idata->stream, body, idata->rawno,
3806 partno, partial_fetch_len, pc,
3807 NULL, NULL,
3808 GBPT_NOINTR | GBPT_PEEK |
3809 (delete_quotes ? GBPT_DELQUOTES : 0));
3810 gf_clear_so_writec(so);
3812 if(success){
3813 so_seek(so, 0L, 0);
3814 p = buf;
3815 while(p-buf < sizeof(buf)-1 && so_readc(&c, so)){
3816 /* delete leading whitespace */
3817 if(p == buf && isspace(c))
3819 /* and include just one space per run of whitespace */
3820 else if(isspace(c)){
3821 if(!one_space_done){
3822 *p++ = SPACE;
3823 one_space_done++;
3826 else{
3827 one_space_done = 0;
3828 *p++ = c;
3832 *p = '\0';
3834 if(p > buf){
3835 size_t l;
3837 l = strlen(buf);
3838 l += 100;
3839 firsttext = fs_get((l+1) * sizeof(char));
3840 firsttext[0] = '\0';
3841 iutf8ncpy(firsttext, buf, l);
3842 firsttext[l] = '\0';
3843 removing_trailing_white_space(firsttext);
3847 so_give(&so);
3849 /* first if means we didn't fetch all of the data */
3850 if(!(success > 1 && success < partial_fetch_len)){
3851 if(partial_fetch_len < 4096L
3852 && (!firsttext || utf8_width(firsttext) < 50)){
3853 if(firsttext)
3854 fs_give((void **) &firsttext);
3856 partial_fetch_len = 4096L;
3857 goto try_again;
3865 if(ss)
3866 mail_free_searchset(&ss);
3868 return(firsttext);
3873 * fetch_date - called to get at the index entry's "Date:" field
3875 char *
3876 fetch_date(INDEXDATA_S *idata)
3878 if(idata->no_fetch) /* implies date is valid */
3879 return(idata->date);
3880 else if(idata->bogus)
3881 idata->bogus = 2;
3882 else{
3883 ENVELOPE *env;
3885 /* c-client call's just cache access at this point */
3886 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3887 return((char *) env->date);
3889 idata->bogus = 1;
3892 return(NULL);
3897 * fetch_header - called to get at the index entry's "Hdrname:" field
3899 char *
3900 fetch_header(INDEXDATA_S *idata, char *hdrname)
3902 if(idata->no_fetch)
3903 idata->bogus = 1;
3904 else if(idata->bogus)
3905 idata->bogus = 2;
3906 else{
3907 char *h, *p, *q, *decoded, *fields[2];
3908 size_t retsize, decsize;
3909 char *ret = NULL;
3910 unsigned char *decode_buf = NULL;
3912 fields[0] = hdrname;
3913 fields[1] = NULL;
3914 if(hdrname && hdrname[0]
3915 && (h = pine_fetchheader_lines(idata->stream, idata->rawno,
3916 NULL, fields))){
3918 if(strlen(h) < strlen(hdrname) + 1){
3919 fs_give((void **) &h);
3920 return(cpystr(""));
3923 /* skip "hdrname:" */
3924 for(p = h + strlen(hdrname) + 1;
3925 *p && isspace((unsigned char)*p); p++)
3928 decsize = (4 * strlen(p)) + 1;
3929 decode_buf = (unsigned char *) fs_get(decsize * sizeof(unsigned char));
3930 decoded = (char *) rfc1522_decode_to_utf8(decode_buf, decsize, p);
3931 p = decoded;
3933 retsize = strlen(decoded);
3934 q = ret = (char *) fs_get((retsize+1) * sizeof(char));
3936 *q = '\0';
3937 while(q-ret < retsize && *p){
3938 if(*p == '\015' || *p == '\012')
3939 p++;
3940 else if(*p == '\t'){
3941 *q++ = SPACE;
3942 p++;
3944 else
3945 *q++ = *p++;
3948 *q = '\0';
3950 fs_give((void **) &h);
3951 if(decode_buf)
3952 fs_give((void **) &decode_buf);
3954 return(ret);
3957 idata->bogus = 1;
3960 return(NULL);
3965 * fetch_size - called to get at the index entry's "size" field
3967 long
3968 fetch_size(INDEXDATA_S *idata)
3970 if(idata->no_fetch) /* implies size is valid */
3971 return(idata->size);
3972 else if(idata->bogus)
3973 idata->bogus = 2;
3974 else{
3975 MESSAGECACHE *mc;
3977 if(idata->stream && idata->rawno > 0L
3978 && idata->rawno <= idata->stream->nmsgs
3979 && (mc = mail_elt(idata->stream, idata->rawno)))
3980 return(mc->rfc822_size);
3982 idata->bogus = 1;
3985 return(0L);
3990 * fetch_body - called to get a the index entry's body structure
3992 BODY *
3993 fetch_body(INDEXDATA_S *idata)
3995 BODY *body;
3997 if(idata->bogus || idata->no_fetch){
3998 idata->bogus = 2;
3999 return(NULL);
4002 if(pine_mail_fetchstructure(idata->stream, idata->rawno, &body))
4003 return(body);
4005 idata->bogus = 1;
4006 return(NULL);
4011 * s is at least size width+1
4014 set_index_addr(INDEXDATA_S *idata,
4015 char *field,
4016 struct mail_address *addr,
4017 char *prefix,
4018 int width,
4019 char *s)
4021 ADDRESS *atmp;
4022 char *p, *stmp = NULL, *sptr;
4023 char *save_personal = NULL;
4024 int orig_width;
4026 s[0] = '\0';
4028 for(atmp = addr; idata->stream && atmp; atmp = atmp->next)
4029 if(atmp->host && atmp->host[0] == '.'){
4030 char *pref, *h, *fields[2];
4032 if(idata->no_fetch){
4033 idata->bogus = 1;
4034 return(TRUE);
4037 fields[0] = field;
4038 fields[1] = NULL;
4039 if((h = pine_fetchheader_lines(idata->stream, idata->rawno,
4040 NULL, fields)) != NULL){
4041 if(strlen(h) < strlen(field) + 1){
4042 p = h + strlen(h);
4044 else{
4045 /* skip "field:" */
4046 for(p = h + strlen(field) + 1;
4047 *p && isspace((unsigned char)*p); p++)
4051 orig_width = width;
4052 sptr = stmp = (char *) fs_get((orig_width+1) * sizeof(char));
4054 /* add prefix */
4055 for(pref = prefix; pref && *pref; pref++)
4056 if(width){
4057 *sptr++ = *pref;
4058 width--;
4060 else
4061 break;
4063 while(width--)
4064 if(*p == '\015' || *p == '\012')
4065 p++; /* skip CR LF */
4066 else if(!*p)
4067 *sptr++ = ' ';
4068 else if(*p == '\t'){
4069 *sptr++ = ' ';
4070 p++;
4072 else
4073 *sptr++ = *p++;
4075 *sptr = '\0'; /* tie off return string */
4077 if(stmp){
4078 iutf8ncpy(s, stmp, orig_width+1);
4079 s[orig_width] = '\0';
4080 fs_give((void **) &stmp);
4083 fs_give((void **) &h);
4084 return(TRUE);
4086 /* else fall thru and display what c-client gave us */
4089 if(addr && !addr->next /* only one address */
4090 && addr->host /* not group syntax */
4091 && addr->personal && addr->personal[0]){ /* there is a personal name */
4092 char buftmp[MAILTMPLEN];
4093 int l;
4095 if((l = prefix ? strlen(prefix) : 0) != 0)
4096 strncpy(s, prefix, width+1);
4098 snprintf(buftmp, sizeof(buftmp), "%s", addr->personal);
4099 p = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
4100 SIZEOF_20KBUF, buftmp);
4101 removing_leading_and_trailing_white_space(p);
4103 iutf8ncpy(s + l, p, width - l);
4105 s[width] = '\0';
4107 if(*(s+l))
4108 return(TRUE);
4109 else{
4110 save_personal = addr->personal;
4111 addr->personal = NULL;
4115 if(addr){
4116 char *a_string;
4117 int l;
4119 a_string = addr_list_string(addr, NULL, 0);
4120 if(save_personal)
4121 addr->personal = save_personal;
4123 if((l = prefix ? strlen(prefix) : 0) != 0)
4124 strncpy(s, prefix, width+1);
4126 iutf8ncpy(s + l, a_string, width - l);
4127 s[width] = '\0';
4129 fs_give((void **)&a_string);
4131 return(TRUE);
4134 if(save_personal)
4135 addr->personal = save_personal;
4137 return(FALSE);
4141 void
4142 index_data_env(INDEXDATA_S *idata, ENVELOPE *env)
4144 if(!env){
4145 idata->bogus = 2;
4146 return;
4149 idata->from = env->from;
4150 idata->to = env->to;
4151 idata->cc = env->cc;
4152 idata->sender = env->sender;
4153 idata->subject = env->subject;
4154 idata->date = (char *) env->date;
4155 idata->newsgroups = env->newsgroups;
4157 idata->valid_to = 1; /* signal that everythings here */
4158 idata->valid_cc = 1;
4159 idata->valid_sender = 1;
4160 idata->valid_news = 1;
4165 * Put a string representing the date into str. The source date is
4166 * in the string datesrc. The format to be used is in type.
4167 * Notice that type is an IndexColType, but really only a subset of
4168 * IndexColType types are allowed.
4170 * Args datesrc -- The source date string
4171 * type -- What type of output we want
4172 * v -- If set, variable width output is ok. (Oct 9 not Oct 9)
4173 * str -- Put the answer here.
4174 * str_len -- Length of str
4175 * monabb_width -- This is a hack to get dates to line up right. For
4176 * example, in French (but without accents here)
4177 * dec. 21
4178 * fevr. 23
4179 * mars 7
4180 * For this monabb_width would be 5.
4182 void
4183 date_str(char *datesrc, IndexColType type, int v, char *str, size_t str_len,
4184 int monabb_width)
4186 char year4[5], /* 4 digit year */
4187 yearzero[3], /* zero padded, 2-digit year */
4188 monzero[3], /* zero padded, 2-digit month */
4189 mon[3], /* 1 or 2-digit month, no pad */
4190 dayzero[3], /* zero padded, 2-digit day */
4191 day[3], /* 1 or 2-digit day, no pad */
4192 dayord[3], /* 2-letter ordinal label */
4193 monabb[10], /* 3-letter month abbrev */
4194 /* actually maybe not 3 if localized */
4195 hour24[3], /* 2-digit, 24 hour clock hour */
4196 hour12[3], /* 12 hour clock hour, no pad */
4197 minzero[3], /* zero padded, 2-digit minutes */
4198 timezone[6]; /* timezone, like -0800 or +... */
4199 int hr12;
4200 int curtype, lastmonthtype, lastyeartype, preftype;
4201 int sdatetimetype, sdatetime24type;
4202 struct date d;
4203 #define TODAYSTR N_("Today")
4205 curtype = (type == iCurDate ||
4206 type == iCurDateIso ||
4207 type == iCurDateIsoS ||
4208 type == iCurPrefDate ||
4209 type == iCurPrefDateTime ||
4210 type == iCurPrefTime ||
4211 type == iCurTime24 ||
4212 type == iCurTime12 ||
4213 type == iCurDay ||
4214 type == iCurDay2Digit ||
4215 type == iCurDayOfWeek ||
4216 type == iCurDayOfWeekAbb ||
4217 type == iCurMon ||
4218 type == iCurMon2Digit ||
4219 type == iCurMonLong ||
4220 type == iCurMonAbb ||
4221 type == iCurYear ||
4222 type == iCurYear2Digit);
4223 lastmonthtype = (type == iLstMon ||
4224 type == iLstMon2Digit ||
4225 type == iLstMonLong ||
4226 type == iLstMonAbb ||
4227 type == iLstMonYear ||
4228 type == iLstMonYear2Digit);
4229 lastyeartype = (type == iLstYear ||
4230 type == iLstYear2Digit);
4231 sdatetimetype = (type == iSDateTime ||
4232 type == iSDateTimeIso ||
4233 type == iSDateTimeIsoS ||
4234 type == iSDateTimeS1 ||
4235 type == iSDateTimeS2 ||
4236 type == iSDateTimeS3 ||
4237 type == iSDateTimeS4 ||
4238 type == iSDateTime24 ||
4239 type == iSDateTimeIso24 ||
4240 type == iSDateTimeIsoS24 ||
4241 type == iSDateTimeS124 ||
4242 type == iSDateTimeS224 ||
4243 type == iSDateTimeS324 ||
4244 type == iSDateTimeS424);
4245 sdatetime24type = (type == iSDateTime24 ||
4246 type == iSDateTimeIso24 ||
4247 type == iSDateTimeIsoS24 ||
4248 type == iSDateTimeS124 ||
4249 type == iSDateTimeS224 ||
4250 type == iSDateTimeS324 ||
4251 type == iSDateTimeS424);
4252 preftype = (type == iPrefDate ||
4253 type == iPrefDateTime ||
4254 type == iPrefTime ||
4255 type == iCurPrefDate ||
4256 type == iCurPrefDateTime ||
4257 type == iCurPrefTime);
4258 if(str_len > 0)
4259 str[0] = '\0';
4261 if(!(datesrc && datesrc[0]) && !(curtype || lastmonthtype || lastyeartype))
4262 return;
4264 if(curtype || lastmonthtype || lastyeartype){
4265 char dbuf[200];
4267 rfc822_date(dbuf);
4268 parse_date(dbuf, &d);
4270 if(lastyeartype)
4271 d.year--;
4272 else if(lastmonthtype){
4273 d.month--;
4274 if(d.month <= 0){
4275 d.month = 12;
4276 d.year--;
4280 else{
4281 parse_date(F_ON(F_DATES_TO_LOCAL,ps_global)
4282 ? convert_date_to_local(datesrc) : datesrc, &d);
4283 if(d.year == -1 || d.month == -1 || d.day == -1){
4284 sdatetimetype = 0;
4285 sdatetime24type = 0;
4286 preftype = 0;
4287 switch(type){
4288 case iSDate: case iSDateTime: case iSDateTime24:
4289 type = iS1Date;
4290 break;
4292 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
4293 case iPrefDate: case iPrefTime: case iPrefDateTime:
4294 type = iDateIso;
4295 break;
4297 case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
4298 type = iDateIsoS;
4299 break;
4301 case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
4302 type = iS1Date;
4303 break;
4305 case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
4306 type = iS1Date;
4307 break;
4309 case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
4310 type = iS1Date;
4311 break;
4313 case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
4314 type = iS1Date;
4315 break;
4317 default:
4318 break;
4323 /* some special ones to start with */
4324 if(preftype){
4325 struct tm tm, *tmptr = NULL;
4326 time_t now;
4329 * Make sure we get the right one if we're using current time.
4331 if(curtype){
4332 now = time((time_t *) 0);
4333 if(now != (time_t) -1)
4334 tmptr = localtime(&now);
4337 if(!tmptr){
4338 memset(&tm, 0, sizeof(tm));
4339 tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
4340 tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
4341 tm.tm_mday = MIN(MAX(d.day, 1), 31);
4342 tm.tm_hour = MIN(MAX(d.hour, 0), 23);
4343 tm.tm_min = MIN(MAX(d.minute, 0), 59);
4344 tm.tm_wday = MIN(MAX(d.wkday, 0), 6);
4345 tmptr = &tm;
4348 switch(type){
4349 case iPrefDate:
4350 case iCurPrefDate:
4351 our_strftime(str, str_len, "%x", tmptr);
4352 break;
4353 case iPrefTime:
4354 case iCurPrefTime:
4355 our_strftime(str, str_len, "%X", tmptr);
4356 break;
4357 case iPrefDateTime:
4358 case iCurPrefDateTime:
4359 our_strftime(str, str_len, "%c", tmptr);
4360 break;
4361 default:
4362 assert(0);
4363 break;
4366 return;
4369 strncpy(monabb, (d.month > 0 && d.month < 13)
4370 ? month_abbrev_locale(d.month) : "", sizeof(monabb));
4371 monabb[sizeof(monabb)-1] = '\0';
4373 strncpy(mon, (d.month > 0 && d.month < 13)
4374 ? int2string(d.month) : "", sizeof(mon));
4375 mon[sizeof(mon)-1] = '\0';
4377 strncpy(day, (d.day > 0 && d.day < 32)
4378 ? int2string(d.day) : "", sizeof(day));
4379 day[sizeof(day)-1] = '\0';
4381 strncpy(dayord,
4382 (d.day <= 0 || d.day > 31) ? "" :
4383 (d.day == 1 || d.day == 21 || d.day == 31) ? "st" :
4384 (d.day == 2 || d.day == 22 ) ? "nd" :
4385 (d.day == 3 || d.day == 23 ) ? "rd" : "th", sizeof(dayord));
4387 dayord[sizeof(dayord)-1] = '\0';
4389 strncpy(year4, (d.year >= 1000 && d.year < 10000)
4390 ? int2string(d.year) : "????", sizeof(year4));
4391 year4[sizeof(year4)-1] = '\0';
4393 if(d.year >= 0){
4394 if((d.year % 100) < 10){
4395 yearzero[0] = '0';
4396 strncpy(yearzero+1, int2string(d.year % 100), sizeof(yearzero)-1);
4398 else
4399 strncpy(yearzero, int2string(d.year % 100), sizeof(yearzero));
4401 else
4402 strncpy(yearzero, "??", sizeof(yearzero));
4404 yearzero[sizeof(yearzero)-1] = '\0';
4406 if(d.month > 0 && d.month < 10){
4407 monzero[0] = '0';
4408 strncpy(monzero+1, int2string(d.month), sizeof(monzero)-1);
4410 else if(d.month >= 10 && d.month <= 12)
4411 strncpy(monzero, int2string(d.month), sizeof(monzero));
4412 else
4413 strncpy(monzero, "??", sizeof(monzero));
4415 monzero[sizeof(monzero)-1] = '\0';
4417 if(d.day > 0 && d.day < 10){
4418 dayzero[0] = '0';
4419 strncpy(dayzero+1, int2string(d.day), sizeof(dayzero)-1);
4421 else if(d.day >= 10 && d.day <= 31)
4422 strncpy(dayzero, int2string(d.day), sizeof(dayzero));
4423 else
4424 strncpy(dayzero, "??", sizeof(dayzero));
4426 dayzero[sizeof(dayzero)-1] = '\0';
4428 hr12 = (d.hour == 0) ? 12 :
4429 (d.hour > 12) ? (d.hour - 12) : d.hour;
4430 hour12[0] = '\0';
4431 if(hr12 > 0 && hr12 <= 12)
4432 strncpy(hour12, int2string(hr12), sizeof(hour12));
4434 hour12[sizeof(hour12)-1] = '\0';
4436 hour24[0] = '\0';
4437 if(d.hour >= 0 && d.hour < 10){
4438 hour24[0] = '0';
4439 strncpy(hour24+1, int2string(d.hour), sizeof(hour24)-1);
4441 else if(d.hour >= 10 && d.hour < 24)
4442 strncpy(hour24, int2string(d.hour), sizeof(hour24));
4444 hour24[sizeof(hour24)-1] = '\0';
4446 minzero[0] = '\0';
4447 if(d.minute >= 0 && d.minute < 10){
4448 minzero[0] = '0';
4449 strncpy(minzero+1, int2string(d.minute), sizeof(minzero)-1);
4451 else if(d.minute >= 10 && d.minute <= 60)
4452 strncpy(minzero, int2string(d.minute), sizeof(minzero));
4454 minzero[sizeof(minzero)-1] = '\0';
4456 if(sizeof(timezone) > 5){
4457 if(d.hours_off_gmt <= 0){
4458 timezone[0] = '-';
4459 d.hours_off_gmt *= -1;
4460 d.min_off_gmt *= -1;
4462 else
4463 timezone[0] = '+';
4465 timezone[1] = '\0';
4466 if(d.hours_off_gmt >= 0 && d.hours_off_gmt < 10){
4467 timezone[1] = '0';
4468 strncpy(timezone+2, int2string(d.hours_off_gmt), sizeof(timezone)-2);
4470 else if(d.hours_off_gmt >= 10 && d.hours_off_gmt < 24)
4471 strncpy(timezone+1, int2string(d.hours_off_gmt), sizeof(timezone)-1);
4472 else{
4473 timezone[1] = '0';
4474 timezone[2] = '0';
4477 timezone[3] = '\0';
4478 if(d.min_off_gmt >= 0 && d.min_off_gmt < 10){
4479 timezone[3] = '0';
4480 strncpy(timezone+4, int2string(d.min_off_gmt), sizeof(timezone)-4);
4482 else if(d.min_off_gmt >= 10 && d.min_off_gmt <= 60)
4483 strncpy(timezone+3, int2string(d.min_off_gmt), sizeof(timezone)-3);
4484 else{
4485 timezone[3] = '0';
4486 timezone[4] = '0';
4489 timezone[5] = '\0';
4490 timezone[sizeof(timezone)-1] = '\0';
4493 switch(type){
4494 case iRDate:
4495 /* this one is not locale-specific */
4496 snprintf(str, str_len, "%s%s%s %s %s",
4497 (d.wkday != -1) ? day_abbrev(d.wkday) : "",
4498 (d.wkday != -1) ? ", " : "",
4499 day,
4500 (d.month > 0 && d.month < 13) ? month_abbrev(d.month) : "",
4501 year4);
4502 break;
4503 case iDayOfWeekAbb:
4504 case iCurDayOfWeekAbb:
4505 strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_abbrev_locale(d.wkday) : "", str_len);
4506 str[str_len-1] = '\0';
4507 break;
4508 case iDayOfWeek:
4509 case iCurDayOfWeek:
4510 strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_name_locale(d.wkday) : "", str_len);
4511 str[str_len-1] = '\0';
4512 break;
4513 case iYear:
4514 case iCurYear:
4515 case iLstYear:
4516 case iLstMonYear:
4517 strncpy(str, year4, str_len);
4518 break;
4519 case iDay2Digit:
4520 case iCurDay2Digit:
4521 strncpy(str, dayzero, str_len);
4522 break;
4523 case iMon2Digit:
4524 case iCurMon2Digit:
4525 case iLstMon2Digit:
4526 strncpy(str, monzero, str_len);
4527 break;
4528 case iYear2Digit:
4529 case iCurYear2Digit:
4530 case iLstYear2Digit:
4531 case iLstMonYear2Digit:
4532 strncpy(str, yearzero, str_len);
4533 break;
4534 case iTimezone:
4535 strncpy(str, timezone, str_len);
4536 break;
4537 case iDay:
4538 case iCurDay:
4539 strncpy(str, day, str_len);
4540 break;
4541 case iDayOrdinal:
4542 snprintf(str, str_len, "%s%s", day, dayord);
4543 break;
4544 case iMon:
4545 case iCurMon:
4546 case iLstMon:
4547 if(d.month > 0 && d.month <= 12)
4548 strncpy(str, int2string(d.month), str_len);
4550 break;
4551 case iMonAbb:
4552 case iCurMonAbb:
4553 case iLstMonAbb:
4554 strncpy(str, monabb, str_len);
4555 break;
4556 case iMonLong:
4557 case iCurMonLong:
4558 case iLstMonLong:
4559 strncpy(str, (d.month > 0 && d.month < 13)
4560 ? month_name_locale(d.month) : "", str_len);
4561 break;
4562 case iDate:
4563 case iCurDate:
4564 if(v)
4565 snprintf(str, str_len, "%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day);
4566 else{
4567 if(monabb_width > 0)
4568 utf8_snprintf(str, str_len, "%-*.*w %2s",
4569 monabb_width, monabb_width, monabb, day);
4570 else
4571 snprintf(str, str_len, "%s %2s", monabb, day);
4574 break;
4575 case iLDate:
4576 if(v)
4577 snprintf(str, str_len, "%s%s%s%s%s", monabb,
4578 (monabb[0] && day[0]) ? " " : "", day,
4579 ((monabb[0] || day[0]) && year4[0]) ? ", " : "",
4580 year4);
4581 else{
4582 if(monabb_width > 0)
4583 utf8_snprintf(str, str_len, "%-*.*w %2s%c %4s",
4584 monabb_width, monabb_width,
4585 monabb, day,
4586 (monabb[0] && day[0] && year4[0]) ? ',' : ' ', year4);
4587 else
4588 snprintf(str, str_len, "%s %2s%c %4s", monabb, day,
4589 (monabb[0] && day[0] && year4[0]) ? ',' : ' ',
4590 year4);
4593 break;
4594 case iS1Date:
4595 case iS2Date:
4596 case iS3Date:
4597 case iS4Date:
4598 case iDateIso:
4599 case iDateIsoS:
4600 case iCurDateIso:
4601 case iCurDateIsoS:
4602 if(monzero[0] == '?' && dayzero[0] == '?' &&
4603 yearzero[0] == '?')
4604 snprintf(str, str_len, "%8s", "");
4605 else{
4606 switch(type){
4607 case iS1Date:
4608 snprintf(str, str_len, "%2s/%2s/%2s",
4609 monzero, dayzero, yearzero);
4610 break;
4611 case iS2Date:
4612 snprintf(str, str_len, "%2s/%2s/%2s",
4613 dayzero, monzero, yearzero);
4614 break;
4615 case iS3Date:
4616 snprintf(str, str_len, "%2s.%2s.%2s",
4617 dayzero, monzero, yearzero);
4618 break;
4619 case iS4Date:
4620 snprintf(str, str_len, "%2s.%2s.%2s",
4621 yearzero, monzero, dayzero);
4622 break;
4623 case iDateIsoS:
4624 case iCurDateIsoS:
4625 snprintf(str, str_len, "%2s-%2s-%2s",
4626 yearzero, monzero, dayzero);
4627 break;
4628 case iDateIso:
4629 case iCurDateIso:
4630 snprintf(str, str_len, "%4s-%2s-%2s",
4631 year4, monzero, dayzero);
4632 break;
4633 default:
4634 break;
4638 break;
4639 case iTime24:
4640 case iCurTime24:
4641 snprintf(str, str_len, "%2s%c%2s",
4642 (hour24[0] && minzero[0]) ? hour24 : "",
4643 (hour24[0] && minzero[0]) ? ':' : ' ',
4644 (hour24[0] && minzero[0]) ? minzero : "");
4645 break;
4646 case iTime12:
4647 case iCurTime12:
4648 snprintf(str, str_len, "%s%c%2s%s",
4649 (hour12[0] && minzero[0]) ? hour12 : "",
4650 (hour12[0] && minzero[0]) ? ':' : ' ',
4651 (hour12[0] && minzero[0]) ? minzero : "",
4652 (hour12[0] && minzero[0] && d.hour < 12) ? "am" :
4653 (hour12[0] && minzero[0] && d.hour >= 12) ? "pm" :
4654 " ");
4655 break;
4656 case iSDate: case iSDateIso: case iSDateIsoS:
4657 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
4658 case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
4659 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
4660 case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
4661 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
4662 { struct date now, last_day;
4663 char dbuf[200];
4664 int msg_day_of_year, now_day_of_year, today;
4665 int diff, ydiff, last_day_of_year;
4667 rfc822_date(dbuf);
4668 parse_date(dbuf, &now);
4669 today = day_of_week(&now) + 7;
4671 if(today >= 0+7 && today <= 6+7){
4672 now_day_of_year = day_of_year(&now);
4673 msg_day_of_year = day_of_year(&d);
4674 ydiff = now.year - d.year;
4676 if(msg_day_of_year == -1)
4677 diff = -100;
4678 else if(ydiff == 0)
4679 diff = now_day_of_year - msg_day_of_year;
4680 else if(ydiff == 1){
4681 last_day = d;
4682 last_day.month = 12;
4683 last_day.day = 31;
4684 last_day_of_year = day_of_year(&last_day);
4686 diff = now_day_of_year +
4687 (last_day_of_year - msg_day_of_year);
4689 else if(ydiff == -1){
4690 last_day = now;
4691 last_day.month = 12;
4692 last_day.day = 31;
4693 last_day_of_year = day_of_year(&last_day);
4695 diff = -1 * (msg_day_of_year +
4696 (last_day_of_year - now_day_of_year));
4698 else if(ydiff > 1)
4699 diff = 100;
4700 else
4701 diff = -100;
4703 if(diff == 0)
4704 strncpy(str, _(TODAYSTR), str_len);
4705 else if(diff == 1)
4706 strncpy(str, _("Yesterday"), str_len);
4707 else if(diff > 1 && diff < 7)
4708 snprintf(str, str_len, "%s", day_name_locale((today - diff) % 7));
4709 else if(diff == -1)
4710 strncpy(str, _("Tomorrow"), str_len);
4711 else if(diff < -1 && diff > -7)
4712 snprintf(str, str_len, _("Next %.3s!"),
4713 day_name_locale((today - diff) % 7));
4714 else if(diff > 0
4715 && (ydiff == 0
4716 || (ydiff == 1 && 12 + now.month - d.month < 6))){
4717 if(v)
4718 snprintf(str, str_len, "%s%s%s", monabb,
4719 (monabb[0] && day[0]) ? " " : "", day);
4720 else{
4721 if(monabb_width > 0)
4722 utf8_snprintf(str, str_len, "%-*.*w %2s",
4723 monabb_width, monabb_width, monabb, day);
4724 else
4725 snprintf(str, str_len, "%s %2s", monabb, day);
4728 else{
4729 if(msg_day_of_year == -1 && (type == iSDate || type == iSDateTime))
4730 type = iSDateTimeIsoS;
4732 switch(type){
4733 case iSDate: case iSDateTime: case iSDateTime24:
4735 struct tm tm;
4737 memset(&tm, 0, sizeof(tm));
4738 tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
4739 tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
4740 tm.tm_mday = MIN(MAX(d.day, 1), 31);
4741 tm.tm_hour = MIN(MAX(d.hour, 0), 23);
4742 tm.tm_min = MIN(MAX(d.minute, 0), 59);
4743 our_strftime(str, str_len, "%x", &tm);
4746 break;
4747 case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
4748 if(v)
4749 snprintf(str, str_len, "%s/%s/%s%s", mon, day, yearzero,
4750 diff < 0 ? "!" : "");
4751 else
4752 snprintf(str, str_len, "%s%s/%s/%s%s",
4753 (mon[0] && mon[1]) ? "" : " ",
4754 mon, dayzero, yearzero,
4755 diff < 0 ? "!" : "");
4756 break;
4757 case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
4758 if(v)
4759 snprintf(str, str_len, "%s/%s/%s%s", day, mon, yearzero,
4760 diff < 0 ? "!" : "");
4761 else
4762 snprintf(str, str_len, "%s%s/%s/%s%s",
4763 (day[0] && day[1]) ? "" : " ",
4764 day, monzero, yearzero,
4765 diff < 0 ? "!" : "");
4766 break;
4767 case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
4768 if(v)
4769 snprintf(str, str_len, "%s.%s.%s%s", day, mon, yearzero,
4770 diff < 0 ? "!" : "");
4771 else
4772 snprintf(str, str_len, "%s%s.%s.%s%s",
4773 (day[0] && day[1]) ? "" : " ",
4774 day, monzero, yearzero,
4775 diff < 0 ? "!" : "");
4776 break;
4777 case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
4778 if(v)
4779 snprintf(str, str_len, "%s.%s.%s%s",
4780 yearzero, monzero, dayzero,
4781 diff < 0 ? "!" : "");
4782 else
4783 snprintf(str, str_len, "%s.%s.%s%s",
4784 yearzero, monzero, dayzero,
4785 diff < 0 ? "!" : "");
4786 break;
4787 case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
4788 snprintf(str, str_len, "%2s-%2s-%2s",
4789 yearzero, monzero, dayzero);
4790 break;
4791 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
4792 snprintf(str, str_len, "%4s-%2s-%2s",
4793 year4, monzero, dayzero);
4794 break;
4795 default:
4796 break;
4801 else{
4802 if(v)
4803 snprintf(str, str_len, "%s%s%s", monabb,
4804 (monabb[0] && day[0]) ? " " : "", day);
4805 else{
4806 if(monabb_width > 0)
4807 utf8_snprintf(str, str_len, "%-*.*w %2s",
4808 monabb_width, monabb_width, monabb, day);
4809 else
4810 snprintf(str, str_len, "%s %2s", monabb, day);
4815 break;
4817 default:
4818 break;
4821 str[str_len-1] = '\0';
4823 if(type == iSTime ||
4824 (sdatetimetype && !strcmp(str, _(TODAYSTR)))){
4825 struct date now, last_day;
4826 char dbuf[200], *Ddd, *ampm;
4827 int daydiff;
4829 str[0] = '\0';
4830 rfc822_date(dbuf);
4831 parse_date(dbuf, &now);
4833 /* Figure out if message date lands in the past week */
4835 /* (if message dated this month or last month...) */
4836 if((d.year == now.year && d.month >= now.month - 1) ||
4837 (d.year == now.year - 1 && d.month == 12 && now.month == 1)){
4839 daydiff = day_of_year(&now) - day_of_year(&d);
4842 * If msg in end of last year (and we're in first bit of "this"
4843 * year), diff will be backwards; fix up by adding number of days
4844 * in last year (usually 365, but occasionally 366)...
4846 if(d.year == now.year - 1){
4847 last_day = d;
4848 last_day.month = 12;
4849 last_day.day = 31;
4851 daydiff += day_of_year(&last_day);
4854 else
4855 daydiff = -100; /* comfortably out of range (of past week) */
4857 /* Build 2-digit hour and am/pm indicator, used below */
4859 if(d.hour >= 0 && d.hour < 24){
4860 snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12);
4861 ampm = (d.hour < 12) ? "am" : "pm";
4862 snprintf(hour24, sizeof(hour24), "%02d", d.hour);
4864 else{
4865 strncpy(hour12, "??", sizeof(hour12));
4866 hour12[sizeof(hour12)-1] = '\0';
4867 ampm = "__";
4868 strncpy(hour24, "??", sizeof(hour24));
4869 hour24[sizeof(hour24)-1] = '\0';
4872 /* Build date/time in str, in format similar to that used by w(1) */
4874 if(daydiff == 0){ /* If date is today, "HH:MMap" */
4875 if(d.minute >= 0 && d.minute < 60)
4876 snprintf(minzero, sizeof(minzero), "%02d", d.minute);
4877 else{
4878 strncpy(minzero, "??", sizeof(minzero));
4879 minzero[sizeof(minzero)-1] = '\0';
4882 snprintf(str, str_len, "%s:%s%s", sdatetime24type ? hour24 : hour12,
4883 minzero, sdatetime24type ? "" : ampm);
4885 else if(daydiff >= 1 && daydiff < 6){ /* If <1wk ago, "DddHHap" */
4887 if(d.month >= 1 && d.day >= 1 && d.year >= 0 &&
4888 d.month <= 12 && d.day <= 31 && d.year <= 9999)
4889 Ddd = day_abbrev_locale(day_of_week(&d));
4890 else
4891 Ddd = "???";
4893 snprintf(str, str_len, "%s%s%s", Ddd, hour12, ampm);
4895 else{ /* date is old or future, "ddMmmyy" */
4896 strncpy(monabb, (d.month >= 1 && d.month <= 12)
4897 ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
4898 monabb[sizeof(monabb)-1] = '\0';
4900 if(d.day >= 1 && d.day <= 31)
4901 snprintf(dayzero, sizeof(dayzero), "%02d", d.day);
4902 else{
4903 strncpy(dayzero, "??", sizeof(dayzero));
4904 dayzero[sizeof(dayzero)-1] = '\0';
4907 if(d.year >= 0 && d.year <= 9999)
4908 snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100);
4909 else{
4910 strncpy(yearzero, "??", sizeof(yearzero));
4911 yearzero[sizeof(yearzero)-1] = '\0';
4914 snprintf(str, str_len, "%s%s%s", dayzero, monabb, yearzero);
4917 if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */
4918 if(v)
4919 memmove(str, str + 1, strlen(str));
4920 else
4921 str[0] = ' ';
4925 /* This is like iSTime, but the format is different */
4926 if(type == iSTime24){
4927 struct date now, last_day;
4928 char dbuf[200], *Ddd, *ampm;
4929 int daydiff;
4931 str[0] = '\0';
4932 rfc822_date(dbuf);
4933 parse_date(dbuf, &now);
4935 /* (if message dated this month or last month...) */
4936 if((d.year == now.year && d.month >= now.month - 6) ||
4937 (d.year == now.year - 1 && d.month == 12 && now.month == 6)){
4939 daydiff = day_of_year(&now) - day_of_year(&d);
4942 * If msg in end of last year (and we're in first bit of "this"
4943 * year), diff will be backwards; fix up by adding number of days
4944 * in last year (usually 365, but occasionally 366)...
4946 if(d.year == now.year - 1){
4947 last_day = d;
4948 last_day.month = 12;
4949 last_day.day = 31;
4951 daydiff += day_of_year(&last_day);
4954 else
4955 daydiff = 181; /* comfortably out of range (of past week) */
4957 /* Build 2-digit hour and am/pm indicator, used below */
4959 if(d.hour >= 0 && d.hour < 24){
4960 snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12);
4961 ampm = (d.hour < 12) ? "am" : "pm";
4962 snprintf(hour24, sizeof(hour24), "%02d", d.hour);
4964 else{
4965 strncpy(hour12, "??", sizeof(hour12));
4966 hour12[sizeof(hour12)-1] = '\0';
4967 ampm = "__";
4968 strncpy(hour24, "??", sizeof(hour24));
4969 hour24[sizeof(hour24)-1] = '\0';
4972 /* Build date/time in str, in format similar to that used by w(1) */
4974 if(daydiff >= 0 && daydiff <= 6){ /* If <1wk ago, "Ddd HH:mm" */
4976 if(d.month >= 1 && d.day >= 1 && d.year >= 0 &&
4977 d.month <= 12 && d.day <= 31 && d.year <= 9999)
4978 Ddd = day_abbrev_locale(day_of_week(&d));
4979 else
4980 Ddd = "???";
4982 if(d.minute >= 0 && d.minute < 60)
4983 snprintf(minzero, sizeof(minzero), "%02d", d.minute);
4984 else{
4985 strncpy(minzero, "??", sizeof(minzero));
4986 minzero[sizeof(minzero)-1] = '\0';
4989 snprintf(str, str_len, "%s %s:%s", Ddd, hour24, minzero);
4991 else if(daydiff < 180){ /* date is "Mmm dd" */
4992 strncpy(monabb, (d.month >= 1 && d.month <= 12)
4993 ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
4994 monabb[sizeof(monabb)-1] = '\0';
4996 if(d.day >= 1 && d.day <= 31)
4997 snprintf(dayzero, sizeof(dayzero), "%*d", 2, d.day);
4998 else{
4999 strncpy(dayzero, "??", sizeof(dayzero));
5000 dayzero[sizeof(dayzero)-1] = '\0';
5003 snprintf(str, str_len, "%s %s", monabb, dayzero);
5005 else { /* date is old or future, "dd/Mmm/yy" */
5006 strncpy(monabb, (d.month >= 1 && d.month <= 12)
5007 ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
5008 monabb[sizeof(monabb)-1] = '\0';
5010 if(d.day >= 1 && d.day <= 31)
5011 snprintf(dayzero, sizeof(dayzero), "%02d", d.day);
5012 else{
5013 strncpy(dayzero, "??", sizeof(dayzero));
5014 dayzero[sizeof(dayzero)-1] = '\0';
5017 if(d.year >= 0 && d.year <= 9999)
5018 snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100);
5019 else{
5020 strncpy(yearzero, "??", sizeof(yearzero));
5021 yearzero[sizeof(yearzero)-1] = '\0';
5024 snprintf(str, str_len, "%s/%s/%s", dayzero, monabb, yearzero);
5027 if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */
5028 if(v)
5029 memmove(str, str + 1, strlen(str));
5030 else
5031 str[0] = ' ';
5038 * Format a string representing the keywords into ice.
5040 * This needs to be done in UTF-8, which may be tricky since it isn't labelled.
5042 * Args idata -- which message?
5043 * kwtype -- keywords or kw initials
5044 * ice -- index cache entry for message
5046 void
5047 key_str(INDEXDATA_S *idata, SubjKW kwtype, ICE_S *ice)
5049 int firstone = 1;
5050 KEYWORD_S *kw;
5051 char *word;
5052 COLOR_PAIR *color = NULL;
5053 SPEC_COLOR_S *sc = ps_global->kw_colors;
5054 IELEM_S *ielem = NULL;
5055 IFIELD_S *ourifield = NULL;
5057 if(ice && ice->ifield){
5058 /* move to last ifield, the one we're working */
5059 for(ourifield = ice->ifield;
5060 ourifield && ourifield->next;
5061 ourifield = ourifield->next)
5065 if(!ourifield)
5066 return;
5068 if(kwtype == KWInit){
5069 for(kw = ps_global->keywords; kw; kw = kw->next){
5070 if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
5071 word = (kw->nick && kw->nick[0]) ? kw->nick :
5072 (kw->kw && kw->kw[0]) ? kw->kw : "";
5075 * Pick off the first initial. Since word is UTF-8 it may
5076 * take more than one byte for the first initial.
5079 if(word && word[0]){
5080 UCS ucs;
5081 unsigned long remaining_octets;
5082 unsigned char *inputp;
5084 remaining_octets = strlen(word);
5085 inputp = (unsigned char *) word;
5086 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
5087 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
5088 ielem = new_ielem(&ourifield->ielem);
5089 ielem->freedata = 1;
5090 ielem->datalen = (unsigned) (inputp - (unsigned char *) word);
5091 ielem->data = (char *) fs_get((ielem->datalen + 1) * sizeof(char));
5092 strncpy(ielem->data, word, ielem->datalen);
5093 ielem->data[ielem->datalen] = '\0';
5095 if(pico_usingcolor()
5096 && ((kw->nick && kw->nick[0]
5097 && (color=hdr_color(kw->nick,NULL,sc)))
5098 || (kw->kw && kw->kw[0]
5099 && (color=hdr_color(kw->kw,NULL,sc))))){
5100 ielem->color = color;
5101 color = NULL;
5106 if(color)
5107 free_color_pair(&color);
5111 else if(kwtype == KW){
5112 for(kw = ps_global->keywords; kw; kw = kw->next){
5113 if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
5115 if(!firstone){
5116 ielem = new_ielem(&ourifield->ielem);
5117 ielem->freedata = 1;
5118 ielem->data = cpystr(" ");
5119 ielem->datalen = 1;
5122 firstone = 0;
5124 word = (kw->nick && kw->nick[0]) ? kw->nick :
5125 (kw->kw && kw->kw[0]) ? kw->kw : "";
5127 if(word[0]){
5128 ielem = new_ielem(&ourifield->ielem);
5129 ielem->freedata = 1;
5130 ielem->data = cpystr(word);
5131 ielem->datalen = strlen(word);
5133 if(pico_usingcolor()
5134 && ((kw->nick && kw->nick[0]
5135 && (color=hdr_color(kw->nick,NULL,sc)))
5136 || (kw->kw && kw->kw[0]
5137 && (color=hdr_color(kw->kw,NULL,sc))))){
5138 ielem->color = color;
5139 color = NULL;
5143 if(color)
5144 free_color_pair(&color);
5150 * If we're coloring some of the fields then add a dummy field
5151 * at the end that can soak up the rest of the space after the last
5152 * colored keyword. Otherwise, the last one's color will extend to
5153 * the end of the field.
5155 if(pico_usingcolor()){
5156 ielem = new_ielem(&ourifield->ielem);
5157 ielem->freedata = 1;
5158 ielem->data = cpystr(" ");
5159 ielem->datalen = 1;
5162 ourifield->leftadj = 1;
5163 set_ielem_widths_in_field(ourifield);
5167 void
5168 prio_str(INDEXDATA_S *idata, IndexColType ctype, ICE_S *ice)
5170 IFIELD_S *ourifield = NULL;
5171 IELEM_S *ielem = NULL;
5172 char *hdrval;
5173 PRIORITY_S *p;
5174 int v;
5176 if(ice && ice->ifield){
5177 /* move to last ifield, the one we're working */
5178 for(ourifield = ice->ifield;
5179 ourifield && ourifield->next;
5180 ourifield = ourifield->next)
5184 if(!ourifield)
5185 return;
5187 hdrval = fetch_header(idata, PRIORITYNAME);
5189 if(hdrval && hdrval[0] && isdigit(hdrval[0])){
5190 v = atoi(hdrval);
5191 if(v >= 1 && v <= 5 && v != 3){
5193 ielem = new_ielem(&ourifield->ielem);
5194 ielem->freedata = 1;
5196 switch(ctype){
5197 case iPrio:
5198 ielem->data = (char *) fs_get(2 * sizeof(char));
5199 ielem->data[0] = hdrval[0];
5200 ielem->data[1] = '\0';
5201 break;
5203 case iPrioAlpha:
5204 for(p = priorities; p && p->desc; p++)
5205 if(p->val == v)
5206 break;
5208 if(p && p->desc)
5209 ielem->data = cpystr(p->desc);
5211 break;
5213 case iPrioBang:
5214 ielem->data = (char *) fs_get(2 * sizeof(char));
5215 ielem->data[0] = '\0';
5216 switch(v){
5217 case 1: case 2:
5218 ielem->data[0] = '!';
5219 break;
5221 case 4: case 5:
5223 * We could put a Unicode downarrow in here but
5224 * we have no way of knowing if the user's font
5225 * will have it (I think).
5227 ielem->data[0] = 'v';
5228 break;
5231 ielem->data[1] = '\0';
5233 break;
5235 default:
5236 alpine_panic("Unhandled case in prio_str");
5237 break;
5240 if(!ielem->data)
5241 ielem->data = cpystr("");
5243 if(ielem && ielem->data)
5244 ielem->datalen = strlen(ielem->data);
5246 if((v == 1 || v == 2) && pico_usingcolor()
5247 && ps_global->VAR_IND_HIPRI_FORE_COLOR
5248 && ps_global->VAR_IND_HIPRI_BACK_COLOR){
5249 ielem->type = eTypeCol;
5250 ielem->freecolor = 1;
5251 ielem->color = new_color_pair(ps_global->VAR_IND_HIPRI_FORE_COLOR, ps_global->VAR_IND_HIPRI_BACK_COLOR);
5253 else if((v == 4 || v == 5) && pico_usingcolor()
5254 && ps_global->VAR_IND_LOPRI_FORE_COLOR
5255 && ps_global->VAR_IND_LOPRI_BACK_COLOR){
5256 ielem->type = eTypeCol;
5257 ielem->freecolor = 1;
5258 ielem->color = new_color_pair(ps_global->VAR_IND_LOPRI_FORE_COLOR, ps_global->VAR_IND_LOPRI_BACK_COLOR);
5261 ourifield->leftadj = 1;
5262 set_ielem_widths_in_field(ourifield);
5265 fs_give((void **) &hdrval);
5270 void
5271 header_str(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok, ICE_S *ice)
5273 IFIELD_S *ourifield = NULL;
5274 IELEM_S *ielem = NULL;
5275 char *fieldval = NULL;
5277 if(ice && ice->ifield){
5278 /* move to last ifield, the one we're working */
5279 for(ourifield = ice->ifield;
5280 ourifield && ourifield->next;
5281 ourifield = ourifield->next)
5285 if(!ourifield)
5286 return;
5288 fieldval = get_fieldval(idata, hdrtok);
5290 if(fieldval){
5291 ielem = new_ielem(&ourifield->ielem);
5292 ielem->freedata = 1;
5293 ielem->data = fieldval;
5294 ielem->datalen = strlen(fieldval);
5295 fieldval = NULL;
5296 ourifield->leftadj = (hdrtok->adjustment == Left) ? 1 : 0;
5299 set_ielem_widths_in_field(ourifield);
5303 char *
5304 get_fieldval(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok)
5306 int sep, fieldnum;
5307 char *hdrval = NULL, *testval;
5308 char *fieldval = NULL, *firstval;
5309 char *retfieldval = NULL;
5311 if(!hdrtok)
5312 return(retfieldval);
5314 if(hdrtok && hdrtok->hdrname && hdrtok->hdrname[0])
5315 hdrval = fetch_header(idata, hdrtok ? hdrtok->hdrname : "");
5317 /* find start of fieldnum'th field */
5318 fieldval = hdrval;
5319 for(fieldnum = MAX(hdrtok->fieldnum-1, 0);
5320 fieldnum > 0 && fieldval && *fieldval; fieldnum--){
5322 firstval = NULL;
5323 for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
5324 testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
5325 if(testval && (!firstval || testval < firstval))
5326 firstval = testval;
5329 fieldval = firstval;
5330 if(fieldval && *fieldval)
5331 fieldval++;
5334 /* tie off end of field */
5335 if(fieldval && *fieldval && hdrtok->fieldnum > 0){
5336 firstval = NULL;
5337 for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
5338 testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
5339 if(testval && (!firstval || testval < firstval))
5340 firstval = testval;
5343 if(firstval)
5344 *firstval = '\0';
5347 if(!fieldval)
5348 fieldval = "";
5350 retfieldval = cpystr(fieldval);
5352 if(hdrval)
5353 fs_give((void **) &hdrval);
5355 return(retfieldval);
5359 long
5360 scorevalfrommsg(MAILSTREAM *stream, MsgNo rawno, HEADER_TOK_S *hdrtok, int no_fetch)
5362 INDEXDATA_S idata;
5363 MESSAGECACHE *mc;
5364 char *fieldval = NULL;
5365 long retval = 0L;
5367 memset(&idata, 0, sizeof(INDEXDATA_S));
5368 idata.stream = stream;
5369 idata.no_fetch = no_fetch;
5370 idata.msgno = mn_raw2m(sp_msgmap(stream), rawno);
5371 idata.rawno = rawno;
5372 if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
5373 && (mc = mail_elt(stream, idata.rawno))){
5374 idata.size = mc->rfc822_size;
5375 index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
5377 else
5378 idata.bogus = 2;
5380 fieldval = get_fieldval(&idata, hdrtok);
5382 if(fieldval){
5383 retval = atol(fieldval);
5384 fs_give((void **) &fieldval);
5387 return(retval);
5391 * Put a string representing the subject into str. Idata tells us which
5392 * message we are referring to.
5394 * This means we should ensure that all data ends up being UTF-8 data.
5395 * That covers the data in ice ielems and str.
5397 * Args idata -- which message?
5398 * str -- destination buffer
5399 * strsize -- size of str buffer
5400 * kwtype -- prepend keywords or kw initials before the subject
5401 * opening -- add first text from body of message if there's room
5402 * shorten -- if on, shorten the subject.
5403 * ice -- index cache entry for message
5405 void
5406 subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int opening, int shorten, ICE_S *ice)
5408 char *subject, *origsubj, *origstr, *rawsubj, *sptr = NULL;
5409 char *p, *border, *q = NULL, *free_subj = NULL;
5410 char *sp;
5411 size_t len;
5412 int width = -1;
5413 int depth = 0, mult = 2;
5414 int save;
5415 int do_subj = 0, truncated_tree = 0;
5416 PINETHRD_S *thd, *thdorig;
5417 IELEM_S *ielem = NULL, *subjielem = NULL;
5418 IFIELD_S *ourifield = NULL;
5420 if(strsize <= 0)
5421 return;
5424 * If we need the data at the start of the message and we're in
5425 * a c-client callback, defer the data lookup until later.
5427 if(opening && idata->no_fetch){
5428 idata->bogus = 1;
5429 return;
5432 if(ice && ice->ifield){
5433 /* move to last ifield, the one we're working on */
5434 for(ourifield = ice->ifield;
5435 ourifield && ourifield->next;
5436 ourifield = ourifield->next)
5440 str[0] = str[strsize-1] = '\0';
5441 origstr = str;
5442 rawsubj = fetch_subject(idata);
5443 if(!rawsubj)
5444 rawsubj = "";
5447 * Before we do anything else, decode the character set in the subject and
5448 * work with the result.
5450 sp = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
5451 SIZEOF_20KBUF, rawsubj);
5453 len = strlen(sp);
5454 len += 100; /* for possible charset, escaped characters */
5455 origsubj = fs_get((len+1) * sizeof(unsigned char));
5456 origsubj[0] = '\0';
5458 iutf8ncpy(origsubj, sp, len);
5460 origsubj[len] = '\0';
5461 replace_tabs_by_space(origsubj);
5462 removing_trailing_white_space(origsubj);
5465 * origsubj is the original subject but it has been decoded. We need
5466 * to free it at the end of this routine.
5469 if(shorten)
5470 shorten_subject(origsubj);
5473 * prepend_keyword will put the keyword stuff before the subject
5474 * and split the subject up into its colored parts in subjielem.
5475 * Subjielem is a local ielem which will have to be fit into the
5476 * real ifield->ielem later. The print_format strings in subjielem will
5477 * not be filled in by prepend_keyword because of the fact that we
5478 * may have to adjust things for threading below.
5479 * We use subjielem in case we want to insert some threading information
5480 * at the front of the subject.
5482 if(kwtype == KW || kwtype == KWInit){
5483 subject = prepend_keyword_subject(idata->stream, idata->rawno,
5484 origsubj, kwtype,
5485 ourifield ? &subjielem : NULL,
5486 ps_global->VAR_KW_BRACES);
5487 free_subj = subject;
5489 else{
5490 subject = origsubj;
5491 if(ourifield){
5492 subjielem = new_ielem(&subjielem);
5493 subjielem->type = eTypeCol;
5494 subjielem->freedata = 1;
5495 subjielem->data = cpystr(subject);
5496 subjielem->datalen = strlen(subject);
5497 if(pico_usingcolor()
5498 && ps_global->VAR_IND_SUBJ_FORE_COLOR
5499 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
5500 subjielem->freecolor = 1;
5501 subjielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
5507 * This space is here so that if the subject does
5508 * not extend all the way to the end of the field then
5509 * we'll switch the color back and paint the rest of the
5510 * field in the Normal color or the index line color.
5512 if(!opening){
5513 ielem = new_ielem(&subjielem);
5514 ielem->freedata = 1;
5515 ielem->data = cpystr(" ");
5516 ielem->datalen = 1;
5519 if(!subject)
5520 subject = "";
5522 if(THREADING()
5523 && (ps_global->thread_disp_style == THREAD_STRUCT
5524 || ps_global->thread_disp_style == THREAD_MUTTLIKE
5525 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ1
5526 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ2)){
5529 * Why do we want to truncate the subject and from strs?
5530 * It's so we can put the [5] thread count things in below when
5531 * we are threading and the thread structure runs off the right
5532 * hand edge of the screen. This routine doesn't know that it
5533 * is running off the edge unless it knows the actual width
5534 * that we have to draw in.
5536 if(pith_opt_truncate_sfstr
5537 && (*pith_opt_truncate_sfstr)()
5538 && ourifield
5539 && ourifield->width > 0)
5540 width = ourifield->width;
5542 if(width < 0)
5543 width = strsize-1;
5545 width = MIN(width, strsize-1);
5548 * We're counting on the fact that this initial part of the
5549 * string is ascii and we have one octet per character and
5550 * characters are width 1 on the screen.
5552 border = str + width;
5554 thdorig = thd = fetch_thread(idata->stream, idata->rawno);
5556 if(pith_opt_condense_thread_cue)
5557 width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
5558 thd && thd->next
5559 && get_lflag(idata->stream,
5560 NULL,idata->rawno,
5561 MN_COLL));
5564 * width is < available strsize and
5565 * border points to something less than or equal
5566 * to the end of the buffer.
5569 sptr = str;
5571 if(thd)
5572 while(thd->parent &&
5573 (thd = fetch_thread(idata->stream, thd->parent)))
5574 depth++;
5576 if(depth > 0){
5577 if(ps_global->thread_disp_style == THREAD_INDENT_SUBJ1)
5578 mult = 1;
5580 sptr += (mult*depth);
5581 for(thd = thdorig, p = str + mult*depth - mult;
5582 thd && thd->parent && p >= str;
5583 thd = fetch_thread(idata->stream, thd->parent), p -= mult){
5584 if(p + mult >= border && !q){
5585 if(width >= 4 && depth < 100){
5586 snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
5587 q = str + width-4;
5589 else if(width >= 5 && depth < 1000){
5590 snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
5591 q = str + width-5;
5593 else{
5594 snprintf(str, width+1, "%s", repeat_char(width, '.'));
5595 q = str;
5598 border = q;
5599 truncated_tree++;
5602 if(p < border){
5603 p[0] = ' ';
5604 if(p + 1 < border)
5605 p[1] = ' ';
5607 if(ps_global->thread_disp_style == THREAD_STRUCT
5608 || ps_global->thread_disp_style == THREAD_MUTTLIKE){
5610 * WARNING!
5611 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
5612 * is ascii.
5614 if(thd == thdorig && !thd->branch)
5615 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
5616 else if(thd == thdorig || thd->branch)
5617 p[0] = '|';
5619 if(p + 1 < border && thd == thdorig)
5620 p[1] = '-';
5626 if(sptr && !truncated_tree){
5628 * Look to see if the subject is the same as the previous
5629 * message in the thread, if any. If it is the same, don't
5630 * reprint the subject.
5632 * Note that when we're prepending keywords to the subject,
5633 * and the user changes a keyword, we do invalidate
5634 * the index cache for that message but we don't go to the
5635 * trouble of invalidating the index cache for the the child
5636 * of that node in the thread, so the MUTT subject line
5637 * display for the child may be wrong. That is, it may show
5638 * it is the same as this subject even though it no longer
5639 * is, or vice versa.
5641 if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
5642 if(depth == 0)
5643 do_subj++;
5644 else{
5645 if(thdorig->parent &&
5646 (thd = fetch_thread(idata->stream, thdorig->parent))
5647 && thd->rawno){
5648 char *this_orig = NULL,
5649 *prev_orig = NULL,
5650 *free_prev_orig = NULL,
5651 *this_prep = NULL, /* includes prepend */
5652 *prev_prep = NULL;
5653 ENVELOPE *env;
5654 mailcache_t mc;
5655 SORTCACHE *sc = NULL;
5657 /* get the stripped subject of previous message */
5658 mc = (mailcache_t) mail_parameters(NIL, GET_CACHE, NIL);
5659 if(mc)
5660 sc = (*mc)(idata->stream, thd->rawno, CH_SORTCACHE);
5662 if(sc && sc->subject)
5663 prev_orig = sc->subject;
5664 else{
5665 char *stripthis;
5667 env = pine_mail_fetchenvelope(idata->stream,
5668 thd->rawno);
5669 stripthis = (env && env->subject)
5670 ? env->subject : "";
5672 mail_strip_subject(stripthis, &prev_orig);
5674 free_prev_orig = prev_orig;
5677 mail_strip_subject(rawsubj, &this_orig);
5679 if(kwtype == KW || kwtype == KWInit){
5680 prev_prep = prepend_keyword_subject(idata->stream,
5681 thd->rawno,
5682 prev_orig,
5683 kwtype, NULL,
5684 ps_global->VAR_KW_BRACES);
5686 this_prep = prepend_keyword_subject(idata->stream,
5687 idata->rawno,
5688 this_orig,
5689 kwtype, NULL,
5690 ps_global->VAR_KW_BRACES);
5692 if((this_prep || prev_prep)
5693 && ((this_prep && !prev_prep)
5694 || (prev_prep && !this_prep)
5695 || strucmp(this_prep, prev_prep)))
5696 do_subj++;
5698 else{
5699 if((this_orig || prev_orig)
5700 && ((this_orig && !prev_orig)
5701 || (prev_orig && !this_orig)
5702 || strucmp(this_orig, prev_orig)))
5703 do_subj++;
5707 * If some of the thread is zoomed out of view, we
5708 * want to display the subject of the first one that
5709 * is in view. If any of the parents or grandparents
5710 * etc of this message are visible, then we don't
5711 * need to worry about it. If all of the parents have
5712 * been zoomed away, then this is the first one.
5714 * When you're looking at a particular case where
5715 * some of the messages of a thread are selected it
5716 * seems like we should look at not only our
5717 * direct parents, but the siblings of the parent
5718 * too. But that's not really correct, because those
5719 * siblings are basically the starts of different
5720 * branches, separate from our branch. They could
5721 * have their own subjects, for example. This will
5722 * give us cases where it looks like we are showing
5723 * the subject too much, but it will be correct!
5725 * In zoom_index() we clear_index_cache_ent for
5726 * some lines which have subjects which might become
5727 * visible when we zoom, and also in set_lflags
5728 * where we might change subjects by unselecting
5729 * something when zoomed.
5731 if(!do_subj){
5732 while(thd){
5733 if(!msgline_hidden(idata->stream,
5734 sp_msgmap(idata->stream),
5735 mn_raw2m(sp_msgmap(idata->stream),
5736 (long) thd->rawno),
5737 0)){
5738 break; /* found a visible parent */
5741 if(thd && thd->parent)
5742 thd = fetch_thread(idata->stream,thd->parent);
5743 else
5744 thd = NULL;
5747 if(!thd) /* none were visible */
5748 do_subj++;
5751 if(this_orig)
5752 fs_give((void **) &this_orig);
5754 if(this_prep)
5755 fs_give((void **) &this_prep);
5757 if(free_prev_orig)
5758 fs_give((void **) &free_prev_orig);
5760 if(prev_prep)
5761 fs_give((void **) &prev_prep);
5763 else
5764 do_subj++;
5767 else
5768 do_subj++;
5770 if(do_subj){
5772 * We don't need to worry about truncating to width
5773 * here. If we go over the right hand edge it will be
5774 * truncated.
5776 strsize -= (sptr - str);
5778 strncpy(sptr, subject, strsize-1);
5779 sptr[strsize-1] = '\0';
5781 else if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
5782 strsize -= (sptr - str);
5784 if(strsize > 0){
5785 sptr[0] = '>';
5786 sptr++;
5790 * We decided we don't need the subject so we'd better
5791 * eliminate subjielem.
5793 free_ielem(&subjielem);
5796 else
5797 free_ielem(&subjielem); /* no room for actual subject */
5799 if(ourifield && sptr && sptr > origstr){
5800 ielem = new_ielem(&ourifield->ielem);
5801 ielem->type = eThreadInfo;
5802 ielem->freedata = 1;
5803 save = *sptr;
5804 *sptr = '\0';
5805 ielem->data = cpystr(origstr);
5806 ielem->datalen = strlen(origstr);
5807 *sptr = save;
5810 else{
5812 * Not much to do for the non-threading case. Just copy the
5813 * subject we have so far into str and truncate it.
5815 strncpy(str, subject, strsize-1);
5816 str[strsize-1] = '\0';
5819 if(ourifield){
5821 * We need to add subjielem to the end of the ourifield->ielem list.
5823 if(subjielem){
5824 if(ourifield->ielem){
5825 for(ielem = ourifield->ielem;
5826 ielem && ielem->next; ielem = ielem->next)
5829 ielem->next = subjielem;
5831 else
5832 ourifield->ielem = subjielem;
5835 ourifield->leftadj = 1;
5838 if(opening && ourifield){
5839 IELEM_S *ftielem = NULL;
5840 size_t len;
5841 char *first_text;
5843 first_text = fetch_firsttext(idata, 0);
5845 if(first_text){
5846 char sep[200];
5847 int seplen;
5849 strncpy(sep, ps_global->VAR_OPENING_SEP ? ps_global->VAR_OPENING_SEP : " - ",
5850 sizeof(sep));
5851 sep[sizeof(sep)-1] = '\0';
5852 removing_double_quotes(sep);
5853 seplen = strlen(sep);
5855 ftielem = new_ielem(&ftielem);
5856 ftielem->type = eTypeCol;
5857 ftielem->freedata = 1;
5858 len = strlen(first_text) + seplen;
5859 ftielem->data = (char *) fs_get((len + 1) * sizeof(char));
5861 strncpy(ftielem->data, sep, seplen);
5862 strncpy(ftielem->data+seplen, first_text, len+1-seplen);
5863 ftielem->data[len] = '\0';
5865 ftielem->datalen = strlen(ftielem->data);
5866 if(first_text)
5867 fs_give((void **) &first_text);
5869 if(ftielem){
5870 if(pico_usingcolor()
5871 && ps_global->VAR_IND_OP_FORE_COLOR
5872 && ps_global->VAR_IND_OP_BACK_COLOR){
5873 ftielem->freecolor = 1;
5874 ftielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR, ps_global->VAR_IND_OP_BACK_COLOR);
5877 * This space is here so that if the opening text does
5878 * not extend all the way to the end of the field then
5879 * we'll switch the color back and paint the rest of the
5880 * field in the Normal color or the index line color.
5882 ielem = new_ielem(&ftielem);
5883 ielem->freedata = 1;
5884 ielem->data = cpystr(" ");
5885 ielem->datalen = 1;
5888 if(ourifield->ielem){
5889 for(ielem = ourifield->ielem;
5890 ielem && ielem->next; ielem = ielem->next)
5893 ielem->next = ftielem;
5895 else
5896 ourifield->ielem = ftielem;
5899 ourifield->leftadj = 1;
5903 if(ourifield)
5904 set_ielem_widths_in_field(ourifield);
5906 if(origsubj)
5907 fs_give((void **) &origsubj);
5909 if(free_subj)
5910 fs_give((void **) &free_subj);
5915 * Returns an allocated string which is the passed in subject with a
5916 * list of keywords prepended.
5918 * If kwtype == KW you will end up with
5920 * {keyword1 keyword2} subject
5922 * (actually, keyword nicknames will be used instead of the actual keywords
5923 * in the case that the user defined nicknames)
5925 * If kwtype == KWInit you get
5927 * {AB} subject
5929 * where A is the first letter of the first keyword and B is the first letter
5930 * of the second defined keyword. No space between them. There could be more
5931 * than two.
5933 * If an ielemp is passed in it will be filled out with the data and colors
5934 * of the pieces of the subject but the print_format strings will not
5935 * be set.
5937 char *
5938 prepend_keyword_subject(MAILSTREAM *stream, long int rawno, char *subject,
5939 SubjKW kwtype, IELEM_S **ielemp, char *braces)
5941 char *p, *next_piece, *retsubj = NULL, *str;
5942 char *left_brace = NULL, *right_brace = NULL;
5943 size_t len;
5944 int some_set = 0, save;
5945 IELEM_S *ielem;
5946 KEYWORD_S *kw;
5947 COLOR_PAIR *color = NULL;
5948 SPEC_COLOR_S *sc = ps_global->kw_colors;
5950 if(!subject)
5951 subject = "";
5953 if(braces && *braces)
5954 get_pair(braces, &left_brace, &right_brace, 1, 0);
5956 len = (left_brace ? strlen(left_brace) : 0) +
5957 (right_brace ? strlen(right_brace) : 0);
5959 if(stream && rawno >= 0L && rawno <= stream->nmsgs){
5960 for(kw = ps_global->keywords; kw; kw = kw->next)
5961 if(user_flag_is_set(stream, rawno, kw->kw)){
5962 if(kwtype == KW){
5963 if(some_set)
5964 len++; /* space between keywords */
5966 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5967 len += strlen(str);
5969 else if(kwtype == KWInit){
5970 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5971 /* interested in only the first UTF-8 initial */
5972 if(str && str[0]){
5973 UCS ucs;
5974 unsigned long remaining_octets;
5975 unsigned char *inputp;
5977 remaining_octets = strlen(str);
5978 inputp = (unsigned char *) str;
5979 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
5980 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
5981 len += (unsigned) (inputp - (unsigned char *) str);
5986 some_set++;
5990 if((kwtype == KW || kwtype == KWInit) && some_set){
5991 len += strlen(subject); /* subject is already UTF-8 if needed */
5992 retsubj = (char *) fs_get((len + 1) * sizeof(*retsubj));
5993 memset(retsubj, 0, (len + 1) * sizeof(*retsubj));
5994 next_piece = p = retsubj;
5996 for(kw = ps_global->keywords; kw; kw = kw->next){
5997 if(user_flag_is_set(stream, rawno, kw->kw)){
5998 if(p == retsubj){
5999 if(left_brace && len > 0)
6000 sstrncpy(&p, left_brace, len);
6002 else if(kwtype == KW)
6003 *p++ = ' ';
6005 if(ielemp && p > next_piece){
6006 save = *p;
6007 *p = '\0';
6008 ielem = new_ielem(ielemp);
6009 ielem->freedata = 1;
6010 ielem->data = cpystr(next_piece);
6011 ielem->datalen = strlen(next_piece);
6012 *p = save;
6013 next_piece = p;
6016 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
6018 if(kwtype == KWInit){
6019 if(str && str[0]){
6020 UCS ucs;
6021 unsigned long remaining_octets;
6022 unsigned char *inputp;
6024 remaining_octets = strlen(str);
6025 inputp = (unsigned char *) str;
6026 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
6027 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
6028 if(len-(p-retsubj) > 0){
6029 sstrncpy(&p, str, MIN(inputp - (unsigned char *) str,len-(p-retsubj)));
6030 if(p > next_piece && ielemp && pico_usingcolor()
6031 && ((kw->nick && kw->nick[0]
6032 && (color=hdr_color(kw->nick,NULL,sc)))
6033 || (kw->kw && kw->kw[0]
6034 && (color=hdr_color(kw->kw,NULL,sc))))){
6035 ielem = new_ielem(ielemp);
6036 ielem->freedata = 1;
6037 save = *p;
6038 *p = '\0';
6039 ielem->data = cpystr(next_piece);
6040 ielem->datalen = strlen(next_piece);
6041 ielem->color = color;
6042 color = NULL;
6043 *p = save;
6044 next_piece = p;
6049 if(color)
6050 free_color_pair(&color);
6053 else{
6054 if(len-(p-retsubj) > 0)
6055 sstrncpy(&p, str, len-(p-retsubj));
6057 if(p > next_piece && ielemp && pico_usingcolor()
6058 && ((kw->nick && kw->nick[0]
6059 && (color=hdr_color(kw->nick,NULL,sc)))
6060 || (kw->kw && kw->kw[0]
6061 && (color=hdr_color(kw->kw,NULL,sc))))){
6062 ielem = new_ielem(ielemp);
6063 ielem->freedata = 1;
6064 save = *p;
6065 *p = '\0';
6066 ielem->data = cpystr(next_piece);
6067 ielem->datalen = strlen(next_piece);
6068 ielem->color = color;
6069 color = NULL;
6070 *p = save;
6071 next_piece = p;
6074 if(color)
6075 free_color_pair(&color);
6080 if(len-(p-retsubj) > 0 && right_brace)
6081 sstrncpy(&p, right_brace, len-(p-retsubj));
6083 if(ielemp && p > next_piece){
6084 save = *p;
6085 *p = '\0';
6086 ielem = new_ielem(ielemp);
6087 ielem->freedata = 1;
6088 ielem->data = cpystr(next_piece);
6089 ielem->datalen = strlen(next_piece);
6090 *p = save;
6091 next_piece = p;
6094 if(len-(p-retsubj) > 0 && subject)
6095 sstrncpy(&p, subject, len-(p-retsubj));
6097 if(ielemp && p > next_piece){
6098 save = *p;
6099 *p = '\0';
6100 ielem = new_ielem(ielemp);
6101 ielem->type = eTypeCol;
6102 ielem->freedata = 1;
6103 ielem->data = cpystr(next_piece);
6104 ielem->datalen = strlen(next_piece);
6105 *p = save;
6106 next_piece = p;
6107 if(pico_usingcolor()
6108 && ps_global->VAR_IND_SUBJ_FORE_COLOR
6109 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
6110 ielem->freecolor = 1;
6111 ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
6115 retsubj[len] = '\0'; /* just making sure */
6117 else{
6118 if(ielemp){
6119 ielem = new_ielem(ielemp);
6120 ielem->type = eTypeCol;
6121 ielem->freedata = 1;
6122 ielem->data = cpystr(subject);
6123 ielem->datalen = strlen(subject);
6124 if(pico_usingcolor()
6125 && ps_global->VAR_IND_SUBJ_FORE_COLOR
6126 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
6127 ielem->freecolor = 1;
6128 ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
6132 retsubj = cpystr(subject);
6135 if(braces){
6136 if(left_brace)
6137 fs_give((void **) &left_brace);
6139 if(right_brace)
6140 fs_give((void **) &right_brace);
6143 return(retsubj);
6148 * This means we should ensure that all data ends up being UTF-8 data.
6149 * That covers the data in ice ielems and str.
6151 void
6152 from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_S *ice)
6154 char *field, *newsgroups, *border, *p, *fptr = NULL, *q = NULL;
6155 ADDRESS *addr;
6156 int width = -1;
6157 int depth = 0, mult = 2;
6158 PINETHRD_S *thd, *thdorig;
6160 if(THREADING()
6161 && (ps_global->thread_disp_style == THREAD_INDENT_FROM1
6162 || ps_global->thread_disp_style == THREAD_INDENT_FROM2
6163 || ps_global->thread_disp_style == THREAD_STRUCT_FROM)){
6165 if(pith_opt_truncate_sfstr && (*pith_opt_truncate_sfstr)()){
6166 IFIELD_S *ourifield = NULL;
6168 if(ice && ice->ifield){
6169 /* move to last ifield, the one we're working on */
6170 for(ourifield = ice->ifield;
6171 ourifield && ourifield->next;
6172 ourifield = ourifield->next)
6176 if(ourifield && ourifield->width > 0)
6177 width = ourifield->width;
6180 if(width < 0)
6181 width = strsize-1;
6183 width = MIN(width, strsize-1);
6185 thdorig = thd = fetch_thread(idata->stream, idata->rawno);
6186 border = str + width;
6187 if(pith_opt_condense_thread_cue)
6188 width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
6189 thd && thd->next
6190 && get_lflag(idata->stream,
6191 NULL,idata->rawno,
6192 MN_COLL));
6194 fptr = str;
6196 if(thd)
6197 while(thd->parent && (thd = fetch_thread(idata->stream, thd->parent)))
6198 depth++;
6200 if(depth > 0){
6201 if(ps_global->thread_disp_style == THREAD_INDENT_FROM1)
6202 mult = 1;
6204 fptr += (mult*depth);
6205 for(thd = thdorig, p = str + mult*depth - mult;
6206 thd && thd->parent && p >= str;
6207 thd = fetch_thread(idata->stream, thd->parent), p -= mult){
6208 if(p + mult >= border && !q){
6209 if(width >= 4 && depth < 100){
6210 snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
6211 q = str + width-4;
6213 else if(width >= 5 && depth < 1000){
6214 snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
6215 q = str + width-5;
6217 else{
6218 snprintf(str, width+1, "%s", repeat_char(width, '.'));
6219 q = str;
6222 border = q;
6223 fptr = NULL;
6226 if(p + 1 < border){
6227 p[0] = p[1] = ' ';
6228 if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
6230 * WARNING!
6231 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
6232 * is ascii.
6234 if(thd == thdorig && !thd->branch)
6235 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
6236 else if(thd == thdorig || thd->branch)
6237 p[0] = '|';
6239 if(thd == thdorig)
6240 p[1] = '-';
6243 else if(p < border){
6244 p[0] = ' ';
6245 if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
6247 * WARNING!
6248 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
6249 * is ascii.
6251 if(thd == thdorig && !thd->branch)
6252 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
6253 else if(thd == thdorig || thd->branch)
6254 p[0] = '|';
6260 else
6261 fptr = str;
6263 if(fptr){
6264 strsize -= (fptr - str);
6265 switch(ctype){
6266 case iFromTo:
6267 case iFromToNotNews:
6268 if(!(addr = fetch_from(idata)) || address_is_us(addr, ps_global)){
6269 if(strsize-1 <= 4){
6270 strncpy(fptr, "To: ", strsize-1);
6271 fptr[strsize-1] = '\0';
6272 break;
6274 else{
6275 if((field = ((addr = fetch_to(idata))
6276 ? "To"
6277 : (addr = fetch_cc(idata))
6278 ? "Cc"
6279 : NULL))
6280 && set_index_addr(idata, field, addr, "To: ",
6281 strsize-1, fptr))
6282 break;
6284 if(ctype == iFromTo &&
6285 (newsgroups = fetch_newsgroups(idata)) &&
6286 *newsgroups){
6287 snprintf(fptr, strsize, "To: %-*.*s", (int)(strsize-1-4), (int)(strsize-1-4),
6288 newsgroups);
6289 break;
6292 /* else fall thru to From: */
6295 /* else fall thru to From: */
6297 if(idata->bogus)
6298 break;
6300 case iFrom:
6301 set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr);
6302 break;
6304 case iAddress:
6305 case iMailbox:
6306 if((addr = fetch_from(idata)) && addr->mailbox && addr->mailbox[0]){
6307 char *mb = NULL, *hst = NULL, *at = NULL;
6308 size_t len;
6310 mb = addr->mailbox;
6311 if(ctype == iAddress && addr->host && addr->host[0]
6312 && addr->host[0] != '.'){
6313 at = "@";
6314 hst = addr->host;
6317 len = strlen(mb);
6318 if(!at || strsize-1 <= len)
6319 snprintf(fptr, strsize, "%-*.*s", (int)(strsize-1), (int)(strsize-1), mb);
6320 else
6321 snprintf(fptr, strsize, "%s@%-*.*s", mb, (int)(strsize-1-len-1), (int)(strsize-1-len-1), hst);
6324 break;
6326 default:
6327 break;
6330 replace_tabs_by_space(str);
6335 * Set up the elements contained in field so that they take up the
6336 * whole field width. Data is assumed to be UTF-8.
6338 void
6339 set_ielem_widths_in_field(IFIELD_S *ifield)
6341 IELEM_S *ielem = NULL;
6342 int datawidth, fmtwidth;
6344 if(!ifield)
6345 return;
6347 fmtwidth = ifield->width;
6349 for(ielem = ifield->ielem; ielem && fmtwidth > 0; ielem = ielem->next){
6350 if(!ifield->leftadj && ielem->next){
6351 dprint((1, "set_ielem_widths_in_field(%d): right adjust with multiple elements, NOT SUPPOSED TO HAPPEN!\n", (int) ifield->ctype));
6352 assert(0);
6355 datawidth = (int) utf8_width(ielem->data);
6356 if(datawidth >= fmtwidth || !ielem->next){
6357 set_print_format(ielem, fmtwidth, ifield->leftadj);
6358 fmtwidth = 0;
6360 else{
6361 set_print_format(ielem, datawidth, ifield->leftadj);
6362 fmtwidth -= datawidth;
6369 * Simple hash function from K&R 2nd edition, p. 144.
6371 * This one is modified to never return 0 so we can use that as a special
6372 * value. Also, LINE_HASH_N fits in an unsigned long, so it too can be used
6373 * as a special value that can't be returned by line_hash.
6375 unsigned long
6376 line_hash(char *s)
6378 unsigned long hashval;
6380 for(hashval = 0; *s != '\0'; s++)
6381 hashval = *s + 31 * hashval;
6383 hashval = hashval % LINE_HASH_N;
6385 if(!hashval)
6386 hashval++;
6388 return(hashval);
6393 * Returns nonzero if considered hidden, 0 if not considered hidden.
6396 msgline_hidden(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int flags)
6398 int ret;
6400 if(flags & MH_ANYTHD){
6401 ret = ((any_lflagged(msgmap, MN_HIDE) > 0)
6402 && get_lflag(stream, msgmap, msgno, MN_HIDE));
6404 else if(flags & MH_THISTHD && THREADING() && sp_viewing_a_thread(stream)){
6405 ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
6406 || !get_lflag(stream, msgmap, msgno, MN_CHID2));
6408 else{
6409 if(THREADING() && sp_viewing_a_thread(stream)){
6410 ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
6411 || !get_lflag(stream, msgmap, msgno, MN_CHID2)
6412 || get_lflag(stream, msgmap, msgno, MN_CHID));
6414 else if(THRD_INDX()){
6416 * If this message is in the collapsed part of a thread,
6417 * it's hidden. It must be a top-level of a thread to be
6418 * considered visible. Even if it is top-level, it is only
6419 * visible if some message in the thread is not hidden.
6421 if(get_lflag(stream, msgmap, msgno, MN_CHID)) /* not top */
6422 ret = 1;
6423 else{
6424 unsigned long rawno;
6425 PINETHRD_S *thrd = NULL;
6427 rawno = mn_m2raw(msgmap, msgno);
6428 if(rawno)
6429 thrd = fetch_thread(stream, rawno);
6431 ret = !thread_has_some_visible(stream, thrd);
6434 else{
6435 ret = ((any_lflagged(msgmap, MN_HIDE | MN_CHID) > 0)
6436 && get_lflag(stream, msgmap, msgno, MN_HIDE | MN_CHID));
6440 dprint((10,
6441 "msgline_hidden(%ld): %s\n", msgno, ret ? "HID" : "VIS"));
6443 return(ret);
6447 void
6448 adjust_cur_to_visible(MAILSTREAM *stream, MSGNO_S *msgmap)
6450 long n, cur;
6451 int dir;
6453 cur = mn_get_cur(msgmap);
6455 /* if current is hidden, adjust */
6456 if(cur >= 1L && cur <= mn_get_total(msgmap)
6457 && msgline_hidden(stream, msgmap, cur, 0)){
6459 dir = mn_get_revsort(msgmap) ? -1 : 1;
6461 for(n = cur;
6462 ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6463 && (n >= 1L && n <= mn_get_total(msgmap))
6464 && msgline_hidden(stream, msgmap, n, 0);
6465 n -= dir)
6468 if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6469 && (n >= 1L && n <= mn_get_total(msgmap)))
6470 mn_reset_cur(msgmap, n);
6471 else{ /* no visible in that direction */
6472 for(n = cur;
6473 ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6474 && (n >= 1L && n <= mn_get_total(msgmap))
6475 && msgline_hidden(stream, msgmap, n, 0);
6476 n += dir)
6479 if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6480 && (n >= 1L && n <= mn_get_total(msgmap)))
6481 mn_reset_cur(msgmap, n);
6482 /* else trouble! */
6488 void
6489 setup_for_index_index_screen(void)
6491 format_index_line = format_index_index_line;
6492 setup_header_widths = setup_index_header_widths;
6496 void
6497 setup_for_thread_index_screen(void)
6499 format_index_line = format_thread_index_line;
6500 setup_header_widths = setup_thread_header_widths;
6504 unsigned long
6505 ice_hash(ICE_S *ice)
6507 char buf[MAX_SCREEN_COLS+1];
6509 buf[0] = '\0';
6511 if(ice)
6512 simple_index_line(buf, sizeof(buf), ice, 0L);
6514 buf[sizeof(buf) - 1] = '\0';
6516 return(line_hash(buf));
6520 char *
6521 left_adjust(int width)
6523 return(format_str(width, 1));
6527 char *
6528 right_adjust(int width)
6530 return(format_str(width, 0));
6535 * Returns allocated and filled in format string.
6537 char *
6538 format_str(int width, int left)
6540 char *format;
6541 size_t len;
6543 len = PRINT_FORMAT_LEN(width,left) * sizeof(char);
6544 format = (char *) fs_get(len + 1);
6545 copy_format_str(width, left, format, len);
6546 format[len] = '\0';
6548 return(format);
6553 * Put the left or right adjusted format string of width width into
6554 * dest. Dest is of size n+1.
6556 char *
6557 copy_format_str(int width, int left, char *dest, int n)
6559 char *p;
6561 p = int2string(width);
6563 snprintf(dest, n+1, "%%%s%s.%ss", left ? "-" : "", p, p);
6565 dest[n] = '\0';
6567 return(dest);
6572 * Sets up the print_format string to be width wide with left or right
6573 * adjust. Takes care of memory freeing and allocation.
6575 void
6576 set_print_format(IELEM_S *ielem, int width, int leftadj)
6578 if(ielem){
6579 ielem->wid = width;
6581 if(ielem->print_format){
6582 /* is there enough room? */
6583 if(ielem->freeprintf < PRINT_FORMAT_LEN(width,leftadj)+1){
6584 fs_resize((void **) &ielem->print_format,
6585 (PRINT_FORMAT_LEN(width,leftadj)+1) * sizeof(char));
6586 ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);
6589 copy_format_str(width, leftadj, ielem->print_format,
6590 PRINT_FORMAT_LEN(width,leftadj));
6592 else{
6593 ielem->print_format = leftadj ? left_adjust(width)
6594 : right_adjust(width);
6595 ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);