* Update to version 2.19.5
[alpine.git] / pith / mailindx.c
blobd420250aca9c9e2bb1f6e288c6a3cce50c43c121
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-2014 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 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((mc=mail_elt(idata->stream,idata->rawno)) && mc->flagged)
2070 to_us = '*'; /* simple */
2071 else if(!IS_NEWS(idata->stream)){
2072 for(addr = fetch_to(idata); addr; addr = addr->next)
2073 if(address_is_us(addr, ps_global)){
2074 ice->to_us = 1;
2075 if(to_us == ' ')
2076 to_us = '+';
2078 break;
2081 if(to_us != '+' && resent_to_us(idata)){
2082 ice->to_us = 1;
2083 if(to_us == ' ')
2084 to_us = '+';
2087 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
2088 for(addr = fetch_cc(idata); addr; addr = addr->next)
2089 if(address_is_us(addr, ps_global)){
2090 ice->cc_us = 1;
2091 to_us = '-';
2092 break;
2096 status = (!idata->stream || !IS_NEWS(idata->stream)
2097 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))
2098 ? 'N' : ' ';
2100 if(mc->seen)
2101 status = ' ';
2103 if(user_flag_is_set(idata->stream, idata->rawno, FORWARDED_FLAG))
2104 status = 'F';
2106 if(mc->answered)
2107 status = 'A';
2109 if(mc->deleted)
2110 status = 'D';
2113 snprintf(str, sizeof(str), "%c %c", to_us, status);
2115 ifield->leftadj = 1;
2116 for(i = 0; i < 3; i++){
2117 ielem = new_ielem(&ifield->ielem);
2118 ielem->freedata = 1;
2119 ielem->data = (char *) fs_get(2 * sizeof(char));
2120 ielem->data[0] = str[i];
2121 ielem->data[1] = '\0';
2122 ielem->datalen = 1;
2123 set_print_format(ielem, 1, ifield->leftadj);
2126 if(pico_usingcolor()){
2128 if(str[0] == '*'){
2129 if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
2130 ielem = ifield->ielem;
2131 ielem->freecolor = 1;
2132 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
2135 else if(str[0] == '+' || str[0] == '-'){
2136 if(VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
2137 ielem = ifield->ielem;
2138 ielem->freecolor = 1;
2139 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
2143 if(str[2] == 'D'){
2144 if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
2145 ielem = ifield->ielem->next->next;
2146 ielem->freecolor = 1;
2147 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
2150 else if(str[2] == 'A'){
2151 if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
2152 ielem = ifield->ielem->next->next;
2153 ielem->freecolor = 1;
2154 ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
2157 else if(str[2] == 'F'){
2158 if(VAR_IND_FWD_FORE_COLOR && VAR_IND_FWD_BACK_COLOR){
2159 ielem = ifield->ielem->next->next;
2160 ielem->freecolor = 1;
2161 ielem->color = new_color_pair(VAR_IND_FWD_FORE_COLOR, VAR_IND_FWD_BACK_COLOR);
2164 else if(str[2] == 'N'){
2165 if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
2166 ielem = ifield->ielem->next->next;
2167 ielem->freecolor = 1;
2168 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
2173 break;
2175 case iFStatus:
2176 case iIStatus:
2177 case iSIStatus:
2179 char new, answered, deleted, flagged;
2181 if(collapsed){
2182 thrd = fetch_thread(idata->stream, idata->rawno);
2183 to_us = to_us_symbol_for_thread(idata->stream, thrd, 0);
2185 else{
2186 to_us = ' ';
2187 if(!IS_NEWS(idata->stream)){
2188 for(addr = fetch_to(idata); addr; addr = addr->next)
2189 if(address_is_us(addr, ps_global)){
2190 to_us = '+';
2191 break;
2194 if(to_us == ' ' && resent_to_us(idata))
2195 to_us = '+';
2197 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
2198 for(addr = fetch_cc(idata); addr; addr = addr->next)
2199 if(address_is_us(addr, ps_global)){
2200 to_us = '-';
2201 break;
2206 new = answered = deleted = flagged = ' ';
2208 if(collapsed){
2209 unsigned long save_branch, cnt, tot_in_thrd;
2212 * Branch is a sibling, not part of the thread, so
2213 * don't consider it when displaying this line.
2215 save_branch = thrd->branch;
2216 thrd->branch = 0L;
2218 tot_in_thrd = count_flags_in_thread(idata->stream, thrd,
2219 F_NONE);
2221 cnt = count_flags_in_thread(idata->stream, thrd, F_DEL);
2222 if(cnt)
2223 deleted = (cnt == tot_in_thrd) ? 'D' : 'd';
2225 cnt = count_flags_in_thread(idata->stream, thrd, F_ANS);
2226 if(cnt)
2227 answered = (cnt == tot_in_thrd) ? 'A' : 'a';
2229 /* no lower case *, same thing for some or all */
2230 if(count_flags_in_thread(idata->stream, thrd, F_FLAG))
2231 flagged = '*';
2233 new = status_symbol_for_thread(idata->stream, thrd,
2234 cdesc->ctype);
2236 thrd->branch = save_branch;
2238 else{
2239 mc = (idata->rawno > 0L && idata->stream
2240 && idata->rawno <= idata->stream->nmsgs)
2241 ? mail_elt(idata->stream, idata->rawno) : NULL;
2242 if(mc && mc->valid){
2243 if(cdesc->ctype == iIStatus || cdesc->ctype == iSIStatus){
2244 if(mc->recent)
2245 new = mc->seen ? 'R' : 'N';
2246 else if (!mc->seen)
2247 new = 'U';
2249 else if(!mc->seen
2250 && (!IS_NEWS(idata->stream)
2251 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)))
2252 new = 'N';
2254 if(mc->answered)
2255 answered = 'A';
2257 if(mc->deleted)
2258 deleted = 'D';
2260 if(mc->flagged)
2261 flagged = '*';
2265 snprintf(str, sizeof(str), "%c %c%c%c%c", to_us, flagged, new,
2266 answered, deleted);
2268 if(cdesc->ctype == iSIStatus)
2269 start = 2;
2270 else
2271 start = 0;
2273 ifield->leftadj = 1;
2274 for(i = start; i < 6; i++){
2275 ielem = new_ielem(&ifield->ielem);
2276 ielem->freedata = 1;
2277 ielem->data = (char *) fs_get(2 * sizeof(char));
2278 ielem->data[0] = str[i];
2279 ielem->data[1] = '\0';
2280 ielem->datalen = 1;
2281 set_print_format(ielem, 1, ifield->leftadj);
2284 if(pico_usingcolor()){
2286 if(str[0] == '+' || str[0] == '-'){
2287 if(start == 0
2288 && VAR_IND_PLUS_FORE_COLOR
2289 && VAR_IND_PLUS_BACK_COLOR){
2290 ielem = ifield->ielem;
2291 ielem->freecolor = 1;
2292 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
2296 if(str[2] == '*'){
2297 if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
2298 if(start == 2)
2299 ielem = ifield->ielem;
2300 else
2301 ielem = ifield->ielem->next->next;
2303 ielem->freecolor = 1;
2304 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
2308 if(str[3] == 'N' || str[3] == 'n'){
2309 if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
2310 if(start == 2)
2311 ielem = ifield->ielem->next;
2312 else
2313 ielem = ifield->ielem->next->next->next;
2315 ielem->freecolor = 1;
2316 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
2319 else if(str[3] == 'R' || str[3] == 'r'){
2320 if(VAR_IND_REC_FORE_COLOR && VAR_IND_REC_BACK_COLOR){
2321 if(start == 2)
2322 ielem = ifield->ielem->next;
2323 else
2324 ielem = ifield->ielem->next->next->next;
2326 ielem->freecolor = 1;
2327 ielem->color = new_color_pair(VAR_IND_REC_FORE_COLOR, VAR_IND_REC_BACK_COLOR);
2330 else if(str[3] == 'U' || str[3] == 'u'){
2331 if(VAR_IND_UNS_FORE_COLOR && VAR_IND_UNS_BACK_COLOR){
2332 if(start == 2)
2333 ielem = ifield->ielem->next;
2334 else
2335 ielem = ifield->ielem->next->next->next;
2337 ielem->freecolor = 1;
2338 ielem->color = new_color_pair(VAR_IND_UNS_FORE_COLOR, VAR_IND_UNS_BACK_COLOR);
2342 if(str[4] == 'A' || str[4] == 'a'){
2343 if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
2344 if(start == 2)
2345 ielem = ifield->ielem->next->next;
2346 else
2347 ielem = ifield->ielem->next->next->next->next;
2349 ielem->freecolor = 1;
2350 ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
2354 if(str[5] == 'D' || str[5] == 'd'){
2355 if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
2356 if(start == 2)
2357 ielem = ifield->ielem->next->next->next;
2358 else
2359 ielem = ifield->ielem->next->next->next->next->next;
2361 ielem->freecolor = 1;
2362 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
2368 break;
2370 case iMessNo:
2372 * This is a special case. The message number is
2373 * generated on the fly in the painting routine.
2374 * But the data array is allocated here in case it
2375 * is useful for the paint routine.
2377 snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
2378 break;
2380 case iArrow:
2381 snprintf(str, sizeof(str), "%-*.*s", ifield->width, ifield->width, " ");
2382 if(VAR_IND_ARR_FORE_COLOR && VAR_IND_ARR_BACK_COLOR){
2383 ifield->leftadj = 1;
2384 ielem = new_ielem(&ifield->ielem);
2385 ielem->freedata = 1;
2386 ielem->data = cpystr(str);
2387 ielem->datalen = strlen(str);
2388 set_print_format(ielem, ifield->width, ifield->leftadj);
2389 ielem->freecolor = 1;
2390 ielem->color = new_color_pair(VAR_IND_ARR_FORE_COLOR,
2391 VAR_IND_ARR_BACK_COLOR);
2394 break;
2396 case iScore:
2397 score = get_msg_score(idata->stream, idata->rawno);
2398 if(score == SCORE_UNDEF){
2399 SEARCHSET *ss = NULL;
2401 ss = mail_newsearchset();
2402 ss->first = ss->last = (unsigned long) idata->rawno;
2403 if(ss){
2405 * This looks like it might be expensive to get the
2406 * score for each message when needed but it shouldn't
2407 * be too bad because we know we have the envelope
2408 * data cached. We can't calculate all of the scores
2409 * we need for the visible messages right here in
2410 * one fell swoop because we don't have the other
2411 * envelopes yet. And we can't get the other
2412 * envelopes at this point because we may be in
2413 * the middle of a c-client callback (pine_imap_env).
2414 * (Actually we could, because we know whether or
2415 * not we're in the callback because of the no_fetch
2416 * parameter.)
2417 * We have another problem if the score rules depend
2418 * on something other than envelope data. I guess they
2419 * only do that if they have an alltext (search the
2420 * text of the message) definition. So, we're going
2421 * to pass no_fetch to calculate_scores so that it
2422 * can return an error if we need the text data but
2423 * can't get it because of no_fetch. Setting bogus
2424 * will cause us to do the scores calculation later
2425 * when we are no longer in the callback.
2427 idata->bogus =
2428 (calculate_some_scores(idata->stream,
2429 ss, idata->no_fetch) == 0)
2430 ? 1 : 0;
2431 score = get_msg_score(idata->stream, idata->rawno);
2432 mail_free_searchset(&ss);
2436 snprintf(str, sizeof(str), "%ld", score != SCORE_UNDEF ? score : 0L);
2437 break;
2439 case iDate: case iMonAbb: case iLDate:
2440 case iSDate: case iSTime:
2441 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
2442 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
2443 case iSDateIsoS: case iSDateIso:
2444 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
2445 case iSDateTime:
2446 case iSDateTimeIsoS: case iSDateTimeIso:
2447 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
2448 case iSDateTime24:
2449 case iSDateTimeIsoS24: case iSDateTimeIso24:
2450 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
2451 case iTimezone: case iYear: case iYear2Digit:
2452 case iRDate: case iDay: case iDay2Digit: case iMon2Digit:
2453 case iDayOrdinal: case iMon: case iMonLong:
2454 case iDayOfWeekAbb: case iDayOfWeek:
2455 case iPrefDate: case iPrefTime: case iPrefDateTime:
2456 date_str(fetch_date(idata), cdesc->ctype, 0, str, sizeof(str), cdesc->monabb_width);
2457 break;
2459 case iFromTo:
2460 case iFromToNotNews:
2461 case iFrom:
2462 case iAddress:
2463 case iMailbox:
2464 fromfield++;
2465 from_str(cdesc->ctype, idata, str, sizeof(str), ice);
2466 break;
2468 case iTo:
2469 if(((field = ((addr = fetch_to(idata))
2470 ? "To"
2471 : (addr = fetch_cc(idata))
2472 ? "Cc"
2473 : NULL))
2474 && !set_index_addr(idata, field, addr, NULL, BIGWIDTH, str))
2475 || !field)
2476 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2477 snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, newsgroups);
2479 break;
2481 case iCc:
2482 set_index_addr(idata, "Cc", fetch_cc(idata), NULL, BIGWIDTH, str);
2483 break;
2485 case iRecips:
2486 toaddr = fetch_to(idata);
2487 ccaddr = fetch_cc(idata);
2488 for(last_to = toaddr;
2489 last_to && last_to->next;
2490 last_to = last_to->next)
2493 /* point end of to list temporarily at cc list */
2494 if(last_to)
2495 last_to->next = ccaddr;
2497 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2499 if(last_to)
2500 last_to->next = NULL;
2502 break;
2504 case iSender:
2505 fromfield++;
2506 if((addr = fetch_sender(idata)) != NULL)
2507 set_index_addr(idata, "Sender", addr, NULL, BIGWIDTH, str);
2509 break;
2511 case iInit:
2512 {ADDRESS *addr;
2514 if((addr = fetch_from(idata)) && addr->personal){
2515 char *name, *initials = NULL;
2517 name = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
2518 SIZEOF_20KBUF, addr->personal);
2519 if(name == addr->personal){
2520 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
2521 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
2522 name = (char *) tmp_20k_buf;
2525 if(name && *name){
2526 initials = reply_quote_initials(name);
2527 snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, initials);
2532 break;
2534 case iSize:
2535 /* 0 ... 9999 */
2536 if((l = fetch_size(idata)) < 10*1000L)
2537 snprintf(str, sizeof(str), "(%lu)", l);
2538 /* 10K ... 999K */
2539 else if(l < 1000L*1000L - 1000L/2){
2540 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2541 snprintf(str, sizeof(str), "(%luK)", l);
2543 /* 1.0M ... 99.9M */
2544 else if(l < 1000L*100L*1000L - 100L*1000L/2){
2545 l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
2546 ? 1L : 0L);
2547 snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
2549 /* 100M ... 2000M */
2550 else if(l <= 2*1000L*1000L*1000L){
2551 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2552 ? 1L : 0L);
2553 snprintf(str, sizeof(str), "(%luM)", l);
2555 else
2556 snprintf(str, sizeof(str), "(HUGE!)");
2558 break;
2560 case iSizeComma:
2561 /* 0 ... 99,999 */
2562 if((l = fetch_size(idata)) < 100*1000L)
2563 snprintf(str, sizeof(str), "(%s)", comatose(l));
2564 /* 100K ... 9,999K */
2565 else if(l < 10L*1000L*1000L - 1000L/2){
2566 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2567 snprintf(str, sizeof(str), "(%sK)", comatose(l));
2569 /* 10.0M ... 999.9M */
2570 else if(l < 1000L*1000L*1000L - 100L*1000L/2){
2571 l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
2572 ? 1L : 0L);
2573 snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
2575 /* 1,000M ... 2,000M */
2576 else if(l <= 2*1000L*1000L*1000L){
2577 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2578 ? 1L : 0L);
2579 snprintf(str, sizeof(str), "(%sM)", comatose(l));
2581 else
2582 snprintf(str, sizeof(str), "(HUGE!)");
2584 break;
2586 case iSizeNarrow:
2587 /* 0 ... 999 */
2588 if((l = fetch_size(idata)) < 1000L)
2589 snprintf(str, sizeof(str), "(%lu)", l);
2590 /* 1K ... 99K */
2591 else if(l < 100L*1000L - 1000L/2){
2592 l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
2593 snprintf(str, sizeof(str), "(%luK)", l);
2595 /* .1M ... .9M */
2596 else if(l < 1000L*1000L - 100L*1000L/2){
2597 l = l/(100L*1000L) + (l%(100L*1000L) >= 100L*1000L/2
2598 ? 1L : 0L);
2599 snprintf(str, sizeof(str), "(.%luM)", l);
2601 /* 1M ... 99M */
2602 else if(l < 1000L*100L*1000L - 1000L*1000L/2){
2603 l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
2604 ? 1L : 0L);
2605 snprintf(str, sizeof(str), "(%luM)", l);
2607 /* .1G ... .9G */
2608 else if(l < 1000L*1000L*1000L - 100L*1000L*1000L/2){
2609 l = l/(100L*1000L*1000L) + (l%(100L*1000L*1000L) >=
2610 (100L*1000L*1000L/2) ? 1L : 0L);
2611 snprintf(str, sizeof(str), "(.%luG)", l);
2613 /* 1G ... 2G */
2614 else if(l <= 2*1000L*1000L*1000L){
2615 l = l/(1000L*1000L*1000L) + (l%(1000L*1000L*1000L) >=
2616 (1000L*1000L*1000L/2) ? 1L : 0L);
2617 snprintf(str, sizeof(str), "(%luG)", l);
2619 else
2620 snprintf(str, sizeof(str), "(HUGE!)");
2622 break;
2624 /* From Carl Jacobsen <carl@ucsd.edu> */
2625 case iKSize:
2626 l = fetch_size(idata);
2627 l = (l / 1024L) + (l % 1024L != 0 ? 1 : 0);
2629 if(l < 1024L) { /* 0k .. 1023k */
2630 snprintf(str, sizeof(str), "(%luk)", l);
2632 } else if (l < 100L * 1024L){ /* 1.0M .. 99.9M */
2633 snprintf(str, sizeof(str), "(%lu.M)", (l * 10L) / 1024L);
2634 if ((p = strchr(str, '.')) != NULL) {
2635 p--; p[1] = p[0]; p[0] = '.'; /* swap last digit & . */
2637 } else if (l <= 2L * 1024L * 1024L) { /* 100M .. 2048 */
2638 snprintf(str, sizeof(str), "(%luM)", l / 1024L);
2639 } else {
2640 snprintf(str, sizeof(str), "(HUGE!)");
2643 break;
2645 case iDescripSize:
2646 if((body = fetch_body(idata)) != NULL)
2647 switch(body->type){
2648 case TYPETEXT:
2650 mc = (idata->rawno > 0L && idata->stream
2651 && idata->rawno <= idata->stream->nmsgs)
2652 ? mail_elt(idata->stream, idata->rawno) : NULL;
2653 if(mc && mc->rfc822_size < 6000)
2654 snprintf(str, sizeof(str), "(short )");
2655 else if(mc && mc->rfc822_size < 25000)
2656 snprintf(str, sizeof(str), "(medium )");
2657 else if(mc && mc->rfc822_size < 100000)
2658 snprintf(str, sizeof(str), "(long )");
2659 else
2660 snprintf(str, sizeof(str), "(huge )");
2663 break;
2665 case TYPEMULTIPART:
2666 if(strucmp(body->subtype, "MIXED") == 0){
2667 int x;
2669 x = body->nested.part
2670 ? body->nested.part->body.type
2671 : TYPETEXT + 1000;
2672 switch(x){
2673 case TYPETEXT:
2674 if(body->nested.part->body.size.bytes < 6000)
2675 snprintf(str, sizeof(str), "(short+ )");
2676 else if(body->nested.part->body.size.bytes
2677 < 25000)
2678 snprintf(str, sizeof(str), "(medium+)");
2679 else if(body->nested.part->body.size.bytes
2680 < 100000)
2681 snprintf(str, sizeof(str), "(long+ )");
2682 else
2683 snprintf(str, sizeof(str), "(huge+ )");
2684 break;
2686 default:
2687 snprintf(str, sizeof(str), "(multi )");
2688 break;
2691 else if(strucmp(body->subtype, "DIGEST") == 0)
2692 snprintf(str, sizeof(str), "(digest )");
2693 else if(strucmp(body->subtype, "ALTERNATIVE") == 0)
2694 snprintf(str, sizeof(str), "(mul/alt)");
2695 else if(strucmp(body->subtype, "PARALLEL") == 0)
2696 snprintf(str, sizeof(str), "(mul/par)");
2697 else
2698 snprintf(str, sizeof(str), "(multi )");
2700 break;
2702 case TYPEMESSAGE:
2703 snprintf(str, sizeof(str), "(message)");
2704 break;
2706 case TYPEAPPLICATION:
2707 snprintf(str, sizeof(str), "(applica)");
2708 break;
2710 case TYPEAUDIO:
2711 snprintf(str, sizeof(str), "(audio )");
2712 break;
2714 case TYPEIMAGE:
2715 snprintf(str, sizeof(str), "(image )");
2716 break;
2718 case TYPEVIDEO:
2719 snprintf(str, sizeof(str), "(video )");
2720 break;
2722 default:
2723 snprintf(str, sizeof(str), "(other )");
2724 break;
2727 break;
2729 case iAtt:
2730 str[0] = SPACE;
2731 str[1] = '\0';
2732 if((body = fetch_body(idata)) &&
2733 body->type == TYPEMULTIPART &&
2734 strucmp(body->subtype, "ALTERNATIVE") != 0){
2735 PART *part;
2736 int atts = 0;
2738 part = body->nested.part; /* 1st part, don't count */
2739 while(part && part->next && atts < 10){
2740 atts++;
2741 part = part->next;
2744 if(atts > 9)
2745 str[0] = '*';
2746 else if(atts > 0)
2747 str[0] = '0' + atts;
2750 break;
2752 case iSubject:
2753 subj_str(idata, str, sizeof(str), NoKW, 0, ice);
2754 break;
2756 case iSubjectText:
2757 subj_str(idata, str, sizeof(str), NoKW, 1, ice);
2758 break;
2760 case iSubjKey:
2761 subj_str(idata, str, sizeof(str), KW, 0, ice);
2762 break;
2764 case iSubjKeyText:
2765 subj_str(idata, str, sizeof(str), KW, 1, ice);
2766 break;
2768 case iSubjKeyInit:
2769 subj_str(idata, str, sizeof(str), KWInit, 0, ice);
2770 break;
2772 case iSubjKeyInitText:
2773 subj_str(idata, str, sizeof(str), KWInit, 1, ice);
2774 break;
2776 case iOpeningText:
2777 case iOpeningTextNQ:
2778 if(idata->no_fetch)
2779 idata->bogus = 1;
2780 else{
2781 char *first_text;
2783 first_text = fetch_firsttext(idata, cdesc->ctype == iOpeningTextNQ);
2785 if(first_text){
2786 strncpy(str, first_text, BIGWIDTH);
2787 str[BIGWIDTH] = '\0';
2788 fs_give((void **) &first_text);
2792 break;
2794 case iKey:
2795 key_str(idata, KW, ice);
2796 break;
2798 case iKeyInit:
2799 key_str(idata, KWInit, ice);
2800 break;
2802 case iNews:
2803 if((newsgroups = fetch_newsgroups(idata)) != NULL){
2804 strncpy(str, newsgroups, BIGWIDTH);
2805 str[BIGWIDTH] = '\0';
2808 break;
2810 case iNewsAndTo:
2811 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2812 strncpy(str, newsgroups, sizeof(str));
2814 if((l = strlen(str)) < sizeof(str)){
2815 if(sizeof(str) - l < 6)
2816 strncpy(str+l, "...", sizeof(str)-l);
2817 else{
2818 if(l > 0){
2819 strncpy(str+l, " and ", sizeof(str)-l);
2820 set_index_addr(idata, "To", fetch_to(idata),
2821 NULL, BIGWIDTH-l-5, str+l+5);
2822 if(!str[l+5])
2823 str[l] = '\0';
2825 else
2826 set_index_addr(idata, "To", fetch_to(idata),
2827 NULL, BIGWIDTH, str);
2831 break;
2833 case iToAndNews:
2834 set_index_addr(idata, "To", fetch_to(idata),
2835 NULL, BIGWIDTH, str);
2836 if((l = strlen(str)) < sizeof(str) &&
2837 (newsgroups = fetch_newsgroups(idata))){
2838 if(sizeof(str) - l < 6)
2839 strncpy(str+l, "...", sizeof(str)-l);
2840 else{
2841 if(l > 0)
2842 strncpy(str+l, " and ", sizeof(str)-l);
2844 if(l > 0)
2845 strncpy(str+l+5, newsgroups, BIGWIDTH-l-5);
2846 else
2847 strncpy(str, newsgroups, BIGWIDTH);
2851 break;
2853 case iNewsAndRecips:
2854 if((newsgroups = fetch_newsgroups(idata)) != NULL)
2855 strncpy(str, newsgroups, BIGWIDTH);
2857 if((l = strlen(str)) < BIGWIDTH){
2858 if(BIGWIDTH - l < 6)
2859 strncpy(str+l, "...", BIGWIDTH-l);
2860 else{
2861 toaddr = fetch_to(idata);
2862 ccaddr = fetch_cc(idata);
2863 for(last_to = toaddr;
2864 last_to && last_to->next;
2865 last_to = last_to->next)
2868 /* point end of to list temporarily at cc list */
2869 if(last_to)
2870 last_to->next = ccaddr;
2872 if(l > 0){
2873 strncpy(str+l, " and ", sizeof(str)-l);
2874 set_index_addr(idata, "To", toaddr,
2875 NULL, BIGWIDTH-l-5, str+l+5);
2876 if(!str[l+5])
2877 str[l] = '\0';
2879 else
2880 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2882 if(last_to)
2883 last_to->next = NULL;
2887 break;
2889 case iRecipsAndNews:
2890 toaddr = fetch_to(idata);
2891 ccaddr = fetch_cc(idata);
2892 for(last_to = toaddr;
2893 last_to && last_to->next;
2894 last_to = last_to->next)
2897 /* point end of to list temporarily at cc list */
2898 if(last_to)
2899 last_to->next = ccaddr;
2901 set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
2903 if(last_to)
2904 last_to->next = NULL;
2906 if((l = strlen(str)) < BIGWIDTH &&
2907 (newsgroups = fetch_newsgroups(idata))){
2908 if(BIGWIDTH - l < 6)
2909 strncpy(str+l, "...", BIGWIDTH-l);
2910 else{
2911 if(l > 0)
2912 strncpy(str+l, " and ", sizeof(str)-l);
2914 if(l > 0)
2915 strncpy(str+l+5, newsgroups, BIGWIDTH-l-5);
2916 else
2917 strncpy(str, newsgroups, BIGWIDTH);
2921 break;
2923 case iPrio:
2924 case iPrioAlpha:
2925 case iPrioBang:
2926 prio_str(idata, cdesc->ctype, ice);
2927 break;
2929 case iHeader:
2930 header_str(idata, cdesc->hdrtok, ice);
2931 break;
2933 case iText:
2934 strncpy(str, (cdesc->hdrtok && cdesc->hdrtok->hdrname) ? cdesc->hdrtok->hdrname : "", sizeof(str));
2935 str[sizeof(str)-1] = '\0';
2936 break;
2938 default:
2939 break;
2943 * If the element wasn't already filled in above, do it here.
2945 if(!ifield->ielem){
2946 ielem = new_ielem(&ifield->ielem);
2948 if(color = hdr_color(itokens[itokensinv[cdesc->ctype].ctype].name, NULL, ps_global->index_token_colors)){
2949 if(pico_usingcolor()){
2950 ielem->color = new_color_pair(color->fg, color->bg);
2951 ielem->type = eTypeCol;
2953 free_color_pair(&color);
2956 ielem->freedata = 1;
2957 ielem->data = cpystr(str);
2958 ielem->datalen = strlen(str);
2960 if(fromfield && pico_usingcolor()
2961 && ps_global->VAR_IND_FROM_FORE_COLOR
2962 && ps_global->VAR_IND_FROM_BACK_COLOR){
2963 ielem->type = eTypeCol;
2964 ielem->freecolor = 1;
2965 ielem->color = new_color_pair(ps_global->VAR_IND_FROM_FORE_COLOR,
2966 ps_global->VAR_IND_FROM_BACK_COLOR);
2968 * This space is here so that if the text does
2969 * not extend all the way to the end of the field then
2970 * we'll switch the color back and paint the rest of the
2971 * field in the Normal color or the index line color.
2973 ielem = new_ielem(&ielem);
2974 ielem->freedata = 1;
2975 ielem->data = cpystr(" ");
2976 ielem->datalen = 1;
2978 else if((cdesc->ctype == iOpeningText || cdesc->ctype == iOpeningTextNQ)
2979 && pico_usingcolor()
2980 && ps_global->VAR_IND_OP_FORE_COLOR
2981 && ps_global->VAR_IND_OP_BACK_COLOR){
2982 ielem->type = eTypeCol;
2983 ielem->freecolor = 1;
2984 ielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR,
2985 ps_global->VAR_IND_OP_BACK_COLOR);
2987 * This space is here so that if the text does
2988 * not extend all the way to the end of the field then
2989 * we'll switch the color back and paint the rest of the
2990 * field in the Normal color or the index line color.
2992 ielem = new_ielem(&ielem);
2993 ielem->freedata = 1;
2994 ielem->data = cpystr(" ");
2995 ielem->datalen = 1;
2998 ifield->leftadj = (cdesc->adjustment == Left) ? 1 : 0;
2999 set_ielem_widths_in_field(ifield);
3003 ice->widths_done = 1;
3004 ice->id = ice_hash(ice);
3007 * Now we have to put the temporary copy of ice back as the
3008 * real thing.
3010 icep = fetch_ice_ptr(idata->stream, idata->rawno);
3011 if(icep){
3012 free_ice(icep); /* free what is already there */
3013 *icep = ice;
3016 return(ice);
3020 ICE_S *
3021 format_thread_index_line(INDEXDATA_S *idata)
3023 char *p, buffer[BIGWIDTH+1];
3024 int thdlen, space_left, i;
3025 PINETHRD_S *thrd = NULL;
3026 ICE_S *ice, *tice = NULL, **ticep = NULL;
3027 IFIELD_S *ifield;
3028 IELEM_S *ielem;
3029 int (*save_sfstr_func)(void);
3030 struct variable *vars = ps_global->vars;
3032 dprint((8, "=== format_thread_index_line(%ld,%ld) ===\n",
3033 idata ? idata->msgno : -1, idata ? idata->rawno : -1));
3035 space_left = ps_global->ttyo->screen_cols;
3037 if(ps_global->msgmap->max_thrdno < 1000)
3038 thdlen = 3;
3039 else if(ps_global->msgmap->max_thrdno < 10000)
3040 thdlen = 4;
3041 else if(ps_global->msgmap->max_thrdno < 100000)
3042 thdlen = 5;
3043 else
3044 thdlen = 6;
3046 ice = fetch_ice(idata->stream, idata->rawno);
3048 thrd = fetch_thread(idata->stream, idata->rawno);
3050 if(!thrd || !ice) /* can't happen? */
3051 return(ice);
3053 if(!ice->tice){
3054 tice = (ICE_S *) fs_get(sizeof(*tice));
3055 memset(tice, 0, sizeof(*tice));
3056 ice->tice = tice;
3059 tice = ice->tice;
3061 if(!tice)
3062 return(ice);
3064 free_ifield(&tice->ifield);
3066 ticep = &ice->tice;
3067 tice = copy_ice(tice);
3069 if(space_left >= 3){
3070 char to_us, status;
3072 p = buffer;
3073 to_us = to_us_symbol_for_thread(idata->stream, thrd, 1);
3074 status = status_symbol_for_thread(idata->stream, thrd, iStatus);
3076 if((p-buffer)+3 < sizeof(buffer)){
3077 p[0] = to_us;
3078 p[1] = ' ';
3079 p[2] = status;
3080 p[3] = '\0';;
3083 space_left -= 3;
3085 ifield = new_ifield(&tice->ifield);
3086 ifield->ctype = iStatus;
3087 ifield->width = 3;
3088 ifield->leftadj = 1;
3089 for(i = 0; i < 3; i++){
3090 ielem = new_ielem(&ifield->ielem);
3091 ielem->freedata = 1;
3092 ielem->data = (char *) fs_get(2 * sizeof(char));
3093 ielem->data[0] = p[i];
3094 ielem->data[1] = '\0';
3095 ielem->datalen = 1;
3096 set_print_format(ielem, 1, ifield->leftadj);
3099 if(pico_usingcolor()){
3100 if(to_us == '*'
3101 && VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
3102 ielem = ifield->ielem;
3103 ielem->freecolor = 1;
3104 ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR,
3105 VAR_IND_IMP_BACK_COLOR);
3106 if(F_ON(F_COLOR_LINE_IMPORTANT, ps_global))
3107 tice->linecolor = new_color_pair(VAR_IND_IMP_FORE_COLOR,
3108 VAR_IND_IMP_BACK_COLOR);
3110 else if((to_us == '+' || to_us == '-')
3111 && VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
3112 ielem = ifield->ielem;
3113 ielem->freecolor = 1;
3114 ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR,
3115 VAR_IND_PLUS_BACK_COLOR);
3118 if(status == 'D'
3119 && VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
3120 ielem = ifield->ielem->next->next;
3121 ielem->freecolor = 1;
3122 ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR,
3123 VAR_IND_DEL_BACK_COLOR);
3125 else if(status == 'N'
3126 && VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
3127 ielem = ifield->ielem->next->next;
3128 ielem->freecolor = 1;
3129 ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR,
3130 VAR_IND_NEW_BACK_COLOR);
3135 if(space_left >= thdlen+1){
3136 p = buffer;
3137 space_left--;
3139 snprintf(p, sizeof(buffer), "%*.*s", thdlen, thdlen, "");
3140 space_left -= thdlen;
3142 ifield = new_ifield(&tice->ifield);
3143 ifield->ctype = iMessNo;
3144 ifield->width = thdlen;
3145 ifield->leftadj = 0;
3146 ielem = new_ielem(&ifield->ielem);
3147 ielem->freedata = 1;
3148 ielem->data = cpystr(p);
3149 ielem->datalen = strlen(p);
3150 set_print_format(ielem, ifield->width, ifield->leftadj);
3153 if(space_left >= 7){
3155 p = buffer;
3156 space_left--;
3158 date_str(fetch_date(idata), iDate, 0, p, sizeof(buffer), 0);
3159 if(sizeof(buffer) > 6)
3160 p[6] = '\0';
3162 if(strlen(p) < 6 && (sizeof(buffer)) > 6){
3163 char *q;
3165 for(q = p + strlen(p); q < p + 6; q++)
3166 *q = ' ';
3169 space_left -= 6;
3171 ifield = new_ifield(&tice->ifield);
3172 ifield->ctype = iDate;
3173 ifield->width = 6;
3174 ifield->leftadj = 1;
3175 ielem = new_ielem(&ifield->ielem);
3176 ielem->freedata = 1;
3177 ielem->data = cpystr(p);
3178 ielem->datalen = ifield->width;
3179 set_print_format(ielem, ifield->width, ifield->leftadj);
3183 if(space_left > 3){
3184 int from_width, subj_width, bigthread_adjust;
3185 long in_thread;
3186 char from[BIGWIDTH+1];
3187 char tcnt[50];
3189 space_left--;
3191 in_thread = count_lflags_in_thread(idata->stream, thrd,
3192 ps_global->msgmap, MN_NONE);
3194 p = buffer;
3195 if(in_thread == 1 && THRD_AUTO_VIEW())
3196 snprintf(tcnt, sizeof(tcnt), " ");
3197 else
3198 snprintf(tcnt, sizeof(tcnt), "(%ld)", in_thread);
3200 bigthread_adjust = MAX(0, strlen(tcnt) - 3);
3202 /* third of the rest */
3203 from_width = MAX((space_left-1)/3 - bigthread_adjust, 1);
3205 /* the rest */
3206 subj_width = space_left - from_width - 1;
3208 if(strlen(tcnt) > subj_width)
3209 tcnt[subj_width] = '\0';
3211 from[0] = '\0';
3212 save_sfstr_func = pith_opt_truncate_sfstr;
3213 pith_opt_truncate_sfstr = NULL;
3214 from_str(iFromTo, idata, from, sizeof(from), tice);
3215 pith_opt_truncate_sfstr = save_sfstr_func;
3217 ifield = new_ifield(&tice->ifield);
3218 ifield->leftadj = 1;
3219 ielem = new_ielem(&ifield->ielem);
3220 ielem->freedata = 1;
3221 ielem->type = eTypeCol;
3222 ielem->data = cpystr(from);
3223 ielem->datalen = strlen(from);
3224 ifield->width = from_width;
3225 set_print_format(ielem, ifield->width, ifield->leftadj);
3226 ifield->ctype = iFrom;
3227 if(from_width > 0 && pico_usingcolor()
3228 && VAR_IND_FROM_FORE_COLOR && VAR_IND_FROM_BACK_COLOR){
3229 ielem->freecolor = 1;
3230 ielem->color = new_color_pair(VAR_IND_FROM_FORE_COLOR,
3231 VAR_IND_FROM_BACK_COLOR);
3234 ifield = new_ifield(&tice->ifield);
3235 ifield->leftadj = 0;
3236 ielem = new_ielem(&ifield->ielem);
3237 ielem->freedata = 1;
3238 ielem->data = cpystr(tcnt);
3239 ielem->datalen = strlen(tcnt);
3240 ifield->width = ielem->datalen;
3241 set_print_format(ielem, ifield->width, ifield->leftadj);
3242 ifield->ctype = iAtt; /* not used, except that it isn't special */
3244 subj_width -= strlen(tcnt);
3246 if(subj_width > 0)
3247 subj_width--;
3249 if(subj_width > 0){
3250 if(idata->bogus){
3251 if(idata->bogus < 2)
3252 snprintf(buffer, sizeof(buffer), "%-.*s", BIGWIDTH,
3253 _("[ No Message Text Available ]"));
3255 else{
3256 buffer[0] = '\0';
3257 save_sfstr_func = pith_opt_truncate_sfstr;
3258 pith_opt_truncate_sfstr = NULL;
3259 subj_str(idata, buffer, sizeof(buffer), NoKW, 0, NULL);
3260 pith_opt_truncate_sfstr = save_sfstr_func;
3263 ifield = new_ifield(&tice->ifield);
3264 ifield->leftadj = 1;
3265 ielem = new_ielem(&ifield->ielem);
3266 ielem->freedata = 1;
3267 ielem->type = eTypeCol;
3268 ielem->data = cpystr(buffer);
3269 ielem->datalen = strlen(buffer);
3270 ifield->width = subj_width;
3271 set_print_format(ielem, ifield->width, ifield->leftadj);
3272 ifield->ctype = iSubject;
3273 if(pico_usingcolor() && VAR_IND_SUBJ_FORE_COLOR && VAR_IND_SUBJ_BACK_COLOR){
3274 ielem->freecolor = 1;
3275 ielem->color = new_color_pair(VAR_IND_SUBJ_FORE_COLOR,
3276 VAR_IND_SUBJ_BACK_COLOR);
3280 else if(space_left > 1){
3281 snprintf(p, sizeof(buffer)-(p-buffer), "%-.*s", space_left-1, " ");
3282 ifield = new_ifield(&tice->ifield);
3283 ifield->leftadj = 1;
3284 ielem = new_ielem(&ifield->ielem);
3285 ielem->freedata = 1;
3286 ielem->data = cpystr(p);
3287 ielem->datalen = strlen(p);
3288 ifield->width = space_left-1;
3289 set_print_format(ielem, ifield->width, ifield->leftadj);
3290 ifield->ctype = iSubject;
3293 tice->widths_done = 1;
3294 tice->id = ice_hash(tice);
3296 if(ticep){
3297 free_ice(ticep); /* free what is already there */
3298 *ticep = tice;
3301 return(ice);
3306 * Print the fields of ice in buf with a single space between fields.
3308 * Args buf -- place to put the line
3309 * ice -- the data for the line
3310 * msgno -- this is the msgno to be used, blanks if <= 0
3312 * Returns a pointer to buf.
3314 char *
3315 simple_index_line(char *buf, size_t buflen, ICE_S *ice, long int msgno)
3317 char *p;
3318 IFIELD_S *ifield, *previfield = NULL;
3319 IELEM_S *ielem;
3321 if(!buf)
3322 panic("NULL buf in simple_index_line()");
3324 if(buflen > 0)
3325 buf[0] = '\0';
3327 p = buf;
3329 if(ice){
3331 for(ifield = ice->ifield; ifield && p-buf < buflen; ifield = ifield->next){
3333 /* space between fields */
3334 if(ifield != ice->ifield && !(previfield && previfield->ctype == iText))
3335 *p++ = ' ';
3337 /* message number string is generated on the fly */
3338 if(ifield->ctype == iMessNo){
3339 ielem = ifield->ielem;
3340 if(ielem && ielem->datalen >= ifield->width){
3341 if(msgno > 0L)
3342 snprintf(ielem->data, ielem->datalen+1, "%*.ld", ifield->width, msgno);
3343 else
3344 snprintf(ielem->data, ielem->datalen+1, "%*.*s", ifield->width, ifield->width, "");
3348 for(ielem = ifield->ielem;
3349 ielem && ielem->print_format && p-buf < buflen;
3350 ielem = ielem->next){
3351 char *src;
3352 size_t bytes_added;
3354 src = ielem->data;
3355 bytes_added = utf8_pad_to_width(p, src,
3356 buflen-(p-buf) * sizeof(char),
3357 ielem->wid, ifield->leftadj);
3358 p += bytes_added;
3361 previfield = ifield;
3364 if(p-buf < buflen)
3365 *p = '\0';
3368 buf[buflen-1] = '\0';
3370 return(buf);
3375 * Look in current mail_stream for matches for messages in the searchset
3376 * which match a color rule pattern. Return the color.
3377 * The searched bit will be set for all of the messages which match the
3378 * first pattern which has a match.
3380 * Args stream -- the mail stream
3381 * searchset -- restrict attention to this set of messages
3382 * pstate -- The pattern state. On the first call it will be Null.
3383 * Null means start over with a new first_pattern.
3384 * After that it will be pointing to our local PAT_STATE
3385 * so that next_pattern goes to the next one after the
3386 * ones we've already checked.
3388 * Returns 0 if no match, 1 if a match.
3389 * The color that goes with the matched rule in returned_color.
3390 * It may be NULL, which indicates default.
3393 get_index_line_color(MAILSTREAM *stream, SEARCHSET *searchset,
3394 PAT_STATE **pstate, COLOR_PAIR **returned_color)
3396 PAT_S *pat = NULL;
3397 long rflags = ROLE_INCOL;
3398 COLOR_PAIR *color = NULL;
3399 int match = 0;
3400 static PAT_STATE localpstate;
3402 dprint((7, "get_index_line_color\n"));
3404 if(returned_color)
3405 *returned_color = NULL;
3407 if(*pstate)
3408 pat = next_pattern(*pstate);
3409 else{
3410 *pstate = &localpstate;
3411 if(!nonempty_patterns(rflags, *pstate))
3412 *pstate = NULL;
3414 if(*pstate)
3415 pat = first_pattern(*pstate);
3418 if(*pstate){
3420 /* Go through the possible roles one at a time until we get a match. */
3421 while(!match && pat){
3422 if(match_pattern(pat->patgrp, stream, searchset, NULL,
3423 get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
3424 if(!pat->action || pat->action->bogus)
3425 break;
3427 match++;
3428 if(pat->action && pat->action->incol)
3429 color = new_color_pair(pat->action->incol->fg,
3430 pat->action->incol->bg);
3432 else
3433 pat = next_pattern(*pstate);
3437 if(match && returned_color)
3438 *returned_color = color;
3440 return(match);
3448 index_in_overview(MAILSTREAM *stream)
3450 INDEX_COL_S *cdesc = NULL;
3452 if(!(stream->mailbox && IS_REMOTE(stream->mailbox)))
3453 return(FALSE); /* no point! */
3455 if(stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "nntp")){
3457 if(THRD_INDX())
3458 return(TRUE);
3460 for(cdesc = ps_global->index_disp_format;
3461 cdesc->ctype != iNothing;
3462 cdesc++)
3463 switch(cdesc->ctype){
3464 case iTo: /* can't be satisfied by XOVER */
3465 case iSender: /* ... or specifically handled */
3466 case iDescripSize: /* ... in news case */
3467 case iAtt:
3468 return(FALSE);
3470 default :
3471 break;
3475 return(TRUE);
3481 * fetch_from - called to get a the index entry's "From:" field
3484 resent_to_us(INDEXDATA_S *idata)
3486 if(!idata->valid_resent_to){
3487 static char *fields[] = {"Resent-To", NULL};
3488 char *h;
3490 if(idata->no_fetch){
3491 idata->bogus = 1; /* don't do this */
3492 return(FALSE);
3495 if((h = pine_fetchheader_lines(idata->stream,idata->rawno,NULL,fields)) != NULL){
3496 idata->resent_to_us = parsed_resent_to_us(h);
3497 fs_give((void **) &h);
3500 idata->valid_resent_to = 1;
3503 return(idata->resent_to_us);
3508 parsed_resent_to_us(char *h)
3510 char *p, *q;
3511 ADDRESS *addr = NULL;
3512 int rv = FALSE;
3514 if((p = strindex(h, ':')) != NULL){
3515 for(q = ++p; (q = strpbrk(q, "\015\012")) != NULL; q++)
3516 *q = ' '; /* quash junk */
3518 rfc822_parse_adrlist(&addr, p, ps_global->maildomain);
3519 if(addr){
3520 rv = address_is_us(addr, ps_global);
3521 mail_free_address(&addr);
3525 return(rv);
3531 * fetch_from - called to get a the index entry's "From:" field
3533 ADDRESS *
3534 fetch_from(INDEXDATA_S *idata)
3536 if(idata->no_fetch) /* implies from is valid */
3537 return(idata->from);
3538 else if(idata->bogus)
3539 idata->bogus = 2;
3540 else{
3541 ENVELOPE *env;
3543 /* c-client call's just cache access at this point */
3544 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3545 return(env->from);
3547 idata->bogus = 1;
3550 return(NULL);
3555 * fetch_to - called to get a the index entry's "To:" field
3557 ADDRESS *
3558 fetch_to(INDEXDATA_S *idata)
3560 if(idata->no_fetch){ /* check for specific validity */
3561 if(idata->valid_to)
3562 return(idata->to);
3563 else
3564 idata->bogus = 1; /* can't give 'em what they want */
3566 else if(idata->bogus){
3567 idata->bogus = 2; /* elevate bogosity */
3569 else{
3570 ENVELOPE *env;
3572 /* c-client call's just cache access at this point */
3573 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3574 return(env->to);
3576 idata->bogus = 1;
3579 return(NULL);
3584 * fetch_cc - called to get a the index entry's "Cc:" field
3586 ADDRESS *
3587 fetch_cc(INDEXDATA_S *idata)
3589 if(idata->no_fetch){ /* check for specific validity */
3590 if(idata->valid_cc)
3591 return(idata->cc);
3592 else
3593 idata->bogus = 1; /* can't give 'em what they want */
3595 else if(idata->bogus){
3596 idata->bogus = 2; /* elevate bogosity */
3598 else{
3599 ENVELOPE *env;
3601 /* c-client call's just cache access at this point */
3602 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3603 return(env->cc);
3605 idata->bogus = 1;
3608 return(NULL);
3614 * fetch_sender - called to get a the index entry's "Sender:" field
3616 ADDRESS *
3617 fetch_sender(INDEXDATA_S *idata)
3619 if(idata->no_fetch){ /* check for specific validity */
3620 if(idata->valid_sender)
3621 return(idata->sender);
3622 else
3623 idata->bogus = 1; /* can't give 'em what they want */
3625 else if(idata->bogus){
3626 idata->bogus = 2; /* elevate bogosity */
3628 else{
3629 ENVELOPE *env;
3631 /* c-client call's just cache access at this point */
3632 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3633 return(env->sender);
3635 idata->bogus = 1;
3638 return(NULL);
3643 * fetch_newsgroups - called to get a the index entry's "Newsgroups:" field
3645 char *
3646 fetch_newsgroups(INDEXDATA_S *idata)
3648 if(idata->no_fetch){ /* check for specific validity */
3649 if(idata->valid_news)
3650 return(idata->newsgroups);
3651 else
3652 idata->bogus = 1; /* can't give 'em what they want */
3654 else if(idata->bogus){
3655 idata->bogus = 2; /* elevate bogosity */
3657 else{
3658 ENVELOPE *env;
3660 /* c-client call's just cache access at this point */
3661 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3662 return(env->newsgroups);
3664 idata->bogus = 1;
3667 return(NULL);
3672 * fetch_subject - called to get at the index entry's "Subject:" field
3674 char *
3675 fetch_subject(INDEXDATA_S *idata)
3677 if(idata->no_fetch) /* implies subject is valid */
3678 return(idata->subject);
3679 else if(idata->bogus)
3680 idata->bogus = 2;
3681 else{
3682 ENVELOPE *env;
3684 /* c-client call's just cache access at this point */
3685 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3686 return(env->subject);
3688 idata->bogus = 1;
3691 return(NULL);
3696 * Return an allocated copy of the first few characters from the body
3697 * of the message for possible use in the index screen.
3699 * Maybe we could figure out some way to do aggregate calls to get
3700 * this info for all the lines in view instead of all the one at a
3701 * time calls we're doing now.
3703 char *
3704 fetch_firsttext(INDEXDATA_S *idata, int delete_quotes)
3706 ENVELOPE *env;
3707 BODY *body = NULL;
3708 char *firsttext = NULL;
3709 STORE_S *so;
3710 gf_io_t pc;
3711 long partial_fetch_len = 0L;
3712 SEARCHSET *ss, **sset;
3714 try_again:
3717 * Prevent wild prefetch, just get the one we're after.
3718 * Can we get this somehow in the overview call in build_header_work?
3720 ss = mail_newsearchset();
3721 ss->first = idata->rawno;
3722 sset = (SEARCHSET **) mail_parameters(idata->stream,
3723 GET_FETCHLOOKAHEAD,
3724 (void *) idata->stream);
3725 if(sset)
3726 *sset = ss;
3728 if((env = pine_mail_fetchstructure(idata->stream, idata->rawno, &body)) != NULL){
3729 if(body){
3730 char *subtype = NULL;
3731 char *partno;
3733 if((body->type == TYPETEXT
3734 && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
3736 (body->type == TYPEMULTIPART && body->nested.part
3737 && body->nested.part->body.type == TYPETEXT
3738 && (subtype=body->nested.part->body.subtype)
3739 && ALLOWED_SUBTYPE(subtype))
3741 (body->type == TYPEMULTIPART && body->nested.part
3742 && body->nested.part->body.type == TYPEMULTIPART
3743 && body->nested.part->body.nested.part
3744 && body->nested.part->body.nested.part->body.type == TYPETEXT
3745 && (subtype=body->nested.part->body.nested.part->body.subtype)
3746 && ALLOWED_SUBTYPE(subtype))){
3748 if((so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
3749 char buf[1025], *p;
3750 unsigned char c;
3751 int success;
3752 int one_space_done = 0;
3754 if(partial_fetch_len == 0L){
3755 if(subtype && !strucmp(subtype, "html"))
3756 partial_fetch_len = 1024L;
3757 else if(subtype && !strucmp(subtype, "plain"))
3758 partial_fetch_len = delete_quotes ? 128L : 64L;
3759 else
3760 partial_fetch_len = 256L;
3763 if((body->type == TYPETEXT
3764 && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
3766 (body->type == TYPEMULTIPART && body->nested.part
3767 && body->nested.part->body.type == TYPETEXT
3768 && (subtype=body->nested.part->body.subtype)
3769 && ALLOWED_SUBTYPE(subtype)))
3770 partno = "1";
3771 else
3772 partno = "1.1";
3774 gf_set_so_writec(&pc, so);
3775 success = get_body_part_text(idata->stream, body, idata->rawno,
3776 partno, partial_fetch_len, pc,
3777 NULL, NULL,
3778 GBPT_NOINTR | GBPT_PEEK |
3779 (delete_quotes ? GBPT_DELQUOTES : 0));
3780 gf_clear_so_writec(so);
3782 if(success){
3783 so_seek(so, 0L, 0);
3784 p = buf;
3785 while(p-buf < sizeof(buf)-1 && so_readc(&c, so)){
3786 /* delete leading whitespace */
3787 if(p == buf && isspace(c))
3789 /* and include just one space per run of whitespace */
3790 else if(isspace(c)){
3791 if(!one_space_done){
3792 *p++ = SPACE;
3793 one_space_done++;
3796 else{
3797 one_space_done = 0;
3798 *p++ = c;
3802 *p = '\0';
3804 if(p > buf){
3805 size_t l;
3807 l = strlen(buf);
3808 l += 100;
3809 firsttext = fs_get((l+1) * sizeof(char));
3810 firsttext[0] = '\0';
3811 iutf8ncpy(firsttext, buf, l);
3812 firsttext[l] = '\0';
3813 removing_trailing_white_space(firsttext);
3817 so_give(&so);
3819 /* first if means we didn't fetch all of the data */
3820 if(!(success > 1 && success < partial_fetch_len)){
3821 if(partial_fetch_len < 4096L
3822 && (!firsttext || utf8_width(firsttext) < 50)){
3823 if(firsttext)
3824 fs_give((void **) &firsttext);
3826 partial_fetch_len = 4096L;
3827 goto try_again;
3835 if(ss)
3836 mail_free_searchset(&ss);
3838 return(firsttext);
3843 * fetch_date - called to get at the index entry's "Date:" field
3845 char *
3846 fetch_date(INDEXDATA_S *idata)
3848 if(idata->no_fetch) /* implies date is valid */
3849 return(idata->date);
3850 else if(idata->bogus)
3851 idata->bogus = 2;
3852 else{
3853 ENVELOPE *env;
3855 /* c-client call's just cache access at this point */
3856 if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
3857 return((char *) env->date);
3859 idata->bogus = 1;
3862 return(NULL);
3867 * fetch_header - called to get at the index entry's "Hdrname:" field
3869 char *
3870 fetch_header(INDEXDATA_S *idata, char *hdrname)
3872 if(idata->no_fetch)
3873 idata->bogus = 1;
3874 else if(idata->bogus)
3875 idata->bogus = 2;
3876 else{
3877 char *h, *p, *q, *decoded, *fields[2];
3878 size_t retsize, decsize;
3879 char *ret = NULL;
3880 unsigned char *decode_buf = NULL;
3882 fields[0] = hdrname;
3883 fields[1] = NULL;
3884 if(hdrname && hdrname[0]
3885 && (h = pine_fetchheader_lines(idata->stream, idata->rawno,
3886 NULL, fields))){
3888 if(strlen(h) < strlen(hdrname) + 1){
3889 fs_give((void **) &h);
3890 return(cpystr(""));
3893 /* skip "hdrname:" */
3894 for(p = h + strlen(hdrname) + 1;
3895 *p && isspace((unsigned char)*p); p++)
3898 decsize = (4 * strlen(p)) + 1;
3899 decode_buf = (unsigned char *) fs_get(decsize * sizeof(unsigned char));
3900 decoded = (char *) rfc1522_decode_to_utf8(decode_buf, decsize, p);
3901 p = decoded;
3903 retsize = strlen(decoded);
3904 q = ret = (char *) fs_get((retsize+1) * sizeof(char));
3906 *q = '\0';
3907 while(q-ret < retsize && *p){
3908 if(*p == '\015' || *p == '\012')
3909 p++;
3910 else if(*p == '\t'){
3911 *q++ = SPACE;
3912 p++;
3914 else
3915 *q++ = *p++;
3918 *q = '\0';
3920 fs_give((void **) &h);
3921 if(decode_buf)
3922 fs_give((void **) &decode_buf);
3924 return(ret);
3927 idata->bogus = 1;
3930 return(NULL);
3935 * fetch_size - called to get at the index entry's "size" field
3937 long
3938 fetch_size(INDEXDATA_S *idata)
3940 if(idata->no_fetch) /* implies size is valid */
3941 return(idata->size);
3942 else if(idata->bogus)
3943 idata->bogus = 2;
3944 else{
3945 MESSAGECACHE *mc;
3947 if(idata->stream && idata->rawno > 0L
3948 && idata->rawno <= idata->stream->nmsgs
3949 && (mc = mail_elt(idata->stream, idata->rawno)))
3950 return(mc->rfc822_size);
3952 idata->bogus = 1;
3955 return(0L);
3960 * fetch_body - called to get a the index entry's body structure
3962 BODY *
3963 fetch_body(INDEXDATA_S *idata)
3965 BODY *body;
3967 if(idata->bogus || idata->no_fetch){
3968 idata->bogus = 2;
3969 return(NULL);
3972 if(pine_mail_fetchstructure(idata->stream, idata->rawno, &body))
3973 return(body);
3975 idata->bogus = 1;
3976 return(NULL);
3981 * s is at least size width+1
3984 set_index_addr(INDEXDATA_S *idata,
3985 char *field,
3986 struct mail_address *addr,
3987 char *prefix,
3988 int width,
3989 char *s)
3991 ADDRESS *atmp;
3992 char *p, *stmp = NULL, *sptr;
3993 char *save_personal = NULL;
3994 int orig_width;
3996 s[0] = '\0';
3998 for(atmp = addr; idata->stream && atmp; atmp = atmp->next)
3999 if(atmp->host && atmp->host[0] == '.'){
4000 char *pref, *h, *fields[2];
4002 if(idata->no_fetch){
4003 idata->bogus = 1;
4004 return(TRUE);
4007 fields[0] = field;
4008 fields[1] = NULL;
4009 if((h = pine_fetchheader_lines(idata->stream, idata->rawno,
4010 NULL, fields)) != NULL){
4011 if(strlen(h) < strlen(field) + 1){
4012 p = h + strlen(h);
4014 else{
4015 /* skip "field:" */
4016 for(p = h + strlen(field) + 1;
4017 *p && isspace((unsigned char)*p); p++)
4021 orig_width = width;
4022 sptr = stmp = (char *) fs_get((orig_width+1) * sizeof(char));
4024 /* add prefix */
4025 for(pref = prefix; pref && *pref; pref++)
4026 if(width){
4027 *sptr++ = *pref;
4028 width--;
4030 else
4031 break;
4033 while(width--)
4034 if(*p == '\015' || *p == '\012')
4035 p++; /* skip CR LF */
4036 else if(!*p)
4037 *sptr++ = ' ';
4038 else if(*p == '\t'){
4039 *sptr++ = ' ';
4040 p++;
4042 else
4043 *sptr++ = *p++;
4045 *sptr = '\0'; /* tie off return string */
4047 if(stmp){
4048 iutf8ncpy(s, stmp, orig_width+1);
4049 s[orig_width] = '\0';
4050 fs_give((void **) &stmp);
4053 fs_give((void **) &h);
4054 return(TRUE);
4056 /* else fall thru and display what c-client gave us */
4059 if(addr && !addr->next /* only one address */
4060 && addr->host /* not group syntax */
4061 && addr->personal && addr->personal[0]){ /* there is a personal name */
4062 char buftmp[MAILTMPLEN];
4063 int l;
4065 if((l = prefix ? strlen(prefix) : 0) != 0)
4066 strncpy(s, prefix, width+1);
4068 snprintf(buftmp, sizeof(buftmp), "%s", addr->personal);
4069 p = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
4070 SIZEOF_20KBUF, buftmp);
4071 removing_leading_and_trailing_white_space(p);
4073 iutf8ncpy(s + l, p, width - l);
4075 s[width] = '\0';
4077 if(*(s+l))
4078 return(TRUE);
4079 else{
4080 save_personal = addr->personal;
4081 addr->personal = NULL;
4085 if(addr){
4086 char *a_string;
4087 int l;
4089 a_string = addr_list_string(addr, NULL, 0);
4090 if(save_personal)
4091 addr->personal = save_personal;
4093 if((l = prefix ? strlen(prefix) : 0) != 0)
4094 strncpy(s, prefix, width+1);
4096 iutf8ncpy(s + l, a_string, width - l);
4097 s[width] = '\0';
4099 fs_give((void **)&a_string);
4101 return(TRUE);
4104 if(save_personal)
4105 addr->personal = save_personal;
4107 return(FALSE);
4111 void
4112 index_data_env(INDEXDATA_S *idata, ENVELOPE *env)
4114 if(!env){
4115 idata->bogus = 2;
4116 return;
4119 idata->from = env->from;
4120 idata->to = env->to;
4121 idata->cc = env->cc;
4122 idata->sender = env->sender;
4123 idata->subject = env->subject;
4124 idata->date = (char *) env->date;
4125 idata->newsgroups = env->newsgroups;
4127 idata->valid_to = 1; /* signal that everythings here */
4128 idata->valid_cc = 1;
4129 idata->valid_sender = 1;
4130 idata->valid_news = 1;
4135 * Put a string representing the date into str. The source date is
4136 * in the string datesrc. The format to be used is in type.
4137 * Notice that type is an IndexColType, but really only a subset of
4138 * IndexColType types are allowed.
4140 * Args datesrc -- The source date string
4141 * type -- What type of output we want
4142 * v -- If set, variable width output is ok. (Oct 9 not Oct 9)
4143 * str -- Put the answer here.
4144 * str_len -- Length of str
4145 * monabb_width -- This is a hack to get dates to line up right. For
4146 * example, in French (but without accents here)
4147 * dec. 21
4148 * fevr. 23
4149 * mars 7
4150 * For this monabb_width would be 5.
4152 void
4153 date_str(char *datesrc, IndexColType type, int v, char *str, size_t str_len,
4154 int monabb_width)
4156 char year4[5], /* 4 digit year */
4157 yearzero[3], /* zero padded, 2-digit year */
4158 monzero[3], /* zero padded, 2-digit month */
4159 mon[3], /* 1 or 2-digit month, no pad */
4160 dayzero[3], /* zero padded, 2-digit day */
4161 day[3], /* 1 or 2-digit day, no pad */
4162 dayord[3], /* 2-letter ordinal label */
4163 monabb[10], /* 3-letter month abbrev */
4164 /* actually maybe not 3 if localized */
4165 hour24[3], /* 2-digit, 24 hour clock hour */
4166 hour12[3], /* 12 hour clock hour, no pad */
4167 minzero[3], /* zero padded, 2-digit minutes */
4168 timezone[6]; /* timezone, like -0800 or +... */
4169 int hr12;
4170 int curtype, lastmonthtype, lastyeartype, preftype;
4171 int sdatetimetype, sdatetime24type;
4172 struct date d;
4173 #define TODAYSTR N_("Today")
4175 curtype = (type == iCurDate ||
4176 type == iCurDateIso ||
4177 type == iCurDateIsoS ||
4178 type == iCurPrefDate ||
4179 type == iCurPrefDateTime ||
4180 type == iCurPrefTime ||
4181 type == iCurTime24 ||
4182 type == iCurTime12 ||
4183 type == iCurDay ||
4184 type == iCurDay2Digit ||
4185 type == iCurDayOfWeek ||
4186 type == iCurDayOfWeekAbb ||
4187 type == iCurMon ||
4188 type == iCurMon2Digit ||
4189 type == iCurMonLong ||
4190 type == iCurMonAbb ||
4191 type == iCurYear ||
4192 type == iCurYear2Digit);
4193 lastmonthtype = (type == iLstMon ||
4194 type == iLstMon2Digit ||
4195 type == iLstMonLong ||
4196 type == iLstMonAbb ||
4197 type == iLstMonYear ||
4198 type == iLstMonYear2Digit);
4199 lastyeartype = (type == iLstYear ||
4200 type == iLstYear2Digit);
4201 sdatetimetype = (type == iSDateTime ||
4202 type == iSDateTimeIso ||
4203 type == iSDateTimeIsoS ||
4204 type == iSDateTimeS1 ||
4205 type == iSDateTimeS2 ||
4206 type == iSDateTimeS3 ||
4207 type == iSDateTimeS4 ||
4208 type == iSDateTime24 ||
4209 type == iSDateTimeIso24 ||
4210 type == iSDateTimeIsoS24 ||
4211 type == iSDateTimeS124 ||
4212 type == iSDateTimeS224 ||
4213 type == iSDateTimeS324 ||
4214 type == iSDateTimeS424);
4215 sdatetime24type = (type == iSDateTime24 ||
4216 type == iSDateTimeIso24 ||
4217 type == iSDateTimeIsoS24 ||
4218 type == iSDateTimeS124 ||
4219 type == iSDateTimeS224 ||
4220 type == iSDateTimeS324 ||
4221 type == iSDateTimeS424);
4222 preftype = (type == iPrefDate ||
4223 type == iPrefDateTime ||
4224 type == iPrefTime ||
4225 type == iCurPrefDate ||
4226 type == iCurPrefDateTime ||
4227 type == iCurPrefTime);
4228 if(str_len > 0)
4229 str[0] = '\0';
4231 if(!(datesrc && datesrc[0]) && !(curtype || lastmonthtype || lastyeartype))
4232 return;
4234 if(curtype || lastmonthtype || lastyeartype){
4235 char dbuf[200];
4237 rfc822_date(dbuf);
4238 parse_date(dbuf, &d);
4240 if(lastyeartype)
4241 d.year--;
4242 else if(lastmonthtype){
4243 d.month--;
4244 if(d.month <= 0){
4245 d.month = 12;
4246 d.year--;
4250 else{
4251 parse_date(F_ON(F_DATES_TO_LOCAL,ps_global)
4252 ? convert_date_to_local(datesrc) : datesrc, &d);
4253 if(d.year == -1 || d.month == -1 || d.day == -1){
4254 sdatetimetype = 0;
4255 sdatetime24type = 0;
4256 preftype = 0;
4257 switch(type){
4258 case iSDate: case iSDateTime: case iSDateTime24:
4259 type = iS1Date;
4260 break;
4262 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
4263 case iPrefDate: case iPrefTime: case iPrefDateTime:
4264 type = iDateIso;
4265 break;
4267 case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
4268 type = iDateIsoS;
4269 break;
4271 case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
4272 type = iS1Date;
4273 break;
4275 case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
4276 type = iS1Date;
4277 break;
4279 case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
4280 type = iS1Date;
4281 break;
4283 case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
4284 type = iS1Date;
4285 break;
4287 default:
4288 break;
4293 /* some special ones to start with */
4294 if(preftype){
4295 struct tm tm, *tmptr = NULL;
4296 time_t now;
4299 * Make sure we get the right one if we're using current time.
4301 if(curtype){
4302 now = time((time_t *) 0);
4303 if(now != (time_t) -1)
4304 tmptr = localtime(&now);
4307 if(!tmptr){
4308 memset(&tm, 0, sizeof(tm));
4309 tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
4310 tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
4311 tm.tm_mday = MIN(MAX(d.day, 1), 31);
4312 tm.tm_hour = MIN(MAX(d.hour, 0), 23);
4313 tm.tm_min = MIN(MAX(d.minute, 0), 59);
4314 tm.tm_wday = MIN(MAX(d.wkday, 0), 6);
4315 tmptr = &tm;
4318 switch(type){
4319 case iPrefDate:
4320 case iCurPrefDate:
4321 our_strftime(str, str_len, "%x", tmptr);
4322 break;
4323 case iPrefTime:
4324 case iCurPrefTime:
4325 our_strftime(str, str_len, "%X", tmptr);
4326 break;
4327 case iPrefDateTime:
4328 case iCurPrefDateTime:
4329 our_strftime(str, str_len, "%c", tmptr);
4330 break;
4331 default:
4332 assert(0);
4333 break;
4336 return;
4339 strncpy(monabb, (d.month > 0 && d.month < 13)
4340 ? month_abbrev_locale(d.month) : "", sizeof(monabb));
4341 monabb[sizeof(monabb)-1] = '\0';
4343 strncpy(mon, (d.month > 0 && d.month < 13)
4344 ? int2string(d.month) : "", sizeof(mon));
4345 mon[sizeof(mon)-1] = '\0';
4347 strncpy(day, (d.day > 0 && d.day < 32)
4348 ? int2string(d.day) : "", sizeof(day));
4349 day[sizeof(day)-1] = '\0';
4351 strncpy(dayord,
4352 (d.day <= 0 || d.day > 31) ? "" :
4353 (d.day == 1 || d.day == 21 || d.day == 31) ? "st" :
4354 (d.day == 2 || d.day == 22 ) ? "nd" :
4355 (d.day == 3 || d.day == 23 ) ? "rd" : "th", sizeof(dayord));
4357 dayord[sizeof(dayord)-1] = '\0';
4359 strncpy(year4, (d.year >= 1000 && d.year < 10000)
4360 ? int2string(d.year) : "????", sizeof(year4));
4361 year4[sizeof(year4)-1] = '\0';
4363 if(d.year >= 0){
4364 if((d.year % 100) < 10){
4365 yearzero[0] = '0';
4366 strncpy(yearzero+1, int2string(d.year % 100), sizeof(yearzero)-1);
4368 else
4369 strncpy(yearzero, int2string(d.year % 100), sizeof(yearzero));
4371 else
4372 strncpy(yearzero, "??", sizeof(yearzero));
4374 yearzero[sizeof(yearzero)-1] = '\0';
4376 if(d.month > 0 && d.month < 10){
4377 monzero[0] = '0';
4378 strncpy(monzero+1, int2string(d.month), sizeof(monzero)-1);
4380 else if(d.month >= 10 && d.month <= 12)
4381 strncpy(monzero, int2string(d.month), sizeof(monzero));
4382 else
4383 strncpy(monzero, "??", sizeof(monzero));
4385 monzero[sizeof(monzero)-1] = '\0';
4387 if(d.day > 0 && d.day < 10){
4388 dayzero[0] = '0';
4389 strncpy(dayzero+1, int2string(d.day), sizeof(dayzero)-1);
4391 else if(d.day >= 10 && d.day <= 31)
4392 strncpy(dayzero, int2string(d.day), sizeof(dayzero));
4393 else
4394 strncpy(dayzero, "??", sizeof(dayzero));
4396 dayzero[sizeof(dayzero)-1] = '\0';
4398 hr12 = (d.hour == 0) ? 12 :
4399 (d.hour > 12) ? (d.hour - 12) : d.hour;
4400 hour12[0] = '\0';
4401 if(hr12 > 0 && hr12 <= 12)
4402 strncpy(hour12, int2string(hr12), sizeof(hour12));
4404 hour12[sizeof(hour12)-1] = '\0';
4406 hour24[0] = '\0';
4407 if(d.hour >= 0 && d.hour < 10){
4408 hour24[0] = '0';
4409 strncpy(hour24+1, int2string(d.hour), sizeof(hour24)-1);
4411 else if(d.hour >= 10 && d.hour < 24)
4412 strncpy(hour24, int2string(d.hour), sizeof(hour24));
4414 hour24[sizeof(hour24)-1] = '\0';
4416 minzero[0] = '\0';
4417 if(d.minute >= 0 && d.minute < 10){
4418 minzero[0] = '0';
4419 strncpy(minzero+1, int2string(d.minute), sizeof(minzero)-1);
4421 else if(d.minute >= 10 && d.minute <= 60)
4422 strncpy(minzero, int2string(d.minute), sizeof(minzero));
4424 minzero[sizeof(minzero)-1] = '\0';
4426 if(sizeof(timezone) > 5){
4427 if(d.hours_off_gmt <= 0){
4428 timezone[0] = '-';
4429 d.hours_off_gmt *= -1;
4430 d.min_off_gmt *= -1;
4432 else
4433 timezone[0] = '+';
4435 timezone[1] = '\0';
4436 if(d.hours_off_gmt >= 0 && d.hours_off_gmt < 10){
4437 timezone[1] = '0';
4438 strncpy(timezone+2, int2string(d.hours_off_gmt), sizeof(timezone)-2);
4440 else if(d.hours_off_gmt >= 10 && d.hours_off_gmt < 24)
4441 strncpy(timezone+1, int2string(d.hours_off_gmt), sizeof(timezone)-1);
4442 else{
4443 timezone[1] = '0';
4444 timezone[2] = '0';
4447 timezone[3] = '\0';
4448 if(d.min_off_gmt >= 0 && d.min_off_gmt < 10){
4449 timezone[3] = '0';
4450 strncpy(timezone+4, int2string(d.min_off_gmt), sizeof(timezone)-4);
4452 else if(d.min_off_gmt >= 10 && d.min_off_gmt <= 60)
4453 strncpy(timezone+3, int2string(d.min_off_gmt), sizeof(timezone)-3);
4454 else{
4455 timezone[3] = '0';
4456 timezone[4] = '0';
4459 timezone[5] = '\0';
4460 timezone[sizeof(timezone)-1] = '\0';
4463 switch(type){
4464 case iRDate:
4465 /* this one is not locale-specific */
4466 snprintf(str, str_len, "%s%s%s %s %s",
4467 (d.wkday != -1) ? day_abbrev(d.wkday) : "",
4468 (d.wkday != -1) ? ", " : "",
4469 day,
4470 (d.month > 0 && d.month < 13) ? month_abbrev(d.month) : "",
4471 year4);
4472 break;
4473 case iDayOfWeekAbb:
4474 case iCurDayOfWeekAbb:
4475 strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_abbrev_locale(d.wkday) : "", str_len);
4476 str[str_len-1] = '\0';
4477 break;
4478 case iDayOfWeek:
4479 case iCurDayOfWeek:
4480 strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_name_locale(d.wkday) : "", str_len);
4481 str[str_len-1] = '\0';
4482 break;
4483 case iYear:
4484 case iCurYear:
4485 case iLstYear:
4486 case iLstMonYear:
4487 strncpy(str, year4, str_len);
4488 break;
4489 case iDay2Digit:
4490 case iCurDay2Digit:
4491 strncpy(str, dayzero, str_len);
4492 break;
4493 case iMon2Digit:
4494 case iCurMon2Digit:
4495 case iLstMon2Digit:
4496 strncpy(str, monzero, str_len);
4497 break;
4498 case iYear2Digit:
4499 case iCurYear2Digit:
4500 case iLstYear2Digit:
4501 case iLstMonYear2Digit:
4502 strncpy(str, yearzero, str_len);
4503 break;
4504 case iTimezone:
4505 strncpy(str, timezone, str_len);
4506 break;
4507 case iDay:
4508 case iCurDay:
4509 strncpy(str, day, str_len);
4510 break;
4511 case iDayOrdinal:
4512 snprintf(str, str_len, "%s%s", day, dayord);
4513 break;
4514 case iMon:
4515 case iCurMon:
4516 case iLstMon:
4517 if(d.month > 0 && d.month <= 12)
4518 strncpy(str, int2string(d.month), str_len);
4520 break;
4521 case iMonAbb:
4522 case iCurMonAbb:
4523 case iLstMonAbb:
4524 strncpy(str, monabb, str_len);
4525 break;
4526 case iMonLong:
4527 case iCurMonLong:
4528 case iLstMonLong:
4529 strncpy(str, (d.month > 0 && d.month < 13)
4530 ? month_name_locale(d.month) : "", str_len);
4531 break;
4532 case iDate:
4533 case iCurDate:
4534 if(v)
4535 snprintf(str, str_len, "%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day);
4536 else{
4537 if(monabb_width > 0)
4538 utf8_snprintf(str, str_len, "%-*.*w %2s",
4539 monabb_width, monabb_width, monabb, day);
4540 else
4541 snprintf(str, str_len, "%s %2s", monabb, day);
4544 break;
4545 case iLDate:
4546 if(v)
4547 snprintf(str, str_len, "%s%s%s%s%s", monabb,
4548 (monabb[0] && day[0]) ? " " : "", day,
4549 ((monabb[0] || day[0]) && year4[0]) ? ", " : "",
4550 year4);
4551 else{
4552 if(monabb_width > 0)
4553 utf8_snprintf(str, str_len, "%-*.*w %2s%c %4s",
4554 monabb_width, monabb_width,
4555 monabb, day,
4556 (monabb[0] && day[0] && year4[0]) ? ',' : ' ', year4);
4557 else
4558 snprintf(str, str_len, "%s %2s%c %4s", monabb, day,
4559 (monabb[0] && day[0] && year4[0]) ? ',' : ' ',
4560 year4);
4563 break;
4564 case iS1Date:
4565 case iS2Date:
4566 case iS3Date:
4567 case iS4Date:
4568 case iDateIso:
4569 case iDateIsoS:
4570 case iCurDateIso:
4571 case iCurDateIsoS:
4572 if(monzero[0] == '?' && dayzero[0] == '?' &&
4573 yearzero[0] == '?')
4574 snprintf(str, str_len, "%8s", "");
4575 else{
4576 switch(type){
4577 case iS1Date:
4578 snprintf(str, str_len, "%2s/%2s/%2s",
4579 monzero, dayzero, yearzero);
4580 break;
4581 case iS2Date:
4582 snprintf(str, str_len, "%2s/%2s/%2s",
4583 dayzero, monzero, yearzero);
4584 break;
4585 case iS3Date:
4586 snprintf(str, str_len, "%2s.%2s.%2s",
4587 dayzero, monzero, yearzero);
4588 break;
4589 case iS4Date:
4590 snprintf(str, str_len, "%2s.%2s.%2s",
4591 yearzero, monzero, dayzero);
4592 break;
4593 case iDateIsoS:
4594 case iCurDateIsoS:
4595 snprintf(str, str_len, "%2s-%2s-%2s",
4596 yearzero, monzero, dayzero);
4597 break;
4598 case iDateIso:
4599 case iCurDateIso:
4600 snprintf(str, str_len, "%4s-%2s-%2s",
4601 year4, monzero, dayzero);
4602 break;
4603 default:
4604 break;
4608 break;
4609 case iTime24:
4610 case iCurTime24:
4611 snprintf(str, str_len, "%2s%c%2s",
4612 (hour24[0] && minzero[0]) ? hour24 : "",
4613 (hour24[0] && minzero[0]) ? ':' : ' ',
4614 (hour24[0] && minzero[0]) ? minzero : "");
4615 break;
4616 case iTime12:
4617 case iCurTime12:
4618 snprintf(str, str_len, "%s%c%2s%s",
4619 (hour12[0] && minzero[0]) ? hour12 : "",
4620 (hour12[0] && minzero[0]) ? ':' : ' ',
4621 (hour12[0] && minzero[0]) ? minzero : "",
4622 (hour12[0] && minzero[0] && d.hour < 12) ? "am" :
4623 (hour12[0] && minzero[0] && d.hour >= 12) ? "pm" :
4624 " ");
4625 break;
4626 case iSDate: case iSDateIso: case iSDateIsoS:
4627 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
4628 case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
4629 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
4630 case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
4631 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
4632 { struct date now, last_day;
4633 char dbuf[200];
4634 int msg_day_of_year, now_day_of_year, today;
4635 int diff, ydiff, last_day_of_year;
4637 rfc822_date(dbuf);
4638 parse_date(dbuf, &now);
4639 today = day_of_week(&now) + 7;
4641 if(today >= 0+7 && today <= 6+7){
4642 now_day_of_year = day_of_year(&now);
4643 msg_day_of_year = day_of_year(&d);
4644 ydiff = now.year - d.year;
4646 if(msg_day_of_year == -1)
4647 diff = -100;
4648 else if(ydiff == 0)
4649 diff = now_day_of_year - msg_day_of_year;
4650 else if(ydiff == 1){
4651 last_day = d;
4652 last_day.month = 12;
4653 last_day.day = 31;
4654 last_day_of_year = day_of_year(&last_day);
4656 diff = now_day_of_year +
4657 (last_day_of_year - msg_day_of_year);
4659 else if(ydiff == -1){
4660 last_day = now;
4661 last_day.month = 12;
4662 last_day.day = 31;
4663 last_day_of_year = day_of_year(&last_day);
4665 diff = -1 * (msg_day_of_year +
4666 (last_day_of_year - now_day_of_year));
4668 else if(ydiff > 1)
4669 diff = 100;
4670 else
4671 diff = -100;
4673 if(diff == 0)
4674 strncpy(str, _(TODAYSTR), str_len);
4675 else if(diff == 1)
4676 strncpy(str, _("Yesterday"), str_len);
4677 else if(diff > 1 && diff < 7)
4678 snprintf(str, str_len, "%s", day_name_locale((today - diff) % 7));
4679 else if(diff == -1)
4680 strncpy(str, _("Tomorrow"), str_len);
4681 else if(diff < -1 && diff > -7)
4682 snprintf(str, str_len, _("Next %.3s!"),
4683 day_name_locale((today - diff) % 7));
4684 else if(diff > 0
4685 && (ydiff == 0
4686 || (ydiff == 1 && 12 + now.month - d.month < 6))){
4687 if(v)
4688 snprintf(str, str_len, "%s%s%s", monabb,
4689 (monabb[0] && day[0]) ? " " : "", day);
4690 else{
4691 if(monabb_width > 0)
4692 utf8_snprintf(str, str_len, "%-*.*w %2s",
4693 monabb_width, monabb_width, monabb, day);
4694 else
4695 snprintf(str, str_len, "%s %2s", monabb, day);
4698 else{
4699 if(msg_day_of_year == -1 && (type == iSDate || type == iSDateTime))
4700 type = iSDateTimeIsoS;
4702 switch(type){
4703 case iSDate: case iSDateTime: case iSDateTime24:
4705 struct tm tm;
4707 memset(&tm, 0, sizeof(tm));
4708 tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
4709 tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
4710 tm.tm_mday = MIN(MAX(d.day, 1), 31);
4711 tm.tm_hour = MIN(MAX(d.hour, 0), 23);
4712 tm.tm_min = MIN(MAX(d.minute, 0), 59);
4713 our_strftime(str, str_len, "%x", &tm);
4716 break;
4717 case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
4718 if(v)
4719 snprintf(str, str_len, "%s/%s/%s%s", mon, day, yearzero,
4720 diff < 0 ? "!" : "");
4721 else
4722 snprintf(str, str_len, "%s%s/%s/%s%s",
4723 (mon[0] && mon[1]) ? "" : " ",
4724 mon, dayzero, yearzero,
4725 diff < 0 ? "!" : "");
4726 break;
4727 case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
4728 if(v)
4729 snprintf(str, str_len, "%s/%s/%s%s", day, mon, yearzero,
4730 diff < 0 ? "!" : "");
4731 else
4732 snprintf(str, str_len, "%s%s/%s/%s%s",
4733 (day[0] && day[1]) ? "" : " ",
4734 day, monzero, yearzero,
4735 diff < 0 ? "!" : "");
4736 break;
4737 case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
4738 if(v)
4739 snprintf(str, str_len, "%s.%s.%s%s", day, mon, yearzero,
4740 diff < 0 ? "!" : "");
4741 else
4742 snprintf(str, str_len, "%s%s.%s.%s%s",
4743 (day[0] && day[1]) ? "" : " ",
4744 day, monzero, yearzero,
4745 diff < 0 ? "!" : "");
4746 break;
4747 case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
4748 if(v)
4749 snprintf(str, str_len, "%s.%s.%s%s",
4750 yearzero, monzero, dayzero,
4751 diff < 0 ? "!" : "");
4752 else
4753 snprintf(str, str_len, "%s.%s.%s%s",
4754 yearzero, monzero, dayzero,
4755 diff < 0 ? "!" : "");
4756 break;
4757 case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
4758 snprintf(str, str_len, "%2s-%2s-%2s",
4759 yearzero, monzero, dayzero);
4760 break;
4761 case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
4762 snprintf(str, str_len, "%4s-%2s-%2s",
4763 year4, monzero, dayzero);
4764 break;
4765 default:
4766 break;
4771 else{
4772 if(v)
4773 snprintf(str, str_len, "%s%s%s", monabb,
4774 (monabb[0] && day[0]) ? " " : "", day);
4775 else{
4776 if(monabb_width > 0)
4777 utf8_snprintf(str, str_len, "%-*.*w %2s",
4778 monabb_width, monabb_width, monabb, day);
4779 else
4780 snprintf(str, str_len, "%s %2s", monabb, day);
4785 break;
4787 default:
4788 break;
4791 str[str_len-1] = '\0';
4793 if(type == iSTime ||
4794 (sdatetimetype && !strcmp(str, _(TODAYSTR)))){
4795 struct date now, last_day;
4796 char dbuf[200], *Ddd, *ampm;
4797 int daydiff;
4799 str[0] = '\0';
4800 rfc822_date(dbuf);
4801 parse_date(dbuf, &now);
4803 /* Figure out if message date lands in the past week */
4805 /* (if message dated this month or last month...) */
4806 if((d.year == now.year && d.month >= now.month - 1) ||
4807 (d.year == now.year - 1 && d.month == 12 && now.month == 1)){
4809 daydiff = day_of_year(&now) - day_of_year(&d);
4812 * If msg in end of last year (and we're in first bit of "this"
4813 * year), diff will be backwards; fix up by adding number of days
4814 * in last year (usually 365, but occasionally 366)...
4816 if(d.year == now.year - 1){
4817 last_day = d;
4818 last_day.month = 12;
4819 last_day.day = 31;
4821 daydiff += day_of_year(&last_day);
4824 else
4825 daydiff = -100; /* comfortably out of range (of past week) */
4827 /* Build 2-digit hour and am/pm indicator, used below */
4829 if(d.hour >= 0 && d.hour < 24){
4830 snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12);
4831 ampm = (d.hour < 12) ? "am" : "pm";
4832 snprintf(hour24, sizeof(hour24), "%02d", d.hour);
4834 else{
4835 strncpy(hour12, "??", sizeof(hour12));
4836 hour12[sizeof(hour12)-1] = '\0';
4837 ampm = "__";
4838 strncpy(hour24, "??", sizeof(hour24));
4839 hour24[sizeof(hour24)-1] = '\0';
4842 /* Build date/time in str, in format similar to that used by w(1) */
4844 if(daydiff == 0){ /* If date is today, "HH:MMap" */
4845 if(d.minute >= 0 && d.minute < 60)
4846 snprintf(minzero, sizeof(minzero), "%02d", d.minute);
4847 else{
4848 strncpy(minzero, "??", sizeof(minzero));
4849 minzero[sizeof(minzero)-1] = '\0';
4852 snprintf(str, str_len, "%s:%s%s", sdatetime24type ? hour24 : hour12,
4853 minzero, sdatetime24type ? "" : ampm);
4855 else if(daydiff >= 1 && daydiff < 6){ /* If <1wk ago, "DddHHap" */
4857 if(d.month >= 1 && d.day >= 1 && d.year >= 0 &&
4858 d.month <= 12 && d.day <= 31 && d.year <= 9999)
4859 Ddd = day_abbrev_locale(day_of_week(&d));
4860 else
4861 Ddd = "???";
4863 snprintf(str, str_len, "%s%s%s", Ddd, hour12, ampm);
4865 else{ /* date is old or future, "ddMmmyy" */
4866 strncpy(monabb, (d.month >= 1 && d.month <= 12)
4867 ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
4868 monabb[sizeof(monabb)-1] = '\0';
4870 if(d.day >= 1 && d.day <= 31)
4871 snprintf(dayzero, sizeof(dayzero), "%02d", d.day);
4872 else{
4873 strncpy(dayzero, "??", sizeof(dayzero));
4874 dayzero[sizeof(dayzero)-1] = '\0';
4877 if(d.year >= 0 && d.year <= 9999)
4878 snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100);
4879 else{
4880 strncpy(yearzero, "??", sizeof(yearzero));
4881 yearzero[sizeof(yearzero)-1] = '\0';
4884 snprintf(str, str_len, "%s%s%s", dayzero, monabb, yearzero);
4887 if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */
4888 if(v)
4889 memmove(str, str + 1, strlen(str));
4890 else
4891 str[0] = ' ';
4898 * Format a string representing the keywords into ice.
4900 * This needs to be done in UTF-8, which may be tricky since it isn't labelled.
4902 * Args idata -- which message?
4903 * kwtype -- keywords or kw initials
4904 * ice -- index cache entry for message
4906 void
4907 key_str(INDEXDATA_S *idata, SubjKW kwtype, ICE_S *ice)
4909 int firstone = 1;
4910 KEYWORD_S *kw;
4911 char *word;
4912 COLOR_PAIR *color = NULL;
4913 SPEC_COLOR_S *sc = ps_global->kw_colors;
4914 IELEM_S *ielem = NULL;
4915 IFIELD_S *ourifield = NULL;
4917 if(ice && ice->ifield){
4918 /* move to last ifield, the one we're working */
4919 for(ourifield = ice->ifield;
4920 ourifield && ourifield->next;
4921 ourifield = ourifield->next)
4925 if(!ourifield)
4926 return;
4928 if(kwtype == KWInit){
4929 for(kw = ps_global->keywords; kw; kw = kw->next){
4930 if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
4931 word = (kw->nick && kw->nick[0]) ? kw->nick :
4932 (kw->kw && kw->kw[0]) ? kw->kw : "";
4935 * Pick off the first initial. Since word is UTF-8 it may
4936 * take more than one byte for the first initial.
4939 if(word && word[0]){
4940 UCS ucs;
4941 unsigned long remaining_octets;
4942 unsigned char *inputp;
4944 remaining_octets = strlen(word);
4945 inputp = (unsigned char *) word;
4946 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
4947 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
4948 ielem = new_ielem(&ourifield->ielem);
4949 ielem->freedata = 1;
4950 ielem->datalen = (unsigned) (inputp - (unsigned char *) word);
4951 ielem->data = (char *) fs_get((ielem->datalen + 1) * sizeof(char));
4952 strncpy(ielem->data, word, ielem->datalen);
4953 ielem->data[ielem->datalen] = '\0';
4955 if(pico_usingcolor()
4956 && ((kw->nick && kw->nick[0]
4957 && (color=hdr_color(kw->nick,NULL,sc)))
4958 || (kw->kw && kw->kw[0]
4959 && (color=hdr_color(kw->kw,NULL,sc))))){
4960 ielem->color = color;
4961 color = NULL;
4966 if(color)
4967 free_color_pair(&color);
4971 else if(kwtype == KW){
4972 for(kw = ps_global->keywords; kw; kw = kw->next){
4973 if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
4975 if(!firstone){
4976 ielem = new_ielem(&ourifield->ielem);
4977 ielem->freedata = 1;
4978 ielem->data = cpystr(" ");
4979 ielem->datalen = 1;
4982 firstone = 0;
4984 word = (kw->nick && kw->nick[0]) ? kw->nick :
4985 (kw->kw && kw->kw[0]) ? kw->kw : "";
4987 if(word[0]){
4988 ielem = new_ielem(&ourifield->ielem);
4989 ielem->freedata = 1;
4990 ielem->data = cpystr(word);
4991 ielem->datalen = strlen(word);
4993 if(pico_usingcolor()
4994 && ((kw->nick && kw->nick[0]
4995 && (color=hdr_color(kw->nick,NULL,sc)))
4996 || (kw->kw && kw->kw[0]
4997 && (color=hdr_color(kw->kw,NULL,sc))))){
4998 ielem->color = color;
4999 color = NULL;
5003 if(color)
5004 free_color_pair(&color);
5010 * If we're coloring some of the fields then add a dummy field
5011 * at the end that can soak up the rest of the space after the last
5012 * colored keyword. Otherwise, the last one's color will extend to
5013 * the end of the field.
5015 if(pico_usingcolor()){
5016 ielem = new_ielem(&ourifield->ielem);
5017 ielem->freedata = 1;
5018 ielem->data = cpystr(" ");
5019 ielem->datalen = 1;
5022 ourifield->leftadj = 1;
5023 set_ielem_widths_in_field(ourifield);
5027 void
5028 prio_str(INDEXDATA_S *idata, IndexColType ctype, ICE_S *ice)
5030 IFIELD_S *ourifield = NULL;
5031 IELEM_S *ielem = NULL;
5032 char *hdrval;
5033 PRIORITY_S *p;
5034 int v;
5036 if(ice && ice->ifield){
5037 /* move to last ifield, the one we're working */
5038 for(ourifield = ice->ifield;
5039 ourifield && ourifield->next;
5040 ourifield = ourifield->next)
5044 if(!ourifield)
5045 return;
5047 hdrval = fetch_header(idata, PRIORITYNAME);
5049 if(hdrval && hdrval[0] && isdigit(hdrval[0])){
5050 v = atoi(hdrval);
5051 if(v >= 1 && v <= 5 && v != 3){
5053 ielem = new_ielem(&ourifield->ielem);
5054 ielem->freedata = 1;
5056 switch(ctype){
5057 case iPrio:
5058 ielem->data = (char *) fs_get(2 * sizeof(char));
5059 ielem->data[0] = hdrval[0];
5060 ielem->data[1] = '\0';
5061 break;
5063 case iPrioAlpha:
5064 for(p = priorities; p && p->desc; p++)
5065 if(p->val == v)
5066 break;
5068 if(p && p->desc)
5069 ielem->data = cpystr(p->desc);
5071 break;
5073 case iPrioBang:
5074 ielem->data = (char *) fs_get(2 * sizeof(char));
5075 ielem->data[0] = '\0';
5076 switch(v){
5077 case 1: case 2:
5078 ielem->data[0] = '!';
5079 break;
5081 case 4: case 5:
5083 * We could put a Unicode downarrow in here but
5084 * we have no way of knowing if the user's font
5085 * will have it (I think).
5087 ielem->data[0] = 'v';
5088 break;
5091 ielem->data[1] = '\0';
5093 break;
5095 default:
5096 panic("Unhandled case in prio_str");
5097 break;
5100 if(!ielem->data)
5101 ielem->data = cpystr("");
5103 if(ielem && ielem->data)
5104 ielem->datalen = strlen(ielem->data);
5106 if((v == 1 || v == 2) && pico_usingcolor()
5107 && ps_global->VAR_IND_HIPRI_FORE_COLOR
5108 && ps_global->VAR_IND_HIPRI_BACK_COLOR){
5109 ielem->type = eTypeCol;
5110 ielem->freecolor = 1;
5111 ielem->color = new_color_pair(ps_global->VAR_IND_HIPRI_FORE_COLOR, ps_global->VAR_IND_HIPRI_BACK_COLOR);
5113 else if((v == 4 || v == 5) && pico_usingcolor()
5114 && ps_global->VAR_IND_LOPRI_FORE_COLOR
5115 && ps_global->VAR_IND_LOPRI_BACK_COLOR){
5116 ielem->type = eTypeCol;
5117 ielem->freecolor = 1;
5118 ielem->color = new_color_pair(ps_global->VAR_IND_LOPRI_FORE_COLOR, ps_global->VAR_IND_LOPRI_BACK_COLOR);
5121 ourifield->leftadj = 1;
5122 set_ielem_widths_in_field(ourifield);
5125 fs_give((void **) &hdrval);
5130 void
5131 header_str(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok, ICE_S *ice)
5133 IFIELD_S *ourifield = NULL;
5134 IELEM_S *ielem = NULL;
5135 char *fieldval = NULL;
5137 if(ice && ice->ifield){
5138 /* move to last ifield, the one we're working */
5139 for(ourifield = ice->ifield;
5140 ourifield && ourifield->next;
5141 ourifield = ourifield->next)
5145 if(!ourifield)
5146 return;
5148 fieldval = get_fieldval(idata, hdrtok);
5150 if(fieldval){
5151 ielem = new_ielem(&ourifield->ielem);
5152 ielem->freedata = 1;
5153 ielem->data = fieldval;
5154 ielem->datalen = strlen(fieldval);
5155 fieldval = NULL;
5156 ourifield->leftadj = (hdrtok->adjustment == Left) ? 1 : 0;
5159 set_ielem_widths_in_field(ourifield);
5163 char *
5164 get_fieldval(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok)
5166 int sep, fieldnum;
5167 char *hdrval = NULL, *testval;
5168 char *fieldval = NULL, *firstval;
5169 char *retfieldval = NULL;
5171 if(!hdrtok)
5172 return(retfieldval);
5174 if(hdrtok && hdrtok->hdrname && hdrtok->hdrname[0])
5175 hdrval = fetch_header(idata, hdrtok ? hdrtok->hdrname : "");
5177 /* find start of fieldnum'th field */
5178 fieldval = hdrval;
5179 for(fieldnum = MAX(hdrtok->fieldnum-1, 0);
5180 fieldnum > 0 && fieldval && *fieldval; fieldnum--){
5182 firstval = NULL;
5183 for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
5184 testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
5185 if(testval && (!firstval || testval < firstval))
5186 firstval = testval;
5189 fieldval = firstval;
5190 if(fieldval && *fieldval)
5191 fieldval++;
5194 /* tie off end of field */
5195 if(fieldval && *fieldval && hdrtok->fieldnum > 0){
5196 firstval = NULL;
5197 for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
5198 testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
5199 if(testval && (!firstval || testval < firstval))
5200 firstval = testval;
5203 if(firstval)
5204 *firstval = '\0';
5207 if(!fieldval)
5208 fieldval = "";
5210 retfieldval = cpystr(fieldval);
5212 if(hdrval)
5213 fs_give((void **) &hdrval);
5215 return(retfieldval);
5219 long
5220 scorevalfrommsg(MAILSTREAM *stream, MsgNo rawno, HEADER_TOK_S *hdrtok, int no_fetch)
5222 INDEXDATA_S idata;
5223 MESSAGECACHE *mc;
5224 char *fieldval = NULL;
5225 long retval = 0L;
5227 memset(&idata, 0, sizeof(INDEXDATA_S));
5228 idata.stream = stream;
5229 idata.no_fetch = no_fetch;
5230 idata.msgno = mn_raw2m(sp_msgmap(stream), rawno);
5231 idata.rawno = rawno;
5232 if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
5233 && (mc = mail_elt(stream, idata.rawno))){
5234 idata.size = mc->rfc822_size;
5235 index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
5237 else
5238 idata.bogus = 2;
5240 fieldval = get_fieldval(&idata, hdrtok);
5242 if(fieldval){
5243 retval = atol(fieldval);
5244 fs_give((void **) &fieldval);
5247 return(retval);
5252 * Put a string representing the subject into str. Idata tells us which
5253 * message we are referring to.
5255 * This means we should ensure that all data ends up being UTF-8 data.
5256 * That covers the data in ice ielems and str.
5258 * Args idata -- which message?
5259 * str -- destination buffer
5260 * strsize -- size of str buffer
5261 * kwtype -- prepend keywords or kw initials before the subject
5262 * opening -- add first text from body of message if there's room
5263 * ice -- index cache entry for message
5265 void
5266 subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int opening, ICE_S *ice)
5268 char *subject, *origsubj, *origstr, *rawsubj, *sptr = NULL;
5269 char *p, *border, *q = NULL, *free_subj = NULL;
5270 char *sp;
5271 size_t len;
5272 int width = -1;
5273 int depth = 0, mult = 2;
5274 int save;
5275 int do_subj = 0, truncated_tree = 0;
5276 PINETHRD_S *thd, *thdorig;
5277 IELEM_S *ielem = NULL, *subjielem = NULL;
5278 IFIELD_S *ourifield = NULL;
5280 if(strsize <= 0)
5281 return;
5284 * If we need the data at the start of the message and we're in
5285 * a c-client callback, defer the data lookup until later.
5287 if(opening && idata->no_fetch){
5288 idata->bogus = 1;
5289 return;
5292 if(ice && ice->ifield){
5293 /* move to last ifield, the one we're working on */
5294 for(ourifield = ice->ifield;
5295 ourifield && ourifield->next;
5296 ourifield = ourifield->next)
5300 str[0] = str[strsize-1] = '\0';
5301 origstr = str;
5302 rawsubj = fetch_subject(idata);
5303 if(!rawsubj)
5304 rawsubj = "";
5307 * Before we do anything else, decode the character set in the subject and
5308 * work with the result.
5310 sp = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
5311 SIZEOF_20KBUF, rawsubj);
5313 len = strlen(sp);
5314 len += 100; /* for possible charset, escaped characters */
5315 origsubj = fs_get((len+1) * sizeof(unsigned char));
5316 origsubj[0] = '\0';
5318 iutf8ncpy(origsubj, sp, len);
5320 origsubj[len] = '\0';
5321 replace_tabs_by_space(origsubj);
5322 removing_trailing_white_space(origsubj);
5325 * origsubj is the original subject but it has been decoded. We need
5326 * to free it at the end of this routine.
5331 * prepend_keyword will put the keyword stuff before the subject
5332 * and split the subject up into its colored parts in subjielem.
5333 * Subjielem is a local ielem which will have to be fit into the
5334 * real ifield->ielem later. The print_format strings in subjielem will
5335 * not be filled in by prepend_keyword because of the fact that we
5336 * may have to adjust things for threading below.
5337 * We use subjielem in case we want to insert some threading information
5338 * at the front of the subject.
5340 if(kwtype == KW || kwtype == KWInit){
5341 subject = prepend_keyword_subject(idata->stream, idata->rawno,
5342 origsubj, kwtype,
5343 ourifield ? &subjielem : NULL,
5344 ps_global->VAR_KW_BRACES);
5345 free_subj = subject;
5347 else{
5348 subject = origsubj;
5349 if(ourifield){
5350 subjielem = new_ielem(&subjielem);
5351 subjielem->type = eTypeCol;
5352 subjielem->freedata = 1;
5353 subjielem->data = cpystr(subject);
5354 subjielem->datalen = strlen(subject);
5355 if(pico_usingcolor()
5356 && ps_global->VAR_IND_SUBJ_FORE_COLOR
5357 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
5358 subjielem->freecolor = 1;
5359 subjielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
5365 * This space is here so that if the subject does
5366 * not extend all the way to the end of the field then
5367 * we'll switch the color back and paint the rest of the
5368 * field in the Normal color or the index line color.
5370 if(!opening){
5371 ielem = new_ielem(&subjielem);
5372 ielem->freedata = 1;
5373 ielem->data = cpystr(" ");
5374 ielem->datalen = 1;
5377 if(!subject)
5378 subject = "";
5380 if(THREADING()
5381 && (ps_global->thread_disp_style == THREAD_STRUCT
5382 || ps_global->thread_disp_style == THREAD_MUTTLIKE
5383 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ1
5384 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ2)){
5387 * Why do we want to truncate the subject and from strs?
5388 * It's so we can put the [5] thread count things in below when
5389 * we are threading and the thread structure runs off the right
5390 * hand edge of the screen. This routine doesn't know that it
5391 * is running off the edge unless it knows the actual width
5392 * that we have to draw in.
5394 if(pith_opt_truncate_sfstr
5395 && (*pith_opt_truncate_sfstr)()
5396 && ourifield
5397 && ourifield->width > 0)
5398 width = ourifield->width;
5400 if(width < 0)
5401 width = strsize-1;
5403 width = MIN(width, strsize-1);
5406 * We're counting on the fact that this initial part of the
5407 * string is ascii and we have one octet per character and
5408 * characters are width 1 on the screen.
5410 border = str + width;
5412 thdorig = thd = fetch_thread(idata->stream, idata->rawno);
5414 if(pith_opt_condense_thread_cue)
5415 width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
5416 thd && thd->next
5417 && get_lflag(idata->stream,
5418 NULL,idata->rawno,
5419 MN_COLL));
5422 * width is < available strsize and
5423 * border points to something less than or equal
5424 * to the end of the buffer.
5427 sptr = str;
5429 if(thd)
5430 while(thd->parent &&
5431 (thd = fetch_thread(idata->stream, thd->parent)))
5432 depth++;
5434 if(depth > 0){
5435 if(ps_global->thread_disp_style == THREAD_INDENT_SUBJ1)
5436 mult = 1;
5438 sptr += (mult*depth);
5439 for(thd = thdorig, p = str + mult*depth - mult;
5440 thd && thd->parent && p >= str;
5441 thd = fetch_thread(idata->stream, thd->parent), p -= mult){
5442 if(p + mult >= border && !q){
5443 if(width >= 4 && depth < 100){
5444 snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
5445 q = str + width-4;
5447 else if(width >= 5 && depth < 1000){
5448 snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
5449 q = str + width-5;
5451 else{
5452 snprintf(str, width+1, "%s", repeat_char(width, '.'));
5453 q = str;
5456 border = q;
5457 truncated_tree++;
5460 if(p < border){
5461 p[0] = ' ';
5462 if(p + 1 < border)
5463 p[1] = ' ';
5465 if(ps_global->thread_disp_style == THREAD_STRUCT
5466 || ps_global->thread_disp_style == THREAD_MUTTLIKE){
5468 * WARNING!
5469 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
5470 * is ascii.
5472 if(thd == thdorig && !thd->branch)
5473 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
5474 else if(thd == thdorig || thd->branch)
5475 p[0] = '|';
5477 if(p + 1 < border && thd == thdorig)
5478 p[1] = '-';
5484 if(sptr && !truncated_tree){
5486 * Look to see if the subject is the same as the previous
5487 * message in the thread, if any. If it is the same, don't
5488 * reprint the subject.
5490 * Note that when we're prepending keywords to the subject,
5491 * and the user changes a keyword, we do invalidate
5492 * the index cache for that message but we don't go to the
5493 * trouble of invalidating the index cache for the the child
5494 * of that node in the thread, so the MUTT subject line
5495 * display for the child may be wrong. That is, it may show
5496 * it is the same as this subject even though it no longer
5497 * is, or vice versa.
5499 if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
5500 if(depth == 0)
5501 do_subj++;
5502 else{
5503 if(thdorig->parent &&
5504 (thd = fetch_thread(idata->stream, thdorig->parent))
5505 && thd->rawno){
5506 char *this_orig = NULL,
5507 *prev_orig = NULL,
5508 *free_prev_orig = NULL,
5509 *this_prep = NULL, /* includes prepend */
5510 *prev_prep = NULL;
5511 ENVELOPE *env;
5512 mailcache_t mc;
5513 SORTCACHE *sc = NULL;
5515 /* get the stripped subject of previous message */
5516 mc = (mailcache_t) mail_parameters(NIL, GET_CACHE, NIL);
5517 if(mc)
5518 sc = (*mc)(idata->stream, thd->rawno, CH_SORTCACHE);
5520 if(sc && sc->subject)
5521 prev_orig = sc->subject;
5522 else{
5523 char *stripthis;
5525 env = pine_mail_fetchenvelope(idata->stream,
5526 thd->rawno);
5527 stripthis = (env && env->subject)
5528 ? env->subject : "";
5530 mail_strip_subject(stripthis, &prev_orig);
5532 free_prev_orig = prev_orig;
5535 mail_strip_subject(rawsubj, &this_orig);
5537 if(kwtype == KW || kwtype == KWInit){
5538 prev_prep = prepend_keyword_subject(idata->stream,
5539 thd->rawno,
5540 prev_orig,
5541 kwtype, NULL,
5542 ps_global->VAR_KW_BRACES);
5544 this_prep = prepend_keyword_subject(idata->stream,
5545 idata->rawno,
5546 this_orig,
5547 kwtype, NULL,
5548 ps_global->VAR_KW_BRACES);
5550 if((this_prep || prev_prep)
5551 && ((this_prep && !prev_prep)
5552 || (prev_prep && !this_prep)
5553 || strucmp(this_prep, prev_prep)))
5554 do_subj++;
5556 else{
5557 if((this_orig || prev_orig)
5558 && ((this_orig && !prev_orig)
5559 || (prev_orig && !this_orig)
5560 || strucmp(this_orig, prev_orig)))
5561 do_subj++;
5565 * If some of the thread is zoomed out of view, we
5566 * want to display the subject of the first one that
5567 * is in view. If any of the parents or grandparents
5568 * etc of this message are visible, then we don't
5569 * need to worry about it. If all of the parents have
5570 * been zoomed away, then this is the first one.
5572 * When you're looking at a particular case where
5573 * some of the messages of a thread are selected it
5574 * seems like we should look at not only our
5575 * direct parents, but the siblings of the parent
5576 * too. But that's not really correct, because those
5577 * siblings are basically the starts of different
5578 * branches, separate from our branch. They could
5579 * have their own subjects, for example. This will
5580 * give us cases where it looks like we are showing
5581 * the subject too much, but it will be correct!
5583 * In zoom_index() we clear_index_cache_ent for
5584 * some lines which have subjects which might become
5585 * visible when we zoom, and also in set_lflags
5586 * where we might change subjects by unselecting
5587 * something when zoomed.
5589 if(!do_subj){
5590 while(thd){
5591 if(!msgline_hidden(idata->stream,
5592 sp_msgmap(idata->stream),
5593 mn_raw2m(sp_msgmap(idata->stream),
5594 (long) thd->rawno),
5595 0)){
5596 break; /* found a visible parent */
5599 if(thd && thd->parent)
5600 thd = fetch_thread(idata->stream,thd->parent);
5601 else
5602 thd = NULL;
5605 if(!thd) /* none were visible */
5606 do_subj++;
5609 if(this_orig)
5610 fs_give((void **) &this_orig);
5612 if(this_prep)
5613 fs_give((void **) &this_prep);
5615 if(free_prev_orig)
5616 fs_give((void **) &free_prev_orig);
5618 if(prev_prep)
5619 fs_give((void **) &prev_prep);
5621 else
5622 do_subj++;
5625 else
5626 do_subj++;
5628 if(do_subj){
5630 * We don't need to worry about truncating to width
5631 * here. If we go over the right hand edge it will be
5632 * truncated.
5634 strsize -= (sptr - str);
5636 strncpy(sptr, subject, strsize-1);
5637 sptr[strsize-1] = '\0';
5639 else if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
5640 strsize -= (sptr - str);
5642 if(strsize > 0){
5643 sptr[0] = '>';
5644 sptr++;
5648 * We decided we don't need the subject so we'd better
5649 * eliminate subjielem.
5651 free_ielem(&subjielem);
5654 else
5655 free_ielem(&subjielem); /* no room for actual subject */
5657 if(ourifield && sptr && sptr > origstr){
5658 ielem = new_ielem(&ourifield->ielem);
5659 ielem->type = eThreadInfo;
5660 ielem->freedata = 1;
5661 save = *sptr;
5662 *sptr = '\0';
5663 ielem->data = cpystr(origstr);
5664 ielem->datalen = strlen(origstr);
5665 *sptr = save;
5668 else{
5670 * Not much to do for the non-threading case. Just copy the
5671 * subject we have so far into str and truncate it.
5673 strncpy(str, subject, strsize-1);
5674 str[strsize-1] = '\0';
5677 if(ourifield){
5679 * We need to add subjielem to the end of the ourifield->ielem list.
5681 if(subjielem){
5682 if(ourifield->ielem){
5683 for(ielem = ourifield->ielem;
5684 ielem && ielem->next; ielem = ielem->next)
5687 ielem->next = subjielem;
5689 else
5690 ourifield->ielem = subjielem;
5693 ourifield->leftadj = 1;
5696 if(opening && ourifield){
5697 IELEM_S *ftielem = NULL;
5698 size_t len;
5699 char *first_text;
5701 first_text = fetch_firsttext(idata, 0);
5703 if(first_text){
5704 char sep[200];
5705 int seplen;
5707 strncpy(sep, ps_global->VAR_OPENING_SEP ? ps_global->VAR_OPENING_SEP : " - ",
5708 sizeof(sep));
5709 sep[sizeof(sep)-1] = '\0';
5710 removing_double_quotes(sep);
5711 seplen = strlen(sep);
5713 ftielem = new_ielem(&ftielem);
5714 ftielem->type = eTypeCol;
5715 ftielem->freedata = 1;
5716 len = strlen(first_text) + seplen;
5717 ftielem->data = (char *) fs_get((len + 1) * sizeof(char));
5719 strncpy(ftielem->data, sep, seplen);
5720 strncpy(ftielem->data+seplen, first_text, len+1-seplen);
5721 ftielem->data[len] = '\0';
5723 ftielem->datalen = strlen(ftielem->data);
5724 if(first_text)
5725 fs_give((void **) &first_text);
5727 if(ftielem){
5728 if(pico_usingcolor()
5729 && ps_global->VAR_IND_OP_FORE_COLOR
5730 && ps_global->VAR_IND_OP_BACK_COLOR){
5731 ftielem->freecolor = 1;
5732 ftielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR, ps_global->VAR_IND_OP_BACK_COLOR);
5735 * This space is here so that if the opening text does
5736 * not extend all the way to the end of the field then
5737 * we'll switch the color back and paint the rest of the
5738 * field in the Normal color or the index line color.
5740 ielem = new_ielem(&ftielem);
5741 ielem->freedata = 1;
5742 ielem->data = cpystr(" ");
5743 ielem->datalen = 1;
5746 if(ourifield->ielem){
5747 for(ielem = ourifield->ielem;
5748 ielem && ielem->next; ielem = ielem->next)
5751 ielem->next = ftielem;
5753 else
5754 ourifield->ielem = ftielem;
5757 ourifield->leftadj = 1;
5761 if(ourifield)
5762 set_ielem_widths_in_field(ourifield);
5764 if(origsubj)
5765 fs_give((void **) &origsubj);
5767 if(free_subj)
5768 fs_give((void **) &free_subj);
5773 * Returns an allocated string which is the passed in subject with a
5774 * list of keywords prepended.
5776 * If kwtype == KW you will end up with
5778 * {keyword1 keyword2} subject
5780 * (actually, keyword nicknames will be used instead of the actual keywords
5781 * in the case that the user defined nicknames)
5783 * If kwtype == KWInit you get
5785 * {AB} subject
5787 * where A is the first letter of the first keyword and B is the first letter
5788 * of the second defined keyword. No space between them. There could be more
5789 * than two.
5791 * If an ielemp is passed in it will be filled out with the data and colors
5792 * of the pieces of the subject but the print_format strings will not
5793 * be set.
5795 char *
5796 prepend_keyword_subject(MAILSTREAM *stream, long int rawno, char *subject,
5797 SubjKW kwtype, IELEM_S **ielemp, char *braces)
5799 char *p, *next_piece, *retsubj = NULL, *str;
5800 char *left_brace = NULL, *right_brace = NULL;
5801 size_t len;
5802 int some_set = 0, save;
5803 IELEM_S *ielem;
5804 KEYWORD_S *kw;
5805 COLOR_PAIR *color = NULL;
5806 SPEC_COLOR_S *sc = ps_global->kw_colors;
5808 if(!subject)
5809 subject = "";
5811 if(braces && *braces)
5812 get_pair(braces, &left_brace, &right_brace, 1, 0);
5814 len = (left_brace ? strlen(left_brace) : 0) +
5815 (right_brace ? strlen(right_brace) : 0);
5817 if(stream && rawno >= 0L && rawno <= stream->nmsgs){
5818 for(kw = ps_global->keywords; kw; kw = kw->next)
5819 if(user_flag_is_set(stream, rawno, kw->kw)){
5820 if(kwtype == KW){
5821 if(some_set)
5822 len++; /* space between keywords */
5824 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5825 len += strlen(str);
5827 else if(kwtype == KWInit){
5828 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5829 /* interested in only the first UTF-8 initial */
5830 if(str && str[0]){
5831 UCS ucs;
5832 unsigned long remaining_octets;
5833 unsigned char *inputp;
5835 remaining_octets = strlen(str);
5836 inputp = (unsigned char *) str;
5837 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
5838 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
5839 len += (unsigned) (inputp - (unsigned char *) str);
5844 some_set++;
5848 if((kwtype == KW || kwtype == KWInit) && some_set){
5849 len += strlen(subject); /* subject is already UTF-8 if needed */
5850 retsubj = (char *) fs_get((len + 1) * sizeof(*retsubj));
5851 memset(retsubj, 0, (len + 1) * sizeof(*retsubj));
5852 next_piece = p = retsubj;
5854 for(kw = ps_global->keywords; kw; kw = kw->next){
5855 if(user_flag_is_set(stream, rawno, kw->kw)){
5856 if(p == retsubj){
5857 if(left_brace && len > 0)
5858 sstrncpy(&p, left_brace, len);
5860 else if(kwtype == KW)
5861 *p++ = ' ';
5863 if(ielemp && p > next_piece){
5864 save = *p;
5865 *p = '\0';
5866 ielem = new_ielem(ielemp);
5867 ielem->freedata = 1;
5868 ielem->data = cpystr(next_piece);
5869 ielem->datalen = strlen(next_piece);
5870 *p = save;
5871 next_piece = p;
5874 str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
5876 if(kwtype == KWInit){
5877 if(str && str[0]){
5878 UCS ucs;
5879 unsigned long remaining_octets;
5880 unsigned char *inputp;
5882 remaining_octets = strlen(str);
5883 inputp = (unsigned char *) str;
5884 ucs = (UCS) utf8_get(&inputp, &remaining_octets);
5885 if(!(ucs & U8G_ERROR || ucs == UBOGON)){
5886 if(len-(p-retsubj) > 0){
5887 sstrncpy(&p, str, MIN(inputp - (unsigned char *) str,len-(p-retsubj)));
5888 if(p > next_piece && ielemp && pico_usingcolor()
5889 && ((kw->nick && kw->nick[0]
5890 && (color=hdr_color(kw->nick,NULL,sc)))
5891 || (kw->kw && kw->kw[0]
5892 && (color=hdr_color(kw->kw,NULL,sc))))){
5893 ielem = new_ielem(ielemp);
5894 ielem->freedata = 1;
5895 save = *p;
5896 *p = '\0';
5897 ielem->data = cpystr(next_piece);
5898 ielem->datalen = strlen(next_piece);
5899 ielem->color = color;
5900 color = NULL;
5901 *p = save;
5902 next_piece = p;
5907 if(color)
5908 free_color_pair(&color);
5911 else{
5912 if(len-(p-retsubj) > 0)
5913 sstrncpy(&p, str, len-(p-retsubj));
5915 if(p > next_piece && ielemp && pico_usingcolor()
5916 && ((kw->nick && kw->nick[0]
5917 && (color=hdr_color(kw->nick,NULL,sc)))
5918 || (kw->kw && kw->kw[0]
5919 && (color=hdr_color(kw->kw,NULL,sc))))){
5920 ielem = new_ielem(ielemp);
5921 ielem->freedata = 1;
5922 save = *p;
5923 *p = '\0';
5924 ielem->data = cpystr(next_piece);
5925 ielem->datalen = strlen(next_piece);
5926 ielem->color = color;
5927 color = NULL;
5928 *p = save;
5929 next_piece = p;
5932 if(color)
5933 free_color_pair(&color);
5938 if(len-(p-retsubj) > 0 && right_brace)
5939 sstrncpy(&p, right_brace, len-(p-retsubj));
5941 if(ielemp && p > next_piece){
5942 save = *p;
5943 *p = '\0';
5944 ielem = new_ielem(ielemp);
5945 ielem->freedata = 1;
5946 ielem->data = cpystr(next_piece);
5947 ielem->datalen = strlen(next_piece);
5948 *p = save;
5949 next_piece = p;
5952 if(len-(p-retsubj) > 0 && subject)
5953 sstrncpy(&p, subject, len-(p-retsubj));
5955 if(ielemp && p > next_piece){
5956 save = *p;
5957 *p = '\0';
5958 ielem = new_ielem(ielemp);
5959 ielem->type = eTypeCol;
5960 ielem->freedata = 1;
5961 ielem->data = cpystr(next_piece);
5962 ielem->datalen = strlen(next_piece);
5963 *p = save;
5964 next_piece = p;
5965 if(pico_usingcolor()
5966 && ps_global->VAR_IND_SUBJ_FORE_COLOR
5967 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
5968 ielem->freecolor = 1;
5969 ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
5973 retsubj[len] = '\0'; /* just making sure */
5975 else{
5976 if(ielemp){
5977 ielem = new_ielem(ielemp);
5978 ielem->type = eTypeCol;
5979 ielem->freedata = 1;
5980 ielem->data = cpystr(subject);
5981 ielem->datalen = strlen(subject);
5982 if(pico_usingcolor()
5983 && ps_global->VAR_IND_SUBJ_FORE_COLOR
5984 && ps_global->VAR_IND_SUBJ_BACK_COLOR){
5985 ielem->freecolor = 1;
5986 ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
5990 retsubj = cpystr(subject);
5993 if(braces){
5994 if(left_brace)
5995 fs_give((void **) &left_brace);
5997 if(right_brace)
5998 fs_give((void **) &right_brace);
6001 return(retsubj);
6006 * This means we should ensure that all data ends up being UTF-8 data.
6007 * That covers the data in ice ielems and str.
6009 void
6010 from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_S *ice)
6012 char *field, *newsgroups, *border, *p, *fptr = NULL, *q = NULL;
6013 ADDRESS *addr;
6014 int width = -1;
6015 int depth = 0, mult = 2;
6016 PINETHRD_S *thd, *thdorig;
6018 if(THREADING()
6019 && (ps_global->thread_disp_style == THREAD_INDENT_FROM1
6020 || ps_global->thread_disp_style == THREAD_INDENT_FROM2
6021 || ps_global->thread_disp_style == THREAD_STRUCT_FROM)){
6023 if(pith_opt_truncate_sfstr && (*pith_opt_truncate_sfstr)()){
6024 IFIELD_S *ourifield = NULL;
6026 if(ice && ice->ifield){
6027 /* move to last ifield, the one we're working on */
6028 for(ourifield = ice->ifield;
6029 ourifield && ourifield->next;
6030 ourifield = ourifield->next)
6034 if(ourifield && ourifield->width > 0)
6035 width = ourifield->width;
6038 if(width < 0)
6039 width = strsize-1;
6041 width = MIN(width, strsize-1);
6043 thdorig = thd = fetch_thread(idata->stream, idata->rawno);
6044 border = str + width;
6045 if(pith_opt_condense_thread_cue)
6046 width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
6047 thd && thd->next
6048 && get_lflag(idata->stream,
6049 NULL,idata->rawno,
6050 MN_COLL));
6052 fptr = str;
6054 if(thd)
6055 while(thd->parent && (thd = fetch_thread(idata->stream, thd->parent)))
6056 depth++;
6058 if(depth > 0){
6059 if(ps_global->thread_disp_style == THREAD_INDENT_FROM1)
6060 mult = 1;
6062 fptr += (mult*depth);
6063 for(thd = thdorig, p = str + mult*depth - mult;
6064 thd && thd->parent && p >= str;
6065 thd = fetch_thread(idata->stream, thd->parent), p -= mult){
6066 if(p + mult >= border && !q){
6067 if(width >= 4 && depth < 100){
6068 snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
6069 q = str + width-4;
6071 else if(width >= 5 && depth < 1000){
6072 snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
6073 q = str + width-5;
6075 else{
6076 snprintf(str, width+1, "%s", repeat_char(width, '.'));
6077 q = str;
6080 border = q;
6081 fptr = NULL;
6084 if(p + 1 < border){
6085 p[0] = p[1] = ' ';
6086 if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
6088 * WARNING!
6089 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
6090 * is ascii.
6092 if(thd == thdorig && !thd->branch)
6093 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
6094 else if(thd == thdorig || thd->branch)
6095 p[0] = '|';
6097 if(thd == thdorig)
6098 p[1] = '-';
6101 else if(p < border){
6102 p[0] = ' ';
6103 if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
6105 * WARNING!
6106 * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
6107 * is ascii.
6109 if(thd == thdorig && !thd->branch)
6110 p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
6111 else if(thd == thdorig || thd->branch)
6112 p[0] = '|';
6118 else
6119 fptr = str;
6121 if(fptr){
6122 strsize -= (fptr - str);
6123 switch(ctype){
6124 case iFromTo:
6125 case iFromToNotNews:
6126 if(!(addr = fetch_from(idata)) || address_is_us(addr, ps_global)){
6127 if(strsize-1 <= 4){
6128 strncpy(fptr, "To: ", strsize-1);
6129 fptr[strsize-1] = '\0';
6130 break;
6132 else{
6133 if((field = ((addr = fetch_to(idata))
6134 ? "To"
6135 : (addr = fetch_cc(idata))
6136 ? "Cc"
6137 : NULL))
6138 && set_index_addr(idata, field, addr, "To: ",
6139 strsize-1, fptr))
6140 break;
6142 if(ctype == iFromTo &&
6143 (newsgroups = fetch_newsgroups(idata)) &&
6144 *newsgroups){
6145 snprintf(fptr, strsize, "To: %-*.*s", strsize-1-4, strsize-1-4,
6146 newsgroups);
6147 break;
6150 /* else fall thru to From: */
6153 /* else fall thru to From: */
6155 if(idata->bogus)
6156 break;
6158 case iFrom:
6159 set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr);
6160 break;
6162 case iAddress:
6163 case iMailbox:
6164 if((addr = fetch_from(idata)) && addr->mailbox && addr->mailbox[0]){
6165 char *mb = NULL, *hst = NULL, *at = NULL;
6166 size_t len;
6168 mb = addr->mailbox;
6169 if(ctype == iAddress && addr->host && addr->host[0]
6170 && addr->host[0] != '.'){
6171 at = "@";
6172 hst = addr->host;
6175 len = strlen(mb);
6176 if(!at || strsize-1 <= len)
6177 snprintf(fptr, strsize, "%-*.*s", strsize-1, strsize-1, mb);
6178 else
6179 snprintf(fptr, strsize, "%s@%-*.*s", mb, strsize-1-len-1, strsize-1-len-1, hst);
6182 break;
6184 default:
6185 break;
6188 replace_tabs_by_space(str);
6193 * Set up the elements contained in field so that they take up the
6194 * whole field width. Data is assumed to be UTF-8.
6196 void
6197 set_ielem_widths_in_field(IFIELD_S *ifield)
6199 IELEM_S *ielem = NULL;
6200 int datawidth, fmtwidth;
6202 if(!ifield)
6203 return;
6205 fmtwidth = ifield->width;
6207 for(ielem = ifield->ielem; ielem && fmtwidth > 0; ielem = ielem->next){
6208 if(!ifield->leftadj && ielem->next){
6209 dprint((1, "set_ielem_widths_in_field(%d): right adjust with multiple elements, NOT SUPPOSED TO HAPPEN!\n", (int) ifield->ctype));
6210 assert(0);
6213 datawidth = (int) utf8_width(ielem->data);
6214 if(datawidth >= fmtwidth || !ielem->next){
6215 set_print_format(ielem, fmtwidth, ifield->leftadj);
6216 fmtwidth = 0;
6218 else{
6219 set_print_format(ielem, datawidth, ifield->leftadj);
6220 fmtwidth -= datawidth;
6227 * Simple hash function from K&R 2nd edition, p. 144.
6229 * This one is modified to never return 0 so we can use that as a special
6230 * value. Also, LINE_HASH_N fits in an unsigned long, so it too can be used
6231 * as a special value that can't be returned by line_hash.
6233 unsigned long
6234 line_hash(char *s)
6236 unsigned long hashval;
6238 for(hashval = 0; *s != '\0'; s++)
6239 hashval = *s + 31 * hashval;
6241 hashval = hashval % LINE_HASH_N;
6243 if(!hashval)
6244 hashval++;
6246 return(hashval);
6251 * Returns nonzero if considered hidden, 0 if not considered hidden.
6254 msgline_hidden(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int flags)
6256 int ret;
6258 if(flags & MH_ANYTHD){
6259 ret = ((any_lflagged(msgmap, MN_HIDE) > 0)
6260 && get_lflag(stream, msgmap, msgno, MN_HIDE));
6262 else if(flags & MH_THISTHD && THREADING() && sp_viewing_a_thread(stream)){
6263 ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
6264 || !get_lflag(stream, msgmap, msgno, MN_CHID2));
6266 else{
6267 if(THREADING() && sp_viewing_a_thread(stream)){
6268 ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
6269 || !get_lflag(stream, msgmap, msgno, MN_CHID2)
6270 || get_lflag(stream, msgmap, msgno, MN_CHID));
6272 else if(THRD_INDX()){
6274 * If this message is in the collapsed part of a thread,
6275 * it's hidden. It must be a top-level of a thread to be
6276 * considered visible. Even if it is top-level, it is only
6277 * visible if some message in the thread is not hidden.
6279 if(get_lflag(stream, msgmap, msgno, MN_CHID)) /* not top */
6280 ret = 1;
6281 else{
6282 unsigned long rawno;
6283 PINETHRD_S *thrd = NULL;
6285 rawno = mn_m2raw(msgmap, msgno);
6286 if(rawno)
6287 thrd = fetch_thread(stream, rawno);
6289 ret = !thread_has_some_visible(stream, thrd);
6292 else{
6293 ret = ((any_lflagged(msgmap, MN_HIDE | MN_CHID) > 0)
6294 && get_lflag(stream, msgmap, msgno, MN_HIDE | MN_CHID));
6298 dprint((10,
6299 "msgline_hidden(%ld): %s\n", msgno, ret ? "HID" : "VIS"));
6301 return(ret);
6305 void
6306 adjust_cur_to_visible(MAILSTREAM *stream, MSGNO_S *msgmap)
6308 long n, cur;
6309 int dir;
6311 cur = mn_get_cur(msgmap);
6313 /* if current is hidden, adjust */
6314 if(cur >= 1L && cur <= mn_get_total(msgmap)
6315 && msgline_hidden(stream, msgmap, cur, 0)){
6317 dir = mn_get_revsort(msgmap) ? -1 : 1;
6319 for(n = cur;
6320 ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6321 && (n >= 1L && n <= mn_get_total(msgmap))
6322 && msgline_hidden(stream, msgmap, n, 0);
6323 n -= dir)
6326 if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6327 && (n >= 1L && n <= mn_get_total(msgmap)))
6328 mn_reset_cur(msgmap, n);
6329 else{ /* no visible in that direction */
6330 for(n = cur;
6331 ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6332 && (n >= 1L && n <= mn_get_total(msgmap))
6333 && msgline_hidden(stream, msgmap, n, 0);
6334 n += dir)
6337 if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
6338 && (n >= 1L && n <= mn_get_total(msgmap)))
6339 mn_reset_cur(msgmap, n);
6340 /* else trouble! */
6346 void
6347 setup_for_index_index_screen(void)
6349 format_index_line = format_index_index_line;
6350 setup_header_widths = setup_index_header_widths;
6354 void
6355 setup_for_thread_index_screen(void)
6357 format_index_line = format_thread_index_line;
6358 setup_header_widths = setup_thread_header_widths;
6362 unsigned long
6363 ice_hash(ICE_S *ice)
6365 char buf[MAX_SCREEN_COLS+1];
6367 buf[0] = '\0';
6369 if(ice)
6370 simple_index_line(buf, sizeof(buf), ice, 0L);
6372 buf[sizeof(buf) - 1] = '\0';
6374 return(line_hash(buf));
6378 char *
6379 left_adjust(int width)
6381 return(format_str(width, 1));
6385 char *
6386 right_adjust(int width)
6388 return(format_str(width, 0));
6393 * Returns allocated and filled in format string.
6395 char *
6396 format_str(int width, int left)
6398 char *format;
6399 size_t len;
6401 len = PRINT_FORMAT_LEN(width,left) * sizeof(char);
6402 format = (char *) fs_get(len + 1);
6403 copy_format_str(width, left, format, len);
6404 format[len] = '\0';
6406 return(format);
6411 * Put the left or right adjusted format string of width width into
6412 * dest. Dest is of size n+1.
6414 char *
6415 copy_format_str(int width, int left, char *dest, int n)
6417 char *p;
6419 p = int2string(width);
6421 snprintf(dest, n+1, "%%%s%s.%ss", left ? "-" : "", p, p);
6423 dest[n] = '\0';
6425 return(dest);
6430 * Sets up the print_format string to be width wide with left or right
6431 * adjust. Takes care of memory freeing and allocation.
6433 void
6434 set_print_format(IELEM_S *ielem, int width, int leftadj)
6436 if(ielem){
6437 ielem->wid = width;
6439 if(ielem->print_format){
6440 /* is there enough room? */
6441 if(ielem->freeprintf < PRINT_FORMAT_LEN(width,leftadj)+1){
6442 fs_resize((void **) &ielem->print_format,
6443 (PRINT_FORMAT_LEN(width,leftadj)+1) * sizeof(char));
6444 ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);
6447 copy_format_str(width, leftadj, ielem->print_format,
6448 PRINT_FORMAT_LEN(width,leftadj));
6450 else{
6451 ielem->print_format = leftadj ? left_adjust(width)
6452 : right_adjust(width);
6453 ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);