* Protect all calls to mail_elt in pith/ and alpine/ code. Protect means
[alpine.git] / pith / mailindx.c
blob323e451d652e9878a7af4d10049235662455ac74
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 2006-2008 University of Washington
7 * Copyright 2013-2016 Eduardo Chappa
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * ========================================================================
18 #include "../pith/headers.h"
19 #include "../pith/mailindx.h"
20 #include "../pith/mailview.h"
21 #include "../pith/flag.h"
22 #include "../pith/icache.h"
23 #include "../pith/msgno.h"
24 #include "../pith/thread.h"
25 #include "../pith/strlst.h"
26 #include "../pith/status.h"
27 #include "../pith/mailcmd.h"
28 #include "../pith/search.h"
29 #include "../pith/charset.h"
30 #include "../pith/reply.h"
31 #include "../pith/bldaddr.h"
32 #include "../pith/addrstring.h"
33 #include "../pith/news.h"
34 #include "../pith/util.h"
35 #include "../pith/pattern.h"
36 #include "../pith/sequence.h"
37 #include "../pith/color.h"
38 #include "../pith/stream.h"
39 #include "../pith/string.h"
40 #include "../pith/send.h"
41 #include "../pith/options.h"
42 #include "../pith/ablookup.h"
43 #ifdef _WINDOWS
44 #include "../pico/osdep/mswin.h"
45 #endif
48 * pointers to formatting functions
50 ICE_S *(*format_index_line)(INDEXDATA_S *);
51 void (*setup_header_widths)(MAILSTREAM *);
54 * pointer to optional load_overview functionality
56 void (*pith_opt_paint_index_hline)(MAILSTREAM *, long, ICE_S *);
59 * pointer to hook for saving index format state
61 void (*pith_opt_save_index_state)(int);
64 * hook to allow caller to insert cue that indicates a condensed
65 * thread relationship cue
67 int (*pith_opt_condense_thread_cue)(PINETHRD_S *, ICE_S *, char **, size_t *, int, int);
68 int (*pith_opt_truncate_sfstr)(void);
72 * Internal prototypes
74 void setup_for_thread_index_screen(void);
75 ICE_S *format_index_index_line(INDEXDATA_S *);
76 ICE_S *format_thread_index_line(INDEXDATA_S *);
77 int set_index_addr(INDEXDATA_S *, char *, ADDRESS *, char *, int, char *);
78 int ctype_is_fixed_length(IndexColType);
79 void setup_index_header_widths(MAILSTREAM *);
80 void setup_thread_header_widths(MAILSTREAM *);
81 int parse_index_format(char *, INDEX_COL_S **);
82 int index_in_overview(MAILSTREAM *);
83 ADDRESS *fetch_from(INDEXDATA_S *);
84 ADDRESS *fetch_sender(INDEXDATA_S *);
85 char *fetch_newsgroups(INDEXDATA_S *);
86 char *fetch_subject(INDEXDATA_S *);
87 char *fetch_date(INDEXDATA_S *);
88 long fetch_size(INDEXDATA_S *);
89 BODY *fetch_body(INDEXDATA_S *);
90 char *fetch_firsttext(INDEXDATA_S *idata, int);
91 char *fetch_header(INDEXDATA_S *idata, char *hdrname);
92 void subj_str(INDEXDATA_S *, char *, size_t, SubjKW, int, ICE_S *);
93 void key_str(INDEXDATA_S *, SubjKW, ICE_S *);
94 void header_str(INDEXDATA_S *, HEADER_TOK_S *, ICE_S *);
95 void prio_str(INDEXDATA_S *, IndexColType, ICE_S *);
96 void from_str(IndexColType, INDEXDATA_S *, char *, size_t, ICE_S *);
97 int day_of_week(struct date *);
98 int day_of_year(struct date *);
99 unsigned long ice_hash(ICE_S *);
100 char *left_adjust(int);
101 char *right_adjust(int);
102 char *format_str(int, int);
103 char *copy_format_str(int, int, char *, int);
104 void set_print_format(IELEM_S *, int, int);
105 void set_ielem_widths_in_field(IFIELD_S *);
108 #define BIGWIDTH 2047
111 /*----------------------------------------------------------------------
112 Initialize the index_disp_format array in ps_global from this
113 format string.
115 Args: format -- the string containing the format tokens
116 answer -- put the answer here, free first if there was a previous
117 value here
118 ----*/
119 void
120 init_index_format(char *format, INDEX_COL_S **answer)
122 char *p;
123 int i, w, monabb_width = 0, column = 0;
126 * Record the fact that SCORE appears in some index format. This
127 * is a heavy-handed approach. It will stick at 1 if any format ever
128 * contains score during this session. This is ok since it will just
129 * cause recalculation if wrong and these things rarely change much.
131 if(!ps_global->a_format_contains_score && format
132 && strstr(format, "SCORE")){
133 ps_global->a_format_contains_score = 1;
134 /* recalculate need for scores */
135 scores_are_used(SCOREUSE_INVALID);
138 set_need_format_setup(ps_global->mail_stream);
139 /* if custom format is specified, try it, else go with default */
140 if(!(format && *format && parse_index_format(format, answer))){
141 static INDEX_COL_S answer_default[] = {
142 {iStatus, Fixed, 3},
143 {iMessNo, WeCalculate},
144 {iSDateTime24, WeCalculate},
145 {iFromTo, Percent, 33}, /* percent of rest */
146 {iSizeNarrow, WeCalculate},
147 {iSubjKey, Percent, 67},
148 {iNothing}
151 if(*answer)
152 free_index_format(answer);
154 *answer = (INDEX_COL_S *)fs_get(sizeof(answer_default));
155 memcpy(*answer, answer_default, sizeof(answer_default));
159 * Test to see how long the month abbreviations are.
161 for(i = 1; i <= 12; i++){
162 p = month_abbrev_locale(i);
163 monabb_width = MAX(utf8_width(p), monabb_width);
166 monabb_width = MIN(MAX(2, monabb_width), 5);
169 * Fill in req_width's for WeCalculate items.
171 for(column = 0; (*answer)[column].ctype != iNothing; column++){
173 /* don't use strftime if we're not trying to use the LC_TIME stuff */
174 if(F_ON(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
175 switch((*answer)[column].ctype){
176 case iSDate:
177 (*answer)[column].ctype = iS1Date;
178 break;
179 case iSDateTime:
180 (*answer)[column].ctype = iSDateTimeS1;
181 break;
182 case iSDateTime24:
183 (*answer)[column].ctype = iSDateTimeS124;
184 break;
185 default:
186 break;
190 if((*answer)[column].wtype == WeCalculate){
191 switch((*answer)[column].ctype){
192 case iPrio:
193 case iPrioBang:
194 case iAtt:
195 (*answer)[column].req_width = 1;
196 break;
197 case iYear2Digit:
198 case iDay:
199 case iMon:
200 case iDay2Digit:
201 case iMon2Digit:
202 case iArrow:
203 case iKeyInit:
204 (*answer)[column].req_width = 2;
205 break;
206 case iStatus:
207 case iMessNo:
208 case iInit:
209 (*answer)[column].req_width = 3;
210 break;
211 case iYear:
212 case iDayOrdinal:
213 case iSIStatus:
214 (*answer)[column].req_width = 4;
215 break;
216 case iTime24:
217 case iTimezone:
218 case iSizeNarrow:
219 case iKey:
220 (*answer)[column].req_width = 5;
221 break;
222 case iFStatus:
223 case iIStatus:
224 case iScore:
225 (*answer)[column].req_width = 6;
226 break;
227 case iTime12:
228 case iSTime:
229 case iKSize:
230 case iSize:
231 case iPrioAlpha:
232 (*answer)[column].req_width = 7;
233 break;
234 case iS1Date:
235 case iS2Date:
236 case iS3Date:
237 case iS4Date:
238 case iDateIsoS:
239 case iSizeComma:
240 (*answer)[column].req_width = 8;
241 break;
242 case iMonAbb:
243 (*answer)[column].req_width = monabb_width;
244 (*answer)[column].monabb_width = monabb_width;
245 break;
246 case iDayOfWeekAbb:
248 w = 0;
251 * Test to see how long it is.
253 for(i = 0; i < 7; i++){
254 p = day_abbrev_locale(i);
255 w = MAX(utf8_width(p), w);
258 (*answer)[column].req_width = MIN(MAX(2, w), 5);
260 break;
261 case iDate:
262 (*answer)[column].req_width = monabb_width + 3;
263 (*answer)[column].monabb_width = monabb_width;
264 break;
265 case iMonLong:
267 w = 0;
270 * Test to see how long it is.
272 for(i = 1; i <= 12; i++){
273 p = month_name_locale(i);
274 w = MAX(utf8_width(p), w);
277 (*answer)[column].req_width = MIN(MAX(3, w), 12);
279 break;
280 case iDayOfWeek:
282 w = 0;
284 for(i = 0; i < 7; i++){
285 p = day_name_locale(i);
286 w = MAX(utf8_width(p), w);
289 (*answer)[column].req_width = MIN(MAX(3, w), 12);
291 break;
292 case iSDate:
293 case iSDateTime:
294 case iSDateTime24:
295 case iPrefDate:
296 case iPrefTime:
297 case iPrefDateTime:
300 * Format a date to see how long it is.
301 * Make it as least as long as "Yesterday".
302 * We should really use the width of the longest
303 * of the translated yesterdays and friends but...
305 struct tm tm;
306 int len = 20;
307 char ss[100];
309 memset(&tm, 0, sizeof(tm));
310 tm.tm_year = 106;
311 tm.tm_mon = 11;
312 tm.tm_mday = 31;
313 tm.tm_hour = 3;
314 tm.tm_min = 23;
315 tm.tm_wday = 3;
316 switch((*answer)[column].ctype){
317 case iPrefTime:
318 our_strftime(ss, sizeof(ss), "%X", &tm);
319 break;
320 case iPrefDateTime:
321 our_strftime(ss, sizeof(ss), "%c", &tm);
322 len = 32;
323 break;
324 default:
325 our_strftime(ss, sizeof(ss), "%x", &tm);
326 break;
328 (*answer)[column].req_width = MIN(MAX(9, utf8_width(ss)), len);
331 (*answer)[column].monabb_width = monabb_width;
332 break;
334 case iDescripSize:
335 case iSDateIsoS:
336 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
337 case iSDateTimeIsoS:
338 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
339 case iSDateTimeIsoS24:
340 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
342 * These SDates are 8 wide but they need to be 9 for "Yesterday".
344 (*answer)[column].req_width = 9;
345 break;
346 case iDateIso:
347 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
348 (*answer)[column].req_width = 10;
349 break;
350 case iLDate:
351 (*answer)[column].req_width = 12;
352 (*answer)[column].monabb_width = monabb_width;
353 break;
354 case iRDate:
355 (*answer)[column].req_width = 16;
356 break;
357 default:
358 break;
363 calc_extra_hdrs();
364 if(get_extra_hdrs())
365 (void) mail_parameters(NULL, SET_IMAPEXTRAHEADERS,
366 (void *) get_extra_hdrs());
370 void
371 reset_index_format(void)
373 long rflags = ROLE_DO_OTHER;
374 PAT_STATE pstate;
375 PAT_S *pat;
376 int we_set_it = 0;
378 if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){
379 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
380 if(match_pattern(pat->patgrp, ps_global->mail_stream, NULL,
381 NULL, NULL, SE_NOSERVER|SE_NOPREFETCH))
382 break;
385 if(pat && pat->action && !pat->action->bogus
386 && pat->action->index_format){
387 we_set_it++;
388 init_index_format(pat->action->index_format,
389 &ps_global->index_disp_format);
393 if(!we_set_it)
394 init_index_format(ps_global->VAR_INDEX_FORMAT,
395 &ps_global->index_disp_format);
399 void
400 free_index_format(INDEX_COL_S **disp_format)
402 INDEX_COL_S *cdesc = NULL;
404 if(disp_format && *disp_format){
405 for(cdesc = (*disp_format); cdesc->ctype != iNothing; cdesc++)
406 if(cdesc->hdrtok)
407 free_hdrtok(&cdesc->hdrtok);
409 fs_give((void **) disp_format);
414 HEADER_TOK_S *
415 new_hdrtok(char *hdrname)
417 HEADER_TOK_S *hdrtok;
419 hdrtok = (HEADER_TOK_S *) fs_get(sizeof(HEADER_TOK_S));
420 memset(hdrtok, 0, sizeof(HEADER_TOK_S));
421 hdrtok->hdrname = hdrname ? cpystr(hdrname) : NULL;
422 hdrtok->fieldnum = 0;
423 hdrtok->adjustment = Left;
424 hdrtok->fieldsepcnt = 1;
425 hdrtok->fieldseps = cpystr(" ");
427 return(hdrtok);
431 void
432 free_hdrtok(HEADER_TOK_S **hdrtok)
434 if(hdrtok && *hdrtok){
435 if((*hdrtok)->hdrname)
436 fs_give((void **) &(*hdrtok)->hdrname);
438 if((*hdrtok)->fieldseps)
439 fs_give((void **) &(*hdrtok)->fieldseps);
441 fs_give((void **) hdrtok);
446 /* popular ones first to make it slightly faster */
447 static INDEX_PARSE_T itokens[] = {
448 {"STATUS", iStatus, FOR_INDEX},
449 {"MSGNO", iMessNo, FOR_INDEX},
450 {"DATE", iDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
451 {"FROMORTO", iFromTo, FOR_INDEX},
452 {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX},
453 {"SIZE", iSize, FOR_INDEX},
454 {"SIZECOMMA", iSizeComma, FOR_INDEX},
455 {"SIZENARROW", iSizeNarrow, FOR_INDEX},
456 {"KSIZE", iKSize, FOR_INDEX},
457 {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
458 {"FULLSTATUS", iFStatus, FOR_INDEX},
459 {"IMAPSTATUS", iIStatus, FOR_INDEX},
460 {"SHORTIMAPSTATUS", iSIStatus, FOR_INDEX},
461 {"SUBJKEY", iSubjKey, FOR_INDEX},
462 {"SUBJKEYINIT", iSubjKeyInit, FOR_INDEX},
463 {"SUBJECTTEXT", iSubjectText, FOR_INDEX},
464 {"SUBJKEYTEXT", iSubjKeyText, FOR_INDEX},
465 {"SUBJKEYINITTEXT", iSubjKeyInitText, FOR_INDEX},
466 {"OPENINGTEXT", iOpeningText, FOR_INDEX},
467 {"OPENINGTEXTNQ", iOpeningTextNQ, FOR_INDEX},
468 {"KEY", iKey, FOR_INDEX},
469 {"KEYINIT", iKeyInit, FOR_INDEX},
470 {"DESCRIPSIZE", iDescripSize, FOR_INDEX},
471 {"ATT", iAtt, FOR_INDEX},
472 {"SCORE", iScore, FOR_INDEX},
473 {"PRIORITY", iPrio, FOR_INDEX},
474 {"PRIORITYALPHA", iPrioAlpha, FOR_INDEX},
475 {"PRIORITY!", iPrioBang, FOR_INDEX},
476 {"LONGDATE", iLDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
477 {"SHORTDATE1", iS1Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
478 {"SHORTDATE2", iS2Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
479 {"SHORTDATE3", iS3Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
480 {"SHORTDATE4", iS4Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
481 {"DATEISO", iDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
482 {"SHORTDATEISO", iDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
483 {"SMARTDATE", iSDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
484 {"SMARTTIME", iSTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
485 {"SMARTDATEISO", iSDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
486 {"SMARTDATESHORTISO",iSDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
487 {"SMARTDATES1", iSDateS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
488 {"SMARTDATES2", iSDateS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
489 {"SMARTDATES3", iSDateS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
490 {"SMARTDATES4", iSDateS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
491 {"SMARTDATETIME", iSDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
492 {"SMARTDATETIMEISO",iSDateTimeIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
493 {"SMARTDATETIMESHORTISO",iSDateTimeIsoS,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
494 {"SMARTDATETIMES1", iSDateTimeS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
495 {"SMARTDATETIMES2", iSDateTimeS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
496 {"SMARTDATETIMES3", iSDateTimeS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
497 {"SMARTDATETIMES4", iSDateTimeS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
498 {"SMARTDATETIME24", iSDateTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
499 {"SMARTDATETIMEISO24", iSDateTimeIso24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
500 {"SMARTDATETIMESHORTISO24",iSDateTimeIsoS24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
501 {"SMARTDATETIMES124", iSDateTimeS124, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
502 {"SMARTDATETIMES224", iSDateTimeS224, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
503 {"SMARTDATETIMES324", iSDateTimeS324, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
504 {"SMARTDATETIMES424", iSDateTimeS424, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
505 {"TIME24", iTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
506 {"TIME12", iTime12, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
507 {"TIMEZONE", iTimezone, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
508 {"MONTHABBREV", iMonAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
509 {"DAYOFWEEKABBREV", iDayOfWeekAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
510 {"DAYOFWEEK", iDayOfWeek, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
511 {"FROM", iFrom, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
512 {"TO", iTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
513 {"SENDER", iSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
514 {"CC", iCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
515 {"RECIPS", iRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
516 {"NEWS", iNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
517 {"TOANDNEWS", iToAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
518 {"NEWSANDTO", iNewsAndTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
519 {"RECIPSANDNEWS", iRecipsAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
520 {"NEWSANDRECIPS", iNewsAndRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
521 {"MSGID", iMsgID, FOR_REPLY_INTRO|FOR_TEMPLATE},
522 {"CURNEWS", iCurNews, FOR_REPLY_INTRO|FOR_TEMPLATE},
523 {"DAYDATE", iRDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
524 {"PREFDATE", iPrefDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
525 {"PREFTIME", iPrefTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
526 {"PREFDATETIME", iPrefDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
527 {"DAY", iDay, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
528 {"DAYORDINAL", iDayOrdinal, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
529 {"DAY2DIGIT", iDay2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
530 {"MONTHLONG", iMonLong, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
531 {"MONTH", iMon, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
532 {"MONTH2DIGIT", iMon2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
533 {"YEAR", iYear, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
534 {"YEAR2DIGIT", iYear2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
535 {"ADDRESS", iAddress, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
536 {"MAILBOX", iMailbox, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
537 {"ROLENICK", iRoleNick, FOR_REPLY_INTRO|FOR_TEMPLATE},
538 {"INIT", iInit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
539 {"CURDATE", iCurDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
540 {"CURDATEISO", iCurDateIso, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
541 {"CURDATEISOS", iCurDateIsoS, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
542 {"CURTIME24", iCurTime24, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
543 {"CURTIME12", iCurTime12, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
544 {"CURDAY", iCurDay, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
545 {"CURDAY2DIGIT", iCurDay2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
546 {"CURDAYOFWEEK", iCurDayOfWeek, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
547 {"CURDAYOFWEEKABBREV", iCurDayOfWeekAbb,
548 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
549 {"CURMONTH", iCurMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
550 {"CURMONTH2DIGIT", iCurMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
551 {"CURMONTHLONG", iCurMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
552 {"CURMONTHABBREV", iCurMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
553 {"CURYEAR", iCurYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
554 {"CURYEAR2DIGIT", iCurYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
555 {"CURPREFDATE", iCurPrefDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
556 {"CURPREFTIME", iCurPrefTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
557 {"CURPREFDATETIME", iCurPrefDateTime,
558 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
559 {"LASTMONTH", iLstMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
560 {"LASTMONTH2DIGIT", iLstMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
561 {"LASTMONTHLONG", iLstMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
562 {"LASTMONTHABBREV", iLstMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
563 {"LASTMONTHYEAR", iLstMonYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
564 {"LASTMONTHYEAR2DIGIT", iLstMonYear2Digit,
565 FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
566 {"LASTYEAR", iLstYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
567 {"LASTYEAR2DIGIT", iLstYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
568 {"HEADER", iHeader, FOR_INDEX},
569 {"TEXT", iText, FOR_INDEX},
570 {"ARROW", iArrow, FOR_INDEX},
571 {"NEWLINE", iNewLine, FOR_REPLY_INTRO},
572 {"CURSORPOS", iCursorPos, FOR_TEMPLATE},
573 {NULL, iNothing, FOR_NOTHING}
576 INDEX_PARSE_T itokensinv[sizeof(itokens)/sizeof(itokens[0])];
578 void
579 inverse_itokens(void)
581 INDEX_PARSE_T *pt;
582 for (pt = itokens; pt->name; pt++)
583 itokensinv[pt->ctype].ctype = pt - itokens;
587 INDEX_PARSE_T *
588 itoken(int i)
590 return((i < sizeof(itokens) && itokens[i].name) ? &itokens[i] : NULL);
595 * Args txt -- The token being checked begins at the beginning
596 * of txt. The end of the token is delimited by a null, or
597 * white space, or an underscore if DELIM_USCORE is set,
598 * or a left paren if DELIM_PAREN is set.
599 * flags -- Flags contains the what_for value, and DELIM_ values.
601 * Returns A ptr to an INDEX_PARSE_T from itokens above, else NULL.
603 INDEX_PARSE_T *
604 itoktype(char *txt, int flags)
606 INDEX_PARSE_T *pt;
607 char token[100 + 1];
608 char *v, *w;
611 * Separate a copy of the possible token out of txt.
613 v = txt;
614 w = token;
615 while(w < token+sizeof(token)-1 &&
616 *v &&
617 !(!(*v & 0x80) && isspace((unsigned char)*v)) &&
618 !(flags & DELIM_USCORE && *v == '_') &&
619 !(flags & DELIM_PAREN && *v == '(') &&
620 !(flags & DELIM_COLON && *v == ':'))
621 *w++ = *v++;
623 *w = '\0';
625 for(pt = itokens; pt->name; pt++)
626 if(pt->what_for & flags && !strucmp(pt->name, token))
627 return(pt);
629 return(NULL);
634 parse_index_format(char *format_str, INDEX_COL_S **answer)
636 int i, column = 0;
637 char *p, *q;
638 INDEX_PARSE_T *pt;
639 INDEX_COL_S cdesc[200]; /* plenty of temp storage for answer */
641 memset((void *)cdesc, 0, sizeof(cdesc));
643 p = format_str;
644 while(p && *p && column < 200-1){
645 /* skip leading white space for next word */
646 p = skip_white_space(p);
647 pt = itoktype(p, FOR_INDEX | DELIM_PAREN | DELIM_COLON);
649 /* ignore unrecognized word */
650 if(!pt){
651 for(q = p; *p && !isspace((unsigned char)*p); p++)
654 if(*p)
655 *p++ = '\0';
657 dprint((1,
658 "parse_index_format: unrecognized token: %s\n",
659 q ? q : "?"));
660 q_status_message1(SM_ORDER | SM_DING, 0, 3,
661 _("Unrecognized word in index-format: %s"), q);
662 continue;
665 cdesc[column].ctype = pt->ctype;
667 if(pt->ctype == iHeader || pt->ctype == iText){
669 * iHeader field has special syntax.
671 * HEADER:hdrname(width,fieldnum,field_separators,L_or_R)
673 * where width is the regular width or percentage width or
674 * left out for default width, fieldnum defaults to 0 for
675 * whole thing, 1 for first field, ...
676 * and field_separators is a list of characters which separate
677 * the fields. The whole parenthesized part is optional. If used
678 * the arguments can be dropped from the right, so
680 * HEADER:hdrname or
681 * HEADER:hdrname(10) or
682 * HEADER:hdrname(10%) or
683 * HEADER:hdrname(10,2) or
684 * HEADER:hdrname(,2) or
685 * HEADER:hdrname(10,2, ) or
686 * HEADER:hdrname(10,2,\,:) or
687 * HEADER:hdrname(10,2,\,:,R)
689 * iText field uses the hdrtok field for convenience. It has syntax
691 * TEXT:text or
692 * TEXT:text(10) or
693 * TEXT:text(10%)
695 * and the literal text goes into the index line. It is also special
696 * because there is no 1 column space after this field.
699 /* skip over name */
700 p += strlen(pt->name);
702 /* look for header name */
703 if(*p == ':'){
704 char *w, hdrname[200];
706 hdrname[0] = '\0';
707 w = hdrname;
708 p++;
709 if(*p == '\"'){ /* quoted name */
710 p++;
711 while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){
712 if(*p == '\\')
713 p++;
715 *w++ = *p++;
718 *w = '\0';
719 if(*p == '\"')
720 p++;
722 else{
723 while(w < hdrname + sizeof(hdrname)-1 &&
724 !(!(*p & 0x80) && isspace((unsigned char)*p)) &&
725 *p != '(')
726 *w++ = *p++;
728 *w = '\0';
731 if(hdrname[0]){
732 cdesc[column].hdrtok = new_hdrtok(hdrname);
734 else{
735 if(pt->ctype == iHeader){
736 dprint((1, "parse_index_token: HEADER should be followed by :hdrname\n"));
737 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname");
739 else{
740 dprint((1, "parse_index_token: TEXT should be followed by :text\n"));
741 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text");
745 else{
746 if(pt->ctype == iHeader){
747 dprint((1, "parse_index_token: HEADER should be followed by :hdrname, not %s\n", p));
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, not %s\n", p));
752 q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text");
755 /* skip over rest of bogus config */
756 while(!(!(*p & 0x80) && isspace((unsigned char)*p)) && *p != '(')
757 p++;
760 else{
761 /* skip over name and look for parens */
762 p += strlen(pt->name);
765 if(*p == '('){
766 p++;
767 q = p;
768 while(p && *p && isdigit((unsigned char) *p))
769 p++;
771 if(pt->ctype == iHeader){
772 /* first argument is width or width percentage, like for others */
773 if(p && *p && (*p == ')' || *p == ',')){
774 if(p > q){
775 cdesc[column].wtype = Fixed;
776 cdesc[column].req_width = atoi(q);
778 else{
779 cdesc[column].wtype = WeCalculate;
780 cdesc[column].req_width = 0;
783 else if(p && *p && *p == '%' && p > q){
784 cdesc[column].wtype = Percent;
785 cdesc[column].req_width = atoi(q);
786 p++;
788 else{
789 cdesc[column].wtype = WeCalculate;
790 cdesc[column].req_width = 0;
793 /* optional 2nd argument is field number, 0 whole thing, 1, 2, ... */
794 if(p && *p && *p == ','){
795 p++;
796 /* no space allowed between arguments */
797 if(*p && isdigit((unsigned char) *p)){
798 q = p;
799 while(*p && isdigit((unsigned char) *p))
800 p++;
802 cdesc[column].hdrtok->fieldnum = atoi(q);
805 * Optional 3rd argument is field separators.
806 * Comma is \, and backslash is \\.
808 if(*p == ','){
809 int j;
811 p++;
812 /* don't use default */
813 if(*p && *p != ')' && *p != ',' && cdesc[column].hdrtok->fieldseps)
814 cdesc[column].hdrtok->fieldseps[0] = '\0';
816 j = 0;
817 if(*p == '\"' && strchr(p+1, '\"')){
818 p++;
819 while(*p && *p != ')' && *p != '\"' && *p != ','){
820 if(cdesc[column].hdrtok->fieldseps)
821 fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2);
823 if(*p == '\\' && *(p+1))
824 p++;
826 if(cdesc[column].hdrtok->fieldseps){
827 cdesc[column].hdrtok->fieldseps[j++] = *p++;
828 cdesc[column].hdrtok->fieldseps[j] = '\0';
829 cdesc[column].hdrtok->fieldsepcnt = j;
833 if(*p == '\"')
834 p++;
836 else{
837 while(*p && *p != ')' && *p != ','){
838 if(cdesc[column].hdrtok->fieldseps)
839 fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2);
840 if(*p == '\\' && *(p+1))
841 p++;
843 if(cdesc[column].hdrtok->fieldseps){
844 cdesc[column].hdrtok->fieldseps[j++] = *p++;
845 cdesc[column].hdrtok->fieldseps[j] = '\0';
846 cdesc[column].hdrtok->fieldsepcnt = j;
851 /* optional 4th argument, left or right adjust */
852 if(*p == ','){
853 p++;
854 if(*p == 'L' || *p == 'l')
855 cdesc[column].hdrtok->adjustment = Left;
856 else if(*p == 'R' || *p == 'r')
857 cdesc[column].hdrtok->adjustment = Right;
858 else{
859 dprint((1, "parse_index_token: HEADER 4th argument should be L or R, not\n", *p ? p : "<null>"));
860 q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 4th argument should be L or R");
865 else{
866 dprint((1, "parse_index_token: HEADER 2nd argument should be field number, not\n", *p ? p : "<null>"));
867 q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 2nd argument should be field number, a non-negative digit");
871 else{
872 if(p && *p && *p == ')' && p > q){
873 cdesc[column].wtype = Fixed;
874 cdesc[column].req_width = atoi(q);
876 else if(p && *p && *p == '%' && p > q){
877 cdesc[column].wtype = Percent;
878 cdesc[column].req_width = atoi(q);
880 else{
881 cdesc[column].wtype = WeCalculate;
882 cdesc[column].req_width = 0;
886 else{
887 /* if they left out width for iText we can figure it out */
888 if(pt->ctype == iText && cdesc[column].hdrtok && cdesc[column].hdrtok->hdrname){
889 cdesc[column].wtype = Fixed;
890 cdesc[column].req_width = utf8_width(cdesc[column].hdrtok->hdrname);
892 else{
893 cdesc[column].wtype = WeCalculate;
894 cdesc[column].req_width = 0;
898 column++;
899 /* skip text at end of word */
900 while(p && *p && !isspace((unsigned char)*p))
901 p++;
904 /* if, after all that, we didn't find anything recognizable, bitch */
905 if(!column){
906 dprint((1, "Completely unrecognizable index-format\n"));
907 q_status_message(SM_ORDER | SM_DING, 0, 3,
908 _("Configured \"index-format\" unrecognizable. Using default."));
909 return(0);
912 /* Finish with Nothing column */
913 cdesc[column].ctype = iNothing;
915 /* free up old answer */
916 if(*answer)
917 free_index_format(answer);
919 /* allocate space for new answer */
920 *answer = (INDEX_COL_S *)fs_get((column+1)*sizeof(INDEX_COL_S));
921 memset((void *)(*answer), 0, (column+1)*sizeof(INDEX_COL_S));
922 /* copy answer to real place */
923 for(i = 0; i <= column; i++)
924 (*answer)[i] = cdesc[i];
926 return(1);
931 * These types are basically fixed in width.
932 * The order is slightly significant. The ones towards the front of the
933 * list get space allocated sooner than the ones at the end of the list.
935 static IndexColType fixed_ctypes[] = {
936 iMessNo, iStatus, iFStatus, iIStatus, iSIStatus,
937 iDate, iSDate, iSDateTime, iSDateTime24,
938 iSTime, iLDate,
939 iS1Date, iS2Date, iS3Date, iS4Date, iDateIso, iDateIsoS,
940 iSDateIso, iSDateIsoS,
941 iSDateS1, iSDateS2, iSDateS3, iSDateS4,
942 iSDateTimeIso, iSDateTimeIsoS,
943 iSDateTimeS1, iSDateTimeS2, iSDateTimeS3, iSDateTimeS4,
944 iSDateTimeIso24, iSDateTimeIsoS24,
945 iSDateTimeS124, iSDateTimeS224, iSDateTimeS324, iSDateTimeS424,
946 iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize,
947 iPrio, iPrioBang, iPrioAlpha, iInit,
948 iAtt, iTime24, iTime12, iTimezone, iMonAbb, iYear, iYear2Digit,
949 iDay2Digit, iMon2Digit, iDayOfWeekAbb, iScore, iMonLong, iDayOfWeek
954 ctype_is_fixed_length(IndexColType ctype)
956 int j;
958 for(j = 0; ; j++){
959 if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes))
960 break;
962 if(ctype == fixed_ctypes[j])
963 return 1;
966 return 0;
970 /*----------------------------------------------------------------------
971 Setup the widths of the various columns in the index display
972 ----*/
973 void
974 setup_index_header_widths(MAILSTREAM *stream)
976 int colspace; /* for reserving space between columns */
977 int j, some_to_calculate;
978 int space_left, screen_width, fix;
979 int keep_going, tot_pct, was_sl;
980 long max_msgno;
981 WidthType wtype;
982 INDEX_COL_S *cdesc;
984 max_msgno = mn_get_total(ps_global->msgmap);
986 dprint((8, "=== setup_index_header_widths() ===\n"));
988 clear_icache_flags(stream);
989 screen_width = ps_global->ttyo->screen_cols;
990 space_left = screen_width;
991 some_to_calculate = 0;
992 colspace = -1;
995 * Calculate how many fields there are so we know how many spaces
996 * between columns to reserve. Fill in Fixed widths now. Reserve
997 * special case WeCalculate with non-zero req_widths before doing
998 * Percent cases below.
1000 for(cdesc = ps_global->index_disp_format;
1001 cdesc->ctype != iNothing;
1002 cdesc++){
1004 if(cdesc->wtype == Fixed){
1005 cdesc->width = cdesc->req_width;
1006 if(cdesc->width > 0)
1007 colspace++;
1009 else if(cdesc->wtype == Percent){
1010 cdesc->width = 0; /* calculated later */
1011 colspace++;
1013 else{ /* WeCalculate */
1014 cdesc->width = cdesc->req_width; /* reserve this for now */
1015 some_to_calculate++;
1016 colspace++;
1019 /* no space after iText */
1020 if(cdesc->ctype == iText)
1021 colspace--;
1023 space_left -= cdesc->width;
1026 colspace = MAX(colspace, 0);
1028 space_left -= colspace; /* space between columns */
1030 ps_global->display_keywords_in_subject = 0;
1031 ps_global->display_keywordinits_in_subject = 0;
1034 * Set the actual lengths for the fixed width fields and set up
1035 * the left or right adjustment for everything.
1036 * There should be a case setting actual_length for all of the types
1037 * in fixed_ctypes.
1039 for(cdesc = ps_global->index_disp_format;
1040 cdesc->ctype != iNothing;
1041 cdesc++){
1043 wtype = cdesc->wtype;
1045 if(cdesc->ctype == iSubjKey || cdesc->ctype == iSubjKeyText)
1046 ps_global->display_keywords_in_subject = 1;
1047 else if(cdesc->ctype == iSubjKeyInit || cdesc->ctype == iSubjKeyInitText)
1048 ps_global->display_keywordinits_in_subject = 1;
1050 if(wtype == WeCalculate || wtype == Percent || cdesc->width != 0){
1052 switch(cdesc->ctype){
1053 case iSDate: case iSDateIso: case iSDateIsoS:
1054 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1055 case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
1056 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1057 case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
1058 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1059 case iSTime:
1060 set_format_includes_smartdate(stream);
1061 break;
1063 default:
1064 break;
1067 if(ctype_is_fixed_length(cdesc->ctype)){
1068 switch(cdesc->ctype){
1069 case iPrio:
1070 case iPrioBang:
1071 case iAtt:
1072 cdesc->actual_length = 1;
1073 cdesc->adjustment = Left;
1074 break;
1076 case iYear2Digit:
1077 case iDay2Digit:
1078 case iMon2Digit:
1079 cdesc->actual_length = 2;
1080 cdesc->adjustment = Left;
1081 break;
1083 case iArrow:
1084 cdesc->actual_length = 2;
1085 cdesc->adjustment = Right;
1086 break;
1088 case iStatus:
1089 case iInit:
1090 cdesc->actual_length = 3;
1091 cdesc->adjustment = Left;
1092 break;
1094 case iMessNo:
1095 set_format_includes_msgno(stream);
1096 if(max_msgno < 1000)
1097 cdesc->actual_length = 3;
1098 else if(max_msgno < 10000)
1099 cdesc->actual_length = 4;
1100 else if(max_msgno < 100000)
1101 cdesc->actual_length = 5;
1102 else
1103 cdesc->actual_length = 6;
1105 cdesc->adjustment = Right;
1106 break;
1108 case iYear:
1109 case iSIStatus:
1110 cdesc->actual_length = 4;
1111 cdesc->adjustment = Left;
1112 break;
1114 case iTime24:
1115 case iTimezone:
1116 cdesc->actual_length = 5;
1117 cdesc->adjustment = Left;
1118 break;
1120 case iSizeNarrow:
1121 cdesc->actual_length = 5;
1122 cdesc->adjustment = Right;
1123 break;
1125 case iFStatus:
1126 case iIStatus:
1127 cdesc->actual_length = 6;
1128 cdesc->adjustment = Left;
1129 break;
1131 case iScore:
1132 cdesc->actual_length = 6;
1133 cdesc->adjustment = Right;
1134 break;
1136 case iTime12:
1137 case iSize:
1138 case iKSize:
1139 cdesc->actual_length = 7;
1140 cdesc->adjustment = Right;
1141 break;
1143 case iSTime:
1144 cdesc->actual_length = 7;
1145 cdesc->adjustment = Left;
1146 break;
1148 case iPrioAlpha:
1149 cdesc->actual_length = 7;
1150 cdesc->adjustment = Left;
1151 break;
1154 case iS1Date:
1155 case iS2Date:
1156 case iS3Date:
1157 case iS4Date:
1158 case iDateIsoS:
1159 cdesc->actual_length = 8;
1160 cdesc->adjustment = Left;
1161 break;
1163 case iSizeComma:
1164 cdesc->actual_length = 8;
1165 cdesc->adjustment = Right;
1166 break;
1168 case iSDate:
1169 case iSDateTime:
1170 case iSDateTime24:
1171 case iMonAbb:
1172 case iDayOfWeekAbb:
1173 case iDayOfWeek:
1174 case iDate:
1175 case iMonLong:
1176 cdesc->actual_length = cdesc->req_width;
1177 cdesc->adjustment = Left;
1178 break;
1180 case iSDateIsoS:
1181 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1182 case iSDateTimeIsoS:
1183 case iSDateTimeIsoS24:
1184 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1185 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1186 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
1187 if(cdesc->ctype == iSDateIso
1188 || cdesc->ctype == iSDateTimeIso
1189 || cdesc->ctype == iSDateTimeIso24)
1190 cdesc->actual_length = 10;
1191 else
1192 cdesc->actual_length = 9;
1194 cdesc->adjustment = Left;
1195 break;
1197 case iDescripSize:
1198 cdesc->actual_length = 9;
1199 cdesc->adjustment = Right;
1200 break;
1202 case iDateIso:
1203 cdesc->actual_length = 10;
1204 cdesc->adjustment = Left;
1205 break;
1207 case iLDate:
1208 cdesc->actual_length = 12;
1209 cdesc->adjustment = Left;
1210 break;
1212 default:
1213 alpine_panic("Unhandled fixed case in setup_index_header");
1214 break;
1217 else if(cdesc->ctype == iHeader)
1218 cdesc->adjustment = cdesc->hdrtok ? cdesc->hdrtok->adjustment : Left;
1219 else
1220 cdesc->adjustment = Left;
1224 if(ps_global->display_keywords_in_subject)
1225 ps_global->display_keywordinits_in_subject = 0;
1227 /* if have reserved unneeded space for size, give it back */
1228 for(cdesc = ps_global->index_disp_format;
1229 cdesc->ctype != iNothing;
1230 cdesc++)
1231 if(cdesc->ctype == iSize || cdesc->ctype == iKSize ||
1232 cdesc->ctype == iSizeNarrow ||
1233 cdesc->ctype == iSizeComma || cdesc->ctype == iDescripSize){
1234 if(cdesc->actual_length == 0){
1235 if((fix=cdesc->width) > 0){ /* had this reserved */
1236 cdesc->width = 0;
1237 space_left += fix;
1240 space_left++; /* +1 for space between columns */
1245 * Calculate the field widths that are basically fixed in width.
1246 * Do them in this order in case we don't have enough space to go around.
1247 * The set of fixed_ctypes here is the same as the set where we
1248 * set the actual_lengths above.
1250 for(j = 0; space_left > 0 && some_to_calculate; j++){
1252 if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes))
1253 break;
1255 for(cdesc = ps_global->index_disp_format;
1256 cdesc->ctype != iNothing && space_left > 0 && some_to_calculate;
1257 cdesc++)
1258 if(cdesc->ctype == fixed_ctypes[j] && cdesc->wtype == WeCalculate){
1259 some_to_calculate--;
1260 fix = MIN(cdesc->actual_length - cdesc->width, space_left);
1261 cdesc->width += fix;
1262 space_left -= fix;
1267 * Fill in widths for Percent cases. If there are no more to calculate,
1268 * use the percentages as relative numbers and use the rest of the space,
1269 * else treat them as absolute percentages of the original avail screen.
1271 if(space_left > 0){
1272 if(some_to_calculate){
1273 int tot_requested = 0;
1276 * Requests are treated as percent of screen width. See if they
1277 * will all fit. If not, trim them back proportionately.
1279 for(cdesc = ps_global->index_disp_format;
1280 cdesc->ctype != iNothing;
1281 cdesc++){
1282 if(cdesc->wtype == Percent){
1283 /* The 2, 200, and +100 are because we're rounding */
1284 fix = ((2*cdesc->req_width *
1285 (screen_width-colspace))+100) / 200;
1286 tot_requested += fix;
1290 if(tot_requested > space_left){
1291 int multiplier = (100 * space_left) / tot_requested;
1293 for(cdesc = ps_global->index_disp_format;
1294 cdesc->ctype != iNothing && space_left > 0;
1295 cdesc++){
1296 if(cdesc->wtype == Percent){
1297 /* The 2, 200, and +100 are because we're rounding */
1298 fix = ((2*cdesc->req_width *
1299 (screen_width-colspace))+100) / 200;
1300 fix = (2 * fix * multiplier + 100) / 200;
1301 fix = MIN(fix, space_left);
1302 cdesc->width += fix;
1303 space_left -= fix;
1307 else{
1308 for(cdesc = ps_global->index_disp_format;
1309 cdesc->ctype != iNothing && space_left > 0;
1310 cdesc++){
1311 if(cdesc->wtype == Percent){
1312 /* The 2, 200, and +100 are because we're rounding */
1313 fix = ((2*cdesc->req_width *
1314 (screen_width-colspace))+100) / 200;
1315 fix = MIN(fix, space_left);
1316 cdesc->width += fix;
1317 space_left -= fix;
1322 else{
1323 tot_pct = 0;
1324 was_sl = space_left;
1325 /* add up total percentages requested */
1326 for(cdesc = ps_global->index_disp_format;
1327 cdesc->ctype != iNothing;
1328 cdesc++)
1329 if(cdesc->wtype == Percent)
1330 tot_pct += cdesc->req_width;
1332 /* give relative weight to requests */
1333 for(cdesc = ps_global->index_disp_format;
1334 cdesc->ctype != iNothing && space_left > 0 && tot_pct > 0;
1335 cdesc++){
1336 if(cdesc->wtype == Percent){
1337 fix = ((2*cdesc->req_width*was_sl)+tot_pct) / (2*tot_pct);
1338 fix = MIN(fix, space_left);
1339 cdesc->width += fix;
1340 space_left -= fix;
1346 /* split up rest, give twice as much to Subject */
1347 keep_going = 1;
1348 while(space_left > 0 && keep_going){
1349 keep_going = 0;
1350 for(cdesc = ps_global->index_disp_format;
1351 cdesc->ctype != iNothing && space_left > 0;
1352 cdesc++){
1353 if(cdesc->wtype == WeCalculate && !ctype_is_fixed_length(cdesc->ctype)){
1354 keep_going++;
1355 cdesc->width++;
1356 space_left--;
1357 if(space_left > 0 && (cdesc->ctype == iSubject
1358 || cdesc->ctype == iSubjectText
1359 || cdesc->ctype == iSubjKey
1360 || cdesc->ctype == iSubjKeyText
1361 || cdesc->ctype == iSubjKeyInit
1362 || cdesc->ctype == iSubjKeyInitText)){
1363 cdesc->width++;
1364 space_left--;
1370 /* if still more, pad out percent's */
1371 keep_going = 1;
1372 while(space_left > 0 && keep_going){
1373 keep_going = 0;
1374 for(cdesc = ps_global->index_disp_format;
1375 cdesc->ctype != iNothing && space_left > 0;
1376 cdesc++){
1377 if(cdesc->wtype == Percent && !ctype_is_fixed_length(cdesc->ctype)){
1378 keep_going++;
1379 cdesc->width++;
1380 space_left--;
1385 /* if user made Fixed fields too big, give back space */
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 == Fixed && cdesc->width > 0){
1393 keep_going++;
1394 cdesc->width--;
1395 space_left++;
1400 if(pith_opt_save_index_state)
1401 (*pith_opt_save_index_state)(FALSE);
1405 void
1406 setup_thread_header_widths(MAILSTREAM *stream)
1408 clear_icache_flags(stream);
1409 if(pith_opt_save_index_state)
1410 (*pith_opt_save_index_state)(TRUE);
1415 * load_overview - c-client call back to gather overview data
1417 * Note: if we never get called, UID represents a hole
1418 * if we're passed a zero UID, totally bogus overview data
1419 * if we're passed a zero obuf, mostly bogus overview data
1421 void
1422 load_overview(MAILSTREAM *stream, imapuid_t uid, OVERVIEW *obuf, long unsigned int rawno)
1424 if(obuf && rawno >= 1L && stream && rawno <= stream->nmsgs){
1425 INDEXDATA_S idata;
1426 ICE_S *ice;
1428 memset(&idata, 0, sizeof(INDEXDATA_S));
1429 idata.no_fetch = 1;
1432 * Only really load the thing if we've got an NNTP stream
1433 * otherwise we're just using mail_fetch_overview to load the
1434 * IMAP envelope cache with the specific set of messages
1435 * in a single RTT.
1437 idata.stream = stream;
1438 idata.rawno = rawno;
1439 idata.msgno = mn_raw2m(sp_msgmap(stream), idata.rawno);
1440 idata.size = obuf->optional.octets;
1441 idata.from = obuf->from;
1442 idata.date = obuf->date;
1443 idata.subject = obuf->subject;
1445 ice = (*format_index_line)(&idata);
1446 if(idata.bogus && ice){
1447 if(THRD_INDX()){
1448 if(ice->tice)
1449 clear_ice(&ice->tice);
1451 else
1452 clear_ice(&ice);
1454 else if(F_OFF(F_QUELL_NEWS_ENV_CB, ps_global)
1455 && (!THRD_INDX() || (ice && ice->tice))
1456 && !msgline_hidden(stream, sp_msgmap(stream), idata.msgno, 0)
1457 && pith_opt_paint_index_hline){
1458 (*pith_opt_paint_index_hline)(stream, idata.msgno, ice);
1464 ICE_S *
1465 build_header_work(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
1466 long int msgno, long int top_msgno, int msgcount, int *fetched)
1468 ICE_S *ice, *ic;
1469 MESSAGECACHE *mc;
1470 long n, i, cnt, rawno, visible, limit = -1L;
1472 rawno = mn_m2raw(msgmap, msgno);
1474 /* cache hit? */
1475 if(THRD_INDX()){
1476 ice = fetch_ice(stream, rawno);
1477 if(!ice)
1478 return(NULL);
1480 if(ice->tice && ice->tice->ifield
1481 && ice->tice->color_lookup_done && ice->tice->widths_done){
1482 #ifdef DEBUG
1483 char buf[MAX_SCREEN_COLS+1];
1484 simple_index_line(buf, sizeof(buf), ice->tice, msgno);
1485 #endif
1486 dprint((9, "Hitt: Returning %p -> <%s (%d)\n",
1487 ice->tice,
1488 buf[0] ? buf : "?",
1489 buf[0] ? strlen(buf) : 0));
1490 return(ice);
1493 else{
1494 ice = fetch_ice(stream, rawno);
1495 if(!ice)
1496 return(NULL);
1498 if(ice->ifield && ice->color_lookup_done && ice->widths_done){
1499 #ifdef DEBUG
1500 char buf[MAX_SCREEN_COLS+1];
1501 simple_index_line(buf, sizeof(buf), ice, msgno);
1502 #endif
1503 dprint((9, "Hit: Returning %p -> <%s (%d)\n",
1504 ice,
1505 buf[0] ? buf : "?",
1506 buf[0] ? strlen(buf) : 0));
1507 return(ice);
1512 * If we are in THRD_INDX() and the width changed we don't currently
1513 * have a method of fixing just the widths and print_format strings.
1514 * Instead, we clear the index cache entry and start over.
1516 if(THRD_INDX() && ice && ice->tice && ice->tice->ifield
1517 && !ice->tice->widths_done){
1518 clear_ice(&ice->tice);
1522 * Fetch everything we need to start filling in the index line
1523 * explicitly via mail_fetch_overview. On an nntp stream
1524 * this has the effect of building the index lines in the
1525 * load_overview callback. Under IMAP we're either getting
1526 * the envelope data via the imap_envelope callback or
1527 * preloading the cache. Either way, we're getting exactly
1528 * what we want rather than relying on linear lookahead sort
1529 * of prefetch...
1531 if(!(fetched && *fetched) && index_in_overview(stream)
1532 && ((THRD_INDX() && !(ice->tice && ice->tice->ifield))
1533 || (!THRD_INDX() && !ice->ifield))){
1534 char *seq;
1535 int count;
1536 MESSAGECACHE *mc;
1537 PINETHRD_S *thrd;
1539 if(fetched)
1540 (*fetched)++;
1542 /* clear sequence bits */
1543 for(n = 1L; n <= stream->nmsgs; n++)
1544 if((mc = mail_elt(stream, n)) != NULL)
1545 mc->sequence = 0;
1548 * Light interesting bits
1549 * NOTE: not set above because m2raw's cheaper
1550 * than raw2m for every message
1554 * Unfortunately, it is expensive to calculate visible pages
1555 * in thread index if we are zoomed, so we don't try.
1557 if(THRD_INDX() && any_lflagged(msgmap, MN_HIDE))
1558 visible = msgmap->visible_threads;
1559 else if(THREADING() && sp_viewing_a_thread(stream)){
1561 * We know that all visible messages in the thread are marked
1562 * with MN_CHID2.
1564 for(visible = 0L, n = top_msgno;
1565 visible < msgcount && n <= mn_get_total(msgmap);
1566 n++){
1568 if(!get_lflag(stream, msgmap, n, MN_CHID2))
1569 break;
1571 if(!msgline_hidden(stream, msgmap, n, 0))
1572 visible++;
1576 else
1577 visible = mn_get_total(msgmap)
1578 - any_lflagged(msgmap, MN_HIDE|MN_CHID);
1580 limit = MIN(visible, msgcount);
1582 if(THRD_INDX()){
1583 count = i = 0;
1586 * First add the msgno we're asking for in case it
1587 * isn't visible.
1589 thrd = fetch_thread(stream, mn_m2raw(msgmap, msgno));
1590 if(msgno <= mn_get_total(msgmap)
1591 && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){
1592 count += mark_msgs_in_thread(stream, thrd, msgmap);
1595 thrd = fetch_thread(stream, mn_m2raw(msgmap, top_msgno));
1598 * Loop through visible threads, marking them for fetching.
1599 * Stop at end of screen or sooner if we run out of visible
1600 * threads.
1602 while(thrd){
1603 n = mn_raw2m(msgmap, thrd->rawno);
1604 if(n >= msgno
1605 && n <= 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 if(++i >= limit)
1611 break;
1613 /* find next thread which is visible */
1615 if(mn_get_revsort(msgmap) && thrd->prevthd)
1616 thrd = fetch_thread(stream, thrd->prevthd);
1617 else if(!mn_get_revsort(msgmap) && thrd->nextthd)
1618 thrd = fetch_thread(stream, thrd->nextthd);
1619 else
1620 thrd = NULL;
1621 } while(thrd
1622 && msgline_hidden(stream, msgmap,
1623 mn_raw2m(msgmap, thrd->rawno), 0));
1626 else{
1627 count = i = 0;
1630 * First add the msgno we're asking for in case it
1631 * isn't visible.
1633 if(msgno > 0L && msgno <= mn_get_total(msgmap)
1634 && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,msgno)))) || !ic->ifield)){
1635 if((thrd = fetch_thread(stream, rawno)) != NULL){
1637 * If we're doing a MUTTLIKE display the index line
1638 * may depend on the thread parent, and grandparent,
1639 * and further back. So just fetch the whole thread
1640 * in that case.
1642 if(THREADING()
1643 && ps_global->thread_disp_style == THREAD_MUTTLIKE
1644 && thrd->top)
1645 thrd = fetch_thread(stream, thrd->top);
1647 count += mark_msgs_in_thread(stream, thrd, msgmap);
1649 else if(rawno > 0L && rawno <= stream->nmsgs
1650 && (mc = mail_elt(stream,rawno))
1651 && !mc->private.msg.env){
1652 mc->sequence = 1;
1653 count++;
1657 n = top_msgno;
1658 while(1){
1659 if(n >= msgno
1660 && n <= mn_get_total(msgmap)
1661 && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,n)))) || !ic->ifield)){
1662 if((thrd = fetch_thread(stream, rawno)) != NULL){
1664 * If we're doing a MUTTLIKE display the index line
1665 * may depend on the thread parent, and grandparent,
1666 * and further back. So just fetch the whole thread
1667 * in that case.
1669 if(THREADING()
1670 && ps_global->thread_disp_style == THREAD_MUTTLIKE
1671 && thrd->top)
1672 thrd = fetch_thread(stream, thrd->top);
1674 count += mark_msgs_in_thread(stream, thrd, msgmap);
1676 else if(rawno > 0L && rawno <= stream->nmsgs
1677 && (mc = mail_elt(stream,rawno))
1678 && !mc->private.msg.env){
1679 mc->sequence = 1;
1680 count++;
1684 if(++i >= limit)
1685 break;
1687 /* find next n which is visible */
1688 while(++n <= mn_get_total(msgmap)
1689 && msgline_hidden(stream, msgmap, n, 0))
1694 if(count){
1695 seq = build_sequence(stream, NULL, NULL);
1696 if(seq){
1697 ps_global->dont_count_flagchanges = 1;
1698 mail_fetch_overview_sequence(stream, seq,
1699 (stream->dtb && stream->dtb->name
1700 && !strcmp(stream->dtb->name, "imap"))
1701 ? NULL : load_overview);
1702 ps_global->dont_count_flagchanges = 0;
1703 fs_give((void **) &seq);
1708 * reassign ice from the cache as it may've been built
1709 * within the overview callback or it may have become stale
1710 * in the prior sequence bit setting loop ...
1712 rawno = mn_m2raw(msgmap, msgno);
1713 ice = fetch_ice(stream, rawno);
1714 if(!ice)
1715 return(NULL);
1718 if((THRD_INDX() && !(ice->tice && ice->tice->ifield))
1719 || (!THRD_INDX() && !ice->ifield)){
1720 INDEXDATA_S idata;
1723 * With pre-fetching/callback-formatting done and no success,
1724 * fall into formatting the requested line...
1726 memset(&idata, 0, sizeof(INDEXDATA_S));
1727 idata.stream = stream;
1728 idata.msgno = msgno;
1729 idata.rawno = mn_m2raw(msgmap, msgno);
1730 if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
1731 && (mc = mail_elt(stream, idata.rawno))){
1732 idata.size = mc->rfc822_size;
1733 index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
1735 else
1736 idata.bogus = 2;
1738 ice = (*format_index_line)(&idata);
1739 if(!ice)
1740 return(NULL);
1744 * If needed, reset the print_format strings so that they add up to
1745 * the right total width. The reset width functionality isn't implemented
1746 * for THRD_INDX() so we are just doing a complete rebuild in that
1747 * case. This is driven by the clear_ice() call in clear_index_cache_ent()
1748 * so it should never be the case that THRD_INDX() is true and only
1749 * widths_done needs to be fixed.
1751 if((!THRD_INDX() && ice->ifield && !ice->widths_done)){
1752 ICE_S *working_ice;
1753 IFIELD_S *ifield;
1754 INDEX_COL_S *cdesc;
1756 if(need_format_setup(stream))
1757 setup_header_widths(stream);
1759 if(THRD_INDX())
1760 working_ice = ice ? ice->tice : NULL;
1761 else
1762 working_ice = ice;
1764 if(working_ice){
1766 * First fix the ifield widths. The cdescs with nonzero widths
1767 * should correspond to the ifields that are defined.
1769 ifield = working_ice->ifield;
1770 for(cdesc = ps_global->index_disp_format;
1771 cdesc->ctype != iNothing && ifield; cdesc++){
1772 if(cdesc->width){
1773 if(cdesc->ctype != ifield->ctype){
1774 dprint((1, "build_header_work(%ld): cdesc->ctype=%d != ifield->ctype=%d NOT SUPPOSED TO HAPPEN!\n", msgno, (int) cdesc->ctype, (int) ifield->ctype));
1775 assert(0);
1778 ifield->width = cdesc->width;
1779 ifield = ifield->next;
1783 /* fix the print_format strings and widths */
1784 for(ifield = working_ice->ifield; ifield; ifield = ifield->next)
1785 set_ielem_widths_in_field(ifield);
1787 working_ice->widths_done = 1;
1791 if(THRD_INDX() && ice->tice)
1792 ice->tice->color_lookup_done = 1;
1795 * Look for a color for this line (and other lines in the current
1796 * view). This does a SEARCH for each role which has a color until
1797 * it finds a match. This will be satisfied by the c-client
1798 * cache created by the mail_fetch_overview above if it is a header
1799 * search.
1801 if(!THRD_INDX() && !ice->color_lookup_done){
1802 COLOR_PAIR *linecolor;
1803 SEARCHSET *ss, *s;
1804 ICE_S *ic;
1805 PAT_STATE *pstate = NULL;
1807 if(pico_usingcolor()){
1808 if(limit < 0L){
1809 if(THREADING() && sp_viewing_a_thread(stream)){
1810 for(visible = 0L, n = top_msgno;
1811 visible < msgcount && n <= mn_get_total(msgmap);
1812 n++){
1814 if(!get_lflag(stream, msgmap, n, MN_CHID2))
1815 break;
1817 if(!msgline_hidden(stream, msgmap, n, 0))
1818 visible++;
1822 else
1823 visible = mn_get_total(msgmap)
1824 - any_lflagged(msgmap, MN_HIDE|MN_CHID);
1826 limit = MIN(visible, msgcount);
1828 /* clear sequence bits */
1829 for(n = 1L; n <= stream->nmsgs; n++)
1830 if((mc = mail_elt(stream, n)) != NULL)
1831 mc->sequence = 0;
1833 cnt = i = 0;
1834 n = top_msgno;
1835 while(1){
1836 if(n >= msgno
1837 && n <= mn_get_total(msgmap)
1838 && (!(ic=fetch_ice(stream,(rawno = mn_m2raw(msgmap, n)))) || !ic->color_lookup_done)){
1840 if(rawno >= 1L && rawno <= stream->nmsgs
1841 && (mc = mail_elt(stream, rawno))){
1842 mc->sequence = 1;
1843 cnt++;
1847 if(++i >= limit)
1848 break;
1850 /* find next n which is visible */
1851 while(++n <= mn_get_total(msgmap)
1852 && msgline_hidden(stream, msgmap, n, 0))
1857 * Why is there a loop here? The first call to get_index_line_color
1858 * will return a set of messages which match one of the roles.
1859 * Then, we eliminate those messages from the search set and try
1860 * again. This time we'd get past that role and into a different
1861 * role. Because of that, we hang onto the state and don't reset
1862 * to the first_pattern on the second and subsequent times
1863 * through the loop, avoiding fruitless match_pattern calls in
1864 * get_index_line_color.
1865 * Before the first call, pstate should be set to NULL.
1867 while(cnt > 0L){
1868 ss = build_searchset(stream);
1869 if(ss){
1870 int colormatch;
1872 linecolor = NULL;
1873 colormatch = get_index_line_color(stream, ss, &pstate,
1874 &linecolor);
1877 * Assign this color to all matched msgno's and
1878 * turn off the sequence bit so we won't check
1879 * for them again.
1881 if(colormatch){
1882 for(s = ss; s; s = s->next){
1883 for(n = s->first; n <= s->last; n++){
1884 if(n >= 1L && n <= stream->nmsgs
1885 && (mc = mail_elt(stream, n))
1886 && mc->searched){
1887 cnt--;
1888 mc->sequence = 0;
1889 ic = fetch_ice(stream, n);
1890 if(ic){
1891 ic->color_lookup_done = 1;
1892 if(linecolor)
1893 ic->linecolor = new_color_pair(linecolor->fg,
1894 linecolor->bg);
1900 if(linecolor)
1901 free_color_pair(&linecolor);
1903 else{
1904 /* have to mark the rest of the lookups done */
1905 for(s = ss; s && cnt > 0; s = s->next){
1906 for(n = s->first; n <= s->last && cnt > 0; n++){
1907 if(n >= 1L && n <= stream->nmsgs
1908 && (mc = mail_elt(stream, n))
1909 && mc->sequence){
1910 cnt--;
1911 ic = fetch_ice(stream, n);
1912 if(ic)
1913 ic->color_lookup_done = 1;
1918 /* just making sure */
1919 cnt = 0L;
1922 mail_free_searchset(&ss);
1924 else
1925 cnt = 0L;
1928 ice = fetch_ice(stream, mn_m2raw(msgmap, msgno));
1930 else
1931 ice->color_lookup_done = 1;
1934 return(ice); /* Return formatted index data */
1939 day_of_week(struct date *d)
1941 int m, y;
1943 m = d->month;
1944 y = d->year;
1945 if(m <= 2){
1946 m += 9;
1947 y--;
1949 else
1950 m -= 3; /* March is month 0 */
1952 return((d->day+2+((7+31*m)/12)+y+(y/4)+(y/400)-(y/100))%7);
1956 static int daytab[2][13] = {
1957 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
1958 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
1962 day_of_year(struct date *d)
1964 int i, leap, doy;
1966 if(d->year <= 0 || d->month < 1 || d->month > 12)
1967 return(-1);
1969 doy = d->day;
1970 leap = (d->year%4 == 0 && d->year%100 != 0) || d->year%400 == 0;
1971 for(i = 1; i < d->month; i++)
1972 doy += daytab[leap][i];
1974 return(doy);
1979 /*----------------------------------------------------------------------
1980 Format a string summarizing the message header for index on screen
1982 Args: buffer -- buffer to place formatted line
1983 idata -- snot it takes to format the line
1985 Result: returns pointer given buffer IF entry formatted
1986 else NULL if there was a problem (but the buffer is
1987 still suitable for display)
1988 ----*/
1989 ICE_S *
1990 format_index_index_line(INDEXDATA_S *idata)
1992 char str[BIGWIDTH+1], to_us, status, *field,
1993 *p, *newsgroups;
1994 int i, collapsed = 0, start, fromfield;
1995 long l, score;
1996 BODY *body = NULL;
1997 MESSAGECACHE *mc;
1998 ADDRESS *addr, *toaddr, *ccaddr, *last_to;
1999 PINETHRD_S *thrd = NULL;
2000 INDEX_COL_S *cdesc = NULL;
2001 ICE_S *ice, **icep;
2002 IFIELD_S *ifield;
2003 IELEM_S *ielem;
2004 COLOR_PAIR *color = NULL;
2005 struct variable *vars = ps_global->vars;
2007 dprint((8, "=== format_index_line(msgno=%ld,rawno=%ld) ===\n",
2008 idata ? idata->msgno : -1, idata ? idata->rawno : -1));
2011 ice = fetch_ice(idata->stream, idata->rawno);
2012 if(!ice)
2013 return(NULL);
2015 free_ifield(&ice->ifield);
2018 * Operate on a temporary copy of ice. The reason for this
2019 * is that we may end up causing a pine_mail_fetchenvelope() call
2020 * (e.g., in to_us_symbol_for_thread()) that causes an mm_flags()
2021 * and mm_flags may do a clear_ice(), freeing the ice we are working
2022 * on out from under us. We try to fetch everything we need in
2023 * build_header_work() but c-client will short-circuit our request
2024 * if we already got the raw header for some reason. One possible
2025 * reason is a categorizer command in a filter. In that case
2026 * we still need a fetch fast to get the rest of the envelope data.
2028 ice = copy_ice(ice);
2030 /* is this a collapsed thread index line? */
2031 if(!idata->bogus && THREADING()){
2032 thrd = fetch_thread(idata->stream, idata->rawno);
2033 collapsed = thrd && thrd->next
2034 && get_lflag(idata->stream, NULL,
2035 idata->rawno, MN_COLL);
2038 /* calculate contents of the required fields */
2039 for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++)
2040 if(cdesc->width){
2041 memset(str, 0, sizeof(str));
2042 ifield = new_ifield(&ice->ifield);
2043 ifield->ctype = cdesc->ctype;
2044 ifield->width = cdesc->width;
2045 fromfield = 0;
2047 if(idata->bogus){
2048 if(cdesc->ctype == iMessNo)
2049 snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
2050 else if(idata->bogus < 2 && (cdesc->ctype == iSubject
2051 || cdesc->ctype == iSubjectText
2052 || cdesc->ctype == iSubjKey
2053 || cdesc->ctype == iSubjKeyText
2054 || cdesc->ctype == iSubjKeyInit
2055 || cdesc->ctype == iSubjKeyInitText))
2056 snprintf(str, sizeof(str), "%s", _("[ No Message Text Available ]"));
2058 else
2059 switch(cdesc->ctype){
2060 case iStatus:
2061 to_us = status = ' ';
2062 if(collapsed){
2063 thrd = fetch_thread(idata->stream, idata->rawno);
2064 to_us = to_us_symbol_for_thread(idata->stream, thrd, 1);
2065 status = status_symbol_for_thread(idata->stream, thrd,
2066 cdesc->ctype);
2068 else{
2069 if(idata->rawno > 0L && idata->rawno <= idata->stream->nmsgs
2070 && (mc=mail_elt(idata->stream,idata->rawno)) && mc->flagged)
2071 to_us = '*'; /* simple */
2072 else if(!IS_NEWS(idata->stream)){
2073 for(addr = fetch_to(idata); addr; addr = addr->next)
2074 if(address_is_us(addr, ps_global)){
2075 ice->to_us = 1;
2076 if(to_us == ' ')
2077 to_us = '+';
2079 break;
2082 if(to_us != '+' && resent_to_us(idata)){
2083 ice->to_us = 1;
2084 if(to_us == ' ')
2085 to_us = '+';
2088 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
2089 for(addr = fetch_cc(idata); addr; addr = addr->next)
2090 if(address_is_us(addr, ps_global)){
2091 ice->cc_us = 1;
2092 to_us = '-';
2093 break;
2097 status = (!idata->stream || !IS_NEWS(idata->stream)
2098 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))
2099 ? 'N' : ' ';
2101 if(mc->seen)
2102 status = ' ';
2104 if(user_flag_is_set(idata->stream, idata->rawno, FORWARDED_FLAG))
2105 status = 'F';
2107 if(mc->answered)
2108 status = 'A';
2110 if(mc->deleted)
2111 status = 'D';
2114 snprintf(str, sizeof(str), "%c %c", to_us, status);
2116 ifield->leftadj = 1;
2117 for(i = 0; i < 3; i++){
2118 ielem = new_ielem(&ifield->ielem);
2119 ielem->freedata = 1;
2120 ielem->data = (char *) fs_get(2 * sizeof(char));
2121 ielem->data[0] = str[i];
2122 ielem->data[1] = '\0';
2123 ielem->datalen = 1;
2124 set_print_format(ielem, 1, ifield->leftadj);
2127 if(pico_usingcolor()){
2129 if(str[0] == '*'){
2130 if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
2131 ielem = ifield->ielem;
2132 ielem->freecolor = 1;
2133 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
2136 else if(str[0] == '+' || str[0] == '-'){
2137 if(VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
2138 ielem = ifield->ielem;
2139 ielem->freecolor = 1;
2140 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
2144 if(str[2] == 'D'){
2145 if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
2146 ielem = ifield->ielem->next->next;
2147 ielem->freecolor = 1;
2148 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
2151 else if(str[2] == 'A'){
2152 if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
2153 ielem = ifield->ielem->next->next;
2154 ielem->freecolor = 1;
2155 ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
2158 else if(str[2] == 'F'){
2159 if(VAR_IND_FWD_FORE_COLOR && VAR_IND_FWD_BACK_COLOR){
2160 ielem = ifield->ielem->next->next;
2161 ielem->freecolor = 1;
2162 ielem->color = new_color_pair(VAR_IND_FWD_FORE_COLOR, VAR_IND_FWD_BACK_COLOR);
2165 else if(str[2] == 'N'){
2166 if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
2167 ielem = ifield->ielem->next->next;
2168 ielem->freecolor = 1;
2169 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
2174 break;
2176 case iFStatus:
2177 case iIStatus:
2178 case iSIStatus:
2180 char new, answered, deleted, flagged;
2182 if(collapsed){
2183 thrd = fetch_thread(idata->stream, idata->rawno);
2184 to_us = to_us_symbol_for_thread(idata->stream, thrd, 0);
2186 else{
2187 to_us = ' ';
2188 if(!IS_NEWS(idata->stream)){
2189 for(addr = fetch_to(idata); addr; addr = addr->next)
2190 if(address_is_us(addr, ps_global)){
2191 to_us = '+';
2192 break;
2195 if(to_us == ' ' && resent_to_us(idata))
2196 to_us = '+';
2198 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
2199 for(addr = fetch_cc(idata); addr; addr = addr->next)
2200 if(address_is_us(addr, ps_global)){
2201 to_us = '-';
2202 break;
2207 new = answered = deleted = flagged = ' ';
2209 if(collapsed){
2210 unsigned long save_branch, cnt, tot_in_thrd;
2213 * Branch is a sibling, not part of the thread, so
2214 * don't consider it when displaying this line.
2216 save_branch = thrd->branch;
2217 thrd->branch = 0L;
2219 tot_in_thrd = count_flags_in_thread(idata->stream, thrd,
2220 F_NONE);
2222 cnt = count_flags_in_thread(idata->stream, thrd, F_DEL);
2223 if(cnt)
2224 deleted = (cnt == tot_in_thrd) ? 'D' : 'd';
2226 cnt = count_flags_in_thread(idata->stream, thrd, F_ANS);
2227 if(cnt)
2228 answered = (cnt == tot_in_thrd) ? 'A' : 'a';
2230 /* no lower case *, same thing for some or all */
2231 if(count_flags_in_thread(idata->stream, thrd, F_FLAG))
2232 flagged = '*';
2234 new = status_symbol_for_thread(idata->stream, thrd,
2235 cdesc->ctype);
2237 thrd->branch = save_branch;
2239 else{
2240 mc = (idata->rawno > 0L && idata->stream
2241 && idata->rawno <= idata->stream->nmsgs)
2242 ? mail_elt(idata->stream, idata->rawno) : NULL;
2243 if(mc && mc->valid){
2244 if(cdesc->ctype == iIStatus || cdesc->ctype == iSIStatus){
2245 if(mc->recent)
2246 new = mc->seen ? 'R' : 'N';
2247 else if (!mc->seen)
2248 new = 'U';
2250 else if(!mc->seen
2251 && (!IS_NEWS(idata->stream)
2252 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)))
2253 new = 'N';
2255 if(mc->answered)
2256 answered = 'A';
2258 if(mc->deleted)
2259 deleted = 'D';
2261 if(mc->flagged)
2262 flagged = '*';
2266 snprintf(str, sizeof(str), "%c %c%c%c%c", to_us, flagged, new,
2267 answered, deleted);
2269 if(cdesc->ctype == iSIStatus)
2270 start = 2;
2271 else
2272 start = 0;
2274 ifield->leftadj = 1;
2275 for(i = start; i < 6; i++){
2276 ielem = new_ielem(&ifield->ielem);
2277 ielem->freedata = 1;
2278 ielem->data = (char *) fs_get(2 * sizeof(char));
2279 ielem->data[0] = str[i];
2280 ielem->data[1] = '\0';
2281 ielem->datalen = 1;
2282 set_print_format(ielem, 1, ifield->leftadj);
2285 if(pico_usingcolor()){
2287 if(str[0] == '+' || str[0] == '-'){
2288 if(start == 0
2289 && VAR_IND_PLUS_FORE_COLOR
2290 && VAR_IND_PLUS_BACK_COLOR){
2291 ielem = ifield->ielem;
2292 ielem->freecolor = 1;
2293 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
2297 if(str[2] == '*'){
2298 if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
2299 if(start == 2)
2300 ielem = ifield->ielem;
2301 else
2302 ielem = ifield->ielem->next->next;
2304 ielem->freecolor = 1;
2305 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
2309 if(str[3] == 'N' || str[3] == 'n'){
2310 if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
2311 if(start == 2)
2312 ielem = ifield->ielem->next;
2313 else
2314 ielem = ifield->ielem->next->next->next;
2316 ielem->freecolor = 1;
2317 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
2320 else if(str[3] == 'R' || str[3] == 'r'){
2321 if(VAR_IND_REC_FORE_COLOR && VAR_IND_REC_BACK_COLOR){
2322 if(start == 2)
2323 ielem = ifield->ielem->next;
2324 else
2325 ielem = ifield->ielem->next->next->next;
2327 ielem->freecolor = 1;
2328 ielem->color = new_color_pair(VAR_IND_REC_FORE_COLOR, VAR_IND_REC_BACK_COLOR);
2331 else if(str[3] == 'U' || str[3] == 'u'){
2332 if(VAR_IND_UNS_FORE_COLOR && VAR_IND_UNS_BACK_COLOR){
2333 if(start == 2)
2334 ielem = ifield->ielem->next;
2335 else
2336 ielem = ifield->ielem->next->next->next;
2338 ielem->freecolor = 1;
2339 ielem->color = new_color_pair(VAR_IND_UNS_FORE_COLOR, VAR_IND_UNS_BACK_COLOR);
2343 if(str[4] == 'A' || str[4] == 'a'){
2344 if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
2345 if(start == 2)
2346 ielem = ifield->ielem->next->next;
2347 else
2348 ielem = ifield->ielem->next->next->next->next;
2350 ielem->freecolor = 1;
2351 ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
2355 if(str[5] == 'D' || str[5] == 'd'){
2356 if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
2357 if(start == 2)
2358 ielem = ifield->ielem->next->next->next;
2359 else
2360 ielem = ifield->ielem->next->next->next->next->next;
2362 ielem->freecolor = 1;
2363 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
2369 break;
2371 case iMessNo:
2373 * This is a special case. The message number is
2374 * generated on the fly in the painting routine.
2375 * But the data array is allocated here in case it
2376 * is useful for the paint routine.
2378 snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
2379 break;
2381 case iArrow:
2382 snprintf(str, sizeof(str), "%-*.*s", ifield->width, ifield->width, " ");
2383 if(VAR_IND_ARR_FORE_COLOR && VAR_IND_ARR_BACK_COLOR){
2384 ifield->leftadj = 1;
2385 ielem = new_ielem(&ifield->ielem);
2386 ielem->freedata = 1;
2387 ielem->data = cpystr(str);
2388 ielem->datalen = strlen(str);
2389 set_print_format(ielem, ifield->width, ifield->leftadj);
2390 ielem->freecolor = 1;
2391 ielem->color = new_color_pair(VAR_IND_ARR_FORE_COLOR,
2392 VAR_IND_ARR_BACK_COLOR);
2395 break;
2397 case iScore:
2398 score = get_msg_score(idata->stream, idata->rawno);
2399 if(score == SCORE_UNDEF){
2400 SEARCHSET *ss = NULL;
2402 ss = mail_newsearchset();
2403 ss->first = ss->last = (unsigned long) idata->rawno;
2404 if(ss){
2406 * This looks like it might be expensive to get the
2407 * score for each message when needed but it shouldn't
2408 * be too bad because we know we have the envelope
2409 * data cached. We can't calculate all of the scores
2410 * we need for the visible messages right here in
2411 * one fell swoop because we don't have the other
2412 * envelopes yet. And we can't get the other
2413 * envelopes at this point because we may be in
2414 * the middle of a c-client callback (pine_imap_env).
2415 * (Actually we could, because we know whether or
2416 * not we're in the callback because of the no_fetch
2417 * parameter.)
2418 * We have another problem if the score rules depend
2419 * on something other than envelope data. I guess they
2420 * only do that if they have an alltext (search the
2421 * text of the message) definition. So, we're going
2422 * to pass no_fetch to calculate_scores so that it
2423 * can return an error if we need the text data but
2424 * can't get it because of no_fetch. Setting bogus
2425 * will cause us to do the scores calculation later
2426 * when we are no longer in the callback.
2428 idata->bogus =
2429 (calculate_some_scores(idata->stream,
2430 ss, idata->no_fetch) == 0)
2431 ? 1 : 0;
2432 score = get_msg_score(idata->stream, idata->rawno);
2433 mail_free_searchset(&ss);
2437 snprintf(str, sizeof(str), "%ld", score != SCORE_UNDEF ? score : 0L);
2438 break;
2440 case iDate: case iMonAbb: case iLDate:
2441 case iSDate: case iSTime:
2442 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
2443 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
2444 case iSDateIsoS: case iSDateIso:
2445 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
2446 case iSDateTime:
2447 case iSDateTimeIsoS: case iSDateTimeIso:
2448 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
2449 case iSDateTime24:
2450 case iSDateTimeIsoS24: case iSDateTimeIso24:
2451 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
2452 case iTimezone: case iYear: case iYear2Digit:
2453 case iRDate: case iDay: case iDay2Digit: case iMon2Digit:
2454 case iDayOrdinal: case iMon: case iMonLong:
2455 case iDayOfWeekAbb: case iDayOfWeek:
2456 case iPrefDate: case iPrefTime: case iPrefDateTime:
2457 date_str(fetch_date(idata), cdesc->ctype, 0, str, sizeof(str), cdesc->monabb_width);
2458 break;
2460 case iFromTo:
2461 case iFromToNotNews:
2462 case iFrom:
2463 case iAddress:
2464 case iMailbox:
2465 fromfield++;
2466 from_str(cdesc->ctype, idata, str, sizeof(str), ice);
2467 break;
2469 case iTo:
2470 if(((field = ((addr = fetch_to(idata))
2471 ? "To"
2472 : (addr = fetch_cc(idata))
2473 ? "Cc"
2474 : NULL))
2475 && !set_index_addr(idata, field, addr, NULL, BIGWIDTH, str))
2476 || !field)
2477 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2478 snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, newsgroups);
2480 break;
2482 case iCc:
2483 set_index_addr(idata, "Cc", fetch_cc(idata), NULL, BIGWIDTH, str);
2484 break;
2486 case iRecips:
2487 toaddr = fetch_to(idata);
2488 ccaddr = fetch_cc(idata);
2489 for(last_to = toaddr;
2490 last_to && last_to->next;
2491 last_to = last_to->next)
2494 /* point end of to list temporarily at cc list */
2495 if(last_to)
2496 last_to->next = ccaddr;
2498 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2500 if(last_to)
2501 last_to->next = NULL;
2503 break;
2505 case iSender:
2506 fromfield++;
2507 if((addr = fetch_sender(idata)) != NULL)
2508 set_index_addr(idata, "Sender", addr, NULL, BIGWIDTH, str);
2510 break;
2512 case iInit:
2513 {ADDRESS *addr;
2515 if((addr = fetch_from(idata)) && addr->personal){
2516 char *name, *initials = NULL;
2518 name = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
2519 SIZEOF_20KBUF, addr->personal);
2520 if(name == addr->personal){
2521 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
2522 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
2523 name = (char *) tmp_20k_buf;
2526 if(name && *name){
2527 initials = reply_quote_initials(name);
2528 snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, initials);
2533 break;
2535 case iSize:
2536 /* 0 ... 9999 */
2537 if((l = fetch_size(idata)) < 10*1000L)
2538 snprintf(str, sizeof(str), "(%lu)", l);
2539 /* 10K ... 999K */
2540 else if(l < 1000L*1000L - 1000L/2){
2541 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2542 snprintf(str, sizeof(str), "(%luK)", l);
2544 /* 1.0M ... 99.9M */
2545 else if(l < 1000L*100L*1000L - 100L*1000L/2){
2546 l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
2547 ? 1L : 0L);
2548 snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
2550 /* 100M ... 2000M */
2551 else if(l <= 2*1000L*1000L*1000L){
2552 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2553 ? 1L : 0L);
2554 snprintf(str, sizeof(str), "(%luM)", l);
2556 else
2557 snprintf(str, sizeof(str), "(HUGE!)");
2559 break;
2561 case iSizeComma:
2562 /* 0 ... 99,999 */
2563 if((l = fetch_size(idata)) < 100*1000L)
2564 snprintf(str, sizeof(str), "(%s)", comatose(l));
2565 /* 100K ... 9,999K */
2566 else if(l < 10L*1000L*1000L - 1000L/2){
2567 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2568 snprintf(str, sizeof(str), "(%sK)", comatose(l));
2570 /* 10.0M ... 999.9M */
2571 else if(l < 1000L*1000L*1000L - 100L*1000L/2){
2572 l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
2573 ? 1L : 0L);
2574 snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
2576 /* 1,000M ... 2,000M */
2577 else if(l <= 2*1000L*1000L*1000L){
2578 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2579 ? 1L : 0L);
2580 snprintf(str, sizeof(str), "(%sM)", comatose(l));
2582 else
2583 snprintf(str, sizeof(str), "(HUGE!)");
2585 break;
2587 case iSizeNarrow:
2588 /* 0 ... 999 */
2589 if((l = fetch_size(idata)) < 1000L)
2590 snprintf(str, sizeof(str), "(%lu)", l);
2591 /* 1K ... 99K */
2592 else if(l < 100L*1000L - 1000L/2){
2593 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2594 snprintf(str, sizeof(str), "(%luK)", l);
2596 /* .1M ... .9M */
2597 else if(l < 1000L*1000L - 100L*1000L/2){
2598 l = l/(100L*1000L) + (l%(100L*1000L) >= 100L*1000L/2
2599 ? 1L : 0L);
2600 snprintf(str, sizeof(str), "(.%luM)", l);
2602 /* 1M ... 99M */
2603 else if(l < 1000L*100L*1000L - 1000L*1000L/2){
2604 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2605 ? 1L : 0L);
2606 snprintf(str, sizeof(str), "(%luM)", l);
2608 /* .1G ... .9G */
2609 else if(l < 1000L*1000L*1000L - 100L*1000L*1000L/2){
2610 l = l/(100L*1000L*1000L) + (l%(100L*1000L*1000L) >=
2611 (100L*1000L*1000L/2) ? 1L : 0L);
2612 snprintf(str, sizeof(str), "(.%luG)", l);
2614 /* 1G ... 2G */
2615 else if(l <= 2*1000L*1000L*1000L){
2616 l = l/(1000L*1000L*1000L) + (l%(1000L*1000L*1000L) >=
2617 (1000L*1000L*1000L/2) ? 1L : 0L);
2618 snprintf(str, sizeof(str), "(%luG)", l);
2620 else
2621 snprintf(str, sizeof(str), "(HUGE!)");
2623 break;
2625 /* From Carl Jacobsen <carl@ucsd.edu> */
2626 case iKSize:
2627 l = fetch_size(idata);
2628 l = (l / 1024L) + (l % 1024L != 0 ? 1 : 0);
2630 if(l < 1024L) { /* 0k .. 1023k */
2631 snprintf(str, sizeof(str), "(%luk)", l);
2633 } else if (l < 100L * 1024L){ /* 1.0M .. 99.9M */
2634 snprintf(str, sizeof(str), "(%lu.M)", (l * 10L) / 1024L);
2635 if ((p = strchr(str, '.')) != NULL) {
2636 p--; p[1] = p[0]; p[0] = '.'; /* swap last digit & . */
2638 } else if (l <= 2L * 1024L * 1024L) { /* 100M .. 2048 */
2639 snprintf(str, sizeof(str), "(%luM)", l / 1024L);
2640 } else {
2641 snprintf(str, sizeof(str), "(HUGE!)");
2644 break;
2646 case iDescripSize:
2647 if((body = fetch_body(idata)) != NULL)
2648 switch(body->type){
2649 case TYPETEXT:
2651 mc = (idata->rawno > 0L && idata->stream
2652 && idata->rawno <= idata->stream->nmsgs)
2653 ? mail_elt(idata->stream, idata->rawno) : NULL;
2654 if(mc && mc->rfc822_size < 6000)
2655 snprintf(str, sizeof(str), "(short )");
2656 else if(mc && mc->rfc822_size < 25000)
2657 snprintf(str, sizeof(str), "(medium )");
2658 else if(mc && mc->rfc822_size < 100000)
2659 snprintf(str, sizeof(str), "(long )");
2660 else
2661 snprintf(str, sizeof(str), "(huge )");
2664 break;
2666 case TYPEMULTIPART:
2667 if(strucmp(body->subtype, "MIXED") == 0){
2668 int x;
2670 x = body->nested.part
2671 ? body->nested.part->body.type
2672 : TYPETEXT + 1000;
2673 switch(x){
2674 case TYPETEXT:
2675 if(body->nested.part->body.size.bytes < 6000)
2676 snprintf(str, sizeof(str), "(short+ )");
2677 else if(body->nested.part->body.size.bytes
2678 < 25000)
2679 snprintf(str, sizeof(str), "(medium+)");
2680 else if(body->nested.part->body.size.bytes
2681 < 100000)
2682 snprintf(str, sizeof(str), "(long+ )");
2683 else
2684 snprintf(str, sizeof(str), "(huge+ )");
2685 break;
2687 default:
2688 snprintf(str, sizeof(str), "(multi )");
2689 break;
2692 else if(strucmp(body->subtype, "DIGEST") == 0)
2693 snprintf(str, sizeof(str), "(digest )");
2694 else if(strucmp(body->subtype, "ALTERNATIVE") == 0)
2695 snprintf(str, sizeof(str), "(mul/alt)");
2696 else if(strucmp(body->subtype, "PARALLEL") == 0)
2697 snprintf(str, sizeof(str), "(mul/par)");
2698 else
2699 snprintf(str, sizeof(str), "(multi )");
2701 break;
2703 case TYPEMESSAGE:
2704 snprintf(str, sizeof(str), "(message)");
2705 break;
2707 case TYPEAPPLICATION:
2708 snprintf(str, sizeof(str), "(applica)");
2709 break;
2711 case TYPEAUDIO:
2712 snprintf(str, sizeof(str), "(audio )");
2713 break;
2715 case TYPEIMAGE:
2716 snprintf(str, sizeof(str), "(image )");
2717 break;
2719 case TYPEVIDEO:
2720 snprintf(str, sizeof(str), "(video )");
2721 break;
2723 default:
2724 snprintf(str, sizeof(str), "(other )");
2725 break;
2728 break;
2730 case iAtt:
2731 str[0] = SPACE;
2732 str[1] = '\0';
2733 if((body = fetch_body(idata)) &&
2734 body->type == TYPEMULTIPART &&
2735 strucmp(body->subtype, "ALTERNATIVE") != 0){
2736 PART *part;
2737 int atts = 0;
2739 part = body->nested.part; /* 1st part, don't count */
2740 while(part && part->next && atts < 10){
2741 atts++;
2742 part = part->next;
2745 if(atts > 9)
2746 str[0] = '*';
2747 else if(atts > 0)
2748 str[0] = '0' + atts;
2751 break;
2753 case iSubject:
2754 subj_str(idata, str, sizeof(str), NoKW, 0, ice);
2755 break;
2757 case iSubjectText:
2758 subj_str(idata, str, sizeof(str), NoKW, 1, ice);
2759 break;
2761 case iSubjKey:
2762 subj_str(idata, str, sizeof(str), KW, 0, ice);
2763 break;
2765 case iSubjKeyText:
2766 subj_str(idata, str, sizeof(str), KW, 1, ice);
2767 break;
2769 case iSubjKeyInit:
2770 subj_str(idata, str, sizeof(str), KWInit, 0, ice);
2771 break;
2773 case iSubjKeyInitText:
2774 subj_str(idata, str, sizeof(str), KWInit, 1, ice);
2775 break;
2777 case iOpeningText:
2778 case iOpeningTextNQ:
2779 if(idata->no_fetch)
2780 idata->bogus = 1;
2781 else{
2782 char *first_text;
2784 first_text = fetch_firsttext(idata, cdesc->ctype == iOpeningTextNQ);
2786 if(first_text){
2787 strncpy(str, first_text, BIGWIDTH);
2788 str[BIGWIDTH] = '\0';
2789 fs_give((void **) &first_text);
2793 break;
2795 case iKey:
2796 key_str(idata, KW, ice);
2797 break;
2799 case iKeyInit:
2800 key_str(idata, KWInit, ice);
2801 break;
2803 case iNews:
2804 if((newsgroups = fetch_newsgroups(idata)) != NULL){
2805 strncpy(str, newsgroups, BIGWIDTH);
2806 str[BIGWIDTH] = '\0';
2809 break;
2811 case iNewsAndTo:
2812 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2813 strncpy(str, newsgroups, sizeof(str));
2815 if((l = strlen(str)) < sizeof(str)){
2816 if(sizeof(str) - l < 6)
2817 strncpy(str+l, "...", sizeof(str)-l);
2818 else{
2819 if(l > 0){
2820 strncpy(str+l, " and ", sizeof(str)-l);
2821 set_index_addr(idata, "To", fetch_to(idata),
2822 NULL, BIGWIDTH-l-5, str+l+5);
2823 if(!str[l+5])
2824 str[l] = '\0';
2826 else
2827 set_index_addr(idata, "To", fetch_to(idata),
2828 NULL, BIGWIDTH, str);
2832 break;
2834 case iToAndNews:
2835 set_index_addr(idata, "To", fetch_to(idata),
2836 NULL, BIGWIDTH, str);
2837 if((l = strlen(str)) < sizeof(str) &&
2838 (newsgroups = fetch_newsgroups(idata))){
2839 if(sizeof(str) - l < 6)
2840 strncpy(str+l, "...", sizeof(str)-l);
2841 else{
2842 if(l > 0)
2843 strncpy(str+l, " and ", sizeof(str)-l);
2845 if(l > 0)
2846 strncpy(str+l+5, newsgroups, BIGWIDTH-l-5);
2847 else
2848 strncpy(str, newsgroups, BIGWIDTH);
2852 break;
2854 case iNewsAndRecips:
2855 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2856 strncpy(str, newsgroups, BIGWIDTH);
2858 if((l = strlen(str)) < BIGWIDTH){
2859 if(BIGWIDTH - l < 6)
2860 strncpy(str+l, "...", BIGWIDTH-l);
2861 else{
2862 toaddr = fetch_to(idata);
2863 ccaddr = fetch_cc(idata);
2864 for(last_to = toaddr;
2865 last_to && last_to->next;
2866 last_to = last_to->next)
2869 /* point end of to list temporarily at cc list */
2870 if(last_to)
2871 last_to->next = ccaddr;
2873 if(l > 0){
2874 strncpy(str+l, " and ", sizeof(str)-l);
2875 set_index_addr(idata, "To", toaddr,
2876 NULL, BIGWIDTH-l-5, str+l+5);
2877 if(!str[l+5])
2878 str[l] = '\0';
2880 else
2881 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2883 if(last_to)
2884 last_to->next = NULL;
2888 break;
2890 case iRecipsAndNews:
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 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2904 if(last_to)
2905 last_to->next = NULL;
2907 if((l = strlen(str)) < BIGWIDTH &&
2908 (newsgroups = fetch_newsgroups(idata))){
2909 if(BIGWIDTH - l < 6)
2910 strncpy(str+l, "...", BIGWIDTH-l);
2911 else{
2912 if(l > 0)
2913 strncpy(str+l, " and ", sizeof(str)-l);
2915 if(l > 0)
2916 strncpy(str+l+5, newsgroups, BIGWIDTH-l-5);
2917 else
2918 strncpy(str, newsgroups, BIGWIDTH);
2922 break;
2924 case iPrio:
2925 case iPrioAlpha:
2926 case iPrioBang:
2927 prio_str(idata, cdesc->ctype, ice);
2928 break;
2930 case iHeader:
2931 header_str(idata, cdesc->hdrtok, ice);
2932 break;
2934 case iText:
2935 strncpy(str, (cdesc->hdrtok && cdesc->hdrtok->hdrname) ? cdesc->hdrtok->hdrname : "", sizeof(str));
2936 str[sizeof(str)-1] = '\0';
2937 break;
2939 default:
2940 break;
2944 * If the element wasn't already filled in above, do it here.
2946 if(!ifield->ielem){
2947 ielem = new_ielem(&ifield->ielem);
2949 if((color = hdr_color(itokens[itokensinv[cdesc->ctype].ctype].name, NULL, ps_global->index_token_colors)) != NULL){
2950 if(pico_usingcolor()){
2951 ielem->color = new_color_pair(color->fg, color->bg);
2952 ielem->type = eTypeCol;
2954 free_color_pair(&color);
2957 ielem->freedata = 1;
2958 ielem->data = cpystr(str);
2959 ielem->datalen = strlen(str);
2961 if(fromfield && pico_usingcolor()
2962 && ps_global->VAR_IND_FROM_FORE_COLOR
2963 && ps_global->VAR_IND_FROM_BACK_COLOR){
2964 ielem->type = eTypeCol;
2965 ielem->freecolor = 1;
2966 ielem->color = new_color_pair(ps_global->VAR_IND_FROM_FORE_COLOR,
2967 ps_global->VAR_IND_FROM_BACK_COLOR);
2969 * This space is here so that if the text does
2970 * not extend all the way to the end of the field then
2971 * we'll switch the color back and paint the rest of the
2972 * field in the Normal color or the index line color.
2974 ielem = new_ielem(&ielem);
2975 ielem->freedata = 1;
2976 ielem->data = cpystr(" ");
2977 ielem->datalen = 1;
2979 else if((cdesc->ctype == iOpeningText || cdesc->ctype == iOpeningTextNQ)
2980 && pico_usingcolor()
2981 && ps_global->VAR_IND_OP_FORE_COLOR
2982 && ps_global->VAR_IND_OP_BACK_COLOR){
2983 ielem->type = eTypeCol;
2984 ielem->freecolor = 1;
2985 ielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR,
2986 ps_global->VAR_IND_OP_BACK_COLOR);
2988 * This space is here so that if the text does
2989 * not extend all the way to the end of the field then
2990 * we'll switch the color back and paint the rest of the
2991 * field in the Normal color or the index line color.
2993 ielem = new_ielem(&ielem);
2994 ielem->freedata = 1;
2995 ielem->data = cpystr(" ");
2996 ielem->datalen = 1;
2999 ifield->leftadj = (cdesc->adjustment == Left) ? 1 : 0;
3000 set_ielem_widths_in_field(ifield);
3004 ice->widths_done = 1;
3005 ice->id = ice_hash(ice);
3008 * Now we have to put the temporary copy of ice back as the
3009 * real thing.
3011 icep = fetch_ice_ptr(idata->stream, idata->rawno);
3012 if(icep){
3013 free_ice(icep); /* free what is already there */
3014 *icep = ice;
3017 return(ice);
3021 ICE_S *
3022 format_thread_index_line(INDEXDATA_S *idata)
3024 char *p, buffer[BIGWIDTH+1];
3025 int thdlen, space_left, i;
3026 PINETHRD_S *thrd = NULL;
3027 ICE_S *ice, *tice = NULL, **ticep = NULL;
3028 IFIELD_S *ifield;
3029 IELEM_S *ielem;
3030 int (*save_sfstr_func)(void);
3031 struct variable *vars = ps_global->vars;
3033 dprint((8, "=== format_thread_index_line(%ld,%ld) ===\n",
3034 idata ? idata->msgno : -1, idata ? idata->rawno : -1));
3036 space_left = ps_global->ttyo->screen_cols;
3038 if(ps_global->msgmap->max_thrdno < 1000)
3039 thdlen = 3;
3040 else if(ps_global->msgmap->max_thrdno < 10000)
3041 thdlen = 4;
3042 else if(ps_global->msgmap->max_thrdno < 100000)
3043 thdlen = 5;
3044 else
3045 thdlen = 6;
3047 ice = fetch_ice(idata->stream, idata->rawno);
3049 thrd = fetch_thread(idata->stream, idata->rawno);
3051 if(!thrd || !ice) /* can't happen? */
3052 return(ice);
3054 if(!ice->tice){
3055 tice = (ICE_S *) fs_get(sizeof(*tice));
3056 memset(tice, 0, sizeof(*tice));
3057 ice->tice = tice;
3060 tice = ice->tice;
3062 if(!tice)
3063 return(ice);
3065 free_ifield(&tice->ifield);
3067 ticep = &ice->tice;
3068 tice = copy_ice(tice);
3070 if(space_left >= 3){
3071 char to_us, status;
3073 p = buffer;
3074 to_us = to_us_symbol_for_thread(idata->stream, thrd, 1);
3075 status = status_symbol_for_thread(idata->stream, thrd, iStatus);
3077 if((p-buffer)+3 < sizeof(buffer)){
3078 p[0] = to_us;
3079 p[1] = ' ';
3080 p[2] = status;
3081 p[3] = '\0';;
3084 space_left -= 3;
3086 ifield = new_ifield(&tice->ifield);
3087 ifield->ctype = iStatus;
3088 ifield->width = 3;
3089 ifield->leftadj = 1;
3090 for(i = 0; i < 3; i++){
3091 ielem = new_ielem(&ifield->ielem);
3092 ielem->freedata = 1;
3093 ielem->data = (char *) fs_get(2 * sizeof(char));
3094 ielem->data[0] = p[i];
3095 ielem->data[1] = '\0';
3096 ielem->datalen = 1;
3097 set_print_format(ielem, 1, ifield->leftadj);
3100 if(pico_usingcolor()){
3101 if(to_us == '*'
3102 && VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
3103 ielem = ifield->ielem;
3104 ielem->freecolor = 1;
3105 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR,
3106 VAR_IND_IMP_BACK_COLOR);
3107 if(F_ON(F_COLOR_LINE_IMPORTANT, ps_global))
3108 tice->linecolor = new_color_pair(VAR_IND_IMP_FORE_COLOR,
3109 VAR_IND_IMP_BACK_COLOR);
3111 else if((to_us == '+' || to_us == '-')
3112 && VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
3113 ielem = ifield->ielem;
3114 ielem->freecolor = 1;
3115 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR,
3116 VAR_IND_PLUS_BACK_COLOR);
3119 if(status == 'D'
3120 && VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
3121 ielem = ifield->ielem->next->next;
3122 ielem->freecolor = 1;
3123 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR,
3124 VAR_IND_DEL_BACK_COLOR);
3126 else if(status == 'N'
3127 && VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
3128 ielem = ifield->ielem->next->next;
3129 ielem->freecolor = 1;
3130 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR,
3131 VAR_IND_NEW_BACK_COLOR);
3136 if(space_left >= thdlen+1){
3137 p = buffer;
3138 space_left--;
3140 snprintf(p, sizeof(buffer), "%*.*s", thdlen, thdlen, "");
3141 space_left -= thdlen;
3143 ifield = new_ifield(&tice->ifield);
3144 ifield->ctype = iMessNo;
3145 ifield->width = thdlen;
3146 ifield->leftadj = 0;
3147 ielem = new_ielem(&ifield->ielem);
3148 ielem->freedata = 1;
3149 ielem->data = cpystr(p);
3150 ielem->datalen = strlen(p);
3151 set_print_format(ielem, ifield->width, ifield->leftadj);
3154 if(space_left >= 7){
3156 p = buffer;
3157 space_left--;
3159 date_str(fetch_date(idata), iDate, 0, p, sizeof(buffer), 0);
3160 if(sizeof(buffer) > 6)
3161 p[6] = '\0';
3163 if(strlen(p) < 6 && (sizeof(buffer)) > 6){
3164 char *q;
3166 for(q = p + strlen(p); q < p + 6; q++)
3167 *q = ' ';
3170 space_left -= 6;
3172 ifield = new_ifield(&tice->ifield);
3173 ifield->ctype = iDate;
3174 ifield->width = 6;
3175 ifield->leftadj = 1;
3176 ielem = new_ielem(&ifield->ielem);
3177 ielem->freedata = 1;
3178 ielem->data = cpystr(p);
3179 ielem->datalen = ifield->width;
3180 set_print_format(ielem, ifield->width, ifield->leftadj);
3184 if(space_left > 3){
3185 int from_width, subj_width, bigthread_adjust;
3186 long in_thread;
3187 char from[BIGWIDTH+1];
3188 char tcnt[50];
3190 space_left--;
3192 in_thread = count_lflags_in_thread(idata->stream, thrd,
3193 ps_global->msgmap, MN_NONE);
3195 p = buffer;
3196 if(in_thread == 1 && THRD_AUTO_VIEW())
3197 snprintf(tcnt, sizeof(tcnt), " ");
3198 else
3199 snprintf(tcnt, sizeof(tcnt), "(%ld)", in_thread);
3201 bigthread_adjust = MAX(0, strlen(tcnt) - 3);
3203 /* third of the rest */
3204 from_width = MAX((space_left-1)/3 - bigthread_adjust, 1);
3206 /* the rest */
3207 subj_width = space_left - from_width - 1;
3209 if(strlen(tcnt) > subj_width)
3210 tcnt[subj_width] = '\0';
3212 from[0] = '\0';
3213 save_sfstr_func = pith_opt_truncate_sfstr;
3214 pith_opt_truncate_sfstr = NULL;
3215 from_str(iFromTo, idata, from, sizeof(from), tice);
3216 pith_opt_truncate_sfstr = save_sfstr_func;
3218 ifield = new_ifield(&tice->ifield);
3219 ifield->leftadj = 1;
3220 ielem = new_ielem(&ifield->ielem);
3221 ielem->freedata = 1;
3222 ielem->type = eTypeCol;
3223 ielem->data = cpystr(from);
3224 ielem->datalen = strlen(from);
3225 ifield->width = from_width;
3226 set_print_format(ielem, ifield->width, ifield->leftadj);
3227 ifield->ctype = iFrom;
3228 if(from_width > 0 && pico_usingcolor()
3229 && VAR_IND_FROM_FORE_COLOR && VAR_IND_FROM_BACK_COLOR){
3230 ielem->freecolor = 1;
3231 ielem->color = new_color_pair(VAR_IND_FROM_FORE_COLOR,
3232 VAR_IND_FROM_BACK_COLOR);
3235 ifield = new_ifield(&tice->ifield);
3236 ifield->leftadj = 0;
3237 ielem = new_ielem(&ifield->ielem);
3238 ielem->freedata = 1;
3239 ielem->data = cpystr(tcnt);
3240 ielem->datalen = strlen(tcnt);
3241 ifield->width = ielem->datalen;
3242 set_print_format(ielem, ifield->width, ifield->leftadj);
3243 ifield->ctype = iAtt; /* not used, except that it isn't special */
3245 subj_width -= strlen(tcnt);
3247 if(subj_width > 0)
3248 subj_width--;
3250 if(subj_width > 0){
3251 if(idata->bogus){
3252 if(idata->bogus < 2)
3253 snprintf(buffer, sizeof(buffer), "%-.*s", BIGWIDTH,
3254 _("[ No Message Text Available ]"));
3256 else{
3257 buffer[0] = '\0';
3258 save_sfstr_func = pith_opt_truncate_sfstr;
3259 pith_opt_truncate_sfstr = NULL;
3260 subj_str(idata, buffer, sizeof(buffer), NoKW, 0, NULL);
3261 pith_opt_truncate_sfstr = save_sfstr_func;
3264 ifield = new_ifield(&tice->ifield);
3265 ifield->leftadj = 1;
3266 ielem = new_ielem(&ifield->ielem);
3267 ielem->freedata = 1;
3268 ielem->type = eTypeCol;
3269 ielem->data = cpystr(buffer);
3270 ielem->datalen = strlen(buffer);
3271 ifield->width = subj_width;
3272 set_print_format(ielem, ifield->width, ifield->leftadj);
3273 ifield->ctype = iSubject;
3274 if(pico_usingcolor() && VAR_IND_SUBJ_FORE_COLOR && VAR_IND_SUBJ_BACK_COLOR){
3275 ielem->freecolor = 1;
3276 ielem->color = new_color_pair(VAR_IND_SUBJ_FORE_COLOR,
3277 VAR_IND_SUBJ_BACK_COLOR);
3281 else if(space_left > 1){
3282 snprintf(p, sizeof(buffer)-(p-buffer), "%-.*s", space_left-1, " ");
3283 ifield = new_ifield(&tice->ifield);
3284 ifield->leftadj = 1;
3285 ielem = new_ielem(&ifield->ielem);
3286 ielem->freedata = 1;
3287 ielem->data = cpystr(p);
3288 ielem->datalen = strlen(p);
3289 ifield->width = space_left-1;
3290 set_print_format(ielem, ifield->width, ifield->leftadj);
3291 ifield->ctype = iSubject;
3294 tice->widths_done = 1;
3295 tice->id = ice_hash(tice);
3297 if(ticep){
3298 free_ice(ticep); /* free what is already there */
3299 *ticep = tice;
3302 return(ice);
3307 * Print the fields of ice in buf with a single space between fields.
3309 * Args buf -- place to put the line
3310 * ice -- the data for the line
3311 * msgno -- this is the msgno to be used, blanks if <= 0
3313 * Returns a pointer to buf.
3315 char *
3316 simple_index_line(char *buf, size_t buflen, ICE_S *ice, long int msgno)
3318 char *p;
3319 IFIELD_S *ifield, *previfield = NULL;
3320 IELEM_S *ielem;
3322 if(!buf)
3323 alpine_panic("NULL buf in simple_index_line()");
3325 if(buflen > 0)
3326 buf[0] = '\0';
3328 p = buf;
3330 if(ice){
3332 for(ifield = ice->ifield; ifield && p-buf < buflen; ifield = ifield->next){
3334 /* space between fields */
3335 if(ifield != ice->ifield && !(previfield && previfield->ctype == iText))
3336 *p++ = ' ';
3338 /* message number string is generated on the fly */
3339 if(ifield->ctype == iMessNo){
3340 ielem = ifield->ielem;
3341 if(ielem && ielem->datalen >= ifield->width){
3342 if(msgno > 0L)
3343 snprintf(ielem->data, ielem->datalen+1, "%*.ld", ifield->width, msgno);
3344 else
3345 snprintf(ielem->data, ielem->datalen+1, "%*.*s", ifield->width, ifield->width, "");
3349 for(ielem = ifield->ielem;
3350 ielem && ielem->print_format && p-buf < buflen;
3351 ielem = ielem->next){
3352 char *src;
3353 size_t bytes_added;
3355 src = ielem->data;
3356 bytes_added = utf8_pad_to_width(p, src,
3357 buflen-(p-buf) * sizeof(char),
3358 ielem->wid, ifield->leftadj);
3359 p += bytes_added;
3362 previfield = ifield;
3365 if(p-buf < buflen)
3366 *p = '\0';
3369 buf[buflen-1] = '\0';
3371 return(buf);
3376 * Look in current mail_stream for matches for messages in the searchset
3377 * which match a color rule pattern. Return the color.
3378 * The searched bit will be set for all of the messages which match the
3379 * first pattern which has a match.
3381 * Args stream -- the mail stream
3382 * searchset -- restrict attention to this set of messages
3383 * pstate -- The pattern state. On the first call it will be Null.
3384 * Null means start over with a new first_pattern.
3385 * After that it will be pointing to our local PAT_STATE
3386 * so that next_pattern goes to the next one after the
3387 * ones we've already checked.
3389 * Returns 0 if no match, 1 if a match.
3390 * The color that goes with the matched rule in returned_color.
3391 * It may be NULL, which indicates default.
3394 get_index_line_color(MAILSTREAM *stream, SEARCHSET *searchset,
3395 PAT_STATE **pstate, COLOR_PAIR **returned_color)
3397 PAT_S *pat = NULL;
3398 long rflags = ROLE_INCOL;
3399 COLOR_PAIR *color = NULL;
3400 int match = 0;
3401 static PAT_STATE localpstate;
3403 dprint((7, "get_index_line_color\n"));
3405 if(returned_color)
3406 *returned_color = NULL;
3408 if(*pstate)
3409 pat = next_pattern(*pstate);
3410 else{
3411 *pstate = &localpstate;
3412 if(!nonempty_patterns(rflags, *pstate))
3413 *pstate = NULL;
3415 if(*pstate)
3416 pat = first_pattern(*pstate);
3419 if(*pstate){
3421 /* Go through the possible roles one at a time until we get a match. */
3422 while(!match && pat){
3423 if(match_pattern(pat->patgrp, stream, searchset, NULL,
3424 get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
3425 if(!pat->action || pat->action->bogus)
3426 break;
3428 match++;
3429 if(pat->action && pat->action->incol)
3430 color = new_color_pair(pat->action->incol->fg,
3431 pat->action->incol->bg);
3433 else
3434 pat = next_pattern(*pstate);
3438 if(match && returned_color)
3439 *returned_color = color;
3441 return(match);
3449 index_in_overview(MAILSTREAM *stream)
3451 INDEX_COL_S *cdesc = NULL;
3453 if(!(stream->mailbox && IS_REMOTE(stream->mailbox)))
3454 return(FALSE); /* no point! */
3456 if(stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "nntp")){
3458 if(THRD_INDX())
3459 return(TRUE);
3461 for(cdesc = ps_global->index_disp_format;
3462 cdesc->ctype != iNothing;
3463 cdesc++)
3464 switch(cdesc->ctype){
3465 case iTo: /* can't be satisfied by XOVER */
3466 case iSender: /* ... or specifically handled */
3467 case iDescripSize: /* ... in news case */
3468 case iAtt:
3469 return(FALSE);
3471 default :
3472 break;
3476 return(TRUE);
3482 * fetch_from - called to get a the index entry's "From:" field
3485 resent_to_us(INDEXDATA_S *idata)
3487 if(!idata->valid_resent_to){
3488 static char *fields[] = {"Resent-To", NULL};
3489 char *h;
3491 if(idata->no_fetch){
3492 idata->bogus = 1; /* don't do this */
3493 return(FALSE);
3496 if((h = pine_fetchheader_lines(idata->stream,idata->rawno,NULL,fields)) != NULL){
3497 idata->resent_to_us = parsed_resent_to_us(h);
3498 fs_give((void **) &h);
3501 idata->valid_resent_to = 1;
3504 return(idata->resent_to_us);
3509 parsed_resent_to_us(char *h)
3511 char *p, *q;
3512 ADDRESS *addr = NULL;
3513 int rv = FALSE;
3515 if((p = strindex(h, ':')) != NULL){
3516 for(q = ++p; (q = strpbrk(q, "\015\012")) != NULL; q++)
3517 *q = ' '; /* quash junk */
3519 rfc822_parse_adrlist(&addr, p, ps_global->maildomain);
3520 if(addr){
3521 rv = address_is_us(addr, ps_global);
3522 mail_free_address(&addr);
3526 return(rv);
3532 * fetch_from - called to get a the index entry's "From:" field
3534 ADDRESS *
3535 fetch_from(INDEXDATA_S *idata)
3537 if(idata->no_fetch) /* implies from is valid */
3538 return(idata->from);
3539 else if(idata->bogus)
3540 idata->bogus = 2;
3541 else{
3542 ENVELOPE *env;
3544 /* c-client call's just cache access at this point */
3545 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3546 return(env->from);
3548 idata->bogus = 1;
3551 return(NULL);
3556 * fetch_to - called to get a the index entry's "To:" field
3558 ADDRESS *
3559 fetch_to(INDEXDATA_S *idata)
3561 if(idata->no_fetch){ /* check for specific validity */
3562 if(idata->valid_to)
3563 return(idata->to);
3564 else
3565 idata->bogus = 1; /* can't give 'em what they want */
3567 else if(idata->bogus){
3568 idata->bogus = 2; /* elevate bogosity */
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->to);
3577 idata->bogus = 1;
3580 return(NULL);
3585 * fetch_cc - called to get a the index entry's "Cc:" field
3587 ADDRESS *
3588 fetch_cc(INDEXDATA_S *idata)
3590 if(idata->no_fetch){ /* check for specific validity */
3591 if(idata->valid_cc)
3592 return(idata->cc);
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->cc);
3606 idata->bogus = 1;
3609 return(NULL);
3615 * fetch_sender - called to get a the index entry's "Sender:" field
3617 ADDRESS *
3618 fetch_sender(INDEXDATA_S *idata)
3620 if(idata->no_fetch){ /* check for specific validity */
3621 if(idata->valid_sender)
3622 return(idata->sender);
3623 else
3624 idata->bogus = 1; /* can't give 'em what they want */
3626 else if(idata->bogus){
3627 idata->bogus = 2; /* elevate bogosity */
3629 else{
3630 ENVELOPE *env;
3632 /* c-client call's just cache access at this point */
3633 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3634 return(env->sender);
3636 idata->bogus = 1;
3639 return(NULL);
3644 * fetch_newsgroups - called to get a the index entry's "Newsgroups:" field
3646 char *
3647 fetch_newsgroups(INDEXDATA_S *idata)
3649 if(idata->no_fetch){ /* check for specific validity */
3650 if(idata->valid_news)
3651 return(idata->newsgroups);
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->newsgroups);
3665 idata->bogus = 1;
3668 return(NULL);
3673 * fetch_subject - called to get at the index entry's "Subject:" field
3675 char *
3676 fetch_subject(INDEXDATA_S *idata)
3678 if(idata->no_fetch) /* implies subject is valid */
3679 return(idata->subject);
3680 else if(idata->bogus)
3681 idata->bogus = 2;
3682 else{
3683 ENVELOPE *env;
3685 /* c-client call's just cache access at this point */
3686 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3687 return(env->subject);
3689 idata->bogus = 1;
3692 return(NULL);
3697 * Return an allocated copy of the first few characters from the body
3698 * of the message for possible use in the index screen.
3700 * Maybe we could figure out some way to do aggregate calls to get
3701 * this info for all the lines in view instead of all the one at a
3702 * time calls we're doing now.
3704 char *
3705 fetch_firsttext(INDEXDATA_S *idata, int delete_quotes)
3707 ENVELOPE *env;
3708 BODY *body = NULL;
3709 char *firsttext = NULL;
3710 STORE_S *so;
3711 gf_io_t pc;
3712 long partial_fetch_len = 0L;
3713 SEARCHSET *ss, **sset;
3715 try_again:
3718 * Prevent wild prefetch, just get the one we're after.
3719 * Can we get this somehow in the overview call in build_header_work?
3721 ss = mail_newsearchset();
3722 ss->first = idata->rawno;
3723 sset = (SEARCHSET **) mail_parameters(idata->stream,
3724 GET_FETCHLOOKAHEAD,
3725 (void *) idata->stream);
3726 if(sset)
3727 *sset = ss;
3729 if((env = pine_mail_fetchstructure(idata->stream, idata->rawno, &body)) != NULL){
3730 if(body){
3731 char *subtype = NULL;
3732 char *partno;
3734 if((body->type == TYPETEXT
3735 && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
3737 (body->type == TYPEMULTIPART && body->nested.part
3738 && body->nested.part->body.type == TYPETEXT
3739 && (subtype=body->nested.part->body.subtype)
3740 && ALLOWED_SUBTYPE(subtype))
3742 (body->type == TYPEMULTIPART && body->nested.part
3743 && body->nested.part->body.type == TYPEMULTIPART
3744 && body->nested.part->body.nested.part
3745 && body->nested.part->body.nested.part->body.type == TYPETEXT
3746 && (subtype=body->nested.part->body.nested.part->body.subtype)
3747 && ALLOWED_SUBTYPE(subtype))){
3749 if((so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
3750 char buf[1025], *p;
3751 unsigned char c;
3752 int success;
3753 int one_space_done = 0;
3755 if(partial_fetch_len == 0L){
3756 if(subtype && !strucmp(subtype, "html"))
3757 partial_fetch_len = 1024L;
3758 else if(subtype && !strucmp(subtype, "plain"))
3759 partial_fetch_len = delete_quotes ? 128L : 64L;
3760 else
3761 partial_fetch_len = 256L;
3764 if((body->type == TYPETEXT
3765 && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
3767 (body->type == TYPEMULTIPART && body->nested.part
3768 && body->nested.part->body.type == TYPETEXT
3769 && (subtype=body->nested.part->body.subtype)
3770 && ALLOWED_SUBTYPE(subtype)))
3771 partno = "1";
3772 else
3773 partno = "1.1";
3775 gf_set_so_writec(&pc, so);
3776 success = get_body_part_text(idata->stream, body, idata->rawno,
3777 partno, partial_fetch_len, pc,
3778 NULL, NULL,
3779 GBPT_NOINTR | GBPT_PEEK |
3780 (delete_quotes ? GBPT_DELQUOTES : 0));
3781 gf_clear_so_writec(so);
3783 if(success){
3784 so_seek(so, 0L, 0);
3785 p = buf;
3786 while(p-buf < sizeof(buf)-1 && so_readc(&c, so)){
3787 /* delete leading whitespace */
3788 if(p == buf && isspace(c))
3790 /* and include just one space per run of whitespace */
3791 else if(isspace(c)){
3792 if(!one_space_done){
3793 *p++ = SPACE;
3794 one_space_done++;
3797 else{
3798 one_space_done = 0;
3799 *p++ = c;
3803 *p = '\0';
3805 if(p > buf){
3806 size_t l;
3808 l = strlen(buf);
3809 l += 100;
3810 firsttext = fs_get((l+1) * sizeof(char));
3811 firsttext[0] = '\0';
3812 iutf8ncpy(firsttext, buf, l);
3813 firsttext[l] = '\0';
3814 removing_trailing_white_space(firsttext);
3818 so_give(&so);
3820 /* first if means we didn't fetch all of the data */
3821 if(!(success > 1 && success < partial_fetch_len)){
3822 if(partial_fetch_len < 4096L
3823 && (!firsttext || utf8_width(firsttext) < 50)){
3824 if(firsttext)
3825 fs_give((void **) &firsttext);
3827 partial_fetch_len = 4096L;
3828 goto try_again;
3836 if(ss)
3837 mail_free_searchset(&ss);
3839 return(firsttext);
3844 * fetch_date - called to get at the index entry's "Date:" field
3846 char *
3847 fetch_date(INDEXDATA_S *idata)
3849 if(idata->no_fetch) /* implies date is valid */
3850 return(idata->date);
3851 else if(idata->bogus)
3852 idata->bogus = 2;
3853 else{
3854 ENVELOPE *env;
3856 /* c-client call's just cache access at this point */
3857 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3858 return((char *) env->date);
3860 idata->bogus = 1;
3863 return(NULL);
3868 * fetch_header - called to get at the index entry's "Hdrname:" field
3870 char *
3871 fetch_header(INDEXDATA_S *idata, char *hdrname)
3873 if(idata->no_fetch)
3874 idata->bogus = 1;
3875 else if(idata->bogus)
3876 idata->bogus = 2;
3877 else{
3878 char *h, *p, *q, *decoded, *fields[2];
3879 size_t retsize, decsize;
3880 char *ret = NULL;
3881 unsigned char *decode_buf = NULL;
3883 fields[0] = hdrname;
3884 fields[1] = NULL;
3885 if(hdrname && hdrname[0]
3886 && (h = pine_fetchheader_lines(idata->stream, idata->rawno,
3887 NULL, fields))){
3889 if(strlen(h) < strlen(hdrname) + 1){
3890 fs_give((void **) &h);
3891 return(cpystr(""));
3894 /* skip "hdrname:" */
3895 for(p = h + strlen(hdrname) + 1;
3896 *p && isspace((unsigned char)*p); p++)
3899 decsize = (4 * strlen(p)) + 1;
3900 decode_buf = (unsigned char *) fs_get(decsize * sizeof(unsigned char));
3901 decoded = (char *) rfc1522_decode_to_utf8(decode_buf, decsize, p);
3902 p = decoded;
3904 retsize = strlen(decoded);
3905 q = ret = (char *) fs_get((retsize+1) * sizeof(char));
3907 *q = '\0';
3908 while(q-ret < retsize && *p){
3909 if(*p == '\015' || *p == '\012')
3910 p++;
3911 else if(*p == '\t'){
3912 *q++ = SPACE;
3913 p++;
3915 else
3916 *q++ = *p++;
3919 *q = '\0';
3921 fs_give((void **) &h);
3922 if(decode_buf)
3923 fs_give((void **) &decode_buf);
3925 return(ret);
3928 idata->bogus = 1;
3931 return(NULL);
3936 * fetch_size - called to get at the index entry's "size" field
3938 long
3939 fetch_size(INDEXDATA_S *idata)
3941 if(idata->no_fetch) /* implies size is valid */
3942 return(idata->size);
3943 else if(idata->bogus)
3944 idata->bogus = 2;
3945 else{
3946 MESSAGECACHE *mc;
3948 if(idata->stream && idata->rawno > 0L
3949 && idata->rawno <= idata->stream->nmsgs
3950 && (mc = mail_elt(idata->stream, idata->rawno)))
3951 return(mc->rfc822_size);
3953 idata->bogus = 1;
3956 return(0L);
3961 * fetch_body - called to get a the index entry's body structure
3963 BODY *
3964 fetch_body(INDEXDATA_S *idata)
3966 BODY *body;
3968 if(idata->bogus || idata->no_fetch){
3969 idata->bogus = 2;
3970 return(NULL);
3973 if(pine_mail_fetchstructure(idata->stream, idata->rawno, &body))
3974 return(body);
3976 idata->bogus = 1;
3977 return(NULL);
3982 * s is at least size width+1
3985 set_index_addr(INDEXDATA_S *idata,
3986 char *field,
3987 struct mail_address *addr,
3988 char *prefix,
3989 int width,
3990 char *s)
3992 ADDRESS *atmp;
3993 char *p, *stmp = NULL, *sptr;
3994 char *save_personal = NULL;
3995 int orig_width;
3997 s[0] = '\0';
3999 for(atmp = addr; idata->stream && atmp; atmp = atmp->next)
4000 if(atmp->host && atmp->host[0] == '.'){
4001 char *pref, *h, *fields[2];
4003 if(idata->no_fetch){
4004 idata->bogus = 1;
4005 return(TRUE);
4008 fields[0] = field;
4009 fields[1] = NULL;
4010 if((h = pine_fetchheader_lines(idata->stream, idata->rawno,
4011 NULL, fields)) != NULL){
4012 if(strlen(h) < strlen(field) + 1){
4013 p = h + strlen(h);
4015 else{
4016 /* skip "field:" */
4017 for(p = h + strlen(field) + 1;
4018 *p && isspace((unsigned char)*p); p++)
4022 orig_width = width;
4023 sptr = stmp = (char *) fs_get((orig_width+1) * sizeof(char));
4025 /* add prefix */
4026 for(pref = prefix; pref && *pref; pref++)
4027 if(width){
4028 *sptr++ = *pref;
4029 width--;
4031 else
4032 break;
4034 while(width--)
4035 if(*p == '\015' || *p == '\012')
4036 p++; /* skip CR LF */
4037 else if(!*p)
4038 *sptr++ = ' ';
4039 else if(*p == '\t'){
4040 *sptr++ = ' ';
4041 p++;
4043 else
4044 *sptr++ = *p++;
4046 *sptr = '\0'; /* tie off return string */
4048 if(stmp){
4049 iutf8ncpy(s, stmp, orig_width+1);
4050 s[orig_width] = '\0';
4051 fs_give((void **) &stmp);
4054 fs_give((void **) &h);
4055 return(TRUE);
4057 /* else fall thru and display what c-client gave us */
4060 if(addr && !addr->next /* only one address */
4061 && addr->host /* not group syntax */
4062 && addr->personal && addr->personal[0]){ /* there is a personal name */
4063 char buftmp[MAILTMPLEN];
4064 int l;
4066 if((l = prefix ? strlen(prefix) : 0) != 0)
4067 strncpy(s, prefix, width+1);
4069 snprintf(buftmp, sizeof(buftmp), "%s", addr->personal);
4070 p = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
4071 SIZEOF_20KBUF, buftmp);
4072 removing_leading_and_trailing_white_space(p);
4074 iutf8ncpy(s + l, p, width - l);
4076 s[width] = '\0';
4078 if(*(s+l))
4079 return(TRUE);
4080 else{
4081 save_personal = addr->personal;
4082 addr->personal = NULL;
4086 if(addr){
4087 char *a_string;
4088 int l;
4090 a_string = addr_list_string(addr, NULL, 0);
4091 if(save_personal)
4092 addr->personal = save_personal;
4094 if((l = prefix ? strlen(prefix) : 0) != 0)
4095 strncpy(s, prefix, width+1);
4097 iutf8ncpy(s + l, a_string, width - l);
4098 s[width] = '\0';
4100 fs_give((void **)&a_string);
4102 return(TRUE);
4105 if(save_personal)
4106 addr->personal = save_personal;
4108 return(FALSE);
4112 void
4113 index_data_env(INDEXDATA_S *idata, ENVELOPE *env)
4115 if(!env){
4116 idata->bogus = 2;
4117 return;
4120 idata->from = env->from;
4121 idata->to = env->to;
4122 idata->cc = env->cc;
4123 idata->sender = env->sender;
4124 idata->subject = env->subject;
4125 idata->date = (char *) env->date;
4126 idata->newsgroups = env->newsgroups;
4128 idata->valid_to = 1; /* signal that everythings here */
4129 idata->valid_cc = 1;
4130 idata->valid_sender = 1;
4131 idata->valid_news = 1;
4136 * Put a string representing the date into str. The source date is
4137 * in the string datesrc. The format to be used is in type.
4138 * Notice that type is an IndexColType, but really only a subset of
4139 * IndexColType types are allowed.
4141 * Args datesrc -- The source date string
4142 * type -- What type of output we want
4143 * v -- If set, variable width output is ok. (Oct 9 not Oct 9)
4144 * str -- Put the answer here.
4145 * str_len -- Length of str
4146 * monabb_width -- This is a hack to get dates to line up right. For
4147 * example, in French (but without accents here)
4148 * dec. 21
4149 * fevr. 23
4150 * mars 7
4151 * For this monabb_width would be 5.
4153 void
4154 date_str(char *datesrc, IndexColType type, int v, char *str, size_t str_len,
4155 int monabb_width)
4157 char year4[5], /* 4 digit year */
4158 yearzero[3], /* zero padded, 2-digit year */
4159 monzero[3], /* zero padded, 2-digit month */
4160 mon[3], /* 1 or 2-digit month, no pad */
4161 dayzero[3], /* zero padded, 2-digit day */
4162 day[3], /* 1 or 2-digit day, no pad */
4163 dayord[3], /* 2-letter ordinal label */
4164 monabb[10], /* 3-letter month abbrev */
4165 /* actually maybe not 3 if localized */
4166 hour24[3], /* 2-digit, 24 hour clock hour */
4167 hour12[3], /* 12 hour clock hour, no pad */
4168 minzero[3], /* zero padded, 2-digit minutes */
4169 timezone[6]; /* timezone, like -0800 or +... */
4170 int hr12;
4171 int curtype, lastmonthtype, lastyeartype, preftype;
4172 int sdatetimetype, sdatetime24type;
4173 struct date d;
4174 #define TODAYSTR N_("Today")
4176 curtype = (type == iCurDate ||
4177 type == iCurDateIso ||
4178 type == iCurDateIsoS ||
4179 type == iCurPrefDate ||
4180 type == iCurPrefDateTime ||
4181 type == iCurPrefTime ||
4182 type == iCurTime24 ||
4183 type == iCurTime12 ||
4184 type == iCurDay ||
4185 type == iCurDay2Digit ||
4186 type == iCurDayOfWeek ||
4187 type == iCurDayOfWeekAbb ||
4188 type == iCurMon ||
4189 type == iCurMon2Digit ||
4190 type == iCurMonLong ||
4191 type == iCurMonAbb ||
4192 type == iCurYear ||
4193 type == iCurYear2Digit);
4194 lastmonthtype = (type == iLstMon ||
4195 type == iLstMon2Digit ||
4196 type == iLstMonLong ||
4197 type == iLstMonAbb ||
4198 type == iLstMonYear ||
4199 type == iLstMonYear2Digit);
4200 lastyeartype = (type == iLstYear ||
4201 type == iLstYear2Digit);
4202 sdatetimetype = (type == iSDateTime ||
4203 type == iSDateTimeIso ||
4204 type == iSDateTimeIsoS ||
4205 type == iSDateTimeS1 ||
4206 type == iSDateTimeS2 ||
4207 type == iSDateTimeS3 ||
4208 type == iSDateTimeS4 ||
4209 type == iSDateTime24 ||
4210 type == iSDateTimeIso24 ||
4211 type == iSDateTimeIsoS24 ||
4212 type == iSDateTimeS124 ||
4213 type == iSDateTimeS224 ||
4214 type == iSDateTimeS324 ||
4215 type == iSDateTimeS424);
4216 sdatetime24type = (type == iSDateTime24 ||
4217 type == iSDateTimeIso24 ||
4218 type == iSDateTimeIsoS24 ||
4219 type == iSDateTimeS124 ||
4220 type == iSDateTimeS224 ||
4221 type == iSDateTimeS324 ||
4222 type == iSDateTimeS424);
4223 preftype = (type == iPrefDate ||
4224 type == iPrefDateTime ||
4225 type == iPrefTime ||
4226 type == iCurPrefDate ||
4227 type == iCurPrefDateTime ||
4228 type == iCurPrefTime);
4229 if(str_len > 0)
4230 str[0] = '\0';
4232 if(!(datesrc && datesrc[0]) && !(curtype || lastmonthtype || lastyeartype))
4233 return;
4235 if(curtype || lastmonthtype || lastyeartype){
4236 char dbuf[200];
4238 rfc822_date(dbuf);
4239 parse_date(dbuf, &d);
4241 if(lastyeartype)
4242 d.year--;
4243 else if(lastmonthtype){
4244 d.month--;
4245 if(d.month <= 0){
4246 d.month = 12;
4247 d.year--;
4251 else{
4252 parse_date(F_ON(F_DATES_TO_LOCAL,ps_global)
4253 ? convert_date_to_local(datesrc) : datesrc, &d);
4254 if(d.year == -1 || d.month == -1 || d.day == -1){
4255 sdatetimetype = 0;
4256 sdatetime24type = 0;
4257 preftype = 0;
4258 switch(type){
4259 case iSDate: case iSDateTime: case iSDateTime24:
4260 type = iS1Date;
4261 break;
4263 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
4264 case iPrefDate: case iPrefTime: case iPrefDateTime:
4265 type = iDateIso;
4266 break;
4268 case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
4269 type = iDateIsoS;
4270 break;
4272 case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
4273 type = iS1Date;
4274 break;
4276 case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
4277 type = iS1Date;
4278 break;
4280 case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
4281 type = iS1Date;
4282 break;
4284 case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
4285 type = iS1Date;
4286 break;
4288 default:
4289 break;
4294 /* some special ones to start with */
4295 if(preftype){
4296 struct tm tm, *tmptr = NULL;
4297 time_t now;
4300 * Make sure we get the right one if we're using current time.
4302 if(curtype){
4303 now = time((time_t *) 0);
4304 if(now != (time_t) -1)
4305 tmptr = localtime(&now);
4308 if(!tmptr){
4309 memset(&tm, 0, sizeof(tm));
4310 tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
4311 tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
4312 tm.tm_mday = MIN(MAX(d.day, 1), 31);
4313 tm.tm_hour = MIN(MAX(d.hour, 0), 23);
4314 tm.tm_min = MIN(MAX(d.minute, 0), 59);
4315 tm.tm_wday = MIN(MAX(d.wkday, 0), 6);
4316 tmptr = &tm;
4319 switch(type){
4320 case iPrefDate:
4321 case iCurPrefDate:
4322 our_strftime(str, str_len, "%x", tmptr);
4323 break;
4324 case iPrefTime:
4325 case iCurPrefTime:
4326 our_strftime(str, str_len, "%X", tmptr);
4327 break;
4328 case iPrefDateTime:
4329 case iCurPrefDateTime:
4330 our_strftime(str, str_len, "%c", tmptr);
4331 break;
4332 default:
4333 assert(0);
4334 break;
4337 return;
4340 strncpy(monabb, (d.month > 0 && d.month < 13)
4341 ? month_abbrev_locale(d.month) : "", sizeof(monabb));
4342 monabb[sizeof(monabb)-1] = '\0';
4344 strncpy(mon, (d.month > 0 && d.month < 13)
4345 ? int2string(d.month) : "", sizeof(mon));
4346 mon[sizeof(mon)-1] = '\0';
4348 strncpy(day, (d.day > 0 && d.day < 32)
4349 ? int2string(d.day) : "", sizeof(day));
4350 day[sizeof(day)-1] = '\0';
4352 strncpy(dayord,
4353 (d.day <= 0 || d.day > 31) ? "" :
4354 (d.day == 1 || d.day == 21 || d.day == 31) ? "st" :
4355 (d.day == 2 || d.day == 22 ) ? "nd" :
4356 (d.day == 3 || d.day == 23 ) ? "rd" : "th", sizeof(dayord));
4358 dayord[sizeof(dayord)-1] = '\0';
4360 strncpy(year4, (d.year >= 1000 && d.year < 10000)
4361 ? int2string(d.year) : "????", sizeof(year4));
4362 year4[sizeof(year4)-1] = '\0';
4364 if(d.year >= 0){
4365 if((d.year % 100) < 10){
4366 yearzero[0] = '0';
4367 strncpy(yearzero+1, int2string(d.year % 100), sizeof(yearzero)-1);
4369 else
4370 strncpy(yearzero, int2string(d.year % 100), sizeof(yearzero));
4372 else
4373 strncpy(yearzero, "??", sizeof(yearzero));
4375 yearzero[sizeof(yearzero)-1] = '\0';
4377 if(d.month > 0 && d.month < 10){
4378 monzero[0] = '0';
4379 strncpy(monzero+1, int2string(d.month), sizeof(monzero)-1);
4381 else if(d.month >= 10 && d.month <= 12)
4382 strncpy(monzero, int2string(d.month), sizeof(monzero));
4383 else
4384 strncpy(monzero, "??", sizeof(monzero));
4386 monzero[sizeof(monzero)-1] = '\0';
4388 if(d.day > 0 && d.day < 10){
4389 dayzero[0] = '0';
4390 strncpy(dayzero+1, int2string(d.day), sizeof(dayzero)-1);
4392 else if(d.day >= 10 && d.day <= 31)
4393 strncpy(dayzero, int2string(d.day), sizeof(dayzero));
4394 else
4395 strncpy(dayzero, "??", sizeof(dayzero));
4397 dayzero[sizeof(dayzero)-1] = '\0';
4399 hr12 = (d.hour == 0) ? 12 :
4400 (d.hour > 12) ? (d.hour - 12) : d.hour;
4401 hour12[0] = '\0';
4402 if(hr12 > 0 && hr12 <= 12)
4403 strncpy(hour12, int2string(hr12), sizeof(hour12));
4405 hour12[sizeof(hour12)-1] = '\0';
4407 hour24[0] = '\0';
4408 if(d.hour >= 0 && d.hour < 10){
4409 hour24[0] = '0';
4410 strncpy(hour24+1, int2string(d.hour), sizeof(hour24)-1);
4412 else if(d.hour >= 10 && d.hour < 24)
4413 strncpy(hour24, int2string(d.hour), sizeof(hour24));
4415 hour24[sizeof(hour24)-1] = '\0';
4417 minzero[0] = '\0';
4418 if(d.minute >= 0 && d.minute < 10){
4419 minzero[0] = '0';
4420 strncpy(minzero+1, int2string(d.minute), sizeof(minzero)-1);
4422 else if(d.minute >= 10 && d.minute <= 60)
4423 strncpy(minzero, int2string(d.minute), sizeof(minzero));
4425 minzero[sizeof(minzero)-1] = '\0';
4427 if(sizeof(timezone) > 5){
4428 if(d.hours_off_gmt <= 0){
4429 timezone[0] = '-';
4430 d.hours_off_gmt *= -1;
4431 d.min_off_gmt *= -1;
4433 else
4434 timezone[0] = '+';
4436 timezone[1] = '\0';
4437 if(d.hours_off_gmt >= 0 && d.hours_off_gmt < 10){
4438 timezone[1] = '0';
4439 strncpy(timezone+2, int2string(d.hours_off_gmt), sizeof(timezone)-2);
4441 else if(d.hours_off_gmt >= 10 && d.hours_off_gmt < 24)
4442 strncpy(timezone+1, int2string(d.hours_off_gmt), sizeof(timezone)-1);
4443 else{
4444 timezone[1] = '0';
4445 timezone[2] = '0';
4448 timezone[3] = '\0';
4449 if(d.min_off_gmt >= 0 && d.min_off_gmt < 10){
4450 timezone[3] = '0';
4451 strncpy(timezone+4, int2string(d.min_off_gmt), sizeof(timezone)-4);
4453 else if(d.min_off_gmt >= 10 && d.min_off_gmt <= 60)
4454 strncpy(timezone+3, int2string(d.min_off_gmt), sizeof(timezone)-3);
4455 else{
4456 timezone[3] = '0';
4457 timezone[4] = '0';
4460 timezone[5] = '\0';
4461 timezone[sizeof(timezone)-1] = '\0';
4464 switch(type){
4465 case iRDate:
4466 /* this one is not locale-specific */
4467 snprintf(str, str_len, "%s%s%s %s %s",
4468 (d.wkday != -1) ? day_abbrev(d.wkday) : "",
4469 (d.wkday != -1) ? ", " : "",
4470 day,
4471 (d.month > 0 && d.month < 13) ? month_abbrev(d.month) : "",
4472 year4);
4473 break;
4474 case iDayOfWeekAbb:
4475 case iCurDayOfWeekAbb:
4476 strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_abbrev_locale(d.wkday) : "", str_len);
4477 str[str_len-1] = '\0';
4478 break;
4479 case iDayOfWeek:
4480 case iCurDayOfWeek:
4481 strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_name_locale(d.wkday) : "", str_len);
4482 str[str_len-1] = '\0';
4483 break;
4484 case iYear:
4485 case iCurYear:
4486 case iLstYear:
4487 case iLstMonYear:
4488 strncpy(str, year4, str_len);
4489 break;
4490 case iDay2Digit:
4491 case iCurDay2Digit:
4492 strncpy(str, dayzero, str_len);
4493 break;
4494 case iMon2Digit:
4495 case iCurMon2Digit:
4496 case iLstMon2Digit:
4497 strncpy(str, monzero, str_len);
4498 break;
4499 case iYear2Digit:
4500 case iCurYear2Digit:
4501 case iLstYear2Digit:
4502 case iLstMonYear2Digit:
4503 strncpy(str, yearzero, str_len);
4504 break;
4505 case iTimezone:
4506 strncpy(str, timezone, str_len);
4507 break;
4508 case iDay:
4509 case iCurDay:
4510 strncpy(str, day, str_len);
4511 break;
4512 case iDayOrdinal:
4513 snprintf(str, str_len, "%s%s", day, dayord);
4514 break;
4515 case iMon:
4516 case iCurMon:
4517 case iLstMon:
4518 if(d.month > 0 && d.month <= 12)
4519 strncpy(str, int2string(d.month), str_len);
4521 break;
4522 case iMonAbb:
4523 case iCurMonAbb:
4524 case iLstMonAbb:
4525 strncpy(str, monabb, str_len);
4526 break;
4527 case iMonLong:
4528 case iCurMonLong:
4529 case iLstMonLong:
4530 strncpy(str, (d.month > 0 && d.month < 13)
4531 ? month_name_locale(d.month) : "", str_len);
4532 break;
4533 case iDate:
4534 case iCurDate:
4535 if(v)
4536 snprintf(str, str_len, "%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day);
4537 else{
4538 if(monabb_width > 0)
4539 utf8_snprintf(str, str_len, "%-*.*w %2s",
4540 monabb_width, monabb_width, monabb, day);
4541 else
4542 snprintf(str, str_len, "%s %2s", monabb, day);
4545 break;
4546 case iLDate:
4547 if(v)
4548 snprintf(str, str_len, "%s%s%s%s%s", monabb,
4549 (monabb[0] && day[0]) ? " " : "", day,
4550 ((monabb[0] || day[0]) && year4[0]) ? ", " : "",
4551 year4);
4552 else{
4553 if(monabb_width > 0)
4554 utf8_snprintf(str, str_len, "%-*.*w %2s%c %4s",
4555 monabb_width, monabb_width,
4556 monabb, day,
4557 (monabb[0] && day[0] && year4[0]) ? ',' : ' ', year4);
4558 else
4559 snprintf(str, str_len, "%s %2s%c %4s", monabb, day,
4560 (monabb[0] && day[0] && year4[0]) ? ',' : ' ',
4561 year4);
4564 break;
4565 case iS1Date:
4566 case iS2Date:
4567 case iS3Date:
4568 case iS4Date:
4569 case iDateIso:
4570 case iDateIsoS:
4571 case iCurDateIso:
4572 case iCurDateIsoS:
4573 if(monzero[0] == '?' && dayzero[0] == '?' &&
4574 yearzero[0] == '?')
4575 snprintf(str, str_len, "%8s", "");
4576 else{
4577 switch(type){
4578 case iS1Date:
4579 snprintf(str, str_len, "%2s/%2s/%2s",
4580 monzero, dayzero, yearzero);
4581 break;
4582 case iS2Date:
4583 snprintf(str, str_len, "%2s/%2s/%2s",
4584 dayzero, monzero, yearzero);
4585 break;
4586 case iS3Date:
4587 snprintf(str, str_len, "%2s.%2s.%2s",
4588 dayzero, monzero, yearzero);
4589 break;
4590 case iS4Date:
4591 snprintf(str, str_len, "%2s.%2s.%2s",
4592 yearzero, monzero, dayzero);
4593 break;
4594 case iDateIsoS:
4595 case iCurDateIsoS:
4596 snprintf(str, str_len, "%2s-%2s-%2s",
4597 yearzero, monzero, dayzero);
4598 break;
4599 case iDateIso:
4600 case iCurDateIso:
4601 snprintf(str, str_len, "%4s-%2s-%2s",
4602 year4, monzero, dayzero);
4603 break;
4604 default:
4605 break;
4609 break;
4610 case iTime24:
4611 case iCurTime24:
4612 snprintf(str, str_len, "%2s%c%2s",
4613 (hour24[0] && minzero[0]) ? hour24 : "",
4614 (hour24[0] && minzero[0]) ? ':' : ' ',
4615 (hour24[0] && minzero[0]) ? minzero : "");
4616 break;
4617 case iTime12:
4618 case iCurTime12:
4619 snprintf(str, str_len, "%s%c%2s%s",
4620 (hour12[0] && minzero[0]) ? hour12 : "",
4621 (hour12[0] && minzero[0]) ? ':' : ' ',
4622 (hour12[0] && minzero[0]) ? minzero : "",
4623 (hour12[0] && minzero[0] && d.hour < 12) ? "am" :
4624 (hour12[0] && minzero[0] && d.hour >= 12) ? "pm" :
4625 " ");
4626 break;
4627 case iSDate: case iSDateIso: case iSDateIsoS:
4628 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
4629 case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
4630 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
4631 case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
4632 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
4633 { struct date now, last_day;
4634 char dbuf[200];
4635 int msg_day_of_year, now_day_of_year, today;
4636 int diff, ydiff, last_day_of_year;
4638 rfc822_date(dbuf);
4639 parse_date(dbuf, &now);
4640 today = day_of_week(&now) + 7;
4642 if(today >= 0+7 && today <= 6+7){
4643 now_day_of_year = day_of_year(&now);
4644 msg_day_of_year = day_of_year(&d);
4645 ydiff = now.year - d.year;
4647 if(msg_day_of_year == -1)
4648 diff = -100;
4649 else if(ydiff == 0)
4650 diff = now_day_of_year - msg_day_of_year;
4651 else if(ydiff == 1){
4652 last_day = d;
4653 last_day.month = 12;
4654 last_day.day = 31;
4655 last_day_of_year = day_of_year(&last_day);
4657 diff = now_day_of_year +
4658 (last_day_of_year - msg_day_of_year);
4660 else if(ydiff == -1){
4661 last_day = now;
4662 last_day.month = 12;
4663 last_day.day = 31;
4664 last_day_of_year = day_of_year(&last_day);
4666 diff = -1 * (msg_day_of_year +
4667 (last_day_of_year - now_day_of_year));
4669 else if(ydiff > 1)
4670 diff = 100;
4671 else
4672 diff = -100;
4674 if(diff == 0)
4675 strncpy(str, _(TODAYSTR), str_len);
4676 else if(diff == 1)
4677 strncpy(str, _("Yesterday"), str_len);
4678 else if(diff > 1 && diff < 7)
4679 snprintf(str, str_len, "%s", day_name_locale((today - diff) % 7));
4680 else if(diff == -1)
4681 strncpy(str, _("Tomorrow"), str_len);
4682 else if(diff < -1 && diff > -7)
4683 snprintf(str, str_len, _("Next %.3s!"),
4684 day_name_locale((today - diff) % 7));
4685 else if(diff > 0
4686 && (ydiff == 0
4687 || (ydiff == 1 && 12 + now.month - d.month < 6))){
4688 if(v)
4689 snprintf(str, str_len, "%s%s%s", monabb,
4690 (monabb[0] && day[0]) ? " " : "", day);
4691 else{
4692 if(monabb_width > 0)
4693 utf8_snprintf(str, str_len, "%-*.*w %2s",
4694 monabb_width, monabb_width, monabb, day);
4695 else
4696 snprintf(str, str_len, "%s %2s", monabb, day);
4699 else{
4700 if(msg_day_of_year == -1 && (type == iSDate || type == iSDateTime))
4701 type = iSDateTimeIsoS;
4703 switch(type){
4704 case iSDate: case iSDateTime: case iSDateTime24:
4706 struct tm tm;
4708 memset(&tm, 0, sizeof(tm));
4709 tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
4710 tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
4711 tm.tm_mday = MIN(MAX(d.day, 1), 31);
4712 tm.tm_hour = MIN(MAX(d.hour, 0), 23);
4713 tm.tm_min = MIN(MAX(d.minute, 0), 59);
4714 our_strftime(str, str_len, "%x", &tm);
4717 break;
4718 case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
4719 if(v)
4720 snprintf(str, str_len, "%s/%s/%s%s", mon, day, yearzero,
4721 diff < 0 ? "!" : "");
4722 else
4723 snprintf(str, str_len, "%s%s/%s/%s%s",
4724 (mon[0] && mon[1]) ? "" : " ",
4725 mon, dayzero, yearzero,
4726 diff < 0 ? "!" : "");
4727 break;
4728 case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
4729 if(v)
4730 snprintf(str, str_len, "%s/%s/%s%s", day, mon, yearzero,
4731 diff < 0 ? "!" : "");
4732 else
4733 snprintf(str, str_len, "%s%s/%s/%s%s",
4734 (day[0] && day[1]) ? "" : " ",
4735 day, monzero, yearzero,
4736 diff < 0 ? "!" : "");
4737 break;
4738 case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
4739 if(v)
4740 snprintf(str, str_len, "%s.%s.%s%s", day, mon, yearzero,
4741 diff < 0 ? "!" : "");
4742 else
4743 snprintf(str, str_len, "%s%s.%s.%s%s",
4744 (day[0] && day[1]) ? "" : " ",
4745 day, monzero, yearzero,
4746 diff < 0 ? "!" : "");
4747 break;
4748 case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
4749 if(v)
4750 snprintf(str, str_len, "%s.%s.%s%s",
4751 yearzero, monzero, dayzero,
4752 diff < 0 ? "!" : "");
4753 else
4754 snprintf(str, str_len, "%s.%s.%s%s",
4755 yearzero, monzero, dayzero,
4756 diff < 0 ? "!" : "");
4757 break;
4758 case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
4759 snprintf(str, str_len, "%2s-%2s-%2s",
4760 yearzero, monzero, dayzero);
4761 break;
4762 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
4763 snprintf(str, str_len, "%4s-%2s-%2s",
4764 year4, monzero, dayzero);
4765 break;
4766 default:
4767 break;
4772 else{
4773 if(v)
4774 snprintf(str, str_len, "%s%s%s", monabb,
4775 (monabb[0] && day[0]) ? " " : "", day);
4776 else{
4777 if(monabb_width > 0)
4778 utf8_snprintf(str, str_len, "%-*.*w %2s",
4779 monabb_width, monabb_width, monabb, day);
4780 else
4781 snprintf(str, str_len, "%s %2s", monabb, day);
4786 break;
4788 default:
4789 break;
4792 str[str_len-1] = '\0';
4794 if(type == iSTime ||
4795 (sdatetimetype && !strcmp(str, _(TODAYSTR)))){
4796 struct date now, last_day;
4797 char dbuf[200], *Ddd, *ampm;
4798 int daydiff;
4800 str[0] = '\0';
4801 rfc822_date(dbuf);
4802 parse_date(dbuf, &now);
4804 /* Figure out if message date lands in the past week */
4806 /* (if message dated this month or last month...) */
4807 if((d.year == now.year && d.month >= now.month - 1) ||
4808 (d.year == now.year - 1 && d.month == 12 && now.month == 1)){
4810 daydiff = day_of_year(&now) - day_of_year(&d);
4813 * If msg in end of last year (and we're in first bit of "this"
4814 * year), diff will be backwards; fix up by adding number of days
4815 * in last year (usually 365, but occasionally 366)...
4817 if(d.year == now.year - 1){
4818 last_day = d;
4819 last_day.month = 12;
4820 last_day.day = 31;
4822 daydiff += day_of_year(&last_day);
4825 else
4826 daydiff = -100; /* comfortably out of range (of past week) */
4828 /* Build 2-digit hour and am/pm indicator, used below */
4830 if(d.hour >= 0 && d.hour < 24){
4831 snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12);
4832 ampm = (d.hour < 12) ? "am" : "pm";
4833 snprintf(hour24, sizeof(hour24), "%02d", d.hour);
4835 else{
4836 strncpy(hour12, "??", sizeof(hour12));
4837 hour12[sizeof(hour12)-1] = '\0';
4838 ampm = "__";
4839 strncpy(hour24, "??", sizeof(hour24));
4840 hour24[sizeof(hour24)-1] = '\0';
4843 /* Build date/time in str, in format similar to that used by w(1) */
4845 if(daydiff == 0){ /* If date is today, "HH:MMap" */
4846 if(d.minute >= 0 && d.minute < 60)
4847 snprintf(minzero, sizeof(minzero), "%02d", d.minute);
4848 else{
4849 strncpy(minzero, "??", sizeof(minzero));
4850 minzero[sizeof(minzero)-1] = '\0';
4853 snprintf(str, str_len, "%s:%s%s", sdatetime24type ? hour24 : hour12,
4854 minzero, sdatetime24type ? "" : ampm);
4856 else if(daydiff >= 1 && daydiff < 6){ /* If <1wk ago, "DddHHap" */
4858 if(d.month >= 1 && d.day >= 1 && d.year >= 0 &&
4859 d.month <= 12 && d.day <= 31 && d.year <= 9999)
4860 Ddd = day_abbrev_locale(day_of_week(&d));
4861 else
4862 Ddd = "???";
4864 snprintf(str, str_len, "%s%s%s", Ddd, hour12, ampm);
4866 else{ /* date is old or future, "ddMmmyy" */
4867 strncpy(monabb, (d.month >= 1 && d.month <= 12)
4868 ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
4869 monabb[sizeof(monabb)-1] = '\0';
4871 if(d.day >= 1 && d.day <= 31)
4872 snprintf(dayzero, sizeof(dayzero), "%02d", d.day);
4873 else{
4874 strncpy(dayzero, "??", sizeof(dayzero));
4875 dayzero[sizeof(dayzero)-1] = '\0';
4878 if(d.year >= 0 && d.year <= 9999)
4879 snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100);
4880 else{
4881 strncpy(yearzero, "??", sizeof(yearzero));
4882 yearzero[sizeof(yearzero)-1] = '\0';
4885 snprintf(str, str_len, "%s%s%s", dayzero, monabb, yearzero);
4888 if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */
4889 if(v)
4890 memmove(str, str + 1, strlen(str));
4891 else
4892 str[0] = ' ';
4899 * Format a string representing the keywords into ice.
4901 * This needs to be done in UTF-8, which may be tricky since it isn't labelled.
4903 * Args idata -- which message?
4904 * kwtype -- keywords or kw initials
4905 * ice -- index cache entry for message
4907 void
4908 key_str(INDEXDATA_S *idata, SubjKW kwtype, ICE_S *ice)
4910 int firstone = 1;
4911 KEYWORD_S *kw;
4912 char *word;
4913 COLOR_PAIR *color = NULL;
4914 SPEC_COLOR_S *sc = ps_global->kw_colors;
4915 IELEM_S *ielem = NULL;
4916 IFIELD_S *ourifield = NULL;
4918 if(ice && ice->ifield){
4919 /* move to last ifield, the one we're working */
4920 for(ourifield = ice->ifield;
4921 ourifield && ourifield->next;
4922 ourifield = ourifield->next)
4926 if(!ourifield)
4927 return;
4929 if(kwtype == KWInit){
4930 for(kw = ps_global->keywords; kw; kw = kw->next){
4931 if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
4932 word = (kw->nick && kw->nick[0]) ? kw->nick :
4933 (kw->kw && kw->kw[0]) ? kw->kw : "";
4936 * Pick off the first initial. Since word is UTF-8 it may
4937 * take more than one byte for the first initial.
4940 if(word && word[0]){
4941 UCS ucs;
4942 unsigned long remaining_octets;
4943 unsigned char *inputp;
4945 remaining_octets = strlen(word);
4946 inputp = (unsigned char *) word;
4947 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
4948 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
4949 ielem = new_ielem(&ourifield->ielem);
4950 ielem->freedata = 1;
4951 ielem->datalen = (unsigned) (inputp - (unsigned char *) word);
4952 ielem->data = (char *) fs_get((ielem->datalen + 1) * sizeof(char));
4953 strncpy(ielem->data, word, ielem->datalen);
4954 ielem->data[ielem->datalen] = '\0';
4956 if(pico_usingcolor()
4957 && ((kw->nick && kw->nick[0]
4958 && (color=hdr_color(kw->nick,NULL,sc)))
4959 || (kw->kw && kw->kw[0]
4960 && (color=hdr_color(kw->kw,NULL,sc))))){
4961 ielem->color = color;
4962 color = NULL;
4967 if(color)
4968 free_color_pair(&color);
4972 else if(kwtype == KW){
4973 for(kw = ps_global->keywords; kw; kw = kw->next){
4974 if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
4976 if(!firstone){
4977 ielem = new_ielem(&ourifield->ielem);
4978 ielem->freedata = 1;
4979 ielem->data = cpystr(" ");
4980 ielem->datalen = 1;
4983 firstone = 0;
4985 word = (kw->nick && kw->nick[0]) ? kw->nick :
4986 (kw->kw && kw->kw[0]) ? kw->kw : "";
4988 if(word[0]){
4989 ielem = new_ielem(&ourifield->ielem);
4990 ielem->freedata = 1;
4991 ielem->data = cpystr(word);
4992 ielem->datalen = strlen(word);
4994 if(pico_usingcolor()
4995 && ((kw->nick && kw->nick[0]
4996 && (color=hdr_color(kw->nick,NULL,sc)))
4997 || (kw->kw && kw->kw[0]
4998 && (color=hdr_color(kw->kw,NULL,sc))))){
4999 ielem->color = color;
5000 color = NULL;
5004 if(color)
5005 free_color_pair(&color);
5011 * If we're coloring some of the fields then add a dummy field
5012 * at the end that can soak up the rest of the space after the last
5013 * colored keyword. Otherwise, the last one's color will extend to
5014 * the end of the field.
5016 if(pico_usingcolor()){
5017 ielem = new_ielem(&ourifield->ielem);
5018 ielem->freedata = 1;
5019 ielem->data = cpystr(" ");
5020 ielem->datalen = 1;
5023 ourifield->leftadj = 1;
5024 set_ielem_widths_in_field(ourifield);
5028 void
5029 prio_str(INDEXDATA_S *idata, IndexColType ctype, ICE_S *ice)
5031 IFIELD_S *ourifield = NULL;
5032 IELEM_S *ielem = NULL;
5033 char *hdrval;
5034 PRIORITY_S *p;
5035 int v;
5037 if(ice && ice->ifield){
5038 /* move to last ifield, the one we're working */
5039 for(ourifield = ice->ifield;
5040 ourifield && ourifield->next;
5041 ourifield = ourifield->next)
5045 if(!ourifield)
5046 return;
5048 hdrval = fetch_header(idata, PRIORITYNAME);
5050 if(hdrval && hdrval[0] && isdigit(hdrval[0])){
5051 v = atoi(hdrval);
5052 if(v >= 1 && v <= 5 && v != 3){
5054 ielem = new_ielem(&ourifield->ielem);
5055 ielem->freedata = 1;
5057 switch(ctype){
5058 case iPrio:
5059 ielem->data = (char *) fs_get(2 * sizeof(char));
5060 ielem->data[0] = hdrval[0];
5061 ielem->data[1] = '\0';
5062 break;
5064 case iPrioAlpha:
5065 for(p = priorities; p && p->desc; p++)
5066 if(p->val == v)
5067 break;
5069 if(p && p->desc)
5070 ielem->data = cpystr(p->desc);
5072 break;
5074 case iPrioBang:
5075 ielem->data = (char *) fs_get(2 * sizeof(char));
5076 ielem->data[0] = '\0';
5077 switch(v){
5078 case 1: case 2:
5079 ielem->data[0] = '!';
5080 break;
5082 case 4: case 5:
5084 * We could put a Unicode downarrow in here but
5085 * we have no way of knowing if the user's font
5086 * will have it (I think).
5088 ielem->data[0] = 'v';
5089 break;
5092 ielem->data[1] = '\0';
5094 break;
5096 default:
5097 alpine_panic("Unhandled case in prio_str");
5098 break;
5101 if(!ielem->data)
5102 ielem->data = cpystr("");
5104 if(ielem && ielem->data)
5105 ielem->datalen = strlen(ielem->data);
5107 if((v == 1 || v == 2) && pico_usingcolor()
5108 && ps_global->VAR_IND_HIPRI_FORE_COLOR
5109 && ps_global->VAR_IND_HIPRI_BACK_COLOR){
5110 ielem->type = eTypeCol;
5111 ielem->freecolor = 1;
5112 ielem->color = new_color_pair(ps_global->VAR_IND_HIPRI_FORE_COLOR, ps_global->VAR_IND_HIPRI_BACK_COLOR);
5114 else if((v == 4 || v == 5) && pico_usingcolor()
5115 && ps_global->VAR_IND_LOPRI_FORE_COLOR
5116 && ps_global->VAR_IND_LOPRI_BACK_COLOR){
5117 ielem->type = eTypeCol;
5118 ielem->freecolor = 1;
5119 ielem->color = new_color_pair(ps_global->VAR_IND_LOPRI_FORE_COLOR, ps_global->VAR_IND_LOPRI_BACK_COLOR);
5122 ourifield->leftadj = 1;
5123 set_ielem_widths_in_field(ourifield);
5126 fs_give((void **) &hdrval);
5131 void
5132 header_str(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok, ICE_S *ice)
5134 IFIELD_S *ourifield = NULL;
5135 IELEM_S *ielem = NULL;
5136 char *fieldval = NULL;
5138 if(ice && ice->ifield){
5139 /* move to last ifield, the one we're working */
5140 for(ourifield = ice->ifield;
5141 ourifield && ourifield->next;
5142 ourifield = ourifield->next)
5146 if(!ourifield)
5147 return;
5149 fieldval = get_fieldval(idata, hdrtok);
5151 if(fieldval){
5152 ielem = new_ielem(&ourifield->ielem);
5153 ielem->freedata = 1;
5154 ielem->data = fieldval;
5155 ielem->datalen = strlen(fieldval);
5156 fieldval = NULL;
5157 ourifield->leftadj = (hdrtok->adjustment == Left) ? 1 : 0;
5160 set_ielem_widths_in_field(ourifield);
5164 char *
5165 get_fieldval(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok)
5167 int sep, fieldnum;
5168 char *hdrval = NULL, *testval;
5169 char *fieldval = NULL, *firstval;
5170 char *retfieldval = NULL;
5172 if(!hdrtok)
5173 return(retfieldval);
5175 if(hdrtok && hdrtok->hdrname && hdrtok->hdrname[0])
5176 hdrval = fetch_header(idata, hdrtok ? hdrtok->hdrname : "");
5178 /* find start of fieldnum'th field */
5179 fieldval = hdrval;
5180 for(fieldnum = MAX(hdrtok->fieldnum-1, 0);
5181 fieldnum > 0 && fieldval && *fieldval; fieldnum--){
5183 firstval = NULL;
5184 for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
5185 testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
5186 if(testval && (!firstval || testval < firstval))
5187 firstval = testval;
5190 fieldval = firstval;
5191 if(fieldval && *fieldval)
5192 fieldval++;
5195 /* tie off end of field */
5196 if(fieldval && *fieldval && hdrtok->fieldnum > 0){
5197 firstval = NULL;
5198 for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
5199 testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
5200 if(testval && (!firstval || testval < firstval))
5201 firstval = testval;
5204 if(firstval)
5205 *firstval = '\0';
5208 if(!fieldval)
5209 fieldval = "";
5211 retfieldval = cpystr(fieldval);
5213 if(hdrval)
5214 fs_give((void **) &hdrval);
5216 return(retfieldval);
5220 long
5221 scorevalfrommsg(MAILSTREAM *stream, MsgNo rawno, HEADER_TOK_S *hdrtok, int no_fetch)
5223 INDEXDATA_S idata;
5224 MESSAGECACHE *mc;
5225 char *fieldval = NULL;
5226 long retval = 0L;
5228 memset(&idata, 0, sizeof(INDEXDATA_S));
5229 idata.stream = stream;
5230 idata.no_fetch = no_fetch;
5231 idata.msgno = mn_raw2m(sp_msgmap(stream), rawno);
5232 idata.rawno = rawno;
5233 if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
5234 && (mc = mail_elt(stream, idata.rawno))){
5235 idata.size = mc->rfc822_size;
5236 index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
5238 else
5239 idata.bogus = 2;
5241 fieldval = get_fieldval(&idata, hdrtok);
5243 if(fieldval){
5244 retval = atol(fieldval);
5245 fs_give((void **) &fieldval);
5248 return(retval);
5253 * Put a string representing the subject into str. Idata tells us which
5254 * message we are referring to.
5256 * This means we should ensure that all data ends up being UTF-8 data.
5257 * That covers the data in ice ielems and str.
5259 * Args idata -- which message?
5260 * str -- destination buffer
5261 * strsize -- size of str buffer
5262 * kwtype -- prepend keywords or kw initials before the subject
5263 * opening -- add first text from body of message if there's room
5264 * ice -- index cache entry for message
5266 void
5267 subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int opening, ICE_S *ice)
5269 char *subject, *origsubj, *origstr, *rawsubj, *sptr = NULL;
5270 char *p, *border, *q = NULL, *free_subj = NULL;
5271 char *sp;
5272 size_t len;
5273 int width = -1;
5274 int depth = 0, mult = 2;
5275 int save;
5276 int do_subj = 0, truncated_tree = 0;
5277 PINETHRD_S *thd, *thdorig;
5278 IELEM_S *ielem = NULL, *subjielem = NULL;
5279 IFIELD_S *ourifield = NULL;
5281 if(strsize <= 0)
5282 return;
5285 * If we need the data at the start of the message and we're in
5286 * a c-client callback, defer the data lookup until later.
5288 if(opening && idata->no_fetch){
5289 idata->bogus = 1;
5290 return;
5293 if(ice && ice->ifield){
5294 /* move to last ifield, the one we're working on */
5295 for(ourifield = ice->ifield;
5296 ourifield && ourifield->next;
5297 ourifield = ourifield->next)
5301 str[0] = str[strsize-1] = '\0';
5302 origstr = str;
5303 rawsubj = fetch_subject(idata);
5304 if(!rawsubj)
5305 rawsubj = "";
5308 * Before we do anything else, decode the character set in the subject and
5309 * work with the result.
5311 sp = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
5312 SIZEOF_20KBUF, rawsubj);
5314 len = strlen(sp);
5315 len += 100; /* for possible charset, escaped characters */
5316 origsubj = fs_get((len+1) * sizeof(unsigned char));
5317 origsubj[0] = '\0';
5319 iutf8ncpy(origsubj, sp, len);
5321 origsubj[len] = '\0';
5322 replace_tabs_by_space(origsubj);
5323 removing_trailing_white_space(origsubj);
5326 * origsubj is the original subject but it has been decoded. We need
5327 * to free it at the end of this routine.
5332 * prepend_keyword will put the keyword stuff before the subject
5333 * and split the subject up into its colored parts in subjielem.
5334 * Subjielem is a local ielem which will have to be fit into the
5335 * real ifield->ielem later. The print_format strings in subjielem will
5336 * not be filled in by prepend_keyword because of the fact that we
5337 * may have to adjust things for threading below.
5338 * We use subjielem in case we want to insert some threading information
5339 * at the front of the subject.
5341 if(kwtype == KW || kwtype == KWInit){
5342 subject = prepend_keyword_subject(idata->stream, idata->rawno,
5343 origsubj, kwtype,
5344 ourifield ? &subjielem : NULL,
5345 ps_global->VAR_KW_BRACES);
5346 free_subj = subject;
5348 else{
5349 subject = origsubj;
5350 if(ourifield){
5351 subjielem = new_ielem(&subjielem);
5352 subjielem->type = eTypeCol;
5353 subjielem->freedata = 1;
5354 subjielem->data = cpystr(subject);
5355 subjielem->datalen = strlen(subject);
5356 if(pico_usingcolor()
5357 && ps_global->VAR_IND_SUBJ_FORE_COLOR
5358 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
5359 subjielem->freecolor = 1;
5360 subjielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
5366 * This space is here so that if the subject does
5367 * not extend all the way to the end of the field then
5368 * we'll switch the color back and paint the rest of the
5369 * field in the Normal color or the index line color.
5371 if(!opening){
5372 ielem = new_ielem(&subjielem);
5373 ielem->freedata = 1;
5374 ielem->data = cpystr(" ");
5375 ielem->datalen = 1;
5378 if(!subject)
5379 subject = "";
5381 if(THREADING()
5382 && (ps_global->thread_disp_style == THREAD_STRUCT
5383 || ps_global->thread_disp_style == THREAD_MUTTLIKE
5384 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ1
5385 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ2)){
5388 * Why do we want to truncate the subject and from strs?
5389 * It's so we can put the [5] thread count things in below when
5390 * we are threading and the thread structure runs off the right
5391 * hand edge of the screen. This routine doesn't know that it
5392 * is running off the edge unless it knows the actual width
5393 * that we have to draw in.
5395 if(pith_opt_truncate_sfstr
5396 && (*pith_opt_truncate_sfstr)()
5397 && ourifield
5398 && ourifield->width > 0)
5399 width = ourifield->width;
5401 if(width < 0)
5402 width = strsize-1;
5404 width = MIN(width, strsize-1);
5407 * We're counting on the fact that this initial part of the
5408 * string is ascii and we have one octet per character and
5409 * characters are width 1 on the screen.
5411 border = str + width;
5413 thdorig = thd = fetch_thread(idata->stream, idata->rawno);
5415 if(pith_opt_condense_thread_cue)
5416 width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
5417 thd && thd->next
5418 && get_lflag(idata->stream,
5419 NULL,idata->rawno,
5420 MN_COLL));
5423 * width is < available strsize and
5424 * border points to something less than or equal
5425 * to the end of the buffer.
5428 sptr = str;
5430 if(thd)
5431 while(thd->parent &&
5432 (thd = fetch_thread(idata->stream, thd->parent)))
5433 depth++;
5435 if(depth > 0){
5436 if(ps_global->thread_disp_style == THREAD_INDENT_SUBJ1)
5437 mult = 1;
5439 sptr += (mult*depth);
5440 for(thd = thdorig, p = str + mult*depth - mult;
5441 thd && thd->parent && p >= str;
5442 thd = fetch_thread(idata->stream, thd->parent), p -= mult){
5443 if(p + mult >= border && !q){
5444 if(width >= 4 && depth < 100){
5445 snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
5446 q = str + width-4;
5448 else if(width >= 5 && depth < 1000){
5449 snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
5450 q = str + width-5;
5452 else{
5453 snprintf(str, width+1, "%s", repeat_char(width, '.'));
5454 q = str;
5457 border = q;
5458 truncated_tree++;
5461 if(p < border){
5462 p[0] = ' ';
5463 if(p + 1 < border)
5464 p[1] = ' ';
5466 if(ps_global->thread_disp_style == THREAD_STRUCT
5467 || ps_global->thread_disp_style == THREAD_MUTTLIKE){
5469 * WARNING!
5470 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
5471 * is ascii.
5473 if(thd == thdorig && !thd->branch)
5474 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
5475 else if(thd == thdorig || thd->branch)
5476 p[0] = '|';
5478 if(p + 1 < border && thd == thdorig)
5479 p[1] = '-';
5485 if(sptr && !truncated_tree){
5487 * Look to see if the subject is the same as the previous
5488 * message in the thread, if any. If it is the same, don't
5489 * reprint the subject.
5491 * Note that when we're prepending keywords to the subject,
5492 * and the user changes a keyword, we do invalidate
5493 * the index cache for that message but we don't go to the
5494 * trouble of invalidating the index cache for the the child
5495 * of that node in the thread, so the MUTT subject line
5496 * display for the child may be wrong. That is, it may show
5497 * it is the same as this subject even though it no longer
5498 * is, or vice versa.
5500 if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
5501 if(depth == 0)
5502 do_subj++;
5503 else{
5504 if(thdorig->parent &&
5505 (thd = fetch_thread(idata->stream, thdorig->parent))
5506 && thd->rawno){
5507 char *this_orig = NULL,
5508 *prev_orig = NULL,
5509 *free_prev_orig = NULL,
5510 *this_prep = NULL, /* includes prepend */
5511 *prev_prep = NULL;
5512 ENVELOPE *env;
5513 mailcache_t mc;
5514 SORTCACHE *sc = NULL;
5516 /* get the stripped subject of previous message */
5517 mc = (mailcache_t) mail_parameters(NIL, GET_CACHE, NIL);
5518 if(mc)
5519 sc = (*mc)(idata->stream, thd->rawno, CH_SORTCACHE);
5521 if(sc && sc->subject)
5522 prev_orig = sc->subject;
5523 else{
5524 char *stripthis;
5526 env = pine_mail_fetchenvelope(idata->stream,
5527 thd->rawno);
5528 stripthis = (env && env->subject)
5529 ? env->subject : "";
5531 mail_strip_subject(stripthis, &prev_orig);
5533 free_prev_orig = prev_orig;
5536 mail_strip_subject(rawsubj, &this_orig);
5538 if(kwtype == KW || kwtype == KWInit){
5539 prev_prep = prepend_keyword_subject(idata->stream,
5540 thd->rawno,
5541 prev_orig,
5542 kwtype, NULL,
5543 ps_global->VAR_KW_BRACES);
5545 this_prep = prepend_keyword_subject(idata->stream,
5546 idata->rawno,
5547 this_orig,
5548 kwtype, NULL,
5549 ps_global->VAR_KW_BRACES);
5551 if((this_prep || prev_prep)
5552 && ((this_prep && !prev_prep)
5553 || (prev_prep && !this_prep)
5554 || strucmp(this_prep, prev_prep)))
5555 do_subj++;
5557 else{
5558 if((this_orig || prev_orig)
5559 && ((this_orig && !prev_orig)
5560 || (prev_orig && !this_orig)
5561 || strucmp(this_orig, prev_orig)))
5562 do_subj++;
5566 * If some of the thread is zoomed out of view, we
5567 * want to display the subject of the first one that
5568 * is in view. If any of the parents or grandparents
5569 * etc of this message are visible, then we don't
5570 * need to worry about it. If all of the parents have
5571 * been zoomed away, then this is the first one.
5573 * When you're looking at a particular case where
5574 * some of the messages of a thread are selected it
5575 * seems like we should look at not only our
5576 * direct parents, but the siblings of the parent
5577 * too. But that's not really correct, because those
5578 * siblings are basically the starts of different
5579 * branches, separate from our branch. They could
5580 * have their own subjects, for example. This will
5581 * give us cases where it looks like we are showing
5582 * the subject too much, but it will be correct!
5584 * In zoom_index() we clear_index_cache_ent for
5585 * some lines which have subjects which might become
5586 * visible when we zoom, and also in set_lflags
5587 * where we might change subjects by unselecting
5588 * something when zoomed.
5590 if(!do_subj){
5591 while(thd){
5592 if(!msgline_hidden(idata->stream,
5593 sp_msgmap(idata->stream),
5594 mn_raw2m(sp_msgmap(idata->stream),
5595 (long) thd->rawno),
5596 0)){
5597 break; /* found a visible parent */
5600 if(thd && thd->parent)
5601 thd = fetch_thread(idata->stream,thd->parent);
5602 else
5603 thd = NULL;
5606 if(!thd) /* none were visible */
5607 do_subj++;
5610 if(this_orig)
5611 fs_give((void **) &this_orig);
5613 if(this_prep)
5614 fs_give((void **) &this_prep);
5616 if(free_prev_orig)
5617 fs_give((void **) &free_prev_orig);
5619 if(prev_prep)
5620 fs_give((void **) &prev_prep);
5622 else
5623 do_subj++;
5626 else
5627 do_subj++;
5629 if(do_subj){
5631 * We don't need to worry about truncating to width
5632 * here. If we go over the right hand edge it will be
5633 * truncated.
5635 strsize -= (sptr - str);
5637 strncpy(sptr, subject, strsize-1);
5638 sptr[strsize-1] = '\0';
5640 else if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
5641 strsize -= (sptr - str);
5643 if(strsize > 0){
5644 sptr[0] = '>';
5645 sptr++;
5649 * We decided we don't need the subject so we'd better
5650 * eliminate subjielem.
5652 free_ielem(&subjielem);
5655 else
5656 free_ielem(&subjielem); /* no room for actual subject */
5658 if(ourifield && sptr && sptr > origstr){
5659 ielem = new_ielem(&ourifield->ielem);
5660 ielem->type = eThreadInfo;
5661 ielem->freedata = 1;
5662 save = *sptr;
5663 *sptr = '\0';
5664 ielem->data = cpystr(origstr);
5665 ielem->datalen = strlen(origstr);
5666 *sptr = save;
5669 else{
5671 * Not much to do for the non-threading case. Just copy the
5672 * subject we have so far into str and truncate it.
5674 strncpy(str, subject, strsize-1);
5675 str[strsize-1] = '\0';
5678 if(ourifield){
5680 * We need to add subjielem to the end of the ourifield->ielem list.
5682 if(subjielem){
5683 if(ourifield->ielem){
5684 for(ielem = ourifield->ielem;
5685 ielem && ielem->next; ielem = ielem->next)
5688 ielem->next = subjielem;
5690 else
5691 ourifield->ielem = subjielem;
5694 ourifield->leftadj = 1;
5697 if(opening && ourifield){
5698 IELEM_S *ftielem = NULL;
5699 size_t len;
5700 char *first_text;
5702 first_text = fetch_firsttext(idata, 0);
5704 if(first_text){
5705 char sep[200];
5706 int seplen;
5708 strncpy(sep, ps_global->VAR_OPENING_SEP ? ps_global->VAR_OPENING_SEP : " - ",
5709 sizeof(sep));
5710 sep[sizeof(sep)-1] = '\0';
5711 removing_double_quotes(sep);
5712 seplen = strlen(sep);
5714 ftielem = new_ielem(&ftielem);
5715 ftielem->type = eTypeCol;
5716 ftielem->freedata = 1;
5717 len = strlen(first_text) + seplen;
5718 ftielem->data = (char *) fs_get((len + 1) * sizeof(char));
5720 strncpy(ftielem->data, sep, seplen);
5721 strncpy(ftielem->data+seplen, first_text, len+1-seplen);
5722 ftielem->data[len] = '\0';
5724 ftielem->datalen = strlen(ftielem->data);
5725 if(first_text)
5726 fs_give((void **) &first_text);
5728 if(ftielem){
5729 if(pico_usingcolor()
5730 && ps_global->VAR_IND_OP_FORE_COLOR
5731 && ps_global->VAR_IND_OP_BACK_COLOR){
5732 ftielem->freecolor = 1;
5733 ftielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR, ps_global->VAR_IND_OP_BACK_COLOR);
5736 * This space is here so that if the opening text does
5737 * not extend all the way to the end of the field then
5738 * we'll switch the color back and paint the rest of the
5739 * field in the Normal color or the index line color.
5741 ielem = new_ielem(&ftielem);
5742 ielem->freedata = 1;
5743 ielem->data = cpystr(" ");
5744 ielem->datalen = 1;
5747 if(ourifield->ielem){
5748 for(ielem = ourifield->ielem;
5749 ielem && ielem->next; ielem = ielem->next)
5752 ielem->next = ftielem;
5754 else
5755 ourifield->ielem = ftielem;
5758 ourifield->leftadj = 1;
5762 if(ourifield)
5763 set_ielem_widths_in_field(ourifield);
5765 if(origsubj)
5766 fs_give((void **) &origsubj);
5768 if(free_subj)
5769 fs_give((void **) &free_subj);
5774 * Returns an allocated string which is the passed in subject with a
5775 * list of keywords prepended.
5777 * If kwtype == KW you will end up with
5779 * {keyword1 keyword2} subject
5781 * (actually, keyword nicknames will be used instead of the actual keywords
5782 * in the case that the user defined nicknames)
5784 * If kwtype == KWInit you get
5786 * {AB} subject
5788 * where A is the first letter of the first keyword and B is the first letter
5789 * of the second defined keyword. No space between them. There could be more
5790 * than two.
5792 * If an ielemp is passed in it will be filled out with the data and colors
5793 * of the pieces of the subject but the print_format strings will not
5794 * be set.
5796 char *
5797 prepend_keyword_subject(MAILSTREAM *stream, long int rawno, char *subject,
5798 SubjKW kwtype, IELEM_S **ielemp, char *braces)
5800 char *p, *next_piece, *retsubj = NULL, *str;
5801 char *left_brace = NULL, *right_brace = NULL;
5802 size_t len;
5803 int some_set = 0, save;
5804 IELEM_S *ielem;
5805 KEYWORD_S *kw;
5806 COLOR_PAIR *color = NULL;
5807 SPEC_COLOR_S *sc = ps_global->kw_colors;
5809 if(!subject)
5810 subject = "";
5812 if(braces && *braces)
5813 get_pair(braces, &left_brace, &right_brace, 1, 0);
5815 len = (left_brace ? strlen(left_brace) : 0) +
5816 (right_brace ? strlen(right_brace) : 0);
5818 if(stream && rawno >= 0L && rawno <= stream->nmsgs){
5819 for(kw = ps_global->keywords; kw; kw = kw->next)
5820 if(user_flag_is_set(stream, rawno, kw->kw)){
5821 if(kwtype == KW){
5822 if(some_set)
5823 len++; /* space between keywords */
5825 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5826 len += strlen(str);
5828 else if(kwtype == KWInit){
5829 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5830 /* interested in only the first UTF-8 initial */
5831 if(str && str[0]){
5832 UCS ucs;
5833 unsigned long remaining_octets;
5834 unsigned char *inputp;
5836 remaining_octets = strlen(str);
5837 inputp = (unsigned char *) str;
5838 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
5839 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
5840 len += (unsigned) (inputp - (unsigned char *) str);
5845 some_set++;
5849 if((kwtype == KW || kwtype == KWInit) && some_set){
5850 len += strlen(subject); /* subject is already UTF-8 if needed */
5851 retsubj = (char *) fs_get((len + 1) * sizeof(*retsubj));
5852 memset(retsubj, 0, (len + 1) * sizeof(*retsubj));
5853 next_piece = p = retsubj;
5855 for(kw = ps_global->keywords; kw; kw = kw->next){
5856 if(user_flag_is_set(stream, rawno, kw->kw)){
5857 if(p == retsubj){
5858 if(left_brace && len > 0)
5859 sstrncpy(&p, left_brace, len);
5861 else if(kwtype == KW)
5862 *p++ = ' ';
5864 if(ielemp && p > next_piece){
5865 save = *p;
5866 *p = '\0';
5867 ielem = new_ielem(ielemp);
5868 ielem->freedata = 1;
5869 ielem->data = cpystr(next_piece);
5870 ielem->datalen = strlen(next_piece);
5871 *p = save;
5872 next_piece = p;
5875 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5877 if(kwtype == KWInit){
5878 if(str && str[0]){
5879 UCS ucs;
5880 unsigned long remaining_octets;
5881 unsigned char *inputp;
5883 remaining_octets = strlen(str);
5884 inputp = (unsigned char *) str;
5885 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
5886 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
5887 if(len-(p-retsubj) > 0){
5888 sstrncpy(&p, str, MIN(inputp - (unsigned char *) str,len-(p-retsubj)));
5889 if(p > next_piece && ielemp && pico_usingcolor()
5890 && ((kw->nick && kw->nick[0]
5891 && (color=hdr_color(kw->nick,NULL,sc)))
5892 || (kw->kw && kw->kw[0]
5893 && (color=hdr_color(kw->kw,NULL,sc))))){
5894 ielem = new_ielem(ielemp);
5895 ielem->freedata = 1;
5896 save = *p;
5897 *p = '\0';
5898 ielem->data = cpystr(next_piece);
5899 ielem->datalen = strlen(next_piece);
5900 ielem->color = color;
5901 color = NULL;
5902 *p = save;
5903 next_piece = p;
5908 if(color)
5909 free_color_pair(&color);
5912 else{
5913 if(len-(p-retsubj) > 0)
5914 sstrncpy(&p, str, len-(p-retsubj));
5916 if(p > next_piece && ielemp && pico_usingcolor()
5917 && ((kw->nick && kw->nick[0]
5918 && (color=hdr_color(kw->nick,NULL,sc)))
5919 || (kw->kw && kw->kw[0]
5920 && (color=hdr_color(kw->kw,NULL,sc))))){
5921 ielem = new_ielem(ielemp);
5922 ielem->freedata = 1;
5923 save = *p;
5924 *p = '\0';
5925 ielem->data = cpystr(next_piece);
5926 ielem->datalen = strlen(next_piece);
5927 ielem->color = color;
5928 color = NULL;
5929 *p = save;
5930 next_piece = p;
5933 if(color)
5934 free_color_pair(&color);
5939 if(len-(p-retsubj) > 0 && right_brace)
5940 sstrncpy(&p, right_brace, len-(p-retsubj));
5942 if(ielemp && p > next_piece){
5943 save = *p;
5944 *p = '\0';
5945 ielem = new_ielem(ielemp);
5946 ielem->freedata = 1;
5947 ielem->data = cpystr(next_piece);
5948 ielem->datalen = strlen(next_piece);
5949 *p = save;
5950 next_piece = p;
5953 if(len-(p-retsubj) > 0 && subject)
5954 sstrncpy(&p, subject, len-(p-retsubj));
5956 if(ielemp && p > next_piece){
5957 save = *p;
5958 *p = '\0';
5959 ielem = new_ielem(ielemp);
5960 ielem->type = eTypeCol;
5961 ielem->freedata = 1;
5962 ielem->data = cpystr(next_piece);
5963 ielem->datalen = strlen(next_piece);
5964 *p = save;
5965 next_piece = p;
5966 if(pico_usingcolor()
5967 && ps_global->VAR_IND_SUBJ_FORE_COLOR
5968 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
5969 ielem->freecolor = 1;
5970 ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
5974 retsubj[len] = '\0'; /* just making sure */
5976 else{
5977 if(ielemp){
5978 ielem = new_ielem(ielemp);
5979 ielem->type = eTypeCol;
5980 ielem->freedata = 1;
5981 ielem->data = cpystr(subject);
5982 ielem->datalen = strlen(subject);
5983 if(pico_usingcolor()
5984 && ps_global->VAR_IND_SUBJ_FORE_COLOR
5985 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
5986 ielem->freecolor = 1;
5987 ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
5991 retsubj = cpystr(subject);
5994 if(braces){
5995 if(left_brace)
5996 fs_give((void **) &left_brace);
5998 if(right_brace)
5999 fs_give((void **) &right_brace);
6002 return(retsubj);
6007 * This means we should ensure that all data ends up being UTF-8 data.
6008 * That covers the data in ice ielems and str.
6010 void
6011 from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_S *ice)
6013 char *field, *newsgroups, *border, *p, *fptr = NULL, *q = NULL;
6014 ADDRESS *addr;
6015 int width = -1;
6016 int depth = 0, mult = 2;
6017 PINETHRD_S *thd, *thdorig;
6019 if(THREADING()
6020 && (ps_global->thread_disp_style == THREAD_INDENT_FROM1
6021 || ps_global->thread_disp_style == THREAD_INDENT_FROM2
6022 || ps_global->thread_disp_style == THREAD_STRUCT_FROM)){
6024 if(pith_opt_truncate_sfstr && (*pith_opt_truncate_sfstr)()){
6025 IFIELD_S *ourifield = NULL;
6027 if(ice && ice->ifield){
6028 /* move to last ifield, the one we're working on */
6029 for(ourifield = ice->ifield;
6030 ourifield && ourifield->next;
6031 ourifield = ourifield->next)
6035 if(ourifield && ourifield->width > 0)
6036 width = ourifield->width;
6039 if(width < 0)
6040 width = strsize-1;
6042 width = MIN(width, strsize-1);
6044 thdorig = thd = fetch_thread(idata->stream, idata->rawno);
6045 border = str + width;
6046 if(pith_opt_condense_thread_cue)
6047 width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
6048 thd && thd->next
6049 && get_lflag(idata->stream,
6050 NULL,idata->rawno,
6051 MN_COLL));
6053 fptr = str;
6055 if(thd)
6056 while(thd->parent && (thd = fetch_thread(idata->stream, thd->parent)))
6057 depth++;
6059 if(depth > 0){
6060 if(ps_global->thread_disp_style == THREAD_INDENT_FROM1)
6061 mult = 1;
6063 fptr += (mult*depth);
6064 for(thd = thdorig, p = str + mult*depth - mult;
6065 thd && thd->parent && p >= str;
6066 thd = fetch_thread(idata->stream, thd->parent), p -= mult){
6067 if(p + mult >= border && !q){
6068 if(width >= 4 && depth < 100){
6069 snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
6070 q = str + width-4;
6072 else if(width >= 5 && depth < 1000){
6073 snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
6074 q = str + width-5;
6076 else{
6077 snprintf(str, width+1, "%s", repeat_char(width, '.'));
6078 q = str;
6081 border = q;
6082 fptr = NULL;
6085 if(p + 1 < border){
6086 p[0] = p[1] = ' ';
6087 if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
6089 * WARNING!
6090 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
6091 * is ascii.
6093 if(thd == thdorig && !thd->branch)
6094 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
6095 else if(thd == thdorig || thd->branch)
6096 p[0] = '|';
6098 if(thd == thdorig)
6099 p[1] = '-';
6102 else if(p < border){
6103 p[0] = ' ';
6104 if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
6106 * WARNING!
6107 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
6108 * is ascii.
6110 if(thd == thdorig && !thd->branch)
6111 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
6112 else if(thd == thdorig || thd->branch)
6113 p[0] = '|';
6119 else
6120 fptr = str;
6122 if(fptr){
6123 strsize -= (fptr - str);
6124 switch(ctype){
6125 case iFromTo:
6126 case iFromToNotNews:
6127 if(!(addr = fetch_from(idata)) || address_is_us(addr, ps_global)){
6128 if(strsize-1 <= 4){
6129 strncpy(fptr, "To: ", strsize-1);
6130 fptr[strsize-1] = '\0';
6131 break;
6133 else{
6134 if((field = ((addr = fetch_to(idata))
6135 ? "To"
6136 : (addr = fetch_cc(idata))
6137 ? "Cc"
6138 : NULL))
6139 && set_index_addr(idata, field, addr, "To: ",
6140 strsize-1, fptr))
6141 break;
6143 if(ctype == iFromTo &&
6144 (newsgroups = fetch_newsgroups(idata)) &&
6145 *newsgroups){
6146 snprintf(fptr, strsize, "To: %-*.*s", (int)(strsize-1-4), (int)(strsize-1-4),
6147 newsgroups);
6148 break;
6151 /* else fall thru to From: */
6154 /* else fall thru to From: */
6156 if(idata->bogus)
6157 break;
6159 case iFrom:
6160 set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr);
6161 break;
6163 case iAddress:
6164 case iMailbox:
6165 if((addr = fetch_from(idata)) && addr->mailbox && addr->mailbox[0]){
6166 char *mb = NULL, *hst = NULL, *at = NULL;
6167 size_t len;
6169 mb = addr->mailbox;
6170 if(ctype == iAddress && addr->host && addr->host[0]
6171 && addr->host[0] != '.'){
6172 at = "@";
6173 hst = addr->host;
6176 len = strlen(mb);
6177 if(!at || strsize-1 <= len)
6178 snprintf(fptr, strsize, "%-*.*s", (int)(strsize-1), (int)(strsize-1), mb);
6179 else
6180 snprintf(fptr, strsize, "%s@%-*.*s", mb, (int)(strsize-1-len-1), (int)(strsize-1-len-1), hst);
6183 break;
6185 default:
6186 break;
6189 replace_tabs_by_space(str);
6194 * Set up the elements contained in field so that they take up the
6195 * whole field width. Data is assumed to be UTF-8.
6197 void
6198 set_ielem_widths_in_field(IFIELD_S *ifield)
6200 IELEM_S *ielem = NULL;
6201 int datawidth, fmtwidth;
6203 if(!ifield)
6204 return;
6206 fmtwidth = ifield->width;
6208 for(ielem = ifield->ielem; ielem && fmtwidth > 0; ielem = ielem->next){
6209 if(!ifield->leftadj && ielem->next){
6210 dprint((1, "set_ielem_widths_in_field(%d): right adjust with multiple elements, NOT SUPPOSED TO HAPPEN!\n", (int) ifield->ctype));
6211 assert(0);
6214 datawidth = (int) utf8_width(ielem->data);
6215 if(datawidth >= fmtwidth || !ielem->next){
6216 set_print_format(ielem, fmtwidth, ifield->leftadj);
6217 fmtwidth = 0;
6219 else{
6220 set_print_format(ielem, datawidth, ifield->leftadj);
6221 fmtwidth -= datawidth;
6228 * Simple hash function from K&R 2nd edition, p. 144.
6230 * This one is modified to never return 0 so we can use that as a special
6231 * value. Also, LINE_HASH_N fits in an unsigned long, so it too can be used
6232 * as a special value that can't be returned by line_hash.
6234 unsigned long
6235 line_hash(char *s)
6237 unsigned long hashval;
6239 for(hashval = 0; *s != '\0'; s++)
6240 hashval = *s + 31 * hashval;
6242 hashval = hashval % LINE_HASH_N;
6244 if(!hashval)
6245 hashval++;
6247 return(hashval);
6252 * Returns nonzero if considered hidden, 0 if not considered hidden.
6255 msgline_hidden(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int flags)
6257 int ret;
6259 if(flags & MH_ANYTHD){
6260 ret = ((any_lflagged(msgmap, MN_HIDE) > 0)
6261 && get_lflag(stream, msgmap, msgno, MN_HIDE));
6263 else if(flags & MH_THISTHD && THREADING() && sp_viewing_a_thread(stream)){
6264 ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
6265 || !get_lflag(stream, msgmap, msgno, MN_CHID2));
6267 else{
6268 if(THREADING() && sp_viewing_a_thread(stream)){
6269 ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
6270 || !get_lflag(stream, msgmap, msgno, MN_CHID2)
6271 || get_lflag(stream, msgmap, msgno, MN_CHID));
6273 else if(THRD_INDX()){
6275 * If this message is in the collapsed part of a thread,
6276 * it's hidden. It must be a top-level of a thread to be
6277 * considered visible. Even if it is top-level, it is only
6278 * visible if some message in the thread is not hidden.
6280 if(get_lflag(stream, msgmap, msgno, MN_CHID)) /* not top */
6281 ret = 1;
6282 else{
6283 unsigned long rawno;
6284 PINETHRD_S *thrd = NULL;
6286 rawno = mn_m2raw(msgmap, msgno);
6287 if(rawno)
6288 thrd = fetch_thread(stream, rawno);
6290 ret = !thread_has_some_visible(stream, thrd);
6293 else{
6294 ret = ((any_lflagged(msgmap, MN_HIDE | MN_CHID) > 0)
6295 && get_lflag(stream, msgmap, msgno, MN_HIDE | MN_CHID));
6299 dprint((10,
6300 "msgline_hidden(%ld): %s\n", msgno, ret ? "HID" : "VIS"));
6302 return(ret);
6306 void
6307 adjust_cur_to_visible(MAILSTREAM *stream, MSGNO_S *msgmap)
6309 long n, cur;
6310 int dir;
6312 cur = mn_get_cur(msgmap);
6314 /* if current is hidden, adjust */
6315 if(cur >= 1L && cur <= mn_get_total(msgmap)
6316 && msgline_hidden(stream, msgmap, cur, 0)){
6318 dir = mn_get_revsort(msgmap) ? -1 : 1;
6320 for(n = cur;
6321 ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6322 && (n >= 1L && n <= mn_get_total(msgmap))
6323 && msgline_hidden(stream, msgmap, n, 0);
6324 n -= dir)
6327 if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6328 && (n >= 1L && n <= mn_get_total(msgmap)))
6329 mn_reset_cur(msgmap, n);
6330 else{ /* no visible in that direction */
6331 for(n = cur;
6332 ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6333 && (n >= 1L && n <= mn_get_total(msgmap))
6334 && msgline_hidden(stream, msgmap, n, 0);
6335 n += dir)
6338 if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6339 && (n >= 1L && n <= mn_get_total(msgmap)))
6340 mn_reset_cur(msgmap, n);
6341 /* else trouble! */
6347 void
6348 setup_for_index_index_screen(void)
6350 format_index_line = format_index_index_line;
6351 setup_header_widths = setup_index_header_widths;
6355 void
6356 setup_for_thread_index_screen(void)
6358 format_index_line = format_thread_index_line;
6359 setup_header_widths = setup_thread_header_widths;
6363 unsigned long
6364 ice_hash(ICE_S *ice)
6366 char buf[MAX_SCREEN_COLS+1];
6368 buf[0] = '\0';
6370 if(ice)
6371 simple_index_line(buf, sizeof(buf), ice, 0L);
6373 buf[sizeof(buf) - 1] = '\0';
6375 return(line_hash(buf));
6379 char *
6380 left_adjust(int width)
6382 return(format_str(width, 1));
6386 char *
6387 right_adjust(int width)
6389 return(format_str(width, 0));
6394 * Returns allocated and filled in format string.
6396 char *
6397 format_str(int width, int left)
6399 char *format;
6400 size_t len;
6402 len = PRINT_FORMAT_LEN(width,left) * sizeof(char);
6403 format = (char *) fs_get(len + 1);
6404 copy_format_str(width, left, format, len);
6405 format[len] = '\0';
6407 return(format);
6412 * Put the left or right adjusted format string of width width into
6413 * dest. Dest is of size n+1.
6415 char *
6416 copy_format_str(int width, int left, char *dest, int n)
6418 char *p;
6420 p = int2string(width);
6422 snprintf(dest, n+1, "%%%s%s.%ss", left ? "-" : "", p, p);
6424 dest[n] = '\0';
6426 return(dest);
6431 * Sets up the print_format string to be width wide with left or right
6432 * adjust. Takes care of memory freeing and allocation.
6434 void
6435 set_print_format(IELEM_S *ielem, int width, int leftadj)
6437 if(ielem){
6438 ielem->wid = width;
6440 if(ielem->print_format){
6441 /* is there enough room? */
6442 if(ielem->freeprintf < PRINT_FORMAT_LEN(width,leftadj)+1){
6443 fs_resize((void **) &ielem->print_format,
6444 (PRINT_FORMAT_LEN(width,leftadj)+1) * sizeof(char));
6445 ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);
6448 copy_format_str(width, leftadj, ielem->print_format,
6449 PRINT_FORMAT_LEN(width,leftadj));
6451 else{
6452 ielem->print_format = leftadj ? left_adjust(width)
6453 : right_adjust(width);
6454 ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);