#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: mailindx.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
#endif

/* ========================================================================
 * Copyright 2013-2020 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 : "<null>"));
				    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 : "<null>"));
			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;
    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 <carl@ucsd.edu> */
	      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;

    /* 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);
	}
    }
}
