#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: mailview.c,v 4.132 1994/10/10 20:00:43 mikes Exp $";
#endif
/*----------------------------------------------------------------------

            T H E    P I N E    M A I L   S Y S T E M

   Laurence Lundblade and Mike Seibel
   Networks and Distributed Computing
   Computing and Communications
   University of Washington
   Administration Builiding, AG-44
   Seattle, Washington, 98195, USA
   Internet: lgl@CAC.Washington.EDU
             mikes@CAC.Washington.EDU

   Please address all bugs and comments to "pine-bugs@cac.washington.edu"

   Copyright 1989-1994  University of Washington

    Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee to the University of
   Washington is hereby granted, provided that the above copyright notice
   appears in all copies and that both the above copyright notice and this
   permission notice appear in supporting documentation, and that the name
   of the University of Washington not be used in advertising or publicity
   pertaining to distribution of the software without specific, written
   prior permission.  This software is made available "as is", and
   THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
   WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
   NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
   INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
   LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
   (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
   WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  
   Pine and Pico are trademarks of the University of Washington.
   No commercial use of these trademarks may be made without prior
   written permission of the University of Washington.

   Pine is in part based on The Elm Mail System:
    ***********************************************************************
    *  The Elm Mail System  -  Revision: 2.13                             *
    *                                                                     *
    * 			Copyright (c) 1986, 1987 Dave Taylor              *
    * 			Copyright (c) 1988, 1989 USENET Community Trust   *
    ***********************************************************************
 

  ----------------------------------------------------------------------*/

/*======================================================================
     mailview.c
     Implements the mailview screen
     Also includes scrolltool used to display help text
  ====*/


#include "headers.h"


/*----------------------------------------------------------------------
    Saved state for scrolling text 
 ----*/
typedef struct scroll_text {
    void *text;          /* Original text */
    char **text_lines;   /* Lines to display */
    FILE  *findex;	 /* file containing line offsets in another file */
    char  *fname;	 /* name of file containing line offsets */
    int top_text_line;   /* index into text array of line on top of screen */
    int num_lines;       /* Calculated number lines of text to display */
    int lines_allocated; /* size of array text_lines */
    int screen_width;    /* screen width of current formatting */
    int screen_start_line; /* First line on screen that we scroll text on */
    int screen_other_lines;/* Line ons screen not used for scroll text */
    short *line_lengths;   /* Lengths of lines for display, not \0 terminatd*/
    SourceType source;	 /* How to interpret "text" field */
} SCROLL_S;

static SCROLL_S *st;		/* local, yet global scroll state */

#define	PGSIZE (ps_global->ttyo->screen_rows - st->screen_other_lines)

#define TYPICAL_BIG_MESSAGE_LINES 200 

#ifdef ANSI
void       format_envelope(ENVELOPE *, gf_io_t);
void	   set_scroll_text(void *, int, int, int, SourceType);
void	   scroll_scroll_text(int, int);
static int print_to_printer(void *, SourceType, char *);
int	   search_scroll_text(int, char *);
void       describe_mime(BODY *, char *, int, int);
void       format_mime_size(char *, BODY *);
void       zero_atmts(ATTACH_S *);
void	   zero_scroll_text(void);
void	   ScrollFile(int);
int	   make_file_index();
char      *show_multipart(MESSAGECACHE *, int);
MimeShow   mime_show(BODY *);
char      *part_desc(char *,BODY *, int);
SCROLL_S  *new_scroll_state();
int	   search_text(int, int, char *);
void	   format_scroll_text(void);
int	   get_scroll_text_lines(void);
void	   redraw_scroll_text(void);
#ifdef	_WINDOWS
int	   mswin_readscrollbuf(int);
#endif

#else
void       format_envelope();
void	   set_scroll_text();
void       scroll_scroll_text();
static int print_to_printer();
int        search_scroll_text();
void       describe_mime();
void       format_mime_size();
void       zero_atmts();
void	   zero_scroll_text();
void	   ScrollFile();
int	   make_file_index();
char      *show_multipart();
MimeShow   mime_show();
char      *part_desc();
SCROLL_S  *new_scroll_state();
int	   search_text();
void	   format_scroll_text();
int	   get_scroll_text_lines();
void	   redraw_scroll_text();
#endif



/*----------------------------------------------------------------------
     Format a buffer with the text of the current message for browser

    Args: ps - pine state structure
  
  Result: The scrolltool is called to display the message

  Loop here viewing mail until the folder changed or a command takes
us to another screen. Inside the loop the message text is fetched and
formatted into a buffer allocated for it.  These are passed to the
scrolltool(), that displays the message and executes commands. It
returns when it's times to display a different message when a we
change folders, or when it's time for a different screen. We also
break out fo the big loop when there is no more mail to read.
  ---*/

void
mail_view_screen(ps)
     struct pine *ps;
{
    char            last_was_full_header = 0;
    long            last_message_viewed;
    MESSAGECACHE   *mc;
    ENVELOPE       *env;
    BODY           *body;
    STORE_S        *store;
    gf_io_t         pc;
    SourceType	    src = CharStar;

    dprint(1, (debugfile, "\n\n  -----  MAIL VIEW  -----\n"));

    if(mn_get_total(ps->msgmap) <= 0 || ps->mail_stream == NULL) {
        q_status_message(0, 0, 3, "No messages to read!");
	/*
	 * Don't just come back here if there aren't any messages!
	 */
	ps->next_screen = (ps->prev_screen != mail_view_screen)
			    ? ps->prev_screen : mail_index_screen;
	return;
    }

    if(mn_get_cur(ps->msgmap) <= 0L)
      mn_set_cur(ps->msgmap, 1L);
   
    last_message_viewed = -1;

    /*------ Loop viewing messages ------------------*/
    do {
        clear_index_cache_ent(mn_get_cur(ps->msgmap));

        /* BUG, should check this return code */
	(void)mail_fetchstructure(ps->mail_stream,
				  mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap)),
				  NULL);

	mc = mail_elt(ps->mail_stream,
		      mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap)));

	if(mc && !mc->seen)	/* count state change in check_point */
	  check_point_change();

#ifdef	DOS
	/* 
	 * Handle big text for DOS here
	 *
	 * this size could be adaptive if we don't find a good
	 * solution to the elt caching problem with mrc.
	 *
	 * judging from 1000+ message folders around here, it looks
	 * like 9X% of messages fall in the < 8k range, so it
	 * seems like this is as good a place to draw the line as any
	 *
	 * We ALSO need to divert all news articles we read to secondary
	 * storage as its possible we're using c-client's NNTP driver
	 * which returns BOGUS sizes UNTIL the whole thing is fetched.
	 * Note: this is more a deficiency in NNTP than in c-client.
	 */
	if(mc->rfc822_size > MAX_MSG_INCORE 
	   || strcmp(ps->mail_stream->dtb->name, "nntp") == 0){
	    src = FileStar;
	}
	else{
	    src = CharStar;
	}
#endif
	store = so_get(src, NULL, EDIT_ACCESS);
	gf_set_so_writec(&pc, store);

	body = NULL;
	env = mail_fetchstructure(ps->mail_stream,
				  mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap)),
				  &body);

/* BUG: should check this return code!?! */
	(void) format_message(mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap)),
			      env, body,
			      (last_message_viewed != mn_get_cur(ps->msgmap)
			       || last_was_full_header == 1) ? FM_NEW_MESS : 0,
			      pc);

        last_message_viewed  = mn_get_cur(ps->msgmap);
        last_was_full_header = ps->full_header;

        ps->next_screen = SCREEN_FUN_NULL;

        scrolltool(so_text(store), "MESSAGE TEXT",
                   (int *)NULL, MessageText, src, (ATTACH_S *)NULL);

	so_give(&store);	/* free resources associated with store */

        if(mn_get_total(ps->msgmap) == 0) {
            q_status_message(0, 2, 3, "\007No messages to read!");
            if(ps->next_screen == SCREEN_FUN_NULL)
              ps->next_screen = ps->prev_screen;
            ps->prev_screen = mail_view_screen;
            return;
        }
    }
    while(ps->next_screen == SCREEN_FUN_NULL);

    ps->prev_screen = mail_view_screen;
    return;
}

    


/*----------------------------------------------------------------------
    Add lines to the attachments structure
    
  Args: body   -- body of the part being described
        prefix -- The prefix for numbering the parts
        num    -- The number of this specific part
        should_show -- Flag indicating which of alternate parts should be shown

Result: The ps_global->attachments data structure is filled in. This
is called recursively to descend through all the parts of a message. 
The description strings filled in are malloced and should be freed.

  ----*/
void
describe_mime(body, prefix, num, should_show)
     BODY *body;
     char *prefix;
     int   num, should_show;
{
    PART      *part;
    char       numx[50], string[200], *description;
    int        n, alt_to_show, has_name, use_viewer;
    ATTACH_S  *a;
    MimeShow   alts[50]; /* BUG -- no bounds checking on this */
    PARAMETER *param;

    if(body == NULL)
      return;

    if(body->type == TYPEMULTIPART) {
        if(strucmp(body->subtype, "alternative") == 0) {
            /*---- Figure out which alternative part to display ---*/
            for(part=body->contents.part,n=1; part!=NULL; part=part->next, n++)
              alts[n] = mime_show(&(part->body));
            n--;
            while(n > 1 && alts[n] == ShowNone)
              n--;
            alt_to_show = n;
        } else {
            /*--- Not alternative parts, display them all ----*/
            alt_to_show = 0;
        }
        n = 1;
        for(part = body->contents.part; part != NULL; part = part->next, n++) {
            sprintf(numx, "%s%d.", prefix, n);
            describe_mime(&(part->body),
                          part->body.type == TYPEMULTIPART ? numx : prefix,
                          n,
                          n == alt_to_show || alt_to_show == 0 ? 1 : 0);
        }
    } else {
        for(a = ps_global->atmts; a->description != NULL; a++);
        if(a - ps_global->atmts + 1 >= ps_global->atmts_allocated) {
            ps_global->atmts_allocated *=2;
            fs_resize((void **)&(ps_global->atmts),
                       ps_global->atmts_allocated * sizeof(ATTACH_S));
            a = &ps_global->atmts[ps_global->atmts_allocated/2 - 1];
        }

        format_mime_size(a->size, body);

	description = (body->description)
		        ? body->description
			: (body->type == TYPEMESSAGE && body->subtype
			   && strucmp(body->subtype, "rfc822") == 0
			   && body->contents.msg.env->subject)
			   ? body->contents.msg.env->subject
			   : NULL;

        sprintf(string, "%s%s%.*s%s",
                type_desc(body->type, body->subtype, body->parameter, 0),
                description ? ", \"" : "",
                sizeof(string) - 20, 
                description ? description : "",
                description ? "\"": "");

        a->description = cpystr(string);
        a->body        = body;
	/*
	 * Make sure we have the tools available to display the
	 * type/subtype, *AND* that we can decode it if needed...
	 */
        a->can_display = (mime_can_display(body->type, body->subtype,
                                          body->parameter, &use_viewer)
			  && body->encoding < ENCOTHER);
	a->use_external_viewer = use_viewer;
	for(param = a->body->parameter; 
	    param != NULL && strucmp(param->attribute,"name") != 0;
	    param = param->next)
	      ;
	if (param != NULL && strucmp(param->attribute,"name") == 0)
	    has_name = 1;
	else
	    has_name = 0;
        a->shown = ((a->body->type==TYPETEXT && !has_name) ||
		a->body->type==TYPEMESSAGE) && a->can_display && should_show;
        sprintf(a->number, "%s%d",prefix, num);
        (a+1)->description = NULL;
        if(body->type == TYPEMESSAGE
	   && body->subtype && strucmp(body->subtype, "rfc822") == 0){
	    body = body->contents.msg.body;
	    sprintf(numx, "%s%d.", prefix, num);
	    describe_mime(body, numx, 1, should_show);
        }
    }
}



/*----------------------------------------------------------------------
   Zero out the attachments structure and free up storage
  ----*/
void
zero_atmts(atmts)
     ATTACH_S *atmts;
{
    ATTACH_S *a;

    for(a = atmts; a->description != NULL; a++)
      fs_give((void **)&(a->description)); 

    atmts->description = NULL;
}


char *
body_type_names(t)
     int t;
{
    static char *body_types[] = {"Text", "Multipart", "Message", 
                                 "Application", "Audio", "Image",
                                 "Video"};
    return((t > -1 && t < 7) ? body_types[t] : "Other");
}


/*
 * Mapping table use to neatly display charset parameters
 */
static struct set_names {
    char *rfcname,
	 *fullname,
	 *shortname;
} charset_names[] = {
    {"us-ascii", "Text", "Text"},
    {"iso-8859-1", "Latin 1 text (ISO-8859-1)",
	"Latin 1 text"},
    {"iso-8859-2", "Latin 2 text (ISO-8859-2)",
	"Latin 2 text"},
    {"iso-8859-3", "Latin 3 text (ISO-8859-3)",
	"Latin 3 text"},
    {"iso-8859-4", "Latin 4 text (ISO-8859-4)",
	"Latin 4 text"},
    {"iso-8859-5", "Latin & Cyrillic text (ISO-8859-5)",
	"Latin & Cyrillic text"},
    {"iso-8859-6", "Latin & Arabic text (ISO-8859-6)",
	"Latin & Arabic text"},
    {"iso-8859-7", "Latin & Greek text (ISO-8859-7)",
       "Latin & Greek text"},
    {"iso-8859-8", "Latin & Hebrew text (ISO-8859-8)",
       "Latin & Hebrew text"},
    {"iso-8859-9", "Latin 5 text (ISO-8859-9)",
       "Latin 5 text"},
    {"x-iso-8859-10", "Latin 6 text (ISO-8859-10)",
       "Latin 6 text"},
    {"koi8-r", "Latin & Russian text (KOI8-R)",
       "Latin & Russian text"},
    {"viscii", "Latin & Vietnamese text (VISCII)",
       "Latin & Vietnamese text"},
    {"iso-2022-jp", "Latin & Japanese text (ISO-2022-JP)",
       "Latin & Japanese text"},
    {"iso-2022-kr", "Latin & Korean text (ISO-2022-KR)",
       "Latin & Korean text"},
    {"unicode-1-1", "Unicode text (UNICODE-1-1)",
       "Unicode text"},
    {"unicode-1-1-utf-7", "Mail-safe Unicode text (UNICODE-1-1-UTF-7)",
       "Mail-safe Unicode text"},
    {"iso-2022-jp-2", "Multilingual text (ISO-2022-JP-2)",
       "Multilingual text"},
    {NULL, NULL, NULL}
};


/*----------------------------------------------------------------------
  Return a nicely formatted discription of the type of the part
 ----*/

char *
type_desc(type, subtype, params, full)
     int type, full;
     char *subtype;
     PARAMETER *params;
{
    static char type_d[200];
    int		i;

    switch(type) {
      case TYPETEXT:
        while(params != NULL && strucmp(params->attribute,"charset") != 0)
          params = params->next;
        if(params != NULL) {
	    for(i = 0; charset_names[i].rfcname; i++)
	      if(!strucmp(params->value, charset_names[i].rfcname)){
		  strcpy(type_d, full ? charset_names[i].fullname
				      : charset_names[i].shortname);
		  break;
	      }

	    if(!charset_names[i].rfcname){
		strcpy(type_d, "Unknown text");
		if(full)
		  sprintf(type_d+strlen(type_d), " \"%.100s\"",params->value);
	    }

        } else {
            strcpy(type_d, "Text");
        }
        break;

      case TYPEAPPLICATION:
        if(subtype != NULL && strucmp(subtype, "octet-stream") == 0){
            while(params != NULL && strucmp(params->attribute,"name") != 0)
              params = params->next;
            if(full)
              sprintf(type_d, "Attached file \"%s\"",
                      params != NULL && params->value != NULL ?
                                                    params->value : "");
            else
              sprintf(type_d, "File \"%s\"",
                      params != NULL && params->value != NULL ?
                                                    params->value : "");
        } else if(subtype != NULL && strucmp(subtype, "postscript") == 0)
          strcpy(type_d, "PostScript");
        else
          sprintf(type_d, "Application/%.100s", subtype != NULL ? subtype: "");
        break;

      default:
        if(full)
          sprintf(type_d, "%s/%.100s", body_type_names(type),
                 subtype != NULL ? subtype: "");
        else
          strcpy(type_d, body_type_names(type));
        break;
    }

    return(type_d);
}
     

void
format_mime_size(string, b)
     char *string;
     BODY *b;
{
    char tmp[10], *p;
    if(b->type == TYPETEXT) {
        sprintf(string, "%s lines", comatose(b->size.lines));
    } else {
        strcpy(string, byte_string(b->size.bytes));
        for(p = string; *p && (isdigit(*p) || ispunct(*p)); p++);
        sprintf(tmp, " %-5.5s",p);
        strcpy(p, tmp);
    }
}

        

/*----------------------------------------------------------------------
   Determine if we can show all, some or none of the parts of a body

Args: body --- The message body to check

Returns: ShowAll, ShowPart or ShowNone depending on how much of the body
    can be shown 
 ----*/     
MimeShow
mime_show(body)
     BODY *body;
{
    int   sp, sn, sa;
    PART *p;

    if(body == NULL)
      return(ShowNone);

    switch(body->type) {
      default:
        return(mime_can_display(body->type,
		    body->subtype,body->parameter,(int *)NULL)== 1 ?
               ShowAll:
               ShowNone);

      case TYPEMESSAGE:
        return(mime_show(body->contents.msg.body) == ShowAll ? 
               ShowAll:
               ShowParts);

      case TYPEMULTIPART:
        sp = sn = sa = 0;
        for(p = body->contents.part; p != NULL; p = p->next) {
            switch(mime_show(&(p->body))) {
              case ShowAll:
                sa++;
                break;
              case ShowParts:
                sp++;
                break;
              case ShowNone:
                sn++;
                break;
            }
        }

        if(sa == 0)
          if(sp == 0)
            return(ShowNone);
          else
            return(ShowParts);
        else
          if(sn == 0)
            return(ShowAll);
          else
            return(ShowParts);
    }
}
        


/*----------------------------------------------------------------------
   Format a message message for viewing

 Args: msgno -- The number of the message to view
       env   -- pointer to the message's envelope
       body  -- pointer to the message's body
       src   -- where we can expect the text to come from
       dname -- name of destination if it's a file
       dest  -- where the formatted message is to be returned. valid
                 types are CharStar and FileStar
       flgs  -- possible flags:
                FM_NEW_MESS -- flag indicating a different message being
			       formatted than was formatted last time 
			       function was called
		FM_DO_PRINT -- Indicates formatted text is bound for
			       something other than display by pine
			       (printing, export, etc)

Result: Returns true if no problems encountered, else false.

First the envelope is formatted; next a list of all attachments is
formatted if there are more than one. Then all the body parts are
formatted, fetching them as needed. This includes headers of included
message. Richtext is also formatted. An entry is made in the text for
parts that are not displayed or can't be displayed.  source indicates 
how and where the caller would like to have the text formatted.

 ----*/    
int
format_message(msgno, env, body, flgs, pc)
    long         msgno;
    ENVELOPE    *env;
    BODY        *body;
    int          flgs;
    gf_io_t      pc;
{
    char     *decode_error, *h, *fields[6];
    ATTACH_S *a;
    int       show_parts, error_found = 0;
    gf_io_t   gc;

    show_parts = !(flgs&FM_DO_PRINT);

    /*---- format and copy envelope ----*/
    if(ps_global->full_header
       && (h = mail_fetchheader(ps_global->mail_stream, msgno))){
	if(!messages_queued(NULL))
	  q_status_message(0, 1, 3,
		       "Full header mode ON.  All header text being included");

	while(*h){
	    /*
	     * convert network newlines to local convention...
	     */
	    if(*h == '\015' && *(h+1) == '\012'){
		if(gf_puts(NEWLINE, pc))
		  h += 2;
		else
		  goto write_error;
	    }
	    else if(!(*pc)(*h++))
	      goto write_error;

	    if(*(h-1) == '\012' && *h == '\015')
	      break;
	}
    }
    else{
	format_envelope(env, pc);

	fields[0] = "Resent-Date";
	fields[1] = "Resent-From";
	fields[2] = "Resent-To";
	fields[3] = "Resent-cc";
	fields[4] = "Resent-Subject";
	fields[5] = NULL;
	if(h = xmail_fetchheader_lines(ps_global->mail_stream, msgno, fields)){
	    char *p = h;

	    while(*p){
		if(*p == '\015' && *(p+1) == '\012'){
		    if(gf_puts(NEWLINE, pc))
		      p += 2;
		    else
		      goto write_error;
		}
		else if(!(*pc)(*p++))
		  goto write_error;
	    }

	    fs_give((void **)&h);
	}
    }

    if(body == NULL) {
        /*--- Server is not an IMAP2bis, It can't parse MIME
              so we just show the text here. Hopefully the 
              message isn't a MIME message 
          ---*/
	void *text2;
#ifdef	DOS
	char *append_file_name;

	/* for now, always fetch to disk.  This could be tuned to
	 * check for message size, then decide to deal with it on disk...
	 */
	mail_parameters(ps_global->mail_stream, SET_GETS, (void *)dos_gets);
	if(!(append_file_name = temp_nam(NULL, "pv"))
	   || !(append_file = fopen(append_file_name, "w+b"))){
	    if(append_file_name)
	      fs_give((void **)&append_file_name);

	    q_status_message1(0,2,2,"Can't make temp file: %s",
			      error_description(errno));
	    return(0);
	}
#endif

        if(text2 = (void *)mail_fetchtext(ps_global->mail_stream, msgno)){
	    if(!gf_puts(NEWLINE, pc))		/* write delimiter */
	      goto write_error;
#ifdef	DOS
	    gf_set_readc(&gc, append_file, 0L, FileStar);
#else
	    gf_set_readc(&gc, text2, (unsigned long)strlen(text2), CharStar);
#endif
	    gf_filter_init();
	    gf_link_filter(gf_nvtnl_local);
	    if(decode_error = gf_pipe(gc, pc)){
                sprintf(tmp_20k_buf, "%s%s    [Formatting error: %s]%s",
			NEWLINE, NEWLINE, decode_error, NEWLINE);
		if(!gf_puts(tmp_20k_buf, pc))
		  goto write_error;
	    }
	}

#ifdef	DOS
	fclose(append_file);			/* clean up tmp file */
	append_file = NULL;
	unlink(append_file_name);
	fs_give((void **)&append_file_name);
	mail_gc(ps_global->mail_stream, GC_TEXTS);
	mail_parameters(ps_global->mail_stream, SET_GETS, (void *)NULL);
#endif

	if(!text2){
	    if(!gf_puts(NEWLINE, pc)
	       || !gf_puts("    [ERROR fetching text of message]", pc)
	       || !gf_puts(NEWLINE, pc)
	       || !gf_puts(NEWLINE, pc))
	      goto write_error;
	}

	return(1);
    }

    if(flgs&FM_NEW_MESS) {
        zero_atmts(ps_global->atmts);
        describe_mime(body, "", 1, 1);
    }

    /*---- Calculate the approximate length of what we've got to show  ---*/

    /*=========== Format the header into the buffer =========*/
    /*----- First do the list of parts/attachments if needed ----*/
    if(show_parts && ps_global->atmts[1].description != NULL) {
	int max_num_l = 0, max_size_l = 0;

	if(!gf_puts("Parts/attachments:", pc) || !gf_puts(NEWLINE, pc))
	  goto write_error;

        /*----- Figure display lengths for nice display -----*/
        for(a = ps_global->atmts; a->description != NULL; a++) {
	    if(strlen(a->number) > max_num_l)
	      max_num_l = strlen(a->number);
	    if(strlen(a->size) > max_size_l)
	      max_size_l = strlen(a->size);
	}

        /*----- Format the list of attachments -----*/
        for(a = ps_global->atmts; a->description != NULL; a++) {
	    int i = ps_global->ttyo->screen_cols - max_num_l - max_size_l
		     - 14 - strlen(NEWLINE);
            sprintf(tmp_20k_buf, "   %-*.*s %s  %*.*s  %-*.*s%s",
                    max_num_l, max_num_l, a->number,
                    (a->shown ? "Shown" : a->can_display ? "  OK " : "     "),
                    max_size_l, max_size_l, a->size, i, i, a->description,
		    NEWLINE);
	    if(!gf_puts(tmp_20k_buf, pc))
	      goto write_error;
        }

	if(!gf_puts("----------------------------------------", pc)
	   || !gf_puts(NEWLINE, pc))
	  goto write_error;
    }

    if(!gf_puts(NEWLINE, pc))		/* write delimiter */
      goto write_error;

    show_parts = 0;

    /*======== Now loop through formatting all the parts =======*/
    for(a = ps_global->atmts; a->description != NULL; a++) {

        if(!a->shown) {
	    if(!gf_puts(part_desc(a->number, a->body,
				  (flgs&FM_DO_PRINT)?3:a->can_display ? 1:2),
			pc)
	       || ! gf_puts(NEWLINE, pc))
	      goto write_error;

            continue;
        } 

        switch(a->body->type){

          case TYPETEXT:
	    /*
	     * Don't write our delimiter if this text part is
	     * the first part of a message/rfc822 segment...
	     */
	    if(show_parts && a != ps_global->atmts 
	       && a[-1].body && a[-1].body->type != TYPEMESSAGE){
		sprintf(tmp_20k_buf, "%s  [ Part %s: \"%.55s\" ]%s%s",
			NEWLINE, a->number, 
			a->body->description ? a->body->description
					     : "Attached Text",
			NEWLINE, NEWLINE);
		if(!gf_puts(tmp_20k_buf, pc))
		  goto write_error;
	    }

	    error_found += decode_text(a, msgno, pc,
				       (flgs&FM_DO_PRINT) ? QStatus : InLine,
				       !(flgs&FM_DO_PRINT));
            break;

          case TYPEMESSAGE:
            sprintf(tmp_20k_buf, "%s  [ Part %s: \"%.55s\" ]%s%s",
		    NEWLINE, a->number, 
                    a->body->description ? a->body->description
					 : "Included Message",
		    NEWLINE, NEWLINE);
  	    if(!gf_puts(tmp_20k_buf, pc))
	      goto write_error;

	    if(a->body->subtype && strucmp(a->body->subtype, "rfc822") == 0){
		format_envelope(a->body->contents.msg.env, pc);
	    }
            else if(a->body->subtype 
		    && strucmp(a->body->subtype, "external-body") == 0) {
		if(!gf_puts("This part is not included and can be ", pc)
		   || !gf_puts("fetched as follows:", pc)
		   || !gf_puts(NEWLINE, pc)
		   || !gf_puts(display_parameters(a->body->parameter), pc))
		  goto write_error;
            }
	    else
	      error_found += decode_text(a, msgno, pc, 
					 (flgs&FM_DO_PRINT) ? QStatus : InLine,
					 !(flgs&FM_DO_PRINT));

	    if(!gf_puts(NEWLINE, pc))
	      goto write_error;

            break;

          default:
	    if(!gf_puts(part_desc(a->number,a->body,(flgs&FM_DO_PRINT) ? 3:1),
			pc))
	      goto write_error;
        }

	show_parts++;
    }

    return(!error_found);

  write_error:
    if(flgs & FM_DO_PRINT)
      q_status_message1(1, 2, 4, "Error writing message: %s", 
			error_description(errno));
    return(0);
}



/*----------------------------------------------------------------------
   Handle fetching and filtering a text message segment

Args: att   -- segment to fetch
      msgno -- message number segment is a part of
      pc    -- function to write characters from segment with
      style -- Indicates special handling for error messages
      display_bound -- Indicates special necessary filtering

Returns: 1 if errors encountered, 0 if everything went A-OK

 ----*/     
int
decode_text(att, msgno, pc, style, display_bound)
    ATTACH_S       *att;
    long            msgno;
    gf_io_t         pc;
    DetachErrStyle  style;
    int		    display_bound;
{
    int                match;
    PARAMETER         *param;
    filter_t	       aux_filter[4];
    int		       filtcnt = 0, error_found = 0;
    char	      *err;

    if(att->body->subtype){
	if(!strucmp(att->body->subtype, "richtext")) {
	    gf_rich2plain_opt(!display_bound);	/* maybe strip everything! */
	    aux_filter[filtcnt++] = gf_rich2plain;
	    if(!display_bound) {
		gf_wrap_filter_opt(75);  /* width to use for file or printer */
		aux_filter[filtcnt++] = gf_wrap;
	    }
	}
	else if(!strucmp(att->body->subtype, "enriched")) {
	    gf_enriched2plain_opt(!display_bound);
	    aux_filter[filtcnt++] = gf_enriched2plain;
	    if(!display_bound) {
		gf_wrap_filter_opt(75);  /* width to use for file or printer */
		aux_filter[filtcnt++] = gf_wrap;
	    }
	}
    }

    for(param = att->body->parameter; 
		param != NULL && strucmp(param->attribute,"charset") != 0;
		param = param->next)
	;

    match =  match_charset(param != NULL ? param->value : NULL,
				   ps_global->VAR_CHAR_SET);

    if(match == 1) {
	strcpy(tmp_20k_buf, "    [The following text is in the \"");
	strcat(tmp_20k_buf, param != NULL ? param->value : NULL);
	strcat(tmp_20k_buf, "\" character set]");
	strcat(tmp_20k_buf, NEWLINE);
	strcat(tmp_20k_buf, "    [Your display is set for the \"");
	strcat(tmp_20k_buf, (ps_global->VAR_CHAR_SET) ?  
			       ps_global->VAR_CHAR_SET : "US-ASCII");
	strcat(tmp_20k_buf, "\" character set]");
	strcat(tmp_20k_buf, NEWLINE);
	if(!gf_puts(tmp_20k_buf, pc)
	   || !gf_puts("    [Some characters may be displayed incorrectly]",pc)
	   || !gf_puts(NEWLINE, pc)
	   || !gf_puts(NEWLINE, pc))
	  goto write_error;
    }

    aux_filter[filtcnt] = NULL;
    err = detach(ps_global->mail_stream, msgno, att->body, att->number,
		 NULL, pc, aux_filter);
    if(err) {
	error_found++;
	if(style == QStatus) {
	    q_status_message1(1, 2, 4, "%s", err);
	} else if(style == InLine) {
	    sprintf(tmp_20k_buf, "%s   [Error: %s]  \"%s\"%s%s", err,
		    NEWLINE,
	        att->body->description != NULL ? att->body->description : "",
		    NEWLINE, NEWLINE);
	    if(!gf_puts(tmp_20k_buf, pc))
	      goto write_error;
	}
    }

    if(att->body->subtype
       && (!strucmp(att->body->subtype, "richtext")
	   || !strucmp(att->body->subtype, "enriched"))
       && !display_bound){
	if(!gf_puts(NEWLINE, pc) || !gf_puts(NEWLINE, pc))
	  goto write_error;
    }

    return(error_found);

  write_error:
    if(style == QStatus)
      q_status_message1(1, 2, 4, "Error writing message: %s", 
			error_description(errno));

    return(1);
}


/*----------------------------------------------------------------------
   
  Returns:  0 - can display all characters
            1 - can display most of the characters
            2 - can't display any characters or don't know
		(We never return 2 anymore.  This used to be much more
		 complicated, but now we just return 0 if the text is
		 ascii or if the text type and device type match
		 exactly.  Otherwise, we return 1 which means to print
		 a warning and then try to blat it out to see if it works.
		 The problem was that we were sometimes preventing people
		 from seeing text which they could at least read partially.)
  ----*/
match_charset(text_char_set, device_char_set)
     char *text_char_set, *device_char_set;
{
    int text_is_ascii;

    text_is_ascii = !text_char_set || (strucmp(text_char_set, "us-ascii") == 0);

    if(text_is_ascii || (device_char_set &&
       (strucmp(text_char_set, device_char_set) == 0)))
      return(0);

    return(1);
}



 
/*------------------------------------------------------------------
   This list of iso 2022 escapes is taken from the X11R5 source with only
  a remote understanding of what this all means
  ----*/
static char *iso2022_escapes[] = {
    "\033(B",   "\033(J",   "\033)I",   "\033-A",   "\033-B",
    "\033-C",   "\033-D",   "\033-F",   "\033-G",   "\033-H",
    "\033-L",   "\033-M",   "\033-$(A", "\033$)A",  "\033$(B",
    "\033$)B",  "\033$(C",  "\033$)C",  "\033$(D",  "\033$)D",
    "\033$B",   "\033$@",
    NULL};

match_iso2022(esc_seq)
     char *esc_seq;
{
    char **p;

    for(p = iso2022_escapes;
        *p != NULL && struncmp(esc_seq, *p, strlen(*p));
/* BUG: make sure this shouldn't be CASE SENSITIVE */
/* may be why some people see bugs once in a while!! */
        p++);
    if(*p == NULL)
      return(0);
    else
      return(strlen(*p));
}



/*

Returns: pointer to static buffer with formatted envelope
 ----*/
void
format_envelope(e, pc)
    ENVELOPE   *e;
    gf_io_t     pc;
{
    if(!e)
      return;

    if(e->date) {
	gf_puts("Date: ", pc);
	gf_puts(e->date, pc);
	gf_puts(NEWLINE, pc);
    }

    if(e->from)
	pretty_addr_string("From: ", e->from, "", pc);

    if(e->reply_to && (!e->from || !address_is_same(e->reply_to, e->from)))
      pretty_addr_string("Reply to: ", e->reply_to, "", pc);

    if(e->to)
      pretty_addr_string("To: ", e->to, "", pc);

    if(e->cc)
      pretty_addr_string("Cc: ", e->cc, "", pc);

    if(e->bcc)
      pretty_addr_string("Bcc: ", e->bcc, "", pc);

    if(e->newsgroups && !ps_global->nr_mode)
      pretty_newsgroup_string("Newsgroups: ", e->newsgroups, "", pc);

    if(e->subject && e->subject[0]){
	gf_puts("Subject: ", pc);
	gf_puts(e->subject, pc);
	gf_puts(NEWLINE, pc);
    }
}



/*----------------------------------------------------------------------
    Format a strings describing one unshown part of a Mime message

Args: number -- A string with the part number i.e. "3.2.1"
      body   -- The body part
      type   -- 1 - Not shown, but can be
                2 - Not shown, cannot be shown
                3 - Can't print


Result: pointer to formatted string in static buffer

Note that size of the strings are carefully calculated never to overflow 
the static buffer:
    number  < 20,  description limited to 100, type_desc < 200,
    size    < 20,  second line < 100           other stuff < 60
 ----*/
char *
part_desc(number, body, type)
     BODY *body;
     int type;
     char *number;
{
    char *t;

    sprintf(tmp_20k_buf, "%s  [Part %s, %s%.100s%s%s  %s%s]%s",
	    NEWLINE,
            number,
            body->description == NULL ? "" : "\"",
            body->description == NULL ? "" : body->description,
            body->description == NULL ? "" : "\"  ",
            type_desc(body->type, body->subtype, body->parameter, 1),
            body->type == TYPETEXT ? comatose(body->size.lines) :
                                     byte_string(body->size.bytes),
            body->type == TYPETEXT ? " lines" : "",
	    NEWLINE);

    t = &tmp_20k_buf[strlen(tmp_20k_buf)];

    switch(type) {
      case 1:
        sstrcpy(&t,
	    "  [Not Shown. Use the \"V\" command to view or save this part]");
	sstrcpy(&t, NEWLINE);
        break;

      case 2:
	sstrcpy(&t, "  [Can not ");
	if(body->type != TYPEAUDIO && body->type != TYPEVIDEO)
	  sstrcpy(&t, "dis");

	sstrcpy(&t, 
		"play this part. Use the \"V\" command to save in a file]");
	sstrcpy(&t, NEWLINE);
        break;

      case 3:
        sstrcpy(&t, "  [Unable to print this part]");
	sstrcpy(&t, NEWLINE);
        break;
    }

    return(tmp_20k_buf);
}


/*
 * This could be better.  For example, look at the first two.  The
 * thing that keeps us from using the same array is that the column number
 * is stored with each entry and it could be different for each.
 */
static struct key help_keys[] =
       {{"M","Main Menu",0},  {NULL,NULL,0},            {"E","Exit Help",0},
        {NULL,NULL,0},        {NULL,NULL,0},            {NULL,NULL,0},
        {"-","PrevPage",0},   {"Spc","NextPage",0},     {"Y","prYnt",0},
        {"Z","Print All",0},  {"B","Report Bug",0},     {"W", "WhereIs",0}};
static struct key_menu help_keymenu =
	{sizeof(help_keys)/(sizeof(help_keys[0])*12), 0, 0,0,0,0, help_keys};
#define	HLP_MAIN_KEY	0
#define	HLP_ALL_KEY	9
#define	HLP_BUG_KEY	10

static struct key view_keys[] = 
       {{"?","Help",0},        {"O","OTHER CMDS",0},    {"M","Main Menu",0},
        {"V","ViewAttch",0},   {"P","PrevMsg",0},       {"N","NextMsg",0},
        {"-","PrevPage",0},    {"Spc","NextPage",0},    {"D","Delete",0},
        {"U","Undelete",0},    {"R","Reply",0},         {"F","Forward",0}, 

        {"?","Help",0},        {"O","OTHER CMDS",0},    {"Q","Quit",0},
        {"C","Compose",0},     {"L","ListFldrs",0},     {"G","GotoFldr",0},
        {"I","Index",0},       {"W","WhereIs",0},       {"Y","prYnt",0},
        {"T","TakeAddr",0},    {"S","Save",0},          {"E","Export",0},

        {"?","Help",0},        {"O","OTHER CMDS",0},    {NULL,NULL,0},
        {NULL,NULL,0},         {NULL,NULL,0},           {NULL,NULL,0}, 
        {"J","Jump",0},        {"TAB","NextNew",0},     {"H","HdrMode",0},
        {"B","Bounce",0},      {"*","Flag",0},          {"|","Pipe",0}}; 
static struct key_menu view_keymenu =
	{sizeof(view_keys)/(sizeof(view_keys[0])*12), 0, 0,0,0,0, view_keys};
#define VIEW_FULL_HEADERS_KEY 32
#define BOUNCE_KEY 33
#define FLAG_KEY 34
#define VIEW_PIPE_KEY 35

static struct key nr_anon_view_keys[] = 
       {{"?","Help",0},        {"W","WhereIs",0},       {"Q", "Quit", 0},
        {NULL,NULL,0},         {"P","PrevMsg",0},       {"N","NextMsg",0},
        {"-","PrevPage",0},    {"Spc","NextPage",0},    {"F","Fwd Email",0},
        {"J","Jump",0},        {"I", "Index",0},        {NULL, NULL, 0}};
static struct key_menu nr_anon_view_keymenu =
     {sizeof(nr_anon_view_keys)/(sizeof(nr_anon_view_keys[0])*12), 0, 0,0,0,0,
							nr_anon_view_keys};

static struct key nr_view_keys[] = 
       {{"?","Help",0},        {"O","OTHER CMDS",0},    {"Q","Quit",0},
        {NULL,NULL,0},         {"P","PrevMsg",0},       {"N","NextMsg",0},
        {"-","PrevPage",0},    {"Spc","NextPage",0},    {"F","Fwd Email",0},
        {"J","Jump",0},        {"Y","prYnt",0},         {"S","Save",0}, 

        {"?","Help",0},        {"O","OTHER CMDS",0},    {"E","Export",0},
        {"C","Compose",0},     {NULL,NULL,0},           {NULL,NULL,0},
        {"I","Index",0},       {"W","WhereIs",0},       {NULL,NULL,0},
        {NULL,NULL,0},         {NULL,NULL,0},           {NULL,NULL,0}}; 
static struct key_menu nr_view_keymenu =
  {sizeof(nr_view_keys)/(sizeof(nr_view_keys[0])*12), 0, 0,0,0,0,nr_view_keys};

static struct key text_att_view_keys[] =
       {{"?","Help",0},        {NULL,NULL,0},           {"E","Exit Viewer",0},
        {NULL,NULL,0},         {NULL,NULL,0},           {NULL,NULL,0},        
        {"-","PrevPage",0},    {"Spc","NextPage",0},    {"Y","prYnt",0},
	{"S","Save",0},        {"|","Pipe",0},          {"W", "WhereIs",0}};
static struct key_menu text_att_view_keymenu =
     {sizeof(text_att_view_keys)/(sizeof(text_att_view_keys[0])*12),0,0,0,0,0,
							text_att_view_keys};
#define ATT_SAVE_KEY 9
#define ATT_PIPE_KEY 10


static struct key simple_view_keys[] =
       {{"?","Help",0},       {NULL,NULL,0},           {"Q","Quit Viewer",0},
        {NULL,NULL,0},        {NULL,NULL,0},           {NULL,NULL,0},        
        {"-","PrevPage",0},   {"Spc","NextPage",0},    {"F","Fwd Email",0},
        {"S","Save",0},       {NULL,NULL,0},           {"W", "WhereIs",0}};
static struct key_menu simple_view_keymenu =
     {sizeof(simple_view_keys)/(sizeof(simple_view_keys[0])*12), 0, 0,0,0,0,
							simple_view_keys};
#define SAVE_KEY 9

#define	STYLE_NAME(s)	(((s) == HelpText || (s) == ComposerHelpText) ? "help"\
			  : ((s) == MessageText) ? "message" : "text")


/*----------------------------------------------------------------------
   routine for displaying help and message text on the screen.

  Args: text          buffer to display
        title         string with title of text being displayed
        pages         page numbering if text is to has page numbers
        style         whether we are display a message, help text ...
	source	      what's text: char **, char * or FILE * ???
 

   This displays in three different kinds of text. One is an array of
lines passed in in text_array. The other is a simple long string of
characters passed in in text. The simple string of characters may have
another string, the header which is prepended to the text with some
special processing. The two header.... args specify how format and
filter the header.

    It can scroll by line or by page. The pages array passed in is a
list of line numbers that are assumed to correspond to pages one
through n. When scrolling down it will go to the next page, whatever
line number that is.

  The style determines what some of the error messages will be, and
what commands are available as different things are appropriate for
help text than for message text etc.

 ---*/
#define LINES_ABOVE 2  /* Title Bar and blank line */
#define LINES_BELOW 3  /* Status line and key menu */
void
scrolltool(text, title, pages, style, source, att)
    void       *text;
    char       *title;
    int        *pages;
    TextType    style;		/* message, news, etc. */
    SourceType  source;		/* char **, char * or FILE * */
    ATTACH_S   *att;		/* used only with style AttachText */
{
    register int     cur_top_line,  num_display_lines;
    int              result, done, ch, num_text_lines,
                     current_page, page_count, found_on,
                     orig_ch, count_by_lines, first_view, force;
    struct key_menu *km;
    bitmap_t         bitmap;
    OtherMenu        what;
    SCROLL_S	    *screen_state;

    page_count        = 0; 
    ch                = 'x';		/* for first time through */
    num_display_lines = ps_global->ttyo->screen_rows-LINES_ABOVE-LINES_BELOW;
    ps_global->mangled_header = 1;
    ps_global->mangled_footer = 1;
    ps_global->mangled_screen = 0;

    what           = FirstMenu;		/* which key menu to display */
    cur_top_line   = 0;
    done           = 0;
    current_page   = 0;
    found_on       = 0;
    first_view     = 1;
    force          = 0;
    count_by_lines = (pages == NULL);

    if(pages != NULL)
      for(page_count = 0; pages[page_count] != -1; page_count++);

    st = screen_state = new_scroll_state(); 	/* allocate new state */

    set_scroll_text(text, cur_top_line, LINES_ABOVE, 
		    LINES_ABOVE + LINES_BELOW, source);
    format_scroll_text();
    num_text_lines          = get_scroll_text_lines();
    ps_global->redrawer     = redraw_scroll_text;
    ps_global->mangled_body = 1;

    setbitmap(bitmap);
    if(style == AttachText) {
      km = &text_att_view_keymenu;
      if(!att){
	  clrbitn(ATT_SAVE_KEY, bitmap);
	  clrbitn(ATT_PIPE_KEY, bitmap);
      }
      else if(F_OFF(F_ENABLE_PIPE, ps_global))
	clrbitn(ATT_PIPE_KEY, bitmap);
    }
    else if(style == SimpleText) {
	km = &simple_view_keymenu;
	if(ps_global->anonymous)
	  clrbitn(SAVE_KEY, bitmap);
    }
    else if(ps_global->anonymous) {
	km = &nr_anon_view_keymenu;
    }
    else if(ps_global->nr_mode) {
	km = &nr_view_keymenu;
    }
    else if(style == MessageText) {
	km = &view_keymenu;
#ifndef DOS
	if(F_OFF(F_ENABLE_PIPE,ps_global))
#endif
	  clrbitn(VIEW_PIPE_KEY, bitmap);	/* always clear for DOS */
	if(F_OFF(F_ENABLE_BOUNCE,ps_global))
	  clrbitn(BOUNCE_KEY, bitmap);
	if(F_OFF(F_ENABLE_FLAG,ps_global))
	  clrbitn(FLAG_KEY, bitmap);
	if(F_OFF(F_ENABLE_FULL_HDR,ps_global))
	  clrbitn(VIEW_FULL_HEADERS_KEY, bitmap);
    }
    else {					/* must be paging help */
	km = &help_keymenu;
	if(style == ComposerHelpText) {		/* composer gets minimum */
	    clrbitn(HLP_MAIN_KEY, bitmap);
	    clrbitn(HLP_BUG_KEY, bitmap);
	}

	if(style != MainHelpText)		/* only main can "print all" */
	  clrbitn(HLP_ALL_KEY, bitmap);
    }

    while(!done) {
	if(ps_global->mangled_screen) {
	    ps_global->mangled_header = 1;
	    ps_global->mangled_footer = 1;
            ps_global->mangled_body   = 1;
            ps_global->mangled_screen = 0;
	}

        if(streams_died())
          ps_global->mangled_header = 1;

        dprint(9, (debugfile, "@@@@ current:%ld\n",
		   mn_get_cur(ps_global->msgmap)));


        /*==================== All Screen painting ====================*/
        /*-------- The title bar ---------------*/
	if(count_by_lines) {
	    if(ps_global->mangled_header) {
		set_titlebar(title, ps_global->mail_stream,
			     ps_global->context_current, ps_global->cur_folder,
			     ps_global->msgmap, 1,
			     (style == HelpText || style == MainHelpText
			      || style == ComposerHelpText)
			       ? TextPercent
			       : (style == SimpleText) 
				   ? FileTextPercent 
				   : MsgTextPercent,
		             (cur_top_line +num_display_lines > num_text_lines)
			       ? num_text_lines
			       : cur_top_line + num_display_lines,
		             num_text_lines);

		ps_global->mangled_header = 0;
	    }
	    else {
		if(style == SimpleText || style == HelpText
		   || style == MainHelpText || style == ComposerHelpText)
		  update_titlebar_lpercent(
			    cur_top_line + num_display_lines > num_text_lines
			      ? num_text_lines
			      : cur_top_line + num_display_lines);
		else
		  update_titlebar_percent(
			     cur_top_line + num_display_lines > num_text_lines
			       ? num_text_lines
			       : cur_top_line + num_display_lines);
	    }
	}
	else {
	    if(ps_global->mangled_header) {
	        set_titlebar(title, ps_global->mail_stream,
			     ps_global->context_current, ps_global->cur_folder,
			     NULL, 1, PageNumber, current_page+1, page_count);
	        ps_global->mangled_header = 0;
	    }else {
		update_titlebar_page(current_page + 1);
	    }

	}


        /*---- Scroll or update the body of the text on the screen -------*/
	st = screen_state;
        scroll_scroll_text(cur_top_line, ps_global->mangled_body);
        ps_global->mangled_body = 0;
        num_text_lines          = get_scroll_text_lines();


        /*------------- The key menu footer --------------------*/
	if(ps_global->mangled_footer) {
               
            draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
							    -2, 0, what, 0);
	    what = SameTwelve;
	    ps_global->mangled_footer = 0;
	}

	/*=================  Check for New Mail and CheckPoint ===*/
        if(new_mail(NULL, force, (ch == NO_OP_IDLE || first_view) ? 0 :
				  ch == NO_OP_COMMAND ? 1 : 2) >= 0){
	    if(count_by_lines) {
		set_titlebar(title, ps_global->mail_stream,
			     ps_global->context_current,
			     ps_global->cur_folder, ps_global->msgmap, 1,
			     (style == HelpText || style == MainHelpText
			      || style == ComposerHelpText)
			       ? TextPercent
			       : (style == SimpleText) 
				   ? FileTextPercent 
				   : MsgTextPercent,
		             (cur_top_line +num_display_lines > num_text_lines)
			       ? num_text_lines
			       : cur_top_line + num_display_lines,
		             num_text_lines);

	    }
	    else {
	        set_titlebar(title, ps_global->mail_stream,
			     ps_global->context_current, ps_global->cur_folder,
			     NULL, 1, PageNumber, current_page+1, page_count);
	    }
        }

	if(first_view && num_display_lines >= num_text_lines)
	  q_status_message(0, 0, 1, "ALL of message text");

	force      = 0;		/* may not need to next time around */
	first_view = 0;		/* check_point a priority any more? */

	/*==================== Output the status message ==============*/
        display_message(ch);
	MoveCursor(min(LINES_ABOVE + num_display_lines,
                       ps_global->ttyo->screen_rows), 0);


	/*================ Get command and validate =====================*/
#ifdef	_WINDOWS
	mswin_allowcopy(mswin_readscrollbuf);
	mswin_allowexit (MSWIN_EXIT_SENDCHAR, NULL,
			 F_ON(F_USE_FK, ps_global) ? OPF3 : 'q');
#endif
        ch = read_command();
        orig_ch = ch;
#ifdef	_WINDOWS
	mswin_allowcopy(NULL);
	mswin_allowexit (MSWIN_EXIT_DENY, "Complete current operation", '\0');
#endif

        if(ch < 0x0100 && isupper(ch))
	  ch = tolower(ch);
        else if(ch >= PF1 && ch <= PF12 && km->which > 0 && km->which < 3)
	  ch = (km->which == 1) ? PF2OPF(ch) : PF2OOPF(ch);

	ch = validatekeys(ch);


	/*============= Execute command =======================*/
        switch(ch){

            /* ------ Help -------*/
          case PF1:
          case OPF1:
          case OOPF1:
          case '?':
          case ctrl('G'):
            if(ps_global->nr_mode || style == SimpleText) {
                q_status_message(1, 3, 5, "No help text currently available");
                break;
            }
            if(ch == PF1) {
	        if(style == HelpText || style == MainHelpText)
                  goto df;
		if(style == ComposerHelpText)
                  goto unknown;
	    }
            if(style == HelpText || style == MainHelpText
	       || style == ComposerHelpText) {
                q_status_message(1, 3, 5, "Already in Help");
		break;
	    }

            if(style == AttachText)
	      helper(h_mail_text_att_view, "HELP FOR ATTACHED TEXT VIEW", 0);
	    else
	      helper(h_mail_view, "HELP FOR MESSAGE TEXT VIEW", 0);

	    if(ps_global->next_screen != main_menu_screen) {
		/* Have to reset because helper uses scroll_text */
		num_display_lines = ps_global->ttyo->screen_rows
				       - LINES_ABOVE - LINES_BELOW;
		ps_global->mangled_screen = 1;
	    }
	    else
	      done = 1;

            break; 


            /*---------- Roll keymenu ------*/
          case PF2:
          case OPF2:
          case OOPF2:
	  case 'o':
	    if(ps_global->anonymous && ch == PF2)
	      goto whereis;
	    if(km->how_many == 1)
	      goto unknown;
            if (ch == 'o')
	      warn_other_cmds();
	    what = NextTwelve;
	    ps_global->mangled_footer = 1;
	    break;
            

            /* -------- Scroll back one page -----------*/
          case PF7:
          case '-':   
          case ctrl('Y'): 
          case KEY_PGUP:
	    if(cur_top_line) {
	        if(pages == NULL) {
                    cur_top_line -= (num_display_lines-OVERLAP);
	            if(cur_top_line < 0)
	              cur_top_line = 0;
	        }else {
		    current_page--;
		    cur_top_line = pages[current_page];
	        }

		if(cur_top_line == 0 && !messages_queued(NULL))
		  q_status_message(0, 0, 1, "START of message text");
	    }
	    else
	      q_status_message1(0, 0, 1, "Already at start of %s",
				STYLE_NAME(style));
            break;


            /*---- Scroll down one page -------*/
          case PF8:
          case '+':     
          case ctrl('V'): 
          case KEY_PGDN:
          case ' ':
            if(cur_top_line + num_display_lines < num_text_lines){
	        if(pages == NULL) {
                    cur_top_line += (num_display_lines - OVERLAP);
	        }else {
                    if(current_page + 1 <  page_count) {
			current_page++;
			cur_top_line = pages[current_page];
                    }
		    else
		      q_status_message1(0, 0, 1, "Already at end of %s",
					STYLE_NAME(style));
	        }

		if(cur_top_line + num_display_lines >= num_text_lines
		   && !messages_queued(NULL))
		  q_status_message(0, 0, 1, "END of message text");
            }
	    else
	      q_status_message1(0, 0, 1, "Already at end of %s",
				STYLE_NAME(style));

            break;


            /*------ Scroll down one line -----*/
	  case KEY_DOWN:
	  case ctrl('N'):
            if(cur_top_line + num_display_lines < num_text_lines){
	        cur_top_line++;
	        if(pages != NULL&& cur_top_line >= pages[current_page+1] &&
	           pages[current_page+1] != -1)
	          current_page++;

		if(cur_top_line + num_display_lines >= num_text_lines
		   && !messages_queued(NULL))
		  q_status_message(0, 0, 1, "END of message text");
	    }
	    else
	      q_status_message1(0, 0, 1, "Already at end of %s",
				  STYLE_NAME(style));

	    break;


            /* ------ Scroll back up one line -------*/
          case KEY_UP:
	  case ctrl('P'):
	    if(cur_top_line == 0) {
	       q_status_message1(0, 0, 1, "Already at start of %s",
				 STYLE_NAME(style));
	    }else {
	        cur_top_line--;
	        if(pages != NULL && cur_top_line < pages[current_page])
	          current_page--;

		if(cur_top_line == 0 && !messages_queued(NULL))
		  q_status_message(0, 0, 1, "START of message text");
	    }
	    break;


            /*---------- Search text (where is) ----------*/
          case PF12:
          case 'w':
          case ctrl('W'):
	    /* PF12 is not whereis in this case */
	    if(style == MessageText && ch == PF12)
	      goto df;

whereis:
            ps_global->mangled_footer = 1;
            found_on = search_text(-3,
			     (found_on <= 0 || found_on < cur_top_line
			      || found_on >= cur_top_line + num_display_lines)
				? cur_top_line : found_on + 1,
				   tmp_20k_buf);
            if(found_on >= 0) {
		result = found_on <= cur_top_line;
		if(F_ON(F_FORCE_LOW_SPEED,ps_global) || ps_global->low_speed){
		    if(found_on > cur_top_line + num_display_lines ||
		       found_on < cur_top_line)  {
			cur_top_line = (found_on/(num_display_lines-OVERLAP))
			  *(num_display_lines -OVERLAP);
		    }
		}
		else
		  cur_top_line = found_on - ((found_on > 0) ? 1 : 0);

                if(pages != NULL) {
                    int *pg;
                    for(pg = pages; pg < &pages[page_count]; pg++)
                      if(cur_top_line <= *pg)
                        break;

                    if(pg != pages)
                      pg--;

                    current_page = pg - pages;
                }

		if(tmp_20k_buf[0])
		  q_status_message(0, 1, 3, tmp_20k_buf);
		else
		  q_status_message2(0, 1, 3, "%sFound on line %s on screen",
				    result ? "Search wrapped to start. " : "",
				    int2string(found_on - cur_top_line + 1));
            }else if(found_on == -1) {
                q_status_message(0, 0, 2, "Search cancelled");
            }else{
                q_status_message(0, 1, 3, "\007Word not found");
            }
            break; 


            /*---------- Suspend Pine ( ^Z ) ----------*/
          case ctrl('Z'):
            if(!have_job_control())
              goto df;
            if(F_OFF(F_CAN_SUSPEND,ps_global)) {
                q_status_message(1, 1, 3,
                        "\007Pine suspension not enabled - see help text");
                break;
            }else {
                do_suspend(ps_global);
            }
            /*-- Fall through to redraw --*/

            /*-------------- refresh -------------*/
          case KEY_RESIZE:
          case ctrl('L'):
	    ClearScreen();
            num_display_lines = ps_global->ttyo->screen_rows - LINES_ABOVE -
                         LINES_BELOW;
            ps_global->mangled_screen = 1;
	    force                     = 1;
	    if(ch == KEY_RESIZE)
	      clear_index_cache();

            break;


            /*------- no op timeout to check for new mail ------*/
          case NO_OP_IDLE:
          case NO_OP_COMMAND:
            break;

	  case ctrl('M'):
	  case ctrl('J'):
	    if(style != MessageText){
		q_status_message(0, 0, 2,
				 "\007No default command in this screen");
		break;
	    }			/* else fall thru and handle default */


	    /*------- Other commands of error ------*/
          default:
	  df:
	    if(style == SimpleText){
		/*
		 * Yet another pitiful hack.
		 */
		if(ch == 'f' || ch == PF9){ 	/* Forward */
		    forward_text(ps_global, text, source);
		}
		else if((ch == PF10 || ch == 's') && !ps_global->anonymous){
		    char filename[MAXFOLDER+1];
		    int  rv;

		    filename[0] = '\0';
		    ps_global->mangled_footer = 1;
		    while(1) {
			rv = optionally_enter(filename, -3, 0, MAXPATH, 1, 0,
					      "File to save text in : ",
					      NULL, NO_HELP, 0);
			if(rv == 4)
			  redraw_scroll_text();
			else if(rv != 3)
			  break;
		    }

		    removing_trailing_white_space(filename);
		    removing_leading_white_space(filename);
		    if(rv == 0 && filename[0]){
			gf_io_t  pc, gc;
			STORE_S *store=so_get(FileStar,filename,WRITE_ACCESS);

			if(store){
			    char *pipe_err;

			    gf_set_so_writec(&pc, store);
			    gf_set_readc(&gc, text, (source == CharStar)
							? strlen((char *)text)
							: 0L,
					 source);

			    gf_filter_init();
			    if(pipe_err = gf_pipe(gc, pc)){
				q_status_message2(0,2,2,
					       "\007Problem saving to %s: %s",
					       filename, pipe_err);
			    }
			    else
			      q_status_message1(0,2,2,"Text saved to %s",
					       filename);

			    so_give(&store);
			}
			else
			  q_status_message2(0,2,2,"\007Can't save to %s: %s",
					    filename,error_description(errno));

		    }
		    else
		      q_status_message(0,2,2,"\007Save Cancelled");
		}
		else if(ch == PF3 || ch == 'q'){
		    done = 1;
		}
		else{
		    q_status_message(0,0,2,"\007Unknown Command");
		}
	    }
	    else if(style == AttachText && (ch == 's' || ch == PF10) && att) {
	        /*
	         * This section is an expedient hack to get this working.
	         * We'd probably like to pass a "process_cmd()" in as an
	         * argument or something like that.  Also, save_attachment()
	         * opens and reads the attachment again even though we've
	         * already opened and read it before in this case.
	         */
		save_attachment(-3, mn_m2raw(ps_global->msgmap, 
					     mn_get_cur(ps_global->msgmap)),
				att);
                ps_global->mangled_footer = 1;

	    }
	    else if(style == AttachText && (ch == '|' || ch == PF11) && att
		    && F_ON(F_ENABLE_PIPE, ps_global)) {
	        /*
	         * This section is an expedient hack to get this working.
	         * We'd probably like to pass a "process_cmd()" in as an
	         * argument or something like that.  Also, pipe_attachment()
	         * opens and reads the attachment again even though we've
	         * already opened and read it before in this case.
	         */
		pipe_attachment(mn_m2raw(ps_global->msgmap, 
					 mn_get_cur(ps_global->msgmap)),
				att);
                ps_global->mangled_footer = 1;

	    }
	    else if(style == MessageText){
	        result = process_cmd(ps_global, ps_global->msgmap, ch, 0,
				     orig_ch, &force);
	        dprint(7, (debugfile, "PROCESS_CMD return: %d\n", result));

                if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
		    done = 1;
		}
		else if(!st){
		    ps_global->mangled_screen = 1;
		    num_display_lines	      =
		        ps_global->ttyo->screen_rows -LINES_ABOVE- LINES_BELOW;
		}
            }
	    else {
		if(!ps_global->nr_mode
		   && ((ch == 'm' || ch == PF1)
		       && (style == HelpText || style == MainHelpText))) {
    	            /*---------- Main menu -----------*/
                    ps_global->next_screen = main_menu_screen;
                    done = 1;
		}
		else if(!ps_global->nr_mode
			&& ((ch == 'b' || ch == PF11)
			    && (style == HelpText || style == MainHelpText))) {
    	            /*---------- report a bug  -----------*/
		    gripe(ps_global);
		    num_display_lines =
			  ps_global->ttyo->screen_rows-LINES_ABOVE-LINES_BELOW;
		}
		else if((ch == 'z' || ch == PF10) && style == MainHelpText){
		    print_all_help();
    	        }else if(!ps_global->nr_mode && (ch == PF3 || ch == 'e')) {
    	            /*----------- Done -----------*/
                    done = 1;
                }else if((ch == 'y' && !ps_global->anonymous) ||
		 (ch == PF9 && !ps_global->nr_mode) ||
		 (ch == PF11 && ps_global->nr_mode)) {
    	            /*----------- Print ------------*/
		    char message[12];
		    if(style == AttachText){
			if(att)
			  strcpy(message, "attachment ");
			else
			  strcpy(message, "text ");
		    }
		    else
		      strcpy(message, "help text ");

		    print_to_printer(text, source, message);
                }
		else {		/*----------- Unknown command -------*/
unknown:
		    bogus_command(orig_ch,
				  (style == MainHelpText || style == HelpText)
				    ? NULL
				    : F_ON(F_USE_FK,ps_global) ? "F1" : "?");
    	        }
            }
            break;

        } /* End of switch() */

    } /* End of while() -- loop executing commands */

    st = screen_state;		/* make sure global pointer is correct */
    zero_scroll_text();		/* very important to zero out on return!!! */
    return;
}


/*----------------------------------------------------------------------
      Print text on paper

    Args:  text -- The text to print out
	   source -- What type of source text is
	   message -- Message for open_printer()
    Handling of error conditions is very poor.

  ----*/
static int
print_to_printer(text, source, message)
     void		*text;		/* the data to be printed */
     SourceType		 source;	/* char **, char * or FILE * */
     char		*message;
{
    register char **t;

    if(open_printer(message) != 0)
      return(-1);

    if(source == CharStar && text != (char *)NULL) {
        print_text((char *)text);

    } else if(source == CharStarStar && text != (char **)NULL) {
        for(t = text; *t != NULL; t++) {
            print_text(*t);
	    print_text(NEWLINE);
        }

    } else if(source == FileStar && text != (FILE *)NULL) {
	size_t n;
	int i;
	fseek((FILE *)text, 0L, 0);
	n = 20480 - 1;
	while(i=fread((void *)tmp_20k_buf, sizeof(char), n, (FILE *)text)) {
	    tmp_20k_buf[i] = '\0';
	    print_text(tmp_20k_buf);
	}
    }

    close_printer();
    return(0);
}


/*----------------------------------------------------------------------
   Search text being viewed (help or message)

      Args: q_line      -- The screen line to prompt for search string on
            start_line  -- Line number in text to begin search on

    Result: returns line number string was found on
            -1 for cancel
            -2 if not found
	    -3 if jump to top
	    -4 if jump to bottom

 ---*/

search_text(q_line, start_line, report)
     int   q_line, start_line;
     char *report;
{
    char        prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1];
    HelpType	help;
    int         rc;
    static char search_string[MAX_SEARCH+1] = { '\0' };
    static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
					 {ctrl('Y'), 10, "^Y", "First Line"},
					 {ctrl('V'), 11, "^V", "Last Line"},
					 {-1, 0, NULL, NULL}
				       };

    report[0] = '\0';
    sprintf(prompt, "Word to search for [%s] : ", search_string);
    help = NO_HELP;
    nsearch_string[0] = '\0';

    while(1) {
        rc = optionally_enter(nsearch_string, q_line, 0, MAX_SEARCH, 1, 0,
                              prompt, word_search_key, help, 0);
        if(rc == 3) {
            help = help == NO_HELP ? h_oe_searchview : NO_HELP;
            continue;
        }
	else if(rc == 10){
	    strcpy(report, "Searched to First Line.");
	    return(1);
	}
	else if(rc == 11){
	    strcpy(report, "Searched to Last Line."); 
	    return(get_scroll_text_lines() - max(ps_global->ttyo->screen_rows
                    - LINES_ABOVE - LINES_BELOW - 2, 0));
	}

        if(rc != 4)
          break;
    }

    if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
      return(-1);

    if(nsearch_string[0] != '\0')
      strcpy(search_string, nsearch_string);


    rc = search_scroll_text(start_line, search_string);
    return(rc);
}




/*
 * new_scroll_state - allocate and initialize new global scroll
 *                    state structure
 */
SCROLL_S *
new_scroll_state()
{
    SCROLL_S *s = fs_get(sizeof(SCROLL_S));
    memset((void *)s, 0, sizeof(SCROLL_S));
    return(s);
}


/*----------------------------------------------------------------------
      Save all the data for scrolling text and paint the screen


  ----*/
void
set_scroll_text(text, current_line, screen_top, screen_other, source)
     void       *text;
     int         current_line, screen_top, screen_other;
     SourceType  source;
{
    /* save all the stuff for possible asynchronous redraws */
    st->text               = text;
    st->top_text_line      = current_line;
    st->screen_start_line  = screen_top;
    st->screen_other_lines = screen_other;
    st->source             = source;
    
    st->screen_width = -1; /* Force text formatting calculation */

}



/*----------------------------------------------------------------------
     Redraw the text on the screen, possibly reformatting if necessary

   Args None

 ----*/
void
redraw_scroll_text()
{
    int i, len, offset;

    format_scroll_text();

    offset = (st->source == FileStar) ? 0 : st->top_text_line;

#ifdef _WINDOWS
    mswin_beginupdate();
#endif
    /*---- Actually display the text on the screen ------*/
    for(i = 0; i < ps_global->ttyo->screen_rows - st->screen_other_lines; i++){
        MoveCursor((i + st->screen_start_line), 0);
        CleartoEOLN();
        if((offset + i) < st->num_lines) {
            len = min(st->line_lengths[offset + i], st->screen_width);
            PutLine0n8b(i + st->screen_start_line, 0,
                      st->text_lines[offset + i], len);
        }
    }

#ifdef _WINDOWS
    mswin_endupdate();
#endif
    fflush(stdout);
}




/*----------------------------------------------------------------------
  Free memory used as scrolling buffers for text on disk.  Also mark
  text_lines as available
  ----*/
void
zero_scroll_text()
{
    register int i;

    for(i = 0; i < st->lines_allocated; i++)
      if(st->source == FileStar && st->text_lines[i])
	fs_give((void **)&st->text_lines[i]);
      else
	st->text_lines[i] = NULL;

    if(st->source == FileStar && st->findex != NULL){
	fclose(st->findex);
	st->findex = NULL;
	if(st->fname){
	    unlink(st->fname);
	    fs_give((void **)&st->fname);
	}
    }

    if(st->text_lines)
      fs_give((void **)&st->text_lines);

    if(st->line_lengths)
      fs_give((void **)&st->line_lengths);

    fs_give((void **)&st);
}



/*----------------------------------------------------------------------

Always format at least 20 chars wide. Wrapping lines would be crazy for
screen widths of 1-20 characters 
  ----*/
void
format_scroll_text()
{
    int             i, line_len;
    char           *p, **pp;
#ifdef	X_NEW
    char           *max_line;
#endif
    register short  *ll;
    register char  **tl, **tl_end, *last_space;

    if(st->screen_width == ps_global->ttyo->screen_cols)
        return;

    st->screen_width = max(20, ps_global->ttyo->screen_cols);

    if(st->lines_allocated == 0) {
        st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
        st->text_lines   = (char **)fs_get(st->lines_allocated *sizeof(char *));
	memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
        st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
    }

    tl     = st->text_lines;
    ll     = st->line_lengths;
    tl_end = &st->text_lines[st->lines_allocated];

    if(st->source == CharStarStar) {
        /*---- original text is already list of lines -----*/
        /*   The text could be wrapped nicely for narrow screens; for now
             it will get truncated as it is displayed */
        for(pp = (char **)st->text; *pp != NULL;) {
            *tl++ = *pp++;
            *ll++ = st->screen_width;
            if(tl >= tl_end) {
                int index = tl - st->text_lines;
                st->lines_allocated *= 2;
                fs_resize((void **)&st->text_lines,
                          st->lines_allocated * sizeof(char *));
                fs_resize((void **)&st->line_lengths,
                          st->lines_allocated*sizeof(short));
                tl     = &st->text_lines[index];
                ll     = &st->line_lengths[index];
                tl_end = &st->text_lines[st->lines_allocated];
            }
        }

	st->num_lines = tl - st->text_lines;
    } else if (st->source == CharStar) {
        /*------ Format the plain text ------*/
        for(p = (char *)st->text; *p; ) {
            *tl = p;
#ifdef X_NEW
            max_line = p + st->screen_width;
            while(*p != '\r' && *p != '\n' && p < max_line && *p) p++;
#else
            line_len = 0;            
            last_space = NULL;

            while(*p) {
                if(*p == '\r' || *p == '\n') {
                    break;
                } else if(*p == '\033') {
                    int x;
                    if(x = match_iso2022(p))  {
                        /* Don't count 2022 escape in length */
                        while(x--)
                          p++;
                    } else {
                        /* A plain escape, not sure what it means in the 
                           text, but count it's length anyway to be safe 
                         */
                        p++;
                        line_len++;
                    }
                } else if(*p == '\t') {
		    while(line_len < st->screen_width - 1 
			  && ((++line_len)&0x07) != 0) /* add tab's spaces */
		      ;

                    p++;
                    last_space = p;
                } else if(*p >= '\01' && *p  <= '\04') {
                    /* Don't count the bold and underline hacks */
                    p++;
                } else if(*p == ' ') {
                    p++;
                    last_space = p;
                    line_len++;
                } else {
                    p++;
                    line_len++;
                }
                if(line_len >= st->screen_width) {
                    if(last_space == NULL) {
                        break;
                    } else {
                        p = last_space;
                        break;
                    }
                }
            }
        
#endif
            *ll = p - *tl;
            ll++; tl++;
            if(tl >= tl_end) {
                int index = tl - st->text_lines;
                st->lines_allocated *= 2;
                fs_resize((void **)&st->text_lines,
                          st->lines_allocated * sizeof(char *));
                fs_resize((void **)&st->line_lengths,
                         st->lines_allocated*sizeof(short));
                tl     = &st->text_lines[index];
                ll     = &st->line_lengths[index];
                tl_end = &st->text_lines[st->lines_allocated];
            }      
            if(*p == '\r' && *(p+1) == '\n') 
              p += 2;
            else if(*p == '\n' || *p == '\r')
              p++;
             continue;
        }

	st->num_lines = tl - st->text_lines;
    }
    else {
	/*------ Display text is in a file --------*/

	/*
	 * This is pretty much only useful under DOS where we can't fit
	 * all of big messages in core at once.  This scheme makes
	 * some simplifying assumptions:
	 *  1. Lines are on disk just the way we'll display them.  That
	 *     is, line breaks and such are left to the function that
	 *     writes the disk file to catch and fix.
	 *  2. We get away with this mainly because the DOS display isn't
	 *     going to be resized out from under us.
	 *
	 * The idea is to use the already alloc'd array of char * as a 
	 * buffer for sections of what's on disk.  We'll set up the first
	 * few lines here, and read new ones in as needed in 
	 * scroll_scroll_text().
	 *  
	 * but first, make sure there are enough buffer lines allocated
	 * to serve as a place to hold lines from the file.
	 */
	if(st->lines_allocated < (2 * PGSIZE) + 1){
	    /*
	     * clear lines already alloc'd 
	     */
	    for(i = 0; i < st->lines_allocated; i++)
	      if(st->text_lines[i]) 		/* clear alloc'd lines */
		fs_give((void **)&st->text_lines[i]);

	    st->lines_allocated = (2 * PGSIZE) + 1; /* resize */

	    fs_resize((void **)&st->text_lines,
		      st->lines_allocated * sizeof(char *));
	    memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
	    fs_resize((void **)&st->line_lengths,
		      st->lines_allocated*sizeof(short));
	}

	for(i = 0; i <= PGSIZE; i++)
	  if(st->text_lines[i] == NULL)
	    st->text_lines[i] = (char *)fs_get(st->screen_width*sizeof(char));

	tl = &st->text_lines[i];

	st->num_lines = make_file_index();

	ScrollFile(st->top_text_line);		/* then load them up */

    }

    *tl = NULL;
}




/*
 * ScrollFile - scroll text into the st struct file making sure 'line'
 *              of the file is the one first in the text_lines buffer.
 *
 *   NOTE: talk about massive potential for tuning...
 *         Goes without saying this is still under constuction
 */
void
ScrollFile(line)
int line;
{
    register int  i;
             long x;

    if(line <= 0){		/* reset and load first couple of pages */
	fseek((FILE *) st->text, 0L, 0);
    }
    else{
	/*** do stuff to get the file pointer into the right place ***/
	/*
	 * BOGUS: this is painfully crude right now, but I just want to get
	 * it going. 
	 *
	 * possibly in the near furture, an array of indexes into the 
	 * file that are the offset for the beginning of each line will
	 * speed things up.  Of course, this
	 * will have limits, so maybe a disk file that is an array
	 * of indexes is the answer.
	 */
	fseek(st->findex, (size_t)(line) * sizeof(long), 0);
	if(fread(&x, sizeof(long), (size_t)1, st->findex) != 1){
	    return;
	}

	fseek((FILE *) st->text, x, 0);
    }

    for(i = 0; i < PGSIZE; i++){
	if(!st->text_lines || !st->text_lines[i]
	   || fgets(st->text_lines[i],st->screen_width,(FILE *)st->text) == NULL)
	  break;

	st->line_lengths[i] = strlen(st->text_lines[i]);
    }

    for(; i < PGSIZE; i++)
      if(st->text_lines && st->text_lines[i]) /* blank out any unused lines */
	*st->text_lines[i] = '\0';
}


/*
 * make_file_index - do a single pass over the file containing the text
 *                   to display, recording line lengths and offsets.
 *    NOTE: This is never really to be used on a real OS with virtual
 *          memory.  This is the whole reason st->findex exists.  Don't
 *          want to waste precious memory on a stupid array that could 
 *          be very large.
 */
int
make_file_index()
{
    register int l = 0;
    long i = 0;

    if(!st->findex){
	if(!st->fname)
	  st->fname = temp_nam(NULL, "pi");

	if((st->findex = fopen(st->fname,"w+b")) == NULL)
	  return(0);
    }
    else
      fseek(st->findex, 0L, 0);

    fseek((FILE *)st->text, 0L, 0);

    fwrite((void *)&i, sizeof(long), (size_t)1, st->findex);
    while(fgets(tmp_20k_buf, st->screen_width, (FILE *)st->text) != NULL){
	i = ftell((FILE *)st->text);
	fwrite((void *)&i, sizeof(long), (size_t)1, st->findex);
	l++;
    }

    fseek((FILE *)st->text, 0L, 0);

    return(l);
}



/*----------------------------------------------------------------------
     Scroll the text on the screen

   Args:  new_top_line -- The line to be displayed on top of the screen
          redraw -- Flag to force a redraw even in nothing changed 

 ----*/
void
scroll_scroll_text(new_top_line, redraw)
     int new_top_line, redraw;
{
    int num_display_lines, len, l;
    num_display_lines = ps_global->ttyo->screen_rows - st->screen_other_lines;

    if(st->top_text_line == new_top_line && !redraw)
      return;

    format_scroll_text();

    if(st->source == FileStar)
      ScrollFile(new_top_line);		/* set up new st->text_lines */

    /* --- 
       Check out the scrolling situation. If we want to scroll, but BeginScroll
       says we can't then repaint,  + 10 is so we repaint most of the time.
      ----*/
    if(redraw ||
       (st->top_text_line - new_top_line + 10 >= num_display_lines ||
        new_top_line - st->top_text_line + 10 >= num_display_lines) ||
	BeginScroll(st->screen_start_line,
                    st->screen_start_line + num_display_lines - 1) != 0) {
        /* Too much text to scroll, or can't scroll -- just repaint */
        st->top_text_line = new_top_line;
        redraw_scroll_text();
        return;
    }

    if(new_top_line > st->top_text_line  ) {
        /*------ scroll down ------*/
        while(new_top_line > st->top_text_line) {
            ScrollRegion(1);
	    if(st->source == FileStar)
	      l = num_display_lines - (new_top_line - st->top_text_line);
	    else
              l = st->top_text_line + num_display_lines;
            if(l < st->num_lines) {
                len = min(st->line_lengths[l], st->screen_width);
                PutLine0n8b(st->screen_start_line + num_display_lines - 1, 0,
                          st->text_lines[l], len);
            }
            st->top_text_line++;
        }
    } else {
        /*------ scroll up -----*/
        while(new_top_line < st->top_text_line) {
            ScrollRegion(-1);
            st->top_text_line--;
	    if(st->source == FileStar)
	      l = st->top_text_line - new_top_line;
	    else
	      l = st->top_text_line;
	    len = min(st->line_lengths[l], st->screen_width);
	    PutLine0n8b(st->screen_start_line, 0, st->text_lines[l], len);
        }
    }
    EndScroll();
    fflush(stdout);
}



/*----------------------------------------------------------------------
     Return the number of lines of text with the current formatting
  ----*/
int
get_scroll_text_lines()
{
    return(st->num_lines);
}



/*----------------------------------------------------------------------
      Search the set scrolling text

   Args:   start_line -- line to start searching on
           word       -- string to search for

   Returns: the line the word was found on or -2 if it wasn't found

 ----*/
int
search_scroll_text(start_line, word)
     char *word;
     int   start_line;
{
    char tmp[MAX_SCREEN_COLS+1];
    int  l, offset, dlines;

    dlines = ps_global->ttyo->screen_rows - st->screen_other_lines;
    offset = (st->source == FileStar) ? st->top_text_line + 1 : 0;

    for(l = start_line+1; l < st->num_lines; l++) {
	if(st->source == FileStar && l > offset + dlines)
	  ScrollFile(offset += dlines);

	strncpy(tmp, st->text_lines[l-offset], 
		min(st->line_lengths[l-offset], MAX_SCREEN_COLS));
	tmp[min(st->line_lengths[l-offset], MAX_SCREEN_COLS) + 1] = '\0';
        if(srchstr(tmp, word)!= NULL)
          break;
    }

    if(l < st->num_lines) {
        return(l);
    }

    if(st->source == FileStar)		/* wrap offset */
      ScrollFile(offset = 0);

    for(l = 0; l < start_line; l++) {
	if(st->source == FileStar && l > offset + dlines)
	  ScrollFile(offset += dlines);

	strncpy(tmp, st->text_lines[l-offset], 
		min(st->line_lengths[l-offset], MAX_SCREEN_COLS));
	tmp[min(st->line_lengths[l-offset], MAX_SCREEN_COLS) + 1] = '\0';
        if(srchstr(tmp, word)!= NULL)
          break;
    }

    return(l == start_line ? -2 : l);
}
     

char *    
display_parameters(parameter_list)
     PARAMETER *parameter_list;
{
    int        longest_attribute;
    PARAMETER *p;
    char      *d;

    if(parameter_list == NULL) {
        tmp_20k_buf[0] = '\0';

    } else {
        longest_attribute = 0;
    
        for(p = parameter_list; p != NULL; p = p->next)
          longest_attribute = max(longest_attribute, (p->attribute == NULL ?
                                                      0 :
                                                      strlen(p->attribute)));
    
        longest_attribute = min(longest_attribute, 11);
    
        d = tmp_20k_buf;
        for(p = parameter_list; p != NULL; p = p->next) {
            sprintf(d, "%-*s: %s\n", longest_attribute,
                    p->attribute != NULL ? p->attribute : "",
                    p->value     != NULL ? p->value     : "");
            d += strlen(d);
        }
    }
    return(tmp_20k_buf);
}


/*----------------------------------------------------------------------
      Fetch the requested header fields from the msgno specified

   Args: stream -- mail stream of open folder
         msgno -- number of message to get header lines from
         fields -- array of pointers to desired fields

   Returns: allocated string containing matched header lines,
	    NULL on error.
 ----*/
char *
xmail_fetchheader_lines(stream, msgno, fields)
     MAILSTREAM  *stream;
     long         msgno;
     char       **fields;
{
    int   i, old_prefetch;
    char *p, *m, *h = NULL, *match = NULL, tmp[MAILTMPLEN];
    extern int find_field();

    old_prefetch = (int) mail_parameters(stream, SET_PREFETCH, NULL);
    h		 = mail_fetchheader(stream, msgno);
    mail_parameters(stream, SET_PREFETCH, (void *) old_prefetch);

    if(!h)
      return(NULL);				/* fetchheader return error? */

    while(find_field(&h, tmp)){
	for(i = 0; fields[i] && strucmp(tmp, fields[i]); i++)
	  ;

	if(p = fields[i]){			/* interesting field */
	    /*
	     * Hold off allocating space for matching fields until
	     * we at least find one to copy...
	     */
	    if(!match)
	      match = m = fs_get(strlen(h) + strlen(p) + 1);

	    while(*p)				/* copy field name */
	      *m++ = *p++;

	    while(*h && (*m++ = *h++))		/* header includes colon */
	      if(*(m-1) == '\n' && (*h == '\r' || !isspace(*h)))
		break;

	    *m = '\0';				/* tie off match string */
	}
	else{					/* no match, pass this field */
	    while(*h && !(*h++ == '\n' && (*h == '\r' || !isspace(*h))))
	      ;
	}
    }

    return(match ? match : cpystr(""));
}


/*----------------------------------------------------------------------
   Fetch everything except the requested header fields from given msgno

   Args: stream -- mail stream of open folder
         msgno -- number of message to get header lines from
         fields -- array of pointers to UNdesired fields

   Returns: allocated string containing NON-matching header lines,
	    NULL on error.
 ----*/
char *
xmail_fetchheader_lines_not(stream, msgno, fields)
     MAILSTREAM  *stream;
     long         msgno;
     char       **fields;
{
    int   i, old_prefetch;
    char *p, *m, *h = NULL, *match = NULL, tmp[MAILTMPLEN];
    extern int find_field();

    old_prefetch = (int) mail_parameters(stream, SET_PREFETCH, NULL);
    h		 = mail_fetchheader(stream, msgno);
    mail_parameters(stream, SET_PREFETCH, (void *) old_prefetch);

    if(!h)
      return(NULL);				/* fetchheader return error? */

    while(find_field(&h, tmp)){
	for(i = 0; fields[i] && strucmp(tmp, fields[i]); i++)
	  ;

	if(!fields[i]){				/* interesting field */
	    /*
	     * Hold off allocating space for matching fields until
	     * we at least find one to copy...
	     */
	    if(!match)
	      match = m = fs_get(strlen(h) + strlen(tmp) + 1);

	    sstrcpy(&m, tmp);			/* copy field name */
	    while(*h && (*m++ = *h++))		/* header includes colon */
	      if(*(m-1) == '\n' && (*h == '\r' || !isspace(*h)))
		break;

	    *m = '\0';				/* tie off match string */
	}
	else{					/* no match, pass this field */
	    while(*h && !(*h++ == '\n' && (*h == '\r' || !isspace(*h))))
	      ;
	}
    }

    return(match ? match : cpystr(""));
}


int
find_field(h, tmp)
     char **h;
     char *tmp;
{
    if(!h || !*h || !**h || isspace(**h))
      return(0);

    while(**h && **h != ':' && !isspace(**h))
      *tmp++ = *(*h)++;

    *tmp = '\0';
    return(1);
}


#ifdef	_WINDOWS
/*----------------------------------------------------------------------
    Return characters in scroll tool buffer serially

   Args: n -- index of char to return

   Returns: returns the character at index 'n', or -1 on error or
	    end of buffer.

 ----*/
int
mswin_readscrollbuf(n)
    int n;
{
    int    c;
    static char **orig = NULL, **l, *p;
    static int    lastn;

    if(!st)
      return(-1);

    /*
     * All of these are mind-numbingly slow at the moment...
     */
    switch(st->source){
      case CharStar :
	return((n >= strlen((char *)st->text)) ? -1 : ((char *)st->text)[n]);

      case CharStarStar :
	/* BUG? is this test rigorous enough? */
	if(orig != (char **)st->text || n < lastn){
	    lastn = n;
	    if(orig = l = (char **)st->text)	/* reset l and p */
	      p = *l;
	}
	else{				/* use cached l and p */
	    c = n;			/* and adjust n */
	    n -= lastn;
	    lastn = c;
	}

	while(l){			/* look for 'n' on each line  */
	    for(; n && *p; n--, p++)
	      ;

	    if(n--)			/* 'n' found ? */
	      p = *++l;
	    else
	      break;
	}

	return((l && *l) ? *p ? *p : '\n' : -1);

      case FileStar :
	return((fseek((FILE *)st->text, (long) n, 0) < 0
		|| (c = fgetc((FILE *)st->text)) == EOF) ? -1 : c);

      default:
	return(-1);
    }
}
#endif
