/* ======================================================================== * Copyright 2013-2022 Eduardo Chappa * Copyright 2006-2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ #include "../pith/headers.h" #include "../pith/mailindx.h" #include "../pith/pineelt.h" #include "../pith/mailview.h" #include "../pith/flag.h" #include "../pith/icache.h" #include "../pith/msgno.h" #include "../pith/thread.h" #include "../pith/strlst.h" #include "../pith/status.h" #include "../pith/mailcmd.h" #include "../pith/search.h" #include "../pith/charset.h" #include "../pith/reply.h" #include "../pith/bldaddr.h" #include "../pith/addrstring.h" #include "../pith/news.h" #include "../pith/util.h" #include "../pith/pattern.h" #include "../pith/sequence.h" #include "../pith/color.h" #include "../pith/stream.h" #include "../pith/string.h" #include "../pith/send.h" #include "../pith/options.h" #include "../pith/ablookup.h" #ifdef _WINDOWS #include "../pico/osdep/mswin.h" #endif /* * pointers to formatting functions */ ICE_S *(*format_index_line)(INDEXDATA_S *); void (*setup_header_widths)(MAILSTREAM *); /* * pointer to optional load_overview functionality */ void (*pith_opt_paint_index_hline)(MAILSTREAM *, long, ICE_S *); /* * pointer to hook for saving index format state */ void (*pith_opt_save_index_state)(int); /* * hook to allow caller to insert cue that indicates a condensed * thread relationship cue */ int (*pith_opt_condense_thread_cue)(PINETHRD_S *, ICE_S *, char **, size_t *, int, int); int (*pith_opt_truncate_sfstr)(void); /* * Internal prototypes */ void setup_for_thread_index_screen(void); ICE_S *format_index_index_line(INDEXDATA_S *); ICE_S *format_thread_index_line(INDEXDATA_S *); int set_index_addr(INDEXDATA_S *, char *, ADDRESS *, char *, int, char *); int ctype_is_fixed_length(IndexColType); void setup_index_header_widths(MAILSTREAM *); void setup_thread_header_widths(MAILSTREAM *); int parse_index_format(char *, INDEX_COL_S **); int index_in_overview(MAILSTREAM *); ADDRESS *fetch_from(INDEXDATA_S *); ADDRESS *fetch_sender(INDEXDATA_S *); char *fetch_newsgroups(INDEXDATA_S *); char *fetch_subject(INDEXDATA_S *); char *fetch_date(INDEXDATA_S *); long fetch_size(INDEXDATA_S *); BODY *fetch_body(INDEXDATA_S *); char *fetch_firsttext(INDEXDATA_S *idata, int); char *fetch_header(INDEXDATA_S *idata, char *hdrname); void subj_str(INDEXDATA_S *, char *, size_t, SubjKW, int, int, ICE_S *); void key_str(INDEXDATA_S *, SubjKW, ICE_S *); void header_str(INDEXDATA_S *, HEADER_TOK_S *, ICE_S *); void prio_str(INDEXDATA_S *, IndexColType, ICE_S *); void from_str(IndexColType, INDEXDATA_S *, char *, size_t, ICE_S *); int day_of_week(struct date *); int day_of_year(struct date *); unsigned long ice_hash(ICE_S *); char *left_adjust(int); char *right_adjust(int); char *format_str(int, int); char *copy_format_str(int, int, char *, int); void set_print_format(IELEM_S *, int, int); void set_ielem_widths_in_field(IFIELD_S *); #define BIGWIDTH 2047 /*---------------------------------------------------------------------- Initialize the index_disp_format array in ps_global from this format string. Args: format -- the string containing the format tokens answer -- put the answer here, free first if there was a previous value here ----*/ void init_index_format(char *format, INDEX_COL_S **answer) { char *p; int i, w, monabb_width = 0, column = 0; /* * Record the fact that SCORE appears in some index format. This * is a heavy-handed approach. It will stick at 1 if any format ever * contains score during this session. This is ok since it will just * cause recalculation if wrong and these things rarely change much. */ if(!ps_global->a_format_contains_score && format && strstr(format, "SCORE")){ ps_global->a_format_contains_score = 1; /* recalculate need for scores */ scores_are_used(SCOREUSE_INVALID); } set_need_format_setup(ps_global->mail_stream); /* if custom format is specified, try it, else go with default */ if(!(format && *format && parse_index_format(format, answer))){ static INDEX_COL_S answer_default[] = { {iStatus, Fixed, 3}, {iMessNo, WeCalculate}, {iSDateTime24, WeCalculate}, {iFromTo, Percent, 33}, /* percent of rest */ {iSizeNarrow, WeCalculate}, {iSubjKey, Percent, 67}, {iNothing} }; if(*answer) free_index_format(answer); *answer = (INDEX_COL_S *)fs_get(sizeof(answer_default)); memcpy(*answer, answer_default, sizeof(answer_default)); } /* * Test to see how long the month abbreviations are. */ for(i = 1; i <= 12; i++){ p = month_abbrev_locale(i); monabb_width = MAX(utf8_width(p), monabb_width); } monabb_width = MIN(MAX(2, monabb_width), 5); /* * Fill in req_width's for WeCalculate items. */ for(column = 0; (*answer)[column].ctype != iNothing; column++){ /* don't use strftime if we're not trying to use the LC_TIME stuff */ if(F_ON(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){ switch((*answer)[column].ctype){ case iSDate: (*answer)[column].ctype = iS1Date; break; case iSDateTime: (*answer)[column].ctype = iSDateTimeS1; break; case iSDateTime24: (*answer)[column].ctype = iSDateTimeS124; break; default: break; } } if((*answer)[column].wtype == WeCalculate){ switch((*answer)[column].ctype){ case iPrio: case iPrioBang: case iAtt: (*answer)[column].req_width = 1; break; case iYear2Digit: case iDay: case iMon: case iDay2Digit: case iMon2Digit: case iArrow: case iKeyInit: (*answer)[column].req_width = 2; break; case iStatus: case iMessNo: case iInit: (*answer)[column].req_width = 3; break; case iYear: case iDayOrdinal: case iSIStatus: (*answer)[column].req_width = 4; break; case iTime24: case iTimezone: case iSizeNarrow: case iKey: (*answer)[column].req_width = 5; break; case iFStatus: case iIStatus: case iScore: (*answer)[column].req_width = 6; break; case iTime12: case iSTime: case iKSize: case iSize: case iPrioAlpha: (*answer)[column].req_width = 7; break; case iS1Date: case iS2Date: case iS3Date: case iS4Date: case iDateIsoS: case iSizeComma: (*answer)[column].req_width = 8; break; case iSTime24: (*answer)[column].req_width = 9; break; case iMonAbb: (*answer)[column].req_width = monabb_width; (*answer)[column].monabb_width = monabb_width; break; case iDayOfWeekAbb: { w = 0; /* * Test to see how long it is. */ for(i = 0; i < 7; i++){ p = day_abbrev_locale(i); w = MAX(utf8_width(p), w); } (*answer)[column].req_width = MIN(MAX(2, w), 5); } break; case iDate: (*answer)[column].req_width = monabb_width + 3; (*answer)[column].monabb_width = monabb_width; break; case iMonLong: { w = 0; /* * Test to see how long it is. */ for(i = 1; i <= 12; i++){ p = month_name_locale(i); w = MAX(utf8_width(p), w); } (*answer)[column].req_width = MIN(MAX(3, w), 12); } break; case iDayOfWeek: { w = 0; for(i = 0; i < 7; i++){ p = day_name_locale(i); w = MAX(utf8_width(p), w); } (*answer)[column].req_width = MIN(MAX(3, w), 12); } break; case iSDate: case iSDateTime: case iSDateTime24: case iPrefDate: case iPrefTime: case iPrefDateTime: { /* * Format a date to see how long it is. * Make it as least as long as "Yesterday". * We should really use the width of the longest * of the translated yesterdays and friends but... */ struct tm tm; int len = 20; char ss[100]; memset(&tm, 0, sizeof(tm)); tm.tm_year = 106; tm.tm_mon = 11; tm.tm_mday = 31; tm.tm_hour = 3; tm.tm_min = 23; tm.tm_wday = 3; switch((*answer)[column].ctype){ case iPrefTime: our_strftime(ss, sizeof(ss), "%X", &tm); break; case iPrefDateTime: our_strftime(ss, sizeof(ss), "%c", &tm); len = 32; break; default: our_strftime(ss, sizeof(ss), "%x", &tm); break; } (*answer)[column].req_width = MIN(MAX(9, utf8_width(ss)), len); } (*answer)[column].monabb_width = monabb_width; break; case iDescripSize: case iSDateIsoS: case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4: case iSDateTimeIsoS: case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4: case iSDateTimeIsoS24: case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424: /* * These SDates are 8 wide but they need to be 9 for "Yesterday". */ (*answer)[column].req_width = 9; break; case iDateIso: case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24: (*answer)[column].req_width = 10; break; case iLDate: (*answer)[column].req_width = 12; (*answer)[column].monabb_width = monabb_width; break; case iRDate: (*answer)[column].req_width = 16; break; default: break; } } } calc_extra_hdrs(); if(get_extra_hdrs()) (void) mail_parameters(NULL, SET_IMAPEXTRAHEADERS, (void *) get_extra_hdrs()); } void reset_index_format(void) { long rflags = ROLE_DO_OTHER; PAT_STATE pstate; PAT_S *pat; int we_set_it = 0; if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){ if(match_pattern(pat->patgrp, ps_global->mail_stream, NULL, NULL, NULL, SE_NOSERVER|SE_NOPREFETCH)) break; } if(pat && pat->action && !pat->action->bogus && pat->action->index_format){ we_set_it++; init_index_format(pat->action->index_format, &ps_global->index_disp_format); } } if(!we_set_it) init_index_format(ps_global->VAR_INDEX_FORMAT, &ps_global->index_disp_format); } void free_index_format(INDEX_COL_S **disp_format) { INDEX_COL_S *cdesc = NULL; if(disp_format && *disp_format){ for(cdesc = (*disp_format); cdesc->ctype != iNothing; cdesc++) if(cdesc->hdrtok) free_hdrtok(&cdesc->hdrtok); fs_give((void **) disp_format); } } HEADER_TOK_S * new_hdrtok(char *hdrname) { HEADER_TOK_S *hdrtok; hdrtok = (HEADER_TOK_S *) fs_get(sizeof(HEADER_TOK_S)); memset(hdrtok, 0, sizeof(HEADER_TOK_S)); hdrtok->hdrname = hdrname ? cpystr(hdrname) : NULL; hdrtok->fieldnum = 0; hdrtok->adjustment = Left; hdrtok->fieldsepcnt = 1; hdrtok->fieldseps = cpystr(" "); return(hdrtok); } void free_hdrtok(HEADER_TOK_S **hdrtok) { if(hdrtok && *hdrtok){ if((*hdrtok)->hdrname) fs_give((void **) &(*hdrtok)->hdrname); if((*hdrtok)->fieldseps) fs_give((void **) &(*hdrtok)->fieldseps); fs_give((void **) hdrtok); } } /* popular ones first to make it slightly faster */ static INDEX_PARSE_T itokens[] = { {"STATUS", iStatus, FOR_INDEX}, {"MSGNO", iMessNo, FOR_INDEX}, {"DATE", iDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"FROMORTO", iFromTo, FOR_INDEX}, {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX}, {"SIZE", iSize, FOR_INDEX}, {"SIZECOMMA", iSizeComma, FOR_INDEX}, {"SIZENARROW", iSizeNarrow, FOR_INDEX}, {"KSIZE", iKSize, FOR_INDEX}, {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SHORTSUBJECT", iShortSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"FULLSTATUS", iFStatus, FOR_INDEX}, {"IMAPSTATUS", iIStatus, FOR_INDEX}, {"SHORTIMAPSTATUS", iSIStatus, FOR_INDEX}, {"SUBJKEY", iSubjKey, FOR_INDEX}, {"SHORTSUBJKEY", iShortSubjKey, FOR_INDEX}, {"SUBJKEYINIT", iSubjKeyInit, FOR_INDEX}, {"SHORTSUBJKEYINIT",iShortSubjKeyInit, FOR_INDEX}, {"SUBJECTTEXT", iSubjectText, FOR_INDEX}, {"SUBJKEYTEXT", iSubjKeyText, FOR_INDEX}, {"SUBJKEYINITTEXT", iSubjKeyInitText, FOR_INDEX}, {"OPENINGTEXT", iOpeningText, FOR_INDEX}, {"OPENINGTEXTNQ", iOpeningTextNQ, FOR_INDEX}, {"KEY", iKey, FOR_INDEX}, {"KEYINIT", iKeyInit, FOR_INDEX}, {"DESCRIPSIZE", iDescripSize, FOR_INDEX}, {"ATT", iAtt, FOR_INDEX}, {"SCORE", iScore, FOR_INDEX}, {"PRIORITY", iPrio, FOR_INDEX}, {"PRIORITYALPHA", iPrioAlpha, FOR_INDEX}, {"PRIORITY!", iPrioBang, FOR_INDEX}, {"LONGDATE", iLDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SHORTDATE1", iS1Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SHORTDATE2", iS2Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SHORTDATE3", iS3Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SHORTDATE4", iS4Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"DATEISO", iDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SHORTDATEISO", iDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATE", iSDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTTIME", iSTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTTIME24", iSTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATEISO", iSDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATESHORTISO",iSDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATES1", iSDateS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATES2", iSDateS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATES3", iSDateS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATES4", iSDateS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIME", iSDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMEISO",iSDateTimeIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMESHORTISO",iSDateTimeIsoS,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMES1", iSDateTimeS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMES2", iSDateTimeS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMES3", iSDateTimeS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMES4", iSDateTimeS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIME24", iSDateTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMEISO24", iSDateTimeIso24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMESHORTISO24",iSDateTimeIsoS24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMES124", iSDateTimeS124, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMES224", iSDateTimeS224, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMES324", iSDateTimeS324, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SMARTDATETIMES424", iSDateTimeS424, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"TIME24", iTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"TIME12", iTime12, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"TIMEZONE", iTimezone, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"MONTHABBREV", iMonAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"DAYOFWEEKABBREV", iDayOfWeekAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"DAYOFWEEK", iDayOfWeek, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"FROM", iFrom, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"TO", iTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"SENDER", iSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"CC", iCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"RECIPS", iRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"NEWS", iNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"TOANDNEWS", iToAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"NEWSANDTO", iNewsAndTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"RECIPSANDNEWS", iRecipsAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"NEWSANDRECIPS", iNewsAndRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"MSGID", iMsgID, FOR_REPLY_INTRO|FOR_TEMPLATE}, {"CURNEWS", iCurNews, FOR_REPLY_INTRO|FOR_TEMPLATE}, {"DAYDATE", iRDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"PREFDATE", iPrefDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"PREFTIME", iPrefTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"PREFDATETIME", iPrefDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"DAY", iDay, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"DAYORDINAL", iDayOrdinal, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"DAY2DIGIT", iDay2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"MONTHLONG", iMonLong, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"MONTH", iMon, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"MONTH2DIGIT", iMon2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"YEAR", iYear, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"YEAR2DIGIT", iYear2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"ADDRESS", iAddress, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"MAILBOX", iMailbox, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"ROLENICK", iRoleNick, FOR_REPLY_INTRO|FOR_TEMPLATE}, {"INIT", iInit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"CURDATE", iCurDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURDATEISO", iCurDateIso, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURDATEISOS", iCurDateIsoS, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURTIME24", iCurTime24, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURTIME12", iCurTime12, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURDAY", iCurDay, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURDAY2DIGIT", iCurDay2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURDAYOFWEEK", iCurDayOfWeek, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURDAYOFWEEKABBREV", iCurDayOfWeekAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURMONTH", iCurMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURMONTH2DIGIT", iCurMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURMONTHLONG", iCurMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURMONTHABBREV", iCurMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURYEAR", iCurYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURYEAR2DIGIT", iCurYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURPREFDATE", iCurPrefDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURPREFTIME", iCurPrefTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"CURPREFDATETIME", iCurPrefDateTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"LASTMONTH", iLstMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"LASTMONTH2DIGIT", iLstMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"LASTMONTHLONG", iLstMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"LASTMONTHABBREV", iLstMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"LASTMONTHYEAR", iLstMonYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"LASTMONTHYEAR2DIGIT", iLstMonYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"LASTYEAR", iLstYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"LASTYEAR2DIGIT", iLstYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, {"HEADER", iHeader, FOR_INDEX}, {"TEXT", iText, FOR_INDEX}, {"ARROW", iArrow, FOR_INDEX}, {"NEWLINE", iNewLine, FOR_REPLY_INTRO}, {"CURSORPOS", iCursorPos, FOR_TEMPLATE}, {NULL, iNothing, FOR_NOTHING} }; INDEX_PARSE_T itokensinv[sizeof(itokens)/sizeof(itokens[0])]; void inverse_itokens(void) { INDEX_PARSE_T *pt; for (pt = itokens; pt->name; pt++) itokensinv[pt->ctype].ctype = pt - itokens; } INDEX_PARSE_T * itoken(int i) { return((i < sizeof(itokens) && itokens[i].name) ? &itokens[i] : NULL); } /* * Args txt -- The token being checked begins at the beginning * of txt. The end of the token is delimited by a null, or * white space, or an underscore if DELIM_USCORE is set, * or a left paren if DELIM_PAREN is set. * flags -- Flags contains the what_for value, and DELIM_ values. * * Returns A ptr to an INDEX_PARSE_T from itokens above, else NULL. */ INDEX_PARSE_T * itoktype(char *txt, int flags) { INDEX_PARSE_T *pt; char token[100 + 1]; char *v, *w; /* * Separate a copy of the possible token out of txt. */ v = txt; w = token; while(w < token+sizeof(token)-1 && *v && !(!(*v & 0x80) && isspace((unsigned char)*v)) && !(flags & DELIM_USCORE && *v == '_') && !(flags & DELIM_PAREN && *v == '(') && !(flags & DELIM_COLON && *v == ':')) *w++ = *v++; *w = '\0'; for(pt = itokens; pt->name; pt++) if(pt->what_for & flags && !strucmp(pt->name, token)) return(pt); return(NULL); } int parse_index_format(char *format_str, INDEX_COL_S **answer) { int i, column = 0; char *p, *q; INDEX_PARSE_T *pt; INDEX_COL_S cdesc[200]; /* plenty of temp storage for answer */ memset((void *)cdesc, 0, sizeof(cdesc)); p = format_str; while(p && *p && column < 200-1){ /* skip leading white space for next word */ p = skip_white_space(p); pt = itoktype(p, FOR_INDEX | DELIM_PAREN | DELIM_COLON); /* ignore unrecognized word */ if(!pt){ char c; for(q = p; *p && !isspace((unsigned char)*p); p++) ; if((c = *p) != '\0') *p++ = '\0'; dprint((1, "parse_index_format: unrecognized token: %s\n", q ? q : "?")); q_status_message1(SM_ORDER | SM_DING, 0, 3, _("Unrecognized word in index-format: %s"), q); if(c != '\0') *(p-1) = c; continue; } cdesc[column].ctype = pt->ctype; if(pt->ctype == iHeader || pt->ctype == iText){ /* * iHeader field has special syntax. * * HEADER:hdrname(width,fieldnum,field_separators,L_or_R) * * where width is the regular width or percentage width or * left out for default width, fieldnum defaults to 0 for * whole thing, 1 for first field, ... * and field_separators is a list of characters which separate * the fields. The whole parenthesized part is optional. If used * the arguments can be dropped from the right, so * * HEADER:hdrname or * HEADER:hdrname(10) or * HEADER:hdrname(10%) or * HEADER:hdrname(10,2) or * HEADER:hdrname(,2) or * HEADER:hdrname(10,2, ) or * HEADER:hdrname(10,2,\,:) or * HEADER:hdrname(10,2,\,:,R) * * iText field uses the hdrtok field for convenience. It has syntax * * TEXT:text or * TEXT:text(10) or * TEXT:text(10%) * * and the literal text goes into the index line. It is also special * because there is no 1 column space after this field. */ /* skip over name */ p += strlen(pt->name); /* look for header name */ if(*p == ':'){ char *w, hdrname[200]; hdrname[0] = '\0'; w = hdrname; p++; if(*p == '\"'){ /* quoted name */ p++; while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){ if(*p == '\\') p++; *w++ = *p++; } *w = '\0'; if(*p == '\"') p++; } else{ while(w < hdrname + sizeof(hdrname)-1 && !(!(*p & 0x80) && isspace((unsigned char)*p)) && *p != '(') *w++ = *p++; *w = '\0'; } if(hdrname[0]){ cdesc[column].hdrtok = new_hdrtok(hdrname); } else{ if(pt->ctype == iHeader){ dprint((1, "parse_index_token: HEADER should be followed by :hdrname\n")); q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname"); } else{ dprint((1, "parse_index_token: TEXT should be followed by :text\n")); q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text"); } } } else{ if(pt->ctype == iHeader){ dprint((1, "parse_index_token: HEADER should be followed by :hdrname, not %s\n", p)); q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname"); } else{ dprint((1, "parse_index_token: TEXT should be followed by :text, not %s\n", p)); q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text"); } /* skip over rest of bogus config */ while(!(!(*p & 0x80) && isspace((unsigned char)*p)) && *p != '(') p++; } } else{ /* skip over name and look for parens */ p += strlen(pt->name); } if(*p == '('){ p++; q = p; while(p && *p && isdigit((unsigned char) *p)) p++; if(pt->ctype == iHeader){ /* first argument is width or width percentage, like for others */ if(p && *p && (*p == ')' || *p == ',')){ if(p > q){ cdesc[column].wtype = Fixed; cdesc[column].req_width = atoi(q); } else{ cdesc[column].wtype = WeCalculate; cdesc[column].req_width = 0; } } else if(p && *p && *p == '%' && p > q){ cdesc[column].wtype = Percent; cdesc[column].req_width = atoi(q); p++; } else{ cdesc[column].wtype = WeCalculate; cdesc[column].req_width = 0; } /* optional 2nd argument is field number, 0 whole thing, 1, 2, ... */ if(p && *p && *p == ','){ p++; /* no space allowed between arguments */ if(*p && isdigit((unsigned char) *p)){ q = p; while(*p && isdigit((unsigned char) *p)) p++; cdesc[column].hdrtok->fieldnum = atoi(q); /* * Optional 3rd argument is field separators. * Comma is \, and backslash is \\. */ if(*p == ','){ int j; p++; /* don't use default */ if(*p && *p != ')' && *p != ',' && cdesc[column].hdrtok->fieldseps) cdesc[column].hdrtok->fieldseps[0] = '\0'; j = 0; if(*p == '\"' && strchr(p+1, '\"')){ p++; while(*p && *p != ')' && *p != '\"' && *p != ','){ if(cdesc[column].hdrtok->fieldseps) fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2); if(*p == '\\' && *(p+1)) p++; if(cdesc[column].hdrtok->fieldseps){ cdesc[column].hdrtok->fieldseps[j++] = *p++; cdesc[column].hdrtok->fieldseps[j] = '\0'; cdesc[column].hdrtok->fieldsepcnt = j; } } if(*p == '\"') p++; } else{ while(*p && *p != ')' && *p != ','){ if(cdesc[column].hdrtok->fieldseps) fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2); if(*p == '\\' && *(p+1)) p++; if(cdesc[column].hdrtok->fieldseps){ cdesc[column].hdrtok->fieldseps[j++] = *p++; cdesc[column].hdrtok->fieldseps[j] = '\0'; cdesc[column].hdrtok->fieldsepcnt = j; } } } /* optional 4th argument, left or right adjust */ if(*p == ','){ p++; if(*p == 'L' || *p == 'l') cdesc[column].hdrtok->adjustment = Left; else if(*p == 'R' || *p == 'r') cdesc[column].hdrtok->adjustment = Right; else{ dprint((1, "parse_index_token: HEADER 4th argument should be L or R, not\n", *p ? p : "")); q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 4th argument should be L or R"); } } } } else{ dprint((1, "parse_index_token: HEADER 2nd argument should be field number, not\n", *p ? p : "")); q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 2nd argument should be field number, a non-negative digit"); } } } else{ if(p && *p && *p == ')' && p > q){ cdesc[column].wtype = Fixed; cdesc[column].req_width = atoi(q); } else if(p && *p && *p == '%' && p > q){ cdesc[column].wtype = Percent; cdesc[column].req_width = atoi(q); } else{ cdesc[column].wtype = WeCalculate; cdesc[column].req_width = 0; } } } else{ /* if they left out width for iText we can figure it out */ if(pt->ctype == iText && cdesc[column].hdrtok && cdesc[column].hdrtok->hdrname){ cdesc[column].wtype = Fixed; cdesc[column].req_width = utf8_width(cdesc[column].hdrtok->hdrname); } else{ cdesc[column].wtype = WeCalculate; cdesc[column].req_width = 0; } } column++; /* skip text at end of word */ while(p && *p && !isspace((unsigned char)*p)) p++; } /* if, after all that, we didn't find anything recognizable, bitch */ if(!column){ dprint((1, "Completely unrecognizable index-format\n")); q_status_message(SM_ORDER | SM_DING, 0, 3, _("Configured \"index-format\" unrecognizable. Using default.")); return(0); } /* Finish with Nothing column */ cdesc[column].ctype = iNothing; /* free up old answer */ if(*answer) free_index_format(answer); /* allocate space for new answer */ *answer = (INDEX_COL_S *)fs_get((column+1)*sizeof(INDEX_COL_S)); memset((void *)(*answer), 0, (column+1)*sizeof(INDEX_COL_S)); /* copy answer to real place */ for(i = 0; i <= column; i++) (*answer)[i] = cdesc[i]; return(1); } /* * These types are basically fixed in width. * The order is slightly significant. The ones towards the front of the * list get space allocated sooner than the ones at the end of the list. */ static IndexColType fixed_ctypes[] = { iMessNo, iStatus, iFStatus, iIStatus, iSIStatus, iDate, iSDate, iSDateTime, iSDateTime24, iSTime, iSTime24, iLDate, iS1Date, iS2Date, iS3Date, iS4Date, iDateIso, iDateIsoS, iSDateIso, iSDateIsoS, iSDateS1, iSDateS2, iSDateS3, iSDateS4, iSDateTimeIso, iSDateTimeIsoS, iSDateTimeS1, iSDateTimeS2, iSDateTimeS3, iSDateTimeS4, iSDateTimeIso24, iSDateTimeIsoS24, iSDateTimeS124, iSDateTimeS224, iSDateTimeS324, iSDateTimeS424, iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize, iPrio, iPrioBang, iPrioAlpha, iInit, iAtt, iTime24, iTime12, iTimezone, iMonAbb, iYear, iYear2Digit, iDay2Digit, iMon2Digit, iDayOfWeekAbb, iScore, iMonLong, iDayOfWeek }; int ctype_is_fixed_length(IndexColType ctype) { int j; for(j = 0; ; j++){ if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes)) break; if(ctype == fixed_ctypes[j]) return 1; } return 0; } /*---------------------------------------------------------------------- Setup the widths of the various columns in the index display ----*/ void setup_index_header_widths(MAILSTREAM *stream) { int colspace; /* for reserving space between columns */ int j, some_to_calculate; int space_left, screen_width, fix; int keep_going, tot_pct, was_sl; long max_msgno; WidthType wtype; INDEX_COL_S *cdesc; max_msgno = mn_get_total(ps_global->msgmap); dprint((8, "=== setup_index_header_widths() ===\n")); clear_icache_flags(stream); screen_width = ps_global->ttyo->screen_cols; space_left = screen_width; some_to_calculate = 0; colspace = -1; /* * Calculate how many fields there are so we know how many spaces * between columns to reserve. Fill in Fixed widths now. Reserve * special case WeCalculate with non-zero req_widths before doing * Percent cases below. */ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++){ if(cdesc->wtype == Fixed){ cdesc->width = cdesc->req_width; if(cdesc->width > 0) colspace++; } else if(cdesc->wtype == Percent){ cdesc->width = 0; /* calculated later */ colspace++; } else{ /* WeCalculate */ cdesc->width = cdesc->req_width; /* reserve this for now */ some_to_calculate++; colspace++; } /* no space after iText */ if(cdesc->ctype == iText) colspace--; space_left -= cdesc->width; } colspace = MAX(colspace, 0); space_left -= colspace; /* space between columns */ ps_global->display_keywords_in_subject = 0; ps_global->display_keywordinits_in_subject = 0; /* * Set the actual lengths for the fixed width fields and set up * the left or right adjustment for everything. * There should be a case setting actual_length for all of the types * in fixed_ctypes. */ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++){ wtype = cdesc->wtype; if(cdesc->ctype == iSubjKey || cdesc->ctype == iSubjKeyText) ps_global->display_keywords_in_subject = 1; else if(cdesc->ctype == iSubjKeyInit || cdesc->ctype == iSubjKeyInitText) ps_global->display_keywordinits_in_subject = 1; if(wtype == WeCalculate || wtype == Percent || cdesc->width != 0){ switch(cdesc->ctype){ case iSDate: case iSDateIso: case iSDateIsoS: case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4: case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS: case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4: case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24: case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424: case iSTime: case iSTime24: set_format_includes_smartdate(stream); break; default: break; } if(ctype_is_fixed_length(cdesc->ctype)){ switch(cdesc->ctype){ case iPrio: case iPrioBang: case iAtt: cdesc->actual_length = 1; cdesc->adjustment = Left; break; case iYear2Digit: case iDay2Digit: case iMon2Digit: cdesc->actual_length = 2; cdesc->adjustment = Left; break; case iArrow: cdesc->actual_length = 2; cdesc->adjustment = Right; break; case iStatus: case iInit: cdesc->actual_length = 3; cdesc->adjustment = Left; break; case iMessNo: set_format_includes_msgno(stream); if(max_msgno < 1000) cdesc->actual_length = 3; else if(max_msgno < 10000) cdesc->actual_length = 4; else if(max_msgno < 100000) cdesc->actual_length = 5; else cdesc->actual_length = 6; cdesc->adjustment = Right; break; case iYear: case iSIStatus: cdesc->actual_length = 4; cdesc->adjustment = Left; break; case iTime24: case iTimezone: cdesc->actual_length = 5; cdesc->adjustment = Left; break; case iSizeNarrow: cdesc->actual_length = 5; cdesc->adjustment = Right; break; case iFStatus: case iIStatus: cdesc->actual_length = 6; cdesc->adjustment = Left; break; case iScore: cdesc->actual_length = 6; cdesc->adjustment = Right; break; case iTime12: case iSize: case iKSize: cdesc->actual_length = 7; cdesc->adjustment = Right; break; case iSTime: cdesc->actual_length = 7; cdesc->adjustment = Left; break; case iPrioAlpha: cdesc->actual_length = 7; cdesc->adjustment = Left; break; case iS1Date: case iS2Date: case iS3Date: case iS4Date: case iDateIsoS: cdesc->actual_length = 8; cdesc->adjustment = Left; break; case iSizeComma: cdesc->actual_length = 8; cdesc->adjustment = Right; break; case iSDate: case iSDateTime: case iSDateTime24: case iMonAbb: case iDayOfWeekAbb: case iDayOfWeek: case iDate: case iMonLong: cdesc->actual_length = cdesc->req_width; cdesc->adjustment = Left; break; case iSTime24: case iSDateIsoS: case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4: case iSDateTimeIsoS: case iSDateTimeIsoS24: case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4: case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424: case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24: if(cdesc->ctype == iSDateIso || cdesc->ctype == iSDateTimeIso || cdesc->ctype == iSDateTimeIso24) cdesc->actual_length = 10; else cdesc->actual_length = 9; cdesc->adjustment = Left; break; case iDescripSize: cdesc->actual_length = 9; cdesc->adjustment = Right; break; case iDateIso: cdesc->actual_length = 10; cdesc->adjustment = Left; break; case iLDate: cdesc->actual_length = 12; cdesc->adjustment = Left; break; default: alpine_panic("Unhandled fixed case in setup_index_header"); break; } } else if(cdesc->ctype == iHeader) cdesc->adjustment = cdesc->hdrtok ? cdesc->hdrtok->adjustment : Left; else cdesc->adjustment = Left; } } if(ps_global->display_keywords_in_subject) ps_global->display_keywordinits_in_subject = 0; /* if have reserved unneeded space for size, give it back */ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++) if(cdesc->ctype == iSize || cdesc->ctype == iKSize || cdesc->ctype == iSizeNarrow || cdesc->ctype == iSizeComma || cdesc->ctype == iDescripSize){ if(cdesc->actual_length == 0){ if((fix=cdesc->width) > 0){ /* had this reserved */ cdesc->width = 0; space_left += fix; } space_left++; /* +1 for space between columns */ } } /* * Calculate the field widths that are basically fixed in width. * Do them in this order in case we don't have enough space to go around. * The set of fixed_ctypes here is the same as the set where we * set the actual_lengths above. */ for(j = 0; space_left > 0 && some_to_calculate; j++){ if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes)) break; for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing && space_left > 0 && some_to_calculate; cdesc++) if(cdesc->ctype == fixed_ctypes[j] && cdesc->wtype == WeCalculate){ some_to_calculate--; fix = MIN(cdesc->actual_length - cdesc->width, space_left); cdesc->width += fix; space_left -= fix; } } /* * Fill in widths for Percent cases. If there are no more to calculate, * use the percentages as relative numbers and use the rest of the space, * else treat them as absolute percentages of the original avail screen. */ if(space_left > 0){ if(some_to_calculate){ int tot_requested = 0; /* * Requests are treated as percent of screen width. See if they * will all fit. If not, trim them back proportionately. */ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++){ if(cdesc->wtype == Percent){ /* The 2, 200, and +100 are because we're rounding */ fix = ((2*cdesc->req_width * (screen_width-colspace))+100) / 200; tot_requested += fix; } } if(tot_requested > space_left){ int multiplier = (100 * space_left) / tot_requested; for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing && space_left > 0; cdesc++){ if(cdesc->wtype == Percent){ /* The 2, 200, and +100 are because we're rounding */ fix = ((2*cdesc->req_width * (screen_width-colspace))+100) / 200; fix = (2 * fix * multiplier + 100) / 200; fix = MIN(fix, space_left); cdesc->width += fix; space_left -= fix; } } } else{ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing && space_left > 0; cdesc++){ if(cdesc->wtype == Percent){ /* The 2, 200, and +100 are because we're rounding */ fix = ((2*cdesc->req_width * (screen_width-colspace))+100) / 200; fix = MIN(fix, space_left); cdesc->width += fix; space_left -= fix; } } } } else{ tot_pct = 0; was_sl = space_left; /* add up total percentages requested */ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++) if(cdesc->wtype == Percent) tot_pct += cdesc->req_width; /* give relative weight to requests */ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing && space_left > 0 && tot_pct > 0; cdesc++){ if(cdesc->wtype == Percent){ fix = ((2*cdesc->req_width*was_sl)+tot_pct) / (2*tot_pct); fix = MIN(fix, space_left); cdesc->width += fix; space_left -= fix; } } } } /* split up rest, give twice as much to Subject */ keep_going = 1; while(space_left > 0 && keep_going){ keep_going = 0; for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing && space_left > 0; cdesc++){ if(cdesc->wtype == WeCalculate && !ctype_is_fixed_length(cdesc->ctype)){ keep_going++; cdesc->width++; space_left--; if(space_left > 0 && (cdesc->ctype == iSubject || cdesc->ctype == iShortSubject || cdesc->ctype == iSubjectText || cdesc->ctype == iSubjKey || cdesc->ctype == iShortSubjKey || cdesc->ctype == iSubjKeyText || cdesc->ctype == iSubjKeyInit || cdesc->ctype == iShortSubjKeyInit || cdesc->ctype == iSubjKeyInitText)){ cdesc->width++; space_left--; } } } } /* if still more, pad out percent's */ keep_going = 1; while(space_left > 0 && keep_going){ keep_going = 0; for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing && space_left > 0; cdesc++){ if(cdesc->wtype == Percent && !ctype_is_fixed_length(cdesc->ctype)){ keep_going++; cdesc->width++; space_left--; } } } /* if user made Fixed fields too big, give back space */ keep_going = 1; while(space_left < 0 && keep_going){ keep_going = 0; for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing && space_left < 0; cdesc++){ if(cdesc->wtype == Fixed && cdesc->width > 0){ keep_going++; cdesc->width--; space_left++; } } } if(pith_opt_save_index_state) (*pith_opt_save_index_state)(FALSE); } void setup_thread_header_widths(MAILSTREAM *stream) { clear_icache_flags(stream); if(pith_opt_save_index_state) (*pith_opt_save_index_state)(TRUE); } /* * load_overview - c-client call back to gather overview data * * Note: if we never get called, UID represents a hole * if we're passed a zero UID, totally bogus overview data * if we're passed a zero obuf, mostly bogus overview data */ void load_overview(MAILSTREAM *stream, imapuid_t uid, OVERVIEW *obuf, long unsigned int rawno) { if(obuf && rawno >= 1L && stream && rawno <= stream->nmsgs){ INDEXDATA_S idata; ICE_S *ice; memset(&idata, 0, sizeof(INDEXDATA_S)); idata.no_fetch = 1; /* * Only really load the thing if we've got an NNTP stream * otherwise we're just using mail_fetch_overview to load the * IMAP envelope cache with the specific set of messages * in a single RTT. */ idata.stream = stream; idata.rawno = rawno; idata.msgno = mn_raw2m(sp_msgmap(stream), idata.rawno); idata.size = obuf->optional.octets; idata.from = obuf->from; idata.date = obuf->date; idata.subject = obuf->subject; ice = (*format_index_line)(&idata); if(idata.bogus && ice){ if(THRD_INDX()){ if(ice->tice) clear_ice(&ice->tice); } else clear_ice(&ice); } else if(F_OFF(F_QUELL_NEWS_ENV_CB, ps_global) && (!THRD_INDX() || (ice && ice->tice)) && !msgline_hidden(stream, sp_msgmap(stream), idata.msgno, 0) && pith_opt_paint_index_hline){ (*pith_opt_paint_index_hline)(stream, idata.msgno, ice); } } } ICE_S * build_header_work(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, long int top_msgno, int msgcount, int *fetched) { ICE_S *ice, *ic; MESSAGECACHE *mc; long n, i, cnt, rawno, visible, limit = -1L; rawno = mn_m2raw(msgmap, msgno); /* cache hit? */ if(THRD_INDX()){ ice = fetch_ice(stream, rawno); if(!ice) return(NULL); if(ice->tice && ice->tice->ifield && ice->tice->color_lookup_done && ice->tice->widths_done){ #ifdef DEBUG char buf[MAX_SCREEN_COLS+1]; simple_index_line(buf, sizeof(buf), ice->tice, msgno); #endif dprint((9, "Hitt: Returning %p -> <%s (%d)\n", ice->tice, buf[0] ? buf : "?", buf[0] ? strlen(buf) : 0)); return(ice); } } else{ ice = fetch_ice(stream, rawno); if(!ice) return(NULL); if(ice->ifield && ice->color_lookup_done && ice->widths_done){ #ifdef DEBUG char buf[MAX_SCREEN_COLS+1]; simple_index_line(buf, sizeof(buf), ice, msgno); #endif dprint((9, "Hit: Returning %p -> <%s (%d)\n", ice, buf[0] ? buf : "?", buf[0] ? strlen(buf) : 0)); return(ice); } } /* * If we are in THRD_INDX() and the width changed we don't currently * have a method of fixing just the widths and print_format strings. * Instead, we clear the index cache entry and start over. */ if(THRD_INDX() && ice && ice->tice && ice->tice->ifield && !ice->tice->widths_done){ clear_ice(&ice->tice); } /* * Fetch everything we need to start filling in the index line * explicitly via mail_fetch_overview. On an nntp stream * this has the effect of building the index lines in the * load_overview callback. Under IMAP we're either getting * the envelope data via the imap_envelope callback or * preloading the cache. Either way, we're getting exactly * what we want rather than relying on linear lookahead sort * of prefetch... */ if(!(fetched && *fetched) && index_in_overview(stream) && ((THRD_INDX() && !(ice->tice && ice->tice->ifield)) || (!THRD_INDX() && !ice->ifield))){ char *seq; int count; MESSAGECACHE *mc; PINETHRD_S *thrd; if(fetched) (*fetched)++; /* clear sequence bits */ for(n = 1L; n <= stream->nmsgs; n++) if((mc = mail_elt(stream, n)) != NULL) mc->sequence = 0; /* * Light interesting bits * NOTE: not set above because m2raw's cheaper * than raw2m for every message */ /* * Unfortunately, it is expensive to calculate visible pages * in thread index if we are zoomed, so we don't try. */ if(THRD_INDX() && any_lflagged(msgmap, MN_HIDE)) visible = msgmap->visible_threads; else if(THREADING() && sp_viewing_a_thread(stream)){ /* * We know that all visible messages in the thread are marked * with MN_CHID2. */ for(visible = 0L, n = top_msgno; visible < msgcount && n <= mn_get_total(msgmap); n++){ if(!get_lflag(stream, msgmap, n, MN_CHID2)) break; if(!msgline_hidden(stream, msgmap, n, 0)) visible++; } } else visible = mn_get_total(msgmap) - any_lflagged(msgmap, MN_HIDE|MN_CHID); limit = MIN(visible, msgcount); if(THRD_INDX()){ count = i = 0; /* * First add the msgno we're asking for in case it * isn't visible. */ thrd = fetch_thread(stream, mn_m2raw(msgmap, msgno)); if(msgno <= mn_get_total(msgmap) && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){ count += mark_msgs_in_thread(stream, thrd, msgmap); } thrd = fetch_thread(stream, mn_m2raw(msgmap, top_msgno)); /* * Loop through visible threads, marking them for fetching. * Stop at end of screen or sooner if we run out of visible * threads. */ while(thrd){ n = mn_raw2m(msgmap, thrd->rawno); if(n >= msgno && n <= mn_get_total(msgmap) && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){ count += mark_msgs_in_thread(stream, thrd, msgmap); } if(++i >= limit) break; /* find next thread which is visible */ do{ if(mn_get_revsort(msgmap) && thrd->prevthd) thrd = fetch_thread(stream, thrd->prevthd); else if(!mn_get_revsort(msgmap) && thrd->nextthd) thrd = fetch_thread(stream, thrd->nextthd); else thrd = NULL; } while(thrd && msgline_hidden(stream, msgmap, mn_raw2m(msgmap, thrd->rawno), 0)); } } else{ count = i = 0; /* * First add the msgno we're asking for in case it * isn't visible. */ if(msgno > 0L && msgno <= mn_get_total(msgmap) && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,msgno)))) || !ic->ifield)){ if((thrd = fetch_thread(stream, rawno)) != NULL){ /* * If we're doing a MUTTLIKE display the index line * may depend on the thread parent, and grandparent, * and further back. So just fetch the whole thread * in that case. */ if(THREADING() && ps_global->thread_disp_style == THREAD_MUTTLIKE && thrd->top) thrd = fetch_thread(stream, thrd->top); count += mark_msgs_in_thread(stream, thrd, msgmap); } else if(rawno > 0L && rawno <= stream->nmsgs && (mc = mail_elt(stream,rawno)) && !mc->private.msg.env){ mc->sequence = 1; count++; } } n = top_msgno; while(1){ if(n >= msgno && n <= mn_get_total(msgmap) && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,n)))) || !ic->ifield)){ if((thrd = fetch_thread(stream, rawno)) != NULL){ /* * If we're doing a MUTTLIKE display the index line * may depend on the thread parent, and grandparent, * and further back. So just fetch the whole thread * in that case. */ if(THREADING() && ps_global->thread_disp_style == THREAD_MUTTLIKE && thrd->top) thrd = fetch_thread(stream, thrd->top); count += mark_msgs_in_thread(stream, thrd, msgmap); } else if(rawno > 0L && rawno <= stream->nmsgs && (mc = mail_elt(stream,rawno)) && !mc->private.msg.env){ mc->sequence = 1; count++; } } if(++i >= limit) break; /* find next n which is visible */ while(++n <= mn_get_total(msgmap) && msgline_hidden(stream, msgmap, n, 0)) ; } } if(count){ seq = build_sequence(stream, NULL, NULL); if(seq){ ps_global->dont_count_flagchanges = 1; mail_fetch_overview_sequence(stream, seq, (stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "imap")) ? NULL : load_overview); ps_global->dont_count_flagchanges = 0; fs_give((void **) &seq); } } /* * reassign ice from the cache as it may've been built * within the overview callback or it may have become stale * in the prior sequence bit setting loop ... */ rawno = mn_m2raw(msgmap, msgno); ice = fetch_ice(stream, rawno); if(!ice) return(NULL); } if((THRD_INDX() && !(ice->tice && ice->tice->ifield)) || (!THRD_INDX() && !ice->ifield)){ INDEXDATA_S idata; /* * With pre-fetching/callback-formatting done and no success, * fall into formatting the requested line... */ memset(&idata, 0, sizeof(INDEXDATA_S)); idata.stream = stream; idata.msgno = msgno; idata.rawno = mn_m2raw(msgmap, msgno); if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs && (mc = mail_elt(stream, idata.rawno))){ idata.size = mc->rfc822_size; index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno)); } else idata.bogus = 2; ice = (*format_index_line)(&idata); if(!ice) return(NULL); } /* * If needed, reset the print_format strings so that they add up to * the right total width. The reset width functionality isn't implemented * for THRD_INDX() so we are just doing a complete rebuild in that * case. This is driven by the clear_ice() call in clear_index_cache_ent() * so it should never be the case that THRD_INDX() is true and only * widths_done needs to be fixed. */ if((!THRD_INDX() && ice->ifield && !ice->widths_done)){ ICE_S *working_ice; IFIELD_S *ifield; INDEX_COL_S *cdesc; if(need_format_setup(stream)) setup_header_widths(stream); if(THRD_INDX()) working_ice = ice ? ice->tice : NULL; else working_ice = ice; if(working_ice){ /* * First fix the ifield widths. The cdescs with nonzero widths * should correspond to the ifields that are defined. */ ifield = working_ice->ifield; for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing && ifield; cdesc++){ if(cdesc->width){ if(cdesc->ctype != ifield->ctype){ dprint((1, "build_header_work(%ld): cdesc->ctype=%d != ifield->ctype=%d NOT SUPPOSED TO HAPPEN!\n", msgno, (int) cdesc->ctype, (int) ifield->ctype)); assert(0); } ifield->width = cdesc->width; ifield = ifield->next; } } /* fix the print_format strings and widths */ for(ifield = working_ice->ifield; ifield; ifield = ifield->next) set_ielem_widths_in_field(ifield); working_ice->widths_done = 1; } } if(THRD_INDX() && ice->tice) ice->tice->color_lookup_done = 1; /* * Look for a color for this line (and other lines in the current * view). This does a SEARCH for each role which has a color until * it finds a match. This will be satisfied by the c-client * cache created by the mail_fetch_overview above if it is a header * search. */ if(!THRD_INDX() && !ice->color_lookup_done){ COLOR_PAIR *linecolor; SEARCHSET *ss, *s; ICE_S *ic; PAT_STATE *pstate = NULL; if(pico_usingcolor()){ if(limit < 0L){ if(THREADING() && sp_viewing_a_thread(stream)){ for(visible = 0L, n = top_msgno; visible < msgcount && n <= mn_get_total(msgmap); n++){ if(!get_lflag(stream, msgmap, n, MN_CHID2)) break; if(!msgline_hidden(stream, msgmap, n, 0)) visible++; } } else visible = mn_get_total(msgmap) - any_lflagged(msgmap, MN_HIDE|MN_CHID); limit = MIN(visible, msgcount); } /* clear sequence bits */ for(n = 1L; n <= stream->nmsgs; n++) if((mc = mail_elt(stream, n)) != NULL) mc->sequence = 0; cnt = i = 0; n = top_msgno; while(1){ if(n >= msgno && n <= mn_get_total(msgmap) && (!(ic=fetch_ice(stream,(rawno = mn_m2raw(msgmap, n)))) || !ic->color_lookup_done)){ if(rawno >= 1L && rawno <= stream->nmsgs && (mc = mail_elt(stream, rawno))){ mc->sequence = 1; cnt++; } } if(++i >= limit) break; /* find next n which is visible */ while(++n <= mn_get_total(msgmap) && msgline_hidden(stream, msgmap, n, 0)) ; } /* * Why is there a loop here? The first call to get_index_line_color * will return a set of messages which match one of the roles. * Then, we eliminate those messages from the search set and try * again. This time we'd get past that role and into a different * role. Because of that, we hang onto the state and don't reset * to the first_pattern on the second and subsequent times * through the loop, avoiding fruitless match_pattern calls in * get_index_line_color. * Before the first call, pstate should be set to NULL. */ while(cnt > 0L){ ss = build_searchset(stream); if(ss){ int colormatch; linecolor = NULL; colormatch = get_index_line_color(stream, ss, &pstate, &linecolor); /* * Assign this color to all matched msgno's and * turn off the sequence bit so we won't check * for them again. */ if(colormatch){ for(s = ss; s; s = s->next){ for(n = s->first; n <= s->last; n++){ if(n >= 1L && n <= stream->nmsgs && (mc = mail_elt(stream, n)) && mc->searched){ cnt--; mc->sequence = 0; ic = fetch_ice(stream, n); if(ic){ ic->color_lookup_done = 1; if(linecolor) ic->linecolor = new_color_pair(linecolor->fg, linecolor->bg); } } } } if(linecolor) free_color_pair(&linecolor); } else{ /* have to mark the rest of the lookups done */ for(s = ss; s && cnt > 0; s = s->next){ for(n = s->first; n <= s->last && cnt > 0; n++){ if(n >= 1L && n <= stream->nmsgs && (mc = mail_elt(stream, n)) && mc->sequence){ cnt--; ic = fetch_ice(stream, n); if(ic) ic->color_lookup_done = 1; } } } /* just making sure */ cnt = 0L; } mail_free_searchset(&ss); } else cnt = 0L; } ice = fetch_ice(stream, mn_m2raw(msgmap, msgno)); } else ice->color_lookup_done = 1; } return(ice); /* Return formatted index data */ } int day_of_week(struct date *d) { int m, y; m = d->month; y = d->year; if(m <= 2){ m += 9; y--; } else m -= 3; /* March is month 0 */ return((d->day+2+((7+31*m)/12)+y+(y/4)+(y/400)-(y/100))%7); } static int daytab[2][13] = { {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; int day_of_year(struct date *d) { int i, leap, doy; if(d->year <= 0 || d->month < 1 || d->month > 12) return(-1); doy = d->day; leap = (d->year%4 == 0 && d->year%100 != 0) || d->year%400 == 0; for(i = 1; i < d->month; i++) doy += daytab[leap][i]; return(doy); } /*---------------------------------------------------------------------- Format a string summarizing the message header for index on screen Args: buffer -- buffer to place formatted line idata -- snot it takes to format the line Result: returns pointer given buffer IF entry formatted else NULL if there was a problem (but the buffer is still suitable for display) ----*/ ICE_S * format_index_index_line(INDEXDATA_S *idata) { char str[BIGWIDTH+1], to_us, status, *field, *p, *newsgroups; int i, collapsed = 0, start, fromfield; long l, score; BODY *body = NULL; MESSAGECACHE *mc = NULL; ADDRESS *addr, *toaddr, *ccaddr, *last_to; PINETHRD_S *thrd = NULL; INDEX_COL_S *cdesc = NULL; ICE_S *ice, **icep; IFIELD_S *ifield; IELEM_S *ielem; COLOR_PAIR *color = NULL; struct variable *vars = ps_global->vars; dprint((8, "=== format_index_line(msgno=%ld,rawno=%ld) ===\n", idata ? idata->msgno : -1, idata ? idata->rawno : -1)); ice = fetch_ice(idata->stream, idata->rawno); if(!ice) return(NULL); free_ifield(&ice->ifield); /* * Operate on a temporary copy of ice. The reason for this * is that we may end up causing a pine_mail_fetchenvelope() call * (e.g., in to_us_symbol_for_thread()) that causes an mm_flags() * and mm_flags may do a clear_ice(), freeing the ice we are working * on out from under us. We try to fetch everything we need in * build_header_work() but c-client will short-circuit our request * if we already got the raw header for some reason. One possible * reason is a categorizer command in a filter. In that case * we still need a fetch fast to get the rest of the envelope data. */ ice = copy_ice(ice); /* is this a collapsed thread index line? */ if(!idata->bogus && THREADING()){ thrd = fetch_thread(idata->stream, idata->rawno); collapsed = thrd && thrd->next && get_lflag(idata->stream, NULL, idata->rawno, MN_COLL); } /* calculate contents of the required fields */ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++) if(cdesc->width){ memset(str, 0, sizeof(str)); ifield = new_ifield(&ice->ifield); ifield->ctype = cdesc->ctype; ifield->width = cdesc->width; fromfield = 0; if(idata->bogus){ if(cdesc->ctype == iMessNo) snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " "); else if(idata->bogus < 2 && (cdesc->ctype == iSubject || cdesc->ctype == iShortSubject || cdesc->ctype == iSubjectText || cdesc->ctype == iSubjKey || cdesc->ctype == iShortSubjKey || cdesc->ctype == iSubjKeyText || cdesc->ctype == iSubjKeyInit || cdesc->ctype == iShortSubjKeyInit || cdesc->ctype == iSubjKeyInitText)) snprintf(str, sizeof(str), "%s", _("[ No Message Text Available ]")); } else switch(cdesc->ctype){ case iStatus: to_us = status = ' '; if(collapsed){ thrd = fetch_thread(idata->stream, idata->rawno); to_us = to_us_symbol_for_thread(idata->stream, thrd, 1); status = status_symbol_for_thread(idata->stream, thrd, cdesc->ctype); } else{ if(idata->rawno > 0L && idata->rawno <= idata->stream->nmsgs && (mc=mail_elt(idata->stream,idata->rawno)) && mc->flagged) to_us = '*'; /* simple */ else if(!IS_NEWS(idata->stream)){ for(addr = fetch_to(idata); addr; addr = addr->next) if(address_is_us(addr, ps_global)){ ice->to_us = 1; if(to_us == ' ') to_us = '+'; break; } if(to_us != '+' && resent_to_us(idata)){ ice->to_us = 1; if(to_us == ' ') to_us = '+'; } if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global)) for(addr = fetch_cc(idata); addr; addr = addr->next) if(address_is_us(addr, ps_global)){ ice->cc_us = 1; to_us = '-'; break; } } status = (!idata->stream || !IS_NEWS(idata->stream) || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)) ? 'N' : ' '; if(mc->seen) status = ' '; if(user_flag_is_set(idata->stream, idata->rawno, FORWARDED_FLAG)) status = 'F'; if(mc->answered) status = 'A'; if(mc->deleted) status = 'D'; } snprintf(str, sizeof(str), "%c %c", to_us, status); ifield->leftadj = 1; for(i = 0; i < 3; i++){ ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->data = (char *) fs_get(2 * sizeof(char)); ielem->data[0] = str[i]; ielem->data[1] = '\0'; ielem->datalen = 1; set_print_format(ielem, 1, ifield->leftadj); } if(pico_usingcolor()){ if(str[0] == '*'){ if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){ ielem = ifield->ielem; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR); } } else if(str[0] == '+' || str[0] == '-'){ if(VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){ ielem = ifield->ielem; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR); } } if(str[2] == 'D'){ if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){ ielem = ifield->ielem->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR); } } else if(str[2] == 'A'){ if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){ ielem = ifield->ielem->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR); } } else if(str[2] == 'F'){ if(VAR_IND_FWD_FORE_COLOR && VAR_IND_FWD_BACK_COLOR){ ielem = ifield->ielem->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_FWD_FORE_COLOR, VAR_IND_FWD_BACK_COLOR); } } else if(str[2] == 'N'){ if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){ ielem = ifield->ielem->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR); } } } break; case iFStatus: case iIStatus: case iSIStatus: { char new, answered, deleted, flagged; if(collapsed){ thrd = fetch_thread(idata->stream, idata->rawno); to_us = to_us_symbol_for_thread(idata->stream, thrd, 0); } else{ to_us = ' '; if(!IS_NEWS(idata->stream)){ for(addr = fetch_to(idata); addr; addr = addr->next) if(address_is_us(addr, ps_global)){ to_us = '+'; break; } if(to_us == ' ' && resent_to_us(idata)) to_us = '+'; if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global)) for(addr = fetch_cc(idata); addr; addr = addr->next) if(address_is_us(addr, ps_global)){ to_us = '-'; break; } } } new = answered = deleted = flagged = ' '; if(collapsed){ unsigned long save_branch, cnt, tot_in_thrd; /* * Branch is a sibling, not part of the thread, so * don't consider it when displaying this line. */ save_branch = thrd->branch; thrd->branch = 0L; tot_in_thrd = count_flags_in_thread(idata->stream, thrd, F_NONE); cnt = count_flags_in_thread(idata->stream, thrd, F_DEL); if(cnt) deleted = (cnt == tot_in_thrd) ? 'D' : 'd'; cnt = count_flags_in_thread(idata->stream, thrd, F_ANS); if(cnt) answered = (cnt == tot_in_thrd) ? 'A' : 'a'; /* no lower case *, same thing for some or all */ if(count_flags_in_thread(idata->stream, thrd, F_FLAG)) flagged = '*'; new = status_symbol_for_thread(idata->stream, thrd, cdesc->ctype); thrd->branch = save_branch; } else{ mc = (idata->rawno > 0L && idata->stream && idata->rawno <= idata->stream->nmsgs) ? mail_elt(idata->stream, idata->rawno) : NULL; if(mc && mc->valid){ if(cdesc->ctype == iIStatus || cdesc->ctype == iSIStatus){ if(mc->recent) new = mc->seen ? 'R' : 'N'; else if (!mc->seen) new = 'U'; } else if(!mc->seen && (!IS_NEWS(idata->stream) || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))) new = 'N'; if(mc->answered) answered = 'A'; if(mc->deleted) deleted = 'D'; if(mc->flagged) flagged = '*'; } } snprintf(str, sizeof(str), "%c %c%c%c%c", to_us, flagged, new, answered, deleted); if(cdesc->ctype == iSIStatus) start = 2; else start = 0; ifield->leftadj = 1; for(i = start; i < 6; i++){ ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->data = (char *) fs_get(2 * sizeof(char)); ielem->data[0] = str[i]; ielem->data[1] = '\0'; ielem->datalen = 1; set_print_format(ielem, 1, ifield->leftadj); } if(pico_usingcolor()){ if(str[0] == '+' || str[0] == '-'){ if(start == 0 && VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){ ielem = ifield->ielem; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR); } } if(str[2] == '*'){ if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){ if(start == 2) ielem = ifield->ielem; else ielem = ifield->ielem->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR); } } if(str[3] == 'N' || str[3] == 'n'){ if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){ if(start == 2) ielem = ifield->ielem->next; else ielem = ifield->ielem->next->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR); } } else if(str[3] == 'R' || str[3] == 'r'){ if(VAR_IND_REC_FORE_COLOR && VAR_IND_REC_BACK_COLOR){ if(start == 2) ielem = ifield->ielem->next; else ielem = ifield->ielem->next->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_REC_FORE_COLOR, VAR_IND_REC_BACK_COLOR); } } else if(str[3] == 'U' || str[3] == 'u'){ if(VAR_IND_UNS_FORE_COLOR && VAR_IND_UNS_BACK_COLOR){ if(start == 2) ielem = ifield->ielem->next; else ielem = ifield->ielem->next->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_UNS_FORE_COLOR, VAR_IND_UNS_BACK_COLOR); } } if(str[4] == 'A' || str[4] == 'a'){ if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){ if(start == 2) ielem = ifield->ielem->next->next; else ielem = ifield->ielem->next->next->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR); } } if(str[5] == 'D' || str[5] == 'd'){ if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){ if(start == 2) ielem = ifield->ielem->next->next->next; else ielem = ifield->ielem->next->next->next->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR); } } } } break; case iMessNo: /* * This is a special case. The message number is * generated on the fly in the painting routine. * But the data array is allocated here in case it * is useful for the paint routine. */ snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " "); break; case iArrow: snprintf(str, sizeof(str), "%-*.*s", ifield->width, ifield->width, " "); if(VAR_IND_ARR_FORE_COLOR && VAR_IND_ARR_BACK_COLOR){ ifield->leftadj = 1; ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->data = cpystr(str); ielem->datalen = strlen(str); set_print_format(ielem, ifield->width, ifield->leftadj); ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_ARR_FORE_COLOR, VAR_IND_ARR_BACK_COLOR); } break; case iScore: score = get_msg_score(idata->stream, idata->rawno); if(score == SCORE_UNDEF){ SEARCHSET *ss = NULL; ss = mail_newsearchset(); ss->first = ss->last = (unsigned long) idata->rawno; if(ss){ /* * This looks like it might be expensive to get the * score for each message when needed but it shouldn't * be too bad because we know we have the envelope * data cached. We can't calculate all of the scores * we need for the visible messages right here in * one fell swoop because we don't have the other * envelopes yet. And we can't get the other * envelopes at this point because we may be in * the middle of a c-client callback (pine_imap_env). * (Actually we could, because we know whether or * not we're in the callback because of the no_fetch * parameter.) * We have another problem if the score rules depend * on something other than envelope data. I guess they * only do that if they have an alltext (search the * text of the message) definition. So, we're going * to pass no_fetch to calculate_scores so that it * can return an error if we need the text data but * can't get it because of no_fetch. Setting bogus * will cause us to do the scores calculation later * when we are no longer in the callback. */ idata->bogus = (calculate_some_scores(idata->stream, ss, idata->no_fetch) == 0) ? 1 : 0; score = get_msg_score(idata->stream, idata->rawno); mail_free_searchset(&ss); } } snprintf(str, sizeof(str), "%ld", score != SCORE_UNDEF ? score : 0L); break; case iDate: case iMonAbb: case iLDate: case iSDate: case iSTime: case iSTime24: case iS1Date: case iS2Date: case iS3Date: case iS4Date: case iDateIso: case iDateIsoS: case iTime24: case iTime12: case iSDateIsoS: case iSDateIso: case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4: case iSDateTime: case iSDateTimeIsoS: case iSDateTimeIso: case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4: case iSDateTime24: case iSDateTimeIsoS24: case iSDateTimeIso24: case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424: case iTimezone: case iYear: case iYear2Digit: case iRDate: case iDay: case iDay2Digit: case iMon2Digit: case iDayOrdinal: case iMon: case iMonLong: case iDayOfWeekAbb: case iDayOfWeek: case iPrefDate: case iPrefTime: case iPrefDateTime: date_str(fetch_date(idata), cdesc->ctype, 0, str, sizeof(str), cdesc->monabb_width); break; case iFromTo: case iFromToNotNews: case iFrom: case iAddress: case iMailbox: fromfield++; from_str(cdesc->ctype, idata, str, sizeof(str), ice); break; case iTo: if(((field = ((addr = fetch_to(idata)) ? "To" : (addr = fetch_cc(idata)) ? "Cc" : NULL)) && !set_index_addr(idata, field, addr, NULL, BIGWIDTH, str)) || !field) if((newsgroups = fetch_newsgroups(idata)) != NULL) snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, newsgroups); break; case iCc: set_index_addr(idata, "Cc", fetch_cc(idata), NULL, BIGWIDTH, str); break; case iRecips: toaddr = fetch_to(idata); ccaddr = fetch_cc(idata); for(last_to = toaddr; last_to && last_to->next; last_to = last_to->next) ; /* point end of to list temporarily at cc list */ if(last_to) last_to->next = ccaddr; set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str); if(last_to) last_to->next = NULL; break; case iSender: fromfield++; if((addr = fetch_sender(idata)) != NULL) set_index_addr(idata, "Sender", addr, NULL, BIGWIDTH, str); break; case iInit: {ADDRESS *addr; if((addr = fetch_from(idata)) && addr->personal){ char *name, *initials = NULL; name = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, addr->personal); if(name == addr->personal){ strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1); tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0'; name = (char *) tmp_20k_buf; } if(name && *name){ initials = reply_quote_initials(name); snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, initials); } } } break; case iSize: /* 0 ... 9999 */ if((l = fetch_size(idata)) < 10*1000L) snprintf(str, sizeof(str), "(%lu)", l); /* 10K ... 999K */ else if(l < 1000L*1000L - 1000L/2){ l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L); snprintf(str, sizeof(str), "(%luK)", l); } /* 1.0M ... 99.9M */ else if(l < 1000L*100L*1000L - 100L*1000L/2){ l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2) ? 1L : 0L); snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L); } /* 100M ... 2000M */ else if(l <= 2*1000L*1000L*1000L){ l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2) ? 1L : 0L); snprintf(str, sizeof(str), "(%luM)", l); } else snprintf(str, sizeof(str), "(HUGE!)"); break; case iSizeComma: /* 0 ... 99,999 */ if((l = fetch_size(idata)) < 100*1000L) snprintf(str, sizeof(str), "(%s)", comatose(l)); /* 100K ... 9,999K */ else if(l < 10L*1000L*1000L - 1000L/2){ l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L); snprintf(str, sizeof(str), "(%sK)", comatose(l)); } /* 10.0M ... 999.9M */ else if(l < 1000L*1000L*1000L - 100L*1000L/2){ l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2) ? 1L : 0L); snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L); } /* 1,000M ... 2,000M */ else if(l <= 2*1000L*1000L*1000L){ l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2) ? 1L : 0L); snprintf(str, sizeof(str), "(%sM)", comatose(l)); } else snprintf(str, sizeof(str), "(HUGE!)"); break; case iSizeNarrow: /* 0 ... 999 */ if((l = fetch_size(idata)) < 1000L) snprintf(str, sizeof(str), "(%lu)", l); /* 1K ... 99K */ else if(l < 100L*1000L - 1000L/2){ l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L); snprintf(str, sizeof(str), "(%luK)", l); } /* .1M ... .9M */ else if(l < 1000L*1000L - 100L*1000L/2){ l = l/(100L*1000L) + (l%(100L*1000L) >= 100L*1000L/2 ? 1L : 0L); snprintf(str, sizeof(str), "(.%luM)", l); } /* 1M ... 99M */ else if(l < 1000L*100L*1000L - 1000L*1000L/2){ l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2) ? 1L : 0L); snprintf(str, sizeof(str), "(%luM)", l); } /* .1G ... .9G */ else if(l < 1000L*1000L*1000L - 100L*1000L*1000L/2){ l = l/(100L*1000L*1000L) + (l%(100L*1000L*1000L) >= (100L*1000L*1000L/2) ? 1L : 0L); snprintf(str, sizeof(str), "(.%luG)", l); } /* 1G ... 2G */ else if(l <= 2*1000L*1000L*1000L){ l = l/(1000L*1000L*1000L) + (l%(1000L*1000L*1000L) >= (1000L*1000L*1000L/2) ? 1L : 0L); snprintf(str, sizeof(str), "(%luG)", l); } else snprintf(str, sizeof(str), "(HUGE!)"); break; /* From Carl Jacobsen */ case iKSize: l = fetch_size(idata); l = (l / 1024L) + (l % 1024L != 0 ? 1 : 0); if(l < 1024L) { /* 0k .. 1023k */ snprintf(str, sizeof(str), "(%luk)", l); } else if (l < 100L * 1024L){ /* 1.0M .. 99.9M */ snprintf(str, sizeof(str), "(%lu.M)", (l * 10L) / 1024L); if ((p = strchr(str, '.')) != NULL) { p--; p[1] = p[0]; p[0] = '.'; /* swap last digit & . */ } } else if (l <= 2L * 1024L * 1024L) { /* 100M .. 2048 */ snprintf(str, sizeof(str), "(%luM)", l / 1024L); } else { snprintf(str, sizeof(str), "(HUGE!)"); } break; case iDescripSize: if((body = fetch_body(idata)) != NULL) switch(body->type){ case TYPETEXT: { mc = (idata->rawno > 0L && idata->stream && idata->rawno <= idata->stream->nmsgs) ? mail_elt(idata->stream, idata->rawno) : NULL; if(mc && mc->rfc822_size < 6000) snprintf(str, sizeof(str), "(short )"); else if(mc && mc->rfc822_size < 25000) snprintf(str, sizeof(str), "(medium )"); else if(mc && mc->rfc822_size < 100000) snprintf(str, sizeof(str), "(long )"); else snprintf(str, sizeof(str), "(huge )"); } break; case TYPEMULTIPART: if(strucmp(body->subtype, "MIXED") == 0){ int x; x = body->nested.part ? body->nested.part->body.type : TYPETEXT + 1000; switch(x){ case TYPETEXT: if(body->nested.part->body.size.bytes < 6000) snprintf(str, sizeof(str), "(short+ )"); else if(body->nested.part->body.size.bytes < 25000) snprintf(str, sizeof(str), "(medium+)"); else if(body->nested.part->body.size.bytes < 100000) snprintf(str, sizeof(str), "(long+ )"); else snprintf(str, sizeof(str), "(huge+ )"); break; default: snprintf(str, sizeof(str), "(multi )"); break; } } else if(strucmp(body->subtype, "DIGEST") == 0) snprintf(str, sizeof(str), "(digest )"); else if(strucmp(body->subtype, "ALTERNATIVE") == 0) snprintf(str, sizeof(str), "(mul/alt)"); else if(strucmp(body->subtype, "PARALLEL") == 0) snprintf(str, sizeof(str), "(mul/par)"); else snprintf(str, sizeof(str), "(multi )"); break; case TYPEMESSAGE: snprintf(str, sizeof(str), "(message)"); break; case TYPEAPPLICATION: snprintf(str, sizeof(str), "(applica)"); break; case TYPEAUDIO: snprintf(str, sizeof(str), "(audio )"); break; case TYPEIMAGE: snprintf(str, sizeof(str), "(image )"); break; case TYPEVIDEO: snprintf(str, sizeof(str), "(video )"); break; default: snprintf(str, sizeof(str), "(other )"); break; } break; case iAtt: str[0] = SPACE; str[1] = '\0'; if((body = fetch_body(idata)) && body->type == TYPEMULTIPART && strucmp(body->subtype, "ALTERNATIVE") != 0){ PART *part; int atts = 0; part = body->nested.part; /* 1st part, don't count */ while(part && part->next && atts < 10){ atts++; part = part->next; } if(atts > 9) str[0] = '*'; else if(atts > 0) str[0] = '0' + atts; } break; case iSubject: subj_str(idata, str, sizeof(str), NoKW, 0, 0, ice); break; case iShortSubject: subj_str(idata, str, sizeof(str), NoKW, 0, 1, ice); break; case iSubjectText: subj_str(idata, str, sizeof(str), NoKW, 1, 0, ice); break; case iSubjKey: subj_str(idata, str, sizeof(str), KW, 0, 0, ice); break; case iShortSubjKey: subj_str(idata, str, sizeof(str), KW, 0, 1, ice); break; case iSubjKeyText: subj_str(idata, str, sizeof(str), KW, 1, 0, ice); break; case iSubjKeyInit: subj_str(idata, str, sizeof(str), KWInit, 0, 0, ice); break; case iShortSubjKeyInit: subj_str(idata, str, sizeof(str), KWInit, 0, 1, ice); break; case iSubjKeyInitText: subj_str(idata, str, sizeof(str), KWInit, 1, 0, ice); break; case iOpeningText: case iOpeningTextNQ: if(idata->no_fetch) idata->bogus = 1; else{ char *first_text; first_text = fetch_firsttext(idata, cdesc->ctype == iOpeningTextNQ); if(first_text){ strncpy(str, first_text, BIGWIDTH); str[BIGWIDTH] = '\0'; } } break; case iKey: key_str(idata, KW, ice); break; case iKeyInit: key_str(idata, KWInit, ice); break; case iNews: if((newsgroups = fetch_newsgroups(idata)) != NULL){ strncpy(str, newsgroups, BIGWIDTH); str[BIGWIDTH] = '\0'; } break; case iNewsAndTo: if((newsgroups = fetch_newsgroups(idata)) != NULL) strncpy(str, newsgroups, sizeof(str)); if((l = strlen(str)) < sizeof(str)){ if(sizeof(str) - l < 6) strncpy(str+l, "...", sizeof(str)-l); else{ if(l > 0){ strncpy(str+l, " and ", sizeof(str)-l); set_index_addr(idata, "To", fetch_to(idata), NULL, BIGWIDTH-l-5, str+l+5); if(!str[l+5]) str[l] = '\0'; } else set_index_addr(idata, "To", fetch_to(idata), NULL, BIGWIDTH, str); } } break; case iToAndNews: set_index_addr(idata, "To", fetch_to(idata), NULL, BIGWIDTH, str); if((l = strlen(str)) < sizeof(str) && (newsgroups = fetch_newsgroups(idata))){ if(sizeof(str) - l < 6) strncpy(str+l, "...", sizeof(str)-l); else{ if(l > 0) strncpy(str+l, " and ", sizeof(str)-l); if(l > 0) strncpy(str+l+5, newsgroups, BIGWIDTH-l-5); else strncpy(str, newsgroups, BIGWIDTH); } } break; case iNewsAndRecips: if((newsgroups = fetch_newsgroups(idata)) != NULL) strncpy(str, newsgroups, BIGWIDTH); if((l = strlen(str)) < BIGWIDTH){ if(BIGWIDTH - l < 6) strncpy(str+l, "...", BIGWIDTH-l); else{ toaddr = fetch_to(idata); ccaddr = fetch_cc(idata); for(last_to = toaddr; last_to && last_to->next; last_to = last_to->next) ; /* point end of to list temporarily at cc list */ if(last_to) last_to->next = ccaddr; if(l > 0){ strncpy(str+l, " and ", sizeof(str)-l); set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH-l-5, str+l+5); if(!str[l+5]) str[l] = '\0'; } else set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str); if(last_to) last_to->next = NULL; } } break; case iRecipsAndNews: toaddr = fetch_to(idata); ccaddr = fetch_cc(idata); for(last_to = toaddr; last_to && last_to->next; last_to = last_to->next) ; /* point end of to list temporarily at cc list */ if(last_to) last_to->next = ccaddr; set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str); if(last_to) last_to->next = NULL; if((l = strlen(str)) < BIGWIDTH && (newsgroups = fetch_newsgroups(idata))){ if(BIGWIDTH - l < 6) strncpy(str+l, "...", BIGWIDTH-l); else{ if(l > 0) strncpy(str+l, " and ", sizeof(str)-l); if(l > 0) strncpy(str+l+5, newsgroups, BIGWIDTH-l-5); else strncpy(str, newsgroups, BIGWIDTH); } } break; case iPrio: case iPrioAlpha: case iPrioBang: prio_str(idata, cdesc->ctype, ice); break; case iHeader: header_str(idata, cdesc->hdrtok, ice); break; case iText: strncpy(str, (cdesc->hdrtok && cdesc->hdrtok->hdrname) ? cdesc->hdrtok->hdrname : "", sizeof(str)); str[sizeof(str)-1] = '\0'; break; default: break; } /* * If the element wasn't already filled in above, do it here. */ if(!ifield->ielem){ ielem = new_ielem(&ifield->ielem); if((color = hdr_color(itokens[itokensinv[cdesc->ctype].ctype].name, NULL, ps_global->index_token_colors)) != NULL){ if(pico_usingcolor()){ ielem->color = new_color_pair(color->fg, color->bg); ielem->type = eTypeCol; } free_color_pair(&color); } ielem->freedata = 1; ielem->data = cpystr(str); ielem->datalen = strlen(str); if(fromfield && pico_usingcolor() && ps_global->VAR_IND_FROM_FORE_COLOR && ps_global->VAR_IND_FROM_BACK_COLOR){ ielem->type = eTypeCol; ielem->freecolor = 1; ielem->color = new_color_pair(ps_global->VAR_IND_FROM_FORE_COLOR, ps_global->VAR_IND_FROM_BACK_COLOR); /* * This space is here so that if the text does * not extend all the way to the end of the field then * we'll switch the color back and paint the rest of the * field in the Normal color or the index line color. */ ielem = new_ielem(&ielem); ielem->freedata = 1; ielem->data = cpystr(" "); ielem->datalen = 1; } else if((cdesc->ctype == iOpeningText || cdesc->ctype == iOpeningTextNQ) && pico_usingcolor() && ps_global->VAR_IND_OP_FORE_COLOR && ps_global->VAR_IND_OP_BACK_COLOR){ ielem->type = eTypeCol; ielem->freecolor = 1; ielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR, ps_global->VAR_IND_OP_BACK_COLOR); /* * This space is here so that if the text does * not extend all the way to the end of the field then * we'll switch the color back and paint the rest of the * field in the Normal color or the index line color. */ ielem = new_ielem(&ielem); ielem->freedata = 1; ielem->data = cpystr(" "); ielem->datalen = 1; } ifield->leftadj = (cdesc->adjustment == Left) ? 1 : 0; set_ielem_widths_in_field(ifield); } } ice->widths_done = 1; ice->id = ice_hash(ice); /* * Now we have to put the temporary copy of ice back as the * real thing. */ icep = fetch_ice_ptr(idata->stream, idata->rawno); if(icep){ free_ice(icep); /* free what is already there */ *icep = ice; } return(ice); } ICE_S * format_thread_index_line(INDEXDATA_S *idata) { char *p, buffer[BIGWIDTH+1]; int thdlen, space_left, i; PINETHRD_S *thrd = NULL; ICE_S *ice, *tice = NULL, **ticep = NULL; IFIELD_S *ifield; IELEM_S *ielem; int (*save_sfstr_func)(void); struct variable *vars = ps_global->vars; dprint((8, "=== format_thread_index_line(%ld,%ld) ===\n", idata ? idata->msgno : -1, idata ? idata->rawno : -1)); space_left = ps_global->ttyo->screen_cols; if(ps_global->msgmap->max_thrdno < 1000) thdlen = 3; else if(ps_global->msgmap->max_thrdno < 10000) thdlen = 4; else if(ps_global->msgmap->max_thrdno < 100000) thdlen = 5; else thdlen = 6; ice = fetch_ice(idata->stream, idata->rawno); thrd = fetch_thread(idata->stream, idata->rawno); if(!thrd || !ice) /* can't happen? */ return(ice); if(!ice->tice){ tice = (ICE_S *) fs_get(sizeof(*tice)); memset(tice, 0, sizeof(*tice)); ice->tice = tice; } tice = ice->tice; if(!tice) return(ice); free_ifield(&tice->ifield); ticep = &ice->tice; tice = copy_ice(tice); if(space_left >= 3){ char to_us, status; p = buffer; to_us = to_us_symbol_for_thread(idata->stream, thrd, 1); status = status_symbol_for_thread(idata->stream, thrd, iStatus); if((p-buffer)+3 < sizeof(buffer)){ p[0] = to_us; p[1] = ' '; p[2] = status; p[3] = '\0';; } space_left -= 3; ifield = new_ifield(&tice->ifield); ifield->ctype = iStatus; ifield->width = 3; ifield->leftadj = 1; for(i = 0; i < 3; i++){ ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->data = (char *) fs_get(2 * sizeof(char)); ielem->data[0] = p[i]; ielem->data[1] = '\0'; ielem->datalen = 1; set_print_format(ielem, 1, ifield->leftadj); } if(pico_usingcolor()){ if(to_us == '*' && VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){ ielem = ifield->ielem; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR); if(F_ON(F_COLOR_LINE_IMPORTANT, ps_global)) tice->linecolor = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR); } else if((to_us == '+' || to_us == '-') && VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){ ielem = ifield->ielem; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR); } if(status == 'D' && VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){ ielem = ifield->ielem->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR); } else if(status == 'N' && VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){ ielem = ifield->ielem->next->next; ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR); } } } if(space_left >= thdlen+1){ p = buffer; space_left--; snprintf(p, sizeof(buffer), "%*.*s", thdlen, thdlen, ""); space_left -= thdlen; ifield = new_ifield(&tice->ifield); ifield->ctype = iMessNo; ifield->width = thdlen; ifield->leftadj = 0; ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->data = cpystr(p); ielem->datalen = strlen(p); set_print_format(ielem, ifield->width, ifield->leftadj); } if(space_left >= 7){ p = buffer; space_left--; date_str(fetch_date(idata), iDate, 0, p, sizeof(buffer), 0); if(sizeof(buffer) > 6) p[6] = '\0'; if(strlen(p) < 6 && (sizeof(buffer)) > 6){ char *q; for(q = p + strlen(p); q < p + 6; q++) *q = ' '; } space_left -= 6; ifield = new_ifield(&tice->ifield); ifield->ctype = iDate; ifield->width = 6; ifield->leftadj = 1; ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->data = cpystr(p); ielem->datalen = ifield->width; set_print_format(ielem, ifield->width, ifield->leftadj); } if(space_left > 3){ int from_width, subj_width, bigthread_adjust; long in_thread; char from[BIGWIDTH+1]; char tcnt[50]; space_left--; in_thread = count_lflags_in_thread(idata->stream, thrd, ps_global->msgmap, MN_NONE); p = buffer; if(in_thread == 1 && THRD_AUTO_VIEW()) snprintf(tcnt, sizeof(tcnt), " "); else snprintf(tcnt, sizeof(tcnt), "(%ld)", in_thread); bigthread_adjust = MAX(0, strlen(tcnt) - 3); /* third of the rest */ from_width = MAX((space_left-1)/3 - bigthread_adjust, 1); /* the rest */ subj_width = space_left - from_width - 1; if(strlen(tcnt) > subj_width) tcnt[subj_width] = '\0'; from[0] = '\0'; save_sfstr_func = pith_opt_truncate_sfstr; pith_opt_truncate_sfstr = NULL; from_str(iFromTo, idata, from, sizeof(from), tice); pith_opt_truncate_sfstr = save_sfstr_func; ifield = new_ifield(&tice->ifield); ifield->leftadj = 1; ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->type = eTypeCol; ielem->data = cpystr(from); ielem->datalen = strlen(from); ifield->width = from_width; set_print_format(ielem, ifield->width, ifield->leftadj); ifield->ctype = iFrom; if(from_width > 0 && pico_usingcolor() && VAR_IND_FROM_FORE_COLOR && VAR_IND_FROM_BACK_COLOR){ ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_FROM_FORE_COLOR, VAR_IND_FROM_BACK_COLOR); } ifield = new_ifield(&tice->ifield); ifield->leftadj = 0; ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->data = cpystr(tcnt); ielem->datalen = strlen(tcnt); ifield->width = ielem->datalen; set_print_format(ielem, ifield->width, ifield->leftadj); ifield->ctype = iAtt; /* not used, except that it isn't special */ subj_width -= strlen(tcnt); if(subj_width > 0) subj_width--; if(subj_width > 0){ if(idata->bogus){ if(idata->bogus < 2) snprintf(buffer, sizeof(buffer), "%-.*s", BIGWIDTH, _("[ No Message Text Available ]")); } else{ buffer[0] = '\0'; save_sfstr_func = pith_opt_truncate_sfstr; pith_opt_truncate_sfstr = NULL; subj_str(idata, buffer, sizeof(buffer), NoKW, 0, 0, NULL); pith_opt_truncate_sfstr = save_sfstr_func; } ifield = new_ifield(&tice->ifield); ifield->leftadj = 1; ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->type = eTypeCol; ielem->data = cpystr(buffer); ielem->datalen = strlen(buffer); ifield->width = subj_width; set_print_format(ielem, ifield->width, ifield->leftadj); ifield->ctype = iSubject; if(pico_usingcolor() && VAR_IND_SUBJ_FORE_COLOR && VAR_IND_SUBJ_BACK_COLOR){ ielem->freecolor = 1; ielem->color = new_color_pair(VAR_IND_SUBJ_FORE_COLOR, VAR_IND_SUBJ_BACK_COLOR); } } } else if(space_left > 1){ snprintf(p, sizeof(buffer)-(p-buffer), "%-.*s", space_left-1, " "); ifield = new_ifield(&tice->ifield); ifield->leftadj = 1; ielem = new_ielem(&ifield->ielem); ielem->freedata = 1; ielem->data = cpystr(p); ielem->datalen = strlen(p); ifield->width = space_left-1; set_print_format(ielem, ifield->width, ifield->leftadj); ifield->ctype = iSubject; } tice->widths_done = 1; tice->id = ice_hash(tice); if(ticep){ free_ice(ticep); /* free what is already there */ *ticep = tice; } return(ice); } /* * Print the fields of ice in buf with a single space between fields. * * Args buf -- place to put the line * ice -- the data for the line * msgno -- this is the msgno to be used, blanks if <= 0 * * Returns a pointer to buf. */ char * simple_index_line(char *buf, size_t buflen, ICE_S *ice, long int msgno) { char *p; IFIELD_S *ifield, *previfield = NULL; IELEM_S *ielem; if(!buf) alpine_panic("NULL buf in simple_index_line()"); if(buflen > 0) buf[0] = '\0'; p = buf; if(ice){ for(ifield = ice->ifield; ifield && p-buf < buflen; ifield = ifield->next){ /* space between fields */ if(ifield != ice->ifield && !(previfield && previfield->ctype == iText)) *p++ = ' '; /* message number string is generated on the fly */ if(ifield->ctype == iMessNo){ ielem = ifield->ielem; if(ielem && ielem->datalen >= ifield->width){ if(msgno > 0L) snprintf(ielem->data, ielem->datalen+1, "%*.ld", ifield->width, msgno); else snprintf(ielem->data, ielem->datalen+1, "%*.*s", ifield->width, ifield->width, ""); } } for(ielem = ifield->ielem; ielem && ielem->print_format && p-buf < buflen; ielem = ielem->next){ char *src; size_t bytes_added; src = ielem->data; bytes_added = utf8_pad_to_width(p, src, buflen-(p-buf) * sizeof(char), ielem->wid, ifield->leftadj); p += bytes_added; } previfield = ifield; } if(p-buf < buflen) *p = '\0'; } buf[buflen-1] = '\0'; return(buf); } /* * Look in current mail_stream for matches for messages in the searchset * which match a color rule pattern. Return the color. * The searched bit will be set for all of the messages which match the * first pattern which has a match. * * Args stream -- the mail stream * searchset -- restrict attention to this set of messages * pstate -- The pattern state. On the first call it will be Null. * Null means start over with a new first_pattern. * After that it will be pointing to our local PAT_STATE * so that next_pattern goes to the next one after the * ones we've already checked. * * Returns 0 if no match, 1 if a match. * The color that goes with the matched rule in returned_color. * It may be NULL, which indicates default. */ int get_index_line_color(MAILSTREAM *stream, SEARCHSET *searchset, PAT_STATE **pstate, COLOR_PAIR **returned_color) { PAT_S *pat = NULL; long rflags = ROLE_INCOL; COLOR_PAIR *color = NULL; int match = 0; static PAT_STATE localpstate; dprint((7, "get_index_line_color\n")); if(returned_color) *returned_color = NULL; if(*pstate) pat = next_pattern(*pstate); else{ *pstate = &localpstate; if(!nonempty_patterns(rflags, *pstate)) *pstate = NULL; if(*pstate) pat = first_pattern(*pstate); } if(*pstate){ /* Go through the possible roles one at a time until we get a match. */ while(!match && pat){ if(match_pattern(pat->patgrp, stream, searchset, NULL, get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){ if(!pat->action || pat->action->bogus) break; match++; if(pat->action && pat->action->incol) color = new_color_pair(pat->action->incol->fg, pat->action->incol->bg); } else pat = next_pattern(*pstate); } } if(match && returned_color) *returned_color = color; return(match); } /* * */ int index_in_overview(MAILSTREAM *stream) { INDEX_COL_S *cdesc = NULL; if(!(stream->mailbox && IS_REMOTE(stream->mailbox))) return(FALSE); /* no point! */ if(stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "nntp")){ if(THRD_INDX()) return(TRUE); for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++) switch(cdesc->ctype){ case iTo: /* can't be satisfied by XOVER */ case iSender: /* ... or specifically handled */ case iDescripSize: /* ... in news case */ case iAtt: return(FALSE); default : break; } } return(TRUE); } /* * fetch_from - called to get a the index entry's "From:" field */ int resent_to_us(INDEXDATA_S *idata) { if(!idata->valid_resent_to){ static char *fields[] = {"Resent-To", NULL}; char *h; if(idata->no_fetch){ idata->bogus = 1; /* don't do this */ return(FALSE); } if((h = pine_fetchheader_lines(idata->stream,idata->rawno,NULL,fields)) != NULL){ idata->resent_to_us = parsed_resent_to_us(h); fs_give((void **) &h); } idata->valid_resent_to = 1; } return(idata->resent_to_us); } int parsed_resent_to_us(char *h) { char *p, *q; ADDRESS *addr = NULL; int rv = FALSE; if((p = strindex(h, ':')) != NULL){ for(q = ++p; (q = strpbrk(q, "\015\012")) != NULL; q++) *q = ' '; /* quash junk */ rfc822_parse_adrlist(&addr, p, ps_global->maildomain); if(addr){ rv = address_is_us(addr, ps_global); mail_free_address(&addr); } } return(rv); } /* * fetch_from - called to get a the index entry's "From:" field */ ADDRESS * fetch_from(INDEXDATA_S *idata) { if(idata->no_fetch) /* implies from is valid */ return(idata->from); else if(idata->bogus) idata->bogus = 2; else{ ENVELOPE *env; /* c-client call's just cache access at this point */ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL) return(env->from); idata->bogus = 1; } return(NULL); } /* * fetch_to - called to get a the index entry's "To:" field */ ADDRESS * fetch_to(INDEXDATA_S *idata) { if(idata->no_fetch){ /* check for specific validity */ if(idata->valid_to) return(idata->to); else idata->bogus = 1; /* can't give 'em what they want */ } else if(idata->bogus){ idata->bogus = 2; /* elevate bogosity */ } else{ ENVELOPE *env; /* c-client call's just cache access at this point */ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL) return(env->to); idata->bogus = 1; } return(NULL); } /* * fetch_cc - called to get a the index entry's "Cc:" field */ ADDRESS * fetch_cc(INDEXDATA_S *idata) { if(idata->no_fetch){ /* check for specific validity */ if(idata->valid_cc) return(idata->cc); else idata->bogus = 1; /* can't give 'em what they want */ } else if(idata->bogus){ idata->bogus = 2; /* elevate bogosity */ } else{ ENVELOPE *env; /* c-client call's just cache access at this point */ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL) return(env->cc); idata->bogus = 1; } return(NULL); } /* * fetch_sender - called to get a the index entry's "Sender:" field */ ADDRESS * fetch_sender(INDEXDATA_S *idata) { if(idata->no_fetch){ /* check for specific validity */ if(idata->valid_sender) return(idata->sender); else idata->bogus = 1; /* can't give 'em what they want */ } else if(idata->bogus){ idata->bogus = 2; /* elevate bogosity */ } else{ ENVELOPE *env; /* c-client call's just cache access at this point */ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL) return(env->sender); idata->bogus = 1; } return(NULL); } /* * fetch_newsgroups - called to get a the index entry's "Newsgroups:" field */ char * fetch_newsgroups(INDEXDATA_S *idata) { if(idata->no_fetch){ /* check for specific validity */ if(idata->valid_news) return(idata->newsgroups); else idata->bogus = 1; /* can't give 'em what they want */ } else if(idata->bogus){ idata->bogus = 2; /* elevate bogosity */ } else{ ENVELOPE *env; /* c-client call's just cache access at this point */ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL) return(env->newsgroups); idata->bogus = 1; } return(NULL); } /* * fetch_subject - called to get at the index entry's "Subject:" field */ char * fetch_subject(INDEXDATA_S *idata) { if(idata->no_fetch) /* implies subject is valid */ return(idata->subject); else if(idata->bogus) idata->bogus = 2; else{ ENVELOPE *env; /* c-client call's just cache access at this point */ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL) return(env->subject); idata->bogus = 1; } return(NULL); } /* * Return an allocated copy of the first few characters from the body * of the message for possible use in the index screen. * * Maybe we could figure out some way to do aggregate calls to get * this info for all the lines in view instead of all the one at a * time calls we're doing now. */ char * fetch_firsttext(INDEXDATA_S *idata, int delete_quotes) { ENVELOPE *env; BODY *body = NULL, *new_body = NULL; char *firsttext = NULL; STORE_S *so; gf_io_t pc; long partial_fetch_len = 0L; SEARCHSET *ss, **sset; MESSAGECACHE *mc; PINELT_S *pelt = NULL; /* we cache the result we get from this function, so that we do not have to * refetch the text in case there is a change. We could cache in the envelope * but c-client does not have a special field for that, nor we want to use the * sparep pointer, since there could be other uses for sparep later, and even * if we add a pointer to the ENVELOPE structure, we would be caching the same * text twice (one in a private pointer, and the new pointer) and that would * not make sense. Instead we will use an elt for this */ if((mc = mail_elt(idata->stream, idata->rawno)) && ((pelt = (PINELT_S *) mc->sparep) == NULL)){ pelt = (PINELT_S *) fs_get(sizeof(PINELT_S)); memset(pelt, 0, sizeof(PINELT_S)); } if(pelt && pelt->firsttext != NULL) return(pelt->firsttext); try_again: /* * Prevent wild prefetch, just get the one we're after. * Can we get this somehow in the overview call in build_header_work? */ ss = mail_newsearchset(); ss->first = ss->last = idata->rawno; sset = (SEARCHSET **) mail_parameters(idata->stream, GET_FETCHLOOKAHEAD, (void *) idata->stream); if(sset) *sset = ss; if((env = pine_mail_fetchstructure(idata->stream, idata->rawno, &body)) != NULL){ if(body){ char *subtype = NULL; char *partno; if((body->type == TYPETEXT && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype)) || (body->type == TYPEMULTIPART && body->nested.part && body->nested.part->body.type == TYPETEXT && (subtype=body->nested.part->body.subtype) && ALLOWED_SUBTYPE(subtype)) || (body->type == TYPEMULTIPART && body->nested.part && body->nested.part->body.type == TYPEMULTIPART && body->nested.part->body.nested.part && body->nested.part->body.nested.part->body.type == TYPETEXT && (subtype=body->nested.part->body.nested.part->body.subtype) && ALLOWED_SUBTYPE(subtype))){ if((so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){ char buf[1025], *p; unsigned char c; int success; int one_space_done = 0; if(partial_fetch_len == 0L){ if(subtype && !strucmp(subtype, "html")) partial_fetch_len = 1024L; else if(subtype && !strucmp(subtype, "plain")) partial_fetch_len = delete_quotes ? 128L : 64L; else partial_fetch_len = 256L; } if((body->type == TYPETEXT && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype)) || (body->type == TYPEMULTIPART && body->nested.part && (new_body = &body->nested.part->body) != NULL && body->nested.part->body.type == TYPETEXT && (subtype=body->nested.part->body.subtype) && ALLOWED_SUBTYPE(subtype))) partno = "1"; else partno = "1.1"; gf_set_so_writec(&pc, so); success = get_body_part_text(idata->stream, new_body ? new_body : body, idata->rawno, partno, partial_fetch_len, pc, NULL, NULL, GBPT_NOINTR | GBPT_PEEK | (delete_quotes ? GBPT_DELQUOTES : 0)); gf_clear_so_writec(so); if(success){ so_seek(so, 0L, 0); p = buf; while(p-buf < sizeof(buf)-1 && so_readc(&c, so)){ /* delete leading whitespace */ if(p == buf && isspace(c)) ; /* and include just one space per run of whitespace */ else if(isspace(c)){ if(!one_space_done){ *p++ = SPACE; one_space_done++; } } else{ one_space_done = 0; *p++ = c; } } *p = '\0'; if(p > buf){ size_t l; l = strlen(buf); l += 100; firsttext = fs_get((l+1) * sizeof(char)); firsttext[0] = '\0'; iutf8ncpy(firsttext, buf, l); firsttext[l] = '\0'; removing_trailing_white_space(firsttext); } } so_give(&so); /* first if means we didn't fetch all of the data */ if(!(success > 1 && success < partial_fetch_len)){ if(partial_fetch_len < 4096L && (!firsttext || utf8_width(firsttext) < 50)){ if(firsttext) fs_give((void **) &firsttext); if(ss) mail_free_searchset(&ss); partial_fetch_len = 4096L; goto try_again; } } if(mc && pelt) pelt->firsttext = firsttext; } } } } if(ss) mail_free_searchset(&ss); return(firsttext); } /* * fetch_date - called to get at the index entry's "Date:" field */ char * fetch_date(INDEXDATA_S *idata) { if(idata->no_fetch) /* implies date is valid */ return(idata->date); else if(idata->bogus) idata->bogus = 2; else{ ENVELOPE *env; /* c-client call's just cache access at this point */ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL) return((char *) env->date); idata->bogus = 1; } return(NULL); } /* * fetch_header - called to get at the index entry's "Hdrname:" field */ char * fetch_header(INDEXDATA_S *idata, char *hdrname) { if(idata->no_fetch) idata->bogus = 1; else if(idata->bogus) idata->bogus = 2; else{ char *h, *p, *q, *decoded, *fields[2]; size_t retsize, decsize; char *ret = NULL; unsigned char *decode_buf = NULL; fields[0] = hdrname; fields[1] = NULL; if(hdrname && hdrname[0] && (h = pine_fetchheader_lines(idata->stream, idata->rawno, NULL, fields))){ if(strlen(h) < strlen(hdrname) + 1){ fs_give((void **) &h); return(cpystr("")); } /* skip "hdrname:" */ for(p = h + strlen(hdrname) + 1; *p && isspace((unsigned char)*p); p++) ; decsize = (4 * strlen(p)) + 1; decode_buf = (unsigned char *) fs_get(decsize * sizeof(unsigned char)); decoded = (char *) rfc1522_decode_to_utf8(decode_buf, decsize, p); p = decoded; retsize = strlen(decoded); q = ret = (char *) fs_get((retsize+1) * sizeof(char)); *q = '\0'; while(q-ret < retsize && *p){ if(*p == '\015' || *p == '\012') p++; else if(*p == '\t'){ *q++ = SPACE; p++; } else *q++ = *p++; } *q = '\0'; fs_give((void **) &h); if(decode_buf) fs_give((void **) &decode_buf); return(ret); } idata->bogus = 1; } return(NULL); } /* * fetch_size - called to get at the index entry's "size" field */ long fetch_size(INDEXDATA_S *idata) { if(idata->no_fetch) /* implies size is valid */ return(idata->size); else if(idata->bogus) idata->bogus = 2; else{ MESSAGECACHE *mc; if(idata->stream && idata->rawno > 0L && idata->rawno <= idata->stream->nmsgs && (mc = mail_elt(idata->stream, idata->rawno))) return(mc->rfc822_size); idata->bogus = 1; } return(0L); } /* * fetch_body - called to get a the index entry's body structure */ BODY * fetch_body(INDEXDATA_S *idata) { BODY *body; if(idata->bogus || idata->no_fetch){ idata->bogus = 2; return(NULL); } if(pine_mail_fetchstructure(idata->stream, idata->rawno, &body)) return(body); idata->bogus = 1; return(NULL); } /* * s is at least size width+1 */ int set_index_addr(INDEXDATA_S *idata, char *field, struct mail_address *addr, char *prefix, int width, char *s) { ADDRESS *atmp; char *p, *stmp = NULL, *sptr; char *save_personal = NULL; int orig_width; s[0] = '\0'; for(atmp = addr; idata->stream && atmp; atmp = atmp->next) if(atmp->host && atmp->host[0] == '.'){ char *pref, *h, *fields[2]; if(idata->no_fetch){ idata->bogus = 1; return(TRUE); } fields[0] = field; fields[1] = NULL; if((h = pine_fetchheader_lines(idata->stream, idata->rawno, NULL, fields)) != NULL){ if(strlen(h) < strlen(field) + 1){ p = h + strlen(h); } else{ /* skip "field:" */ for(p = h + strlen(field) + 1; *p && isspace((unsigned char)*p); p++) ; } orig_width = width; sptr = stmp = (char *) fs_get((orig_width+1) * sizeof(char)); /* add prefix */ for(pref = prefix; pref && *pref; pref++) if(width){ *sptr++ = *pref; width--; } else break; while(width--) if(*p == '\015' || *p == '\012') p++; /* skip CR LF */ else if(!*p) *sptr++ = ' '; else if(*p == '\t'){ *sptr++ = ' '; p++; } else *sptr++ = *p++; *sptr = '\0'; /* tie off return string */ if(stmp){ iutf8ncpy(s, stmp, orig_width+1); s[orig_width] = '\0'; fs_give((void **) &stmp); } fs_give((void **) &h); return(TRUE); } /* else fall thru and display what c-client gave us */ } if(addr && !addr->next /* only one address */ && addr->host /* not group syntax */ && addr->personal && addr->personal[0]){ /* there is a personal name */ char buftmp[MAILTMPLEN]; int l; if((l = prefix ? strlen(prefix) : 0) != 0) strncpy(s, prefix, width+1); snprintf(buftmp, sizeof(buftmp), "%s", addr->personal); p = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf, SIZEOF_20KBUF, buftmp); removing_leading_and_trailing_white_space(p); iutf8ncpy(s + l, p, width - l); s[width] = '\0'; if(*(s+l)) return(TRUE); else{ save_personal = addr->personal; addr->personal = NULL; } } if(addr){ char *a_string; int l; a_string = addr_list_string(addr, NULL, 0); if(save_personal) addr->personal = save_personal; if((l = prefix ? strlen(prefix) : 0) != 0) strncpy(s, prefix, width+1); iutf8ncpy(s + l, a_string, width - l); s[width] = '\0'; fs_give((void **)&a_string); return(TRUE); } if(save_personal) addr->personal = save_personal; return(FALSE); } void index_data_env(INDEXDATA_S *idata, ENVELOPE *env) { if(!env){ idata->bogus = 2; return; } idata->from = env->from; idata->to = env->to; idata->cc = env->cc; idata->sender = env->sender; idata->subject = env->subject; idata->date = (char *) env->date; idata->newsgroups = env->newsgroups; idata->valid_to = 1; /* signal that everything is here */ idata->valid_cc = 1; idata->valid_sender = 1; idata->valid_news = 1; } /* * Put a string representing the date into str. The source date is * in the string datesrc. The format to be used is in type. * Notice that type is an IndexColType, but really only a subset of * IndexColType types are allowed. * * Args datesrc -- The source date string * type -- What type of output we want * v -- If set, variable width output is ok. (Oct 9 not Oct 9) * str -- Put the answer here. * str_len -- Length of str * monabb_width -- This is a hack to get dates to line up right. For * example, in French (but without accents here) * dec. 21 * fevr. 23 * mars 7 * For this monabb_width would be 5. */ void date_str(char *datesrc, IndexColType type, int v, char *str, size_t str_len, int monabb_width) { char year4[5], /* 4 digit year */ yearzero[3], /* zero padded, 2-digit year */ monzero[3], /* zero padded, 2-digit month */ mon[3], /* 1 or 2-digit month, no pad */ dayzero[3], /* zero padded, 2-digit day */ day[3], /* 1 or 2-digit day, no pad */ dayord[3], /* 2-letter ordinal label */ monabb[10], /* 3-letter month abbrev */ /* actually maybe not 3 if localized */ hour24[3], /* 2-digit, 24 hour clock hour */ hour12[3], /* 12 hour clock hour, no pad */ minzero[3], /* zero padded, 2-digit minutes */ timezone[6]; /* timezone, like -0800 or +... */ int hr12; int curtype, lastmonthtype, lastyeartype, preftype; int sdatetimetype, sdatetime24type; struct date d; #define TODAYSTR N_("Today") curtype = (type == iCurDate || type == iCurDateIso || type == iCurDateIsoS || type == iCurPrefDate || type == iCurPrefDateTime || type == iCurPrefTime || type == iCurTime24 || type == iCurTime12 || type == iCurDay || type == iCurDay2Digit || type == iCurDayOfWeek || type == iCurDayOfWeekAbb || type == iCurMon || type == iCurMon2Digit || type == iCurMonLong || type == iCurMonAbb || type == iCurYear || type == iCurYear2Digit); lastmonthtype = (type == iLstMon || type == iLstMon2Digit || type == iLstMonLong || type == iLstMonAbb || type == iLstMonYear || type == iLstMonYear2Digit); lastyeartype = (type == iLstYear || type == iLstYear2Digit); sdatetimetype = (type == iSDateTime || type == iSDateTimeIso || type == iSDateTimeIsoS || type == iSDateTimeS1 || type == iSDateTimeS2 || type == iSDateTimeS3 || type == iSDateTimeS4 || type == iSDateTime24 || type == iSDateTimeIso24 || type == iSDateTimeIsoS24 || type == iSDateTimeS124 || type == iSDateTimeS224 || type == iSDateTimeS324 || type == iSDateTimeS424); sdatetime24type = (type == iSDateTime24 || type == iSDateTimeIso24 || type == iSDateTimeIsoS24 || type == iSDateTimeS124 || type == iSDateTimeS224 || type == iSDateTimeS324 || type == iSDateTimeS424); preftype = (type == iPrefDate || type == iPrefDateTime || type == iPrefTime || type == iCurPrefDate || type == iCurPrefDateTime || type == iCurPrefTime); if(str_len > 0) str[0] = '\0'; if(!(datesrc && datesrc[0]) && !(curtype || lastmonthtype || lastyeartype)) return; if(curtype || lastmonthtype || lastyeartype){ char dbuf[200]; rfc822_date(dbuf); parse_date(dbuf, &d); if(lastyeartype) d.year--; else if(lastmonthtype){ d.month--; if(d.month <= 0){ d.month = 12; d.year--; } } } else{ parse_date(F_ON(F_DATES_TO_LOCAL,ps_global) ? convert_date_to_local(datesrc) : datesrc, &d); if(d.year == -1 || d.month == -1 || d.day == -1){ sdatetimetype = 0; sdatetime24type = 0; preftype = 0; switch(type){ case iSDate: case iSDateTime: case iSDateTime24: type = iS1Date; break; case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24: case iPrefDate: case iPrefTime: case iPrefDateTime: type = iDateIso; break; case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24: type = iDateIsoS; break; case iSDateS1: case iSDateTimeS1: case iSDateTimeS124: type = iS1Date; break; case iSDateS2: case iSDateTimeS2: case iSDateTimeS224: type = iS1Date; break; case iSDateS3: case iSDateTimeS3: case iSDateTimeS324: type = iS1Date; break; case iSDateS4: case iSDateTimeS4: case iSDateTimeS424: type = iS1Date; break; default: break; } } } /* some special ones to start with */ if(preftype){ struct tm tm, *tmptr = NULL; time_t now; /* * Make sure we get the right one if we're using current time. */ if(curtype){ now = time((time_t *) 0); if(now != (time_t) -1) tmptr = localtime(&now); } if(!tmptr){ memset(&tm, 0, sizeof(tm)); tm.tm_year = MIN(MAX(d.year-1900, 0), 2000); tm.tm_mon = MIN(MAX(d.month-1, 0), 11); tm.tm_mday = MIN(MAX(d.day, 1), 31); tm.tm_hour = MIN(MAX(d.hour, 0), 23); tm.tm_min = MIN(MAX(d.minute, 0), 59); tm.tm_wday = MIN(MAX(d.wkday, 0), 6); tmptr = &tm; } switch(type){ case iPrefDate: case iCurPrefDate: our_strftime(str, str_len, "%x", tmptr); break; case iPrefTime: case iCurPrefTime: our_strftime(str, str_len, "%X", tmptr); break; case iPrefDateTime: case iCurPrefDateTime: our_strftime(str, str_len, "%c", tmptr); break; default: assert(0); break; } return; } strncpy(monabb, (d.month > 0 && d.month < 13) ? month_abbrev_locale(d.month) : "", sizeof(monabb)); monabb[sizeof(monabb)-1] = '\0'; strncpy(mon, (d.month > 0 && d.month < 13) ? int2string(d.month) : "", sizeof(mon)); mon[sizeof(mon)-1] = '\0'; strncpy(day, (d.day > 0 && d.day < 32) ? int2string(d.day) : "", sizeof(day)); day[sizeof(day)-1] = '\0'; strncpy(dayord, (d.day <= 0 || d.day > 31) ? "" : (d.day == 1 || d.day == 21 || d.day == 31) ? "st" : (d.day == 2 || d.day == 22 ) ? "nd" : (d.day == 3 || d.day == 23 ) ? "rd" : "th", sizeof(dayord)); dayord[sizeof(dayord)-1] = '\0'; strncpy(year4, (d.year >= 1000 && d.year < 10000) ? int2string(d.year) : "????", sizeof(year4)); year4[sizeof(year4)-1] = '\0'; if(d.year >= 0){ if((d.year % 100) < 10){ yearzero[0] = '0'; strncpy(yearzero+1, int2string(d.year % 100), sizeof(yearzero)-1); } else strncpy(yearzero, int2string(d.year % 100), sizeof(yearzero)); } else strncpy(yearzero, "??", sizeof(yearzero)); yearzero[sizeof(yearzero)-1] = '\0'; if(d.month > 0 && d.month < 10){ monzero[0] = '0'; strncpy(monzero+1, int2string(d.month), sizeof(monzero)-1); } else if(d.month >= 10 && d.month <= 12) strncpy(monzero, int2string(d.month), sizeof(monzero)); else strncpy(monzero, "??", sizeof(monzero)); monzero[sizeof(monzero)-1] = '\0'; if(d.day > 0 && d.day < 10){ dayzero[0] = '0'; strncpy(dayzero+1, int2string(d.day), sizeof(dayzero)-1); } else if(d.day >= 10 && d.day <= 31) strncpy(dayzero, int2string(d.day), sizeof(dayzero)); else strncpy(dayzero, "??", sizeof(dayzero)); dayzero[sizeof(dayzero)-1] = '\0'; hr12 = (d.hour == 0) ? 12 : (d.hour > 12) ? (d.hour - 12) : d.hour; hour12[0] = '\0'; if(hr12 > 0 && hr12 <= 12) strncpy(hour12, int2string(hr12), sizeof(hour12)); hour12[sizeof(hour12)-1] = '\0'; hour24[0] = '\0'; if(d.hour >= 0 && d.hour < 10){ hour24[0] = '0'; strncpy(hour24+1, int2string(d.hour), sizeof(hour24)-1); } else if(d.hour >= 10 && d.hour < 24) strncpy(hour24, int2string(d.hour), sizeof(hour24)); hour24[sizeof(hour24)-1] = '\0'; minzero[0] = '\0'; if(d.minute >= 0 && d.minute < 10){ minzero[0] = '0'; strncpy(minzero+1, int2string(d.minute), sizeof(minzero)-1); } else if(d.minute >= 10 && d.minute <= 60) strncpy(minzero, int2string(d.minute), sizeof(minzero)); minzero[sizeof(minzero)-1] = '\0'; if(sizeof(timezone) > 5){ if(d.hours_off_gmt <= 0){ timezone[0] = '-'; d.hours_off_gmt *= -1; d.min_off_gmt *= -1; } else timezone[0] = '+'; timezone[1] = '\0'; if(d.hours_off_gmt >= 0 && d.hours_off_gmt < 10){ timezone[1] = '0'; strncpy(timezone+2, int2string(d.hours_off_gmt), sizeof(timezone)-2); } else if(d.hours_off_gmt >= 10 && d.hours_off_gmt < 24) strncpy(timezone+1, int2string(d.hours_off_gmt), sizeof(timezone)-1); else{ timezone[1] = '0'; timezone[2] = '0'; } timezone[3] = '\0'; if(d.min_off_gmt >= 0 && d.min_off_gmt < 10){ timezone[3] = '0'; strncpy(timezone+4, int2string(d.min_off_gmt), sizeof(timezone)-4); } else if(d.min_off_gmt >= 10 && d.min_off_gmt <= 60) strncpy(timezone+3, int2string(d.min_off_gmt), sizeof(timezone)-3); else{ timezone[3] = '0'; timezone[4] = '0'; } timezone[5] = '\0'; timezone[sizeof(timezone)-1] = '\0'; } switch(type){ case iRDate: /* this one is not locale-specific */ snprintf(str, str_len, "%s%s%s %s %s", (d.wkday != -1) ? day_abbrev(d.wkday) : "", (d.wkday != -1) ? ", " : "", day, (d.month > 0 && d.month < 13) ? month_abbrev(d.month) : "", year4); break; case iDayOfWeekAbb: case iCurDayOfWeekAbb: strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_abbrev_locale(d.wkday) : "", str_len); str[str_len-1] = '\0'; break; case iDayOfWeek: case iCurDayOfWeek: strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_name_locale(d.wkday) : "", str_len); str[str_len-1] = '\0'; break; case iYear: case iCurYear: case iLstYear: case iLstMonYear: strncpy(str, year4, str_len); break; case iDay2Digit: case iCurDay2Digit: strncpy(str, dayzero, str_len); break; case iMon2Digit: case iCurMon2Digit: case iLstMon2Digit: strncpy(str, monzero, str_len); break; case iYear2Digit: case iCurYear2Digit: case iLstYear2Digit: case iLstMonYear2Digit: strncpy(str, yearzero, str_len); break; case iTimezone: strncpy(str, timezone, str_len); break; case iDay: case iCurDay: strncpy(str, day, str_len); break; case iDayOrdinal: snprintf(str, str_len, "%s%s", day, dayord); break; case iMon: case iCurMon: case iLstMon: if(d.month > 0 && d.month <= 12) strncpy(str, int2string(d.month), str_len); break; case iMonAbb: case iCurMonAbb: case iLstMonAbb: strncpy(str, monabb, str_len); break; case iMonLong: case iCurMonLong: case iLstMonLong: strncpy(str, (d.month > 0 && d.month < 13) ? month_name_locale(d.month) : "", str_len); break; case iDate: case iCurDate: if(v) snprintf(str, str_len, "%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day); else{ if(monabb_width > 0) utf8_snprintf(str, str_len, "%-*.*w %2s", monabb_width, monabb_width, monabb, day); else snprintf(str, str_len, "%s %2s", monabb, day); } break; case iLDate: if(v) snprintf(str, str_len, "%s%s%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day, ((monabb[0] || day[0]) && year4[0]) ? ", " : "", year4); else{ if(monabb_width > 0) utf8_snprintf(str, str_len, "%-*.*w %2s%c %4s", monabb_width, monabb_width, monabb, day, (monabb[0] && day[0] && year4[0]) ? ',' : ' ', year4); else snprintf(str, str_len, "%s %2s%c %4s", monabb, day, (monabb[0] && day[0] && year4[0]) ? ',' : ' ', year4); } break; case iS1Date: case iS2Date: case iS3Date: case iS4Date: case iDateIso: case iDateIsoS: case iCurDateIso: case iCurDateIsoS: if(monzero[0] == '?' && dayzero[0] == '?' && yearzero[0] == '?') snprintf(str, str_len, "%8s", ""); else{ switch(type){ case iS1Date: snprintf(str, str_len, "%2s/%2s/%2s", monzero, dayzero, yearzero); break; case iS2Date: snprintf(str, str_len, "%2s/%2s/%2s", dayzero, monzero, yearzero); break; case iS3Date: snprintf(str, str_len, "%2s.%2s.%2s", dayzero, monzero, yearzero); break; case iS4Date: snprintf(str, str_len, "%2s.%2s.%2s", yearzero, monzero, dayzero); break; case iDateIsoS: case iCurDateIsoS: snprintf(str, str_len, "%2s-%2s-%2s", yearzero, monzero, dayzero); break; case iDateIso: case iCurDateIso: snprintf(str, str_len, "%4s-%2s-%2s", year4, monzero, dayzero); break; default: break; } } break; case iTime24: case iCurTime24: snprintf(str, str_len, "%2s%c%2s", (hour24[0] && minzero[0]) ? hour24 : "", (hour24[0] && minzero[0]) ? ':' : ' ', (hour24[0] && minzero[0]) ? minzero : ""); break; case iTime12: case iCurTime12: snprintf(str, str_len, "%s%c%2s%s", (hour12[0] && minzero[0]) ? hour12 : "", (hour12[0] && minzero[0]) ? ':' : ' ', (hour12[0] && minzero[0]) ? minzero : "", (hour12[0] && minzero[0] && d.hour < 12) ? "am" : (hour12[0] && minzero[0] && d.hour >= 12) ? "pm" : " "); break; case iSDate: case iSDateIso: case iSDateIsoS: case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4: case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS: case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4: case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24: case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424: { struct date now, last_day; char dbuf[200]; int msg_day_of_year, now_day_of_year, today; int diff, ydiff, last_day_of_year; rfc822_date(dbuf); parse_date(dbuf, &now); today = day_of_week(&now) + 7; if(today >= 0+7 && today <= 6+7){ now_day_of_year = day_of_year(&now); msg_day_of_year = day_of_year(&d); ydiff = now.year - d.year; if(msg_day_of_year == -1) diff = -100; else if(ydiff == 0) diff = now_day_of_year - msg_day_of_year; else if(ydiff == 1){ last_day = d; last_day.month = 12; last_day.day = 31; last_day_of_year = day_of_year(&last_day); diff = now_day_of_year + (last_day_of_year - msg_day_of_year); } else if(ydiff == -1){ last_day = now; last_day.month = 12; last_day.day = 31; last_day_of_year = day_of_year(&last_day); diff = -1 * (msg_day_of_year + (last_day_of_year - now_day_of_year)); } else if(ydiff > 1) diff = 100; else diff = -100; if(diff == 0) strncpy(str, _(TODAYSTR), str_len); else if(diff == 1) strncpy(str, _("Yesterday"), str_len); else if(diff > 1 && diff < 7) snprintf(str, str_len, "%s", day_name_locale((today - diff) % 7)); else if(diff == -1) strncpy(str, _("Tomorrow"), str_len); else if(diff < -1 && diff > -7) snprintf(str, str_len, _("Next %.3s!"), day_name_locale((today - diff) % 7)); else if(diff > 0 && (ydiff == 0 || (ydiff == 1 && 12 + now.month - d.month < 6))){ if(v) snprintf(str, str_len, "%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day); else{ if(monabb_width > 0) utf8_snprintf(str, str_len, "%-*.*w %2s", monabb_width, monabb_width, monabb, day); else snprintf(str, str_len, "%s %2s", monabb, day); } } else{ if(msg_day_of_year == -1 && (type == iSDate || type == iSDateTime)) type = iSDateTimeIsoS; switch(type){ case iSDate: case iSDateTime: case iSDateTime24: { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_year = MIN(MAX(d.year-1900, 0), 2000); tm.tm_mon = MIN(MAX(d.month-1, 0), 11); tm.tm_mday = MIN(MAX(d.day, 1), 31); tm.tm_hour = MIN(MAX(d.hour, 0), 23); tm.tm_min = MIN(MAX(d.minute, 0), 59); our_strftime(str, str_len, "%x", &tm); } break; case iSDateS1: case iSDateTimeS1: case iSDateTimeS124: if(v) snprintf(str, str_len, "%s/%s/%s%s", mon, day, yearzero, diff < 0 ? "!" : ""); else snprintf(str, str_len, "%s%s/%s/%s%s", (mon[0] && mon[1]) ? "" : " ", mon, dayzero, yearzero, diff < 0 ? "!" : ""); break; case iSDateS2: case iSDateTimeS2: case iSDateTimeS224: if(v) snprintf(str, str_len, "%s/%s/%s%s", day, mon, yearzero, diff < 0 ? "!" : ""); else snprintf(str, str_len, "%s%s/%s/%s%s", (day[0] && day[1]) ? "" : " ", day, monzero, yearzero, diff < 0 ? "!" : ""); break; case iSDateS3: case iSDateTimeS3: case iSDateTimeS324: if(v) snprintf(str, str_len, "%s.%s.%s%s", day, mon, yearzero, diff < 0 ? "!" : ""); else snprintf(str, str_len, "%s%s.%s.%s%s", (day[0] && day[1]) ? "" : " ", day, monzero, yearzero, diff < 0 ? "!" : ""); break; case iSDateS4: case iSDateTimeS4: case iSDateTimeS424: if(v) snprintf(str, str_len, "%s.%s.%s%s", yearzero, monzero, dayzero, diff < 0 ? "!" : ""); else snprintf(str, str_len, "%s.%s.%s%s", yearzero, monzero, dayzero, diff < 0 ? "!" : ""); break; case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24: snprintf(str, str_len, "%2s-%2s-%2s", yearzero, monzero, dayzero); break; case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24: snprintf(str, str_len, "%4s-%2s-%2s", year4, monzero, dayzero); break; default: break; } } } else{ if(v) snprintf(str, str_len, "%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day); else{ if(monabb_width > 0) utf8_snprintf(str, str_len, "%-*.*w %2s", monabb_width, monabb_width, monabb, day); else snprintf(str, str_len, "%s %2s", monabb, day); } } } break; default: break; } str[str_len-1] = '\0'; if(type == iSTime || (sdatetimetype && !strcmp(str, _(TODAYSTR)))){ struct date now, last_day; char dbuf[200], *Ddd, *ampm; int daydiff; str[0] = '\0'; rfc822_date(dbuf); parse_date(dbuf, &now); /* Figure out if message date lands in the past week */ /* (if message dated this month or last month...) */ if((d.year == now.year && d.month >= now.month - 1) || (d.year == now.year - 1 && d.month == 12 && now.month == 1)){ daydiff = day_of_year(&now) - day_of_year(&d); /* * If msg in end of last year (and we're in first bit of "this" * year), diff will be backwards; fix up by adding number of days * in last year (usually 365, but occasionally 366)... */ if(d.year == now.year - 1){ last_day = d; last_day.month = 12; last_day.day = 31; daydiff += day_of_year(&last_day); } } else daydiff = -100; /* comfortably out of range (of past week) */ /* Build 2-digit hour and am/pm indicator, used below */ if(d.hour >= 0 && d.hour < 24){ snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12); ampm = (d.hour < 12) ? "am" : "pm"; snprintf(hour24, sizeof(hour24), "%02d", d.hour); } else{ strncpy(hour12, "??", sizeof(hour12)); hour12[sizeof(hour12)-1] = '\0'; ampm = "__"; strncpy(hour24, "??", sizeof(hour24)); hour24[sizeof(hour24)-1] = '\0'; } /* Build date/time in str, in format similar to that used by w(1) */ if(daydiff == 0){ /* If date is today, "HH:MMap" */ if(d.minute >= 0 && d.minute < 60) snprintf(minzero, sizeof(minzero), "%02d", d.minute); else{ strncpy(minzero, "??", sizeof(minzero)); minzero[sizeof(minzero)-1] = '\0'; } snprintf(str, str_len, "%s:%s%s", sdatetime24type ? hour24 : hour12, minzero, sdatetime24type ? "" : ampm); } else if(daydiff >= 1 && daydiff < 6){ /* If <1wk ago, "DddHHap" */ if(d.month >= 1 && d.day >= 1 && d.year >= 0 && d.month <= 12 && d.day <= 31 && d.year <= 9999) Ddd = day_abbrev_locale(day_of_week(&d)); else Ddd = "???"; snprintf(str, str_len, "%s%s%s", Ddd, hour12, ampm); } else{ /* date is old or future, "ddMmmyy" */ strncpy(monabb, (d.month >= 1 && d.month <= 12) ? month_abbrev_locale(d.month) : "???", sizeof(monabb)); monabb[sizeof(monabb)-1] = '\0'; if(d.day >= 1 && d.day <= 31) snprintf(dayzero, sizeof(dayzero), "%02d", d.day); else{ strncpy(dayzero, "??", sizeof(dayzero)); dayzero[sizeof(dayzero)-1] = '\0'; } if(d.year >= 0 && d.year <= 9999) snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100); else{ strncpy(yearzero, "??", sizeof(yearzero)); yearzero[sizeof(yearzero)-1] = '\0'; } snprintf(str, str_len, "%s%s%s", dayzero, monabb, yearzero); } if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */ if(v) memmove(str, str + 1, strlen(str)); else str[0] = ' '; } } /* This is like iSTime, but the format is different */ if(type == iSTime24){ struct date now, last_day; char dbuf[200], *Ddd, *ampm; int daydiff; str[0] = '\0'; rfc822_date(dbuf); parse_date(dbuf, &now); /* (if message dated this month or last month...) */ if((d.year == now.year && d.month >= now.month - 6) || (d.year == now.year - 1 && d.month == 12 && now.month <= 6)){ daydiff = day_of_year(&now) - day_of_year(&d); /* * If msg in end of last year (and we're in first bit of "this" * year), diff will be backwards; fix up by adding number of days * in last year (usually 365, but occasionally 366)... */ if(d.year == now.year - 1){ last_day = d; last_day.month = 12; last_day.day = 31; daydiff += day_of_year(&last_day); } } else daydiff = 181; /* comfortably out of range (of past week) */ /* Build 2-digit hour and am/pm indicator, used below */ if(d.hour >= 0 && d.hour < 24){ snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12); ampm = (d.hour < 12) ? "am" : "pm"; snprintf(hour24, sizeof(hour24), "%02d", d.hour); } else{ strncpy(hour12, "??", sizeof(hour12)); hour12[sizeof(hour12)-1] = '\0'; ampm = "__"; strncpy(hour24, "??", sizeof(hour24)); hour24[sizeof(hour24)-1] = '\0'; } /* Build date/time in str, in format similar to that used by w(1) */ if(daydiff >= 0 && daydiff <= 6){ /* If <1wk ago, "Ddd HH:mm" */ if(d.month >= 1 && d.day >= 1 && d.year >= 0 && d.month <= 12 && d.day <= 31 && d.year <= 9999) Ddd = day_abbrev_locale(day_of_week(&d)); else Ddd = "???"; if(d.minute >= 0 && d.minute < 60) snprintf(minzero, sizeof(minzero), "%02d", d.minute); else{ strncpy(minzero, "??", sizeof(minzero)); minzero[sizeof(minzero)-1] = '\0'; } snprintf(str, str_len, "%s %s:%s", Ddd, hour24, minzero); } else if(daydiff < 180){ /* date is "Mmm dd" */ strncpy(monabb, (d.month >= 1 && d.month <= 12) ? month_abbrev_locale(d.month) : "???", sizeof(monabb)); monabb[sizeof(monabb)-1] = '\0'; if(d.day >= 1 && d.day <= 31) snprintf(dayzero, sizeof(dayzero), "%*d", 2, d.day); else{ strncpy(dayzero, "??", sizeof(dayzero)); dayzero[sizeof(dayzero)-1] = '\0'; } snprintf(str, str_len, "%s %s", monabb, dayzero); } else { /* date is old or future, "dd/Mmm/yy" */ strncpy(monabb, (d.month >= 1 && d.month <= 12) ? month_abbrev_locale(d.month) : "???", sizeof(monabb)); monabb[sizeof(monabb)-1] = '\0'; if(d.day >= 1 && d.day <= 31) snprintf(dayzero, sizeof(dayzero), "%02d", d.day); else{ strncpy(dayzero, "??", sizeof(dayzero)); dayzero[sizeof(dayzero)-1] = '\0'; } if(d.year >= 0 && d.year <= 9999) snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100); else{ strncpy(yearzero, "??", sizeof(yearzero)); yearzero[sizeof(yearzero)-1] = '\0'; } snprintf(str, str_len, "%s/%s/%s", dayzero, monabb, yearzero); } if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */ if(v) memmove(str, str + 1, strlen(str)); else str[0] = ' '; } } } /* * Format a string representing the keywords into ice. * * This needs to be done in UTF-8, which may be tricky since it isn't labelled. * * Args idata -- which message? * kwtype -- keywords or kw initials * ice -- index cache entry for message */ void key_str(INDEXDATA_S *idata, SubjKW kwtype, ICE_S *ice) { int firstone = 1; KEYWORD_S *kw; char *word; COLOR_PAIR *color = NULL; SPEC_COLOR_S *sc = ps_global->kw_colors; IELEM_S *ielem = NULL; IFIELD_S *ourifield = NULL; if(ice && ice->ifield){ /* move to last ifield, the one we're working */ for(ourifield = ice->ifield; ourifield && ourifield->next; ourifield = ourifield->next) ; } if(!ourifield) return; if(kwtype == KWInit){ for(kw = ps_global->keywords; kw; kw = kw->next){ if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){ word = (kw->nick && kw->nick[0]) ? kw->nick : (kw->kw && kw->kw[0]) ? kw->kw : ""; /* * Pick off the first initial. Since word is UTF-8 it may * take more than one byte for the first initial. */ if(word && word[0]){ UCS ucs; unsigned long remaining_octets; unsigned char *inputp; remaining_octets = strlen(word); inputp = (unsigned char *) word; ucs = (UCS) utf8_get(&inputp, &remaining_octets); if(!(ucs & U8G_ERROR || ucs == UBOGON)){ ielem = new_ielem(&ourifield->ielem); ielem->freedata = 1; ielem->datalen = (unsigned) (inputp - (unsigned char *) word); ielem->data = (char *) fs_get((ielem->datalen + 1) * sizeof(char)); strncpy(ielem->data, word, ielem->datalen); ielem->data[ielem->datalen] = '\0'; if(pico_usingcolor() && ((kw->nick && kw->nick[0] && (color=hdr_color(kw->nick,NULL,sc))) || (kw->kw && kw->kw[0] && (color=hdr_color(kw->kw,NULL,sc))))){ ielem->color = color; color = NULL; } } } if(color) free_color_pair(&color); } } } else if(kwtype == KW){ for(kw = ps_global->keywords; kw; kw = kw->next){ if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){ if(!firstone){ ielem = new_ielem(&ourifield->ielem); ielem->freedata = 1; ielem->data = cpystr(" "); ielem->datalen = 1; } firstone = 0; word = (kw->nick && kw->nick[0]) ? kw->nick : (kw->kw && kw->kw[0]) ? kw->kw : ""; if(word[0]){ ielem = new_ielem(&ourifield->ielem); ielem->freedata = 1; ielem->data = cpystr(word); ielem->datalen = strlen(word); if(pico_usingcolor() && ((kw->nick && kw->nick[0] && (color=hdr_color(kw->nick,NULL,sc))) || (kw->kw && kw->kw[0] && (color=hdr_color(kw->kw,NULL,sc))))){ ielem->color = color; color = NULL; } } if(color) free_color_pair(&color); } } } /* * If we're coloring some of the fields then add a dummy field * at the end that can soak up the rest of the space after the last * colored keyword. Otherwise, the last one's color will extend to * the end of the field. */ if(pico_usingcolor()){ ielem = new_ielem(&ourifield->ielem); ielem->freedata = 1; ielem->data = cpystr(" "); ielem->datalen = 1; } ourifield->leftadj = 1; set_ielem_widths_in_field(ourifield); } void prio_str(INDEXDATA_S *idata, IndexColType ctype, ICE_S *ice) { IFIELD_S *ourifield = NULL; IELEM_S *ielem = NULL; char *hdrval; PRIORITY_S *p; int v; if(ice && ice->ifield){ /* move to last ifield, the one we're working */ for(ourifield = ice->ifield; ourifield && ourifield->next; ourifield = ourifield->next) ; } if(!ourifield) return; hdrval = fetch_header(idata, PRIORITYNAME); if(hdrval && hdrval[0] && isdigit(hdrval[0])){ v = atoi(hdrval); if(v >= 1 && v <= 5 && v != 3){ ielem = new_ielem(&ourifield->ielem); ielem->freedata = 1; switch(ctype){ case iPrio: ielem->data = (char *) fs_get(2 * sizeof(char)); ielem->data[0] = hdrval[0]; ielem->data[1] = '\0'; break; case iPrioAlpha: for(p = priorities; p && p->desc; p++) if(p->val == v) break; if(p && p->desc) ielem->data = cpystr(p->desc); break; case iPrioBang: ielem->data = (char *) fs_get(2 * sizeof(char)); ielem->data[0] = '\0'; switch(v){ case 1: case 2: ielem->data[0] = '!'; break; case 4: case 5: /* * We could put a Unicode downarrow in here but * we have no way of knowing if the user's font * will have it (I think). */ ielem->data[0] = 'v'; break; } ielem->data[1] = '\0'; break; default: alpine_panic("Unhandled case in prio_str"); break; } if(!ielem->data) ielem->data = cpystr(""); if(ielem && ielem->data) ielem->datalen = strlen(ielem->data); if((v == 1 || v == 2) && pico_usingcolor() && ps_global->VAR_IND_HIPRI_FORE_COLOR && ps_global->VAR_IND_HIPRI_BACK_COLOR){ ielem->type = eTypeCol; ielem->freecolor = 1; ielem->color = new_color_pair(ps_global->VAR_IND_HIPRI_FORE_COLOR, ps_global->VAR_IND_HIPRI_BACK_COLOR); } else if((v == 4 || v == 5) && pico_usingcolor() && ps_global->VAR_IND_LOPRI_FORE_COLOR && ps_global->VAR_IND_LOPRI_BACK_COLOR){ ielem->type = eTypeCol; ielem->freecolor = 1; ielem->color = new_color_pair(ps_global->VAR_IND_LOPRI_FORE_COLOR, ps_global->VAR_IND_LOPRI_BACK_COLOR); } ourifield->leftadj = 1; set_ielem_widths_in_field(ourifield); } fs_give((void **) &hdrval); } } void header_str(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok, ICE_S *ice) { IFIELD_S *ourifield = NULL; IELEM_S *ielem = NULL; char *fieldval = NULL; if(ice && ice->ifield){ /* move to last ifield, the one we're working */ for(ourifield = ice->ifield; ourifield && ourifield->next; ourifield = ourifield->next) ; } if(!ourifield) return; fieldval = get_fieldval(idata, hdrtok); if(fieldval){ ielem = new_ielem(&ourifield->ielem); ielem->freedata = 1; ielem->data = fieldval; ielem->datalen = strlen(fieldval); fieldval = NULL; ourifield->leftadj = (hdrtok->adjustment == Left) ? 1 : 0; } set_ielem_widths_in_field(ourifield); } char * get_fieldval(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok) { int sep, fieldnum; char *hdrval = NULL, *testval; char *fieldval = NULL, *firstval; char *retfieldval = NULL; if(!hdrtok) return(retfieldval); if(hdrtok && hdrtok->hdrname && hdrtok->hdrname[0]) hdrval = fetch_header(idata, hdrtok ? hdrtok->hdrname : ""); /* find start of fieldnum'th field */ fieldval = hdrval; for(fieldnum = MAX(hdrtok->fieldnum-1, 0); fieldnum > 0 && fieldval && *fieldval; fieldnum--){ firstval = NULL; for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){ testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL; if(testval && (!firstval || testval < firstval)) firstval = testval; } fieldval = firstval; if(fieldval && *fieldval) fieldval++; } /* tie off end of field */ if(fieldval && *fieldval && hdrtok->fieldnum > 0){ firstval = NULL; for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){ testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL; if(testval && (!firstval || testval < firstval)) firstval = testval; } if(firstval) *firstval = '\0'; } if(!fieldval) fieldval = ""; retfieldval = cpystr(fieldval); if(hdrval) fs_give((void **) &hdrval); return(retfieldval); } long scorevalfrommsg(MAILSTREAM *stream, MsgNo rawno, HEADER_TOK_S *hdrtok, int no_fetch) { INDEXDATA_S idata; MESSAGECACHE *mc; char *fieldval = NULL; long retval = 0L; memset(&idata, 0, sizeof(INDEXDATA_S)); idata.stream = stream; idata.no_fetch = no_fetch; idata.msgno = mn_raw2m(sp_msgmap(stream), rawno); idata.rawno = rawno; if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs && (mc = mail_elt(stream, idata.rawno))){ idata.size = mc->rfc822_size; index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno)); } else idata.bogus = 2; fieldval = get_fieldval(&idata, hdrtok); if(fieldval){ retval = atol(fieldval); fs_give((void **) &fieldval); } return(retval); } /* * Put a string representing the subject into str. Idata tells us which * message we are referring to. * * This means we should ensure that all data ends up being UTF-8 data. * That covers the data in ice ielems and str. * * Args idata -- which message? * str -- destination buffer * strsize -- size of str buffer * kwtype -- prepend keywords or kw initials before the subject * opening -- add first text from body of message if there's room * shorten -- if on, shorten the subject. * ice -- index cache entry for message */ void subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int opening, int shorten, ICE_S *ice) { char *subject, *origsubj, *origstr, *rawsubj, *sptr = NULL; char *p, *border, *q = NULL, *free_subj = NULL; char *sp; size_t len; int width = -1; int depth = 0, mult = 2; int save; int do_subj = 0, truncated_tree = 0; PINETHRD_S *thd, *thdorig; IELEM_S *ielem = NULL, *subjielem = NULL; IFIELD_S *ourifield = NULL; if(strsize <= 0) return; /* * If we need the data at the start of the message and we're in * a c-client callback, defer the data lookup until later. */ if(opening && idata->no_fetch){ idata->bogus = 1; return; } if(ice && ice->ifield){ /* move to last ifield, the one we're working on */ for(ourifield = ice->ifield; ourifield && ourifield->next; ourifield = ourifield->next) ; } str[0] = str[strsize-1] = '\0'; origstr = str; rawsubj = fetch_subject(idata); if(!rawsubj) rawsubj = ""; /* * Before we do anything else, decode the character set in the subject and * work with the result. */ sp = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf, SIZEOF_20KBUF, rawsubj); len = strlen(sp); len += 100; /* for possible charset, escaped characters */ origsubj = fs_get((len+1) * sizeof(unsigned char)); origsubj[0] = '\0'; iutf8ncpy(origsubj, sp, len); origsubj[len] = '\0'; replace_tabs_by_space(origsubj); removing_trailing_white_space(origsubj); /* * origsubj is the original subject but it has been decoded. We need * to free it at the end of this routine. */ if(shorten) shorten_subject(origsubj); /* * prepend_keyword will put the keyword stuff before the subject * and split the subject up into its colored parts in subjielem. * Subjielem is a local ielem which will have to be fit into the * real ifield->ielem later. The print_format strings in subjielem will * not be filled in by prepend_keyword because of the fact that we * may have to adjust things for threading below. * We use subjielem in case we want to insert some threading information * at the front of the subject. */ if(kwtype == KW || kwtype == KWInit){ subject = prepend_keyword_subject(idata->stream, idata->rawno, origsubj, kwtype, ourifield ? &subjielem : NULL, ps_global->VAR_KW_BRACES); free_subj = subject; } else{ subject = origsubj; if(ourifield){ subjielem = new_ielem(&subjielem); subjielem->type = eTypeCol; subjielem->freedata = 1; subjielem->data = cpystr(subject); subjielem->datalen = strlen(subject); if(pico_usingcolor() && ps_global->VAR_IND_SUBJ_FORE_COLOR && ps_global->VAR_IND_SUBJ_BACK_COLOR){ subjielem->freecolor = 1; subjielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR); } } } /* * This space is here so that if the subject does * not extend all the way to the end of the field then * we'll switch the color back and paint the rest of the * field in the Normal color or the index line color. */ if(!opening){ ielem = new_ielem(&subjielem); ielem->freedata = 1; ielem->data = cpystr(" "); ielem->datalen = 1; } if(!subject) subject = ""; if(THREADING() && (ps_global->thread_disp_style == THREAD_STRUCT || ps_global->thread_disp_style == THREAD_MUTTLIKE || ps_global->thread_disp_style == THREAD_INDENT_SUBJ1 || ps_global->thread_disp_style == THREAD_INDENT_SUBJ2)){ /* * Why do we want to truncate the subject and from strs? * It's so we can put the [5] thread count things in below when * we are threading and the thread structure runs off the right * hand edge of the screen. This routine doesn't know that it * is running off the edge unless it knows the actual width * that we have to draw in. */ if(pith_opt_truncate_sfstr && (*pith_opt_truncate_sfstr)() && ourifield && ourifield->width > 0) width = ourifield->width; if(width < 0) width = strsize-1; width = MIN(width, strsize-1); /* * We're counting on the fact that this initial part of the * string is ascii and we have one octet per character and * characters are width 1 on the screen. */ border = str + width; thdorig = thd = fetch_thread(idata->stream, idata->rawno); if(pith_opt_condense_thread_cue) width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width, thd && thd->next && get_lflag(idata->stream, NULL,idata->rawno, MN_COLL)); /* * width is < available strsize and * border points to something less than or equal * to the end of the buffer. */ sptr = str; if(thd) while(thd->parent && (thd = fetch_thread(idata->stream, thd->parent))) depth++; if(depth > 0){ if(ps_global->thread_disp_style == THREAD_INDENT_SUBJ1) mult = 1; sptr += (mult*depth); for(thd = thdorig, p = str + mult*depth - mult; thd && thd->parent && p >= str; thd = fetch_thread(idata->stream, thd->parent), p -= mult){ if(p + mult >= border && !q){ if(width >= 4 && depth < 100){ snprintf(str, width+1, "%*s[%2d]", width-4, "", depth); q = str + width-4; } else if(width >= 5 && depth < 1000){ snprintf(str, width+1, "%*s[%3d]", width-5, "", depth); q = str + width-5; } else{ snprintf(str, width+1, "%s", repeat_char(width, '.')); q = str; } border = q; truncated_tree++; } if(p < border){ p[0] = ' '; if(p + 1 < border) p[1] = ' '; if(ps_global->thread_disp_style == THREAD_STRUCT || ps_global->thread_disp_style == THREAD_MUTTLIKE){ /* * WARNING! * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0] * is ascii. */ if(thd == thdorig && !thd->branch) p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0]; else if(thd == thdorig || thd->branch) p[0] = '|'; if(p + 1 < border && thd == thdorig) p[1] = '-'; } } } } if(sptr && !truncated_tree){ /* * Look to see if the subject is the same as the previous * message in the thread, if any. If it is the same, don't * reprint the subject. * * Note that when we're prepending keywords to the subject, * and the user changes a keyword, we do invalidate * the index cache for that message but we don't go to the * trouble of invalidating the index cache for the the child * of that node in the thread, so the MUTT subject line * display for the child may be wrong. That is, it may show * it is the same as this subject even though it no longer * is, or vice versa. */ if(ps_global->thread_disp_style == THREAD_MUTTLIKE){ if(depth == 0) do_subj++; else{ if(thdorig->parent && (thd = fetch_thread(idata->stream, thdorig->parent)) && thd->rawno){ char *this_orig = NULL, *prev_orig = NULL, *free_prev_orig = NULL, *this_prep = NULL, /* includes prepend */ *prev_prep = NULL; ENVELOPE *env; mailcache_t mc; SORTCACHE *sc = NULL; /* get the stripped subject of previous message */ mc = (mailcache_t) mail_parameters(NIL, GET_CACHE, NIL); if(mc) sc = (*mc)(idata->stream, thd->rawno, CH_SORTCACHE); if(sc && sc->subject) prev_orig = sc->subject; else{ char *stripthis; env = pine_mail_fetchenvelope(idata->stream, thd->rawno); stripthis = (env && env->subject) ? env->subject : ""; mail_strip_subject(stripthis, &prev_orig); free_prev_orig = prev_orig; } mail_strip_subject(rawsubj, &this_orig); if(kwtype == KW || kwtype == KWInit){ prev_prep = prepend_keyword_subject(idata->stream, thd->rawno, prev_orig, kwtype, NULL, ps_global->VAR_KW_BRACES); this_prep = prepend_keyword_subject(idata->stream, idata->rawno, this_orig, kwtype, NULL, ps_global->VAR_KW_BRACES); if((this_prep || prev_prep) && ((this_prep && !prev_prep) || (prev_prep && !this_prep) || strucmp(this_prep, prev_prep))) do_subj++; } else{ if((this_orig || prev_orig) && ((this_orig && !prev_orig) || (prev_orig && !this_orig) || strucmp(this_orig, prev_orig))) do_subj++; } /* * If some of the thread is zoomed out of view, we * want to display the subject of the first one that * is in view. If any of the parents or grandparents * etc of this message are visible, then we don't * need to worry about it. If all of the parents have * been zoomed away, then this is the first one. * * When you're looking at a particular case where * some of the messages of a thread are selected it * seems like we should look at not only our * direct parents, but the siblings of the parent * too. But that's not really correct, because those * siblings are basically the starts of different * branches, separate from our branch. They could * have their own subjects, for example. This will * give us cases where it looks like we are showing * the subject too much, but it will be correct! * * In zoom_index() we clear_index_cache_ent for * some lines which have subjects which might become * visible when we zoom, and also in set_lflags * where we might change subjects by unselecting * something when zoomed. */ if(!do_subj){ while(thd){ if(!msgline_hidden(idata->stream, sp_msgmap(idata->stream), mn_raw2m(sp_msgmap(idata->stream), (long) thd->rawno), 0)){ break; /* found a visible parent */ } if(thd && thd->parent) thd = fetch_thread(idata->stream,thd->parent); else thd = NULL; } if(!thd) /* none were visible */ do_subj++; } if(this_orig) fs_give((void **) &this_orig); if(this_prep) fs_give((void **) &this_prep); if(free_prev_orig) fs_give((void **) &free_prev_orig); if(prev_prep) fs_give((void **) &prev_prep); } else do_subj++; } } else do_subj++; if(do_subj){ /* * We don't need to worry about truncating to width * here. If we go over the right hand edge it will be * truncated. */ strsize -= (sptr - str); strncpy(sptr, subject, strsize-1); sptr[strsize-1] = '\0'; } else if(ps_global->thread_disp_style == THREAD_MUTTLIKE){ strsize -= (sptr - str); if(strsize > 0){ sptr[0] = '>'; sptr++; } /* * We decided we don't need the subject so we'd better * eliminate subjielem. */ free_ielem(&subjielem); } } else free_ielem(&subjielem); /* no room for actual subject */ if(ourifield && sptr && sptr > origstr){ ielem = new_ielem(&ourifield->ielem); ielem->type = eThreadInfo; ielem->freedata = 1; save = *sptr; *sptr = '\0'; ielem->data = cpystr(origstr); ielem->datalen = strlen(origstr); *sptr = save; } } else{ /* * Not much to do for the non-threading case. Just copy the * subject we have so far into str and truncate it. */ strncpy(str, subject, strsize-1); str[strsize-1] = '\0'; } if(ourifield){ /* * We need to add subjielem to the end of the ourifield->ielem list. */ if(subjielem){ if(ourifield->ielem){ for(ielem = ourifield->ielem; ielem && ielem->next; ielem = ielem->next) ; ielem->next = subjielem; } else ourifield->ielem = subjielem; } ourifield->leftadj = 1; } if(opening && ourifield){ IELEM_S *ftielem = NULL; size_t len; char *first_text; first_text = fetch_firsttext(idata, 0); if(first_text){ char sep[200]; int seplen; strncpy(sep, ps_global->VAR_OPENING_SEP ? ps_global->VAR_OPENING_SEP : " - ", sizeof(sep)); sep[sizeof(sep)-1] = '\0'; removing_double_quotes(sep); seplen = strlen(sep); ftielem = new_ielem(&ftielem); ftielem->type = eTypeCol; ftielem->freedata = 1; len = strlen(first_text) + seplen; ftielem->data = (char *) fs_get((len + 1) * sizeof(char)); strncpy(ftielem->data, sep, seplen); strncpy(ftielem->data+seplen, first_text, len+1-seplen); ftielem->data[len] = '\0'; ftielem->datalen = strlen(ftielem->data); if(ftielem){ if(pico_usingcolor() && ps_global->VAR_IND_OP_FORE_COLOR && ps_global->VAR_IND_OP_BACK_COLOR){ ftielem->freecolor = 1; ftielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR, ps_global->VAR_IND_OP_BACK_COLOR); /* * This space is here so that if the opening text does * not extend all the way to the end of the field then * we'll switch the color back and paint the rest of the * field in the Normal color or the index line color. */ ielem = new_ielem(&ftielem); ielem->freedata = 1; ielem->data = cpystr(" "); ielem->datalen = 1; } if(ourifield->ielem){ for(ielem = ourifield->ielem; ielem && ielem->next; ielem = ielem->next) ; ielem->next = ftielem; } else ourifield->ielem = ftielem; } ourifield->leftadj = 1; } } if(ourifield) set_ielem_widths_in_field(ourifield); if(origsubj) fs_give((void **) &origsubj); if(free_subj) fs_give((void **) &free_subj); } /* * Returns an allocated string which is the passed in subject with a * list of keywords prepended. * * If kwtype == KW you will end up with * * {keyword1 keyword2} subject * * (actually, keyword nicknames will be used instead of the actual keywords * in the case that the user defined nicknames) * * If kwtype == KWInit you get * * {AB} subject * * where A is the first letter of the first keyword and B is the first letter * of the second defined keyword. No space between them. There could be more * than two. * * If an ielemp is passed in it will be filled out with the data and colors * of the pieces of the subject but the print_format strings will not * be set. */ char * prepend_keyword_subject(MAILSTREAM *stream, long int rawno, char *subject, SubjKW kwtype, IELEM_S **ielemp, char *braces) { char *p, *next_piece, *retsubj = NULL, *str; char *left_brace = NULL, *right_brace = NULL; size_t len; int some_set = 0, save; IELEM_S *ielem; KEYWORD_S *kw; COLOR_PAIR *color = NULL; SPEC_COLOR_S *sc = ps_global->kw_colors; if(!subject) subject = ""; if(braces && *braces) get_pair(braces, &left_brace, &right_brace, 1, 0); len = (left_brace ? strlen(left_brace) : 0) + (right_brace ? strlen(right_brace) : 0); if(stream && rawno >= 0L && rawno <= stream->nmsgs){ for(kw = ps_global->keywords; kw; kw = kw->next) if(user_flag_is_set(stream, rawno, kw->kw)){ if(kwtype == KW){ if(some_set) len++; /* space between keywords */ str = kw->nick ? kw->nick : kw->kw ? kw->kw : ""; len += strlen(str); } else if(kwtype == KWInit){ str = kw->nick ? kw->nick : kw->kw ? kw->kw : ""; /* interested in only the first UTF-8 initial */ if(str && str[0]){ UCS ucs; unsigned long remaining_octets; unsigned char *inputp; remaining_octets = strlen(str); inputp = (unsigned char *) str; ucs = (UCS) utf8_get(&inputp, &remaining_octets); if(!(ucs & U8G_ERROR || ucs == UBOGON)){ len += (unsigned) (inputp - (unsigned char *) str); } } } some_set++; } } if((kwtype == KW || kwtype == KWInit) && some_set){ len += strlen(subject); /* subject is already UTF-8 if needed */ retsubj = (char *) fs_get((len + 1) * sizeof(*retsubj)); memset(retsubj, 0, (len + 1) * sizeof(*retsubj)); next_piece = p = retsubj; for(kw = ps_global->keywords; kw; kw = kw->next){ if(user_flag_is_set(stream, rawno, kw->kw)){ if(p == retsubj){ if(left_brace && len > 0) sstrncpy(&p, left_brace, len); } else if(kwtype == KW) *p++ = ' '; if(ielemp && p > next_piece){ save = *p; *p = '\0'; ielem = new_ielem(ielemp); ielem->freedata = 1; ielem->data = cpystr(next_piece); ielem->datalen = strlen(next_piece); *p = save; next_piece = p; } str = kw->nick ? kw->nick : kw->kw ? kw->kw : ""; if(kwtype == KWInit){ if(str && str[0]){ UCS ucs; unsigned long remaining_octets; unsigned char *inputp; remaining_octets = strlen(str); inputp = (unsigned char *) str; ucs = (UCS) utf8_get(&inputp, &remaining_octets); if(!(ucs & U8G_ERROR || ucs == UBOGON)){ if(len-(p-retsubj) > 0){ sstrncpy(&p, str, MIN(inputp - (unsigned char *) str,len-(p-retsubj))); if(p > next_piece && ielemp && pico_usingcolor() && ((kw->nick && kw->nick[0] && (color=hdr_color(kw->nick,NULL,sc))) || (kw->kw && kw->kw[0] && (color=hdr_color(kw->kw,NULL,sc))))){ ielem = new_ielem(ielemp); ielem->freedata = 1; save = *p; *p = '\0'; ielem->data = cpystr(next_piece); ielem->datalen = strlen(next_piece); ielem->color = color; color = NULL; *p = save; next_piece = p; } } } if(color) free_color_pair(&color); } } else{ if(len-(p-retsubj) > 0) sstrncpy(&p, str, len-(p-retsubj)); if(p > next_piece && ielemp && pico_usingcolor() && ((kw->nick && kw->nick[0] && (color=hdr_color(kw->nick,NULL,sc))) || (kw->kw && kw->kw[0] && (color=hdr_color(kw->kw,NULL,sc))))){ ielem = new_ielem(ielemp); ielem->freedata = 1; save = *p; *p = '\0'; ielem->data = cpystr(next_piece); ielem->datalen = strlen(next_piece); ielem->color = color; color = NULL; *p = save; next_piece = p; } if(color) free_color_pair(&color); } } } if(len-(p-retsubj) > 0 && right_brace) sstrncpy(&p, right_brace, len-(p-retsubj)); if(ielemp && p > next_piece){ save = *p; *p = '\0'; ielem = new_ielem(ielemp); ielem->freedata = 1; ielem->data = cpystr(next_piece); ielem->datalen = strlen(next_piece); *p = save; next_piece = p; } if(len-(p-retsubj) > 0 && subject) sstrncpy(&p, subject, len-(p-retsubj)); if(ielemp && p > next_piece){ save = *p; *p = '\0'; ielem = new_ielem(ielemp); ielem->type = eTypeCol; ielem->freedata = 1; ielem->data = cpystr(next_piece); ielem->datalen = strlen(next_piece); *p = save; next_piece = p; if(pico_usingcolor() && ps_global->VAR_IND_SUBJ_FORE_COLOR && ps_global->VAR_IND_SUBJ_BACK_COLOR){ ielem->freecolor = 1; ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR); } } retsubj[len] = '\0'; /* just making sure */ } else{ if(ielemp){ ielem = new_ielem(ielemp); ielem->type = eTypeCol; ielem->freedata = 1; ielem->data = cpystr(subject); ielem->datalen = strlen(subject); if(pico_usingcolor() && ps_global->VAR_IND_SUBJ_FORE_COLOR && ps_global->VAR_IND_SUBJ_BACK_COLOR){ ielem->freecolor = 1; ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR); } } retsubj = cpystr(subject); } if(braces){ if(left_brace) fs_give((void **) &left_brace); if(right_brace) fs_give((void **) &right_brace); } return(retsubj); } /* * This means we should ensure that all data ends up being UTF-8 data. * That covers the data in ice ielems and str. */ void from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_S *ice) { char *field, *newsgroups, *border, *p, *fptr = NULL, *q = NULL; ADDRESS *addr; int width = -1; int depth = 0, mult = 2; PINETHRD_S *thd, *thdorig; if(THREADING() && (ps_global->thread_disp_style == THREAD_INDENT_FROM1 || ps_global->thread_disp_style == THREAD_INDENT_FROM2 || ps_global->thread_disp_style == THREAD_STRUCT_FROM)){ if(pith_opt_truncate_sfstr && (*pith_opt_truncate_sfstr)()){ IFIELD_S *ourifield = NULL; if(ice && ice->ifield){ /* move to last ifield, the one we're working on */ for(ourifield = ice->ifield; ourifield && ourifield->next; ourifield = ourifield->next) ; } if(ourifield && ourifield->width > 0) width = ourifield->width; } if(width < 0) width = strsize-1; width = MIN(width, strsize-1); thdorig = thd = fetch_thread(idata->stream, idata->rawno); border = str + width; if(pith_opt_condense_thread_cue) width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width, thd && thd->next && get_lflag(idata->stream, NULL,idata->rawno, MN_COLL)); fptr = str; if(thd) while(thd->parent && (thd = fetch_thread(idata->stream, thd->parent))) depth++; if(depth > 0){ if(ps_global->thread_disp_style == THREAD_INDENT_FROM1) mult = 1; fptr += (mult*depth); for(thd = thdorig, p = str + mult*depth - mult; thd && thd->parent && p >= str; thd = fetch_thread(idata->stream, thd->parent), p -= mult){ if(p + mult >= border && !q){ if(width >= 4 && depth < 100){ snprintf(str, width+1, "%*s[%2d]", width-4, "", depth); q = str + width-4; } else if(width >= 5 && depth < 1000){ snprintf(str, width+1, "%*s[%3d]", width-5, "", depth); q = str + width-5; } else{ snprintf(str, width+1, "%s", repeat_char(width, '.')); q = str; } border = q; fptr = NULL; } if(p + 1 < border){ p[0] = p[1] = ' '; if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){ /* * WARNING! * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0] * is ascii. */ if(thd == thdorig && !thd->branch) p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0]; else if(thd == thdorig || thd->branch) p[0] = '|'; if(thd == thdorig) p[1] = '-'; } } else if(p < border){ p[0] = ' '; if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){ /* * WARNING! * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0] * is ascii. */ if(thd == thdorig && !thd->branch) p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0]; else if(thd == thdorig || thd->branch) p[0] = '|'; } } } } } else fptr = str; if(fptr){ strsize -= (fptr - str); switch(ctype){ case iFromTo: case iFromToNotNews: if(!(addr = fetch_from(idata)) || address_is_us(addr, ps_global)){ if(strsize-1 <= 4){ strncpy(fptr, "To: ", strsize-1); fptr[strsize-1] = '\0'; break; } else{ if((field = ((addr = fetch_to(idata)) ? "To" : (addr = fetch_cc(idata)) ? "Cc" : NULL)) && set_index_addr(idata, field, addr, "To: ", strsize-1, fptr)) break; if(ctype == iFromTo && (newsgroups = fetch_newsgroups(idata)) && *newsgroups){ snprintf(fptr, strsize, "To: %-*.*s", (int)(strsize-1-4), (int)(strsize-1-4), newsgroups); break; } /* else fall thru to From: */ } } /* else fall thru to From: */ if(idata->bogus) break; case iFrom: set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr); break; case iAddress: case iMailbox: if((addr = fetch_from(idata)) && addr->mailbox && addr->mailbox[0]){ char *mb = NULL, *hst = NULL, *at = NULL; size_t len; mb = addr->mailbox; if(ctype == iAddress && addr->host && addr->host[0] && addr->host[0] != '.'){ at = "@"; hst = addr->host; } len = strlen(mb); if(!at || strsize-1 <= len) snprintf(fptr, strsize, "%-*.*s", (int)(strsize-1), (int)(strsize-1), mb); else snprintf(fptr, strsize, "%s@%-*.*s", mb, (int)(strsize-1-len-1), (int)(strsize-1-len-1), hst); } break; default: break; } } replace_tabs_by_space(str); } /* * Set up the elements contained in field so that they take up the * whole field width. Data is assumed to be UTF-8. */ void set_ielem_widths_in_field(IFIELD_S *ifield) { IELEM_S *ielem = NULL; int datawidth, fmtwidth; if(!ifield) return; fmtwidth = ifield->width; for(ielem = ifield->ielem; ielem && fmtwidth > 0; ielem = ielem->next){ if(!ifield->leftadj && ielem->next){ dprint((1, "set_ielem_widths_in_field(%d): right adjust with multiple elements, NOT SUPPOSED TO HAPPEN!\n", (int) ifield->ctype)); assert(0); } datawidth = (int) utf8_width(ielem->data); if(datawidth >= fmtwidth || !ielem->next){ set_print_format(ielem, fmtwidth, ifield->leftadj); fmtwidth = 0; } else{ set_print_format(ielem, datawidth, ifield->leftadj); fmtwidth -= datawidth; } } } /* * Simple hash function from K&R 2nd edition, p. 144. * * This one is modified to never return 0 so we can use that as a special * value. Also, LINE_HASH_N fits in an unsigned long, so it too can be used * as a special value that can't be returned by line_hash. */ unsigned long line_hash(char *s) { unsigned long hashval; for(hashval = 0; *s != '\0'; s++) hashval = *s + 31 * hashval; hashval = hashval % LINE_HASH_N; if(!hashval) hashval++; return(hashval); } /* * Returns nonzero if considered hidden, 0 if not considered hidden. */ int msgline_hidden(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int flags) { int ret; if(flags & MH_ANYTHD){ ret = ((any_lflagged(msgmap, MN_HIDE) > 0) && get_lflag(stream, msgmap, msgno, MN_HIDE)); } else if(flags & MH_THISTHD && THREADING() && sp_viewing_a_thread(stream)){ ret = (get_lflag(stream, msgmap, msgno, MN_HIDE) || !get_lflag(stream, msgmap, msgno, MN_CHID2)); } else{ if(THREADING() && sp_viewing_a_thread(stream)){ ret = (get_lflag(stream, msgmap, msgno, MN_HIDE) || !get_lflag(stream, msgmap, msgno, MN_CHID2) || get_lflag(stream, msgmap, msgno, MN_CHID)); } else if(THRD_INDX()){ /* * If this message is in the collapsed part of a thread, * it's hidden. It must be a top-level of a thread to be * considered visible. Even if it is top-level, it is only * visible if some message in the thread is not hidden. */ if(get_lflag(stream, msgmap, msgno, MN_CHID)) /* not top */ ret = 1; else{ unsigned long rawno; PINETHRD_S *thrd = NULL; rawno = mn_m2raw(msgmap, msgno); if(rawno) thrd = fetch_thread(stream, rawno); ret = !thread_has_some_visible(stream, thrd); } } else{ ret = ((any_lflagged(msgmap, MN_HIDE | MN_CHID) > 0) && get_lflag(stream, msgmap, msgno, MN_HIDE | MN_CHID)); } } dprint((10, "msgline_hidden(%ld): %s\n", msgno, ret ? "HID" : "VIS")); return(ret); } void adjust_cur_to_visible(MAILSTREAM *stream, MSGNO_S *msgmap) { long n, cur; int dir; cur = mn_get_cur(msgmap); /* if current is hidden, adjust */ if(cur >= 1L && cur <= mn_get_total(msgmap) && msgline_hidden(stream, msgmap, cur, 0)){ dir = mn_get_revsort(msgmap) ? -1 : 1; for(n = cur; ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap))) && (n >= 1L && n <= mn_get_total(msgmap)) && msgline_hidden(stream, msgmap, n, 0); n -= dir) ; if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap))) && (n >= 1L && n <= mn_get_total(msgmap))) mn_reset_cur(msgmap, n); else{ /* no visible in that direction */ for(n = cur; ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap))) && (n >= 1L && n <= mn_get_total(msgmap)) && msgline_hidden(stream, msgmap, n, 0); n += dir) ; if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap))) && (n >= 1L && n <= mn_get_total(msgmap))) mn_reset_cur(msgmap, n); /* else trouble! */ } } } void setup_for_index_index_screen(void) { format_index_line = format_index_index_line; setup_header_widths = setup_index_header_widths; } void setup_for_thread_index_screen(void) { format_index_line = format_thread_index_line; setup_header_widths = setup_thread_header_widths; } unsigned long ice_hash(ICE_S *ice) { char buf[MAX_SCREEN_COLS+1]; buf[0] = '\0'; if(ice) simple_index_line(buf, sizeof(buf), ice, 0L); buf[sizeof(buf) - 1] = '\0'; return(line_hash(buf)); } char * left_adjust(int width) { return(format_str(width, 1)); } char * right_adjust(int width) { return(format_str(width, 0)); } /* * Returns allocated and filled in format string. */ char * format_str(int width, int left) { char *format; size_t len; len = PRINT_FORMAT_LEN(width,left) * sizeof(char); format = (char *) fs_get(len + 1); copy_format_str(width, left, format, len); format[len] = '\0'; return(format); } /* * Put the left or right adjusted format string of width width into * dest. Dest is of size n+1. */ char * copy_format_str(int width, int left, char *dest, int n) { char *p; p = int2string(width); snprintf(dest, n+1, "%%%s%s.%ss", left ? "-" : "", p, p); dest[n] = '\0'; return(dest); } /* * Sets up the print_format string to be width wide with left or right * adjust. Takes care of memory freeing and allocation. */ void set_print_format(IELEM_S *ielem, int width, int leftadj) { if(ielem){ ielem->wid = width; if(ielem->print_format){ /* is there enough room? */ if(ielem->freeprintf < PRINT_FORMAT_LEN(width,leftadj)+1){ fs_resize((void **) &ielem->print_format, (PRINT_FORMAT_LEN(width,leftadj)+1) * sizeof(char)); ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char); } copy_format_str(width, leftadj, ielem->print_format, PRINT_FORMAT_LEN(width,leftadj)); } else{ ielem->print_format = leftadj ? left_adjust(width) : right_adjust(width); ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char); } } }