#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: mailcmd.c,v 4.240 1994/10/11 16:37:58 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   *
    ***********************************************************************
 

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

/*======================================================================
     mailcmd.c
     The meat and pototoes of mail processing here:
       - initial command processing and dispatch
       - save message
       - capture address off incoming mail
       - jump to specific numbered message
       - open (broach) a new folder
       - search message headers (where is) command
  ====*/

#include "headers.h"


#ifdef ANSI
void      cmd_delete(struct pine *, MSGNO_S *, int);
void      cmd_undelete(struct pine *, MSGNO_S *, int);
void      cmd_reply(struct pine *, MSGNO_S *, int);
void      cmd_forward(struct pine *, MSGNO_S *, int);
void      cmd_bounce(struct pine *, MSGNO_S *, int);
void      cmd_print(struct pine *, MSGNO_S *, int);
void      cmd_save(struct pine *, MSGNO_S *, int, int);
void      cmd_export(struct pine *, MSGNO_S *, int, int);
void      cmd_pipe(struct pine *, MSGNO_S *, int);
void      cmd_flag(struct pine *, MSGNO_S *, int);
int	  save(char *, CONTEXT_S *, MESSAGECACHE *);
int	  create_for_save(MAILSTREAM *, CONTEXT_S *, char *);
void	  select_sort(struct pine *, int);
void	  aggregate_select(struct pine *, MSGNO_S *, int);
int	  select_number(MAILSTREAM *, MSGNO_S *, long);
int	  select_date(MAILSTREAM *, MSGNO_S *, long);
int	  select_text(MAILSTREAM *, MSGNO_S *, long);
int	  select_flagged(MAILSTREAM *, MSGNO_S *, long);
void	  apply_command(struct pine *, MSGNO_S *, int);
void	  jump_to(MSGNO_S *, int, int);
void	  search_headers(MAILSTREAM *, int, MSGNO_S *);
char	 *build_selected_sequence(MAILSTREAM *, MSGNO_S *, long *);
char	 *build_sequence(MAILSTREAM *, long *);
int	  pseudo_selected(MSGNO_S *);
void	  restore_selected(MSGNO_S *);

#else
void      cmd_delete();
void      cmd_undelete();
void      cmd_reply();
void      cmd_forward();
void      cmd_bounce();
void      cmd_print();
void      cmd_save();
void      cmd_export();
void      cmd_pipe();
void      cmd_flag();
int       save();
int	  create_for_save();
void	  aggregate_select();
int	  select_number();
int	  select_date();
int	  select_text();
int	  select_flagged();
void	  apply_command();
void      select_sort();
void	  jump_to();
void	  search_headers();
char	 *build_selected_sequence();
char	 *build_sequence();
int	  pseudo_selected();
void	  restore_selected();
#endif


/*
 * List of Select options used by apply_* functions...
 */
static char *sel_pmt1 = "ALTER message selection : ";
static ESCKEY_S sel_opts1[] = {
    {'a', 'a', "A", "unselect All"},
    {'c', 'c', "C", NULL},
    {'b', 'b', "B", "Broaden selctn"},
    {'n', 'n', "N", "Narrow selctn"},
    {-1, 0, NULL, NULL}
};


static char *sel_pmt2 = "SELECT criteria : ";
static ESCKEY_S sel_opts2[] = {
    {'a', 'a', "A", "select All"},
    {'c', 'c', "C", "select Cur"},
    {'n', 'n', "N", "Number"},
    {'d', 'd', "D", "Date"},
    {'t', 't', "T", "Text"},
    {'s', 's', "S", "Status"},
    {-1, 0, NULL, NULL}
};


static char *sel_pmt3 = "APPLY command : ";
static ESCKEY_S sel_opts3[] = {
    {'d', 'd',  "D", "Del"},
    {'u', 'u',  "U", "Undel"},
    {'r', 'r',  "R", "Reply"},
    {'f', 'f',  "F", "Forward"},
    {'y', 'y',  "Y", "prYnt"},
    {'t', 't',  "T", "TakeAddr"},
    {'s', 's',  "S", "Save"},
    {'e', 'e',  "E", "Export"},
    { -1,   0, NULL, NULL},
    { -1,   0, NULL, NULL},
#ifdef	LATER
    {'b', 'b', "B", "Bounce"},
#endif
    {-1, 0, NULL, NULL}
};


static char *sel_flag = 
    "Select New, Deleted, Answered, or Important messages ? ";
static char *sel_flag_not = 
    "Select NOT New, NOT Deleted, NOT Answered or NOT Tagged msgs ? ";
static ESCKEY_S sel_flag_opt[] = {
    {'n', 'n', "N", "New"},
    {'i', 'i', "I", "Important"},
    {'d', 'd', "D", "Deleted"},
    {'a', 'a', "A", "Answered"},
    {'!', '!', "!", "Not"},
    {-1, 0, NULL, NULL}
};

static char *sel_date = "Select On, Before or Since the date to be entered ? ";
static ESCKEY_S sel_date_opt[] = {
    {'o', 'o', "O", "On"},
    {'b', 'b', "B", "Before"},
    {'s', 's', "S", "Since"},
    {-1, 0, NULL, NULL}
};

static char *sel_text =
    "Select based on To, From, Cc, or Subject fields or All message text ? ";
static ESCKEY_S sel_text_opt[] = {
    {'f', 'f', "F", "From"},
    {'s', 's', "S", "Subject"},
    {'t', 't', "T", "To"},
    {'a', 'a', "A", "All Text"},
    {'c', 'c', "C", "Cc"},
    {-1, 0, NULL, NULL}
};

static char *select_num =
  "Enter comma-delimited list of numbers (dash between ranges): ";

static char *flag_text = "Flag New, Deleted, Answered, or Important ? ";
static char *flag_text2	=
    "Flag NOT New, NOT Deleted, NOT Answered, or NOT Important ? ";
static ESCKEY_S flag_text_opt[] = {
    {'n', 'n', "N", "New"},
    {'i', 'i', "I", "Important"},
    {'d', 'd', "D", "Deleted"},
    {'a', 'a', "A", "Answered"},
    {'!', '!', "!", "Not"},
    {-1, 0, NULL, NULL}
};


 
/*----------------------------------------------------------------------
         The giant switch on the commands for index and viewing

  Input:  command  -- The command char/code
          in_index -- flag indicating command is from index
          orig_command -- The original command typed before pre-processing
  Output: force_mailchk -- Set to tell caller to force call to new_mail().

  Result: Manifold

          Returns 1 if the message number or attachment to show changed 
 ---*/
int
process_cmd(state, msgmap, command, in_index, orig_command, force_mailchk)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  command, in_index, orig_command;
     int	 *force_mailchk;
{
    int           question_line, a_changed, is_unread;
    long          new_msgno, del_count, old_msgno, cur_msgno, i,
		  hide_count, exld_count, select_count;
    char         *newfolder, prompt[80+MAXFOLDER];
    MESSAGECACHE *mc;
    CONTEXT_S    *tc;
#ifdef	DOS
    extern long coreleft();
#endif

    dprint(4, (debugfile, "\n - process_cmd((%d)%c) -\n",
                                                 command, (char)command));

    question_line         = -3;
    state->status_changed = 0;
    state->mangled_screen = 0;
    state->mangled_footer = 0;
    state->mangled_header = 0;
    state->next_screen    = SCREEN_FUN_NULL;
    cur_msgno		  = mn_get_cur(msgmap);
    old_msgno             = cur_msgno;
    mc                    = NULL;
    a_changed             = 0;
    *force_mailchk        = 0;

    switch (command)
      {
          /*------------- Help --------*/
        case PF1:
        case OPF1:
        case OOPF1:
        case OOOPF1:
        case '?':
        case ctrl('G'):
          if(state->nr_mode) {
              q_status_message(1, 3, 5, "No help text currently available");
              break;
          }
	  /*
	   * We're not using the h_mail_view portion of this right now because
	   * that call is being handled in scrolltool() before it gets
	   * here.  Leave it in case we change how it works.
	   */
          helper(in_index?h_mail_index : h_mail_view,
		 in_index?"HELP FOR FOLDER INDEX VIEW":"HELP FOR MESSAGE VIEW",
		 0);
          dprint(2, (debugfile,"MAIL_CMD: did help command\n"));
          state->mangled_screen = 1;
          break;


          /*--------- Return to main menu ------------*/
        case PF3: 
        case 'm':
          if(state->nr_mode && command == 'm')
            goto bogus;
          if(state->nr_mode && command == PF3)
	    goto do_quit;
          state->next_screen = main_menu_screen;
#ifdef	DOS
	  flush_index_cache();		/* save room on PC */
#endif
          dprint(2, (debugfile,"MAIL_CMD: going back to main menu\n"));
          break;


          /*------- View mail or attachment --------*/
        case ctrl('M'):
        case ctrl('J'):
	  if(!in_index){
	      q_status_message(0, 1, 3,
			  "\007No default action in the Message Text screen.");
	      break;
	  }

        case PF4:
        case 'v':
	  if(in_index) {
	      if(mn_get_total(msgmap) < 1L) {
		  q_status_message(0, 1, 3, "\007No message to view");
	      }
	      else if(mn_total_cur(msgmap) > 1L){
		  q_status_message(0, 1, 3,
		    "\007Can only view one message at a time!  Try Zoom Cmd.");
	      }
	      else {
		  state->next_screen = mail_view_screen;
#ifdef	DOS
		  flush_index_cache();		/* save room on PC */
#endif
	      }
	  }
	  else if(state->nr_mode)
	    goto bogus;
	  else
	    attachment_screen(state, msgmap);

          break;


          /*---------- Previous message ----------*/
        case PF5: 
        case KEY_UP:
        case 'p':
        case ctrl('P'):		    
	  if(mn_get_total(msgmap) < 1L){
	      q_status_message(0, 1, 3, "\007No message in folder");
	  }
	  else if(mn_total_cur(msgmap) > 1L){
	      q_status_message1(0,0,2,
				"\007%s msgs selected.  Try paging commands!",
				long2string(mn_total_cur(msgmap)));
	  }
	  else if(cur_msgno > 1L){
	      mn_dec_cur(state->mail_stream, msgmap);
	      if(cur_msgno == mn_get_cur(msgmap))
		q_status_message(0, 0, 1,
			       "\007Already on first message in Zoomed Index");
	      else
		cur_msgno = mn_get_cur(msgmap);
	  }
	  else
	    q_status_message(0, 0, 1, "Already on first message");

          break;


          /*---------- Next Message ----------*/
        case PF6:
        case KEY_DOWN:
        case 'n':
        case ctrl('N'):
	  if(mn_total_cur(msgmap) > 1L){
	      q_status_message1(0,0,2,
				"\007%s msgs selected.  Try paging commands!",
				long2string(mn_total_cur(msgmap)));
	  }
	  else if(mn_get_total(msgmap) > 0L
		  && cur_msgno < mn_get_total(msgmap)){
	      mn_inc_cur(state->mail_stream, msgmap);
	      if(cur_msgno == mn_get_cur(msgmap))
		q_status_message(0,0,1,"\007No more messages in Zoomed Index");
	      else
		cur_msgno = mn_get_cur(msgmap);
          }
	  else{
	      sprintf(prompt, "\007No m%s",
		      (mn_get_total(msgmap) > 0)
		        ? "ore messages" : "essages in folder");

	      if(!state->nr_mode
		 && (IS_NEWS(state->mail_stream)
		     || (state->context_current->use & CNTXT_INCMNG))){
		  char nextfolder[MAXPATH];

		  strcpy(nextfolder, state->cur_folder);
		  if(next_folder(nextfolder, nextfolder,
				 state->context_current, FALSE))
		    strcat(prompt, ".  Press TAB for next folder.");
		  else
		    strcat(prompt, ".  No more folders to TAB to.");
	      }

	      q_status_message(0, 0, 1, prompt);

	      if(!IS_NEWS(state->mail_stream))
		*force_mailchk = 1;
	  }
          break;


          /*---------- Delete message ----------*/
        case PF9: 
        case 'd':
        case KEY_DEL:
          if(state->nr_mode) {
	    if(command == PF9)
	      goto do_forward;
	    else
              goto bogus;
	  }

	  cmd_delete(state, msgmap, 0);
	  cur_msgno = mn_get_cur(msgmap);
	  break;
          

          /*---------- Undelete message ----------*/
        case PF10:
        case 'u':
          if(state->nr_mode) {
	    if(command == PF10)
	      goto do_jump;
	    else
              goto bogus;
	  }

	  cmd_undelete(state, msgmap, 0);
          break;


          /*---------- Reply to message ----------*/
        case PF11: 
        case 'r':
          if(state->anonymous && command == PF11) {
	    if(in_index)
	      goto do_sortindex;
	    else
	      goto do_index;
	  }

          if(state->nr_mode && command == PF11)
	    goto do_print;
          if(state->nr_mode)
            goto bogus;

	  cmd_reply(state, msgmap, 0);
	  cur_msgno = mn_get_cur(msgmap);
          break;


          /*---------- Forward message ----------*/
        case PF12: 
        case 'f':
do_forward:
          if(command == PF12) {
	    if(state->anonymous)
              goto bogus;
	    if(state->nr_mode)
	      goto do_save;
	  }

	  cmd_forward(state, msgmap, 0);
	  cur_msgno = mn_get_cur(msgmap);
          break;


          /*---------- Quit pine ------------*/
        case 'q':
        case OPF3:
          if(state->nr_mode && command == OPF3)
            goto do_export;
do_quit:
	  state->next_screen = quit_screen;
	  dprint(1, (debugfile,"MAIL_CMD: quit\n"));		    
          break;


          /*---------- Compose message ----------*/
        case OPF4:		    
        case 'c':
          if(state->anonymous)
            goto bogus;
          state->prev_screen = in_index ? mail_index_screen :
                                        mail_view_screen;
#ifdef	DOS
	  flush_index_cache();		/* save room on PC */
#endif
          compose_screen(state);
          state->mangled_screen = 1;
          break;


          /*--------- Folders menu ------------*/
	case OPF5: 
        case 'l':
          if(state->anonymous)
	    goto bogus;
          if(state->nr_mode) {
	    if(command == OPF5)
	      goto do_sortindex;
	    goto bogus;
	  }
          state->next_screen = folder_screen;
#ifdef	DOS
	  flush_index_cache();		/* save room on PC */
#endif
          dprint(2, (debugfile,"MAIL_CMD: going to folder/collection menu\n"));
          break;


          /*---------- Open specific new folder ----------*/
        case OPF6:
        case 'g':
          if(state->nr_mode)
            goto bogus;

	  tc = (state->context_last
		      && !(state->context_current->type & FTYPE_BBOARD)) 
                       ? state->context_last : state->context_current;

          newfolder = broach_folder(question_line, 1, &tc);
#if	defined(DOS) && !defined(_WINDOWS)
	  if(newfolder && *newfolder == '{' && coreleft() < 20000){
	      q_status_message(0, 0, 2,
			       "\007Not enough memory to open IMAP folder");
	      newfolder = NULL;
	  }
#endif
          if(newfolder != NULL) {
              dprint(9, (debugfile, "do_broach_folder (%s, %s)\n",
                         newfolder, tc->context));
              if(do_broach_folder(newfolder, tc) > 0)
                  /* so we go out and come back into the index */
                  state->next_screen = mail_index_screen;
          }
          break;
    	  
    	    
          /*------- Go to index (or tab) ----------*/
        case OPF7:
        case 'i':
do_index:
          if(!in_index) {
#ifdef	DOS
	      flush_index_cache();		/* save room on PC */
#endif
              state->next_screen = mail_index_screen;
          }
	  else {
	      if(command == OPF7)
		goto do_tab;
	      else
		q_status_message(0, 0, 3, "\007Already in Index");
          }
          break;

do_tab:
          /*------- Skip to next interesting message -----------*/
        case TAB :
          if(mn_get_total(msgmap) > 0) {
              new_msgno = next_sorted_flagged((F_UNDEL 
					       | ((IS_NEWS(state->mail_stream))
						    ? 0 : F_UNSEEN)
					       | F_OR_FLAG),
					      state->mail_stream,
					      cur_msgno + 1L, &is_unread);
              if(is_unread){
                  if(new_msgno > 0){
		      mn_set_cur(msgmap, new_msgno);
                      cur_msgno = new_msgno;
                  }
	      }
	  }

	  /*
	   * If there weren't any unread messages left, OR there
	   * aren't any messages at all, we may want to offer  to
	   * go on to the next folder...
	   */
	  if(!is_unread || mn_get_total(msgmap) <= 0){
	      char ret = 'n';

	      if(!state->nr_mode
		 && (state->context_current
		    && (state->context_current->use&(CNTXT_INCMNG|CNTXT_NEWS)))
		 && context_isambig(state->cur_folder)){
		  char nextfolder[MAXPATH];
		  int  another = 0;

		  strcpy(nextfolder, state->cur_folder);
		  while(1){
		      if(!(next_folder(nextfolder, nextfolder,
					       state->context_current, TRUE))){
			  if(strucmp(state->cur_folder, state->inbox_name)){
			      sprintf(prompt, "No more %ss.  Return to \"%s\"",
				     (state->context_current->use&CNTXT_INCMNG)
				       ? "incoming folder" : "news group", 
				      state->inbox_name);
			      ret = want_to(prompt, 'y', 'x', NO_HELP, 0, 0);
			      if(ret == 'y'){
				  if(do_broach_folder(state->inbox_name,
						   state->context_current) > 0)
				    state->next_screen = mail_index_screen;
			      }
			  }
			  else
			    q_status_message1(0, 0, 1, "No more %ss",
				     (state->context_current->use&CNTXT_INCMNG)
				        ? "incoming folder" : "news group");


			  break;
		      }

		      sprintf(prompt, "%sView next %s \"%s\"? ",
			      (another++) ? "" : "No more messages.  ",
			      (state->context_current->use&CNTXT_INCMNG)
				 ? "Incoming folder" : "news group",
			      nextfolder);

		      /*
		       * When help gets added, this'll have to become
		       * a loop like the rest...
		       */
		      if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
			  static ESCKEY_S next_opt[] = {
			      {'y', 'y', "Y", "Yes"},
			      {'n', 'n', "N", "No"},
			      {TAB, 'n', "Tab", "NextNew"},
			      {-1, 0, NULL, NULL}
			  };

			  ret = radio_buttons(prompt, -3, 0, next_opt, 'y',
					      'x', 0, NO_HELP, 0);
			  if(ret == 'x'){
			      q_status_message(0,0,1,"Command cancelled");
			      break;
			  }
		      }

		      if(ret == 'y' || F_ON(F_AUTO_OPEN_NEXT_UNREAD, state)){
			  if(do_broach_folder(nextfolder,
					      state->context_current) > 0)
			    state->next_screen = mail_index_screen;

			  break;
		      }
		  }
	      }
	      else{
		  if(mn_get_total(msgmap) > 0)
		    q_status_message1(0, 0, 1, "No more %s messages",
			    IS_NEWS(state->mail_stream) ? "undeleted" : "new");
		  else
		    q_status_message(0, 1, 3, "\007No message in folder");
	      }
	  }

          break;


          /*------- Zoom -----------*/
	case OOOPF4:
        case 'z':
	  if(F_OFF(F_ENABLE_AGG_OPS,state) || !in_index)
	    goto bogus;

	  /*
	   * Right now the way zoom is implemented is sort of silly.
	   * There are two per-message flags where just one and a 
	   * global "zoom mode" flag to suppress messags from the index
	   * should suffice.
	   */
	  if(mn_get_total(msgmap) < 1L){
	      q_status_message(0,2,3,"\007No messages to Zoom on");
	  }
	  else if(any_lflagged(msgmap, MN_HIDE)){
	      dprint(4, (debugfile, "\n\n ---- Exiting ZOOM mode ----\n"));
	      q_status_message(0, 0, 2, "Index Zoom Mode is now off");
	      for(i = 1L; i <= mn_get_total(msgmap); i++){
		  if(get_lflag(state->mail_stream, msgmap, i, MN_HIDE))
		    set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
	      }
	  }
	  else{					/* ENTER zoomed index mode */
	      dprint(4, (debugfile, "\n\n ---- Entering ZOOM mode ----\n"));
	      del_count = 0L;
	      if(any_lflagged(msgmap, MN_SLCT)){
		  long first = 0L;
		  for(i = 1L; i <= mn_get_total(msgmap); i++){
		      if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
			  set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
		      }
		      else{
			  del_count++;
			  if(!first)
			    first = i;
		      }
		  }

		  if(!get_lflag(state->mail_stream, msgmap, cur_msgno,MN_SLCT))
		    mn_set_cur(msgmap, first);
	      }
	      else
		q_status_message(0,1,3,
				 "\007No selected messages to Zoom on");

	      if(del_count)
		q_status_message2(0, 1, 2,
	"In Zoomed Index of %s message%s.  Use \"Z\" to restore regular Index",
				  comatose(del_count), plural(del_count));
	  }

          break;


          /*---------- Jump To ----------*/
       case OOPF8:
	  if(!in_index)
	    goto do_tab;
       case '0':
       case '1':
       case '2':
       case '3':
       case '4':
       case '5':
       case '6':
       case '7':
       case '8':
       case '9':
          if(F_OFF(F_ENABLE_JUMP,state))
	    goto bogus;
       case 'j':
do_jump:
	  jump_to(msgmap, question_line,
		  (command >= '0' && command <= '9') ? command : '\0');
	  cur_msgno		= mn_get_cur(msgmap);
	  state->mangled_footer = 1;
	  break;


          /*---------- Search (where is command) ----------*/
       case OPF8:
       case ctrl('W'):
       case 'w':		/* Note, whereis only effective in Index */
	  search_headers(state->mail_stream, question_line, msgmap);
	  cur_msgno		= mn_get_cur(msgmap);
	  state->mangled_footer = 1;
          break;


          /*---------- print message on paper ----------*/
        case OPF9:
        case 'y':
do_print:
          if(state->anonymous || (state->nr_mode && command == OPF9))
            goto bogus;

          if(mn_get_total(msgmap) < 1L){
              q_status_message(0, 1, 3, "\007No message in folder");
	      break;
	  }

	  cmd_print(state, msgmap, 0);
          break;


          /*---------- Take Address ----------*/
        case OPF10:
        case 't':
          if(state->nr_mode)
            goto bogus;

	  cmd_take_addr(state, msgmap, 0);
          break;


          /*---------- Save Message ----------*/
        case OPF11: 
        case 's':
          if(state->anonymous || (state->nr_mode && command == OPF11))
            goto bogus;
do_save:
          if(mn_get_total(msgmap) == 0) {
              q_status_message(0, 0, 2, "\007No message in folder to Save");
              break;
          }

          cmd_save(state, msgmap, question_line, 0);
	  cur_msgno = mn_get_cur(msgmap);
          break;


          /*---------- Export message ----------*/
        case OPF12:
        case 'e':
          if(state->anonymous || (state->nr_mode && command == OPF12))
            goto bogus;
do_export:
          if(mn_get_total(msgmap) == 0) {
              q_status_message(0, 0, 2, "\007No message in folder");
              break;
          }

          cmd_export(state, msgmap, question_line, 0);
	  cur_msgno = mn_get_cur(msgmap);
          state->mangled_footer = 1;

          break;


          /*---------- Expunge ----------*/
        case OOPF3:
        case 'x':
          if(state->nr_mode || !in_index)
            goto bogus;

          dprint(2, (debugfile, "\n - expunge -\n"));
	  if(IS_NEWS(state->mail_stream) && state->mail_stream->rdonly){
	      del_count = count_flagged(state->mail_stream, "DELETED")
					       - any_lflagged(msgmap, MN_EXLD);
	      if(del_count > 0L){
		  state->mangled_footer = 1;
		  sprintf(prompt, "Exclude %ld message%s from %s", del_count,
			  plural(del_count), pretty_fn(state->cur_folder));
		  if(F_ON(F_AUTO_EXPUNGE, state)
		     || want_to(prompt, 'y', 0, NO_HELP, 0, 0) == 'y'){
		      msgno_exclude(state->mail_stream, msgmap);
		      clear_index_cache();
		      state->mangled_header = 1;
		      q_status_message2(1, 2, 4, "%s message%s excluded",
					long2string(del_count),
					plural(del_count));
		  }
		  else
		    q_status_message(1, 2, 4, "No messages excluded");
	      }
	      else
		q_status_message(1, 2, 4, "No deleted messages to exclude");

              break;
	  } else if(READONLY_FOLDER){
              q_status_message(1, 2, 4, "Can't expunge. Folder is read-only");
              break;
          }

	  if((del_count = count_flagged(state->mail_stream, "DELETED")) == 0){
	     q_status_message(1, 2, 4, 
		"Nothing to Expunge!  No messages marked \"Deleted\".");
	     *force_mailchk = 1;
	     break;
	  }

	  if(F_OFF(F_AUTO_EXPUNGE,state)) {
	      int ret;

	      sprintf(prompt, "Expunge %ld message%s from %s", del_count,
		      plural(del_count), pretty_fn(state->cur_folder));
	      state->mangled_footer = 1;
	      if((ret = want_to(prompt, 'y', 'x', NO_HELP, 0, 0)) == 'n'){
		  break;
	      }else if(ret == 'x') {		/* ^C */
		  q_status_message(0,0,3,"Expunge cancelled");
		  break;
	      }
	  }

	  /*
	   * count local flags so we can maintain the total count
	   * without having to traipse thru the whole folder every time.
	   * we don't want to actually removed the flags here in case
	   * the expunge should fail for some reason...
	   */
	  hide_count = exld_count = select_count = 0L;
	  if(!any_lflagged(msgmap, MN_NONE)){
	      /*
	       * Make sure c-client elt's current
	       */
	      FETCH_ALL_FLAGS(state->mail_stream);
	      for(i = 1L; i <= mn_get_total(msgmap); i++){
		  if((mc = mail_elt(state->mail_stream, mn_m2raw(msgmap, i)))
		     && mc->deleted){
		      if(get_lflag(state->mail_stream, msgmap, i, MN_HIDE))
			hide_count++;

		      if(get_lflag(state->mail_stream, msgmap, i, MN_EXLD))
			exld_count++;

		      if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
			select_count++;
		  }
	      }
	  }

          dprint(8,(debugfile, "Expunge max:%ld cur:%ld kill:%d\n",
                    mn_get_total(msgmap), cur_msgno, del_count));
          StartInverse();
          PutLine0(0, 0, "**");			/* indicate delay */
	  MoveCursor(state->ttyo->screen_rows -3, 0);
          fflush(stdout);

	  mail_expunge(state->mail_stream);
	  dprint(2,(debugfile,"expunge complete cur:%ld max:%ld\n",
		    mn_get_cur(msgmap), mn_get_total(msgmap)));

          /*
	   * mm_exists and mm_expunge take care of updating max_msgno
	   * and selecting a new message should the selected get removed
	   */
          reset_check_point();
          sort_current_folder();
          cur_msgno = mn_get_cur(msgmap);
          PutLine0(0, 0, "  ");			/* indicate delay's over */
          EndInverse();
          fflush(stdout);
          if(state->expunge_count > 0) {
              q_status_message3(0, 0, 3,
                        "Expunged %s message%s from folder \"%s\"",
                         long2string(state->expunge_count),
                         plural(state->expunge_count),
			 pretty_fn(state->cur_folder));
              state->expunge_count = 0;

	      /*
	       * This is kind of hokey.  We decrement each type of 
	       * local flag's count according to the number we counted
	       * before the expunge...
	       */
	      if(hide_count)
		dec_lflagged(msgmap, MN_HIDE, hide_count);

	      if(exld_count)
		dec_lflagged(msgmap, MN_EXLD, exld_count);

	      if(select_count)
		dec_lflagged(msgmap, MN_SLCT, select_count);

	      /*
	       * remember: since the ps->mail_box_changed bit got
	       * set, the call to new_mail() in the loops that called
	       * us will take care of fixing up the current msgno
	       * if there are selected msgs and such...
	       */
          }
	  else
	    q_status_message1(0, 0, 3,
			      "No messages expunged from folder \"%s\"",
			      pretty_fn(state->cur_folder));

          break;


          /*------- Unexclude -----------*/
	case OOPF4:
        case '&':
	  if(!in_index)
	    goto bogus;
	  else if(!(IS_NEWS(state->mail_stream)
		    && state->mail_stream->rdonly))
            q_status_message(1, 2, 3,
			     "\007Unexclude not available for mail folders");
	  else{
	      del_count = any_lflagged(msgmap, MN_EXLD);
	      if(del_count > 0L){
		  state->mangled_footer = 1;
		  sprintf(prompt, "UNexclude %ld message%s in %s", del_count,
			  plural(del_count), pretty_fn(state->cur_folder));
		  if(F_ON(F_AUTO_EXPUNGE, state)
		     || want_to(prompt, 'y', 0, NO_HELP, 0, 0) == 'y'){
		      msgno_include(state->mail_stream, msgmap);
		      clear_index_cache();
		      sort_current_folder();
		      state->mangled_header = 1;
		      q_status_message2(1, 2, 4, "%s message%s UNexcluded",
					long2string(del_count),
					plural(del_count));
		  }
		  else
		    q_status_message(1, 2, 4, "No messages UNexcluded");
	      }
	      else
		q_status_message(1, 2, 4, "No excluded messages to UNexclude");
	  }

          break;


          /*------- Make Selection -----------*/
	case OOPF5:
        case ';':
	  if(!in_index || F_OFF(F_ENABLE_AGG_OPS, state))
	    goto bogus;

	  if(mn_get_total(msgmap) > 0L){
	      aggregate_select(state, msgmap, question_line);
	      cur_msgno = mn_get_cur(msgmap);
	  }
	  else
	    q_status_message(0, 0, 2, "\007No message in folder");

          break;


          /*------- Apply command -----------*/
	case OOPF6:
        case 'a':
	  if(!in_index || F_OFF(F_ENABLE_AGG_OPS, state))
	    goto bogus;

	  if(mn_get_total(msgmap) > 0L){
	      if(any_lflagged(msgmap, MN_SLCT) > 0L)
		apply_command(state, msgmap, question_line);
	      else
		q_status_message(0, 1, 2,
		       "\007No messages to Apply command to.  Try \"Select\"");

	      cur_msgno = mn_get_cur(msgmap);
	  }
	  else
	    q_status_message(0, 0, 2, "\007No message in folder");

          break;


          /*-------- Sort command -------*/
	case OOPF7:
        case '$':
do_sortindex:
	  if(!in_index){
	    if(command == '$')
              goto bogus;
	    else
	      goto do_jump;
	  }
	  dprint(1, (debugfile,"MAIL_CMD: sort\n"));		    
	  select_sort(state, question_line);
	  state->mangled_footer = 1;		/* at least, body changed */
	  clear_index_cache();
	  sort_current_folder();
          break;


          /*------- Toggle Full Headers -----------*/
	case OOPF9: 
        case 'h':
          if(F_OFF(F_ENABLE_FULL_HDR,state))
            goto bogus;
          state->full_header = !state->full_header;
          q_status_message3(0, 1, 3,
		"Display of full headers is now o%s.  Use %s to turn back o%s",
			    state->full_header ? "n" : "ff",
			    F_ON(F_USE_FK, state) ? "F9" : "H",
			    !state->full_header ? "n" : "ff");
          a_changed = 1;
          break;


          /*------- Bounce -----------*/
	case OOPF10:
        case 'b':
          if(F_OFF(F_ENABLE_BOUNCE,state))
            goto bogus;

          if(command == PF12) {
	    if(state->anonymous)
              goto bogus;
	    if(state->nr_mode)
	      goto do_save;
	  }

	  cmd_bounce(state, msgmap, 0);
	  cur_msgno = mn_get_cur(msgmap);
          break;


          /*------- Flag -----------*/
	case OOPF11:
        case '*':
	  if(F_OFF(F_ENABLE_FLAG,state))
	    goto bogus;

          dprint(4, (debugfile, "\n - flag message -\n"));
	  cmd_flag(state, msgmap, 0);
	  cur_msgno = mn_get_cur(msgmap);

          break;


#ifndef DOS
          /*------- Pipe message -----------*/
	case OOPF12:
        case '|':
	  if(F_ON(F_ENABLE_PIPE,state)){
	      cmd_pipe(state, msgmap, 0);
	      break;
	  }
#endif


          /*--------- Default, unknown command ----------*/
        default:
        bogus:
	  bogus_command(orig_command, F_ON(F_USE_FK,state) ? "F1" : "?");
          break;
      }

    /*
     * Here we try to avoid unsightly message queue buildup.
     * The problem is several messages may have queued up as the result
     * of an error or something, so we want to write all but the last 
     * one here.  The last will get written in the loop that called
     * us.  What we really want to avoid is having some error message
     * bubble up after the next command that the user executes.
     */
    while(messages_queued(NULL) > 1)
      sleep(display_message('x'));

    return(cur_msgno != old_msgno || a_changed);
}



/*----------------------------------------------------------------------
   Complain about bogus input

  Args: ch -- input command to complain about
	help -- string indicating where to get help

 ----*/
void
bogus_command(cmd, help)
    int   cmd;
    char *help;
{
    if(cmd == ctrl('Q') || cmd == ctrl('S'))
      q_status_message1(0, 0, 2,
 "\007%s char received.  See \"preserve-start-stop\" feature in Setup/Config.",
			pretty_command(cmd));
    else
      q_status_message4(0, 0, 2,
		      "\007Command \"%s\" not defined for this screen.%s%s%s",
		      pretty_command(cmd),
		      (help) ? " Use " : "",
		      (help) ?  help   : "",
		      (help) ? " for help" : "");
}



/*----------------------------------------------------------------------
   Execute DELETE message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: with side effect of "current" message delete flag set

 ----*/
void
cmd_delete(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    int           is_unread;
    long	  cur_msgno, new_msgno, del_count = 0;
    char	 *sequence = NULL, prompt[128];
    MESSAGECACHE *mc;

    dprint(4, (debugfile, "\n - delete message -\n"));
    if(mn_get_total(msgmap) <= 0L) {
	q_status_message(0, 0, 3,"\007No message in folder");
	return;
    }

    cur_msgno = mn_get_cur(msgmap);
    if(READONLY_FOLDER){
	q_status_message(1, 0, 3, "Can't delete message. Folder is read-only");
	return;
    }

    if(state->io_error_on_stream) {
	state->io_error_on_stream = 0;
	mail_check(state->mail_stream); /* forces write */
    }

    if(agg){
	if(!(sequence = build_selected_sequence(state->mail_stream,
						msgmap, &del_count)))
	  return;				/* silently fail */
	else
	  sprintf(prompt, "%ld message%s ", del_count, plural(del_count));
    }
    else{
	del_count = 1L;				/* deleting single message */

	(void)mail_fetchstructure(state->mail_stream,
				  mn_m2raw(state->msgmap, cur_msgno), NULL);
	mc = mail_elt(state->mail_stream, mn_m2raw(msgmap, cur_msgno));
	if(!mc || state->dead_stream) {
	    q_status_message1(1, 2, 4,
			     "Can't delete message %s. Error accessing folder",
			      long2string(cur_msgno));
	    return;
	}

	if(cur_msgno >= mn_get_total(msgmap))
	  strcpy(prompt, "Last message ");
	else
	  sprintf(prompt, "Message %ld ", cur_msgno);

	if(!mc->deleted){
	    clear_index_cache_ent(cur_msgno);
	    sequence = cpystr(long2string(mn_m2raw(msgmap, cur_msgno)));
	}
	else
	  strcat(prompt, "already ");
    }

    dprint(3,(debugfile, "Delete: msg %s%s\n",
	      (sequence) ? sequence : long2string(cur_msgno),
	      (sequence) ? "" : " ALREADY DELETED"));

    if(sequence){
	mail_setflag(state->mail_stream, sequence, "\\DELETED");
	fs_give((void **)&sequence);
	state->status_changed = 1;
	check_point_change();
	update_titlebar_status(mail_elt(state->mail_stream,
					mn_m2raw(msgmap, cur_msgno)));
    }

    if(agg){
	sprintf(prompt, "%ld selected message%s marked for deletion",
		del_count, plural(del_count));
    }
    else{
	is_unread = 1;
	if((IS_NEWS(state->mail_stream)
	    || (state->context_current->use & CNTXT_INCMNG))
	   || F_ON(F_DEL_SKIPS_DEL, state))
	  new_msgno = next_sorted_flagged(F_UNDEL, state->mail_stream,
					  cur_msgno + 1, &is_unread);
	if(F_OFF(F_DEL_SKIPS_DEL,state) && cur_msgno < mn_get_total(msgmap)){
	    mn_inc_cur(state->mail_stream, msgmap);
	    cur_msgno = mn_get_cur(msgmap);
	}
	else if(F_ON(F_DEL_SKIPS_DEL,state) && is_unread){
	    /* more interesting mail, goto next msg */
	    mn_set_cur(msgmap, new_msgno);
	    cur_msgno = new_msgno;
	}

	if(prompt[0])
	  strcat(prompt, "deleted");

	if((IS_NEWS(state->mail_stream)
	    || (state->context_current->use & CNTXT_INCMNG))
	   && !state->nr_mode && !is_unread){
	    char nextfolder[MAXPATH];

	    if(prompt[0] == '\0')
	      strcat(prompt, "Last undeleted message");

	    strcpy(nextfolder, state->cur_folder);
	    if(next_folder(nextfolder,nextfolder,state->context_current,FALSE))
	      strcat(prompt, ".  Press TAB for next folder.");
	    else
	      strcat(prompt, ".  No more folders to TAB to.");
	}
    }

    if(prompt[0])
      q_status_message(0, 0, 3, prompt);

    mn_set_cur(msgmap, cur_msgno);
}



/*----------------------------------------------------------------------
   Execute UNDELETE message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: with side effect of "current" message delete flag UNset

 ----*/
void
cmd_undelete(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    long	  cur_msgno, del_count = 0;
    char	 *sequence = NULL;
    ENVELOPE     *e;
    MESSAGECACHE *mc;

    dprint(4, (debugfile, "\n - undelete -\n"));
    if(mn_get_total(msgmap) < 1L) {
	q_status_message(0, 0, 3, "\007No message in folder");
	return;
    }
    else if(READONLY_FOLDER){
	q_status_message(1, 0, 3,
			 "Can't undelete message. Folder is read-only");
	return;
    }

    cur_msgno = mn_get_cur(msgmap);

    if(agg){
	if(!(sequence = build_selected_sequence(state->mail_stream,
						msgmap, &del_count)))
	  return;				/* silently fail */
    }
    else{
	del_count = 1L;				/* deleting single message */
	e  = mail_fetchstructure(state->mail_stream,mn_m2raw(msgmap,cur_msgno),
				 NULL); 
	mc = mail_elt(state->mail_stream, mn_m2raw(msgmap, cur_msgno));
	if(!e || !mc || state->dead_stream) {
	    q_status_message(1, 2, 4,
			 "\007Can't undelete message. Error accessing folder");
	    return;
	}
	else if(mc->deleted) {
	    clear_index_cache_ent(cur_msgno);
	    sequence = cpystr(long2string(mn_m2raw(msgmap, cur_msgno)));
	}
	else{
	    q_status_message(0, 0, 3, 
			     "Can't undelete a message that isn't deleted");
	    return;
	}
    }

    dprint(3,(debugfile, "Undeleted: msg %s\n",
	      (sequence) ? sequence : "already deleted"));

    if(sequence){
	mail_clearflag(state->mail_stream, sequence, "\\DELETED");
	fs_give((void **)&sequence);
	state->status_changed = 1;

	if(del_count == 1L){
	    mc = mail_elt(state->mail_stream, mn_m2raw(msgmap, cur_msgno));
	    update_titlebar_status(mc);
	    q_status_message(0, 0, 3,
			    "Deletion mark removed, message won't be deleted");
	}
	else
	  q_status_message2(0, 0, 3,
			    "Deletion mark removed from %s message%s",
			    comatose(del_count), plural(del_count));

	if(state->io_error_on_stream) {
	    state->io_error_on_stream = 0;
	    mail_check(state->mail_stream); /* forces write */
	}
	else
	  check_point_change();
    }
}



/*----------------------------------------------------------------------
   Execute FLAG message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: with side effect of "current" message FLAG flag set or UNset

 ----*/
void
cmd_flag(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    int           r, setflag = 1, usersetflag;
    long	  i, flagged;
    char	 *sequence, *flag, *userflag;
    MESSAGECACHE *mc;

    if(mn_get_total(msgmap) <= 0L) {
	q_status_message(0, 1, 3,"\007No message in folder");
	return;
    }
    else if(READONLY_FOLDER){
	q_status_message(1, 1, 3, "Can't flag message. Folder is read-only");
	return;
    }
    else if(state->io_error_on_stream) {
	state->io_error_on_stream = 0;
	mail_check(state->mail_stream); /* forces write */
	return;
    }
    else if(agg && !pseudo_selected(msgmap))
      return;

    while(1){
	r = radio_buttons((setflag) ? flag_text : flag_text2,
			  -3, 0, flag_text_opt, 'i', 'x', 0, NO_HELP, 0);

	if(r == 'x'){
	    q_status_message(0, 0, 2, "Flag message cancelled");
	    goto fini;
	}
	else if(r == '!')
	  setflag = !setflag;
	else
	  break;
    }

    flag = (r == 'd') ? "\\DELETED" : 
	     (r == 'n') ? "\\SEEN" :
		(r == 'a') ? "\\ANSWERED" : "\\FLAGGED";

    userflag = (r == 'd') ? "Deleted" : 
		 (r == 'n') ? "New" :
		   (r == 'a') ? "Answered" : "Important";

    usersetflag = setflag;
    /* goofy case because we don't tell the user the real flag name */
    if(r == 'n')
      setflag = !setflag;

    FETCH_ALL_FLAGS(state->mail_stream); /* make sure all flags current */
    for(i = 1L; i <= state->mail_stream->nmsgs; i++)
      mail_elt(state->mail_stream, i)->sequence = 0; /* clear "sequence" bit */

    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
	/* if not already set, go on... */
	mc = mail_elt(state->mail_stream, mn_m2raw(msgmap, i));
	if(setflag == ((r == 'd') ? mc->deleted :
			 (r == 'n') ? mc->seen :
			   (r == 'a') ? mc->answered : mc->flagged))
	  continue;

	mc->sequence = 1;			/* set "sequence" flag */
	clear_index_cache_ent(i);		/* force new index line */
	check_point_change();			/* count state change */
    }

    if(!(sequence = build_sequence(state->mail_stream, &flagged))){
	if(mn_total_cur(msgmap) <= 1)
	  q_status_message3(0,1,3,"Message %s already %sflagged %s",
			    long2string(mn_get_cur(msgmap)),
			    usersetflag ? "" : "un", userflag);
	else
	  q_status_message2(0,1,3,"Selected messages already %sflagged %s",
			    usersetflag ? "" : "un", userflag);
	goto fini;
    }

    dprint(3,(debugfile, "Flag_cmd: msgs %s, %s %s\n", sequence,
	      setflag ? "set" : "cleared", flag));

    if(setflag)
      mail_setflag(state->mail_stream, sequence, flag);
    else
      mail_clearflag(state->mail_stream, sequence, flag);

    fs_give((void **)&sequence);

    if(mn_total_cur(msgmap) <= 1L){
	sprintf(tmp_20k_buf, "Message %s %sflagged %s",
		long2string(mn_get_cur(msgmap)), usersetflag ? "": "un",
		userflag);
    }else{
	if(flagged == mn_total_cur(msgmap) || flagged == 0L)
	  sprintf(tmp_20k_buf, "%ld messages %sflagged %s",
		  mn_total_cur(msgmap), usersetflag ? "" : "un",
		  userflag);
	else
	  sprintf(tmp_20k_buf, "%ld message%s %sflagged %s, %ld unchanged",
		  flagged, plural(flagged), usersetflag ? "" : "un", userflag,
		  mn_total_cur(msgmap) - flagged);
    }

    q_status_message(0, 1, 3, tmp_20k_buf);

    /*
     * Special case to warn that not all flags persist from one session
     * to the next.  Don't worry about "deleted" since c-client fakes
     * that one.  Don't worry about "New" since we don't display it.
     */
    if(IS_NEWS(state->mail_stream) && state->mail_stream->rdonly && setflag
       && r != 'd' && r != 'n')
      q_status_message1(0, 1, 3, "\"%s\" flag ONLY set while folder is open",
			userflag);

    state->status_changed = 1;

  fini:
    if(agg)
      restore_selected(msgmap);
}



/*----------------------------------------------------------------------
   Execute REPLY message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: reply sent or not

 ----*/
void
cmd_reply(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    if(mn_get_total(msgmap) > 0L){
#ifdef	DOS
	flush_index_cache();		/* save room on PC */
#endif
	if(agg && !pseudo_selected(msgmap))
	  return;

	reply(state);

	if(agg)
	  restore_selected(msgmap);

	state->mangled_screen = 1;
    }
    else
      q_status_message(0, 0, 2, "\007No message in folder");
}



/*----------------------------------------------------------------------
   Execute FORWARD message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: selected message[s] forwarded or not

 ----*/
void
cmd_forward(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    if(mn_get_total(msgmap) > 0L) {
#ifdef	DOS
	flush_index_cache();		/* save room on PC */
#endif
	if(agg && !pseudo_selected(msgmap))
	  return;

	forward(state);

	if(agg)
	  restore_selected(msgmap);

	if(state->anonymous)
	  state->mangled_footer = 1;
	else
	  state->mangled_screen = 1;
    }
    else
      q_status_message(0,0,2,"\007Folder is empty.  No message to Forward.");
}



/*----------------------------------------------------------------------
   Execute BOUNCE message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: selected message[s] bounced or not

 ----*/
void
cmd_bounce(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    if(mn_get_total(msgmap) > 0L) {
#ifdef	DOS
	flush_index_cache();			/* save room on PC */
#endif
	if(agg && !pseudo_selected(msgmap))
	  return;

	bounce(state);
	if(agg)
	  restore_selected(msgmap);

	if(state->anonymous)
	  state->mangled_footer = 1;
	else
	  state->mangled_screen = 1;
    }
    else
      q_status_message(0, 0, 2, "\007Folder is empty.  No message to Bounce.");
}



/*----------------------------------------------------------------------
   Execute save message command: prompt for folder and call function to save

  Args: screen_line    --  Line on the screen to prompt on
        message        --  The MESSAGECACHE entry of message to save

 Result: The folder lister can be called to make selection; mangled screen set

   This does the prompting for the folder name to save to, possibly calling 
 up the folder display for selection of folder by user.                 
 ----*/
void
cmd_save(state, msgmap, screen_line, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int          screen_line;
     int	  agg;
{
    char       newfolder[MAXFOLDER+1], prompt[MAXFOLDER+80], nmsgs[32], *p;
    HelpType   help;
    int        rc, saveable_count = 0;
    long       i, number_saved = 0L;
    static     char folder[MAXFOLDER+1] = {'\0'};
    static     CONTEXT_S *last_context = NULL;
    CONTEXT_S *cntxt = NULL, *tc;
    ESCKEY_S   ekey[5];
    ENVELOPE  *e = NULL;
    MESSAGECACHE *mc;

    dprint(4, (debugfile, "\n - saving message -\n"));

    if(agg && !pseudo_selected(msgmap))
      return;

    if(mn_total_cur(msgmap) <= 1L){
	nmsgs[0] = '\0';
	e = mail_fetchstructure(state->mail_stream,
				mn_m2raw(msgmap, mn_get_cur(msgmap)), NULL);
	if(!e) {
	    q_status_message(1, 2, 4,
			    "\007Can't save message.  Error accessing folder");
	    goto jumpout;
	}
    }
    else
      sprintf(nmsgs, "%s msgs ", comatose(mn_total_cur(msgmap)));

    /* start with the default save context */
    if((cntxt = default_save_context(state->context_list)) == NULL)
       cntxt = state->context_list;

    if(e && (ps_global->save_msg_rule == MSG_RULE_FROM
	       || ps_global->save_msg_rule == MSG_RULE_SENDER
	       || ps_global->save_msg_rule == MSG_RULE_RECIP)){
	char      *folder_name;

	if(ps_global->save_msg_rule == MSG_RULE_FROM)
	  folder_name = e->from ? e->from->mailbox 
				  : e->sender ? e->sender->mailbox : NULL;
	else if(ps_global->save_msg_rule == MSG_RULE_SENDER)
	  folder_name = e->sender ? e->sender->mailbox
				    : e->from ? e->from->mailbox : NULL;
	else if(ps_global->mail_stream
		&& ps_global->mail_stream->mailbox[0] == '*') /* IS_NEWS ? */
	  folder_name = (ps_global->mail_stream->mailbox[1] == '{')
			   ? strchr(ps_global->mail_stream->mailbox, '}') + 1
			   : &ps_global->mail_stream->mailbox[1];
	else
	  folder_name = e->to ? e->to->mailbox : NULL;

	/*
	 * Now fish out the user's name from the mailbox portion of
	 * the address and put it in folder.
	 */
	if(!get_uname(folder_name, folder, MAXFOLDER+1))
	  strcpy(folder, DEFAULT_SAVE);
    }
    else if(ps_global->save_msg_rule == MSG_RULE_LAST){
	if(last_context)
	  cntxt = last_context;
	else
	  strcpy(folder, DEFAULT_SAVE);
    }
    else
      strcpy(folder, DEFAULT_SAVE);

    /* how many context's can be saved to... */
    for(tc = state->context_list; tc; tc = tc->next)
      if(!(tc->type&FTYPE_BBOARD))
        saveable_count++;

    /* set up extra command option keys */
    rc = 0;
    ekey[rc].ch      = ctrl('T');
    ekey[rc].rval    = 2;
    ekey[rc].name    = "^T";
    ekey[rc++].label = "To Fldrs";

    if(saveable_count > 1){
	ekey[rc].ch      = ctrl('P');
	ekey[rc].rval    = 10;
	ekey[rc].name    = "^P";
	ekey[rc++].label = "Prev Collection";

	ekey[rc].ch      = ctrl('N');
	ekey[rc].rval    = 11;
	ekey[rc].name    = "^N";
	ekey[rc++].label = "Next Collection";
    }

    if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
	ekey[rc].ch      = TAB;
	ekey[rc].rval    = 12;
	ekey[rc].name    = "TAB";
	ekey[rc++].label = "Complete";
    }

    ekey[rc].ch = -1;

    *newfolder = '\0';
    help = NO_HELP;
    ps_global->mangled_footer = 1;
    while(1) {
	/* only show collection number if more than one available */
	if(ps_global->context_list->next){
	    sprintf(prompt, "SAVE %sto folder in <%.25s> [%s] : ",
		    nmsgs, cntxt->label[0], folder);
	}
	else
	  sprintf(prompt, "SAVE %sto folder [%s] : ", nmsgs, folder);

        rc = optionally_enter(newfolder, screen_line, 0, MAXFOLDER, 1, 0,
                              prompt, ekey, help, 0);

	if(rc == -1){
	    q_status_message(1, 1, 3, "\007Error reading folder name");
	    goto jumpout;
	}
	else if(rc == 1){
	    q_status_message(0, 0, 2, "Save message cancelled");
	    goto jumpout;
	}
	else if(rc == 2) {
	    int  f_rc;				/* folder_lister return val */
	    void (*redraw)() = ps_global->redrawer;

	    push_titlebar_state();
	    f_rc = folder_lister(ps_global,SaveMessage,cntxt,&cntxt,newfolder,
				 ps_global->context_list, NULL);
            ClearScreen();
	    pop_titlebar_state();
	    redraw_titlebar();
            if(ps_global->redrawer = redraw)	/* reset old value, and test */
              (*ps_global->redrawer)();

	    if(f_rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
	      break;
	}
	else if(rc == 3){
            help = (help == NO_HELP) ? h_oe_save : NO_HELP;
	}
	else if(rc == 10){			/* Previous collection */
	    CONTEXT_S *start;
	    start = cntxt;
	    tc    = cntxt;

	    while(1){
		if((tc = tc->next) == NULL)
		  tc = ps_global->context_list;

		if(tc == start)
		  break;

		if((tc->type&FTYPE_BBOARD) == 0)
		  cntxt = tc;
	    }
	}
	else if(rc == 11){			/* Next collection */
	    tc = cntxt;

	    do
	      if((cntxt = cntxt->next) == NULL)
		cntxt = ps_global->context_list;
	    while((cntxt->type&FTYPE_BBOARD) && cntxt != tc);
	}
	else if(rc == 12){			/* file name completion! */
	    if(!folder_complete(cntxt, newfolder))
	      Writechar('\007', 0);

	}
	else if(rc != 4)
          break;
    }

    dprint(9, (debugfile, "rc = %d, \"%s\"  \"%s\"\n", rc, newfolder,folder));
    if(rc == 1 || (!*newfolder && !*folder) || (rc == 2 && !*newfolder)) {
        q_status_message(0,0,2,"No folder named; save message cancelled");
	goto jumpout;
    }

    removing_trailing_white_space(newfolder);
    removing_leading_white_space(newfolder);

    last_context = cntxt;		/* remember for next time */
    if(!*newfolder)
      strcpy(newfolder, folder);
    else
      strcpy(folder, newfolder);

    /* is it a nickname? */
    if(context_isambig(newfolder)
       && (p = folder_is_nick(newfolder, cntxt->folders))){
	strcpy(newfolder, p);
    }

    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
	if((e=mail_fetchstructure(state->mail_stream,mn_m2raw(msgmap,i),NULL))
	   && (mc=mail_elt(ps_global->mail_stream, mn_m2raw(msgmap,i)))){
	    if(!save(newfolder, cntxt, mc)){
		dprint(1, (debugfile,
			   "FAILED save of msg-id <%s>\n",
			   (e && e->message_id) ? e->message_id : "NONE"));

		if(mn_total_cur(msgmap) > 1L && number_saved)
		  q_status_message1(0, 1, 3,
				    "%ld message saved before error occurred",
				    (void *)number_saved);

		goto jumpout;
	    }

	    number_saved++;
	    if(!mc->deleted && !READONLY_FOLDER
	       && F_OFF(F_SAVE_WONT_DELETE, ps_global)){
		mail_setflag(ps_global->mail_stream,
			     long2string(mn_m2raw(msgmap, i)),
			     "\\DELETED");
		ps_global->mangled_header = 1;
		ps_global->status_changed = 1;
		clear_index_cache_ent(i);
		check_point_change();
	    }
	}
	else{
	    q_status_message(1, 2, 4,
			     "\007Can't save message. Error accessing folder");
	    goto jumpout;
	}
    }

    if(mn_total_cur(msgmap) <= 1L){
	if(ps_global->context_list->next && context_isambig(newfolder)){
	    sprintf(tmp_20k_buf, 
		    "Message %s copied to \"%.15s%s\" in <%.15s%s>",
		    long2string(mn_get_cur(msgmap)), newfolder,
		    (strlen(newfolder) > 15) ? "..." : "",
		    cntxt->label[0],
		    (strlen(cntxt->label[0]) > 15) ? "..." : "");
	}
	else
	  sprintf(tmp_20k_buf, "Message %s copied to folder \"%.27s%s\"",
		  long2string(mn_get_cur(msgmap)), newfolder,
		  (strlen(newfolder) > 27) ? "..." : "");

	if(F_OFF(F_SAVE_WONT_DELETE, ps_global))
	  strcat(tmp_20k_buf, " and marked deleted");
    }
    else
      sprintf(tmp_20k_buf, "%ld messages saved%s", number_saved, 
	      F_OFF(F_SAVE_WONT_DELETE, ps_global)?" and deleted":"");

    q_status_message(0, 1, 3, tmp_20k_buf);

    if(F_ON(F_SAVE_ADVANCES, state)){
	mn_inc_cur(state->mail_stream, msgmap);
    }

  jumpout:
    if(agg)
      restore_selected(msgmap);
}




/*----------------------------------------------------------------------
        Do the work of actually saving message in a folder

    Args: folder  -- The folder to save the message in
	  context -- context to interpret name in if not fully qualified
          message -- The MESSAGECACHE entry of message to save

  Result: Returns 0 if message couldn't be saved
          returns 1 if it was saved

Just gather the message pieces and append to requested folder
in provided context...
 ----*/
int
save(folder, context, message)
    char         *folder;
    CONTEXT_S    *context;
    MESSAGECACHE *message;
{
    int         rv, rc;
    char       *tmp, *save_folder, *flags, *date;
    long        mlen;
    STORE_S    *so;
    STRING      msg;
    MAILSTREAM *save_stream = NULL;
    SourceType  src;
#ifdef	DOS
    struct {			/* hack! stolen from dawz.c */
	int fd;
	unsigned long pos;
    } d;
    extern STRINGDRIVER dawz_string;

    src = FileStar;
#else
    src = CharStar;
#endif

    save_folder = (strucmp(folder, ps_global->inbox_name) == 0)
                   ? ps_global->VAR_INBOX_PATH : folder;

    /*
     * Compare the current stream (the save's source) and the stream
     * the destination folder will need...
     */
    save_stream = context_same_stream(context->context, save_folder,
				      ps_global->mail_stream);

    /*
     * Here we try to use context_copy if at all possible.  This should 
     * preserve flags, and, in the remote case, let the server move
     * the bits without network traffic.  It also turns out to be 
     * necessary talking to certain, known imapd's that don't/can't
     * provide APPEND...
     *
     * So, if we don't yet have a save_stream, then the source folder
     * and the destination aren't remote *and* the same server, so we
     * want to see if they're both local *and* exist (or will exist)
     * in the same local driver...
     */
    if(!save_stream)
      save_stream = context_same_driver(context->context, save_folder,
					ps_global->mail_stream);

    ps_global->try_to_create = 0;		/* set in mm_notify? */
    rv = rc = 0;

    /*
     * At this point, if we found a save_stream, then the current stream
     * is either remote, or local with both current folder and destination
     * in the same driver...
     */
    if(save_stream){
	while(!(rv = (int) context_copy(context->context, save_stream,
					long2string(message->msgno),
					save_folder))){
	    if(rc++ || !ps_global->try_to_create)   /* abysmal failure! */
	      break;				/* c-client returned error? */

	    if((context->use & CNTXT_INCMNG) && context_isambig(save_folder)){
		q_status_message(0, 3, 3,
	       "\007Can only save to existing folders in Incoming Collection");
		break;
	    }

	    ps_global->try_to_create = 0;	/* reset for next time */
	    if(!create_for_save(save_stream, context, save_folder))
	      break;
	}
    }
    else {
	/*
	 * Looks like the current mail stream and the stream needed by
	 * the destination folder don't match, so we might as well see
	 * if there's another pine stream to piggy back the APPEND for
	 * the destination on...
	 */
	save_stream = context_same_stream(context->context, save_folder,
					  ps_global->inbox_stream);

	/*
	 * First build the raw message in a storage object, then map
	 * it into a c-client STRING struct for handing to context_append...
	 */
	if(!(so = so_get(src, NULL, WRITE_ACCESS))){
	    q_status_message(1, 2, 4,
			     "\007Problem creating space for message text.");
	    return(0);
	}

	if(!(tmp = mail_fetchheader(ps_global->mail_stream, message->msgno))){
	    so_give(&so);
	    return(0);
	}
	else
	  so_puts(so, tmp);

#ifdef	DOS
	/* set append file and install dos_gets so message text
	 * is fetched directly to disk.  Also, protect the elt pointer
	 * as our elt manager may have swapped it out during mail_gc...
	 */
	{ long msgno = message->msgno;
	  mail_parameters(ps_global->mail_stream, SET_GETS, (void *)dos_gets);
	  append_file = (FILE *) so_text(so);
	  mail_gc(ps_global->mail_stream, GC_TEXTS);
	  message = mail_elt(ps_global->mail_stream, msgno);
	}
#endif

	if(!(tmp = mail_fetchtext(ps_global->mail_stream, message->msgno))){
	    so_give(&so);
	    return(0);
	}
#ifndef	DOS
	else
	  so_puts(so, tmp);
#endif

	so_seek(so, 0L, 0);			/* just in case */

	/*
	 * What's really needed is a way to pipe this crap right into
	 * context_append...
	 */
	/* set up string driver */
#ifdef	DOS
	d.fd  = fileno((FILE *)so_text(so));
	d.pos = 0L;
	if((mlen = filelength(d.fd)) < message->rfc822_size){
	    q_status_message3(1, 2, 4, 
			  "\007Message to save shrank!  (#%ld: %ld --> %ld)",
			  (void *)message->msgno, (void *)message->rfc822_size,
			  (void *)mlen);
	    dprint(1, (debugfile,
		   "BOTCH: %ld save shrank mc->size == %ld, string == %ld\n",
		   message->msgno, message->rfc822_size, mlen));
	    so_give(&so);
	    return(0);
	}

	INIT(&msg, dawz_string, (void *)&d, mlen);
#else
	if((mlen = strlen((char *)so_text(so))) < message->rfc822_size){
	    q_status_message3(1, 2, 4, 
			  "\007Message to save shrank!  (#%ld: %ld --> %ld)",
			  (void *)message->msgno, (void *)message->rfc822_size,
			  (void *)mlen);
	    dprint(1, (debugfile,
		   "BOTCH: %ld save shrank mc->size == %ld, string == %ld\n",
		   message->msgno, message->rfc822_size, mlen));
	    so_give(&so);
	    return(0);
	}

	INIT(&msg, mail_string, (void *)so_text(so), mlen);
#endif

	/*
	 * bulid message's internal date and flags
	 */
	date    = cpystr(mail_date(tmp_20k_buf, message));
	*(flags = tmp_20k_buf) = '\0';		/* NOTE: "flags" overloaded */
	if(message->deleted)
	  sstrcpy(&flags, "\\DELETED ");

	if(message->answered)
	  sstrcpy(&flags, "\\ANSWERED ");

	if(message->flagged)
	  sstrcpy(&flags, "\\FLAGGED ");

	if(message->seen)
	  sstrcpy(&flags, "\\SEEN ");

	if(flags != tmp_20k_buf)
	  *--flags = '\0';			/* tie off tmp_20k_buf   */

	flags = cpystr(tmp_20k_buf);		/* point flags at a copy */
	while(!(rv = (int) context_append_full(context->context, NULL,
					       save_folder, flags,date,&msg))){
	    if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
	      break;				/* c-client returned error? */

	    if((context->use & CNTXT_INCMNG) && context_isambig(save_folder)){
		q_status_message(0, 3, 3,
	       "\007Can only save to existing folders in Incoming Collection");
		break;
	    }

	    ps_global->try_to_create = 0;	/* reset for next time */
	    if(!create_for_save(NULL, context, save_folder))
	      break;

	    SETPOS((&msg), 0L);			/* reset string driver */
	}

#ifdef	DOS
	append_file = NULL;				/* reset DOS hacks */
	mail_parameters(ps_global->mail_stream, SET_GETS, (void *)NULL);
	mail_gc(ps_global->mail_stream, GC_TEXTS);
#endif
	fs_give((void **)&date);
	fs_give((void **)&flags);
	so_give(&so);
    }

    ps_global->try_to_create = 0;		/* reset for next time */
    return(rv);
}


/*----------------------------------------------------------------------
    Offer to create a non-existant folder to copy message[s] into

   Args: stream -- stream to use for creation
	 context -- context to create folder in
	 name -- name of folder to create

 Result: FALSE if user opts not or create failed (c-client writes error)
	 TRUE if create worked
 ----*/
int
create_for_save(stream, context, folder)
    MAILSTREAM *stream;
    CONTEXT_S  *context;
    char       *folder;
{
    if(ps_global->context_list->next && context_isambig(folder)){
	sprintf(tmp_20k_buf,
		"Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create",
		folder, (strlen(folder) > 15) ? "..." : "",
		context->label[0],
		(strlen(context->label[0]) > 15) ? "..." : "");
    }
    else
      sprintf(tmp_20k_buf,"Folder \"%.40s\" doesn't exist.  Create", folder);

    if(want_to(tmp_20k_buf, 'y', 'n', NO_HELP, 0, 0) != 'y'){
	q_status_message(0, 1, 3, "Save message cancelled");
	return(0);
    }

    if(!context_create(context->context, create_proto(stream, context, folder),
		       folder))
      return(0);

    return(1);
}


/*----------------------------------------------------------------------
    Export a message to a plain file in users home directory

   Args:  q_line -- screen line to prompt on
         message -- MESSAGECACHE enrty of message to export

 Result: 
 ----*/
void
cmd_export(state, msgmap, q_line, agg)
    struct pine *state;
    MSGNO_S     *msgmap;
    int          q_line;
    int		 agg;
{
    HelpType  help;
    char      filename[MAXPATH+1], full_filename[MAXPATH+1],*ill;
    int       rc, new_file, failure = 0, orig_errno;
    ENVELOPE *env;
    BODY     *b;
    long      i, now, count = 0L, start_of_append;
    gf_io_t   pc;
    STORE_S  *store;

    if(ps_global->restricted){
	q_status_message(1, 1, 3, "Pine demo can't export messages to files");
	return;
    }

    if(agg && !pseudo_selected(msgmap))
      return;

    help = NO_HELP;
    filename[0] = '\0';
    while(1) {
        char prompt[100];
#ifdef	DOS
        (void)strcpy(prompt, "File to save message text in: ");
#else
        sprintf(prompt, "EXPORT: (copy message) to file in %s directory: ",
	         F_ON(F_USE_CURRENT_DIR, ps_global) ? "current" : "home");
#endif
        rc = optionally_enter(filename, q_line, 0, MAXPATH, 1, 0,
                 prompt, NULL, help, 0);

        /*--- Help ----*/
        if(rc == 3) {
            help = (help == NO_HELP) ? h_oe_export : NO_HELP;
            continue;
        }

        removing_trailing_white_space(filename);
        removing_leading_white_space(filename);
        if(rc == 1 || filename[0] == '\0') {
            q_status_message(0, 0, 2, "Export message cancelled");
	    goto fini;
        }

        if(rc == 4)
          continue;


        /*-- check out and expand file name. give possible error messages --*/
        strcpy(full_filename, filename);
        if((ill = filter_filename(filename)) != NULL) {
            q_status_message1(1, 1, 3, "\007%s in file name", ill);
            display_message('x');
            sleep(3);
            continue;
        }
#ifndef	DOS
        if(full_filename[0] == '~') {
            if(fnexpand(full_filename, sizeof(full_filename)) == NULL) {
                char *p = strindex(full_filename, '/');
                if(p != NULL)
                  *p = '\0';
                q_status_message1(1, 1, 3,
                    "\007Error expanding file name: \"%s\" unknown user",
    	            full_filename);
                display_message('x');
                sleep(3);
                continue;
            }
        } else if(full_filename[0] != '/') {
#else
        if(full_filename[0] == '\\' || full_filename[1] == ':') {
	    fixpath(full_filename, MAXPATH);
	}
	else{
#endif
	    if(F_ON(F_USE_CURRENT_DIR, ps_global))
              (void)strcpy(full_filename, filename);
	    else
              build_path(full_filename, ps_global->home_dir, filename);
        }

        break; /* Must have got an OK file name */

    }


    /* ---- full_filename already contains the absolute path ---*/
    if(!can_access(full_filename, ACCESS_EXISTS)) {
        char prompt[100];
	static ESCKEY_S access_opts[] = {
	    {'o', 'o', "O", "Overwrite"},
	    {'a', 'a', "A", "Append"},
	    {-1, 0, NULL, NULL}};

	rc = strlen(filename);
        sprintf(prompt,
		"File \"%s%s\" already exists.  Overwrite or append it ? ",
		(rc > 20) ? "..." : "",
                filename + ((rc > 20) ? rc - 20 : 0));
	switch(radio_buttons(prompt,-3,0,access_opts,'a','x',0,NO_HELP,0)){
	  case 'o' :
	    new_file = 1;
	    if(unlink(full_filename) < 0){	/* BUG: breaks links */
		q_status_message2(1, 3, 5, "\007Error deleting old %s: %s",
				  full_filename, error_description(errno));
		return;
	    }

	    break;

	  case 'a' :
	    new_file = 0;
	    break;

	  case 'x' :
	  default :
            q_status_message(0, 0, 2, "Export message cancelled");
	    goto fini;
	}
    }
    else
      new_file = 1;

    dprint(5, (debugfile, "Opening file \"%s\" for export\n", full_filename));

    if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS))){
        q_status_message2(1, 2, 4,
			 "\007Error opening file \"%s\" to export message: %s",
                          full_filename, error_description(errno));
	goto fini;
    }
    else
      gf_set_so_writec(&pc, store);

    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
	env = mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i), &b);
	if(!env) {
	    q_status_message(1, 2, 4,
		      "\007Can't export message. Error accessing mail folder");
	    goto fini;
	}

	/*--------- The message separator -------*/
	now = time(0);
	sprintf(tmp_20k_buf, "%sFrom %s%s%s%s",
		new_file ? "" : NEWLINE,
		(env && env->from) ? env->from->mailbox
				   : "the-concourse-on-high",
		(env && env->from && env->from->host) ? "@" : "",
		(env && env->from && env->from->host) ? env->from->host : "",
		ctime(&now));

	/* write our own newline since this folder's open in binary mode
	 * under DOS...
	 */
	tmp_20k_buf[strlen(tmp_20k_buf) - 1] = '\0';

	start_of_append = ftell((FILE *)so_text(store));

	if(!so_puts(store, tmp_20k_buf)
	   || !so_puts(store, NEWLINE)
	   || !format_message(mn_m2raw(msgmap, i), env, b,
			      (FM_NEW_MESS|FM_DO_PRINT), pc)){
	    orig_errno = errno;		/* save incase things are really bad */
	    failure    = 1;		/* pop out of here */
	    break;
	}
    }

    so_give(&store);				/* release storage */
    if(failure){
#ifndef	DOS
	truncate(full_filename, start_of_append);
#endif
	dprint(1, (debugfile, "FAILED Export: file \"%s\" : %s\n",
		   full_filename,  error_description(orig_errno)));
	q_status_message2(0, 2, 4, "\007Error exporting to \"%s\" : %s",
			  filename, error_description(orig_errno));
    }
    else{
	if(mn_total_cur(msgmap) > 1L)
	  q_status_message3(0, 1, 3, "%s message%s exported to file \"%s\"",
			    long2string(count), plural(count), filename);
	else
	  q_status_message2(0, 1, 3, "Message %s exported to file \"%s\"",
			    long2string(mn_get_cur(msgmap)), filename);
    }

  fini:
    if(agg)
      restore_selected(msgmap);
}



/*----------------------------------------------------------------------
      Execute command to jump to a given message number

    Args: qline -- Line to ask question on

  Result: returns -1 or the message number to jump to
          the mangled_footer flag is set
 ----*/
void
jump_to(msgmap, qline, first_num)
     MSGNO_S *msgmap;
     int      qline, first_num;
{
    char     jump_num_string[80], *j, prompt[70];
    HelpType help;
    int      rc;
    long     jump_num;

    dprint(4, (debugfile, "\n - jump_to -\n"));

    if(mn_get_total(msgmap) < 1L) {
	q_status_message(0, 0, 2, "\007No messages to jump to");
	return;
    }

    if(first_num){
	jump_num_string[0] = first_num;
	jump_num_string[1] = '\0';
    }
    else
      jump_num_string[0] = '\0';

    if(mn_total_cur(msgmap) > 1L){
	sprintf(prompt, "Unselect %s msgs in favor of number to be entered", 
		comatose(mn_total_cur(msgmap)));
	if((rc = want_to(prompt, 'n', 0, NO_HELP, 0, 0)) == 'n')
	    return;
    }

    strcpy(prompt, "Message number to jump to : ");

    help = NO_HELP;
    while (1) {
        rc = optionally_enter(jump_num_string, qline, 0,
                              sizeof(jump_num_string) - 1, 1, 0, prompt,
                              NULL, help, 0);
        if(rc == 3) {
            help = help == NO_HELP ? h_oe_jump : NO_HELP;
            continue;
        }

        if(rc == 0 && *jump_num_string != '\0') {
	    removing_trailing_white_space(jump_num_string);
	    removing_leading_white_space(jump_num_string);
            for(j = jump_num_string; isdigit(*j) || *j == '-'; j++);
	    if(*j != '\0') {
	        q_status_message(0, 2, 2,
                           "\007Invalid number entered. Use only digits 0-9");
            } else {
                jump_num = atol(jump_num_string);
                if(jump_num < 1L) {
	            q_status_message1(1, 2, 2,
			      "\007Message number (%s) must be greater than 0",
			      long2string(jump_num));
                } else if(jump_num > mn_get_total(msgmap)) {
                    q_status_message1(1, 2, 2,
	  "\007Message number must be no more than %s, the number of messages",
		    long2string(mn_get_total(msgmap)));
                } else if(get_lflag(ps_global->mail_stream, msgmap,
				    jump_num, MN_HIDE)){
	            q_status_message1(1, 2, 2,
			  "\007Message number (%s) is not in \"Zoomed Index\"",
			  long2string(jump_num));
		} else {
		    if(mn_total_cur(msgmap) > 1L){
			mn_reset_cur(msgmap, jump_num);
		    }
		    else{
			mn_set_cur(msgmap, jump_num);
		    }

		    break;
                }
            }
            jump_num_string[0] = '\0';
            display_message(NO_OP_COMMAND);
            sleep (3);
            continue;
	}

        if(rc != 4)
          break;
    }
}



/*----------------------------------------------------------------------
     Prompt for folder name to open, expand the name and return it

   Args: qline      -- Screen line to prompt on
         allow_list -- if 1, allow ^T to bring up collection lister

 Result: returns the folder name or NULL
         pine structure mangled_footer flag is set
         may call the collection lister in which case mangled screen will be set

 This prompts the user for the folder to open, possibly calling up
the collection lister if the user types ^T.
----------------------------------------------------------------------*/
char *
broach_folder(qline, allow_list, context)
    int qline, allow_list;
    CONTEXT_S **context;
{
    HelpType	help;
    static char newfolder[MAXFOLDER+1];
    char        expanded[MAXPATH+1],
                prompt[MAXFOLDER+80],
               *last_folder;
    CONTEXT_S  *tc, *tc2;
    ESCKEY_S    ekey[5];
    int         rc, inbox;

    /*
     * the idea is to provide a clue for the context the file name
     * will be saved in (if a non-imap names is typed), and to
     * only show the previous if it was also in the same context
     */
    help           = NO_HELP;
    *expanded      = '\0';
    *newfolder    = '\0';
    last_folder    = NULL;
    inbox          = strucmp(ps_global->cur_folder,ps_global->inbox_name) == 0;
    ps_global->mangled_footer = 1;

    /*
     * if we're given a valid context AND we don't have our current
     * folder open, go with what's given ELSE go with inbox collection
     */
    tc = (inbox) ? (context && *context) ? *context:ps_global->context_current
		 : ps_global->context_list;

    /* set up extra command option keys */
    rc = 0;
    ekey[rc].ch	     = (allow_list) ? ctrl('T') : 0 ;
    ekey[rc].rval    = (allow_list) ? 2 : 0;
    ekey[rc].name    = (allow_list) ? "^T" : "";
    ekey[rc++].label = (allow_list) ? "ToFldrs" : "";

    if(ps_global->context_list->next){
	ekey[rc].ch      = ctrl('P');
	ekey[rc].rval    = 10;
	ekey[rc].name    = "^P";
	ekey[rc++].label = "Prev Collection";

	ekey[rc].ch      = ctrl('N');
	ekey[rc].rval    = 11;
	ekey[rc].name    = "^N";
	ekey[rc++].label = "Next Collection";
    }

    if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
	ekey[rc].ch      = TAB;
	ekey[rc].rval    = 12;
	ekey[rc].name    = "TAB";
	ekey[rc++].label = "Complete";
    }

    ekey[rc].ch = -1;

    while(1) {
	/*
	 * Figure out next default value for this context.  The idea
	 * is that in each context the last folder opened is cached.
	 * It's up to pick it out and display it.  This is fine
	 * and dandy if we've currently got the inbox open, BUT
	 * if not, make the inbox the default the first time thru.
	 */
	if(tc->last_folder[0]){
	    last_folder = (inbox) ? tc->last_folder: ps_global->inbox_name;
	    sprintf(expanded, " [%s]", last_folder);
	    inbox = 1;	/* pretend we're in inbox from here on  */
	}
	else{
	    last_folder = NULL;
	    *expanded = '\0';
	}

	/* only show collection number if more than one available */
	if(ps_global->context_list->next){
	    sprintf(prompt, "GOTO %s in <%.20s> %s%s: ",
		    (tc->type&FTYPE_BBOARD) ? "news group" : "folder",
		    tc->label[0], expanded, *expanded ? " " : "");
	}
	else
	  sprintf(prompt, "GOTO folder %s: ", expanded, *expanded ? " " : "");

        rc = optionally_enter(newfolder, qline, 0, MAXFOLDER, 1 ,0, prompt,
                              ekey, help, 0);

	if(rc == -1){
	    q_status_message(1, 1, 3, "\007Error reading folder name");
	    return(NULL);
	}
	else if(rc == 1){
	    q_status_message(0, 0, 2, "Open Folder cancelled");
	    return(NULL);
	}
	else if(rc == 2){
	    void (*redraw)() = ps_global->redrawer;

	    push_titlebar_state();
	    rc = folder_lister(ps_global, OpenFolder, tc, &tc, newfolder,
			       ps_global->context_list, NULL);
            ClearScreen();
	    pop_titlebar_state();
            redraw_titlebar();
            if(ps_global->redrawer = redraw) /* reset old value, and test */
              (*ps_global->redrawer)();

	    if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
	      break;
	}
	else if(rc == 3){
            help = help == NO_HELP ? h_oe_broach : NO_HELP;
	}
	else if(rc == 10){			/* Previous collection */
	    tc2 = ps_global->context_list;
	    while(tc2->next && tc2->next != tc)
	      tc2 = tc2->next;

	    tc = tc2;
	}
	else if(rc == 11){			/* Next collection */
	    tc = (tc->next) ? tc->next : ps_global->context_list;
	}
	else if(rc == 12){			/* file name completion! */
	    if(!folder_complete(tc, newfolder))
	      Writechar('\007', 0);
	}
	else if(rc != 4)
          break;
    }

    removing_trailing_white_space(newfolder);
    removing_leading_white_space(newfolder);

    if(*newfolder == '\0'  && last_folder == NULL) {
        q_status_message(0, 0, 2, "Open folder cancelled");
        return(NULL);
    }

    if(*newfolder == '\0')
      strcpy(newfolder, last_folder);

    dprint(2, (debugfile, "broach folder, name entered \"%s\"\n",newfolder));

    /*-- Just check that we can expand this. It gets done for real later --*/
    strcpy(expanded, newfolder);
    if (! expand_foldername(expanded)) {
        dprint(1, (debugfile,
                    "Error: Failed on expansion of filename %s (save)\n", 
    	  expanded));
        return(NULL);
    }

    *context = tc;
    return(newfolder);
}




/*----------------------------------------------------------------------
    Actually attempt to open given folder 

  Args: newfolder -- The folder name to open

 Result:  1 if the folder was successfully opened
          0 if the folder open failed and went back to old folder
         -1 if open failed and no folder is left open
      
  Attempt to open the folder name given. If the open of the new folder
  fails then the previously open folder will remain open, unless
  something really bad has happened. The designate inbox will always be
  kept open, and when a request to open it is made the already open
  stream will be used. Making a folder the current folder requires
  setting the following elements of struct pine: mail_stream, cur_folder,
  current_msgno, max_msgno. Attempting to reopen the current folder is a 
  no-op.

  The first time the inbox folder is opened, usually as Pine starts up,
  it will be actually opened.
  ----*/

do_broach_folder(newfolder, new_context) 
     char      *newfolder;
     CONTEXT_S *new_context;
{
    MAILSTREAM *m;
    int         open_inbox, rv;
    char        expanded_file[MAXPATH+1], *old_folder,*old_path, *p;
    long        openmode;

#ifdef	DOS
    openmode = OP_SHORTCACHE;
#else
    openmode = 0L;
#endif
#ifdef	DEBUG
    if(debug > 8)
      openmode |= OP_DEBUG;
#endif
    dprint(1, (debugfile, "About to open folder \"%s\"    inbox: \"%s\"\n",
	       newfolder, ps_global->inbox_name));

    /*----- Little to do to if reopening same folder -----*/
    if(new_context == ps_global->context_current && ps_global->mail_stream
       && strcmp(newfolder, ps_global->cur_folder) == 0)
      return(1);			/* successful open of same folder! */

    /*--- Set flag that we're opening the inbox, a special case ---*/
    /*
     * We want to know if inbox is being opened either by name OR
     * fully qualified path...
     */
    if(open_inbox = (strucmp(newfolder, ps_global->inbox_name) == 0
       || strcmp(newfolder, ps_global->VAR_INBOX_PATH) == 0)){
	new_context = ps_global->context_list; /* restore first context */

	/*
	 * IF we're asked to open inbox AND it's already open AND
	 * the only stream, just return ELSE fall thru and close 
	 * mail_stream returning with inbox_stream as new stream...
	 */
	if(ps_global->inbox_stream 
	   && (ps_global->inbox_stream == ps_global->mail_stream))
	  return(1);
    }

    /*
     * If ambiguous foldername (not fully qualified), make sure it's
     * not a nickname for a folder in the given context...
     */
    strcpy(expanded_file, newfolder); 	/* might get reset below */
    if(!open_inbox && new_context && context_isambig(newfolder)){
	if (p = folder_is_nick(newfolder, new_context->folders)){
	    strcpy(expanded_file, p);
	    dprint(2, (debugfile, "broach_folder: nickname for %s is %s\n",
		       expanded_file, newfolder));
	}
	else if ((new_context->use & CNTXT_INCMNG)
		 && (folder_index(newfolder, new_context->folders) < 0)){
	    q_status_message1(1, 2, 4,
			    "\007Can't find Incoming Folder %s.", newfolder);
	    return(0);
	}
    }

    /*--- Opening inbox, inbox has been already opened, the easy case ---*/
    if(open_inbox && ps_global->inbox_stream != NULL ) {
        expunge_and_close(ps_global->mail_stream, ps_global->cur_folder);

	ps_global->mail_stream              = ps_global->inbox_stream;
        ps_global->new_mail_count           = 0L;
        ps_global->expunge_count            = 0L;
        ps_global->last_msgno_flagged       = 0L;
        ps_global->mail_box_changed         = 0;
        ps_global->noticed_dead_stream      = 0;
        ps_global->noticed_dead_inbox       = 0;
        ps_global->dead_stream              = 0;
        ps_global->dead_inbox               = 0;
	mn_give(&ps_global->msgmap);
	ps_global->msgmap		    = ps_global->inbox_msgmap;
	ps_global->inbox_msgmap		    = NULL;

	dprint(7, (debugfile, "%ld %ld %x\n",
		   mn_get_cur(ps_global->msgmap),
                   mn_get_total(ps_global->msgmap),
		   ps_global->mail_stream));
	/*
	 * remember last context and folder
	 */
	if(context_isambig(ps_global->cur_folder)){
	    ps_global->context_last = ps_global->context_current;
	    strcpy(ps_global->context_current->last_folder,
		 ps_global->cur_folder);
	}

	strcpy(ps_global->cur_folder, ps_global->inbox_name);
	ps_global->context_current = ps_global->context_list;
        clear_index_cache();
        sort_current_folder();		/* MUST sort before restoring msgno! */
        q_status_message2(0, 1, 3, "Opened folder \"INBOX\" with %s message%s",
                          long2string(mn_get_total(ps_global->msgmap)),
                          mn_get_total(ps_global->msgmap) != 1 ? "s" : "" );
	return(1);
    }

    if(!new_context && ! expand_foldername(expanded_file))
      return(0);

    old_folder = NULL;
    old_path   = NULL;
    /*---- now close the old one we had open if there was one ----*/
    if(ps_global->mail_stream != NULL) {
        old_folder   = cpystr(ps_global->cur_folder);
        old_path     = cpystr(ps_global->mail_stream->mailbox);
	if(strcmp(ps_global->cur_folder, ps_global->inbox_name) == 0) {
	    /*-- don't close the inbox stream, save a bit of state --*/
	    if(ps_global->inbox_msgmap)
	      mn_give(&ps_global->inbox_msgmap);

	    ps_global->inbox_msgmap = ps_global->msgmap;
	    ps_global->msgmap       = NULL;

	    dprint(2, (debugfile,
		       "Close - saved inbox state: max %ld\n",
		       mn_get_total(ps_global->inbox_msgmap)));
	} else {
            expunge_and_close(ps_global->mail_stream, ps_global->cur_folder);
	}
    }

    q_status_message1(0, 1, 1, "Opening \"%s\"...", pretty_fn(newfolder));
    flush_status_messages();		/* So they see the message */

    /* 
     * if requested, make access to folder readonly (only once)
     */
    if (ps_global->open_readonly_on_startup) {
	openmode |= OP_READONLY ;
	ps_global->open_readonly_on_startup = 0 ;
    }

    /*
     * The name "inbox" is special, so treat it so 
     * (used to by handled by expand_folder)...
     */
    if(ps_global->nr_mode)
      ps_global->noshow_warn = 1;

    m = context_open((new_context && !open_inbox) ? new_context->context:NULL,
		     NULL, 
		     (open_inbox) ? ps_global->VAR_INBOX_PATH : expanded_file,
		     openmode);

    if(ps_global->nr_mode)
      ps_global->noshow_warn = 0;

    dprint(8, (debugfile, "Opened folder %p \"%s\" (context: \"%s\")\n",
               m, (m) ? m->mailbox : "nil",
	       (new_context) ? new_context->context : "nil"));

    /* Can get m != NULL if correct passwd for remote, but wrong name */
    if(m == NULL || ((p = strindex(m->mailbox, '<')) != NULL &&
                      strcmp(p + 1, "no_mailbox>") == 0)) {
	/*-- non-existent local mailbox, or wrong passwd for remote mailbox--*/
        /* fall back to currently open mailbox */
        rv = 0;
        dprint(8, (debugfile, "Old folder: \"%s\"\n",
               old_folder == NULL ? "" : old_folder));
        if(old_folder != NULL) {
            if(strcmp(old_folder, ps_global->inbox_name) == 0){
                ps_global->mail_stream = ps_global->inbox_stream;
		if(ps_global->msgmap)
		  mn_give(&ps_global->msgmap);

		ps_global->msgmap       = ps_global->inbox_msgmap;
		ps_global->inbox_msgmap = NULL;

                dprint(8, (debugfile, "Reactivate inbox %ld %ld %p\n",
                           mn_get_cur(ps_global->msgmap),
                           mn_get_total(ps_global->msgmap),
                           ps_global->mail_stream));
                strcpy(ps_global->cur_folder, ps_global->inbox_name);
            } else {
                ps_global->mail_stream = mail_open(NULL, old_path, openmode);
                /* mm_log will take care of error message here */
                if(ps_global->mail_stream == NULL) {
                    rv = -1;
                } else {
		    mn_init(&(ps_global->msgmap),
			    ps_global->mail_stream->nmsgs);
                    ps_global->expunge_count       = 0;
                    ps_global->new_mail_count      = 0;
                    ps_global->noticed_dead_stream = 0;
                    ps_global->dead_stream         = 0;
		    ps_global->last_msgno_flagged  = 0L;
		    ps_global->mangled_header	   = 1;

		    clear_index_cache();
                    reset_check_point();
		    if(mn_get_total(ps_global->msgmap) > 0)
		      mn_set_cur(ps_global->msgmap, 1L);

                    q_status_message1(0, 1, 3, "Folder \"%s\" reopened",
                                      old_folder);
                }
            }

	    if(rv == 0)
	      mn_set_cur(ps_global->msgmap,
			 min(mn_get_cur(ps_global->msgmap), 
			     mn_get_total(ps_global->msgmap)));

            fs_give((void **)&old_folder);
            fs_give((void **)&old_path);
        } else {
            rv = -1;
        }
        if(rv == -1) {
            q_status_message(0, 2, 4, "\007No folder opened");
	    mn_set_total(ps_global->msgmap, 0L);
	    mn_set_cur(ps_global->msgmap, -1L);
            strcpy(ps_global->cur_folder, "");
        }
        return(rv);
    } else {
        if(old_folder != NULL) {
            fs_give((void **)&old_folder);
            fs_give((void **)&old_path);
        }
    }

    /*----- success in opening the new folder ----*/
    dprint(2, (debugfile, "Opened folder \"%s\" with %ld messages\n",
	       m->mailbox, m->nmsgs));


    /*--- A Little house keeping ---*/
    ps_global->mail_stream          = m;
    ps_global->expunge_count        = 0L;
    ps_global->new_mail_count       = 0L;
    ps_global->last_msgno_flagged   = 0L;
    ps_global->noticed_dead_stream  = 0;
    ps_global->noticed_dead_inbox   = 0;
    ps_global->dead_stream          = 0;
    ps_global->dead_inbox           = 0;
    mn_init(&(ps_global->msgmap), m->nmsgs);

    /*
     * remember old folder and context...
     */
    if(context_isambig(ps_global->cur_folder))
      strcpy(ps_global->context_current->last_folder,ps_global->cur_folder);

    strcpy(ps_global->cur_folder, (open_inbox) ? ps_global->inbox_name
					       : newfolder);
    if(new_context){
	ps_global->context_last    = ps_global->context_current;
	ps_global->context_current = new_context;
    }

    reset_check_point();
    clear_index_cache();
    ps_global->mail_box_changed = 0;

    /*
     * Start news reading with messages the user's marked deleted
     * hidden from view...
     */
    if(IS_NEWS(ps_global->mail_stream) && ps_global->mail_stream->rdonly)
      msgno_exclude(ps_global->mail_stream, ps_global->msgmap);

    /* UWIN doesn't want to see this message */
    if(!ps_global->nr_mode)
      q_status_message7(0, 2, 4, "%s \"%s\" opened with %s message%s%s",
			IS_NEWS(ps_global->mail_stream)
			  ? "News group" : "Folder",
			pretty_fn(newfolder),
			comatose(mn_get_total(ps_global->msgmap)),
			plural(mn_get_total(ps_global->msgmap)),
			READONLY_FOLDER ?" READONLY" : "",
			NULL, NULL);

    sort_current_folder();

    if(mn_get_total(ps_global->msgmap) > 0L) {
	if(ps_global->start_entry > 0) {
	    mn_set_cur(ps_global->msgmap,
		       min(ps_global->start_entry,
			   mn_get_total(ps_global->msgmap)));
	    ps_global->start_entry = 0;
        }
	/* if faking new, goto first recent, unseen */
	else if(IS_NEWS(ps_global->mail_stream)
		&& F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
	    mn_set_cur(ps_global->msgmap,
		       first_sorted_flagged(F_RECENT|F_UNDEL,m));
	}
	/* if it is an incoming mailbox, goto first unseen */
	else if(open_inbox || (ps_global->context_current->use&CNTXT_INCMNG)
		  || IS_NEWS(ps_global->mail_stream)){
	    mn_set_cur(ps_global->msgmap,
		       first_sorted_flagged(F_UNSEEN|F_UNDEL,m));
	}
        else{
	    mn_set_cur(ps_global->msgmap,
		       mn_get_revsort(ps_global->msgmap)
		         ? 1L
			 : mn_get_total(ps_global->msgmap));
	}
    }
    else{
	mn_set_cur(ps_global->msgmap, -1L);
    }

    /*--- If we just opened the inbox remember it's special stream ---*/
    if(open_inbox && ps_global->inbox_stream == NULL)
      ps_global->inbox_stream = ps_global->mail_stream;
	
    return(1);
}


/*----------------------------------------------------------------------
      Expunge (if confirmed) and close a mail stream

    Args: stream   -- The MAILSTREAM * to close
          folder   -- The name the folder is know by 

   Result:  Mail box is expunged and closed. A message is displayed to
             say what happened
 ----*/
void
expunge_and_close(stream, folder)
     MAILSTREAM *stream;
     char *folder;
{
    long  delete_count, max_folder, seen_not_del, saved_msgs, i;
    char  prompt_b[MAX_SCREEN_COLS+1], short_folder_name[MAXFOLDER*2+1],
          temp[MAXFOLDER*2+1];
    CONTEXT_S *save_context;
    MESSAGECACHE *mc;
    struct variable *vars = ps_global->vars;
    int ret;

    if(stream != NULL) {
        dprint(2, (debugfile, "expunge and close mail stream \"%s\"\n",
                   stream->mailbox));

        if(!stream->rdonly) {

            q_status_message1(0, 0, 1, "Closing \"%s\"...", folder);
	    display_message('x');

	    /* Save read messages? */
	    if(VAR_READ_MESSAGE_FOLDER && VAR_READ_MESSAGE_FOLDER[0]
	       && stream == ps_global->inbox_stream
	       && (seen_not_del = count_flagged(stream, "SEEN UNDELETED"))) {

	      if(F_OFF(F_AUTO_READ_MSGS,ps_global)) {
                sprintf(prompt_b,
                       "Save the %ld read message%s in \"%s\"",
                         seen_not_del, plural(seen_not_del),
                         VAR_READ_MESSAGE_FOLDER);
		ret = want_to(prompt_b, 'y', 0, NO_HELP, 0, 0);
	      }
	      if(F_ON(F_AUTO_READ_MSGS,ps_global) || ret == 'y') {
	        save_context = default_save_context(ps_global->context_list);
	        if(!save_context)
		  save_context = ps_global->context_list;

		FETCH_ALL_FLAGS(stream);
		saved_msgs = 0;
		for(i=1; i <= stream->nmsgs; i++) {
		  mc = mail_elt(stream, (long)i);
		  if(mc && mc->seen && !mc->deleted) {
		    /* saved, set deleted flag in inbox */
		    if(save(VAR_READ_MESSAGE_FOLDER, save_context, mc)) {
			mail_setflag(stream, long2string(i), "\\DELETED");
			saved_msgs++;
		    }
		    /* failure */
		    else {
		      if(saved_msgs > 0) {
                        q_status_message3(0, 2, 3,
	       "Saved %ld of %ld read messages, aborted after error on msg %ld",
			   (void *)saved_msgs, (void *)seen_not_del,
			   (void *)(i+1L));
		      }else {
                        q_status_message2(0, 2, 3,
				       "Unable to save %s read messages to %s",
					long2string(seen_not_del),
					VAR_READ_MESSAGE_FOLDER);
		      }
		      break;
		    }
		  }
		}
	      }
	    }

            delete_count = count_flagged(stream, "DELETED");
	    ret = 'n';
	    if(delete_count){
		max_folder = ps_global->ttyo->screen_cols - 50;
		strcpy(temp, pretty_fn(folder));
		if(strlen(temp) > max_folder){
		    strcpy(short_folder_name, "...");
		    strcat(short_folder_name,temp + strlen(temp) -max_folder);
		}
		else
		  strcpy(short_folder_name, temp);
                
		if(F_OFF(F_AUTO_EXPUNGE,ps_global)) {
		    sprintf(prompt_b,
			    "Expunge the %ld deleted message%s from \"%s\"",
			    delete_count,
			    delete_count == 1 ? "" : "s",
			    short_folder_name);
		    ret = want_to(prompt_b, 'y', 0, NO_HELP, 0, 0);
		}
		else
		  ret = 'y';

		if(ret == 'y'){
		    q_status_message4(0, 2, 3,
			"Closing \"%s\". Keeping %s message%s and deleting %s",
			pretty_fn(folder),
			comatose((stream->nmsgs - delete_count)),
			plural(stream->nmsgs - delete_count),
			long2string(delete_count));
		    display_message('q'); /* So they see the message */
		    mail_expunge(stream);
		}
	    }

	    if(ret != 'y'){
		if(stream->nmsgs)
		  q_status_message4(0, 2, 3,
				"Closing folder \"%s\". Keeping%s%s message%s",
				    pretty_fn(folder), 
				    (stream->nmsgs > 1L) ? " all " : "",
				    (stream->nmsgs > 1L)
				      ? comatose(stream->nmsgs) : "",
				    plural(stream->nmsgs));
		else
		  q_status_message1(0, 2, 3, "Closing empty folder \"%s\"",
				    pretty_fn(folder));

                display_message('q'); /* So they see the message */	
	    }
        }
	else{
            if(IS_NEWS(stream))
	      q_status_message1(0, 0, 3, "Closing news group \"%s\"",
			       pretty_fn(folder));
            else
              q_status_message1(0, 0, 3,
                       "Closing read-only folder \"%s\". No changes to save",
                              pretty_fn(folder));
        }

	/*
	 * Make darn sure any mm_log fallout caused above get's seen...
	 */
	flush_status_messages();
    }
    mail_close(stream);
}



/*----------------------------------------------------------------------
      Search the message headers as display in index
 
  Args: command_line -- The screen line to prompt on
        msg          -- The current message number to start searching at
        max_msg      -- The largest message number in the current folder

  The headers are searched exactly as they are displayed on the screen. The
search will wrap around to the beginning if not string is not found right 
away.
  ----*/
void
search_headers(stream, command_line, msgmap)
     MAILSTREAM *stream;
     int         command_line;
     MSGNO_S    *msgmap;
{
    int         rc, select_all = 0;
    long        i, sorted_msg, selected = 0L;
    char        prompt[MAX_SEARCH+50], new_string[MAX_SEARCH+1];
    HelpType	help;
    static char search_string[MAX_SEARCH+1] = { '\0' };
    static ESCKEY_S header_search_key[] = { {0, 0, NULL, NULL },
					    {ctrl('Y'), 10, "^Y", "First Msg"},
					    {ctrl('V'), 11, "^V", "Last Msg"},
					    {-1, 0, NULL, NULL} };

    dprint(4, (debugfile, "\n - search headers - \n"));

    if(mn_get_total(msgmap) < 1L){
	q_status_message(0, 0, 2, "\007No messages to search");
	return;
    }
    else if(mn_total_cur(msgmap) > 1L){
	q_status_message1(0, 0, 2, "%s msgs selected; Can't search",
			  comatose(mn_total_cur(msgmap)));
	return;
    }
    else
      sorted_msg = mn_get_cur(msgmap);

    help = NO_HELP;
    new_string[0] = '\0';

    while(1) {
	sprintf(prompt, "Word to search for [%s] : ", search_string);

	if(F_ON(F_ENABLE_AGG_OPS, ps_global)){
	    header_search_key[0].ch    = ctrl('X');
	    header_search_key[0].rval  = 12;
	    header_search_key[0].name  = "^X";
	    header_search_key[0].label = "Select Matches";
	}
	else{
	    header_search_key[0].ch   = header_search_key[0].rval  = 0;
	    header_search_key[0].name = header_search_key[0].label = NULL;
	}
	
        rc = optionally_enter(new_string, command_line, 0, MAX_SEARCH, 1,
			      0, prompt, header_search_key, help, 0);

        if(rc == 3) {
	    help = (help != NO_HELP) ? NO_HELP :
		     F_ON(F_ENABLE_AGG_OPS, ps_global) ? h_os_index_whereis_agg
		       : h_os_index_whereis;
            continue;
        }
	else if(rc == 10){
	    q_status_message(0, 1, 3, "Searched to First Message.");
	    if(any_lflagged(msgmap, MN_HIDE)){
		do{
		    selected = sorted_msg;
		    mn_dec_cur(stream, msgmap);
		    sorted_msg = mn_get_cur(msgmap);
		}
		while(selected != sorted_msg);
	    }
	    else
	      sorted_msg = (mn_get_total(msgmap) > 0L) ? 1L : 0L;

	    mn_set_cur(msgmap, sorted_msg);
	    return;
	}
	else if(rc == 11){
	    q_status_message(0, 1, 3, "Searched to Last Message.");
	    if(any_lflagged(msgmap, MN_HIDE)){
		do{
		    selected = sorted_msg;
		    mn_inc_cur(stream, msgmap);
		    sorted_msg = mn_get_cur(msgmap);
		}
		while(selected != sorted_msg);
	    }
	    else
	      sorted_msg = mn_get_total(msgmap);

	    mn_set_cur(msgmap, sorted_msg);
	    return;
	}
	else if(rc == 12){
	    select_all = 1;
	    break;
	}

        if(rc != 4)			/* redraw */
          break; /* redraw */
    }

    if(rc == 1 || (new_string[0] == '\0' && search_string[0] == '\0')) {
        q_status_message(0, 0, 2, "Search cancelled");
        return;
    }

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

    strcpy(search_string, new_string);

    for(i = sorted_msg + ((select_all)?0:1); i <= mn_get_total(msgmap); i++) {
        if(!get_lflag(stream, msgmap, i, MN_HIDE) &&
	   srchstr(build_header_line(stream, msgmap, i)->line, search_string)){
	    if(select_all){
		set_lflag(stream, msgmap, i, MN_SLCT, 1);
		selected++;
	    }
	    else
	      goto found;
	}
    }

    for(i = 1; i < sorted_msg; i++) {
        if(!get_lflag(stream, msgmap, i, MN_HIDE) &&
	   srchstr(build_header_line(stream, msgmap, i)->line, search_string)){
	    if(select_all){
		set_lflag(stream, msgmap, i, MN_SLCT, 1);
		selected++;
	    }
	    else
	      goto found;
	}
    }

    if(!select_all){
	q_status_message(0, 1, 3, "\007Word not found");
	return;
  found:
	q_status_message1(0, 1, 3, "Word found%s", i > sorted_msg ? "" :
			  ". Search wrapped to beginning"); 
	mn_set_cur(msgmap, i);
    }
    else{
	q_status_message1(0, 1, 3, "%s messages found matching word",
			  long2string(selected));
    }
}



/*----------------------------------------------------------------------
    Print current message[s]



 Filters the original header and sends stuff to printer
  ---*/
void
cmd_print(state, msgmap, agg)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  agg;
{
    char      prompt[30];
    long      i;
    int	      opened;
    ENVELOPE *e;
    BODY     *b;

    if(agg && !pseudo_selected(msgmap))
      return;

    if((i = mn_total_cur(msgmap)) > 1L)
      sprintf(prompt, "%s messages ", long2string(i));
    else
      sprintf(prompt, "message %s ", long2string(mn_get_cur(msgmap)));

    if(opened = (open_printer(prompt) == 0))
      for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap))
	if(!(e=mail_fetchstructure(state->mail_stream,mn_m2raw(msgmap,i),&b))
	   || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)), e, b,
			      (FM_NEW_MESS|FM_DO_PRINT), print_char)){
	    q_status_message(0, 1, 3, "\007Error printing message");
	    break;
	}

    if(opened)
      close_printer();

    if(agg)
      restore_selected(msgmap);
}



/*----------------------------------------------------------------------
    Pipe message number "m"

   Args: state -- various pine state bits
	 msmap -- Message number mapping table to pipe.

   Filters the original header and sends stuff to specified command
  ---*/
void
cmd_pipe(state, msgmap, agg)
     struct pine *state;
     MSGNO_S *msgmap;
     int	  agg;
{
    ENVELOPE      *e;
    BODY	  *b;
    PIPE_S	  *syspipe;
    char          *resultfilename = NULL ;
    int            done = 0;
    long           i;
    HelpType       help;

    if(ps_global->restricted){
	q_status_message(0,2,2 ,"\007Pine demo can't pipe messages");
	return;
    }

    if(agg && !pseudo_selected(msgmap))
      return;

    help = NO_HELP;
    while (!done) {
	switch(optionally_enter(ps_global->pipe_command, -3, 0, MAXPATH, 1, 0,
				"Pipe message to : ", NULL, help, 0)){
	  case -1 :
	    q_status_message(0,2,2 ,"\007Internal problem encountered");
	    done++;
	    break;
      
	  case 0 :
	    if(ps_global->pipe_command[0] == '\0'){
	        q_status_message(0, 1, 2, "Pipe command cancelled");
	    } else if(syspipe = open_system_pipe(ps_global->pipe_command,
					  &resultfilename, PIPE_USER)){
		gf_io_t pc;		/* wire up a generic putchar */

		gf_set_writec(&pc, syspipe->ifile, 0L, FileStar);
		for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
		    e = mail_fetchstructure(ps_global->mail_stream,
					    mn_m2raw(msgmap, i), &b);
		    if(!format_message(mn_m2raw(msgmap, i), e, b,
				       (FM_NEW_MESS), pc)){
			q_status_message(0, 1, 3, "\007Error piping message");
			done++;
			break;
		    }
		}

		close_system_pipe(&syspipe, PIPE_USER);
		if(!done)		/* only display if no error */
		  display_system_pipe_output(resultfilename, "PIPE MESSAGE");

		fs_give((void **)&resultfilename);
	    } else {
		q_status_message(0,2,2, "\007Error opening pipe") ;
	    }
	    done++;
	    break;   

	  case 1 :
	    q_status_message(0,2,2 ,"Pipe cancelled");
	    done++;
	    break;

	  case 3 :
            help = (help == NO_HELP) ? h_pipe_msg : NO_HELP;
	    break;

	  case 2 :                              /* no place to escape to */
	  case 4 :                              /* can't suspend */
	  default :
	    break;   
	}
    }

    ps_global->mangled_footer = 1;
    if(agg)
      restore_selected(msgmap);
}




/*----------------------------------------------------------------------
 Prompt the user for the type of sort desired

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
void
aggregate_select(state, msgmap, q_line)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  q_line;
{
    long       i, diff, old_tot, old_msg;
    int        q = 0, rv = 0, hidden;
    HelpType   help = NO_HELP;
    MSGNO_S   *msgs = NULL;
    ESCKEY_S  *sel_opts;
    extern     MAILSTREAM *mm_search_stream;
    extern     MSGNO_S *mm_search_map, *mm_exclude_map;
    extern     int mm_search_count;

    hidden           = any_lflagged(msgmap, MN_HIDE) > 0L;
    mm_search_stream = state->mail_stream;
    mm_search_count  = 0;
    mm_exclude_map   = NULL;
    mm_search_map    = msgmap;
    old_msg          = mn_get_cur(msgmap);

    /*
     * Fake the selected array.  See below for why the heck we'd want
     * (or need) to do this...
     */
    for(diff = 0L, i = 1L; i <= mn_get_total(msgmap); i++){
	if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
	    if(!diff++){
		mn_set_cur(msgmap, i);
	    }
	    else{
		mn_add_cur(msgmap, i);
	    }
	}
    }

    sel_opts = sel_opts2;
    if(old_tot = any_lflagged(msgmap, MN_SLCT)){
	sel_opts1[1].label = (get_lflag(state->mail_stream,
					msgmap, old_msg, MN_SLCT))
				? "unselect Cur" : "select Cur";
	sel_opts += 2;			/* disable extra options */
	switch(q = radio_buttons(sel_pmt1,q_line,0,sel_opts1,'c','x',0,help,0)){
	  case 'n' :			/* narrow selection */
	    mm_exclude_map = msgmap;
	    mn_init(&msgs, 0L);		/* note: msgs only used for its... */
	    mm_search_map  = msgs;	/* select array */

	  case 'b' :			/* broaden selection */
	    q = 0;
	    break;

	  case 'c' :			/* Un/Select Current */
	  case 'a' :			/* Unselect All */
	  case 'x' :			/* cancel */
	  default :
	    break;
	}
    }
    else{
	mn_init(&msgs, 0L);		/* note: msgs only used for its... */
	mm_search_map  = msgs;		/* select array */
    }

    if(!q)
      q = radio_buttons(sel_pmt2, q_line, 0, sel_opts, 'c', 'x', 0, help, 0);

    switch(q){
      case 'x':				/* cancel */
	q_status_message(0, 0, 3, "Select command cancelled");
	mn_reset_cur(msgmap, old_msg);
	if(msgs && msgs != msgmap)
	  mn_give(&msgs);

	return;

      case 'c' :			/* select/unselect current */
	mn_reset_cur(msgmap, old_msg);
	if(msgs && msgs != msgmap)
	  mn_give(&msgs);

	i = mn_get_cur(msgmap);
	if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){ /* set? unset */
	    set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
	    if(hidden){
		set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
		/*
		 * See if there's anything left to zoom on.  If so, 
		 * pick an adjacent one for highlighting, else make
		 * sure nothing is left hidden...
		 */
		if(any_lflagged(msgmap, MN_SLCT)){
		    mn_inc_cur(state->mail_stream, msgmap);
		    if(mn_get_cur(msgmap) == old_msg)
		      mn_dec_cur(state->mail_stream, msgmap);
		}
		else{			/* clear all hidden flags */
		    for(i = 1L; i <= mn_get_total(msgmap); i++)
		      set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
		}
	    }
	}
	else
	  set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);

	return;

      case 'a' :			/* select/unselect all */
	mn_reset_cur(msgmap, old_msg);
	if(msgs && msgs != msgmap)
	  mn_give(&msgs);

	old_msg = any_lflagged(msgmap, MN_SLCT);
	diff    = (!old_msg) ? mn_get_total(msgmap) : 0L;

	for(i = 1L; i <= mn_get_total(msgmap); i++){
	    if(old_msg){		/* unmark 'em all */
		if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
		    diff++;
		    set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
		}
		else if(hidden)
		  set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
	    }
	    else			/* mark 'em all */
	      set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
	}

	q_status_message4(0,0,2,"%s%ld message%s %sselected",
			  old_msg ? "" : "All ", (void *) diff, 
			  plural(diff), old_msg ? "UN" : "");
	return;

      case 'n' :			/* Select by Number */
	rv = select_number(state->mail_stream, msgmap, old_msg);
	break;

      case 'd' :			/* Select by Date */
	rv = select_date(state->mail_stream, msgmap, old_msg);
	break;

      case 't' :			/* Text */
	rv = select_text(state->mail_stream, msgmap, old_msg);
	break;

      case 's' :			/* Status */
	rv = select_flagged(state->mail_stream, msgmap, old_msg);
	break;

      default :				/* no op */
	break;
    }

    /*
     * On the surface, you'd think building the selected array 
     * above and taking it apart down here is kind of dumb.  But it snot!
     * Since the flag indicating selectedness is in an unused elt bit,
     * we can't set/unset those bits in the mm_searched callback with
     * set_lflag (which in turn calls) mail_elt.  The ol' c-client isn't
     * reentrant.  So, we build the array of what's to be selected above,
     * and then set the appropriate flags here...
     */
    if(!msgs)			/* replace global map with new one */
      msgs = msgmap;

    if(!rv){
	if(mm_search_count && mm_exclude_map && mm_exclude_map != msgs){
	    for(i = mn_first_cur(mm_exclude_map); i > 0L;
		i = mn_next_cur(mm_exclude_map)){
		/*
		 * if the message number is current in the "msgs" set,
		 * then it's still selected so don't unflag it...
		 */
		if(!mn_is_cur(msgs, i)){
		    set_lflag(state->mail_stream,mm_exclude_map,i,MN_SLCT,0);

		    /*
		     * if others hidden, then flag the one we just unselected
		     * as hidden.  If we hid the current message, pick
		     * a new one...
		     */
		    if(hidden){
			set_lflag(state->mail_stream, mm_exclude_map, i, 
				 MN_HIDE, 1);
			if(i == old_msg){
			    mn_inc_cur(state->mail_stream, msgmap);
			    if(mn_get_cur(msgmap) == old_msg)
			      mn_dec_cur(state->mail_stream, msgmap);

			    old_msg = mn_get_cur(msgmap);
			}
		    }
		}
	    }
	}

	for(i = mn_first_cur(msgs); i > 0L; i = mn_next_cur(msgs)){
	    if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
		set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
		if(hidden)
		  set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
	    }
	}
    }

    if(msgs && msgs != msgmap)
      mn_give(&msgs);

    mn_reset_cur(msgmap, old_msg);

    if(rv)				/* bad return value.. */
      return;				/* error already displayed */

    if(!mm_search_count)
      q_status_message1(0, 1, 2, "\007Select failed!  No %smessages selected.",
			any_lflagged(msgmap, MN_SLCT) ? "additional " : "");
    else if(diff = (any_lflagged(msgmap, MN_SLCT) - old_tot))
      q_status_message4(0, 1, 2,
		     "Select matched %ld messages! %ld %s messages %sselected",
			(void *)mm_search_count,
			(void *)((diff > 0) ? diff : -diff),
			(diff > 0) ? "new" : "old",
			(diff > 0) ? "" : "de-");
    else
      q_status_message(0, 1, 2,"\007Only one message selected!");
}



/*----------------------------------------------------------------------
 Prompt the user for the command to perform on selected messages

   Args: state -- pointer pine's state variables
	 msgmmap -- message collection to operate on
	 q_line -- line on display to write prompts
   Returns: with the selected messages suitably commanded
            

  ----*/
void
apply_command(state, msgmap, q_line)
     struct pine *state;
     MSGNO_S     *msgmap;
     int	  q_line;
{
    int i = 8;
    HelpType   help = NO_HELP;

    if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
	sel_opts3[i].ch      = '*';
	sel_opts3[i].rval    = '*';
	sel_opts3[i].name    = "*";
	sel_opts3[i++].label = "Flag";
    }

    if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
	sel_opts3[i].ch      = '|';
	sel_opts3[i].rval    = '|';
	sel_opts3[i].name    = "|";
	sel_opts3[i++].label = "Pipe";
    }

    sel_opts3[i].ch = -1;
    switch(radio_buttons(sel_pmt3,q_line,0,sel_opts3,0,'x',0,help,0)){
      case 'd' :			/* delete */
	cmd_delete(state, msgmap, 1);
	break;

      case 'u' :			/* undelete */
	cmd_undelete(state, msgmap, 1);
	break;

      case 'r' :			/* reply */
	cmd_reply(state, msgmap, 1);
	break;

      case 'f' :			/* Forward */
	cmd_forward(state, msgmap, 1);
	break;

      case 'y' :			/* prYnt */
	cmd_print(state, msgmap, 1);
	break;

      case 't' :			/* take address */
	cmd_take_addr(state, msgmap, 1);
	break;

      case 's' :			/* save */
	cmd_save(state, msgmap, q_line, 1);
	break;

      case 'e' :			/* export */
	cmd_export(state, msgmap, q_line, 1);
	break;

      case '|' :			/* pipe */
	cmd_pipe(state, msgmap, 1);
	break;

      case '*' :			/* flag */
	cmd_flag(state, msgmap, 1);
	break;
#ifdef	LATER
      case 'b' :			/* bounce */
	cmd_bounce(state, msgmap, 1);
	break;
#endif
      case 'x' :			/* cancel */
	q_status_message(0,1,2,"Apply command cancelled");
	break;

	default:
	break;
    }
}



/*----------------------------------------------------------------------
 Prompt the user for the type of sort he desires

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
int
select_number(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S    *msgmap;
     long	 msgno;
{
    int r;
    long n1, n2;
    char number1[16], number2[16], numbers[80], *p, *t;
    HelpType help;

    numbers[0] = '\0';
    ps_global->mangled_footer = 1;
    help = NO_HELP;
    while(1){
        r = optionally_enter(numbers, -3, 0, 79, 1, 0,
                             select_num, NULL, help, 0);
        if(r == 4)
	  continue;

        if(r == 3){
            help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
	    continue;
	}

	for(t = p = numbers; *p ; p++)	/* strip whitespace */
	  if(!isspace(*p))
	    *t++ = *p;

	*t = '\0';

        if(r == 1 || numbers[0] == '\0'){
	    q_status_message(0,0,2,"Selection by number cancelled");
	    return(1);
        }
	else
	  break;
    }

    for(p = numbers; *p ; p++){
	t = number1;
	while(*p && isdigit(*p))
	  *t++ = *p++;

	*t = '\0';
	if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
	    q_status_message1(0,1,2,"\007%ld out of message number range",
			      (void *)n2);
	    return(1);
	}

	t = number2;
	if(*p && *p == '-'){
	    while(*++p && isdigit(*p))
	      *t++ = *p;

	    *t = '\0';
	    if((n2 = atol(number2)) < 1L 
	       || n2 > mn_get_total(msgmap)){
		q_status_message1(0,1,2,"\007%ld out of message number range",
				  (void *)n2);
		return(1);
	    }

	    if(n2 <= n1){
		q_status_message(0,1,2,"\007Invalid message number range");
		break;
	    }

	    for(;n1 <= n2; n1++)
	      mm_searched(stream, mn_m2raw(msgmap, n1));
	}
	else
	  mm_searched(stream, mn_m2raw(msgmap, n1));

	if(*p == '\0')
	  break;
    }
    
    return(0);
}



/*----------------------------------------------------------------------
 Prompt the user for the type of sort he desires

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
int
select_date(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S    *msgmap;
     long	 msgno;
{
    int r;
    char *drange, date[32], prompt[128];

    date[0] = '\0';
    r = radio_buttons(sel_date, -3, 0, sel_date_opt, 'o', 'x', 0, NO_HELP, 0);

    if(r != 'x'){
	ESCKEY_S  ekey[4];

	ekey[0].ch    = ekey[0].rval  = 0;
	ekey[0].name  = ekey[0].label = NULL;
	ekey[1].ch    = ctrl('T');
	ekey[1].name  = "^T";
	ekey[1].rval  = 10;
	ekey[1].label = "Today";
	ekey[2].ch    = ctrl('X');
	ekey[2].name  = "^X";
	ekey[2].rval  = 11;
	ekey[2].label = "Cur Msg";
	ekey[3].ch = -1;

	ps_global->mangled_footer = 1;
	drange = (r == 'b') ? "BEFORE" : (r == 's') ? "SINCE" : "ON";

	while(1){
	    sprintf(prompt,"Select messages dated %s (eg: 10-DEC-93): ",
		    drange);
	    r = optionally_enter(date,-3,0,31,1,0,prompt,ekey,NO_HELP,0);
	    if(r == 3 || r == 4)
	      continue;
	    else if(r == 10){
		long   now;
		struct tm *now_tm;

		now    = time(0);
		now_tm = localtime(&now);
		sprintf(date, "%d-%s-%d", now_tm->tm_mday,
			month_abbrev(now_tm->tm_mon + 1),
			now_tm->tm_year + 1900);
		continue;
	    }
	    else if(r == 11){
		MESSAGECACHE *mc;

		if(mc = mail_elt(stream, mn_m2raw(msgmap, msgno))){
		    /* mail_date returns fixed field width date */
		    *(mail_date(tmp_20k_buf, mc) + 11) = '\0';
		    strcpy(date, tmp_20k_buf);
		}

		continue;
	    }

	    removing_leading_white_space(date);
	    removing_trailing_white_space(date);
	    if(r == 1 || date[0] == '\0')
	      r = 'x';

	    break;
	}
    }

    if(r == 'x'){
	q_status_message(0,0,2,"Selection by date cancelled");
	return(1);
    }

    sprintf(prompt, "%s %s", drange, date);
    mail_search(stream, prompt);
    return(0);
}



/*----------------------------------------------------------------------
 Prompt the user for the type of sort he desires

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
int
select_text(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S    *msgmap;
     long	 msgno;
{
    int       r, type;
    char     *sval = NULL, sstring[80], prompt[128];
    ESCKEY_S  ekey[3];
    ENVELOPE *env = NULL;
    HelpType  help;

    ps_global->mangled_footer = 1;

    /*
     * prepare some friendly defaults...
     */
    ekey[1].ch   = -1;
    ekey[2].ch   = -1;
    switch(type=radio_buttons(sel_text,-3,0,sel_text_opt,'s','x',0,NO_HELP,0)){
      case 't' :			/* address fields, offer To or From */
      case 'f' :
      case 'c' :
	sval          = (type == 't') ? "TO" : (type == 'f') ? "FROM" : "CC";
	ekey[0].ch    = ctrl('T');
	ekey[0].name  = "^T";
	ekey[0].rval  = 10;
	ekey[0].label = "Cur To";
	ekey[1].ch    = ctrl('R');
	ekey[1].name  = "^R";
	ekey[1].rval  = 11;
	ekey[1].label = "Cur From";
	break;

      case 's' :
	sval          = "SUBJECT";
	ekey[0].ch    = ctrl('X');
	ekey[0].name  = "^X";
	ekey[0].rval  = 13;
	ekey[0].label = "Cur Subject";
	break;

      case 'a' :
	sval = "TEXT";			/* fall thru */
	ekey[0].ch = -1;
	break;

      case 'x':
	break;

      default:
	q_status_message(0,2,3,"\007Programmer botch: unrecognized option");
	return(1);
    }

    if(type != 'x'){
	if(ekey[0].ch > -1 && msgno > 0L
	   && !(env=mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),NULL)))
	  ekey[0].ch = -1;

	sstring[0] = '\0';
	help = NO_HELP;
	r = type;
	while(r != 'x'){
	    sprintf(prompt, "String in message %s to match : ", sval);
	    switch(r = optionally_enter(sstring, -3, 0, 79, 1, 0, prompt,
					ekey, help, 0)){
	      case 3 :
		help = (help == NO_HELP)
			    ? ((type == 'f') ? h_select_txt_from
			      : (type == 't') ? h_select_txt_to
			       : (type == 'c') ? h_select_txt_cc
				: (type == 's') ? h_select_txt_subj
				 : (type == 'a') ? h_select_txt_all
				  :              NO_HELP)
			    : NO_HELP;
	      case 4 :
		continue;

	      case 10 :			/* To: default */
		if(env && env->to && env->to->mailbox)
		  sprintf(sstring, "%.30s%s%.40s", env->to->mailbox,
			  env->to->host ? "@" : "",
			  env->to->host ? env->to->host : "");
		continue;

	      case 11 :			/* From: default */
		if(env && env->from && env->from->mailbox)
		  sprintf(sstring, "%.30s%s%.40s", env->from->mailbox,
			  env->from->host ? "@" : "",
			  env->from->host ? env->from->host : "");
		continue;

	      case 12 :			/* Cc: default */
		if(env && env->cc && env->cc->mailbox)
		  sprintf(sstring, "%.30s%s%.40s", env->cc->mailbox,
			  env->cc->host ? "@" : "",
			  env->cc->host ? env->cc->host : "");
	    continue;

	      case 13 :			/* Subject: default */
		if(env && env->subject && env->subject[0])
		  sprintf(sstring, "%.70s", env->subject);

		continue;

	      default :
		break;
	    }

	    if(r == 1 || sstring[0] == '\0')
	      r = 'x';

	    break;
	}
    }

    if(type == 'x' || r == 'x'){
	q_status_message(0,0,2,"Selection by text match cancelled");
	return(1);
    }

    sprintf(prompt, "%s ", sval);
    sval = prompt + strlen(prompt);		/* sval overloaded! */
    if(strpbrk(sstring, "\012\015\"%{\\"))
      sprintf(sval, "{%d}\015\012%s", strlen(sstring), sstring);
    else if(strchr(sstring, ' '))
      sprintf(sval, "\"%s\"", sstring);
    else
      strcpy(sval, sstring);

    mail_search(stream, prompt);
    return(0);
}



/*----------------------------------------------------------------------
 Prompt the user for the type of sort he desires

   Args: none
   Returns: 0 if search OK (matching numbers selected by side effect)
            1 if there's a problem

  ----*/
int
select_flagged(stream, msgmap, msgno)
     MAILSTREAM *stream;
     MSGNO_S    *msgmap;
     long	 msgno;
{
    int   s, not = 0;
    char *flagp;

    while(1){
	s = radio_buttons((not) ? sel_flag_not : sel_flag,
			  -3, 0, sel_flag_opt, 'i', 'x', 0, NO_HELP, 0);
	if(s == 'x'){
	    q_status_message(0, 0, 2, "Selection by status cancelled");
	    return(1);
	}
	else if(s == '!')
	  not = !not;
	else
	  break;
    }

    flagp = cpystr((s == 'n') ? (not) ? "SEEN UNDELETED"
				      : "UNSEEN UNDELETED UNANSWERED" :
		      (s == 'd') ? (not) ? "UNDELETED"
					 : "DELETED" :
			 (s == 'a') ? (not) ? "SEEN UNANSWERED UNDELETED" 
					    : "ANSWERED UNDELETED" :
			    (not) ? "UNFLAGGED" : "FLAGGED");
    mail_search(stream, flagp);
    fs_give((void **)&flagp);
    return(0);
}



/*----------------------------------------------------------------------
   Prompt the user for the type of sort he desires

Args: state -- pine state pointer
      q1 -- Line to prompt on

  ----*/
void
select_sort(state, ql)
     struct pine *state;
     int ql;
{
    char      prompt[200], tmp[3], *p;
    int       s, i;
    int       deefault = 'a';
    HelpType  help;
    ESCKEY_S  sorts[10];

    /*----- String together the prompt ------*/
    tmp[1] = '\0';
    strcpy(prompt, "Choose type of sort, or Reverse current sort : ");
    for(i = 0; state->sort_types[i] != EndofList && i < 8; i++) {
	sorts[i].rval	   = i;
	p = sorts[i].label = sort_name(state->sort_types[i]);
	while(*(p+1) && islower(*p))
	  p++;

	sorts[i].ch   = tolower(tmp[0] = *p);
	sorts[i].name = cpystr(tmp);

        if(mn_get_sort(state->msgmap) == state->sort_types[i])
	  deefault = sorts[i].rval;
    }

    sorts[i].ch     = 'r';
    sorts[i].rval   = 'r';
    sorts[i].name   = cpystr("R");
    sorts[i].label  = "Reverse";
    sorts[++i].ch   = -1;
    help = h_select_sort;

    if((s = radio_buttons(prompt,ql,0,sorts,deefault,'x',0,help,0)) != 'x'){
	state->mangled_body = 1;		/* signal screen's changed */
	if(s == 'r'){
	    mn_set_revsort(state->msgmap, !mn_get_revsort(state->msgmap));
	}
	else
	  mn_set_sort(state->msgmap, state->sort_types[s]);
    }
    else
      q_status_message(0, 0, 3, "Sort cancelled");

    while(--i >= 0)
      fs_give((void **)&sorts[i].name);

    blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
    q_status_message2(0,0,3,"%s%s sort complete.",
		      mn_get_revsort(state->msgmap) ? "Reverse " : "",
		      sort_name(mn_get_sort(state->msgmap)));
}




/*----------------------------------------------------------------------
  Build comma delimited list of currently selected messages

  Args: stream -- mail stream to use for flag testing
	msgmap -- message number struct of to build selected messages in
	count -- pointer to place to write number of comma delimited

  Returns: malloc'd string containing sequence, else NULL if
	   no messages in msgmap with local "selected" flag.
  ----*/
char *
build_selected_sequence(stream, msgmap, count)
    MAILSTREAM *stream;
    MSGNO_S    *msgmap;
    long       *count;
{
    long  i;

    if(!any_lflagged(msgmap, MN_SLCT) || !stream)
      return(NULL);

    /*
     * The plan here is to use the c-client elt's "sequence" bit
     * to work around any orderings or exclusions in pine's internal
     * mapping that might cause the sequence to be artificially
     * lengthy.  It's probably cheaper to run down the elt list
     * twice rather than call nm_raw2m() for each message as
     * we run down the elt list once...
     */
    for(i = 1L; i <= stream->nmsgs; i++)
      mail_elt(stream, i)->sequence = 0;

    for(i = 1L; i <= mn_get_total(msgmap); i++)
      if(get_lflag(stream, msgmap, i, MN_SLCT)){
	  /*
	   * Forget we knew about it, and set "add to sequence"
	   * bit...
	   */
	  clear_index_cache_ent(i);
	  mail_elt(stream, mn_m2raw(msgmap, i))->sequence = 1;
      }

    return(build_sequence(stream, count));
}


/*----------------------------------------------------------------------
  Build comma delimited list of messages with elt "sequence" bit set

  Args: stream -- mail stream to use for flag testing
	count -- pointer to place to write number of comma delimited

  Returns: malloc'd string containing sequence, else NULL if
	   no messages in msgmap with elt's "sequence" bit set
  ----*/
char *
build_sequence(stream, count)
    MAILSTREAM *stream;
    long       *count;
{
#define	SEQ_INCREMENT	128
    long    i, lastn = 0L, runstart = 0L;
    size_t  size = SEQ_INCREMENT;
    char   *seq = NULL, *p, *nstring;

    *count = 0L;
    for(i = 1L; i <= stream->nmsgs; i++){
	if(mail_elt(stream, i)->sequence){
	    (*count)++;
	    if(!seq)				/* initialize if needed */
	      seq = p = fs_get(size);

	    /*
	     * This code will coalesce the ascending runs of
	     * sequence numbers, but fails to break sequences
	     * into a reasonably sensible length for imapd's to
	     * swallow (reasonable addtition to c-client?)...
	     */
	    if(lastn){				/* if may be in a run */
		if(lastn + 1L == i){		/* and its the next raw num */
		    lastn = i;			/* skip writing anything... */
		    continue;
		}
		else if(runstart != lastn){
		    *p++ = (runstart + 1L == lastn) ? ',' : ':';
		    sstrcpy(&p, long2string(lastn));
		}				/* wrote end of run */
	    }

	    runstart = lastn = i;		/* remember last raw num */

	    if(*count > 1L)			/* !first num, write delim */
	      *p++ = ',';

	    if(size - (p - seq) < 16){	/* room for two more nums? */
		size_t offset = p - seq;	/* grow the sequence array */
		size += SEQ_INCREMENT;
		fs_resize((void **)&seq, size);
		p = seq + offset;
	    }

	    sstrcpy(&p, long2string(i));	/* write raw number */
	}
    }

    if(lastn && runstart != lastn){		/* were in a run? */
	*p++ = (runstart + 1L == lastn) ? ',' : ':';
	sstrcpy(&p, long2string(lastn));	/* write the trailing num */
    }

    if(seq)					/* if sequence, tie it off */
      *p  = '\0';

    return(seq);
}



/*----------------------------------------------------------------------
  If any messages flagged "selected", fake the "currently selected" array

  Args: map -- message number struct of to build selected messages in

  OK folks, here's the tradeoff: either all the functions have to
  know if the user want's to deal with the "current" hilited message
  or the list of currently "selected" messages, *or* we just
  wrap the call to these functions with some glue that tweeks
  what these functions see as the "current" message list, and let them
  do their thing.
  ----*/
int
pseudo_selected(map)
    MSGNO_S *map;
{
    long i, later = 0L;

    if(any_lflagged(map, MN_SLCT)){
	map->hilited = mn_m2raw(map, mn_get_cur(map));

	for(i = 1L; i <= mn_get_total(map); i++)
	  /* BUG: using the global mail_stream is kind of bogus since
	   * everybody that calls us get's a pine stuct passed it.
	   * perhaps a stream pointer in the message struct makes 
	   * sense?
	   */
	  if(get_lflag(ps_global->mail_stream, map, i, MN_SLCT)){
	      if(!later++){
		  mn_set_cur(map, i);
	      }
	      else{
		  mn_add_cur(map, i);
	      }
	  }

	return(1);
    }

    return(0);
}


/*----------------------------------------------------------------------
  Antidote for the monkey business committed above

  Args: map -- message number struct of to build selected messages in

  ----*/
void
restore_selected(map)
    MSGNO_S *map;
{
    if(map->hilited){
	mn_reset_cur(map, mn_raw2m(map, map->hilited));
	map->hilited = 0L;
    }
}


/*
 * Get the user name from the mailbox portion of an address.
 *
 * Args: mailbox -- the mailbox portion of an address (lhs of address)
 *       target  -- a buffer to put the result in
 *       len     -- length of the target buffer
 *
 * Returns the left most portion up to the first '%', ':' or '@',
 * and to the right of any '!' (as if c-client would give us such a mailbox).
 * Returns NULL if it can't find a username to point to.
 */
char *
get_uname(mailbox, target, len)
    char  *mailbox,
	  *target;
    int    len;
{
    int i, start, end;

    if(!mailbox || !*mailbox)
      return(NULL);

    end = strlen(mailbox) - 1;
    for(start = end; mailbox[start] != '!' && start > -1; start--)
        if(strindex("%:@", mailbox[start]))
	    end = start - 1;

    start++;			/* compensate for either case above */

    for(i = start; i <= end && (i-start) < (len-1); i++) /* copy name */
      target[i-start] = isupper(mailbox[i]) ? tolower(mailbox[i]) : mailbox[i];

    target[i-start] = '\0';	/* tie it off */

    return(*target ? target : NULL);
}
