* clear out some warnings by gcc 9.3.1.
[alpine.git] / pith / mailindx.c
blob227dfb477ea4a283b879ef377c8aace035f337aa
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-2020 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/pineelt.h"
21 #include "../pith/mailview.h"
22 #include "../pith/flag.h"
23 #include "../pith/icache.h"
24 #include "../pith/msgno.h"
25 #include "../pith/thread.h"
26 #include "../pith/strlst.h"
27 #include "../pith/status.h"
28 #include "../pith/mailcmd.h"
29 #include "../pith/search.h"
30 #include "../pith/charset.h"
31 #include "../pith/reply.h"
32 #include "../pith/bldaddr.h"
33 #include "../pith/addrstring.h"
34 #include "../pith/news.h"
35 #include "../pith/util.h"
36 #include "../pith/pattern.h"
37 #include "../pith/sequence.h"
38 #include "../pith/color.h"
39 #include "../pith/stream.h"
40 #include "../pith/string.h"
41 #include "../pith/send.h"
42 #include "../pith/options.h"
43 #include "../pith/ablookup.h"
44 #ifdef _WINDOWS
45 #include "../pico/osdep/mswin.h"
46 #endif
49 * pointers to formatting functions
51 ICE_S *(*format_index_line)(INDEXDATA_S *);
52 void (*setup_header_widths)(MAILSTREAM *);
55 * pointer to optional load_overview functionality
57 void (*pith_opt_paint_index_hline)(MAILSTREAM *, long, ICE_S *);
60 * pointer to hook for saving index format state
62 void (*pith_opt_save_index_state)(int);
65 * hook to allow caller to insert cue that indicates a condensed
66 * thread relationship cue
68 int (*pith_opt_condense_thread_cue)(PINETHRD_S *, ICE_S *, char **, size_t *, int, int);
69 int (*pith_opt_truncate_sfstr)(void);
73 * Internal prototypes
75 void setup_for_thread_index_screen(void);
76 ICE_S *format_index_index_line(INDEXDATA_S *);
77 ICE_S *format_thread_index_line(INDEXDATA_S *);
78 int set_index_addr(INDEXDATA_S *, char *, ADDRESS *, char *, int, char *);
79 int ctype_is_fixed_length(IndexColType);
80 void setup_index_header_widths(MAILSTREAM *);
81 void setup_thread_header_widths(MAILSTREAM *);
82 int parse_index_format(char *, INDEX_COL_S **);
83 int index_in_overview(MAILSTREAM *);
84 ADDRESS *fetch_from(INDEXDATA_S *);
85 ADDRESS *fetch_sender(INDEXDATA_S *);
86 char *fetch_newsgroups(INDEXDATA_S *);
87 char *fetch_subject(INDEXDATA_S *);
88 char *fetch_date(INDEXDATA_S *);
89 long fetch_size(INDEXDATA_S *);
90 BODY *fetch_body(INDEXDATA_S *);
91 char *fetch_firsttext(INDEXDATA_S *idata, int);
92 char *fetch_header(INDEXDATA_S *idata, char *hdrname);
93 void subj_str(INDEXDATA_S *, char *, size_t, SubjKW, int, int, ICE_S *);
94 void key_str(INDEXDATA_S *, SubjKW, ICE_S *);
95 void header_str(INDEXDATA_S *, HEADER_TOK_S *, ICE_S *);
96 void prio_str(INDEXDATA_S *, IndexColType, ICE_S *);
97 void from_str(IndexColType, INDEXDATA_S *, char *, size_t, ICE_S *);
98 int day_of_week(struct date *);
99 int day_of_year(struct date *);
100 unsigned long ice_hash(ICE_S *);
101 char *left_adjust(int);
102 char *right_adjust(int);
103 char *format_str(int, int);
104 char *copy_format_str(int, int, char *, int);
105 void set_print_format(IELEM_S *, int, int);
106 void set_ielem_widths_in_field(IFIELD_S *);
109 #define BIGWIDTH 2047
112 /*----------------------------------------------------------------------
113 Initialize the index_disp_format array in ps_global from this
114 format string.
116 Args: format -- the string containing the format tokens
117 answer -- put the answer here, free first if there was a previous
118 value here
119 ----*/
120 void
121 init_index_format(char *format, INDEX_COL_S **answer)
123 char *p;
124 int i, w, monabb_width = 0, column = 0;
127 * Record the fact that SCORE appears in some index format. This
128 * is a heavy-handed approach. It will stick at 1 if any format ever
129 * contains score during this session. This is ok since it will just
130 * cause recalculation if wrong and these things rarely change much.
132 if(!ps_global->a_format_contains_score && format
133 && strstr(format, "SCORE")){
134 ps_global->a_format_contains_score = 1;
135 /* recalculate need for scores */
136 scores_are_used(SCOREUSE_INVALID);
139 set_need_format_setup(ps_global->mail_stream);
140 /* if custom format is specified, try it, else go with default */
141 if(!(format && *format && parse_index_format(format, answer))){
142 static INDEX_COL_S answer_default[] = {
143 {iStatus, Fixed, 3},
144 {iMessNo, WeCalculate},
145 {iSDateTime24, WeCalculate},
146 {iFromTo, Percent, 33}, /* percent of rest */
147 {iSizeNarrow, WeCalculate},
148 {iSubjKey, Percent, 67},
149 {iNothing}
152 if(*answer)
153 free_index_format(answer);
155 *answer = (INDEX_COL_S *)fs_get(sizeof(answer_default));
156 memcpy(*answer, answer_default, sizeof(answer_default));
160 * Test to see how long the month abbreviations are.
162 for(i = 1; i <= 12; i++){
163 p = month_abbrev_locale(i);
164 monabb_width = MAX(utf8_width(p), monabb_width);
167 monabb_width = MIN(MAX(2, monabb_width), 5);
170 * Fill in req_width's for WeCalculate items.
172 for(column = 0; (*answer)[column].ctype != iNothing; column++){
174 /* don't use strftime if we're not trying to use the LC_TIME stuff */
175 if(F_ON(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
176 switch((*answer)[column].ctype){
177 case iSDate:
178 (*answer)[column].ctype = iS1Date;
179 break;
180 case iSDateTime:
181 (*answer)[column].ctype = iSDateTimeS1;
182 break;
183 case iSDateTime24:
184 (*answer)[column].ctype = iSDateTimeS124;
185 break;
186 default:
187 break;
191 if((*answer)[column].wtype == WeCalculate){
192 switch((*answer)[column].ctype){
193 case iPrio:
194 case iPrioBang:
195 case iAtt:
196 (*answer)[column].req_width = 1;
197 break;
198 case iYear2Digit:
199 case iDay:
200 case iMon:
201 case iDay2Digit:
202 case iMon2Digit:
203 case iArrow:
204 case iKeyInit:
205 (*answer)[column].req_width = 2;
206 break;
207 case iStatus:
208 case iMessNo:
209 case iInit:
210 (*answer)[column].req_width = 3;
211 break;
212 case iYear:
213 case iDayOrdinal:
214 case iSIStatus:
215 (*answer)[column].req_width = 4;
216 break;
217 case iTime24:
218 case iTimezone:
219 case iSizeNarrow:
220 case iKey:
221 (*answer)[column].req_width = 5;
222 break;
223 case iFStatus:
224 case iIStatus:
225 case iScore:
226 (*answer)[column].req_width = 6;
227 break;
228 case iTime12:
229 case iSTime:
230 case iKSize:
231 case iSize:
232 case iPrioAlpha:
233 (*answer)[column].req_width = 7;
234 break;
235 case iS1Date:
236 case iS2Date:
237 case iS3Date:
238 case iS4Date:
239 case iDateIsoS:
240 case iSizeComma:
241 (*answer)[column].req_width = 8;
242 break;
243 case iSTime24:
244 (*answer)[column].req_width = 9;
245 break;
246 case iMonAbb:
247 (*answer)[column].req_width = monabb_width;
248 (*answer)[column].monabb_width = monabb_width;
249 break;
250 case iDayOfWeekAbb:
252 w = 0;
255 * Test to see how long it is.
257 for(i = 0; i < 7; i++){
258 p = day_abbrev_locale(i);
259 w = MAX(utf8_width(p), w);
262 (*answer)[column].req_width = MIN(MAX(2, w), 5);
264 break;
265 case iDate:
266 (*answer)[column].req_width = monabb_width + 3;
267 (*answer)[column].monabb_width = monabb_width;
268 break;
269 case iMonLong:
271 w = 0;
274 * Test to see how long it is.
276 for(i = 1; i <= 12; i++){
277 p = month_name_locale(i);
278 w = MAX(utf8_width(p), w);
281 (*answer)[column].req_width = MIN(MAX(3, w), 12);
283 break;
284 case iDayOfWeek:
286 w = 0;
288 for(i = 0; i < 7; i++){
289 p = day_name_locale(i);
290 w = MAX(utf8_width(p), w);
293 (*answer)[column].req_width = MIN(MAX(3, w), 12);
295 break;
296 case iSDate:
297 case iSDateTime:
298 case iSDateTime24:
299 case iPrefDate:
300 case iPrefTime:
301 case iPrefDateTime:
304 * Format a date to see how long it is.
305 * Make it as least as long as "Yesterday".
306 * We should really use the width of the longest
307 * of the translated yesterdays and friends but...
309 struct tm tm;
310 int len = 20;
311 char ss[100];
313 memset(&tm, 0, sizeof(tm));
314 tm.tm_year = 106;
315 tm.tm_mon = 11;
316 tm.tm_mday = 31;
317 tm.tm_hour = 3;
318 tm.tm_min = 23;
319 tm.tm_wday = 3;
320 switch((*answer)[column].ctype){
321 case iPrefTime:
322 our_strftime(ss, sizeof(ss), "%X", &tm);
323 break;
324 case iPrefDateTime:
325 our_strftime(ss, sizeof(ss), "%c", &tm);
326 len = 32;
327 break;
328 default:
329 our_strftime(ss, sizeof(ss), "%x", &tm);
330 break;
332 (*answer)[column].req_width = MIN(MAX(9, utf8_width(ss)), len);
335 (*answer)[column].monabb_width = monabb_width;
336 break;
338 case iDescripSize:
339 case iSDateIsoS:
340 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
341 case iSDateTimeIsoS:
342 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
343 case iSDateTimeIsoS24:
344 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
346 * These SDates are 8 wide but they need to be 9 for "Yesterday".
348 (*answer)[column].req_width = 9;
349 break;
350 case iDateIso:
351 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
352 (*answer)[column].req_width = 10;
353 break;
354 case iLDate:
355 (*answer)[column].req_width = 12;
356 (*answer)[column].monabb_width = monabb_width;
357 break;
358 case iRDate:
359 (*answer)[column].req_width = 16;
360 break;
361 default:
362 break;
367 calc_extra_hdrs();
368 if(get_extra_hdrs())
369 (void) mail_parameters(NULL, SET_IMAPEXTRAHEADERS,
370 (void *) get_extra_hdrs());
374 void
375 reset_index_format(void)
377 long rflags = ROLE_DO_OTHER;
378 PAT_STATE pstate;
379 PAT_S *pat;
380 int we_set_it = 0;
382 if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){
383 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
384 if(match_pattern(pat->patgrp, ps_global->mail_stream, NULL,
385 NULL, NULL, SE_NOSERVER|SE_NOPREFETCH))
386 break;
389 if(pat && pat->action && !pat->action->bogus
390 && pat->action->index_format){
391 we_set_it++;
392 init_index_format(pat->action->index_format,
393 &ps_global->index_disp_format);
397 if(!we_set_it)
398 init_index_format(ps_global->VAR_INDEX_FORMAT,
399 &ps_global->index_disp_format);
403 void
404 free_index_format(INDEX_COL_S **disp_format)
406 INDEX_COL_S *cdesc = NULL;
408 if(disp_format && *disp_format){
409 for(cdesc = (*disp_format); cdesc->ctype != iNothing; cdesc++)
410 if(cdesc->hdrtok)
411 free_hdrtok(&cdesc->hdrtok);
413 fs_give((void **) disp_format);
418 HEADER_TOK_S *
419 new_hdrtok(char *hdrname)
421 HEADER_TOK_S *hdrtok;
423 hdrtok = (HEADER_TOK_S *) fs_get(sizeof(HEADER_TOK_S));
424 memset(hdrtok, 0, sizeof(HEADER_TOK_S));
425 hdrtok->hdrname = hdrname ? cpystr(hdrname) : NULL;
426 hdrtok->fieldnum = 0;
427 hdrtok->adjustment = Left;
428 hdrtok->fieldsepcnt = 1;
429 hdrtok->fieldseps = cpystr(" ");
431 return(hdrtok);
435 void
436 free_hdrtok(HEADER_TOK_S **hdrtok)
438 if(hdrtok && *hdrtok){
439 if((*hdrtok)->hdrname)
440 fs_give((void **) &(*hdrtok)->hdrname);
442 if((*hdrtok)->fieldseps)
443 fs_give((void **) &(*hdrtok)->fieldseps);
445 fs_give((void **) hdrtok);
450 /* popular ones first to make it slightly faster */
451 static INDEX_PARSE_T itokens[] = {
452 {"STATUS", iStatus, FOR_INDEX},
453 {"MSGNO", iMessNo, FOR_INDEX},
454 {"DATE", iDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
455 {"FROMORTO", iFromTo, FOR_INDEX},
456 {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX},
457 {"SIZE", iSize, FOR_INDEX},
458 {"SIZECOMMA", iSizeComma, FOR_INDEX},
459 {"SIZENARROW", iSizeNarrow, FOR_INDEX},
460 {"KSIZE", iKSize, FOR_INDEX},
461 {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
462 {"SHORTSUBJECT", iShortSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
463 {"FULLSTATUS", iFStatus, FOR_INDEX},
464 {"IMAPSTATUS", iIStatus, FOR_INDEX},
465 {"SHORTIMAPSTATUS", iSIStatus, FOR_INDEX},
466 {"SUBJKEY", iSubjKey, FOR_INDEX},
467 {"SHORTSUBJKEY", iShortSubjKey, FOR_INDEX},
468 {"SUBJKEYINIT", iSubjKeyInit, FOR_INDEX},
469 {"SHORTSUBJKEYINIT",iShortSubjKeyInit, FOR_INDEX},
470 {"SUBJECTTEXT", iSubjectText, FOR_INDEX},
471 {"SUBJKEYTEXT", iSubjKeyText, FOR_INDEX},
472 {"SUBJKEYINITTEXT", iSubjKeyInitText, FOR_INDEX},
473 {"OPENINGTEXT", iOpeningText, FOR_INDEX},
474 {"OPENINGTEXTNQ", iOpeningTextNQ, FOR_INDEX},
475 {"KEY", iKey, FOR_INDEX},
476 {"KEYINIT", iKeyInit, FOR_INDEX},
477 {"DESCRIPSIZE", iDescripSize, FOR_INDEX},
478 {"ATT", iAtt, FOR_INDEX},
479 {"SCORE", iScore, FOR_INDEX},
480 {"PRIORITY", iPrio, FOR_INDEX},
481 {"PRIORITYALPHA", iPrioAlpha, FOR_INDEX},
482 {"PRIORITY!", iPrioBang, FOR_INDEX},
483 {"LONGDATE", iLDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
484 {"SHORTDATE1", iS1Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
485 {"SHORTDATE2", iS2Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
486 {"SHORTDATE3", iS3Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
487 {"SHORTDATE4", iS4Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
488 {"DATEISO", iDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
489 {"SHORTDATEISO", iDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
490 {"SMARTDATE", iSDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
491 {"SMARTTIME", iSTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
492 {"SMARTTIME24", iSTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
493 {"SMARTDATEISO", iSDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
494 {"SMARTDATESHORTISO",iSDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
495 {"SMARTDATES1", iSDateS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
496 {"SMARTDATES2", iSDateS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
497 {"SMARTDATES3", iSDateS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
498 {"SMARTDATES4", iSDateS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
499 {"SMARTDATETIME", iSDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
500 {"SMARTDATETIMEISO",iSDateTimeIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
501 {"SMARTDATETIMESHORTISO",iSDateTimeIsoS,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
502 {"SMARTDATETIMES1", iSDateTimeS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
503 {"SMARTDATETIMES2", iSDateTimeS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
504 {"SMARTDATETIMES3", iSDateTimeS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
505 {"SMARTDATETIMES4", iSDateTimeS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
506 {"SMARTDATETIME24", iSDateTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
507 {"SMARTDATETIMEISO24", iSDateTimeIso24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
508 {"SMARTDATETIMESHORTISO24",iSDateTimeIsoS24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
509 {"SMARTDATETIMES124", iSDateTimeS124, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
510 {"SMARTDATETIMES224", iSDateTimeS224, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
511 {"SMARTDATETIMES324", iSDateTimeS324, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
512 {"SMARTDATETIMES424", iSDateTimeS424, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
513 {"TIME24", iTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
514 {"TIME12", iTime12, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
515 {"TIMEZONE", iTimezone, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
516 {"MONTHABBREV", iMonAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
517 {"DAYOFWEEKABBREV", iDayOfWeekAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
518 {"DAYOFWEEK", iDayOfWeek, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
519 {"FROM", iFrom, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
520 {"TO", iTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
521 {"SENDER", iSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
522 {"CC", iCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
523 {"RECIPS", iRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
524 {"NEWS", iNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
525 {"TOANDNEWS", iToAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
526 {"NEWSANDTO", iNewsAndTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
527 {"RECIPSANDNEWS", iRecipsAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
528 {"NEWSANDRECIPS", iNewsAndRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
529 {"MSGID", iMsgID, FOR_REPLY_INTRO|FOR_TEMPLATE},
530 {"CURNEWS", iCurNews, FOR_REPLY_INTRO|FOR_TEMPLATE},
531 {"DAYDATE", iRDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
532 {"PREFDATE", iPrefDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
533 {"PREFTIME", iPrefTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
534 {"PREFDATETIME", iPrefDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
535 {"DAY", iDay, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
536 {"DAYORDINAL", iDayOrdinal, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
537 {"DAY2DIGIT", iDay2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
538 {"MONTHLONG", iMonLong, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
539 {"MONTH", iMon, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
540 {"MONTH2DIGIT", iMon2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
541 {"YEAR", iYear, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
542 {"YEAR2DIGIT", iYear2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
543 {"ADDRESS", iAddress, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
544 {"MAILBOX", iMailbox, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
545 {"ROLENICK", iRoleNick, FOR_REPLY_INTRO|FOR_TEMPLATE},
546 {"INIT", iInit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
547 {"CURDATE", iCurDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
548 {"CURDATEISO", iCurDateIso, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
549 {"CURDATEISOS", iCurDateIsoS, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
550 {"CURTIME24", iCurTime24, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
551 {"CURTIME12", iCurTime12, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
552 {"CURDAY", iCurDay, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
553 {"CURDAY2DIGIT", iCurDay2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
554 {"CURDAYOFWEEK", iCurDayOfWeek, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
555 {"CURDAYOFWEEKABBREV", iCurDayOfWeekAbb,
556 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
557 {"CURMONTH", iCurMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
558 {"CURMONTH2DIGIT", iCurMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
559 {"CURMONTHLONG", iCurMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
560 {"CURMONTHABBREV", iCurMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
561 {"CURYEAR", iCurYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
562 {"CURYEAR2DIGIT", iCurYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
563 {"CURPREFDATE", iCurPrefDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
564 {"CURPREFTIME", iCurPrefTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
565 {"CURPREFDATETIME", iCurPrefDateTime,
566 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
567 {"LASTMONTH", iLstMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
568 {"LASTMONTH2DIGIT", iLstMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
569 {"LASTMONTHLONG", iLstMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
570 {"LASTMONTHABBREV", iLstMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
571 {"LASTMONTHYEAR", iLstMonYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
572 {"LASTMONTHYEAR2DIGIT", iLstMonYear2Digit,
573 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
574 {"LASTYEAR", iLstYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
575 {"LASTYEAR2DIGIT", iLstYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
576 {"HEADER", iHeader, FOR_INDEX},
577 {"TEXT", iText, FOR_INDEX},
578 {"ARROW", iArrow, FOR_INDEX},
579 {"NEWLINE", iNewLine, FOR_REPLY_INTRO},
580 {"CURSORPOS", iCursorPos, FOR_TEMPLATE},
581 {NULL, iNothing, FOR_NOTHING}
584 INDEX_PARSE_T itokensinv[sizeof(itokens)/sizeof(itokens[0])];
586 void
587 inverse_itokens(void)
589 INDEX_PARSE_T *pt;
590 for (pt = itokens; pt->name; pt++)
591 itokensinv[pt->ctype].ctype = pt - itokens;
595 INDEX_PARSE_T *
596 itoken(int i)
598 return((i < sizeof(itokens) && itokens[i].name) ? &itokens[i] : NULL);
603 * Args txt -- The token being checked begins at the beginning
604 * of txt. The end of the token is delimited by a null, or
605 * white space, or an underscore if DELIM_USCORE is set,
606 * or a left paren if DELIM_PAREN is set.
607 * flags -- Flags contains the what_for value, and DELIM_ values.
609 * Returns A ptr to an INDEX_PARSE_T from itokens above, else NULL.
611 INDEX_PARSE_T *
612 itoktype(char *txt, int flags)
614 INDEX_PARSE_T *pt;
615 char token[100 + 1];
616 char *v, *w;
619 * Separate a copy of the possible token out of txt.
621 v = txt;
622 w = token;
623 while(w < token+sizeof(token)-1 &&
624 *v &&
625 !(!(*v & 0x80) && isspace((unsigned char)*v)) &&
626 !(flags & DELIM_USCORE && *v == '_') &&
627 !(flags & DELIM_PAREN && *v == '(') &&
628 !(flags & DELIM_COLON && *v == ':'))
629 *w++ = *v++;
631 *w = '\0';
633 for(pt = itokens; pt->name; pt++)
634 if(pt->what_for & flags && !strucmp(pt->name, token))
635 return(pt);
637 return(NULL);
642 parse_index_format(char *format_str, INDEX_COL_S **answer)
644 int i, column = 0;
645 char *p, *q;
646 INDEX_PARSE_T *pt;
647 INDEX_COL_S cdesc[200]; /* plenty of temp storage for answer */
649 memset((void *)cdesc, 0, sizeof(cdesc));
651 p = format_str;
652 while(p && *p && column < 200-1){
653 /* skip leading white space for next word */
654 p = skip_white_space(p);
655 pt = itoktype(p, FOR_INDEX | DELIM_PAREN | DELIM_COLON);
657 /* ignore unrecognized word */
658 if(!pt){
659 char c;
660 for(q = p; *p && !isspace((unsigned char)*p); p++)
663 if((c = *p) != '\0')
664 *p++ = '\0';
666 dprint((1,
667 "parse_index_format: unrecognized token: %s\n",
668 q ? q : "?"));
669 q_status_message1(SM_ORDER | SM_DING, 0, 3,
670 _("Unrecognized word in index-format: %s"), q);
671 if(c != '\0')
672 *(p-1) = c;
673 continue;
676 cdesc[column].ctype = pt->ctype;
678 if(pt->ctype == iHeader || pt->ctype == iText){
680 * iHeader field has special syntax.
682 * HEADER:hdrname(width,fieldnum,field_separators,L_or_R)
684 * where width is the regular width or percentage width or
685 * left out for default width, fieldnum defaults to 0 for
686 * whole thing, 1 for first field, ...
687 * and field_separators is a list of characters which separate
688 * the fields. The whole parenthesized part is optional. If used
689 * the arguments can be dropped from the right, so
691 * HEADER:hdrname or
692 * HEADER:hdrname(10) or
693 * HEADER:hdrname(10%) or
694 * HEADER:hdrname(10,2) or
695 * HEADER:hdrname(,2) or
696 * HEADER:hdrname(10,2, ) or
697 * HEADER:hdrname(10,2,\,:) or
698 * HEADER:hdrname(10,2,\,:,R)
700 * iText field uses the hdrtok field for convenience. It has syntax
702 * TEXT:text or
703 * TEXT:text(10) or
704 * TEXT:text(10%)
706 * and the literal text goes into the index line. It is also special
707 * because there is no 1 column space after this field.
710 /* skip over name */
711 p += strlen(pt->name);
713 /* look for header name */
714 if(*p == ':'){
715 char *w, hdrname[200];
717 hdrname[0] = '\0';
718 w = hdrname;
719 p++;
720 if(*p == '\"'){ /* quoted name */
721 p++;
722 while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){
723 if(*p == '\\')
724 p++;
726 *w++ = *p++;
729 *w = '\0';
730 if(*p == '\"')
731 p++;
733 else{
734 while(w < hdrname + sizeof(hdrname)-1 &&
735 !(!(*p & 0x80) && isspace((unsigned char)*p)) &&
736 *p != '(')
737 *w++ = *p++;
739 *w = '\0';
742 if(hdrname[0]){
743 cdesc[column].hdrtok = new_hdrtok(hdrname);
745 else{
746 if(pt->ctype == iHeader){
747 dprint((1, "parse_index_token: HEADER should be followed by :hdrname\n"));
748 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname");
750 else{
751 dprint((1, "parse_index_token: TEXT should be followed by :text\n"));
752 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text");
756 else{
757 if(pt->ctype == iHeader){
758 dprint((1, "parse_index_token: HEADER should be followed by :hdrname, not %s\n", p));
759 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname");
761 else{
762 dprint((1, "parse_index_token: TEXT should be followed by :text, not %s\n", p));
763 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text");
766 /* skip over rest of bogus config */
767 while(!(!(*p & 0x80) && isspace((unsigned char)*p)) && *p != '(')
768 p++;
771 else{
772 /* skip over name and look for parens */
773 p += strlen(pt->name);
776 if(*p == '('){
777 p++;
778 q = p;
779 while(p && *p && isdigit((unsigned char) *p))
780 p++;
782 if(pt->ctype == iHeader){
783 /* first argument is width or width percentage, like for others */
784 if(p && *p && (*p == ')' || *p == ',')){
785 if(p > q){
786 cdesc[column].wtype = Fixed;
787 cdesc[column].req_width = atoi(q);
789 else{
790 cdesc[column].wtype = WeCalculate;
791 cdesc[column].req_width = 0;
794 else if(p && *p && *p == '%' && p > q){
795 cdesc[column].wtype = Percent;
796 cdesc[column].req_width = atoi(q);
797 p++;
799 else{
800 cdesc[column].wtype = WeCalculate;
801 cdesc[column].req_width = 0;
804 /* optional 2nd argument is field number, 0 whole thing, 1, 2, ... */
805 if(p && *p && *p == ','){
806 p++;
807 /* no space allowed between arguments */
808 if(*p && isdigit((unsigned char) *p)){
809 q = p;
810 while(*p && isdigit((unsigned char) *p))
811 p++;
813 cdesc[column].hdrtok->fieldnum = atoi(q);
816 * Optional 3rd argument is field separators.
817 * Comma is \, and backslash is \\.
819 if(*p == ','){
820 int j;
822 p++;
823 /* don't use default */
824 if(*p && *p != ')' && *p != ',' && cdesc[column].hdrtok->fieldseps)
825 cdesc[column].hdrtok->fieldseps[0] = '\0';
827 j = 0;
828 if(*p == '\"' && strchr(p+1, '\"')){
829 p++;
830 while(*p && *p != ')' && *p != '\"' && *p != ','){
831 if(cdesc[column].hdrtok->fieldseps)
832 fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2);
834 if(*p == '\\' && *(p+1))
835 p++;
837 if(cdesc[column].hdrtok->fieldseps){
838 cdesc[column].hdrtok->fieldseps[j++] = *p++;
839 cdesc[column].hdrtok->fieldseps[j] = '\0';
840 cdesc[column].hdrtok->fieldsepcnt = j;
844 if(*p == '\"')
845 p++;
847 else{
848 while(*p && *p != ')' && *p != ','){
849 if(cdesc[column].hdrtok->fieldseps)
850 fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2);
851 if(*p == '\\' && *(p+1))
852 p++;
854 if(cdesc[column].hdrtok->fieldseps){
855 cdesc[column].hdrtok->fieldseps[j++] = *p++;
856 cdesc[column].hdrtok->fieldseps[j] = '\0';
857 cdesc[column].hdrtok->fieldsepcnt = j;
862 /* optional 4th argument, left or right adjust */
863 if(*p == ','){
864 p++;
865 if(*p == 'L' || *p == 'l')
866 cdesc[column].hdrtok->adjustment = Left;
867 else if(*p == 'R' || *p == 'r')
868 cdesc[column].hdrtok->adjustment = Right;
869 else{
870 dprint((1, "parse_index_token: HEADER 4th argument should be L or R, not\n", *p ? p : "<null>"));
871 q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 4th argument should be L or R");
876 else{
877 dprint((1, "parse_index_token: HEADER 2nd argument should be field number, not\n", *p ? p : "<null>"));
878 q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 2nd argument should be field number, a non-negative digit");
882 else{
883 if(p && *p && *p == ')' && p > q){
884 cdesc[column].wtype = Fixed;
885 cdesc[column].req_width = atoi(q);
887 else if(p && *p && *p == '%' && p > q){
888 cdesc[column].wtype = Percent;
889 cdesc[column].req_width = atoi(q);
891 else{
892 cdesc[column].wtype = WeCalculate;
893 cdesc[column].req_width = 0;
897 else{
898 /* if they left out width for iText we can figure it out */
899 if(pt->ctype == iText && cdesc[column].hdrtok && cdesc[column].hdrtok->hdrname){
900 cdesc[column].wtype = Fixed;
901 cdesc[column].req_width = utf8_width(cdesc[column].hdrtok->hdrname);
903 else{
904 cdesc[column].wtype = WeCalculate;
905 cdesc[column].req_width = 0;
909 column++;
910 /* skip text at end of word */
911 while(p && *p && !isspace((unsigned char)*p))
912 p++;
915 /* if, after all that, we didn't find anything recognizable, bitch */
916 if(!column){
917 dprint((1, "Completely unrecognizable index-format\n"));
918 q_status_message(SM_ORDER | SM_DING, 0, 3,
919 _("Configured \"index-format\" unrecognizable. Using default."));
920 return(0);
923 /* Finish with Nothing column */
924 cdesc[column].ctype = iNothing;
926 /* free up old answer */
927 if(*answer)
928 free_index_format(answer);
930 /* allocate space for new answer */
931 *answer = (INDEX_COL_S *)fs_get((column+1)*sizeof(INDEX_COL_S));
932 memset((void *)(*answer), 0, (column+1)*sizeof(INDEX_COL_S));
933 /* copy answer to real place */
934 for(i = 0; i <= column; i++)
935 (*answer)[i] = cdesc[i];
937 return(1);
942 * These types are basically fixed in width.
943 * The order is slightly significant. The ones towards the front of the
944 * list get space allocated sooner than the ones at the end of the list.
946 static IndexColType fixed_ctypes[] = {
947 iMessNo, iStatus, iFStatus, iIStatus, iSIStatus,
948 iDate, iSDate, iSDateTime, iSDateTime24,
949 iSTime, iSTime24, iLDate,
950 iS1Date, iS2Date, iS3Date, iS4Date, iDateIso, iDateIsoS,
951 iSDateIso, iSDateIsoS,
952 iSDateS1, iSDateS2, iSDateS3, iSDateS4,
953 iSDateTimeIso, iSDateTimeIsoS,
954 iSDateTimeS1, iSDateTimeS2, iSDateTimeS3, iSDateTimeS4,
955 iSDateTimeIso24, iSDateTimeIsoS24,
956 iSDateTimeS124, iSDateTimeS224, iSDateTimeS324, iSDateTimeS424,
957 iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize,
958 iPrio, iPrioBang, iPrioAlpha, iInit,
959 iAtt, iTime24, iTime12, iTimezone, iMonAbb, iYear, iYear2Digit,
960 iDay2Digit, iMon2Digit, iDayOfWeekAbb, iScore, iMonLong, iDayOfWeek
965 ctype_is_fixed_length(IndexColType ctype)
967 int j;
969 for(j = 0; ; j++){
970 if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes))
971 break;
973 if(ctype == fixed_ctypes[j])
974 return 1;
977 return 0;
981 /*----------------------------------------------------------------------
982 Setup the widths of the various columns in the index display
983 ----*/
984 void
985 setup_index_header_widths(MAILSTREAM *stream)
987 int colspace; /* for reserving space between columns */
988 int j, some_to_calculate;
989 int space_left, screen_width, fix;
990 int keep_going, tot_pct, was_sl;
991 long max_msgno;
992 WidthType wtype;
993 INDEX_COL_S *cdesc;
995 max_msgno = mn_get_total(ps_global->msgmap);
997 dprint((8, "=== setup_index_header_widths() ===\n"));
999 clear_icache_flags(stream);
1000 screen_width = ps_global->ttyo->screen_cols;
1001 space_left = screen_width;
1002 some_to_calculate = 0;
1003 colspace = -1;
1006 * Calculate how many fields there are so we know how many spaces
1007 * between columns to reserve. Fill in Fixed widths now. Reserve
1008 * special case WeCalculate with non-zero req_widths before doing
1009 * Percent cases below.
1011 for(cdesc = ps_global->index_disp_format;
1012 cdesc->ctype != iNothing;
1013 cdesc++){
1015 if(cdesc->wtype == Fixed){
1016 cdesc->width = cdesc->req_width;
1017 if(cdesc->width > 0)
1018 colspace++;
1020 else if(cdesc->wtype == Percent){
1021 cdesc->width = 0; /* calculated later */
1022 colspace++;
1024 else{ /* WeCalculate */
1025 cdesc->width = cdesc->req_width; /* reserve this for now */
1026 some_to_calculate++;
1027 colspace++;
1030 /* no space after iText */
1031 if(cdesc->ctype == iText)
1032 colspace--;
1034 space_left -= cdesc->width;
1037 colspace = MAX(colspace, 0);
1039 space_left -= colspace; /* space between columns */
1041 ps_global->display_keywords_in_subject = 0;
1042 ps_global->display_keywordinits_in_subject = 0;
1045 * Set the actual lengths for the fixed width fields and set up
1046 * the left or right adjustment for everything.
1047 * There should be a case setting actual_length for all of the types
1048 * in fixed_ctypes.
1050 for(cdesc = ps_global->index_disp_format;
1051 cdesc->ctype != iNothing;
1052 cdesc++){
1054 wtype = cdesc->wtype;
1056 if(cdesc->ctype == iSubjKey || cdesc->ctype == iSubjKeyText)
1057 ps_global->display_keywords_in_subject = 1;
1058 else if(cdesc->ctype == iSubjKeyInit || cdesc->ctype == iSubjKeyInitText)
1059 ps_global->display_keywordinits_in_subject = 1;
1061 if(wtype == WeCalculate || wtype == Percent || cdesc->width != 0){
1063 switch(cdesc->ctype){
1064 case iSDate: case iSDateIso: case iSDateIsoS:
1065 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1066 case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
1067 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1068 case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
1069 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1070 case iSTime: case iSTime24:
1071 set_format_includes_smartdate(stream);
1072 break;
1074 default:
1075 break;
1078 if(ctype_is_fixed_length(cdesc->ctype)){
1079 switch(cdesc->ctype){
1080 case iPrio:
1081 case iPrioBang:
1082 case iAtt:
1083 cdesc->actual_length = 1;
1084 cdesc->adjustment = Left;
1085 break;
1087 case iYear2Digit:
1088 case iDay2Digit:
1089 case iMon2Digit:
1090 cdesc->actual_length = 2;
1091 cdesc->adjustment = Left;
1092 break;
1094 case iArrow:
1095 cdesc->actual_length = 2;
1096 cdesc->adjustment = Right;
1097 break;
1099 case iStatus:
1100 case iInit:
1101 cdesc->actual_length = 3;
1102 cdesc->adjustment = Left;
1103 break;
1105 case iMessNo:
1106 set_format_includes_msgno(stream);
1107 if(max_msgno < 1000)
1108 cdesc->actual_length = 3;
1109 else if(max_msgno < 10000)
1110 cdesc->actual_length = 4;
1111 else if(max_msgno < 100000)
1112 cdesc->actual_length = 5;
1113 else
1114 cdesc->actual_length = 6;
1116 cdesc->adjustment = Right;
1117 break;
1119 case iYear:
1120 case iSIStatus:
1121 cdesc->actual_length = 4;
1122 cdesc->adjustment = Left;
1123 break;
1125 case iTime24:
1126 case iTimezone:
1127 cdesc->actual_length = 5;
1128 cdesc->adjustment = Left;
1129 break;
1131 case iSizeNarrow:
1132 cdesc->actual_length = 5;
1133 cdesc->adjustment = Right;
1134 break;
1136 case iFStatus:
1137 case iIStatus:
1138 cdesc->actual_length = 6;
1139 cdesc->adjustment = Left;
1140 break;
1142 case iScore:
1143 cdesc->actual_length = 6;
1144 cdesc->adjustment = Right;
1145 break;
1147 case iTime12:
1148 case iSize:
1149 case iKSize:
1150 cdesc->actual_length = 7;
1151 cdesc->adjustment = Right;
1152 break;
1154 case iSTime:
1155 cdesc->actual_length = 7;
1156 cdesc->adjustment = Left;
1157 break;
1159 case iPrioAlpha:
1160 cdesc->actual_length = 7;
1161 cdesc->adjustment = Left;
1162 break;
1165 case iS1Date:
1166 case iS2Date:
1167 case iS3Date:
1168 case iS4Date:
1169 case iDateIsoS:
1170 cdesc->actual_length = 8;
1171 cdesc->adjustment = Left;
1172 break;
1174 case iSizeComma:
1175 cdesc->actual_length = 8;
1176 cdesc->adjustment = Right;
1177 break;
1179 case iSDate:
1180 case iSDateTime:
1181 case iSDateTime24:
1182 case iMonAbb:
1183 case iDayOfWeekAbb:
1184 case iDayOfWeek:
1185 case iDate:
1186 case iMonLong:
1187 cdesc->actual_length = cdesc->req_width;
1188 cdesc->adjustment = Left;
1189 break;
1191 case iSTime24:
1192 case iSDateIsoS:
1193 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1194 case iSDateTimeIsoS:
1195 case iSDateTimeIsoS24:
1196 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1197 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1198 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
1199 if(cdesc->ctype == iSDateIso
1200 || cdesc->ctype == iSDateTimeIso
1201 || cdesc->ctype == iSDateTimeIso24)
1202 cdesc->actual_length = 10;
1203 else
1204 cdesc->actual_length = 9;
1206 cdesc->adjustment = Left;
1207 break;
1209 case iDescripSize:
1210 cdesc->actual_length = 9;
1211 cdesc->adjustment = Right;
1212 break;
1214 case iDateIso:
1215 cdesc->actual_length = 10;
1216 cdesc->adjustment = Left;
1217 break;
1219 case iLDate:
1220 cdesc->actual_length = 12;
1221 cdesc->adjustment = Left;
1222 break;
1224 default:
1225 alpine_panic("Unhandled fixed case in setup_index_header");
1226 break;
1229 else if(cdesc->ctype == iHeader)
1230 cdesc->adjustment = cdesc->hdrtok ? cdesc->hdrtok->adjustment : Left;
1231 else
1232 cdesc->adjustment = Left;
1236 if(ps_global->display_keywords_in_subject)
1237 ps_global->display_keywordinits_in_subject = 0;
1239 /* if have reserved unneeded space for size, give it back */
1240 for(cdesc = ps_global->index_disp_format;
1241 cdesc->ctype != iNothing;
1242 cdesc++)
1243 if(cdesc->ctype == iSize || cdesc->ctype == iKSize ||
1244 cdesc->ctype == iSizeNarrow ||
1245 cdesc->ctype == iSizeComma || cdesc->ctype == iDescripSize){
1246 if(cdesc->actual_length == 0){
1247 if((fix=cdesc->width) > 0){ /* had this reserved */
1248 cdesc->width = 0;
1249 space_left += fix;
1252 space_left++; /* +1 for space between columns */
1257 * Calculate the field widths that are basically fixed in width.
1258 * Do them in this order in case we don't have enough space to go around.
1259 * The set of fixed_ctypes here is the same as the set where we
1260 * set the actual_lengths above.
1262 for(j = 0; space_left > 0 && some_to_calculate; j++){
1264 if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes))
1265 break;
1267 for(cdesc = ps_global->index_disp_format;
1268 cdesc->ctype != iNothing && space_left > 0 && some_to_calculate;
1269 cdesc++)
1270 if(cdesc->ctype == fixed_ctypes[j] && cdesc->wtype == WeCalculate){
1271 some_to_calculate--;
1272 fix = MIN(cdesc->actual_length - cdesc->width, space_left);
1273 cdesc->width += fix;
1274 space_left -= fix;
1279 * Fill in widths for Percent cases. If there are no more to calculate,
1280 * use the percentages as relative numbers and use the rest of the space,
1281 * else treat them as absolute percentages of the original avail screen.
1283 if(space_left > 0){
1284 if(some_to_calculate){
1285 int tot_requested = 0;
1288 * Requests are treated as percent of screen width. See if they
1289 * will all fit. If not, trim them back proportionately.
1291 for(cdesc = ps_global->index_disp_format;
1292 cdesc->ctype != iNothing;
1293 cdesc++){
1294 if(cdesc->wtype == Percent){
1295 /* The 2, 200, and +100 are because we're rounding */
1296 fix = ((2*cdesc->req_width *
1297 (screen_width-colspace))+100) / 200;
1298 tot_requested += fix;
1302 if(tot_requested > space_left){
1303 int multiplier = (100 * space_left) / tot_requested;
1305 for(cdesc = ps_global->index_disp_format;
1306 cdesc->ctype != iNothing && space_left > 0;
1307 cdesc++){
1308 if(cdesc->wtype == Percent){
1309 /* The 2, 200, and +100 are because we're rounding */
1310 fix = ((2*cdesc->req_width *
1311 (screen_width-colspace))+100) / 200;
1312 fix = (2 * fix * multiplier + 100) / 200;
1313 fix = MIN(fix, space_left);
1314 cdesc->width += fix;
1315 space_left -= fix;
1319 else{
1320 for(cdesc = ps_global->index_disp_format;
1321 cdesc->ctype != iNothing && space_left > 0;
1322 cdesc++){
1323 if(cdesc->wtype == Percent){
1324 /* The 2, 200, and +100 are because we're rounding */
1325 fix = ((2*cdesc->req_width *
1326 (screen_width-colspace))+100) / 200;
1327 fix = MIN(fix, space_left);
1328 cdesc->width += fix;
1329 space_left -= fix;
1334 else{
1335 tot_pct = 0;
1336 was_sl = space_left;
1337 /* add up total percentages requested */
1338 for(cdesc = ps_global->index_disp_format;
1339 cdesc->ctype != iNothing;
1340 cdesc++)
1341 if(cdesc->wtype == Percent)
1342 tot_pct += cdesc->req_width;
1344 /* give relative weight to requests */
1345 for(cdesc = ps_global->index_disp_format;
1346 cdesc->ctype != iNothing && space_left > 0 && tot_pct > 0;
1347 cdesc++){
1348 if(cdesc->wtype == Percent){
1349 fix = ((2*cdesc->req_width*was_sl)+tot_pct) / (2*tot_pct);
1350 fix = MIN(fix, space_left);
1351 cdesc->width += fix;
1352 space_left -= fix;
1358 /* split up rest, give twice as much to Subject */
1359 keep_going = 1;
1360 while(space_left > 0 && keep_going){
1361 keep_going = 0;
1362 for(cdesc = ps_global->index_disp_format;
1363 cdesc->ctype != iNothing && space_left > 0;
1364 cdesc++){
1365 if(cdesc->wtype == WeCalculate && !ctype_is_fixed_length(cdesc->ctype)){
1366 keep_going++;
1367 cdesc->width++;
1368 space_left--;
1369 if(space_left > 0 && (cdesc->ctype == iSubject
1370 || cdesc->ctype == iShortSubject
1371 || cdesc->ctype == iSubjectText
1372 || cdesc->ctype == iSubjKey
1373 || cdesc->ctype == iShortSubjKey
1374 || cdesc->ctype == iSubjKeyText
1375 || cdesc->ctype == iSubjKeyInit
1376 || cdesc->ctype == iShortSubjKeyInit
1377 || cdesc->ctype == iSubjKeyInitText)){
1378 cdesc->width++;
1379 space_left--;
1385 /* if still more, pad out percent's */
1386 keep_going = 1;
1387 while(space_left > 0 && keep_going){
1388 keep_going = 0;
1389 for(cdesc = ps_global->index_disp_format;
1390 cdesc->ctype != iNothing && space_left > 0;
1391 cdesc++){
1392 if(cdesc->wtype == Percent && !ctype_is_fixed_length(cdesc->ctype)){
1393 keep_going++;
1394 cdesc->width++;
1395 space_left--;
1400 /* if user made Fixed fields too big, give back space */
1401 keep_going = 1;
1402 while(space_left < 0 && keep_going){
1403 keep_going = 0;
1404 for(cdesc = ps_global->index_disp_format;
1405 cdesc->ctype != iNothing && space_left < 0;
1406 cdesc++){
1407 if(cdesc->wtype == Fixed && cdesc->width > 0){
1408 keep_going++;
1409 cdesc->width--;
1410 space_left++;
1415 if(pith_opt_save_index_state)
1416 (*pith_opt_save_index_state)(FALSE);
1420 void
1421 setup_thread_header_widths(MAILSTREAM *stream)
1423 clear_icache_flags(stream);
1424 if(pith_opt_save_index_state)
1425 (*pith_opt_save_index_state)(TRUE);
1430 * load_overview - c-client call back to gather overview data
1432 * Note: if we never get called, UID represents a hole
1433 * if we're passed a zero UID, totally bogus overview data
1434 * if we're passed a zero obuf, mostly bogus overview data
1436 void
1437 load_overview(MAILSTREAM *stream, imapuid_t uid, OVERVIEW *obuf, long unsigned int rawno)
1439 if(obuf && rawno >= 1L && stream && rawno <= stream->nmsgs){
1440 INDEXDATA_S idata;
1441 ICE_S *ice;
1443 memset(&idata, 0, sizeof(INDEXDATA_S));
1444 idata.no_fetch = 1;
1447 * Only really load the thing if we've got an NNTP stream
1448 * otherwise we're just using mail_fetch_overview to load the
1449 * IMAP envelope cache with the specific set of messages
1450 * in a single RTT.
1452 idata.stream = stream;
1453 idata.rawno = rawno;
1454 idata.msgno = mn_raw2m(sp_msgmap(stream), idata.rawno);
1455 idata.size = obuf->optional.octets;
1456 idata.from = obuf->from;
1457 idata.date = obuf->date;
1458 idata.subject = obuf->subject;
1460 ice = (*format_index_line)(&idata);
1461 if(idata.bogus && ice){
1462 if(THRD_INDX()){
1463 if(ice->tice)
1464 clear_ice(&ice->tice);
1466 else
1467 clear_ice(&ice);
1469 else if(F_OFF(F_QUELL_NEWS_ENV_CB, ps_global)
1470 && (!THRD_INDX() || (ice && ice->tice))
1471 && !msgline_hidden(stream, sp_msgmap(stream), idata.msgno, 0)
1472 && pith_opt_paint_index_hline){
1473 (*pith_opt_paint_index_hline)(stream, idata.msgno, ice);
1479 ICE_S *
1480 build_header_work(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
1481 long int msgno, long int top_msgno, int msgcount, int *fetched)
1483 ICE_S *ice, *ic;
1484 MESSAGECACHE *mc;
1485 long n, i, cnt, rawno, visible, limit = -1L;
1487 rawno = mn_m2raw(msgmap, msgno);
1489 /* cache hit? */
1490 if(THRD_INDX()){
1491 ice = fetch_ice(stream, rawno);
1492 if(!ice)
1493 return(NULL);
1495 if(ice->tice && ice->tice->ifield
1496 && ice->tice->color_lookup_done && ice->tice->widths_done){
1497 #ifdef DEBUG
1498 char buf[MAX_SCREEN_COLS+1];
1499 simple_index_line(buf, sizeof(buf), ice->tice, msgno);
1500 #endif
1501 dprint((9, "Hitt: Returning %p -> <%s (%d)\n",
1502 ice->tice,
1503 buf[0] ? buf : "?",
1504 buf[0] ? strlen(buf) : 0));
1505 return(ice);
1508 else{
1509 ice = fetch_ice(stream, rawno);
1510 if(!ice)
1511 return(NULL);
1513 if(ice->ifield && ice->color_lookup_done && ice->widths_done){
1514 #ifdef DEBUG
1515 char buf[MAX_SCREEN_COLS+1];
1516 simple_index_line(buf, sizeof(buf), ice, msgno);
1517 #endif
1518 dprint((9, "Hit: Returning %p -> <%s (%d)\n",
1519 ice,
1520 buf[0] ? buf : "?",
1521 buf[0] ? strlen(buf) : 0));
1522 return(ice);
1527 * If we are in THRD_INDX() and the width changed we don't currently
1528 * have a method of fixing just the widths and print_format strings.
1529 * Instead, we clear the index cache entry and start over.
1531 if(THRD_INDX() && ice && ice->tice && ice->tice->ifield
1532 && !ice->tice->widths_done){
1533 clear_ice(&ice->tice);
1537 * Fetch everything we need to start filling in the index line
1538 * explicitly via mail_fetch_overview. On an nntp stream
1539 * this has the effect of building the index lines in the
1540 * load_overview callback. Under IMAP we're either getting
1541 * the envelope data via the imap_envelope callback or
1542 * preloading the cache. Either way, we're getting exactly
1543 * what we want rather than relying on linear lookahead sort
1544 * of prefetch...
1546 if(!(fetched && *fetched) && index_in_overview(stream)
1547 && ((THRD_INDX() && !(ice->tice && ice->tice->ifield))
1548 || (!THRD_INDX() && !ice->ifield))){
1549 char *seq;
1550 int count;
1551 MESSAGECACHE *mc;
1552 PINETHRD_S *thrd;
1554 if(fetched)
1555 (*fetched)++;
1557 /* clear sequence bits */
1558 for(n = 1L; n <= stream->nmsgs; n++)
1559 if((mc = mail_elt(stream, n)) != NULL)
1560 mc->sequence = 0;
1563 * Light interesting bits
1564 * NOTE: not set above because m2raw's cheaper
1565 * than raw2m for every message
1569 * Unfortunately, it is expensive to calculate visible pages
1570 * in thread index if we are zoomed, so we don't try.
1572 if(THRD_INDX() && any_lflagged(msgmap, MN_HIDE))
1573 visible = msgmap->visible_threads;
1574 else if(THREADING() && sp_viewing_a_thread(stream)){
1576 * We know that all visible messages in the thread are marked
1577 * with MN_CHID2.
1579 for(visible = 0L, n = top_msgno;
1580 visible < msgcount && n <= mn_get_total(msgmap);
1581 n++){
1583 if(!get_lflag(stream, msgmap, n, MN_CHID2))
1584 break;
1586 if(!msgline_hidden(stream, msgmap, n, 0))
1587 visible++;
1591 else
1592 visible = mn_get_total(msgmap)
1593 - any_lflagged(msgmap, MN_HIDE|MN_CHID);
1595 limit = MIN(visible, msgcount);
1597 if(THRD_INDX()){
1598 count = i = 0;
1601 * First add the msgno we're asking for in case it
1602 * isn't visible.
1604 thrd = fetch_thread(stream, mn_m2raw(msgmap, msgno));
1605 if(msgno <= mn_get_total(msgmap)
1606 && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){
1607 count += mark_msgs_in_thread(stream, thrd, msgmap);
1610 thrd = fetch_thread(stream, mn_m2raw(msgmap, top_msgno));
1613 * Loop through visible threads, marking them for fetching.
1614 * Stop at end of screen or sooner if we run out of visible
1615 * threads.
1617 while(thrd){
1618 n = mn_raw2m(msgmap, thrd->rawno);
1619 if(n >= msgno
1620 && n <= mn_get_total(msgmap)
1621 && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){
1622 count += mark_msgs_in_thread(stream, thrd, msgmap);
1625 if(++i >= limit)
1626 break;
1628 /* find next thread which is visible */
1630 if(mn_get_revsort(msgmap) && thrd->prevthd)
1631 thrd = fetch_thread(stream, thrd->prevthd);
1632 else if(!mn_get_revsort(msgmap) && thrd->nextthd)
1633 thrd = fetch_thread(stream, thrd->nextthd);
1634 else
1635 thrd = NULL;
1636 } while(thrd
1637 && msgline_hidden(stream, msgmap,
1638 mn_raw2m(msgmap, thrd->rawno), 0));
1641 else{
1642 count = i = 0;
1645 * First add the msgno we're asking for in case it
1646 * isn't visible.
1648 if(msgno > 0L && msgno <= mn_get_total(msgmap)
1649 && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,msgno)))) || !ic->ifield)){
1650 if((thrd = fetch_thread(stream, rawno)) != NULL){
1652 * If we're doing a MUTTLIKE display the index line
1653 * may depend on the thread parent, and grandparent,
1654 * and further back. So just fetch the whole thread
1655 * in that case.
1657 if(THREADING()
1658 && ps_global->thread_disp_style == THREAD_MUTTLIKE
1659 && thrd->top)
1660 thrd = fetch_thread(stream, thrd->top);
1662 count += mark_msgs_in_thread(stream, thrd, msgmap);
1664 else if(rawno > 0L && rawno <= stream->nmsgs
1665 && (mc = mail_elt(stream,rawno))
1666 && !mc->private.msg.env){
1667 mc->sequence = 1;
1668 count++;
1672 n = top_msgno;
1673 while(1){
1674 if(n >= msgno
1675 && n <= mn_get_total(msgmap)
1676 && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,n)))) || !ic->ifield)){
1677 if((thrd = fetch_thread(stream, rawno)) != NULL){
1679 * If we're doing a MUTTLIKE display the index line
1680 * may depend on the thread parent, and grandparent,
1681 * and further back. So just fetch the whole thread
1682 * in that case.
1684 if(THREADING()
1685 && ps_global->thread_disp_style == THREAD_MUTTLIKE
1686 && thrd->top)
1687 thrd = fetch_thread(stream, thrd->top);
1689 count += mark_msgs_in_thread(stream, thrd, msgmap);
1691 else if(rawno > 0L && rawno <= stream->nmsgs
1692 && (mc = mail_elt(stream,rawno))
1693 && !mc->private.msg.env){
1694 mc->sequence = 1;
1695 count++;
1699 if(++i >= limit)
1700 break;
1702 /* find next n which is visible */
1703 while(++n <= mn_get_total(msgmap)
1704 && msgline_hidden(stream, msgmap, n, 0))
1709 if(count){
1710 seq = build_sequence(stream, NULL, NULL);
1711 if(seq){
1712 ps_global->dont_count_flagchanges = 1;
1713 mail_fetch_overview_sequence(stream, seq,
1714 (stream->dtb && stream->dtb->name
1715 && !strcmp(stream->dtb->name, "imap"))
1716 ? NULL : load_overview);
1717 ps_global->dont_count_flagchanges = 0;
1718 fs_give((void **) &seq);
1723 * reassign ice from the cache as it may've been built
1724 * within the overview callback or it may have become stale
1725 * in the prior sequence bit setting loop ...
1727 rawno = mn_m2raw(msgmap, msgno);
1728 ice = fetch_ice(stream, rawno);
1729 if(!ice)
1730 return(NULL);
1733 if((THRD_INDX() && !(ice->tice && ice->tice->ifield))
1734 || (!THRD_INDX() && !ice->ifield)){
1735 INDEXDATA_S idata;
1738 * With pre-fetching/callback-formatting done and no success,
1739 * fall into formatting the requested line...
1741 memset(&idata, 0, sizeof(INDEXDATA_S));
1742 idata.stream = stream;
1743 idata.msgno = msgno;
1744 idata.rawno = mn_m2raw(msgmap, msgno);
1745 if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
1746 && (mc = mail_elt(stream, idata.rawno))){
1747 idata.size = mc->rfc822_size;
1748 index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
1750 else
1751 idata.bogus = 2;
1753 ice = (*format_index_line)(&idata);
1754 if(!ice)
1755 return(NULL);
1759 * If needed, reset the print_format strings so that they add up to
1760 * the right total width. The reset width functionality isn't implemented
1761 * for THRD_INDX() so we are just doing a complete rebuild in that
1762 * case. This is driven by the clear_ice() call in clear_index_cache_ent()
1763 * so it should never be the case that THRD_INDX() is true and only
1764 * widths_done needs to be fixed.
1766 if((!THRD_INDX() && ice->ifield && !ice->widths_done)){
1767 ICE_S *working_ice;
1768 IFIELD_S *ifield;
1769 INDEX_COL_S *cdesc;
1771 if(need_format_setup(stream))
1772 setup_header_widths(stream);
1774 if(THRD_INDX())
1775 working_ice = ice ? ice->tice : NULL;
1776 else
1777 working_ice = ice;
1779 if(working_ice){
1781 * First fix the ifield widths. The cdescs with nonzero widths
1782 * should correspond to the ifields that are defined.
1784 ifield = working_ice->ifield;
1785 for(cdesc = ps_global->index_disp_format;
1786 cdesc->ctype != iNothing && ifield; cdesc++){
1787 if(cdesc->width){
1788 if(cdesc->ctype != ifield->ctype){
1789 dprint((1, "build_header_work(%ld): cdesc->ctype=%d != ifield->ctype=%d NOT SUPPOSED TO HAPPEN!\n", msgno, (int) cdesc->ctype, (int) ifield->ctype));
1790 assert(0);
1793 ifield->width = cdesc->width;
1794 ifield = ifield->next;
1798 /* fix the print_format strings and widths */
1799 for(ifield = working_ice->ifield; ifield; ifield = ifield->next)
1800 set_ielem_widths_in_field(ifield);
1802 working_ice->widths_done = 1;
1806 if(THRD_INDX() && ice->tice)
1807 ice->tice->color_lookup_done = 1;
1810 * Look for a color for this line (and other lines in the current
1811 * view). This does a SEARCH for each role which has a color until
1812 * it finds a match. This will be satisfied by the c-client
1813 * cache created by the mail_fetch_overview above if it is a header
1814 * search.
1816 if(!THRD_INDX() && !ice->color_lookup_done){
1817 COLOR_PAIR *linecolor;
1818 SEARCHSET *ss, *s;
1819 ICE_S *ic;
1820 PAT_STATE *pstate = NULL;
1822 if(pico_usingcolor()){
1823 if(limit < 0L){
1824 if(THREADING() && sp_viewing_a_thread(stream)){
1825 for(visible = 0L, n = top_msgno;
1826 visible < msgcount && n <= mn_get_total(msgmap);
1827 n++){
1829 if(!get_lflag(stream, msgmap, n, MN_CHID2))
1830 break;
1832 if(!msgline_hidden(stream, msgmap, n, 0))
1833 visible++;
1837 else
1838 visible = mn_get_total(msgmap)
1839 - any_lflagged(msgmap, MN_HIDE|MN_CHID);
1841 limit = MIN(visible, msgcount);
1843 /* clear sequence bits */
1844 for(n = 1L; n <= stream->nmsgs; n++)
1845 if((mc = mail_elt(stream, n)) != NULL)
1846 mc->sequence = 0;
1848 cnt = i = 0;
1849 n = top_msgno;
1850 while(1){
1851 if(n >= msgno
1852 && n <= mn_get_total(msgmap)
1853 && (!(ic=fetch_ice(stream,(rawno = mn_m2raw(msgmap, n)))) || !ic->color_lookup_done)){
1855 if(rawno >= 1L && rawno <= stream->nmsgs
1856 && (mc = mail_elt(stream, rawno))){
1857 mc->sequence = 1;
1858 cnt++;
1862 if(++i >= limit)
1863 break;
1865 /* find next n which is visible */
1866 while(++n <= mn_get_total(msgmap)
1867 && msgline_hidden(stream, msgmap, n, 0))
1872 * Why is there a loop here? The first call to get_index_line_color
1873 * will return a set of messages which match one of the roles.
1874 * Then, we eliminate those messages from the search set and try
1875 * again. This time we'd get past that role and into a different
1876 * role. Because of that, we hang onto the state and don't reset
1877 * to the first_pattern on the second and subsequent times
1878 * through the loop, avoiding fruitless match_pattern calls in
1879 * get_index_line_color.
1880 * Before the first call, pstate should be set to NULL.
1882 while(cnt > 0L){
1883 ss = build_searchset(stream);
1884 if(ss){
1885 int colormatch;
1887 linecolor = NULL;
1888 colormatch = get_index_line_color(stream, ss, &pstate,
1889 &linecolor);
1892 * Assign this color to all matched msgno's and
1893 * turn off the sequence bit so we won't check
1894 * for them again.
1896 if(colormatch){
1897 for(s = ss; s; s = s->next){
1898 for(n = s->first; n <= s->last; n++){
1899 if(n >= 1L && n <= stream->nmsgs
1900 && (mc = mail_elt(stream, n))
1901 && mc->searched){
1902 cnt--;
1903 mc->sequence = 0;
1904 ic = fetch_ice(stream, n);
1905 if(ic){
1906 ic->color_lookup_done = 1;
1907 if(linecolor)
1908 ic->linecolor = new_color_pair(linecolor->fg,
1909 linecolor->bg);
1915 if(linecolor)
1916 free_color_pair(&linecolor);
1918 else{
1919 /* have to mark the rest of the lookups done */
1920 for(s = ss; s && cnt > 0; s = s->next){
1921 for(n = s->first; n <= s->last && cnt > 0; n++){
1922 if(n >= 1L && n <= stream->nmsgs
1923 && (mc = mail_elt(stream, n))
1924 && mc->sequence){
1925 cnt--;
1926 ic = fetch_ice(stream, n);
1927 if(ic)
1928 ic->color_lookup_done = 1;
1933 /* just making sure */
1934 cnt = 0L;
1937 mail_free_searchset(&ss);
1939 else
1940 cnt = 0L;
1943 ice = fetch_ice(stream, mn_m2raw(msgmap, msgno));
1945 else
1946 ice->color_lookup_done = 1;
1949 return(ice); /* Return formatted index data */
1954 day_of_week(struct date *d)
1956 int m, y;
1958 m = d->month;
1959 y = d->year;
1960 if(m <= 2){
1961 m += 9;
1962 y--;
1964 else
1965 m -= 3; /* March is month 0 */
1967 return((d->day+2+((7+31*m)/12)+y+(y/4)+(y/400)-(y/100))%7);
1971 static int daytab[2][13] = {
1972 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
1973 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
1977 day_of_year(struct date *d)
1979 int i, leap, doy;
1981 if(d->year <= 0 || d->month < 1 || d->month > 12)
1982 return(-1);
1984 doy = d->day;
1985 leap = (d->year%4 == 0 && d->year%100 != 0) || d->year%400 == 0;
1986 for(i = 1; i < d->month; i++)
1987 doy += daytab[leap][i];
1989 return(doy);
1994 /*----------------------------------------------------------------------
1995 Format a string summarizing the message header for index on screen
1997 Args: buffer -- buffer to place formatted line
1998 idata -- snot it takes to format the line
2000 Result: returns pointer given buffer IF entry formatted
2001 else NULL if there was a problem (but the buffer is
2002 still suitable for display)
2003 ----*/
2004 ICE_S *
2005 format_index_index_line(INDEXDATA_S *idata)
2007 char str[BIGWIDTH+1], to_us, status, *field,
2008 *p, *newsgroups;
2009 int i, collapsed = 0, start, fromfield;
2010 long l, score;
2011 BODY *body = NULL;
2012 MESSAGECACHE *mc;
2013 ADDRESS *addr, *toaddr, *ccaddr, *last_to;
2014 PINETHRD_S *thrd = NULL;
2015 INDEX_COL_S *cdesc = NULL;
2016 ICE_S *ice, **icep;
2017 IFIELD_S *ifield;
2018 IELEM_S *ielem;
2019 COLOR_PAIR *color = NULL;
2020 struct variable *vars = ps_global->vars;
2022 dprint((8, "=== format_index_line(msgno=%ld,rawno=%ld) ===\n",
2023 idata ? idata->msgno : -1, idata ? idata->rawno : -1));
2026 ice = fetch_ice(idata->stream, idata->rawno);
2027 if(!ice)
2028 return(NULL);
2030 free_ifield(&ice->ifield);
2033 * Operate on a temporary copy of ice. The reason for this
2034 * is that we may end up causing a pine_mail_fetchenvelope() call
2035 * (e.g., in to_us_symbol_for_thread()) that causes an mm_flags()
2036 * and mm_flags may do a clear_ice(), freeing the ice we are working
2037 * on out from under us. We try to fetch everything we need in
2038 * build_header_work() but c-client will short-circuit our request
2039 * if we already got the raw header for some reason. One possible
2040 * reason is a categorizer command in a filter. In that case
2041 * we still need a fetch fast to get the rest of the envelope data.
2043 ice = copy_ice(ice);
2045 /* is this a collapsed thread index line? */
2046 if(!idata->bogus && THREADING()){
2047 thrd = fetch_thread(idata->stream, idata->rawno);
2048 collapsed = thrd && thrd->next
2049 && get_lflag(idata->stream, NULL,
2050 idata->rawno, MN_COLL);
2053 /* calculate contents of the required fields */
2054 for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++)
2055 if(cdesc->width){
2056 memset(str, 0, sizeof(str));
2057 ifield = new_ifield(&ice->ifield);
2058 ifield->ctype = cdesc->ctype;
2059 ifield->width = cdesc->width;
2060 fromfield = 0;
2062 if(idata->bogus){
2063 if(cdesc->ctype == iMessNo)
2064 snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
2065 else if(idata->bogus < 2 && (cdesc->ctype == iSubject
2066 || cdesc->ctype == iShortSubject
2067 || cdesc->ctype == iSubjectText
2068 || cdesc->ctype == iSubjKey
2069 || cdesc->ctype == iShortSubjKey
2070 || cdesc->ctype == iSubjKeyText
2071 || cdesc->ctype == iSubjKeyInit
2072 || cdesc->ctype == iShortSubjKeyInit
2073 || cdesc->ctype == iSubjKeyInitText))
2074 snprintf(str, sizeof(str), "%s", _("[ No Message Text Available ]"));
2076 else
2077 switch(cdesc->ctype){
2078 case iStatus:
2079 to_us = status = ' ';
2080 if(collapsed){
2081 thrd = fetch_thread(idata->stream, idata->rawno);
2082 to_us = to_us_symbol_for_thread(idata->stream, thrd, 1);
2083 status = status_symbol_for_thread(idata->stream, thrd,
2084 cdesc->ctype);
2086 else{
2087 if(idata->rawno > 0L && idata->rawno <= idata->stream->nmsgs
2088 && (mc=mail_elt(idata->stream,idata->rawno)) && mc->flagged)
2089 to_us = '*'; /* simple */
2090 else if(!IS_NEWS(idata->stream)){
2091 for(addr = fetch_to(idata); addr; addr = addr->next)
2092 if(address_is_us(addr, ps_global)){
2093 ice->to_us = 1;
2094 if(to_us == ' ')
2095 to_us = '+';
2097 break;
2100 if(to_us != '+' && resent_to_us(idata)){
2101 ice->to_us = 1;
2102 if(to_us == ' ')
2103 to_us = '+';
2106 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
2107 for(addr = fetch_cc(idata); addr; addr = addr->next)
2108 if(address_is_us(addr, ps_global)){
2109 ice->cc_us = 1;
2110 to_us = '-';
2111 break;
2115 status = (!idata->stream || !IS_NEWS(idata->stream)
2116 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))
2117 ? 'N' : ' ';
2119 if(mc->seen)
2120 status = ' ';
2122 if(user_flag_is_set(idata->stream, idata->rawno, FORWARDED_FLAG))
2123 status = 'F';
2125 if(mc->answered)
2126 status = 'A';
2128 if(mc->deleted)
2129 status = 'D';
2132 snprintf(str, sizeof(str), "%c %c", to_us, status);
2134 ifield->leftadj = 1;
2135 for(i = 0; i < 3; i++){
2136 ielem = new_ielem(&ifield->ielem);
2137 ielem->freedata = 1;
2138 ielem->data = (char *) fs_get(2 * sizeof(char));
2139 ielem->data[0] = str[i];
2140 ielem->data[1] = '\0';
2141 ielem->datalen = 1;
2142 set_print_format(ielem, 1, ifield->leftadj);
2145 if(pico_usingcolor()){
2147 if(str[0] == '*'){
2148 if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
2149 ielem = ifield->ielem;
2150 ielem->freecolor = 1;
2151 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
2154 else if(str[0] == '+' || str[0] == '-'){
2155 if(VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
2156 ielem = ifield->ielem;
2157 ielem->freecolor = 1;
2158 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
2162 if(str[2] == 'D'){
2163 if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
2164 ielem = ifield->ielem->next->next;
2165 ielem->freecolor = 1;
2166 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
2169 else if(str[2] == 'A'){
2170 if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
2171 ielem = ifield->ielem->next->next;
2172 ielem->freecolor = 1;
2173 ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
2176 else if(str[2] == 'F'){
2177 if(VAR_IND_FWD_FORE_COLOR && VAR_IND_FWD_BACK_COLOR){
2178 ielem = ifield->ielem->next->next;
2179 ielem->freecolor = 1;
2180 ielem->color = new_color_pair(VAR_IND_FWD_FORE_COLOR, VAR_IND_FWD_BACK_COLOR);
2183 else if(str[2] == 'N'){
2184 if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
2185 ielem = ifield->ielem->next->next;
2186 ielem->freecolor = 1;
2187 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
2192 break;
2194 case iFStatus:
2195 case iIStatus:
2196 case iSIStatus:
2198 char new, answered, deleted, flagged;
2200 if(collapsed){
2201 thrd = fetch_thread(idata->stream, idata->rawno);
2202 to_us = to_us_symbol_for_thread(idata->stream, thrd, 0);
2204 else{
2205 to_us = ' ';
2206 if(!IS_NEWS(idata->stream)){
2207 for(addr = fetch_to(idata); addr; addr = addr->next)
2208 if(address_is_us(addr, ps_global)){
2209 to_us = '+';
2210 break;
2213 if(to_us == ' ' && resent_to_us(idata))
2214 to_us = '+';
2216 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
2217 for(addr = fetch_cc(idata); addr; addr = addr->next)
2218 if(address_is_us(addr, ps_global)){
2219 to_us = '-';
2220 break;
2225 new = answered = deleted = flagged = ' ';
2227 if(collapsed){
2228 unsigned long save_branch, cnt, tot_in_thrd;
2231 * Branch is a sibling, not part of the thread, so
2232 * don't consider it when displaying this line.
2234 save_branch = thrd->branch;
2235 thrd->branch = 0L;
2237 tot_in_thrd = count_flags_in_thread(idata->stream, thrd,
2238 F_NONE);
2240 cnt = count_flags_in_thread(idata->stream, thrd, F_DEL);
2241 if(cnt)
2242 deleted = (cnt == tot_in_thrd) ? 'D' : 'd';
2244 cnt = count_flags_in_thread(idata->stream, thrd, F_ANS);
2245 if(cnt)
2246 answered = (cnt == tot_in_thrd) ? 'A' : 'a';
2248 /* no lower case *, same thing for some or all */
2249 if(count_flags_in_thread(idata->stream, thrd, F_FLAG))
2250 flagged = '*';
2252 new = status_symbol_for_thread(idata->stream, thrd,
2253 cdesc->ctype);
2255 thrd->branch = save_branch;
2257 else{
2258 mc = (idata->rawno > 0L && idata->stream
2259 && idata->rawno <= idata->stream->nmsgs)
2260 ? mail_elt(idata->stream, idata->rawno) : NULL;
2261 if(mc && mc->valid){
2262 if(cdesc->ctype == iIStatus || cdesc->ctype == iSIStatus){
2263 if(mc->recent)
2264 new = mc->seen ? 'R' : 'N';
2265 else if (!mc->seen)
2266 new = 'U';
2268 else if(!mc->seen
2269 && (!IS_NEWS(idata->stream)
2270 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)))
2271 new = 'N';
2273 if(mc->answered)
2274 answered = 'A';
2276 if(mc->deleted)
2277 deleted = 'D';
2279 if(mc->flagged)
2280 flagged = '*';
2284 snprintf(str, sizeof(str), "%c %c%c%c%c", to_us, flagged, new,
2285 answered, deleted);
2287 if(cdesc->ctype == iSIStatus)
2288 start = 2;
2289 else
2290 start = 0;
2292 ifield->leftadj = 1;
2293 for(i = start; i < 6; i++){
2294 ielem = new_ielem(&ifield->ielem);
2295 ielem->freedata = 1;
2296 ielem->data = (char *) fs_get(2 * sizeof(char));
2297 ielem->data[0] = str[i];
2298 ielem->data[1] = '\0';
2299 ielem->datalen = 1;
2300 set_print_format(ielem, 1, ifield->leftadj);
2303 if(pico_usingcolor()){
2305 if(str[0] == '+' || str[0] == '-'){
2306 if(start == 0
2307 && VAR_IND_PLUS_FORE_COLOR
2308 && VAR_IND_PLUS_BACK_COLOR){
2309 ielem = ifield->ielem;
2310 ielem->freecolor = 1;
2311 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
2315 if(str[2] == '*'){
2316 if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
2317 if(start == 2)
2318 ielem = ifield->ielem;
2319 else
2320 ielem = ifield->ielem->next->next;
2322 ielem->freecolor = 1;
2323 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
2327 if(str[3] == 'N' || str[3] == 'n'){
2328 if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
2329 if(start == 2)
2330 ielem = ifield->ielem->next;
2331 else
2332 ielem = ifield->ielem->next->next->next;
2334 ielem->freecolor = 1;
2335 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
2338 else if(str[3] == 'R' || str[3] == 'r'){
2339 if(VAR_IND_REC_FORE_COLOR && VAR_IND_REC_BACK_COLOR){
2340 if(start == 2)
2341 ielem = ifield->ielem->next;
2342 else
2343 ielem = ifield->ielem->next->next->next;
2345 ielem->freecolor = 1;
2346 ielem->color = new_color_pair(VAR_IND_REC_FORE_COLOR, VAR_IND_REC_BACK_COLOR);
2349 else if(str[3] == 'U' || str[3] == 'u'){
2350 if(VAR_IND_UNS_FORE_COLOR && VAR_IND_UNS_BACK_COLOR){
2351 if(start == 2)
2352 ielem = ifield->ielem->next;
2353 else
2354 ielem = ifield->ielem->next->next->next;
2356 ielem->freecolor = 1;
2357 ielem->color = new_color_pair(VAR_IND_UNS_FORE_COLOR, VAR_IND_UNS_BACK_COLOR);
2361 if(str[4] == 'A' || str[4] == 'a'){
2362 if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
2363 if(start == 2)
2364 ielem = ifield->ielem->next->next;
2365 else
2366 ielem = ifield->ielem->next->next->next->next;
2368 ielem->freecolor = 1;
2369 ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
2373 if(str[5] == 'D' || str[5] == 'd'){
2374 if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
2375 if(start == 2)
2376 ielem = ifield->ielem->next->next->next;
2377 else
2378 ielem = ifield->ielem->next->next->next->next->next;
2380 ielem->freecolor = 1;
2381 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
2387 break;
2389 case iMessNo:
2391 * This is a special case. The message number is
2392 * generated on the fly in the painting routine.
2393 * But the data array is allocated here in case it
2394 * is useful for the paint routine.
2396 snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
2397 break;
2399 case iArrow:
2400 snprintf(str, sizeof(str), "%-*.*s", ifield->width, ifield->width, " ");
2401 if(VAR_IND_ARR_FORE_COLOR && VAR_IND_ARR_BACK_COLOR){
2402 ifield->leftadj = 1;
2403 ielem = new_ielem(&ifield->ielem);
2404 ielem->freedata = 1;
2405 ielem->data = cpystr(str);
2406 ielem->datalen = strlen(str);
2407 set_print_format(ielem, ifield->width, ifield->leftadj);
2408 ielem->freecolor = 1;
2409 ielem->color = new_color_pair(VAR_IND_ARR_FORE_COLOR,
2410 VAR_IND_ARR_BACK_COLOR);
2413 break;
2415 case iScore:
2416 score = get_msg_score(idata->stream, idata->rawno);
2417 if(score == SCORE_UNDEF){
2418 SEARCHSET *ss = NULL;
2420 ss = mail_newsearchset();
2421 ss->first = ss->last = (unsigned long) idata->rawno;
2422 if(ss){
2424 * This looks like it might be expensive to get the
2425 * score for each message when needed but it shouldn't
2426 * be too bad because we know we have the envelope
2427 * data cached. We can't calculate all of the scores
2428 * we need for the visible messages right here in
2429 * one fell swoop because we don't have the other
2430 * envelopes yet. And we can't get the other
2431 * envelopes at this point because we may be in
2432 * the middle of a c-client callback (pine_imap_env).
2433 * (Actually we could, because we know whether or
2434 * not we're in the callback because of the no_fetch
2435 * parameter.)
2436 * We have another problem if the score rules depend
2437 * on something other than envelope data. I guess they
2438 * only do that if they have an alltext (search the
2439 * text of the message) definition. So, we're going
2440 * to pass no_fetch to calculate_scores so that it
2441 * can return an error if we need the text data but
2442 * can't get it because of no_fetch. Setting bogus
2443 * will cause us to do the scores calculation later
2444 * when we are no longer in the callback.
2446 idata->bogus =
2447 (calculate_some_scores(idata->stream,
2448 ss, idata->no_fetch) == 0)
2449 ? 1 : 0;
2450 score = get_msg_score(idata->stream, idata->rawno);
2451 mail_free_searchset(&ss);
2455 snprintf(str, sizeof(str), "%ld", score != SCORE_UNDEF ? score : 0L);
2456 break;
2458 case iDate: case iMonAbb: case iLDate:
2459 case iSDate: case iSTime: case iSTime24:
2460 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
2461 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
2462 case iSDateIsoS: case iSDateIso:
2463 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
2464 case iSDateTime:
2465 case iSDateTimeIsoS: case iSDateTimeIso:
2466 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
2467 case iSDateTime24:
2468 case iSDateTimeIsoS24: case iSDateTimeIso24:
2469 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
2470 case iTimezone: case iYear: case iYear2Digit:
2471 case iRDate: case iDay: case iDay2Digit: case iMon2Digit:
2472 case iDayOrdinal: case iMon: case iMonLong:
2473 case iDayOfWeekAbb: case iDayOfWeek:
2474 case iPrefDate: case iPrefTime: case iPrefDateTime:
2475 date_str(fetch_date(idata), cdesc->ctype, 0, str, sizeof(str), cdesc->monabb_width);
2476 break;
2478 case iFromTo:
2479 case iFromToNotNews:
2480 case iFrom:
2481 case iAddress:
2482 case iMailbox:
2483 fromfield++;
2484 from_str(cdesc->ctype, idata, str, sizeof(str), ice);
2485 break;
2487 case iTo:
2488 if(((field = ((addr = fetch_to(idata))
2489 ? "To"
2490 : (addr = fetch_cc(idata))
2491 ? "Cc"
2492 : NULL))
2493 && !set_index_addr(idata, field, addr, NULL, BIGWIDTH, str))
2494 || !field)
2495 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2496 snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, newsgroups);
2498 break;
2500 case iCc:
2501 set_index_addr(idata, "Cc", fetch_cc(idata), NULL, BIGWIDTH, str);
2502 break;
2504 case iRecips:
2505 toaddr = fetch_to(idata);
2506 ccaddr = fetch_cc(idata);
2507 for(last_to = toaddr;
2508 last_to && last_to->next;
2509 last_to = last_to->next)
2512 /* point end of to list temporarily at cc list */
2513 if(last_to)
2514 last_to->next = ccaddr;
2516 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2518 if(last_to)
2519 last_to->next = NULL;
2521 break;
2523 case iSender:
2524 fromfield++;
2525 if((addr = fetch_sender(idata)) != NULL)
2526 set_index_addr(idata, "Sender", addr, NULL, BIGWIDTH, str);
2528 break;
2530 case iInit:
2531 {ADDRESS *addr;
2533 if((addr = fetch_from(idata)) && addr->personal){
2534 char *name, *initials = NULL;
2536 name = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
2537 SIZEOF_20KBUF, addr->personal);
2538 if(name == addr->personal){
2539 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
2540 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
2541 name = (char *) tmp_20k_buf;
2544 if(name && *name){
2545 initials = reply_quote_initials(name);
2546 snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, initials);
2551 break;
2553 case iSize:
2554 /* 0 ... 9999 */
2555 if((l = fetch_size(idata)) < 10*1000L)
2556 snprintf(str, sizeof(str), "(%lu)", l);
2557 /* 10K ... 999K */
2558 else if(l < 1000L*1000L - 1000L/2){
2559 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2560 snprintf(str, sizeof(str), "(%luK)", l);
2562 /* 1.0M ... 99.9M */
2563 else if(l < 1000L*100L*1000L - 100L*1000L/2){
2564 l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
2565 ? 1L : 0L);
2566 snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
2568 /* 100M ... 2000M */
2569 else if(l <= 2*1000L*1000L*1000L){
2570 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2571 ? 1L : 0L);
2572 snprintf(str, sizeof(str), "(%luM)", l);
2574 else
2575 snprintf(str, sizeof(str), "(HUGE!)");
2577 break;
2579 case iSizeComma:
2580 /* 0 ... 99,999 */
2581 if((l = fetch_size(idata)) < 100*1000L)
2582 snprintf(str, sizeof(str), "(%s)", comatose(l));
2583 /* 100K ... 9,999K */
2584 else if(l < 10L*1000L*1000L - 1000L/2){
2585 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2586 snprintf(str, sizeof(str), "(%sK)", comatose(l));
2588 /* 10.0M ... 999.9M */
2589 else if(l < 1000L*1000L*1000L - 100L*1000L/2){
2590 l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
2591 ? 1L : 0L);
2592 snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
2594 /* 1,000M ... 2,000M */
2595 else if(l <= 2*1000L*1000L*1000L){
2596 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2597 ? 1L : 0L);
2598 snprintf(str, sizeof(str), "(%sM)", comatose(l));
2600 else
2601 snprintf(str, sizeof(str), "(HUGE!)");
2603 break;
2605 case iSizeNarrow:
2606 /* 0 ... 999 */
2607 if((l = fetch_size(idata)) < 1000L)
2608 snprintf(str, sizeof(str), "(%lu)", l);
2609 /* 1K ... 99K */
2610 else if(l < 100L*1000L - 1000L/2){
2611 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2612 snprintf(str, sizeof(str), "(%luK)", l);
2614 /* .1M ... .9M */
2615 else if(l < 1000L*1000L - 100L*1000L/2){
2616 l = l/(100L*1000L) + (l%(100L*1000L) >= 100L*1000L/2
2617 ? 1L : 0L);
2618 snprintf(str, sizeof(str), "(.%luM)", l);
2620 /* 1M ... 99M */
2621 else if(l < 1000L*100L*1000L - 1000L*1000L/2){
2622 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2623 ? 1L : 0L);
2624 snprintf(str, sizeof(str), "(%luM)", l);
2626 /* .1G ... .9G */
2627 else if(l < 1000L*1000L*1000L - 100L*1000L*1000L/2){
2628 l = l/(100L*1000L*1000L) + (l%(100L*1000L*1000L) >=
2629 (100L*1000L*1000L/2) ? 1L : 0L);
2630 snprintf(str, sizeof(str), "(.%luG)", l);
2632 /* 1G ... 2G */
2633 else if(l <= 2*1000L*1000L*1000L){
2634 l = l/(1000L*1000L*1000L) + (l%(1000L*1000L*1000L) >=
2635 (1000L*1000L*1000L/2) ? 1L : 0L);
2636 snprintf(str, sizeof(str), "(%luG)", l);
2638 else
2639 snprintf(str, sizeof(str), "(HUGE!)");
2641 break;
2643 /* From Carl Jacobsen <carl@ucsd.edu> */
2644 case iKSize:
2645 l = fetch_size(idata);
2646 l = (l / 1024L) + (l % 1024L != 0 ? 1 : 0);
2648 if(l < 1024L) { /* 0k .. 1023k */
2649 snprintf(str, sizeof(str), "(%luk)", l);
2651 } else if (l < 100L * 1024L){ /* 1.0M .. 99.9M */
2652 snprintf(str, sizeof(str), "(%lu.M)", (l * 10L) / 1024L);
2653 if ((p = strchr(str, '.')) != NULL) {
2654 p--; p[1] = p[0]; p[0] = '.'; /* swap last digit & . */
2656 } else if (l <= 2L * 1024L * 1024L) { /* 100M .. 2048 */
2657 snprintf(str, sizeof(str), "(%luM)", l / 1024L);
2658 } else {
2659 snprintf(str, sizeof(str), "(HUGE!)");
2662 break;
2664 case iDescripSize:
2665 if((body = fetch_body(idata)) != NULL)
2666 switch(body->type){
2667 case TYPETEXT:
2669 mc = (idata->rawno > 0L && idata->stream
2670 && idata->rawno <= idata->stream->nmsgs)
2671 ? mail_elt(idata->stream, idata->rawno) : NULL;
2672 if(mc && mc->rfc822_size < 6000)
2673 snprintf(str, sizeof(str), "(short )");
2674 else if(mc && mc->rfc822_size < 25000)
2675 snprintf(str, sizeof(str), "(medium )");
2676 else if(mc && mc->rfc822_size < 100000)
2677 snprintf(str, sizeof(str), "(long )");
2678 else
2679 snprintf(str, sizeof(str), "(huge )");
2682 break;
2684 case TYPEMULTIPART:
2685 if(strucmp(body->subtype, "MIXED") == 0){
2686 int x;
2688 x = body->nested.part
2689 ? body->nested.part->body.type
2690 : TYPETEXT + 1000;
2691 switch(x){
2692 case TYPETEXT:
2693 if(body->nested.part->body.size.bytes < 6000)
2694 snprintf(str, sizeof(str), "(short+ )");
2695 else if(body->nested.part->body.size.bytes
2696 < 25000)
2697 snprintf(str, sizeof(str), "(medium+)");
2698 else if(body->nested.part->body.size.bytes
2699 < 100000)
2700 snprintf(str, sizeof(str), "(long+ )");
2701 else
2702 snprintf(str, sizeof(str), "(huge+ )");
2703 break;
2705 default:
2706 snprintf(str, sizeof(str), "(multi )");
2707 break;
2710 else if(strucmp(body->subtype, "DIGEST") == 0)
2711 snprintf(str, sizeof(str), "(digest )");
2712 else if(strucmp(body->subtype, "ALTERNATIVE") == 0)
2713 snprintf(str, sizeof(str), "(mul/alt)");
2714 else if(strucmp(body->subtype, "PARALLEL") == 0)
2715 snprintf(str, sizeof(str), "(mul/par)");
2716 else
2717 snprintf(str, sizeof(str), "(multi )");
2719 break;
2721 case TYPEMESSAGE:
2722 snprintf(str, sizeof(str), "(message)");
2723 break;
2725 case TYPEAPPLICATION:
2726 snprintf(str, sizeof(str), "(applica)");
2727 break;
2729 case TYPEAUDIO:
2730 snprintf(str, sizeof(str), "(audio )");
2731 break;
2733 case TYPEIMAGE:
2734 snprintf(str, sizeof(str), "(image )");
2735 break;
2737 case TYPEVIDEO:
2738 snprintf(str, sizeof(str), "(video )");
2739 break;
2741 default:
2742 snprintf(str, sizeof(str), "(other )");
2743 break;
2746 break;
2748 case iAtt:
2749 str[0] = SPACE;
2750 str[1] = '\0';
2751 if((body = fetch_body(idata)) &&
2752 body->type == TYPEMULTIPART &&
2753 strucmp(body->subtype, "ALTERNATIVE") != 0){
2754 PART *part;
2755 int atts = 0;
2757 part = body->nested.part; /* 1st part, don't count */
2758 while(part && part->next && atts < 10){
2759 atts++;
2760 part = part->next;
2763 if(atts > 9)
2764 str[0] = '*';
2765 else if(atts > 0)
2766 str[0] = '0' + atts;
2769 break;
2771 case iSubject:
2772 subj_str(idata, str, sizeof(str), NoKW, 0, 0, ice);
2773 break;
2775 case iShortSubject:
2776 subj_str(idata, str, sizeof(str), NoKW, 0, 1, ice);
2777 break;
2779 case iSubjectText:
2780 subj_str(idata, str, sizeof(str), NoKW, 1, 0, ice);
2781 break;
2783 case iSubjKey:
2784 subj_str(idata, str, sizeof(str), KW, 0, 0, ice);
2785 break;
2787 case iShortSubjKey:
2788 subj_str(idata, str, sizeof(str), KW, 0, 1, ice);
2789 break;
2791 case iSubjKeyText:
2792 subj_str(idata, str, sizeof(str), KW, 1, 0, ice);
2793 break;
2795 case iSubjKeyInit:
2796 subj_str(idata, str, sizeof(str), KWInit, 0, 0, ice);
2797 break;
2799 case iShortSubjKeyInit:
2800 subj_str(idata, str, sizeof(str), KWInit, 0, 1, ice);
2801 break;
2803 case iSubjKeyInitText:
2804 subj_str(idata, str, sizeof(str), KWInit, 1, 0, ice);
2805 break;
2807 case iOpeningText:
2808 case iOpeningTextNQ:
2809 if(idata->no_fetch)
2810 idata->bogus = 1;
2811 else{
2812 char *first_text;
2814 first_text = fetch_firsttext(idata, cdesc->ctype == iOpeningTextNQ);
2816 if(first_text){
2817 strncpy(str, first_text, BIGWIDTH);
2818 str[BIGWIDTH] = '\0';
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, *new_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;
3743 MESSAGECACHE *mc;
3744 PINELT_S *pelt;
3746 /* we cache the result we get from this function, so that we do not have to
3747 * refetch the text in case there is a change. We could cache in the envelope
3748 * but c-client does not have a special field for that, nor we want to use the
3749 * sparep pointer, since there could be other uses for sparep later, and even
3750 * if we add a pointer to the ENVELOPE structure, we would be caching the same
3751 * text twice (one in a private pointer, and the new pointer) and that would
3752 * not make sense. Instead we will use an elt for this
3755 if((mc = mail_elt(idata->stream, idata->rawno))
3756 && ((pelt = (PINELT_S *) mc->sparep) == NULL)){
3757 pelt = (PINELT_S *) fs_get(sizeof(PINELT_S));
3758 memset(pelt, 0, sizeof(PINELT_S));
3761 if(pelt && pelt->firsttext != NULL)
3762 return(pelt->firsttext);
3764 try_again:
3767 * Prevent wild prefetch, just get the one we're after.
3768 * Can we get this somehow in the overview call in build_header_work?
3770 ss = mail_newsearchset();
3771 ss->first = ss->last = idata->rawno;
3772 sset = (SEARCHSET **) mail_parameters(idata->stream,
3773 GET_FETCHLOOKAHEAD,
3774 (void *) idata->stream);
3775 if(sset)
3776 *sset = ss;
3778 if((env = pine_mail_fetchstructure(idata->stream, idata->rawno, &body)) != NULL){
3779 if(body){
3780 char *subtype = NULL;
3781 char *partno;
3783 if((body->type == TYPETEXT
3784 && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
3786 (body->type == TYPEMULTIPART && body->nested.part
3787 && body->nested.part->body.type == TYPETEXT
3788 && (subtype=body->nested.part->body.subtype)
3789 && ALLOWED_SUBTYPE(subtype))
3791 (body->type == TYPEMULTIPART && body->nested.part
3792 && body->nested.part->body.type == TYPEMULTIPART
3793 && body->nested.part->body.nested.part
3794 && body->nested.part->body.nested.part->body.type == TYPETEXT
3795 && (subtype=body->nested.part->body.nested.part->body.subtype)
3796 && ALLOWED_SUBTYPE(subtype))){
3798 if((so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
3799 char buf[1025], *p;
3800 unsigned char c;
3801 int success;
3802 int one_space_done = 0;
3804 if(partial_fetch_len == 0L){
3805 if(subtype && !strucmp(subtype, "html"))
3806 partial_fetch_len = 1024L;
3807 else if(subtype && !strucmp(subtype, "plain"))
3808 partial_fetch_len = delete_quotes ? 128L : 64L;
3809 else
3810 partial_fetch_len = 256L;
3813 if((body->type == TYPETEXT
3814 && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
3816 (body->type == TYPEMULTIPART && body->nested.part
3817 && (new_body = &body->nested.part->body) != NULL
3818 && body->nested.part->body.type == TYPETEXT
3819 && (subtype=body->nested.part->body.subtype)
3820 && ALLOWED_SUBTYPE(subtype)))
3821 partno = "1";
3822 else
3823 partno = "1.1";
3825 gf_set_so_writec(&pc, so);
3826 success = get_body_part_text(idata->stream, new_body ? new_body : body,
3827 idata->rawno,
3828 partno, partial_fetch_len, pc,
3829 NULL, NULL,
3830 GBPT_NOINTR | GBPT_PEEK |
3831 (delete_quotes ? GBPT_DELQUOTES : 0));
3832 gf_clear_so_writec(so);
3834 if(success){
3835 so_seek(so, 0L, 0);
3836 p = buf;
3837 while(p-buf < sizeof(buf)-1 && so_readc(&c, so)){
3838 /* delete leading whitespace */
3839 if(p == buf && isspace(c))
3841 /* and include just one space per run of whitespace */
3842 else if(isspace(c)){
3843 if(!one_space_done){
3844 *p++ = SPACE;
3845 one_space_done++;
3848 else{
3849 one_space_done = 0;
3850 *p++ = c;
3854 *p = '\0';
3856 if(p > buf){
3857 size_t l;
3859 l = strlen(buf);
3860 l += 100;
3861 firsttext = fs_get((l+1) * sizeof(char));
3862 firsttext[0] = '\0';
3863 iutf8ncpy(firsttext, buf, l);
3864 firsttext[l] = '\0';
3865 removing_trailing_white_space(firsttext);
3869 so_give(&so);
3871 /* first if means we didn't fetch all of the data */
3872 if(!(success > 1 && success < partial_fetch_len)){
3873 if(partial_fetch_len < 4096L
3874 && (!firsttext || utf8_width(firsttext) < 50)){
3875 if(firsttext)
3876 fs_give((void **) &firsttext);
3878 if(ss)
3879 mail_free_searchset(&ss);
3881 partial_fetch_len = 4096L;
3882 goto try_again;
3885 if(mc && pelt)
3886 pelt->firsttext = firsttext;
3892 if(ss)
3893 mail_free_searchset(&ss);
3895 return(firsttext);
3900 * fetch_date - called to get at the index entry's "Date:" field
3902 char *
3903 fetch_date(INDEXDATA_S *idata)
3905 if(idata->no_fetch) /* implies date is valid */
3906 return(idata->date);
3907 else if(idata->bogus)
3908 idata->bogus = 2;
3909 else{
3910 ENVELOPE *env;
3912 /* c-client call's just cache access at this point */
3913 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3914 return((char *) env->date);
3916 idata->bogus = 1;
3919 return(NULL);
3924 * fetch_header - called to get at the index entry's "Hdrname:" field
3926 char *
3927 fetch_header(INDEXDATA_S *idata, char *hdrname)
3929 if(idata->no_fetch)
3930 idata->bogus = 1;
3931 else if(idata->bogus)
3932 idata->bogus = 2;
3933 else{
3934 char *h, *p, *q, *decoded, *fields[2];
3935 size_t retsize, decsize;
3936 char *ret = NULL;
3937 unsigned char *decode_buf = NULL;
3939 fields[0] = hdrname;
3940 fields[1] = NULL;
3941 if(hdrname && hdrname[0]
3942 && (h = pine_fetchheader_lines(idata->stream, idata->rawno,
3943 NULL, fields))){
3945 if(strlen(h) < strlen(hdrname) + 1){
3946 fs_give((void **) &h);
3947 return(cpystr(""));
3950 /* skip "hdrname:" */
3951 for(p = h + strlen(hdrname) + 1;
3952 *p && isspace((unsigned char)*p); p++)
3955 decsize = (4 * strlen(p)) + 1;
3956 decode_buf = (unsigned char *) fs_get(decsize * sizeof(unsigned char));
3957 decoded = (char *) rfc1522_decode_to_utf8(decode_buf, decsize, p);
3958 p = decoded;
3960 retsize = strlen(decoded);
3961 q = ret = (char *) fs_get((retsize+1) * sizeof(char));
3963 *q = '\0';
3964 while(q-ret < retsize && *p){
3965 if(*p == '\015' || *p == '\012')
3966 p++;
3967 else if(*p == '\t'){
3968 *q++ = SPACE;
3969 p++;
3971 else
3972 *q++ = *p++;
3975 *q = '\0';
3977 fs_give((void **) &h);
3978 if(decode_buf)
3979 fs_give((void **) &decode_buf);
3981 return(ret);
3984 idata->bogus = 1;
3987 return(NULL);
3992 * fetch_size - called to get at the index entry's "size" field
3994 long
3995 fetch_size(INDEXDATA_S *idata)
3997 if(idata->no_fetch) /* implies size is valid */
3998 return(idata->size);
3999 else if(idata->bogus)
4000 idata->bogus = 2;
4001 else{
4002 MESSAGECACHE *mc;
4004 if(idata->stream && idata->rawno > 0L
4005 && idata->rawno <= idata->stream->nmsgs
4006 && (mc = mail_elt(idata->stream, idata->rawno)))
4007 return(mc->rfc822_size);
4009 idata->bogus = 1;
4012 return(0L);
4017 * fetch_body - called to get a the index entry's body structure
4019 BODY *
4020 fetch_body(INDEXDATA_S *idata)
4022 BODY *body;
4024 if(idata->bogus || idata->no_fetch){
4025 idata->bogus = 2;
4026 return(NULL);
4029 if(pine_mail_fetchstructure(idata->stream, idata->rawno, &body))
4030 return(body);
4032 idata->bogus = 1;
4033 return(NULL);
4038 * s is at least size width+1
4041 set_index_addr(INDEXDATA_S *idata,
4042 char *field,
4043 struct mail_address *addr,
4044 char *prefix,
4045 int width,
4046 char *s)
4048 ADDRESS *atmp;
4049 char *p, *stmp = NULL, *sptr;
4050 char *save_personal = NULL;
4051 int orig_width;
4053 s[0] = '\0';
4055 for(atmp = addr; idata->stream && atmp; atmp = atmp->next)
4056 if(atmp->host && atmp->host[0] == '.'){
4057 char *pref, *h, *fields[2];
4059 if(idata->no_fetch){
4060 idata->bogus = 1;
4061 return(TRUE);
4064 fields[0] = field;
4065 fields[1] = NULL;
4066 if((h = pine_fetchheader_lines(idata->stream, idata->rawno,
4067 NULL, fields)) != NULL){
4068 if(strlen(h) < strlen(field) + 1){
4069 p = h + strlen(h);
4071 else{
4072 /* skip "field:" */
4073 for(p = h + strlen(field) + 1;
4074 *p && isspace((unsigned char)*p); p++)
4078 orig_width = width;
4079 sptr = stmp = (char *) fs_get((orig_width+1) * sizeof(char));
4081 /* add prefix */
4082 for(pref = prefix; pref && *pref; pref++)
4083 if(width){
4084 *sptr++ = *pref;
4085 width--;
4087 else
4088 break;
4090 while(width--)
4091 if(*p == '\015' || *p == '\012')
4092 p++; /* skip CR LF */
4093 else if(!*p)
4094 *sptr++ = ' ';
4095 else if(*p == '\t'){
4096 *sptr++ = ' ';
4097 p++;
4099 else
4100 *sptr++ = *p++;
4102 *sptr = '\0'; /* tie off return string */
4104 if(stmp){
4105 iutf8ncpy(s, stmp, orig_width+1);
4106 s[orig_width] = '\0';
4107 fs_give((void **) &stmp);
4110 fs_give((void **) &h);
4111 return(TRUE);
4113 /* else fall thru and display what c-client gave us */
4116 if(addr && !addr->next /* only one address */
4117 && addr->host /* not group syntax */
4118 && addr->personal && addr->personal[0]){ /* there is a personal name */
4119 char buftmp[MAILTMPLEN];
4120 int l;
4122 if((l = prefix ? strlen(prefix) : 0) != 0)
4123 strncpy(s, prefix, width+1);
4125 snprintf(buftmp, sizeof(buftmp), "%s", addr->personal);
4126 p = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
4127 SIZEOF_20KBUF, buftmp);
4128 removing_leading_and_trailing_white_space(p);
4130 iutf8ncpy(s + l, p, width - l);
4132 s[width] = '\0';
4134 if(*(s+l))
4135 return(TRUE);
4136 else{
4137 save_personal = addr->personal;
4138 addr->personal = NULL;
4142 if(addr){
4143 char *a_string;
4144 int l;
4146 a_string = addr_list_string(addr, NULL, 0);
4147 if(save_personal)
4148 addr->personal = save_personal;
4150 if((l = prefix ? strlen(prefix) : 0) != 0)
4151 strncpy(s, prefix, width+1);
4153 iutf8ncpy(s + l, a_string, width - l);
4154 s[width] = '\0';
4156 fs_give((void **)&a_string);
4158 return(TRUE);
4161 if(save_personal)
4162 addr->personal = save_personal;
4164 return(FALSE);
4168 void
4169 index_data_env(INDEXDATA_S *idata, ENVELOPE *env)
4171 if(!env){
4172 idata->bogus = 2;
4173 return;
4176 idata->from = env->from;
4177 idata->to = env->to;
4178 idata->cc = env->cc;
4179 idata->sender = env->sender;
4180 idata->subject = env->subject;
4181 idata->date = (char *) env->date;
4182 idata->newsgroups = env->newsgroups;
4184 idata->valid_to = 1; /* signal that everything is here */
4185 idata->valid_cc = 1;
4186 idata->valid_sender = 1;
4187 idata->valid_news = 1;
4192 * Put a string representing the date into str. The source date is
4193 * in the string datesrc. The format to be used is in type.
4194 * Notice that type is an IndexColType, but really only a subset of
4195 * IndexColType types are allowed.
4197 * Args datesrc -- The source date string
4198 * type -- What type of output we want
4199 * v -- If set, variable width output is ok. (Oct 9 not Oct 9)
4200 * str -- Put the answer here.
4201 * str_len -- Length of str
4202 * monabb_width -- This is a hack to get dates to line up right. For
4203 * example, in French (but without accents here)
4204 * dec. 21
4205 * fevr. 23
4206 * mars 7
4207 * For this monabb_width would be 5.
4209 void
4210 date_str(char *datesrc, IndexColType type, int v, char *str, size_t str_len,
4211 int monabb_width)
4213 char year4[5], /* 4 digit year */
4214 yearzero[3], /* zero padded, 2-digit year */
4215 monzero[3], /* zero padded, 2-digit month */
4216 mon[3], /* 1 or 2-digit month, no pad */
4217 dayzero[3], /* zero padded, 2-digit day */
4218 day[3], /* 1 or 2-digit day, no pad */
4219 dayord[3], /* 2-letter ordinal label */
4220 monabb[10], /* 3-letter month abbrev */
4221 /* actually maybe not 3 if localized */
4222 hour24[3], /* 2-digit, 24 hour clock hour */
4223 hour12[3], /* 12 hour clock hour, no pad */
4224 minzero[3], /* zero padded, 2-digit minutes */
4225 timezone[6]; /* timezone, like -0800 or +... */
4226 int hr12;
4227 int curtype, lastmonthtype, lastyeartype, preftype;
4228 int sdatetimetype, sdatetime24type;
4229 struct date d;
4230 #define TODAYSTR N_("Today")
4232 curtype = (type == iCurDate ||
4233 type == iCurDateIso ||
4234 type == iCurDateIsoS ||
4235 type == iCurPrefDate ||
4236 type == iCurPrefDateTime ||
4237 type == iCurPrefTime ||
4238 type == iCurTime24 ||
4239 type == iCurTime12 ||
4240 type == iCurDay ||
4241 type == iCurDay2Digit ||
4242 type == iCurDayOfWeek ||
4243 type == iCurDayOfWeekAbb ||
4244 type == iCurMon ||
4245 type == iCurMon2Digit ||
4246 type == iCurMonLong ||
4247 type == iCurMonAbb ||
4248 type == iCurYear ||
4249 type == iCurYear2Digit);
4250 lastmonthtype = (type == iLstMon ||
4251 type == iLstMon2Digit ||
4252 type == iLstMonLong ||
4253 type == iLstMonAbb ||
4254 type == iLstMonYear ||
4255 type == iLstMonYear2Digit);
4256 lastyeartype = (type == iLstYear ||
4257 type == iLstYear2Digit);
4258 sdatetimetype = (type == iSDateTime ||
4259 type == iSDateTimeIso ||
4260 type == iSDateTimeIsoS ||
4261 type == iSDateTimeS1 ||
4262 type == iSDateTimeS2 ||
4263 type == iSDateTimeS3 ||
4264 type == iSDateTimeS4 ||
4265 type == iSDateTime24 ||
4266 type == iSDateTimeIso24 ||
4267 type == iSDateTimeIsoS24 ||
4268 type == iSDateTimeS124 ||
4269 type == iSDateTimeS224 ||
4270 type == iSDateTimeS324 ||
4271 type == iSDateTimeS424);
4272 sdatetime24type = (type == iSDateTime24 ||
4273 type == iSDateTimeIso24 ||
4274 type == iSDateTimeIsoS24 ||
4275 type == iSDateTimeS124 ||
4276 type == iSDateTimeS224 ||
4277 type == iSDateTimeS324 ||
4278 type == iSDateTimeS424);
4279 preftype = (type == iPrefDate ||
4280 type == iPrefDateTime ||
4281 type == iPrefTime ||
4282 type == iCurPrefDate ||
4283 type == iCurPrefDateTime ||
4284 type == iCurPrefTime);
4285 if(str_len > 0)
4286 str[0] = '\0';
4288 if(!(datesrc && datesrc[0]) && !(curtype || lastmonthtype || lastyeartype))
4289 return;
4291 if(curtype || lastmonthtype || lastyeartype){
4292 char dbuf[200];
4294 rfc822_date(dbuf);
4295 parse_date(dbuf, &d);
4297 if(lastyeartype)
4298 d.year--;
4299 else if(lastmonthtype){
4300 d.month--;
4301 if(d.month <= 0){
4302 d.month = 12;
4303 d.year--;
4307 else{
4308 parse_date(F_ON(F_DATES_TO_LOCAL,ps_global)
4309 ? convert_date_to_local(datesrc) : datesrc, &d);
4310 if(d.year == -1 || d.month == -1 || d.day == -1){
4311 sdatetimetype = 0;
4312 sdatetime24type = 0;
4313 preftype = 0;
4314 switch(type){
4315 case iSDate: case iSDateTime: case iSDateTime24:
4316 type = iS1Date;
4317 break;
4319 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
4320 case iPrefDate: case iPrefTime: case iPrefDateTime:
4321 type = iDateIso;
4322 break;
4324 case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
4325 type = iDateIsoS;
4326 break;
4328 case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
4329 type = iS1Date;
4330 break;
4332 case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
4333 type = iS1Date;
4334 break;
4336 case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
4337 type = iS1Date;
4338 break;
4340 case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
4341 type = iS1Date;
4342 break;
4344 default:
4345 break;
4350 /* some special ones to start with */
4351 if(preftype){
4352 struct tm tm, *tmptr = NULL;
4353 time_t now;
4356 * Make sure we get the right one if we're using current time.
4358 if(curtype){
4359 now = time((time_t *) 0);
4360 if(now != (time_t) -1)
4361 tmptr = localtime(&now);
4364 if(!tmptr){
4365 memset(&tm, 0, sizeof(tm));
4366 tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
4367 tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
4368 tm.tm_mday = MIN(MAX(d.day, 1), 31);
4369 tm.tm_hour = MIN(MAX(d.hour, 0), 23);
4370 tm.tm_min = MIN(MAX(d.minute, 0), 59);
4371 tm.tm_wday = MIN(MAX(d.wkday, 0), 6);
4372 tmptr = &tm;
4375 switch(type){
4376 case iPrefDate:
4377 case iCurPrefDate:
4378 our_strftime(str, str_len, "%x", tmptr);
4379 break;
4380 case iPrefTime:
4381 case iCurPrefTime:
4382 our_strftime(str, str_len, "%X", tmptr);
4383 break;
4384 case iPrefDateTime:
4385 case iCurPrefDateTime:
4386 our_strftime(str, str_len, "%c", tmptr);
4387 break;
4388 default:
4389 assert(0);
4390 break;
4393 return;
4396 strncpy(monabb, (d.month > 0 && d.month < 13)
4397 ? month_abbrev_locale(d.month) : "", sizeof(monabb));
4398 monabb[sizeof(monabb)-1] = '\0';
4400 strncpy(mon, (d.month > 0 && d.month < 13)
4401 ? int2string(d.month) : "", sizeof(mon));
4402 mon[sizeof(mon)-1] = '\0';
4404 strncpy(day, (d.day > 0 && d.day < 32)
4405 ? int2string(d.day) : "", sizeof(day));
4406 day[sizeof(day)-1] = '\0';
4408 strncpy(dayord,
4409 (d.day <= 0 || d.day > 31) ? "" :
4410 (d.day == 1 || d.day == 21 || d.day == 31) ? "st" :
4411 (d.day == 2 || d.day == 22 ) ? "nd" :
4412 (d.day == 3 || d.day == 23 ) ? "rd" : "th", sizeof(dayord));
4414 dayord[sizeof(dayord)-1] = '\0';
4416 strncpy(year4, (d.year >= 1000 && d.year < 10000)
4417 ? int2string(d.year) : "????", sizeof(year4));
4418 year4[sizeof(year4)-1] = '\0';
4420 if(d.year >= 0){
4421 if((d.year % 100) < 10){
4422 yearzero[0] = '0';
4423 strncpy(yearzero+1, int2string(d.year % 100), sizeof(yearzero)-1);
4425 else
4426 strncpy(yearzero, int2string(d.year % 100), sizeof(yearzero));
4428 else
4429 strncpy(yearzero, "??", sizeof(yearzero));
4431 yearzero[sizeof(yearzero)-1] = '\0';
4433 if(d.month > 0 && d.month < 10){
4434 monzero[0] = '0';
4435 strncpy(monzero+1, int2string(d.month), sizeof(monzero)-1);
4437 else if(d.month >= 10 && d.month <= 12)
4438 strncpy(monzero, int2string(d.month), sizeof(monzero));
4439 else
4440 strncpy(monzero, "??", sizeof(monzero));
4442 monzero[sizeof(monzero)-1] = '\0';
4444 if(d.day > 0 && d.day < 10){
4445 dayzero[0] = '0';
4446 strncpy(dayzero+1, int2string(d.day), sizeof(dayzero)-1);
4448 else if(d.day >= 10 && d.day <= 31)
4449 strncpy(dayzero, int2string(d.day), sizeof(dayzero));
4450 else
4451 strncpy(dayzero, "??", sizeof(dayzero));
4453 dayzero[sizeof(dayzero)-1] = '\0';
4455 hr12 = (d.hour == 0) ? 12 :
4456 (d.hour > 12) ? (d.hour - 12) : d.hour;
4457 hour12[0] = '\0';
4458 if(hr12 > 0 && hr12 <= 12)
4459 strncpy(hour12, int2string(hr12), sizeof(hour12));
4461 hour12[sizeof(hour12)-1] = '\0';
4463 hour24[0] = '\0';
4464 if(d.hour >= 0 && d.hour < 10){
4465 hour24[0] = '0';
4466 strncpy(hour24+1, int2string(d.hour), sizeof(hour24)-1);
4468 else if(d.hour >= 10 && d.hour < 24)
4469 strncpy(hour24, int2string(d.hour), sizeof(hour24));
4471 hour24[sizeof(hour24)-1] = '\0';
4473 minzero[0] = '\0';
4474 if(d.minute >= 0 && d.minute < 10){
4475 minzero[0] = '0';
4476 strncpy(minzero+1, int2string(d.minute), sizeof(minzero)-1);
4478 else if(d.minute >= 10 && d.minute <= 60)
4479 strncpy(minzero, int2string(d.minute), sizeof(minzero));
4481 minzero[sizeof(minzero)-1] = '\0';
4483 if(sizeof(timezone) > 5){
4484 if(d.hours_off_gmt <= 0){
4485 timezone[0] = '-';
4486 d.hours_off_gmt *= -1;
4487 d.min_off_gmt *= -1;
4489 else
4490 timezone[0] = '+';
4492 timezone[1] = '\0';
4493 if(d.hours_off_gmt >= 0 && d.hours_off_gmt < 10){
4494 timezone[1] = '0';
4495 strncpy(timezone+2, int2string(d.hours_off_gmt), sizeof(timezone)-2);
4497 else if(d.hours_off_gmt >= 10 && d.hours_off_gmt < 24)
4498 strncpy(timezone+1, int2string(d.hours_off_gmt), sizeof(timezone)-1);
4499 else{
4500 timezone[1] = '0';
4501 timezone[2] = '0';
4504 timezone[3] = '\0';
4505 if(d.min_off_gmt >= 0 && d.min_off_gmt < 10){
4506 timezone[3] = '0';
4507 strncpy(timezone+4, int2string(d.min_off_gmt), sizeof(timezone)-4);
4509 else if(d.min_off_gmt >= 10 && d.min_off_gmt <= 60)
4510 strncpy(timezone+3, int2string(d.min_off_gmt), sizeof(timezone)-3);
4511 else{
4512 timezone[3] = '0';
4513 timezone[4] = '0';
4516 timezone[5] = '\0';
4517 timezone[sizeof(timezone)-1] = '\0';
4520 switch(type){
4521 case iRDate:
4522 /* this one is not locale-specific */
4523 snprintf(str, str_len, "%s%s%s %s %s",
4524 (d.wkday != -1) ? day_abbrev(d.wkday) : "",
4525 (d.wkday != -1) ? ", " : "",
4526 day,
4527 (d.month > 0 && d.month < 13) ? month_abbrev(d.month) : "",
4528 year4);
4529 break;
4530 case iDayOfWeekAbb:
4531 case iCurDayOfWeekAbb:
4532 strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_abbrev_locale(d.wkday) : "", str_len);
4533 str[str_len-1] = '\0';
4534 break;
4535 case iDayOfWeek:
4536 case iCurDayOfWeek:
4537 strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_name_locale(d.wkday) : "", str_len);
4538 str[str_len-1] = '\0';
4539 break;
4540 case iYear:
4541 case iCurYear:
4542 case iLstYear:
4543 case iLstMonYear:
4544 strncpy(str, year4, str_len);
4545 break;
4546 case iDay2Digit:
4547 case iCurDay2Digit:
4548 strncpy(str, dayzero, str_len);
4549 break;
4550 case iMon2Digit:
4551 case iCurMon2Digit:
4552 case iLstMon2Digit:
4553 strncpy(str, monzero, str_len);
4554 break;
4555 case iYear2Digit:
4556 case iCurYear2Digit:
4557 case iLstYear2Digit:
4558 case iLstMonYear2Digit:
4559 strncpy(str, yearzero, str_len);
4560 break;
4561 case iTimezone:
4562 strncpy(str, timezone, str_len);
4563 break;
4564 case iDay:
4565 case iCurDay:
4566 strncpy(str, day, str_len);
4567 break;
4568 case iDayOrdinal:
4569 snprintf(str, str_len, "%s%s", day, dayord);
4570 break;
4571 case iMon:
4572 case iCurMon:
4573 case iLstMon:
4574 if(d.month > 0 && d.month <= 12)
4575 strncpy(str, int2string(d.month), str_len);
4577 break;
4578 case iMonAbb:
4579 case iCurMonAbb:
4580 case iLstMonAbb:
4581 strncpy(str, monabb, str_len);
4582 break;
4583 case iMonLong:
4584 case iCurMonLong:
4585 case iLstMonLong:
4586 strncpy(str, (d.month > 0 && d.month < 13)
4587 ? month_name_locale(d.month) : "", str_len);
4588 break;
4589 case iDate:
4590 case iCurDate:
4591 if(v)
4592 snprintf(str, str_len, "%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day);
4593 else{
4594 if(monabb_width > 0)
4595 utf8_snprintf(str, str_len, "%-*.*w %2s",
4596 monabb_width, monabb_width, monabb, day);
4597 else
4598 snprintf(str, str_len, "%s %2s", monabb, day);
4601 break;
4602 case iLDate:
4603 if(v)
4604 snprintf(str, str_len, "%s%s%s%s%s", monabb,
4605 (monabb[0] && day[0]) ? " " : "", day,
4606 ((monabb[0] || day[0]) && year4[0]) ? ", " : "",
4607 year4);
4608 else{
4609 if(monabb_width > 0)
4610 utf8_snprintf(str, str_len, "%-*.*w %2s%c %4s",
4611 monabb_width, monabb_width,
4612 monabb, day,
4613 (monabb[0] && day[0] && year4[0]) ? ',' : ' ', year4);
4614 else
4615 snprintf(str, str_len, "%s %2s%c %4s", monabb, day,
4616 (monabb[0] && day[0] && year4[0]) ? ',' : ' ',
4617 year4);
4620 break;
4621 case iS1Date:
4622 case iS2Date:
4623 case iS3Date:
4624 case iS4Date:
4625 case iDateIso:
4626 case iDateIsoS:
4627 case iCurDateIso:
4628 case iCurDateIsoS:
4629 if(monzero[0] == '?' && dayzero[0] == '?' &&
4630 yearzero[0] == '?')
4631 snprintf(str, str_len, "%8s", "");
4632 else{
4633 switch(type){
4634 case iS1Date:
4635 snprintf(str, str_len, "%2s/%2s/%2s",
4636 monzero, dayzero, yearzero);
4637 break;
4638 case iS2Date:
4639 snprintf(str, str_len, "%2s/%2s/%2s",
4640 dayzero, monzero, yearzero);
4641 break;
4642 case iS3Date:
4643 snprintf(str, str_len, "%2s.%2s.%2s",
4644 dayzero, monzero, yearzero);
4645 break;
4646 case iS4Date:
4647 snprintf(str, str_len, "%2s.%2s.%2s",
4648 yearzero, monzero, dayzero);
4649 break;
4650 case iDateIsoS:
4651 case iCurDateIsoS:
4652 snprintf(str, str_len, "%2s-%2s-%2s",
4653 yearzero, monzero, dayzero);
4654 break;
4655 case iDateIso:
4656 case iCurDateIso:
4657 snprintf(str, str_len, "%4s-%2s-%2s",
4658 year4, monzero, dayzero);
4659 break;
4660 default:
4661 break;
4665 break;
4666 case iTime24:
4667 case iCurTime24:
4668 snprintf(str, str_len, "%2s%c%2s",
4669 (hour24[0] && minzero[0]) ? hour24 : "",
4670 (hour24[0] && minzero[0]) ? ':' : ' ',
4671 (hour24[0] && minzero[0]) ? minzero : "");
4672 break;
4673 case iTime12:
4674 case iCurTime12:
4675 snprintf(str, str_len, "%s%c%2s%s",
4676 (hour12[0] && minzero[0]) ? hour12 : "",
4677 (hour12[0] && minzero[0]) ? ':' : ' ',
4678 (hour12[0] && minzero[0]) ? minzero : "",
4679 (hour12[0] && minzero[0] && d.hour < 12) ? "am" :
4680 (hour12[0] && minzero[0] && d.hour >= 12) ? "pm" :
4681 " ");
4682 break;
4683 case iSDate: case iSDateIso: case iSDateIsoS:
4684 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
4685 case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
4686 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
4687 case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
4688 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
4689 { struct date now, last_day;
4690 char dbuf[200];
4691 int msg_day_of_year, now_day_of_year, today;
4692 int diff, ydiff, last_day_of_year;
4694 rfc822_date(dbuf);
4695 parse_date(dbuf, &now);
4696 today = day_of_week(&now) + 7;
4698 if(today >= 0+7 && today <= 6+7){
4699 now_day_of_year = day_of_year(&now);
4700 msg_day_of_year = day_of_year(&d);
4701 ydiff = now.year - d.year;
4703 if(msg_day_of_year == -1)
4704 diff = -100;
4705 else if(ydiff == 0)
4706 diff = now_day_of_year - msg_day_of_year;
4707 else if(ydiff == 1){
4708 last_day = d;
4709 last_day.month = 12;
4710 last_day.day = 31;
4711 last_day_of_year = day_of_year(&last_day);
4713 diff = now_day_of_year +
4714 (last_day_of_year - msg_day_of_year);
4716 else if(ydiff == -1){
4717 last_day = now;
4718 last_day.month = 12;
4719 last_day.day = 31;
4720 last_day_of_year = day_of_year(&last_day);
4722 diff = -1 * (msg_day_of_year +
4723 (last_day_of_year - now_day_of_year));
4725 else if(ydiff > 1)
4726 diff = 100;
4727 else
4728 diff = -100;
4730 if(diff == 0)
4731 strncpy(str, _(TODAYSTR), str_len);
4732 else if(diff == 1)
4733 strncpy(str, _("Yesterday"), str_len);
4734 else if(diff > 1 && diff < 7)
4735 snprintf(str, str_len, "%s", day_name_locale((today - diff) % 7));
4736 else if(diff == -1)
4737 strncpy(str, _("Tomorrow"), str_len);
4738 else if(diff < -1 && diff > -7)
4739 snprintf(str, str_len, _("Next %.3s!"),
4740 day_name_locale((today - diff) % 7));
4741 else if(diff > 0
4742 && (ydiff == 0
4743 || (ydiff == 1 && 12 + now.month - d.month < 6))){
4744 if(v)
4745 snprintf(str, str_len, "%s%s%s", monabb,
4746 (monabb[0] && day[0]) ? " " : "", day);
4747 else{
4748 if(monabb_width > 0)
4749 utf8_snprintf(str, str_len, "%-*.*w %2s",
4750 monabb_width, monabb_width, monabb, day);
4751 else
4752 snprintf(str, str_len, "%s %2s", monabb, day);
4755 else{
4756 if(msg_day_of_year == -1 && (type == iSDate || type == iSDateTime))
4757 type = iSDateTimeIsoS;
4759 switch(type){
4760 case iSDate: case iSDateTime: case iSDateTime24:
4762 struct tm tm;
4764 memset(&tm, 0, sizeof(tm));
4765 tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
4766 tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
4767 tm.tm_mday = MIN(MAX(d.day, 1), 31);
4768 tm.tm_hour = MIN(MAX(d.hour, 0), 23);
4769 tm.tm_min = MIN(MAX(d.minute, 0), 59);
4770 our_strftime(str, str_len, "%x", &tm);
4773 break;
4774 case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
4775 if(v)
4776 snprintf(str, str_len, "%s/%s/%s%s", mon, day, yearzero,
4777 diff < 0 ? "!" : "");
4778 else
4779 snprintf(str, str_len, "%s%s/%s/%s%s",
4780 (mon[0] && mon[1]) ? "" : " ",
4781 mon, dayzero, yearzero,
4782 diff < 0 ? "!" : "");
4783 break;
4784 case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
4785 if(v)
4786 snprintf(str, str_len, "%s/%s/%s%s", day, mon, yearzero,
4787 diff < 0 ? "!" : "");
4788 else
4789 snprintf(str, str_len, "%s%s/%s/%s%s",
4790 (day[0] && day[1]) ? "" : " ",
4791 day, monzero, yearzero,
4792 diff < 0 ? "!" : "");
4793 break;
4794 case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
4795 if(v)
4796 snprintf(str, str_len, "%s.%s.%s%s", day, mon, yearzero,
4797 diff < 0 ? "!" : "");
4798 else
4799 snprintf(str, str_len, "%s%s.%s.%s%s",
4800 (day[0] && day[1]) ? "" : " ",
4801 day, monzero, yearzero,
4802 diff < 0 ? "!" : "");
4803 break;
4804 case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
4805 if(v)
4806 snprintf(str, str_len, "%s.%s.%s%s",
4807 yearzero, monzero, dayzero,
4808 diff < 0 ? "!" : "");
4809 else
4810 snprintf(str, str_len, "%s.%s.%s%s",
4811 yearzero, monzero, dayzero,
4812 diff < 0 ? "!" : "");
4813 break;
4814 case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
4815 snprintf(str, str_len, "%2s-%2s-%2s",
4816 yearzero, monzero, dayzero);
4817 break;
4818 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
4819 snprintf(str, str_len, "%4s-%2s-%2s",
4820 year4, monzero, dayzero);
4821 break;
4822 default:
4823 break;
4828 else{
4829 if(v)
4830 snprintf(str, str_len, "%s%s%s", monabb,
4831 (monabb[0] && day[0]) ? " " : "", day);
4832 else{
4833 if(monabb_width > 0)
4834 utf8_snprintf(str, str_len, "%-*.*w %2s",
4835 monabb_width, monabb_width, monabb, day);
4836 else
4837 snprintf(str, str_len, "%s %2s", monabb, day);
4842 break;
4844 default:
4845 break;
4848 str[str_len-1] = '\0';
4850 if(type == iSTime ||
4851 (sdatetimetype && !strcmp(str, _(TODAYSTR)))){
4852 struct date now, last_day;
4853 char dbuf[200], *Ddd, *ampm;
4854 int daydiff;
4856 str[0] = '\0';
4857 rfc822_date(dbuf);
4858 parse_date(dbuf, &now);
4860 /* Figure out if message date lands in the past week */
4862 /* (if message dated this month or last month...) */
4863 if((d.year == now.year && d.month >= now.month - 1) ||
4864 (d.year == now.year - 1 && d.month == 12 && now.month == 1)){
4866 daydiff = day_of_year(&now) - day_of_year(&d);
4869 * If msg in end of last year (and we're in first bit of "this"
4870 * year), diff will be backwards; fix up by adding number of days
4871 * in last year (usually 365, but occasionally 366)...
4873 if(d.year == now.year - 1){
4874 last_day = d;
4875 last_day.month = 12;
4876 last_day.day = 31;
4878 daydiff += day_of_year(&last_day);
4881 else
4882 daydiff = -100; /* comfortably out of range (of past week) */
4884 /* Build 2-digit hour and am/pm indicator, used below */
4886 if(d.hour >= 0 && d.hour < 24){
4887 snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12);
4888 ampm = (d.hour < 12) ? "am" : "pm";
4889 snprintf(hour24, sizeof(hour24), "%02d", d.hour);
4891 else{
4892 strncpy(hour12, "??", sizeof(hour12));
4893 hour12[sizeof(hour12)-1] = '\0';
4894 ampm = "__";
4895 strncpy(hour24, "??", sizeof(hour24));
4896 hour24[sizeof(hour24)-1] = '\0';
4899 /* Build date/time in str, in format similar to that used by w(1) */
4901 if(daydiff == 0){ /* If date is today, "HH:MMap" */
4902 if(d.minute >= 0 && d.minute < 60)
4903 snprintf(minzero, sizeof(minzero), "%02d", d.minute);
4904 else{
4905 strncpy(minzero, "??", sizeof(minzero));
4906 minzero[sizeof(minzero)-1] = '\0';
4909 snprintf(str, str_len, "%s:%s%s", sdatetime24type ? hour24 : hour12,
4910 minzero, sdatetime24type ? "" : ampm);
4912 else if(daydiff >= 1 && daydiff < 6){ /* If <1wk ago, "DddHHap" */
4914 if(d.month >= 1 && d.day >= 1 && d.year >= 0 &&
4915 d.month <= 12 && d.day <= 31 && d.year <= 9999)
4916 Ddd = day_abbrev_locale(day_of_week(&d));
4917 else
4918 Ddd = "???";
4920 snprintf(str, str_len, "%s%s%s", Ddd, hour12, ampm);
4922 else{ /* date is old or future, "ddMmmyy" */
4923 strncpy(monabb, (d.month >= 1 && d.month <= 12)
4924 ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
4925 monabb[sizeof(monabb)-1] = '\0';
4927 if(d.day >= 1 && d.day <= 31)
4928 snprintf(dayzero, sizeof(dayzero), "%02d", d.day);
4929 else{
4930 strncpy(dayzero, "??", sizeof(dayzero));
4931 dayzero[sizeof(dayzero)-1] = '\0';
4934 if(d.year >= 0 && d.year <= 9999)
4935 snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100);
4936 else{
4937 strncpy(yearzero, "??", sizeof(yearzero));
4938 yearzero[sizeof(yearzero)-1] = '\0';
4941 snprintf(str, str_len, "%s%s%s", dayzero, monabb, yearzero);
4944 if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */
4945 if(v)
4946 memmove(str, str + 1, strlen(str));
4947 else
4948 str[0] = ' ';
4952 /* This is like iSTime, but the format is different */
4953 if(type == iSTime24){
4954 struct date now, last_day;
4955 char dbuf[200], *Ddd, *ampm;
4956 int daydiff;
4958 str[0] = '\0';
4959 rfc822_date(dbuf);
4960 parse_date(dbuf, &now);
4962 /* (if message dated this month or last month...) */
4963 if((d.year == now.year && d.month >= now.month - 6) ||
4964 (d.year == now.year - 1 && d.month == 12 && now.month <= 6)){
4966 daydiff = day_of_year(&now) - day_of_year(&d);
4969 * If msg in end of last year (and we're in first bit of "this"
4970 * year), diff will be backwards; fix up by adding number of days
4971 * in last year (usually 365, but occasionally 366)...
4973 if(d.year == now.year - 1){
4974 last_day = d;
4975 last_day.month = 12;
4976 last_day.day = 31;
4978 daydiff += day_of_year(&last_day);
4981 else
4982 daydiff = 181; /* comfortably out of range (of past week) */
4984 /* Build 2-digit hour and am/pm indicator, used below */
4986 if(d.hour >= 0 && d.hour < 24){
4987 snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12);
4988 ampm = (d.hour < 12) ? "am" : "pm";
4989 snprintf(hour24, sizeof(hour24), "%02d", d.hour);
4991 else{
4992 strncpy(hour12, "??", sizeof(hour12));
4993 hour12[sizeof(hour12)-1] = '\0';
4994 ampm = "__";
4995 strncpy(hour24, "??", sizeof(hour24));
4996 hour24[sizeof(hour24)-1] = '\0';
4999 /* Build date/time in str, in format similar to that used by w(1) */
5001 if(daydiff >= 0 && daydiff <= 6){ /* If <1wk ago, "Ddd HH:mm" */
5003 if(d.month >= 1 && d.day >= 1 && d.year >= 0 &&
5004 d.month <= 12 && d.day <= 31 && d.year <= 9999)
5005 Ddd = day_abbrev_locale(day_of_week(&d));
5006 else
5007 Ddd = "???";
5009 if(d.minute >= 0 && d.minute < 60)
5010 snprintf(minzero, sizeof(minzero), "%02d", d.minute);
5011 else{
5012 strncpy(minzero, "??", sizeof(minzero));
5013 minzero[sizeof(minzero)-1] = '\0';
5016 snprintf(str, str_len, "%s %s:%s", Ddd, hour24, minzero);
5018 else if(daydiff < 180){ /* date is "Mmm dd" */
5019 strncpy(monabb, (d.month >= 1 && d.month <= 12)
5020 ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
5021 monabb[sizeof(monabb)-1] = '\0';
5023 if(d.day >= 1 && d.day <= 31)
5024 snprintf(dayzero, sizeof(dayzero), "%*d", 2, d.day);
5025 else{
5026 strncpy(dayzero, "??", sizeof(dayzero));
5027 dayzero[sizeof(dayzero)-1] = '\0';
5030 snprintf(str, str_len, "%s %s", monabb, dayzero);
5032 else { /* date is old or future, "dd/Mmm/yy" */
5033 strncpy(monabb, (d.month >= 1 && d.month <= 12)
5034 ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
5035 monabb[sizeof(monabb)-1] = '\0';
5037 if(d.day >= 1 && d.day <= 31)
5038 snprintf(dayzero, sizeof(dayzero), "%02d", d.day);
5039 else{
5040 strncpy(dayzero, "??", sizeof(dayzero));
5041 dayzero[sizeof(dayzero)-1] = '\0';
5044 if(d.year >= 0 && d.year <= 9999)
5045 snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100);
5046 else{
5047 strncpy(yearzero, "??", sizeof(yearzero));
5048 yearzero[sizeof(yearzero)-1] = '\0';
5051 snprintf(str, str_len, "%s/%s/%s", dayzero, monabb, yearzero);
5054 if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */
5055 if(v)
5056 memmove(str, str + 1, strlen(str));
5057 else
5058 str[0] = ' ';
5065 * Format a string representing the keywords into ice.
5067 * This needs to be done in UTF-8, which may be tricky since it isn't labelled.
5069 * Args idata -- which message?
5070 * kwtype -- keywords or kw initials
5071 * ice -- index cache entry for message
5073 void
5074 key_str(INDEXDATA_S *idata, SubjKW kwtype, ICE_S *ice)
5076 int firstone = 1;
5077 KEYWORD_S *kw;
5078 char *word;
5079 COLOR_PAIR *color = NULL;
5080 SPEC_COLOR_S *sc = ps_global->kw_colors;
5081 IELEM_S *ielem = NULL;
5082 IFIELD_S *ourifield = NULL;
5084 if(ice && ice->ifield){
5085 /* move to last ifield, the one we're working */
5086 for(ourifield = ice->ifield;
5087 ourifield && ourifield->next;
5088 ourifield = ourifield->next)
5092 if(!ourifield)
5093 return;
5095 if(kwtype == KWInit){
5096 for(kw = ps_global->keywords; kw; kw = kw->next){
5097 if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
5098 word = (kw->nick && kw->nick[0]) ? kw->nick :
5099 (kw->kw && kw->kw[0]) ? kw->kw : "";
5102 * Pick off the first initial. Since word is UTF-8 it may
5103 * take more than one byte for the first initial.
5106 if(word && word[0]){
5107 UCS ucs;
5108 unsigned long remaining_octets;
5109 unsigned char *inputp;
5111 remaining_octets = strlen(word);
5112 inputp = (unsigned char *) word;
5113 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
5114 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
5115 ielem = new_ielem(&ourifield->ielem);
5116 ielem->freedata = 1;
5117 ielem->datalen = (unsigned) (inputp - (unsigned char *) word);
5118 ielem->data = (char *) fs_get((ielem->datalen + 1) * sizeof(char));
5119 strncpy(ielem->data, word, ielem->datalen);
5120 ielem->data[ielem->datalen] = '\0';
5122 if(pico_usingcolor()
5123 && ((kw->nick && kw->nick[0]
5124 && (color=hdr_color(kw->nick,NULL,sc)))
5125 || (kw->kw && kw->kw[0]
5126 && (color=hdr_color(kw->kw,NULL,sc))))){
5127 ielem->color = color;
5128 color = NULL;
5133 if(color)
5134 free_color_pair(&color);
5138 else if(kwtype == KW){
5139 for(kw = ps_global->keywords; kw; kw = kw->next){
5140 if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
5142 if(!firstone){
5143 ielem = new_ielem(&ourifield->ielem);
5144 ielem->freedata = 1;
5145 ielem->data = cpystr(" ");
5146 ielem->datalen = 1;
5149 firstone = 0;
5151 word = (kw->nick && kw->nick[0]) ? kw->nick :
5152 (kw->kw && kw->kw[0]) ? kw->kw : "";
5154 if(word[0]){
5155 ielem = new_ielem(&ourifield->ielem);
5156 ielem->freedata = 1;
5157 ielem->data = cpystr(word);
5158 ielem->datalen = strlen(word);
5160 if(pico_usingcolor()
5161 && ((kw->nick && kw->nick[0]
5162 && (color=hdr_color(kw->nick,NULL,sc)))
5163 || (kw->kw && kw->kw[0]
5164 && (color=hdr_color(kw->kw,NULL,sc))))){
5165 ielem->color = color;
5166 color = NULL;
5170 if(color)
5171 free_color_pair(&color);
5177 * If we're coloring some of the fields then add a dummy field
5178 * at the end that can soak up the rest of the space after the last
5179 * colored keyword. Otherwise, the last one's color will extend to
5180 * the end of the field.
5182 if(pico_usingcolor()){
5183 ielem = new_ielem(&ourifield->ielem);
5184 ielem->freedata = 1;
5185 ielem->data = cpystr(" ");
5186 ielem->datalen = 1;
5189 ourifield->leftadj = 1;
5190 set_ielem_widths_in_field(ourifield);
5194 void
5195 prio_str(INDEXDATA_S *idata, IndexColType ctype, ICE_S *ice)
5197 IFIELD_S *ourifield = NULL;
5198 IELEM_S *ielem = NULL;
5199 char *hdrval;
5200 PRIORITY_S *p;
5201 int v;
5203 if(ice && ice->ifield){
5204 /* move to last ifield, the one we're working */
5205 for(ourifield = ice->ifield;
5206 ourifield && ourifield->next;
5207 ourifield = ourifield->next)
5211 if(!ourifield)
5212 return;
5214 hdrval = fetch_header(idata, PRIORITYNAME);
5216 if(hdrval && hdrval[0] && isdigit(hdrval[0])){
5217 v = atoi(hdrval);
5218 if(v >= 1 && v <= 5 && v != 3){
5220 ielem = new_ielem(&ourifield->ielem);
5221 ielem->freedata = 1;
5223 switch(ctype){
5224 case iPrio:
5225 ielem->data = (char *) fs_get(2 * sizeof(char));
5226 ielem->data[0] = hdrval[0];
5227 ielem->data[1] = '\0';
5228 break;
5230 case iPrioAlpha:
5231 for(p = priorities; p && p->desc; p++)
5232 if(p->val == v)
5233 break;
5235 if(p && p->desc)
5236 ielem->data = cpystr(p->desc);
5238 break;
5240 case iPrioBang:
5241 ielem->data = (char *) fs_get(2 * sizeof(char));
5242 ielem->data[0] = '\0';
5243 switch(v){
5244 case 1: case 2:
5245 ielem->data[0] = '!';
5246 break;
5248 case 4: case 5:
5250 * We could put a Unicode downarrow in here but
5251 * we have no way of knowing if the user's font
5252 * will have it (I think).
5254 ielem->data[0] = 'v';
5255 break;
5258 ielem->data[1] = '\0';
5260 break;
5262 default:
5263 alpine_panic("Unhandled case in prio_str");
5264 break;
5267 if(!ielem->data)
5268 ielem->data = cpystr("");
5270 if(ielem && ielem->data)
5271 ielem->datalen = strlen(ielem->data);
5273 if((v == 1 || v == 2) && pico_usingcolor()
5274 && ps_global->VAR_IND_HIPRI_FORE_COLOR
5275 && ps_global->VAR_IND_HIPRI_BACK_COLOR){
5276 ielem->type = eTypeCol;
5277 ielem->freecolor = 1;
5278 ielem->color = new_color_pair(ps_global->VAR_IND_HIPRI_FORE_COLOR, ps_global->VAR_IND_HIPRI_BACK_COLOR);
5280 else if((v == 4 || v == 5) && pico_usingcolor()
5281 && ps_global->VAR_IND_LOPRI_FORE_COLOR
5282 && ps_global->VAR_IND_LOPRI_BACK_COLOR){
5283 ielem->type = eTypeCol;
5284 ielem->freecolor = 1;
5285 ielem->color = new_color_pair(ps_global->VAR_IND_LOPRI_FORE_COLOR, ps_global->VAR_IND_LOPRI_BACK_COLOR);
5288 ourifield->leftadj = 1;
5289 set_ielem_widths_in_field(ourifield);
5292 fs_give((void **) &hdrval);
5297 void
5298 header_str(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok, ICE_S *ice)
5300 IFIELD_S *ourifield = NULL;
5301 IELEM_S *ielem = NULL;
5302 char *fieldval = NULL;
5304 if(ice && ice->ifield){
5305 /* move to last ifield, the one we're working */
5306 for(ourifield = ice->ifield;
5307 ourifield && ourifield->next;
5308 ourifield = ourifield->next)
5312 if(!ourifield)
5313 return;
5315 fieldval = get_fieldval(idata, hdrtok);
5317 if(fieldval){
5318 ielem = new_ielem(&ourifield->ielem);
5319 ielem->freedata = 1;
5320 ielem->data = fieldval;
5321 ielem->datalen = strlen(fieldval);
5322 fieldval = NULL;
5323 ourifield->leftadj = (hdrtok->adjustment == Left) ? 1 : 0;
5326 set_ielem_widths_in_field(ourifield);
5330 char *
5331 get_fieldval(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok)
5333 int sep, fieldnum;
5334 char *hdrval = NULL, *testval;
5335 char *fieldval = NULL, *firstval;
5336 char *retfieldval = NULL;
5338 if(!hdrtok)
5339 return(retfieldval);
5341 if(hdrtok && hdrtok->hdrname && hdrtok->hdrname[0])
5342 hdrval = fetch_header(idata, hdrtok ? hdrtok->hdrname : "");
5344 /* find start of fieldnum'th field */
5345 fieldval = hdrval;
5346 for(fieldnum = MAX(hdrtok->fieldnum-1, 0);
5347 fieldnum > 0 && fieldval && *fieldval; fieldnum--){
5349 firstval = NULL;
5350 for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
5351 testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
5352 if(testval && (!firstval || testval < firstval))
5353 firstval = testval;
5356 fieldval = firstval;
5357 if(fieldval && *fieldval)
5358 fieldval++;
5361 /* tie off end of field */
5362 if(fieldval && *fieldval && hdrtok->fieldnum > 0){
5363 firstval = NULL;
5364 for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
5365 testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
5366 if(testval && (!firstval || testval < firstval))
5367 firstval = testval;
5370 if(firstval)
5371 *firstval = '\0';
5374 if(!fieldval)
5375 fieldval = "";
5377 retfieldval = cpystr(fieldval);
5379 if(hdrval)
5380 fs_give((void **) &hdrval);
5382 return(retfieldval);
5386 long
5387 scorevalfrommsg(MAILSTREAM *stream, MsgNo rawno, HEADER_TOK_S *hdrtok, int no_fetch)
5389 INDEXDATA_S idata;
5390 MESSAGECACHE *mc;
5391 char *fieldval = NULL;
5392 long retval = 0L;
5394 memset(&idata, 0, sizeof(INDEXDATA_S));
5395 idata.stream = stream;
5396 idata.no_fetch = no_fetch;
5397 idata.msgno = mn_raw2m(sp_msgmap(stream), rawno);
5398 idata.rawno = rawno;
5399 if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
5400 && (mc = mail_elt(stream, idata.rawno))){
5401 idata.size = mc->rfc822_size;
5402 index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
5404 else
5405 idata.bogus = 2;
5407 fieldval = get_fieldval(&idata, hdrtok);
5409 if(fieldval){
5410 retval = atol(fieldval);
5411 fs_give((void **) &fieldval);
5414 return(retval);
5418 * Put a string representing the subject into str. Idata tells us which
5419 * message we are referring to.
5421 * This means we should ensure that all data ends up being UTF-8 data.
5422 * That covers the data in ice ielems and str.
5424 * Args idata -- which message?
5425 * str -- destination buffer
5426 * strsize -- size of str buffer
5427 * kwtype -- prepend keywords or kw initials before the subject
5428 * opening -- add first text from body of message if there's room
5429 * shorten -- if on, shorten the subject.
5430 * ice -- index cache entry for message
5432 void
5433 subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int opening, int shorten, ICE_S *ice)
5435 char *subject, *origsubj, *origstr, *rawsubj, *sptr = NULL;
5436 char *p, *border, *q = NULL, *free_subj = NULL;
5437 char *sp;
5438 size_t len;
5439 int width = -1;
5440 int depth = 0, mult = 2;
5441 int save;
5442 int do_subj = 0, truncated_tree = 0;
5443 PINETHRD_S *thd, *thdorig;
5444 IELEM_S *ielem = NULL, *subjielem = NULL;
5445 IFIELD_S *ourifield = NULL;
5447 if(strsize <= 0)
5448 return;
5451 * If we need the data at the start of the message and we're in
5452 * a c-client callback, defer the data lookup until later.
5454 if(opening && idata->no_fetch){
5455 idata->bogus = 1;
5456 return;
5459 if(ice && ice->ifield){
5460 /* move to last ifield, the one we're working on */
5461 for(ourifield = ice->ifield;
5462 ourifield && ourifield->next;
5463 ourifield = ourifield->next)
5467 str[0] = str[strsize-1] = '\0';
5468 origstr = str;
5469 rawsubj = fetch_subject(idata);
5470 if(!rawsubj)
5471 rawsubj = "";
5474 * Before we do anything else, decode the character set in the subject and
5475 * work with the result.
5477 sp = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
5478 SIZEOF_20KBUF, rawsubj);
5480 len = strlen(sp);
5481 len += 100; /* for possible charset, escaped characters */
5482 origsubj = fs_get((len+1) * sizeof(unsigned char));
5483 origsubj[0] = '\0';
5485 iutf8ncpy(origsubj, sp, len);
5487 origsubj[len] = '\0';
5488 replace_tabs_by_space(origsubj);
5489 removing_trailing_white_space(origsubj);
5492 * origsubj is the original subject but it has been decoded. We need
5493 * to free it at the end of this routine.
5496 if(shorten)
5497 shorten_subject(origsubj);
5500 * prepend_keyword will put the keyword stuff before the subject
5501 * and split the subject up into its colored parts in subjielem.
5502 * Subjielem is a local ielem which will have to be fit into the
5503 * real ifield->ielem later. The print_format strings in subjielem will
5504 * not be filled in by prepend_keyword because of the fact that we
5505 * may have to adjust things for threading below.
5506 * We use subjielem in case we want to insert some threading information
5507 * at the front of the subject.
5509 if(kwtype == KW || kwtype == KWInit){
5510 subject = prepend_keyword_subject(idata->stream, idata->rawno,
5511 origsubj, kwtype,
5512 ourifield ? &subjielem : NULL,
5513 ps_global->VAR_KW_BRACES);
5514 free_subj = subject;
5516 else{
5517 subject = origsubj;
5518 if(ourifield){
5519 subjielem = new_ielem(&subjielem);
5520 subjielem->type = eTypeCol;
5521 subjielem->freedata = 1;
5522 subjielem->data = cpystr(subject);
5523 subjielem->datalen = strlen(subject);
5524 if(pico_usingcolor()
5525 && ps_global->VAR_IND_SUBJ_FORE_COLOR
5526 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
5527 subjielem->freecolor = 1;
5528 subjielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
5534 * This space is here so that if the subject does
5535 * not extend all the way to the end of the field then
5536 * we'll switch the color back and paint the rest of the
5537 * field in the Normal color or the index line color.
5539 if(!opening){
5540 ielem = new_ielem(&subjielem);
5541 ielem->freedata = 1;
5542 ielem->data = cpystr(" ");
5543 ielem->datalen = 1;
5546 if(!subject)
5547 subject = "";
5549 if(THREADING()
5550 && (ps_global->thread_disp_style == THREAD_STRUCT
5551 || ps_global->thread_disp_style == THREAD_MUTTLIKE
5552 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ1
5553 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ2)){
5556 * Why do we want to truncate the subject and from strs?
5557 * It's so we can put the [5] thread count things in below when
5558 * we are threading and the thread structure runs off the right
5559 * hand edge of the screen. This routine doesn't know that it
5560 * is running off the edge unless it knows the actual width
5561 * that we have to draw in.
5563 if(pith_opt_truncate_sfstr
5564 && (*pith_opt_truncate_sfstr)()
5565 && ourifield
5566 && ourifield->width > 0)
5567 width = ourifield->width;
5569 if(width < 0)
5570 width = strsize-1;
5572 width = MIN(width, strsize-1);
5575 * We're counting on the fact that this initial part of the
5576 * string is ascii and we have one octet per character and
5577 * characters are width 1 on the screen.
5579 border = str + width;
5581 thdorig = thd = fetch_thread(idata->stream, idata->rawno);
5583 if(pith_opt_condense_thread_cue)
5584 width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
5585 thd && thd->next
5586 && get_lflag(idata->stream,
5587 NULL,idata->rawno,
5588 MN_COLL));
5591 * width is < available strsize and
5592 * border points to something less than or equal
5593 * to the end of the buffer.
5596 sptr = str;
5598 if(thd)
5599 while(thd->parent &&
5600 (thd = fetch_thread(idata->stream, thd->parent)))
5601 depth++;
5603 if(depth > 0){
5604 if(ps_global->thread_disp_style == THREAD_INDENT_SUBJ1)
5605 mult = 1;
5607 sptr += (mult*depth);
5608 for(thd = thdorig, p = str + mult*depth - mult;
5609 thd && thd->parent && p >= str;
5610 thd = fetch_thread(idata->stream, thd->parent), p -= mult){
5611 if(p + mult >= border && !q){
5612 if(width >= 4 && depth < 100){
5613 snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
5614 q = str + width-4;
5616 else if(width >= 5 && depth < 1000){
5617 snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
5618 q = str + width-5;
5620 else{
5621 snprintf(str, width+1, "%s", repeat_char(width, '.'));
5622 q = str;
5625 border = q;
5626 truncated_tree++;
5629 if(p < border){
5630 p[0] = ' ';
5631 if(p + 1 < border)
5632 p[1] = ' ';
5634 if(ps_global->thread_disp_style == THREAD_STRUCT
5635 || ps_global->thread_disp_style == THREAD_MUTTLIKE){
5637 * WARNING!
5638 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
5639 * is ascii.
5641 if(thd == thdorig && !thd->branch)
5642 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
5643 else if(thd == thdorig || thd->branch)
5644 p[0] = '|';
5646 if(p + 1 < border && thd == thdorig)
5647 p[1] = '-';
5653 if(sptr && !truncated_tree){
5655 * Look to see if the subject is the same as the previous
5656 * message in the thread, if any. If it is the same, don't
5657 * reprint the subject.
5659 * Note that when we're prepending keywords to the subject,
5660 * and the user changes a keyword, we do invalidate
5661 * the index cache for that message but we don't go to the
5662 * trouble of invalidating the index cache for the the child
5663 * of that node in the thread, so the MUTT subject line
5664 * display for the child may be wrong. That is, it may show
5665 * it is the same as this subject even though it no longer
5666 * is, or vice versa.
5668 if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
5669 if(depth == 0)
5670 do_subj++;
5671 else{
5672 if(thdorig->parent &&
5673 (thd = fetch_thread(idata->stream, thdorig->parent))
5674 && thd->rawno){
5675 char *this_orig = NULL,
5676 *prev_orig = NULL,
5677 *free_prev_orig = NULL,
5678 *this_prep = NULL, /* includes prepend */
5679 *prev_prep = NULL;
5680 ENVELOPE *env;
5681 mailcache_t mc;
5682 SORTCACHE *sc = NULL;
5684 /* get the stripped subject of previous message */
5685 mc = (mailcache_t) mail_parameters(NIL, GET_CACHE, NIL);
5686 if(mc)
5687 sc = (*mc)(idata->stream, thd->rawno, CH_SORTCACHE);
5689 if(sc && sc->subject)
5690 prev_orig = sc->subject;
5691 else{
5692 char *stripthis;
5694 env = pine_mail_fetchenvelope(idata->stream,
5695 thd->rawno);
5696 stripthis = (env && env->subject)
5697 ? env->subject : "";
5699 mail_strip_subject(stripthis, &prev_orig);
5701 free_prev_orig = prev_orig;
5704 mail_strip_subject(rawsubj, &this_orig);
5706 if(kwtype == KW || kwtype == KWInit){
5707 prev_prep = prepend_keyword_subject(idata->stream,
5708 thd->rawno,
5709 prev_orig,
5710 kwtype, NULL,
5711 ps_global->VAR_KW_BRACES);
5713 this_prep = prepend_keyword_subject(idata->stream,
5714 idata->rawno,
5715 this_orig,
5716 kwtype, NULL,
5717 ps_global->VAR_KW_BRACES);
5719 if((this_prep || prev_prep)
5720 && ((this_prep && !prev_prep)
5721 || (prev_prep && !this_prep)
5722 || strucmp(this_prep, prev_prep)))
5723 do_subj++;
5725 else{
5726 if((this_orig || prev_orig)
5727 && ((this_orig && !prev_orig)
5728 || (prev_orig && !this_orig)
5729 || strucmp(this_orig, prev_orig)))
5730 do_subj++;
5734 * If some of the thread is zoomed out of view, we
5735 * want to display the subject of the first one that
5736 * is in view. If any of the parents or grandparents
5737 * etc of this message are visible, then we don't
5738 * need to worry about it. If all of the parents have
5739 * been zoomed away, then this is the first one.
5741 * When you're looking at a particular case where
5742 * some of the messages of a thread are selected it
5743 * seems like we should look at not only our
5744 * direct parents, but the siblings of the parent
5745 * too. But that's not really correct, because those
5746 * siblings are basically the starts of different
5747 * branches, separate from our branch. They could
5748 * have their own subjects, for example. This will
5749 * give us cases where it looks like we are showing
5750 * the subject too much, but it will be correct!
5752 * In zoom_index() we clear_index_cache_ent for
5753 * some lines which have subjects which might become
5754 * visible when we zoom, and also in set_lflags
5755 * where we might change subjects by unselecting
5756 * something when zoomed.
5758 if(!do_subj){
5759 while(thd){
5760 if(!msgline_hidden(idata->stream,
5761 sp_msgmap(idata->stream),
5762 mn_raw2m(sp_msgmap(idata->stream),
5763 (long) thd->rawno),
5764 0)){
5765 break; /* found a visible parent */
5768 if(thd && thd->parent)
5769 thd = fetch_thread(idata->stream,thd->parent);
5770 else
5771 thd = NULL;
5774 if(!thd) /* none were visible */
5775 do_subj++;
5778 if(this_orig)
5779 fs_give((void **) &this_orig);
5781 if(this_prep)
5782 fs_give((void **) &this_prep);
5784 if(free_prev_orig)
5785 fs_give((void **) &free_prev_orig);
5787 if(prev_prep)
5788 fs_give((void **) &prev_prep);
5790 else
5791 do_subj++;
5794 else
5795 do_subj++;
5797 if(do_subj){
5799 * We don't need to worry about truncating to width
5800 * here. If we go over the right hand edge it will be
5801 * truncated.
5803 strsize -= (sptr - str);
5805 strncpy(sptr, subject, strsize-1);
5806 sptr[strsize-1] = '\0';
5808 else if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
5809 strsize -= (sptr - str);
5811 if(strsize > 0){
5812 sptr[0] = '>';
5813 sptr++;
5817 * We decided we don't need the subject so we'd better
5818 * eliminate subjielem.
5820 free_ielem(&subjielem);
5823 else
5824 free_ielem(&subjielem); /* no room for actual subject */
5826 if(ourifield && sptr && sptr > origstr){
5827 ielem = new_ielem(&ourifield->ielem);
5828 ielem->type = eThreadInfo;
5829 ielem->freedata = 1;
5830 save = *sptr;
5831 *sptr = '\0';
5832 ielem->data = cpystr(origstr);
5833 ielem->datalen = strlen(origstr);
5834 *sptr = save;
5837 else{
5839 * Not much to do for the non-threading case. Just copy the
5840 * subject we have so far into str and truncate it.
5842 strncpy(str, subject, strsize-1);
5843 str[strsize-1] = '\0';
5846 if(ourifield){
5848 * We need to add subjielem to the end of the ourifield->ielem list.
5850 if(subjielem){
5851 if(ourifield->ielem){
5852 for(ielem = ourifield->ielem;
5853 ielem && ielem->next; ielem = ielem->next)
5856 ielem->next = subjielem;
5858 else
5859 ourifield->ielem = subjielem;
5862 ourifield->leftadj = 1;
5865 if(opening && ourifield){
5866 IELEM_S *ftielem = NULL;
5867 size_t len;
5868 char *first_text;
5870 first_text = fetch_firsttext(idata, 0);
5872 if(first_text){
5873 char sep[200];
5874 int seplen;
5876 strncpy(sep, ps_global->VAR_OPENING_SEP ? ps_global->VAR_OPENING_SEP : " - ",
5877 sizeof(sep));
5878 sep[sizeof(sep)-1] = '\0';
5879 removing_double_quotes(sep);
5880 seplen = strlen(sep);
5882 ftielem = new_ielem(&ftielem);
5883 ftielem->type = eTypeCol;
5884 ftielem->freedata = 1;
5885 len = strlen(first_text) + seplen;
5886 ftielem->data = (char *) fs_get((len + 1) * sizeof(char));
5888 strncpy(ftielem->data, sep, seplen);
5889 strncpy(ftielem->data+seplen, first_text, len+1-seplen);
5890 ftielem->data[len] = '\0';
5892 ftielem->datalen = strlen(ftielem->data);
5894 if(ftielem){
5895 if(pico_usingcolor()
5896 && ps_global->VAR_IND_OP_FORE_COLOR
5897 && ps_global->VAR_IND_OP_BACK_COLOR){
5898 ftielem->freecolor = 1;
5899 ftielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR, ps_global->VAR_IND_OP_BACK_COLOR);
5902 * This space is here so that if the opening text does
5903 * not extend all the way to the end of the field then
5904 * we'll switch the color back and paint the rest of the
5905 * field in the Normal color or the index line color.
5907 ielem = new_ielem(&ftielem);
5908 ielem->freedata = 1;
5909 ielem->data = cpystr(" ");
5910 ielem->datalen = 1;
5913 if(ourifield->ielem){
5914 for(ielem = ourifield->ielem;
5915 ielem && ielem->next; ielem = ielem->next)
5918 ielem->next = ftielem;
5920 else
5921 ourifield->ielem = ftielem;
5924 ourifield->leftadj = 1;
5928 if(ourifield)
5929 set_ielem_widths_in_field(ourifield);
5931 if(origsubj)
5932 fs_give((void **) &origsubj);
5934 if(free_subj)
5935 fs_give((void **) &free_subj);
5940 * Returns an allocated string which is the passed in subject with a
5941 * list of keywords prepended.
5943 * If kwtype == KW you will end up with
5945 * {keyword1 keyword2} subject
5947 * (actually, keyword nicknames will be used instead of the actual keywords
5948 * in the case that the user defined nicknames)
5950 * If kwtype == KWInit you get
5952 * {AB} subject
5954 * where A is the first letter of the first keyword and B is the first letter
5955 * of the second defined keyword. No space between them. There could be more
5956 * than two.
5958 * If an ielemp is passed in it will be filled out with the data and colors
5959 * of the pieces of the subject but the print_format strings will not
5960 * be set.
5962 char *
5963 prepend_keyword_subject(MAILSTREAM *stream, long int rawno, char *subject,
5964 SubjKW kwtype, IELEM_S **ielemp, char *braces)
5966 char *p, *next_piece, *retsubj = NULL, *str;
5967 char *left_brace = NULL, *right_brace = NULL;
5968 size_t len;
5969 int some_set = 0, save;
5970 IELEM_S *ielem;
5971 KEYWORD_S *kw;
5972 COLOR_PAIR *color = NULL;
5973 SPEC_COLOR_S *sc = ps_global->kw_colors;
5975 if(!subject)
5976 subject = "";
5978 if(braces && *braces)
5979 get_pair(braces, &left_brace, &right_brace, 1, 0);
5981 len = (left_brace ? strlen(left_brace) : 0) +
5982 (right_brace ? strlen(right_brace) : 0);
5984 if(stream && rawno >= 0L && rawno <= stream->nmsgs){
5985 for(kw = ps_global->keywords; kw; kw = kw->next)
5986 if(user_flag_is_set(stream, rawno, kw->kw)){
5987 if(kwtype == KW){
5988 if(some_set)
5989 len++; /* space between keywords */
5991 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5992 len += strlen(str);
5994 else if(kwtype == KWInit){
5995 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5996 /* interested in only the first UTF-8 initial */
5997 if(str && str[0]){
5998 UCS ucs;
5999 unsigned long remaining_octets;
6000 unsigned char *inputp;
6002 remaining_octets = strlen(str);
6003 inputp = (unsigned char *) str;
6004 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
6005 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
6006 len += (unsigned) (inputp - (unsigned char *) str);
6011 some_set++;
6015 if((kwtype == KW || kwtype == KWInit) && some_set){
6016 len += strlen(subject); /* subject is already UTF-8 if needed */
6017 retsubj = (char *) fs_get((len + 1) * sizeof(*retsubj));
6018 memset(retsubj, 0, (len + 1) * sizeof(*retsubj));
6019 next_piece = p = retsubj;
6021 for(kw = ps_global->keywords; kw; kw = kw->next){
6022 if(user_flag_is_set(stream, rawno, kw->kw)){
6023 if(p == retsubj){
6024 if(left_brace && len > 0)
6025 sstrncpy(&p, left_brace, len);
6027 else if(kwtype == KW)
6028 *p++ = ' ';
6030 if(ielemp && p > next_piece){
6031 save = *p;
6032 *p = '\0';
6033 ielem = new_ielem(ielemp);
6034 ielem->freedata = 1;
6035 ielem->data = cpystr(next_piece);
6036 ielem->datalen = strlen(next_piece);
6037 *p = save;
6038 next_piece = p;
6041 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
6043 if(kwtype == KWInit){
6044 if(str && str[0]){
6045 UCS ucs;
6046 unsigned long remaining_octets;
6047 unsigned char *inputp;
6049 remaining_octets = strlen(str);
6050 inputp = (unsigned char *) str;
6051 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
6052 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
6053 if(len-(p-retsubj) > 0){
6054 sstrncpy(&p, str, MIN(inputp - (unsigned char *) str,len-(p-retsubj)));
6055 if(p > next_piece && ielemp && pico_usingcolor()
6056 && ((kw->nick && kw->nick[0]
6057 && (color=hdr_color(kw->nick,NULL,sc)))
6058 || (kw->kw && kw->kw[0]
6059 && (color=hdr_color(kw->kw,NULL,sc))))){
6060 ielem = new_ielem(ielemp);
6061 ielem->freedata = 1;
6062 save = *p;
6063 *p = '\0';
6064 ielem->data = cpystr(next_piece);
6065 ielem->datalen = strlen(next_piece);
6066 ielem->color = color;
6067 color = NULL;
6068 *p = save;
6069 next_piece = p;
6074 if(color)
6075 free_color_pair(&color);
6078 else{
6079 if(len-(p-retsubj) > 0)
6080 sstrncpy(&p, str, len-(p-retsubj));
6082 if(p > next_piece && ielemp && pico_usingcolor()
6083 && ((kw->nick && kw->nick[0]
6084 && (color=hdr_color(kw->nick,NULL,sc)))
6085 || (kw->kw && kw->kw[0]
6086 && (color=hdr_color(kw->kw,NULL,sc))))){
6087 ielem = new_ielem(ielemp);
6088 ielem->freedata = 1;
6089 save = *p;
6090 *p = '\0';
6091 ielem->data = cpystr(next_piece);
6092 ielem->datalen = strlen(next_piece);
6093 ielem->color = color;
6094 color = NULL;
6095 *p = save;
6096 next_piece = p;
6099 if(color)
6100 free_color_pair(&color);
6105 if(len-(p-retsubj) > 0 && right_brace)
6106 sstrncpy(&p, right_brace, len-(p-retsubj));
6108 if(ielemp && p > next_piece){
6109 save = *p;
6110 *p = '\0';
6111 ielem = new_ielem(ielemp);
6112 ielem->freedata = 1;
6113 ielem->data = cpystr(next_piece);
6114 ielem->datalen = strlen(next_piece);
6115 *p = save;
6116 next_piece = p;
6119 if(len-(p-retsubj) > 0 && subject)
6120 sstrncpy(&p, subject, len-(p-retsubj));
6122 if(ielemp && p > next_piece){
6123 save = *p;
6124 *p = '\0';
6125 ielem = new_ielem(ielemp);
6126 ielem->type = eTypeCol;
6127 ielem->freedata = 1;
6128 ielem->data = cpystr(next_piece);
6129 ielem->datalen = strlen(next_piece);
6130 *p = save;
6131 next_piece = p;
6132 if(pico_usingcolor()
6133 && ps_global->VAR_IND_SUBJ_FORE_COLOR
6134 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
6135 ielem->freecolor = 1;
6136 ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
6140 retsubj[len] = '\0'; /* just making sure */
6142 else{
6143 if(ielemp){
6144 ielem = new_ielem(ielemp);
6145 ielem->type = eTypeCol;
6146 ielem->freedata = 1;
6147 ielem->data = cpystr(subject);
6148 ielem->datalen = strlen(subject);
6149 if(pico_usingcolor()
6150 && ps_global->VAR_IND_SUBJ_FORE_COLOR
6151 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
6152 ielem->freecolor = 1;
6153 ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
6157 retsubj = cpystr(subject);
6160 if(braces){
6161 if(left_brace)
6162 fs_give((void **) &left_brace);
6164 if(right_brace)
6165 fs_give((void **) &right_brace);
6168 return(retsubj);
6173 * This means we should ensure that all data ends up being UTF-8 data.
6174 * That covers the data in ice ielems and str.
6176 void
6177 from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_S *ice)
6179 char *field, *newsgroups, *border, *p, *fptr = NULL, *q = NULL;
6180 ADDRESS *addr;
6181 int width = -1;
6182 int depth = 0, mult = 2;
6183 PINETHRD_S *thd, *thdorig;
6185 if(THREADING()
6186 && (ps_global->thread_disp_style == THREAD_INDENT_FROM1
6187 || ps_global->thread_disp_style == THREAD_INDENT_FROM2
6188 || ps_global->thread_disp_style == THREAD_STRUCT_FROM)){
6190 if(pith_opt_truncate_sfstr && (*pith_opt_truncate_sfstr)()){
6191 IFIELD_S *ourifield = NULL;
6193 if(ice && ice->ifield){
6194 /* move to last ifield, the one we're working on */
6195 for(ourifield = ice->ifield;
6196 ourifield && ourifield->next;
6197 ourifield = ourifield->next)
6201 if(ourifield && ourifield->width > 0)
6202 width = ourifield->width;
6205 if(width < 0)
6206 width = strsize-1;
6208 width = MIN(width, strsize-1);
6210 thdorig = thd = fetch_thread(idata->stream, idata->rawno);
6211 border = str + width;
6212 if(pith_opt_condense_thread_cue)
6213 width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
6214 thd && thd->next
6215 && get_lflag(idata->stream,
6216 NULL,idata->rawno,
6217 MN_COLL));
6219 fptr = str;
6221 if(thd)
6222 while(thd->parent && (thd = fetch_thread(idata->stream, thd->parent)))
6223 depth++;
6225 if(depth > 0){
6226 if(ps_global->thread_disp_style == THREAD_INDENT_FROM1)
6227 mult = 1;
6229 fptr += (mult*depth);
6230 for(thd = thdorig, p = str + mult*depth - mult;
6231 thd && thd->parent && p >= str;
6232 thd = fetch_thread(idata->stream, thd->parent), p -= mult){
6233 if(p + mult >= border && !q){
6234 if(width >= 4 && depth < 100){
6235 snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
6236 q = str + width-4;
6238 else if(width >= 5 && depth < 1000){
6239 snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
6240 q = str + width-5;
6242 else{
6243 snprintf(str, width+1, "%s", repeat_char(width, '.'));
6244 q = str;
6247 border = q;
6248 fptr = NULL;
6251 if(p + 1 < border){
6252 p[0] = p[1] = ' ';
6253 if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
6255 * WARNING!
6256 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
6257 * is ascii.
6259 if(thd == thdorig && !thd->branch)
6260 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
6261 else if(thd == thdorig || thd->branch)
6262 p[0] = '|';
6264 if(thd == thdorig)
6265 p[1] = '-';
6268 else if(p < border){
6269 p[0] = ' ';
6270 if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
6272 * WARNING!
6273 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
6274 * is ascii.
6276 if(thd == thdorig && !thd->branch)
6277 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
6278 else if(thd == thdorig || thd->branch)
6279 p[0] = '|';
6285 else
6286 fptr = str;
6288 if(fptr){
6289 strsize -= (fptr - str);
6290 switch(ctype){
6291 case iFromTo:
6292 case iFromToNotNews:
6293 if(!(addr = fetch_from(idata)) || address_is_us(addr, ps_global)){
6294 if(strsize-1 <= 4){
6295 strncpy(fptr, "To: ", strsize-1);
6296 fptr[strsize-1] = '\0';
6297 break;
6299 else{
6300 if((field = ((addr = fetch_to(idata))
6301 ? "To"
6302 : (addr = fetch_cc(idata))
6303 ? "Cc"
6304 : NULL))
6305 && set_index_addr(idata, field, addr, "To: ",
6306 strsize-1, fptr))
6307 break;
6309 if(ctype == iFromTo &&
6310 (newsgroups = fetch_newsgroups(idata)) &&
6311 *newsgroups){
6312 snprintf(fptr, strsize, "To: %-*.*s", (int)(strsize-1-4), (int)(strsize-1-4),
6313 newsgroups);
6314 break;
6317 /* else fall thru to From: */
6320 /* else fall thru to From: */
6322 if(idata->bogus)
6323 break;
6325 case iFrom:
6326 set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr);
6327 break;
6329 case iAddress:
6330 case iMailbox:
6331 if((addr = fetch_from(idata)) && addr->mailbox && addr->mailbox[0]){
6332 char *mb = NULL, *hst = NULL, *at = NULL;
6333 size_t len;
6335 mb = addr->mailbox;
6336 if(ctype == iAddress && addr->host && addr->host[0]
6337 && addr->host[0] != '.'){
6338 at = "@";
6339 hst = addr->host;
6342 len = strlen(mb);
6343 if(!at || strsize-1 <= len)
6344 snprintf(fptr, strsize, "%-*.*s", (int)(strsize-1), (int)(strsize-1), mb);
6345 else
6346 snprintf(fptr, strsize, "%s@%-*.*s", mb, (int)(strsize-1-len-1), (int)(strsize-1-len-1), hst);
6349 break;
6351 default:
6352 break;
6355 replace_tabs_by_space(str);
6360 * Set up the elements contained in field so that they take up the
6361 * whole field width. Data is assumed to be UTF-8.
6363 void
6364 set_ielem_widths_in_field(IFIELD_S *ifield)
6366 IELEM_S *ielem = NULL;
6367 int datawidth, fmtwidth;
6369 if(!ifield)
6370 return;
6372 fmtwidth = ifield->width;
6374 for(ielem = ifield->ielem; ielem && fmtwidth > 0; ielem = ielem->next){
6375 if(!ifield->leftadj && ielem->next){
6376 dprint((1, "set_ielem_widths_in_field(%d): right adjust with multiple elements, NOT SUPPOSED TO HAPPEN!\n", (int) ifield->ctype));
6377 assert(0);
6380 datawidth = (int) utf8_width(ielem->data);
6381 if(datawidth >= fmtwidth || !ielem->next){
6382 set_print_format(ielem, fmtwidth, ifield->leftadj);
6383 fmtwidth = 0;
6385 else{
6386 set_print_format(ielem, datawidth, ifield->leftadj);
6387 fmtwidth -= datawidth;
6394 * Simple hash function from K&R 2nd edition, p. 144.
6396 * This one is modified to never return 0 so we can use that as a special
6397 * value. Also, LINE_HASH_N fits in an unsigned long, so it too can be used
6398 * as a special value that can't be returned by line_hash.
6400 unsigned long
6401 line_hash(char *s)
6403 unsigned long hashval;
6405 for(hashval = 0; *s != '\0'; s++)
6406 hashval = *s + 31 * hashval;
6408 hashval = hashval % LINE_HASH_N;
6410 if(!hashval)
6411 hashval++;
6413 return(hashval);
6418 * Returns nonzero if considered hidden, 0 if not considered hidden.
6421 msgline_hidden(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int flags)
6423 int ret;
6425 if(flags & MH_ANYTHD){
6426 ret = ((any_lflagged(msgmap, MN_HIDE) > 0)
6427 && get_lflag(stream, msgmap, msgno, MN_HIDE));
6429 else if(flags & MH_THISTHD && THREADING() && sp_viewing_a_thread(stream)){
6430 ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
6431 || !get_lflag(stream, msgmap, msgno, MN_CHID2));
6433 else{
6434 if(THREADING() && sp_viewing_a_thread(stream)){
6435 ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
6436 || !get_lflag(stream, msgmap, msgno, MN_CHID2)
6437 || get_lflag(stream, msgmap, msgno, MN_CHID));
6439 else if(THRD_INDX()){
6441 * If this message is in the collapsed part of a thread,
6442 * it's hidden. It must be a top-level of a thread to be
6443 * considered visible. Even if it is top-level, it is only
6444 * visible if some message in the thread is not hidden.
6446 if(get_lflag(stream, msgmap, msgno, MN_CHID)) /* not top */
6447 ret = 1;
6448 else{
6449 unsigned long rawno;
6450 PINETHRD_S *thrd = NULL;
6452 rawno = mn_m2raw(msgmap, msgno);
6453 if(rawno)
6454 thrd = fetch_thread(stream, rawno);
6456 ret = !thread_has_some_visible(stream, thrd);
6459 else{
6460 ret = ((any_lflagged(msgmap, MN_HIDE | MN_CHID) > 0)
6461 && get_lflag(stream, msgmap, msgno, MN_HIDE | MN_CHID));
6465 dprint((10,
6466 "msgline_hidden(%ld): %s\n", msgno, ret ? "HID" : "VIS"));
6468 return(ret);
6472 void
6473 adjust_cur_to_visible(MAILSTREAM *stream, MSGNO_S *msgmap)
6475 long n, cur;
6476 int dir;
6478 cur = mn_get_cur(msgmap);
6480 /* if current is hidden, adjust */
6481 if(cur >= 1L && cur <= mn_get_total(msgmap)
6482 && msgline_hidden(stream, msgmap, cur, 0)){
6484 dir = mn_get_revsort(msgmap) ? -1 : 1;
6486 for(n = cur;
6487 ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6488 && (n >= 1L && n <= mn_get_total(msgmap))
6489 && msgline_hidden(stream, msgmap, n, 0);
6490 n -= dir)
6493 if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6494 && (n >= 1L && n <= mn_get_total(msgmap)))
6495 mn_reset_cur(msgmap, n);
6496 else{ /* no visible in that direction */
6497 for(n = cur;
6498 ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6499 && (n >= 1L && n <= mn_get_total(msgmap))
6500 && msgline_hidden(stream, msgmap, n, 0);
6501 n += dir)
6504 if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6505 && (n >= 1L && n <= mn_get_total(msgmap)))
6506 mn_reset_cur(msgmap, n);
6507 /* else trouble! */
6513 void
6514 setup_for_index_index_screen(void)
6516 format_index_line = format_index_index_line;
6517 setup_header_widths = setup_index_header_widths;
6521 void
6522 setup_for_thread_index_screen(void)
6524 format_index_line = format_thread_index_line;
6525 setup_header_widths = setup_thread_header_widths;
6529 unsigned long
6530 ice_hash(ICE_S *ice)
6532 char buf[MAX_SCREEN_COLS+1];
6534 buf[0] = '\0';
6536 if(ice)
6537 simple_index_line(buf, sizeof(buf), ice, 0L);
6539 buf[sizeof(buf) - 1] = '\0';
6541 return(line_hash(buf));
6545 char *
6546 left_adjust(int width)
6548 return(format_str(width, 1));
6552 char *
6553 right_adjust(int width)
6555 return(format_str(width, 0));
6560 * Returns allocated and filled in format string.
6562 char *
6563 format_str(int width, int left)
6565 char *format;
6566 size_t len;
6568 len = PRINT_FORMAT_LEN(width,left) * sizeof(char);
6569 format = (char *) fs_get(len + 1);
6570 copy_format_str(width, left, format, len);
6571 format[len] = '\0';
6573 return(format);
6578 * Put the left or right adjusted format string of width width into
6579 * dest. Dest is of size n+1.
6581 char *
6582 copy_format_str(int width, int left, char *dest, int n)
6584 char *p;
6586 p = int2string(width);
6588 snprintf(dest, n+1, "%%%s%s.%ss", left ? "-" : "", p, p);
6590 dest[n] = '\0';
6592 return(dest);
6597 * Sets up the print_format string to be width wide with left or right
6598 * adjust. Takes care of memory freeing and allocation.
6600 void
6601 set_print_format(IELEM_S *ielem, int width, int leftadj)
6603 if(ielem){
6604 ielem->wid = width;
6606 if(ielem->print_format){
6607 /* is there enough room? */
6608 if(ielem->freeprintf < PRINT_FORMAT_LEN(width,leftadj)+1){
6609 fs_resize((void **) &ielem->print_format,
6610 (PRINT_FORMAT_LEN(width,leftadj)+1) * sizeof(char));
6611 ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);
6614 copy_format_str(width, leftadj, ielem->print_format,
6615 PRINT_FORMAT_LEN(width,leftadj));
6617 else{
6618 ielem->print_format = leftadj ? left_adjust(width)
6619 : right_adjust(width);
6620 ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);