/*
 * ========================================================================
 * Copyright 2006-2008 University of Washington
 * Copyright 2013-2022 Eduardo Chappa
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

#include "headers.h"
#include "confscroll.h"
#include "keymenu.h"
#include "status.h"
#include "titlebar.h"
#include "help.h"
#include "radio.h"
#include "print.h"
#include "ldapconf.h"
#include "roleconf.h"
#include "colorconf.h"
#include "mailview.h"
#include "mailcmd.h"
#include "mailindx.h"
#include "talk.h"
#include "setup.h"
#include "smime.h"
#include "xoauth2conf.h"
#include "../pith/state.h"
#include "../pith/flag.h"
#include "../pith/list.h"
#include "../pith/conf.h"
#include "../pith/util.h"
#include "../pith/newmail.h"
#include "../pith/sort.h"
#include "../pith/thread.h"
#include "../pith/color.h"
#include "../pith/hist.h"
#include "../pith/icache.h"
#include "../pith/conf.h"
#include "../pith/init.h"
#include "../pith/folder.h"
#include "../pith/busy.h"
#include "../pith/tempfile.h"
#include "../pith/pattern.h"
#include "../pith/charconv/utf8.h"


#define	CONFIG_SCREEN_HELP_TITLE	_("HELP FOR SETUP CONFIGURATION")

/* TRANSLATORS: Empty Value is what is shown in the configuration
   screen when the user not only does not set an option but also
   wants to explicitly not use the default value. Empty value means
   an option with no value. */
char *empty_val  = N_("Empty Value");
char *empty_val2 = N_("<Empty Value>");
/* TRANSLATORS: No Value set is similar to Empty Value, but the
   user has not explicitly decided to not use the default. It is
   just an option which the user has left at the default value. */
char *no_val     = N_("No Value Set");
/* TRANSLATORS: Value is Fixed is what is displayed in the config
   screen when the system managers have set an option to a specific
   value and they don't allow the user to change it. The value
   is fixed to a certain value. This isn't the same word as
   Repaired, it means Unchanging. */
char *fixed_val  = N_("Value is Fixed");
char yesstr[] = "Yes";
char nostr[]  = "No";


EditWhich ew = Main;


OPT_SCREEN_S *opt_screen;


/*
 * This is pretty ugly. Some of the routines operate differently depending
 * on which variable they are operating on. Sometimes those variables are
 * global (real alpine.h V_ style variables) and sometimes they are just
 * local variables (as in role_config_edit_screen). These pointers are here
 * so that the routines can figure out which variable they are operating
 * on and do the right thing.
 */
struct variable	        *score_act_global_ptr,
			*scorei_pat_global_ptr,
			*age_pat_global_ptr,
			*size_pat_global_ptr,
			*cati_global_ptr,
			*cat_cmd_global_ptr,
			*cat_lim_global_ptr,
			*startup_ptr,
			*role_comment_ptr,
			*role_forw_ptr,
			*role_repl_ptr,
			*role_fldr_ptr,
			*role_afrom_ptr,
			*role_filt_ptr,
			*role_status1_ptr,
			*role_status2_ptr,
			*role_status3_ptr,
			*role_status4_ptr,
			*role_status5_ptr,
			*role_status6_ptr,
			*role_status7_ptr,
			*role_status8_ptr,
			*msg_state1_ptr,
			*msg_state2_ptr,
			*msg_state3_ptr,
			*msg_state4_ptr;


typedef NAMEVAL_S *(*PTR_TO_RULEFUNC)(int);


/*
 * Internal prototypes
 */
PTR_TO_RULEFUNC rulefunc_from_var(struct pine *, struct variable *);
void     set_radio_pretty_vals(struct pine *, CONF_S **);
int      save_include(struct pine *, struct variable *, int);
void	 config_scroll_up(long);
void	 config_scroll_down(long);
void	 config_scroll_to_pos(long);
CONF_S  *config_top_scroll(struct pine *, CONF_S *);
void	 update_option_screen(struct pine *, OPT_SCREEN_S *, Pos *);
void	 print_option_screen(OPT_SCREEN_S *, char *);
void	 option_screen_redrawer(void);
char    *text_pretty_value(struct pine *, CONF_S *);
char    *checkbox_pretty_value(struct pine *, CONF_S *);
char    *yesno_pretty_value(struct pine *, CONF_S *);
char    *radio_pretty_value(struct pine *, CONF_S *);
char    *sigfile_pretty_value(struct pine *, CONF_S *);
char    *color_pretty_value(struct pine *, CONF_S *);
char    *sort_pretty_value(struct pine *, CONF_S *);
int      longest_feature_name(void);
COLOR_PAIR *sample_color(struct pine *, struct variable *);
COLOR_PAIR *sampleexc_color(struct pine *, struct variable *);
void     clear_feature(char ***, char *);
CONF_S	*last_confline(CONF_S *);
#ifdef	_WINDOWS
int	 config_scroll_callback(int, long);
#endif


/*
 * We test for this same set of vars in a few places.
 */
int
standard_radio_var(struct pine *ps, struct variable *v)
{
    return(v == &ps->vars[V_SAVED_MSG_NAME_RULE] ||
	   v == &ps->vars[V_FCC_RULE] ||
	   v == &ps->vars[V_GOTO_DEFAULT_RULE] ||
	   v == &ps->vars[V_INCOMING_STARTUP] ||
	   v == &ps->vars[V_PRUNING_RULE] ||
	   v == &ps->vars[V_REOPEN_RULE] ||
	   v == &ps->vars[V_THREAD_DISP_STYLE] ||
	   v == &ps->vars[V_THREAD_INDEX_STYLE] ||
	   v == &ps->vars[V_FLD_SORT_RULE] ||
#ifndef	_WINDOWS
	   v == &ps->vars[V_COLOR_STYLE] ||
#endif
	   v == &ps->vars[V_INDEX_COLOR_STYLE] ||
	   v == &ps->vars[V_TITLEBAR_COLOR_STYLE] ||
	   v == &ps->vars[V_AB_SORT_RULE]);
}


PTR_TO_RULEFUNC
rulefunc_from_var(struct pine *ps, struct variable *v)
{
    PTR_TO_RULEFUNC rulefunc = NULL;

    if(v == &ps->vars[V_SAVED_MSG_NAME_RULE])
      rulefunc = save_msg_rules;
    else if(v == &ps->vars[V_FCC_RULE])
      rulefunc = fcc_rules;
    else if(v == &ps->vars[V_GOTO_DEFAULT_RULE])
      rulefunc = goto_rules;
    else if(v == &ps->vars[V_INCOMING_STARTUP])
      rulefunc = incoming_startup_rules;
    else if(v == startup_ptr)
      rulefunc = startup_rules;
    else if(v == &ps->vars[V_PRUNING_RULE])
      rulefunc = pruning_rules;
    else if(v == &ps->vars[V_REOPEN_RULE])
      rulefunc = reopen_rules;
    else if(v == &ps->vars[V_THREAD_DISP_STYLE])
      rulefunc = thread_disp_styles;
    else if(v == &ps->vars[V_THREAD_INDEX_STYLE])
      rulefunc = thread_index_styles;
    else if(v == &ps->vars[V_FLD_SORT_RULE])
      rulefunc = fld_sort_rules;
    else if(v == &ps->vars[V_AB_SORT_RULE])
      rulefunc = ab_sort_rules;
    else if(v == &ps->vars[V_INDEX_COLOR_STYLE])
      rulefunc = index_col_style;
    else if(v == &ps->vars[V_TITLEBAR_COLOR_STYLE])
      rulefunc = titlebar_col_style;
#ifndef	_WINDOWS
    else if(v == &ps->vars[V_COLOR_STYLE])
      rulefunc = col_style;
#endif
    
    return(rulefunc);
}


void
standard_radio_setup(struct pine *ps, CONF_S **cl, struct variable *v, CONF_S **first_line)
{
    int     i, rindent = 12;
    CONF_S *ctmpb;
    PTR_TO_RULEFUNC rulefunc;
    NAMEVAL_S *f;
    char b[100];

    if(!(cl && *cl))
      return;

    rulefunc = rulefunc_from_var(ps, v);
    ctmpb = (*cl);

    (*cl)->flags		|= CF_NOSELECT;
    (*cl)->keymenu      	 = &config_radiobutton_keymenu;
    (*cl)->tool			 = NULL;

    /* put a nice delimiter before list */
    new_confline(cl)->var	 = NULL;
    (*cl)->varnamep		 = ctmpb;
    (*cl)->keymenu		 = &config_radiobutton_keymenu;
    (*cl)->help			 = NO_HELP;
    (*cl)->tool			 = radiobutton_tool;
    (*cl)->valoffset		 = rindent;
    (*cl)->flags		|= CF_NOSELECT;
    /* TRANSLATORS: Set and Rule Values are the headings for an option
       that can take one of several values. Underneath the Set heading
       will be a column where one possibility is turned on (is Set).
       The other column will be very short descriptions of what
       the possibilities are (the Rule Values). */
    utf8_snprintf(b, sizeof(b), "%-5.5w  %s", _("Set"), _("Rule Values"));
    (*cl)->value		 = cpystr(b);

    new_confline(cl)->var	 = NULL;
    (*cl)->varnamep		 = ctmpb;
    (*cl)->keymenu		 = &config_radiobutton_keymenu;
    (*cl)->help			 = NO_HELP;
    (*cl)->tool			 = radiobutton_tool;
    (*cl)->valoffset		 = rindent;
    (*cl)->flags		|= CF_NOSELECT;
    (*cl)->value		 = cpystr("---  ----------------------");

    if(rulefunc)
      for(i = 0; (f = (*rulefunc)(i)); i++){
	new_confline(cl)->var	= v;
	if(first_line && !*first_line && !pico_usingcolor())
	  *first_line = (*cl);

	(*cl)->varnamep		= ctmpb;
	(*cl)->keymenu		= &config_radiobutton_keymenu;
	(*cl)->help		= (v == startup_ptr)
				    ? h_config_other_startup
				    : config_help(v - ps->vars,0);
	(*cl)->tool		= radiobutton_tool;
	(*cl)->valoffset	= rindent;
	(*cl)->varmem		= i;
	(*cl)->value		= pretty_value(ps, *cl);
      }
}


/*
 * Reset the displayed values for all of the lines for this
 * variable because others besides this line may change.
 */
void
set_radio_pretty_vals(struct pine *ps, CONF_S **cl)
{
    CONF_S *ctmp;

    if(!(cl && *cl &&
       ((*cl)->var == &ps->vars[V_SORT_KEY] ||
        standard_radio_var(ps, (*cl)->var) ||
	(*cl)->var == startup_ptr)))
      return;

    /* hunt backwards */
    for(ctmp = *cl;
	ctmp && !(ctmp->flags & CF_NOSELECT) && !ctmp->varname;
	ctmp = prev_confline(ctmp)){
	if(ctmp->value)
	  fs_give((void **)&ctmp->value);
	
	ctmp->value = pretty_value(ps, ctmp);
    }

    /* hunt forwards */
    for(ctmp = *cl;
	ctmp && !ctmp->varname && !(ctmp->flags & CF_NOSELECT);
	ctmp = next_confline(ctmp)){
	if(ctmp->value)
	  fs_give((void **)&ctmp->value);
	
	ctmp->value = pretty_value(ps, ctmp);
    }
}


/*
 * test whether or not a var is 
 *
 * returns:  1 if it should be excluded, 0 otw
 */
int
exclude_config_var(struct pine *ps, struct variable *var, int allow_hard_to_config_remotely)
{
    if((ew != Main && (var->is_onlymain)) ||
       (ew != ps_global->ew_for_except_vars && var->is_outermost))
      return(1);

    if(allow_hard_to_config_remotely)
      return(!(var->is_user && var->is_used && !var->is_obsolete));

    switch(var - ps->vars){
      case V_HTML_DIRECTORY :
      case V_MAIL_DIRECTORY :
      case V_INCOMING_FOLDERS :
      case V_FOLDER_SPEC :
      case V_NEWS_SPEC :
      case V_STANDARD_PRINTER :
      case V_LAST_TIME_PRUNE_QUESTION :
      case V_LAST_VERS_USED :
      case V_ADDRESSBOOK :
      case V_GLOB_ADDRBOOK :
      case V_DISABLE_DRIVERS :
      case V_DISABLE_AUTHS :
#ifdef DF_ENCRYPTION_RANGE
      case V_ENCRYPTION_RANGE :
#endif /* DF_ENCRYPTION_RANGE */
      case V_REMOTE_ABOOK_METADATA :
      case V_REMOTE_ABOOK_HISTORY :
      case V_REMOTE_ABOOK_VALIDITY :
      case V_OPER_DIR :
      case V_USERINPUTTIMEO :
      case V_TCPOPENTIMEO :
      case V_TCPREADWARNTIMEO :
      case V_TCPWRITEWARNTIMEO :
      case V_TCPQUERYTIMEO :
      case V_QUITQUERYTIMEO :
      case V_RSHCMD :
      case V_RSHPATH :
      case V_RSHOPENTIMEO :
      case V_SSHCMD :
      case V_SSHPATH :
      case V_SSHOPENTIMEO :
      case V_SENDMAIL_PATH :
      case V_NEW_VER_QUELL :
      case V_PATTERNS :
      case V_PAT_ROLES :
      case V_PAT_FILTS :
      case V_PAT_SCORES :
      case V_PAT_INCOLS :
      case V_PAT_OTHER :
      case V_PAT_SRCH :
      case V_PRINTER :
      case V_PERSONAL_PRINT_COMMAND :
      case V_PERSONAL_PRINT_CATEGORY :
      case V_RSS_NEWS :
      case V_RSS_WEATHER :
      case V_WP_INDEXHEIGHT :
      case V_WP_INDEXLINES :
      case V_WP_AGGSTATE :
      case V_WP_STATE :
      case V_WP_COLUMNS :
#ifndef	_WINDOWS
      case V_OLD_CHAR_SET :
#endif	/* ! _WINDOWS */
#if defined(DOS) || defined(OS2)
      case V_UPLOAD_CMD :
      case V_UPLOAD_CMD_PREFIX :
      case V_DOWNLOAD_CMD :
      case V_DOWNLOAD_CMD_PREFIX :
#ifdef	_WINDOWS
      case V_FONT_NAME :
      case V_FONT_SIZE :
      case V_FONT_STYLE :
      case V_FONT_CHAR_SET :
      case V_PRINT_FONT_NAME :
      case V_PRINT_FONT_SIZE :
      case V_PRINT_FONT_STYLE :
      case V_PRINT_FONT_CHAR_SET :
      case V_WINDOW_POSITION :
      case V_CURSOR_STYLE :
#endif	/* _WINDOWS */
#endif	/* DOS */
#ifdef	ENABLE_LDAP
      case V_LDAP_SERVERS :
#endif	/* ENABLE_LDAP */
	return(1);

      default:
	break;
    }

    return(!(var->is_user && var->is_used && !var->is_obsolete &&
#ifdef	SMIME
	     !smime_related_var(ps, var) &&
#endif	/* SMIME */
	     !color_related_var(ps, var)));
}


/*
 * Test to indicate what should be saved in case user wants to abandon
 * changes.
 */
int
save_include(struct pine *ps, struct variable *v, int allow_hard_to_config_remotely)
{
    return(!exclude_config_var(ps, v, allow_hard_to_config_remotely)
	   || (v->is_user
	    && v->is_used
	    && !v->is_obsolete
	    && (v == &ps->vars[V_PERSONAL_PRINT_COMMAND]
#ifdef	ENABLE_LDAP
	     || v == &ps->vars[V_LDAP_SERVERS]
#endif
		)));
}


/*
 * Handles screen painting and motion.  Passes other commands to
 * custom tools.
 *
 * Tool return values:  Tools should return the following:
 *     0 nothing changed
 *    -1 unrecognized command
 *     1 something changed, conf_scroll_screen should remember that
 *     2 tells conf_scroll_screen to return with value 1 or 0 depending
 *       on whether or not it has previously gotten a 1 from some tool.
 *     3 tells conf_scroll_screen to return 1 (like 1 and 2 combined)
 *     ? Other tool-specific values can be used.  They will cause
 *       conf_scroll_screen to return that value.
 *
 * Return values:
 *     0 if nothing happened.  That is, a tool returned 2 and we hadn't
 *       previously noted a return of 1
 *     1 if something happened.  That is, a tool returned 2 and we had
 *       previously noted a return of 1
 *     ? Tool-returned value different from -1, 0, 1, 2, or 3. This is it.
 *
 * Special proviso: If first_line->flags has CF_CHANGES set on entry, then
 * that will cause things to behave like a change was made since entering
 * this function.
 */
int
conf_scroll_screen(struct pine *ps, OPT_SCREEN_S *screen, CONF_S *start_line, char *title, char *pdesc, int multicol, int *pos)
{
    char	  tmp[MAXPATH+1];
    char         *utf8str;
    UCS           ch = 'x';
    int		  cmd, i, j, done = 0, changes = 0;
    int		  retval = 0;
    int		  km_popped = 0, stay_in_col = 0;
    struct	  key_menu  *km = NULL;
    CONF_S	 *ctmpa = NULL, *ctmpb = NULL;
    Pos           cursor_pos;
    OtherMenu	  what_keymenu = FirstMenu;
    void        (*prev_redrawer)(void);

    dprint((7, "conf_scroll_screen()\n"));

    if(BODY_LINES(ps) < 1){
	q_status_message(SM_ORDER | SM_DING, 3, 3, _("Screen too small"));
	return(0);
    }

    if(screen && screen->ro_warning)
      q_status_message1(SM_ORDER, 1, 3,
			/* TRANSLATORS: "Config file not changeable," is what replaces the %s */
			_("%s can't change options or settings"),
			ps_global->restricted ? "Alpine demo"
					      : _("Config file not changeable,"));

    screen->current    = start_line;
    if(start_line && start_line->flags & CF_CHANGES)
      changes++;

    opt_screen	       = screen;
    ps->mangled_screen = 1;
    ps->redrawer       = option_screen_redrawer;

    while(!done){
	ps->user_says_cancel = 0;
	if(km_popped){
	    km_popped--;
	    if(km_popped == 0){
		clearfooter(ps);
		ps->mangled_body = 1;
	    }
	}

	if(ps->mangled_screen){
	    ps->mangled_header = 1;
	    ps->mangled_footer = 1;
	    ps->mangled_body   = 1;
	    ps->mangled_screen = 0;
	}

	/*----------- Check for new mail -----------*/
        if(new_mail(0, NM_TIMING(ch), NM_STATUS_MSG | NM_DEFER_SORT) >= 0)
          ps->mangled_header = 1;

	if(ps->mangled_header){
	    set_titlebar(title, ps->mail_stream,
			 ps->context_current,
			 ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0, NULL);
	    ps->mangled_header = 0;
	}

	update_option_screen(ps, screen, &cursor_pos);

	if(F_OFF(F_SHOW_CURSOR, ps)){
	    cursor_pos.row = ps->ttyo->screen_rows - FOOTER_ROWS(ps);
	    cursor_pos.col = 0;
	}

	/*---- This displays new mail notification, or errors ---*/
	if(km_popped){
	    FOOTER_ROWS(ps) = 3;
	    mark_status_unknown();
	}

        display_message(ch);
	if(km_popped){
	    FOOTER_ROWS(ps) = 1;
	    mark_status_unknown();
	}

	if(ps->mangled_footer || km != screen->current->keymenu){
	    bitmap_t	 bitmap;

	    setbitmap(bitmap);

	    ps->mangled_footer = 0;
	    km                 = screen->current->keymenu;

	    if(multicol &&
	       (F_OFF(F_ARROW_NAV, ps_global) ||
	        F_ON(F_RELAXED_ARROW_NAV, ps_global))){
		menu_clear_binding(km, KEY_LEFT);
		menu_clear_binding(km, KEY_RIGHT);
		menu_clear_binding(km, KEY_UP);
		menu_clear_binding(km, KEY_DOWN);
		menu_add_binding(km, KEY_UP, MC_CHARUP);
		menu_add_binding(km, KEY_DOWN, MC_CHARDOWN);	
		menu_add_binding(km, KEY_LEFT, MC_PREVITEM);
		menu_add_binding(km, ctrl('B'), MC_PREVITEM);
		menu_add_binding(km, KEY_RIGHT, MC_NEXTITEM);
		menu_add_binding(km, ctrl('F'), MC_NEXTITEM);
	    }
	    else{
		menu_clear_binding(km, KEY_LEFT);
		menu_clear_binding(km, KEY_RIGHT);
		menu_clear_binding(km, KEY_UP);
		menu_clear_binding(km, KEY_DOWN);

		/*
		 * Fix up arrow nav mode if necessary...
		 */
		if(F_ON(F_ARROW_NAV, ps_global)){
		    int cmd;

		    if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
			menu_add_binding(km, '<', cmd);
			menu_add_binding(km, KEY_LEFT, cmd);
		    }

		    if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
			menu_add_binding(km, '>', cmd);
			menu_add_binding(km, KEY_RIGHT, cmd);
		    }

		    if((cmd = menu_clear_binding(km, 'p')) != MC_UNKNOWN){
			menu_add_binding(km, 'p', cmd);
			menu_add_binding(km, KEY_UP, cmd);
		    }

		    if((cmd = menu_clear_binding(km, 'n')) != MC_UNKNOWN){
			menu_add_binding(km, 'n', cmd);
			menu_add_binding(km, KEY_DOWN, cmd);
		    }
		}
	    }

	    if(km_popped){
		FOOTER_ROWS(ps) = 3;
		clearfooter(ps);
	    }

	    draw_keymenu(km, bitmap, ps->ttyo->screen_cols,
			 1-FOOTER_ROWS(ps), 0, what_keymenu);
	    what_keymenu = SameMenu;

	    if(km_popped){
		FOOTER_ROWS(ps) = 1;
		mark_keymenu_dirty();
	    }
	}

	MoveCursor(cursor_pos.row, cursor_pos.col);
#ifdef	MOUSE
	mouse_in_content(KEY_MOUSE, -1, -1, 0, 0);	/* prime the handler */
	register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
		       ps_global->ttyo->screen_rows -(FOOTER_ROWS(ps_global)+1),
		       ps_global->ttyo->screen_cols);
#endif
#ifdef	_WINDOWS
	mswin_setscrollcallback(config_scroll_callback);
#endif
        /*------ Read the command from the keyboard ----*/
	ch = READ_COMMAND(&utf8str);
#ifdef	MOUSE
	clear_mfunc(mouse_in_content);
#endif
#ifdef	_WINDOWS
	mswin_setscrollcallback(NULL);
#endif

	cmd = menu_command(ch, km);

	if(km_popped)
	  switch(cmd){
	    case MC_NONE:
	    case MC_OTHER: 
	    case MC_RESIZE: 
	    case MC_REPAINT:
	      km_popped++;
	      break;
	    
	    default:
	      clearfooter(ps);
	      break;
	  }

	switch(cmd){
	  case MC_OTHER :
	    what_keymenu = NextMenu;
	    ps->mangled_footer = 1;
	    break;

	  case MC_HELP:					/* help! */
	    if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
		km_popped = 2;
		ps->mangled_footer = 1;
		break;
	    }

	    if(screen->current->help != NO_HELP){
	        prev_redrawer = ps_global->redrawer;
		helper(screen->current->help,
		       (screen->current->help_title)
		         ? screen->current->help_title
		         : CONFIG_SCREEN_HELP_TITLE,
		       HLPD_SIMPLE);
		ps_global->redrawer = prev_redrawer;
		ps->mangled_screen = 1;
	    }
	    else
	      q_status_message(SM_ORDER,0,3,_("No help yet."));

	    break;


	  case MC_NEXTITEM:			/* next list element */
	  case MC_CHARDOWN:
	    stay_in_col = 0;
	    if(screen->current->flags & CF_DOUBLEVAR){
		/* if going from col1 to col2, it's simple */
		if(!(screen->current->flags & CF_VAR2) && cmd == MC_NEXTITEM){
		    screen->current->flags |= CF_VAR2;
		    break;
		}

		/* otherwise we fall through to normal next */
		stay_in_col = (screen->current->flags & CF_VAR2 &&
			       cmd == MC_CHARDOWN);
		screen->current->flags &= ~CF_VAR2;
	    }

	    for(ctmpa = next_confline(screen->current), i = 1;
		ctmpa && (ctmpa->flags & CF_NOSELECT);
		ctmpa = next_confline(ctmpa), i++)
	      ;

	    if(ctmpa){
		screen->current = ctmpa;
		if(screen->current->flags & CF_DOUBLEVAR && stay_in_col)
		  screen->current->flags |= CF_VAR2;

		if(cmd == MC_CHARDOWN){
		    for(ctmpa = screen->top_line,
			j = BODY_LINES(ps) - 1 - HS_MARGIN(ps);
			j > 0 && ctmpa && ctmpa != screen->current;
			ctmpa = next_confline(ctmpa), j--)
		      ;

		    if(!j && ctmpa){
			for(i = 0;
			    ctmpa && ctmpa != screen->current;
			    ctmpa = next_confline(ctmpa), i++)
			  ;

			if(i)
			  config_scroll_up(i);
		    }
		}
	    }
	    else{
		/*
		 * Scroll screen a bit so we show the non-selectable
		 * lines at the bottom.
		 */

		/* set ctmpa to the bottom line on the screen */
		for(ctmpa = screen->top_line, j = BODY_LINES(ps) - 1;
		    j > 0 && ctmpa;
		    ctmpa = next_confline(ctmpa), j--)
		  ;

		i = 0;
		if(ctmpa){
		    for(ctmpa = next_confline(ctmpa);
			ctmpa &&
			(ctmpa->flags & (CF_NOSELECT | CF_B_LINE)) ==
								CF_NOSELECT;
			ctmpa = next_confline(ctmpa), i++)
		      ;
		}

		if(i)
		  config_scroll_up(i);
		else
		  q_status_message(SM_ORDER,0,1, _("Already at end of screen"));
	    }

	    break;

	  case MC_PREVITEM:			/* prev list element */
	  case MC_CHARUP:
	    stay_in_col = 0;
	    if(screen->current->flags & CF_DOUBLEVAR){
		if(screen->current->flags & CF_VAR2 && cmd == MC_PREVITEM){
		    screen->current->flags &= ~CF_VAR2;
		    break;
		}

		/* otherwise we fall through to normal prev */
		stay_in_col = (!(screen->current->flags & CF_VAR2) &&
			       cmd == MC_CHARUP);
		screen->current->flags &= ~CF_VAR2;
	    }
	    else if(cmd == MC_CHARUP)
	      stay_in_col = 1;

	    ctmpa = screen->current;
	    i = 0;
	    do
	      if(ctmpa == config_top_scroll(ps, screen->top_line))
		i = 1;
	      else if(i)
		i++;
	    while((ctmpa = prev_confline(ctmpa))
		  && (ctmpa->flags&CF_NOSELECT));

	    if(ctmpa){
		screen->current = ctmpa;
		if(screen->current->flags & CF_DOUBLEVAR && !stay_in_col)
		  screen->current->flags |= CF_VAR2;

		if((cmd == MC_CHARUP) && i)
		  config_scroll_down(i);
	    }
	    else
	      q_status_message(SM_ORDER, 0, 1,
			       _("Already at start of screen"));

	    break;

	  case MC_PAGEDN:			/* page forward */
	    screen->current->flags &= ~CF_VAR2;
	    for(ctmpa = screen->top_line, i = BODY_LINES(ps);
		i > 0 && ctmpa;
		ctmpb = ctmpa, ctmpa = next_confline(ctmpa), i--)
	      ;

	    if(ctmpa){			/* first line off bottom of screen */
		ctmpb = ctmpa;
		ps->mangled_body = 1;
		/* find first selectable line on next page */
		for(screen->top_line = ctmpa;
		    ctmpa && (ctmpa->flags & CF_NOSELECT);
		    ctmpa = next_confline(ctmpa))
		  ;
		
		/*
		 * No selectable lines on next page. Slide up to first
		 * selectable.
		 */
		if(!ctmpa){
		    for(ctmpa = prev_confline(ctmpb);
			ctmpa && (ctmpa->flags & CF_NOSELECT);
			ctmpa = prev_confline(ctmpa))
		      ;
		    
		    if(ctmpa)
		      screen->top_line = ctmpa;
		}
	    }
	    else{  /* on last screen */
		/* just move current down to last entry on screen */
		if(ctmpb){		/* last line of data */
		    for(ctmpa = ctmpb, i = BODY_LINES(ps); 
			i > 0 && ctmpa && (ctmpa->flags & CF_NOSELECT);
			ctmpa = prev_confline(ctmpa), i--)
		      ;

		    if(ctmpa == screen->current){
			q_status_message(SM_ORDER,0,1,
					 _("Already at end of screen"));
			goto no_down;
		    }

		    ps->mangled_body = 1;
		}
	    }

	    if(ctmpa)
	      screen->current = ctmpa;
no_down:
	    break;

	  case MC_PAGEUP:			/* page backward */
	    ps->mangled_body = 1;
	    screen->current->flags &= ~CF_VAR2;
	    if(!(ctmpa=prev_confline(screen->top_line)))
	      ctmpa = screen->current;

	    for(i = BODY_LINES(ps) - 1;
		i > 0 && prev_confline(ctmpa);
		i--, ctmpa = prev_confline(ctmpa))
	      ;

	    for(screen->top_line = ctmpa;
	        ctmpa && (ctmpa->flags & CF_NOSELECT);
		ctmpa = next_confline(ctmpa))
	      ;

	    if(ctmpa){
		if(ctmpa == screen->current){
		    /*
		     * We get to here if there was nothing selectable on
		     * the previous page. There still may be something
		     * selectable further back than the previous page,
		     * so look for that.
		     */
		    for(ctmpa = prev_confline(screen->top_line);
			ctmpa && (ctmpa->flags & CF_NOSELECT);
			ctmpa = prev_confline(ctmpa))
		      ;

		    if(!ctmpa){
			ctmpa = screen->current;
			q_status_message(SM_ORDER, 0, 1,
					 _("Already at start of screen"));
		    }
		}

		screen->current = ctmpa;
	    }

	    break;

#ifdef MOUSE	    
	  case MC_MOUSE:
	    {   
		MOUSEPRESS mp;

		mouse_get_last (NULL, &mp);
		mp.row -= HEADER_ROWS(ps);
		ctmpa = screen->top_line;

		while (mp.row && ctmpa != NULL) {
		    --mp.row;
		    ctmpa = ctmpa->next;
		}

		if (ctmpa != NULL && !(ctmpa->flags & CF_NOSELECT)){
		    if(screen->current->flags & CF_DOUBLEVAR)
		      screen->current->flags &= ~CF_VAR2;

		    screen->current = ctmpa;

		    if(screen->current->flags & CF_DOUBLEVAR &&
		       mp.col >= screen->current->val2offset)
		      screen->current->flags |= CF_VAR2;

		    update_option_screen(ps, screen, &cursor_pos);

		    if(mp.button == M_BUTTON_LEFT && mp.doubleclick){
		       
			if(screen->current->tool){
			    unsigned flags;
			    int default_cmd;

			    flags  = screen->current->flags;
			    flags |= (changes ? CF_CHANGES : 0);

			    default_cmd = menu_command(ctrl('M'), km);
			    switch(i=(*screen->current->tool)(ps, default_cmd,
						     &screen->current, flags)){
			      case -1:
			      case 0:
				break;

			      case 1:
				changes = 1;
				break;

			      case 2:
				retval = changes;
				done++;
				break;

			      case 3:
				retval = 1;
				done++;
				break;

			      default:
				retval = i;
				done++;
				break;
			    }
			}
		    }
#ifdef	_WINDOWS
		    else if(mp.button == M_BUTTON_RIGHT) {
			MPopup other_popup[20];
			int    n = -1, cmd, i;
			struct key_menu *sckm = screen->current->keymenu; /* only for popup */

			if((cmd = menu_command(ctrl('M'), sckm)) != MC_UNKNOWN){
			    i = menu_binding_index(sckm, cmd);
			    other_popup[++n].type	= tQueue;
			    other_popup[n].label.style  = lNormal;
			    other_popup[n].label.string = sckm->keys[i].label;
			    other_popup[n].data.val   = ctrl('M');
			}
			else if((cmd = menu_command('>', sckm)) != MC_UNKNOWN){
			    i = menu_binding_index(sckm, cmd);
			    other_popup[++n].type	= tQueue;
			    other_popup[n].label.style  = lNormal;
			    other_popup[n].label.string = sckm->keys[i].label;
			    other_popup[n].data.val	= '>';
			}

			if(((i = menu_binding_index(sckm, MC_RGB1)) >= 0) ||
			   ((i = menu_binding_index(sckm, MC_RGB2)) >= 0)){
			    other_popup[++n].type	= tQueue;
			    other_popup[n].label.style  = lNormal;
			    other_popup[n].label.string = sckm->keys[i].label;
			    other_popup[n].data.val	=
							sckm->keys[i].bind.ch[0];
			}

			if((cmd = menu_command('<', sckm)) != MC_UNKNOWN){
			    i = menu_binding_index(sckm, cmd);
			    other_popup[++n].type	= tQueue;
			    other_popup[n].label.style  = lNormal;
			    other_popup[n].label.string = sckm->keys[i].label;
			    other_popup[n].data.val	= '<';
			}
			else if((i = menu_binding_index(sckm, MC_EXIT)) >= 0){
			    other_popup[++n].type	= tQueue;
			    other_popup[n].label.style  = lNormal;
			    other_popup[n].label.string = sckm->keys[i].label;
			    other_popup[n].data.val	=
							sckm->keys[i].bind.ch[0];
			}

			if((i = menu_binding_index(sckm, MC_HELP)) >= 0){
			    if(n > 0)
			      other_popup[++n].type = tSeparator;

			    other_popup[++n].type	= tQueue;
			    other_popup[n].label.style  = lNormal;
			    other_popup[n].label.string = sckm->keys[i].label;
			    other_popup[n].data.val = sckm->keys[i].bind.ch[0];
			}

			if(n > 0){
			    other_popup[++n].type = tTail;
			    mswin_popup(other_popup);
			}
		    }
		}
		else if(mp.button == M_BUTTON_RIGHT) {
		    MPopup other_popup[20];
		    int    n = -1, cmd, i;
		    struct key_menu *sckm = screen->current->keymenu; /* only for popup */

		    if((cmd = menu_command('<', sckm)) != MC_UNKNOWN){
			i = menu_binding_index(sckm, cmd);
			other_popup[++n].type	    = tQueue;
			other_popup[n].label.style  = lNormal;
			other_popup[n].label.string = sckm->keys[i].label;
			other_popup[n].data.val	    = '<';
		    }
		    else if((i = menu_binding_index(sckm, MC_EXIT)) >= 0){
			other_popup[++n].type	    = tQueue;
			other_popup[n].label.style  = lNormal;
			other_popup[n].label.string = sckm->keys[i].label;
			other_popup[n].data.val	    = sckm->keys[i].bind.ch[0];
		    }

		    other_popup[++n].type = tTail;

		    if(n > 0)
		      mswin_popup(other_popup);
#endif
		}
	    }
	    break;
#endif

	  case MC_PRINTTXT:			/* print screen */
	    print_option_screen(screen, pdesc ? pdesc : "");
	    break;

	  case MC_WHEREIS:			/* whereis */
	    /*--- get string  ---*/
	    {int   rc, found = 0;
#define FOUND_IT       0x01
#define FOUND_CURRENT  0x02
#define FOUND_WRAPPED  0x04
#define FOUND_NOSELECT 0x08
#define FOUND_ABOVE    0x10
	     char *result = NULL, buf[64];
	     char *p, last[64];
	     static HISTORY_S *history = NULL;
	     HelpType help;
	     static ESCKEY_S ekey[] = {
		{0, 0, "", ""},
		/* TRANSLATORS: go to Top of screen */
		{ctrl('Y'), 10, "^Y", N_("Top")},
		{ctrl('V'), 11, "^V", N_("Bottom")},
		{KEY_UP,    30, "", ""},
		{KEY_DOWN,  31, "", ""},
		{-1, 0, NULL, NULL}};
#define KU_WI (3)	/* index of KEY_UP */

	     init_hist(&history, HISTSIZE);
	     last[0] = '\0';
	     if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
		strncpy(last, p, sizeof(last));
		last[sizeof(last)-1] = '\0';
	     }

	     ps->mangled_footer = 1;
	     buf[0] = '\0';
	     snprintf(tmp, sizeof(tmp), "Word to find %s%s%s: ",
		     (last[0]) ? "[" : "",
		     (last[0]) ? last : "",
		     (last[0]) ? "]" : "");
	     tmp[sizeof(tmp)-1] = '\0';
	     help = NO_HELP;
	     while(1){
		 int flags = OE_APPEND_CURRENT;

		/*
		 * 2 is really 1 because there will be one real entry and
		 * one entry of "" because of the get_prev_hist above.
		 */
		if(items_in_hist(history) > 2){
		    ekey[KU_WI].name  = HISTORY_UP_KEYNAME;
		    ekey[KU_WI].label = HISTORY_KEYLABEL;
		    ekey[KU_WI+1].name  = HISTORY_DOWN_KEYNAME;
		    ekey[KU_WI+1].label = HISTORY_KEYLABEL;
		}
		else{
		    ekey[KU_WI].name  = "";
		    ekey[KU_WI].label = "";
		    ekey[KU_WI+1].name  = "";
		    ekey[KU_WI+1].label = "";
		}

		 rc = optionally_enter(buf,-FOOTER_ROWS(ps),0,sizeof(buf),
					 tmp,ekey,help,&flags);
		 if(rc == 3)
		   help = help == NO_HELP ? h_config_whereis : NO_HELP;
		 else if(rc == 30){
		    if((p = get_prev_hist(history, buf, 0, NULL)) != NULL){
			strncpy(buf, p, sizeof(buf));
			buf[sizeof(buf)-1] = '\0';
		    }
		    else
		      Writechar(BELL, 0);

		    continue;
		 }
		 else if(rc == 31){
		    if((p = get_next_hist(history, buf, 0, NULL)) != NULL){
			strncpy(buf, p, sizeof(buf));
			buf[sizeof(buf)-1] = '\0';
		    }
		    else
		      Writechar(BELL, 0);

		    continue;
		 }
		 else if(rc == 0 || rc == 1 || rc == 10 || rc == 11 || !buf[0]){
		     if(rc == 0 && !buf[0] && last[0])
		       strncpy(buf, last, 64);

		     break;
		 }
	     }

	     screen->current->flags &= ~CF_VAR2;
	     if(rc == 0 && buf[0]){
		 CONF_S *started_here;

		 save_hist(history, buf, 0, NULL);

		 ch   = KEY_DOWN;
		 ctmpa = screen->current;
		 /*
		  * Skip over the unselectable lines of this "item"
		  * before starting search so that we don't find the
		  * same one again.
		  */
		 while((ctmpb = next_confline(ctmpa)) &&
		       (ctmpb->flags & CF_NOSELECT) &&
		       !(ctmpb->flags & CF_STARTITEM))
		   ctmpa = ctmpb;

		 started_here = next_confline(ctmpa);
		 while((ctmpa = next_confline(ctmpa)) != NULL)
		   if(srchstr(ctmpa->varname, buf)
		      || srchstr(ctmpa->value, buf)){

		       found = FOUND_IT;
		       /*
			* If this line is not selectable, back up to the
			* previous selectable line, but not past the
			* start of this "entry".
			*/
		       if(ctmpa->flags & CF_NOSELECT)
			 found |= FOUND_NOSELECT;

		       while((ctmpa->flags & CF_NOSELECT) &&
			     !(ctmpa->flags & CF_STARTITEM) &&
			     (ctmpb = prev_confline(ctmpa)))
			 ctmpa = ctmpb;
		       
		       /*
			* If that isn't selectable, better search forward
			* for something that is.
			*/
		       while((ctmpa->flags & CF_NOSELECT) &&
			     (ctmpb = next_confline(ctmpa))){
			   ctmpa = ctmpb;
			   found |= FOUND_ABOVE;
		       }

		       /*
			* If that still isn't selectable, better search
			* backwards for something that is.
			*/
		       while((ctmpa->flags & CF_NOSELECT) &&
			     (ctmpb = prev_confline(ctmpa))){
			   ctmpa = ctmpb;
			   found &= ~FOUND_ABOVE;
		       }

		       break;
		   }

		 if(!found){
		     found = FOUND_WRAPPED;
		     ctmpa = first_confline(screen->current);

		     while(ctmpa != started_here)
		       if(srchstr(ctmpa->varname, buf)
			  || srchstr(ctmpa->value, buf)){

			   found |= FOUND_IT;
			   if(ctmpa->flags & CF_NOSELECT)
			       found |= FOUND_NOSELECT;

			   while((ctmpa->flags & CF_NOSELECT) &&
				 !(ctmpa->flags & CF_STARTITEM) &&
				 (ctmpb = prev_confline(ctmpa)))
			     ctmpa = ctmpb;

			   while((ctmpa->flags & CF_NOSELECT) &&
				 (ctmpb = next_confline(ctmpa))){
			       ctmpa = ctmpb;
			       found |= FOUND_ABOVE;
			   }

			   if(ctmpa == screen->current)
			     found |= FOUND_CURRENT;

			   break;
		       }
		       else
			 ctmpa = next_confline(ctmpa);
		 }
	     }
	     else if(rc == 10){
		 screen->current = first_confline(screen->current);
		 if(screen->current && screen->current->flags & CF_NOSELECT){
		    for(ctmpa = next_confline(screen->current);
			ctmpa && (ctmpa->flags & CF_NOSELECT);
			ctmpa = next_confline(ctmpa))
		      ;
		    
		    if(ctmpa)
		      screen->current = ctmpa;
		 }

		 /* TRANSLATORS: Searched to ... is the result of the search, searched
		    to top means the search went past the bottom of the screen and
		    wrapped back around to the top. */
		 result = _("Searched to top");
	     }
	     else if(rc == 11){
		 screen->current = last_confline(screen->current);
		 if(screen->current && screen->current->flags & CF_NOSELECT){
		    for(ctmpa = prev_confline(screen->current);
			ctmpa && (ctmpa->flags & CF_NOSELECT);
			ctmpa = prev_confline(ctmpa))
		      ;
		    
		    if(ctmpa)
		      screen->current = ctmpa;
		 }
		 
		 result = _("Searched to bottom");
	     }
	     else
	       result = _("WhereIs cancelled");

	     if((found & FOUND_IT) && ctmpa){
		 strncpy(last, buf, 64);
		 result =
    (found & FOUND_CURRENT && found & FOUND_WRAPPED && found & FOUND_NOSELECT)
      ? _("Current item contains the only match")
      : (found & FOUND_CURRENT && found & FOUND_WRAPPED)
	? _("Current line contains the only match")
	: (found & FOUND_NOSELECT && found & FOUND_WRAPPED)
	  ? ((found & FOUND_ABOVE)
	       ? _("Search wrapped: word found in text above current line")
	       : _("Search wrapped: word found in text below current line"))
	  : (found & FOUND_WRAPPED)
	    ? _("Search wrapped to beginning: word found")
	    : (found & FOUND_NOSELECT)
	      ? ((found & FOUND_ABOVE)
		   ? _("Word found in text above current line")
		   : _("Word found in text below current line"))
	      : _("Word found");
		 screen->current = ctmpa;
	     }

	     q_status_message(SM_ORDER,0,3,result ? result : _("Word not found"));
	    }

	    break;

	  case MC_HOMEKEY:
	    screen->current = first_confline(screen->current);
	    if(screen->current && screen->current->flags & CF_NOSELECT){
		for(ctmpa = next_confline(screen->current);
		    ctmpa && (ctmpa->flags & CF_NOSELECT);
		    ctmpa = next_confline(ctmpa))
		  ;
		
		if(ctmpa)
		  screen->current = ctmpa;
	    }

	    q_status_message(SM_ORDER,0,3, _("Moved to top"));
	    break;

	  case MC_ENDKEY:
	    screen->current = last_confline(screen->current);
	    if(screen->current && screen->current->flags & CF_NOSELECT){
		for(ctmpa = prev_confline(screen->current);
		    ctmpa && (ctmpa->flags & CF_NOSELECT);
		    ctmpa = prev_confline(ctmpa))
		  ;
		
		if(ctmpa)
		  screen->current = ctmpa;
	    }

	    q_status_message(SM_ORDER,0,3, _("Moved to bottom"));
	    break;

	  case MC_XSHELP:					/* help! */
	    if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
		km_popped = 2;
		ps->mangled_footer = 1;
		break;
	    }

	    prev_redrawer = ps_global->redrawer;
	    helper(h_xoauth2_config_screen, "XOAUTH2 CONFIGURATION SCREEN", HLPD_SIMPLE);
	    ps_global->redrawer = prev_redrawer;
	    ps->mangled_screen = 1;
	    break;

	  case MC_XSDELETE:			/* Send caller to delete XOAUTH2 info */
	  case MC_XSADD:			/* Send caller to add XOAUTH2 info */
	     if(pos){
		*pos = get_confline_number(screen->current);
		done++;
		retval = cmd == MC_XSADD ? 4 : 5;
	     }
	     break;

	  case MC_REPAINT:			/* redraw the display */
	  case MC_RESIZE:
	    ClearScreen();
	    ps->mangled_screen = 1;
	    if(pos){
	      *pos = get_confline_number(screen->current);
	       done++;
	       retval = 0;
	    }
	    break;

	  default:
	    if(screen && screen->ro_warning){
		if(cmd == MC_EXIT){
		    retval = 0;
		    done++;
		}
		else
		  q_status_message1(SM_ORDER|SM_DING, 1, 3,
		     _("%s can't change options or settings"),
		     ps_global->restricted ? "Alpine demo"
					   : _("Config file not changeable,"));
	    }
	    else if(screen->current->tool){
		unsigned flags;

		flags  = screen->current->flags;
		flags |= (changes ? CF_CHANGES : 0);

		switch(i=(*screen->current->tool)(ps, cmd,
		    &screen->current, flags)){
		  case -1:
		    q_status_message2(SM_ORDER, 0, 2,
		      /* TRANSLATORS: Command <command letter> not defined here.
		         Leave the trailing %s which might be a parenthetical
			 remark. */
		      _("Command \"%s\" not defined here.%s"),
		      pretty_command(ch),
		      F_ON(F_BLANK_KEYMENU,ps) ? "" : "  See key menu below.");
		    break;

		  case 0:
		    break;

		  case 1:
		    changes = 1;
		    break;

		  case 2:
		    retval = changes;
		    done++;
		    break;

		  case 3:
		    retval = 1;
		    done++;
		    break;

		  default:
		    retval = i;
		    done++;
		    break;
		}
	    }

	    break;

	  case MC_UTF8:
	    bogus_utf8_command(utf8str, "?");
	    break;

	  case MC_NONE:				/* simple timeout */
	    break;
	}
    }

    screen->current = first_confline(screen->current);
    free_conflines(&screen->current);
    return(retval);
}


/*
 *
 */
void
config_scroll_up(long int n)
{
    CONF_S *ctmp = opt_screen->top_line;
    int     cur_found = 0;

    if(n < 0)
      config_scroll_down(-n);
    else if(n){
      for(; n>0 && ctmp->next; n--){
	ctmp = next_confline(ctmp);
	if(prev_confline(ctmp) == opt_screen->current)
	  cur_found++;
      }

      opt_screen->top_line = ctmp;
      ps_global->mangled_body = 1;
      if(cur_found){
	for(ctmp = opt_screen->top_line;
	    ctmp && (ctmp->flags & CF_NOSELECT);
	    ctmp = next_confline(ctmp))
	  ;

	if(ctmp)
	  opt_screen->current = opt_screen->prev = ctmp;
	else {
	  while(opt_screen->top_line->flags & CF_NOSELECT)
	    opt_screen->top_line = prev_confline(opt_screen->top_line);
	  opt_screen->current = opt_screen->prev = opt_screen->top_line;
	}
      }
    }
}


/*
 * config_scroll_down -
 */
void
config_scroll_down(long int n)
{
    CONF_S *ctmp = opt_screen->top_line, *last_sel = NULL;
    int     i;

    if(n < 0)
      config_scroll_up(-n);
    else if(n){
	for(; n>0 && ctmp->prev; n--)
	  ctmp = prev_confline(ctmp);

	opt_screen->top_line = ctmp;
	ps_global->mangled_body = 1;
	for(ctmp = opt_screen->top_line, i = BODY_LINES(ps_global);
	    i > 0 && ctmp && ctmp != opt_screen->current;
	    ctmp = next_confline(ctmp), i--)
	  if(!(ctmp->flags & CF_NOSELECT))
	    last_sel = ctmp;

	if(!i && last_sel)
	  opt_screen->current = opt_screen->prev = last_sel;
    }
}


/*
 * config_scroll_to_pos -
 */
void
config_scroll_to_pos(long int n)
{
    CONF_S *ctmp;

    for(ctmp = first_confline(opt_screen->current);
	n && ctmp && ctmp != opt_screen->top_line;
	ctmp = next_confline(ctmp), n--)
      ;

    if(n == 0)
      while(ctmp && ctmp != opt_screen->top_line)
	if((ctmp = next_confline(ctmp)) != NULL)
	  n--;

    config_scroll_up(n);
}


/*
 * config_top_scroll - return pointer to the 
 */
CONF_S *
config_top_scroll(struct pine *ps, CONF_S *topline)
{
    int     i;
    CONF_S *ctmp;

    for(ctmp = topline, i = HS_MARGIN(ps);
	ctmp && i;
	ctmp = next_confline(ctmp), i--)
      ;

    return(ctmp ? ctmp : topline);
}


int
text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
{
    return(text_toolit(ps, cmd, cl, flags, 0));
}


/*
 * simple text variable handler
 *
 * note, things get a little involved due to the
 *	 screen struct <--> variable mapping. (but, once its
 *       running it shouldn't need changing ;).
 *
 *    look_for_backslash == 1 means that backslash is an escape character.
 *                In particular, \, can be used to put a literal comma
 *                into a value. The value will still have the backslash
 *                in it, but the comma after the backslash won't be treated
 *                as an item separator.
 * 
 * returns:  -1 on unrecognized cmd, 0 if no change, 1 if change
 *           returns what conf_exit_cmd returns for exit command.
 */
int
text_toolit(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags, int look_for_backslash)
{
    char	     prompt[81], *sval, *tmp, *swap_val, **newval = NULL;
    char            *pval = NULL, **apval = NULL, **lval = NULL, ***alval = NULL;
    char            *olddefval = NULL;
    int		     rv = 0, skip_to_next = 0, after = 0, i = 4, j, k;
    int		     lowrange = 0, hirange = 0, incr = 0, oeflags, oebufsize;
    int		     numval, repeat_key = 0;
    int              curindex, previndex, nextindex, deefault;
    HelpType         help;
    ESCKEY_S         ekey[6];

    if((*cl)->var->is_list){
	lval  = LVAL((*cl)->var, ew);
	alval = ALVAL((*cl)->var, ew);
    }
    else{
	pval  = PVAL((*cl)->var, ew);
	apval = APVAL((*cl)->var, ew);
    }

    oebufsize = 6*MAXPATH;
    sval = (char *) fs_get(oebufsize*sizeof(char));
    sval[0] = '\0';

    if(flags&CF_NUMBER){ /* only happens if !is_list */
	incr = 1;
	if((*cl)->var == &ps->vars[V_FILLCOL]){
	    lowrange = 1;
	    hirange  = MAX_FILLCOL;
	}
	else if((*cl)->var == &ps->vars[V_OVERLAP]
		|| (*cl)->var == &ps->vars[V_MARGIN]){
	    lowrange = 0;
	    hirange  = 20;
	}
	else if((*cl)->var == &ps->vars[V_QUOTE_SUPPRESSION]){
	    lowrange = -(Q_SUPP_LIMIT-1);
	    hirange  = 1000;
	}
	else if((*cl)->var == &ps->vars[V_MAXREMSTREAM]){
	    lowrange = 0;
	    hirange  = 15;
	}
	else if((*cl)->var == &ps->vars[V_STATUS_MSG_DELAY]){
	    lowrange = -10;
	    hirange  = 30;
	}
	else if((*cl)->var == &ps->vars[V_ACTIVE_MSG_INTERVAL]){
	    lowrange = 0;
	    hirange  = 20;
	}
	else if((*cl)->var == &ps->vars[V_MAILCHECK] ||
	        (*cl)->var == &ps->vars[V_INCCHECKINTERVAL] ||
	        (*cl)->var == &ps->vars[V_INC2NDCHECKINTERVAL] ||
		(*cl)->var == &ps->vars[V_MAILCHECKNONCURR]){
	    lowrange = 0;
	    hirange  = 25000;
	    incr     = 15;
	}
	else if((*cl)->var == &ps->vars[V_DEADLETS]){
	    lowrange = 0;
	    hirange  = 9;
	}
	else if((*cl)->var == &ps->vars[V_NMW_WIDTH]){
	    lowrange = 20;
	    hirange  = MAX_SCREEN_COLS;
	}
	else if((*cl)->var == score_act_global_ptr){
	    lowrange = -100;
	    hirange  = 100;
	}
	else if((*cl)->var == &ps->vars[V_TCPOPENTIMEO] ||
	        (*cl)->var == &ps->vars[V_TCPREADWARNTIMEO] ||
	        (*cl)->var == &ps->vars[V_TCPQUERYTIMEO]){
	    lowrange = 5;
	    hirange  = 1000;
	}
	else if((*cl)->var == &ps->vars[V_QUITQUERYTIMEO]){
	    lowrange = 0;
	    hirange  = 1000;
	}
	else if((*cl)->var == &ps->vars[V_TCPWRITEWARNTIMEO] ||
	        (*cl)->var == &ps->vars[V_RSHOPENTIMEO] ||
	        (*cl)->var == &ps->vars[V_SSHOPENTIMEO] ||
	        (*cl)->var == &ps->vars[V_USERINPUTTIMEO]){
	    lowrange = 0;
	    hirange  = 1000;
	}
	else if((*cl)->var == &ps->vars[V_INCCHECKTIMEO]){
	    lowrange = 1;
	    hirange  = 1000;
	}
	else if((*cl)->var == &ps->vars[V_MAILDROPCHECK]){
	    lowrange = 0;
	    hirange  = 1000000;
	    incr     = 60;
	}
	else if((*cl)->var == &ps->vars[V_NNTPRANGE]){
	    lowrange = 0;
	    hirange  = 1000000;
	    incr     = 100;
	}
	else if((*cl)->var == &ps->vars[V_REMOTE_ABOOK_VALIDITY]){
	    lowrange = -1;
	    hirange  = 25000;
	}
	else if((*cl)->var == &ps->vars[V_REMOTE_ABOOK_HISTORY]){
	    lowrange = 0;
	    hirange  = 100;
	}
	else if((*cl)->var == cat_lim_global_ptr){
	    lowrange = -1;
	    hirange  = 10000000;
	}
	else{
	    lowrange = 0;
	    hirange  = 25000;
	}

	ekey[0].ch    = -2;
	ekey[0].rval  = 'x';
	ekey[0].name  = "";
	ekey[0].label = "";
	ekey[1].ch    = ctrl('P');
	ekey[1].rval  = ctrl('P');
	ekey[1].name  = "^P";
	ekey[1].label = N_("Decrease");
	ekey[2].ch    = ctrl('N');
	ekey[2].rval  = ctrl('N');
	ekey[2].name  = "^N";
	ekey[2].label = N_("Increase");
	ekey[3].ch    = KEY_DOWN;
	ekey[3].rval  = ctrl('P');
	ekey[3].name  = "";
	ekey[3].label = "";
	ekey[4].ch    = KEY_UP;
	ekey[4].rval  = ctrl('N');
	ekey[4].name  = "";
	ekey[4].label = "";
	ekey[5].ch    = -1;
    }

    switch(cmd){
      case MC_ADD:				/* add to list */
	if(fixed_var((*cl)->var, "add to", NULL)){
	    break;
	}
	else if(!(*cl)->var->is_list && pval){
	    q_status_message(SM_ORDER, 3, 3,
			    _("Only single value allowed.  Use \"Change\"."));
	}
	else{
	    int maxwidth;
	    char *p;

	    if((*cl)->var->is_list
	       && lval && lval[0] && lval[0][0]
	       && (*cl)->value){
		char tmpval[101];
		/* regular add to an existing list */

		strncpy(tmpval, (*cl)->value, sizeof(tmpval));
		tmpval[sizeof(tmpval)-1] = '\0';
		removing_trailing_white_space(tmpval);

		/* 33 is the number of chars other than the value */
		maxwidth = MIN(80, ps->ttyo->screen_cols) - 15;
		k = MIN(18, MAX(maxwidth-33,0));
		if(utf8_width(tmpval) > k && k >= 3){
		    (void) utf8_truncate(tmpval, k-3);
		    strncat(tmpval, "...", sizeof(tmpval)-strlen(tmpval)-1);
		    tmpval[sizeof(tmpval)-1] = '\0';
		}

		utf8_snprintf(prompt, sizeof(prompt),
		    _("Enter text to insert before \"%.*w\": "), k, tmpval);
		prompt[sizeof(prompt)-1] = '\0';
	    }
	    else if((*cl)->var->is_list
		    && !lval
		    && (*cl)->var->current_val.l){
		/* Add to list which doesn't exist, but default does exist */
		ekey[0].ch    = 'r';
		ekey[0].rval  = 'r';
		ekey[0].name  = "R";
		ekey[0].label = N_("Replace");
		ekey[1].ch    = 'a';
		ekey[1].rval  = 'a';
		ekey[1].name  = "A";
		ekey[1].label = N_("Add To");
		ekey[2].ch    = -1;
		strncpy(prompt, _("Replace or Add To default value ? "), sizeof(prompt));
		prompt[sizeof(prompt)-1] = '\0';
		switch(radio_buttons(prompt, -FOOTER_ROWS(ps), ekey, 'a', 'x',
				     h_config_replace_add, RB_NORM)){
		  case 'a':
		    p = sval;
		    for(j = 0; (*cl)->var->current_val.l[j]; j++){
			sstrncpy(&p, (*cl)->var->current_val.l[j], oebufsize-(p-sval));
			if(oebufsize-(p-sval) > 2){
			    *p++ = ',';
			    *p++ = ' ';
			}

			if(oebufsize-(p-sval) > 0)
			  *p = '\0';
		    }

		    sval[oebufsize-1] = '\0';

add_text:
		    if(flags & CF_NUMBER)
		      snprintf(prompt, sizeof(prompt), _("Enter the numeric text to be added : "));
		    else
		      snprintf(prompt, sizeof(prompt), _("Enter the text to be added : "));

		    break;
		    
		  case 'r':
replace_text:
		    if(olddefval){
			strncpy(sval, olddefval, oebufsize);
			sval[oebufsize-1] = '\0';
		    }

		    if(flags & CF_NUMBER)
		      snprintf(prompt, sizeof(prompt), _("Enter the  numeric replacement text : "));
		    else
		      snprintf(prompt, sizeof(prompt), _("Enter the  replacement text : "));

		    break;
		    
		  case 'x':
		    i = 1;
		    cmd_cancelled("Add");
		    break;
		}
	    }
	    else{
		if(flags & CF_NUMBER)
		  snprintf(prompt, sizeof(prompt), _("Enter the numeric text to be added : "));
		else
		  snprintf(prompt, sizeof(prompt), _("Enter the text to be added : "));
	    }

	    prompt[sizeof(prompt)-1] = '\0';

	    ps->mangled_footer = 1;

	    if(i == 1)
	      break;

	    help = NO_HELP;
	    while(1){
		if((*cl)->var->is_list
		    && lval && lval[0] && lval[0][0]
		    && (*cl)->value){
		    ekey[0].ch    = ctrl('W');
		    ekey[0].rval  = 5;
		    ekey[0].name  = "^W";
		    /* TRANSLATORS: Insert new item before current item */
		    ekey[0].label = after ? N_("InsertBefore") : N_("InsertAfter");
		    ekey[1].ch    = -1;
		}
		else if(!(flags&CF_NUMBER))
		  ekey[0].ch    = -1;

		oeflags = OE_APPEND_CURRENT;
		i = optionally_enter(sval, -FOOTER_ROWS(ps), 0, oebufsize,
				     prompt,
				     (ekey[0].ch != -1) ? ekey : NULL,
				     help, &oeflags);
		if(i == 0){
		    rv = 1;
		    if((*cl)->var->is_list)
		      ps->mangled_body = 1;
		    else
		      ps->mangled_footer = 1;

		    removing_leading_and_trailing_white_space(sval);
		    /*
		     * Coerce "" and <Empty Value> to empty string input.
		     * Catch <No Value Set> as a substitute for deleting.
		     */
		    if((*sval == '\"' && *(sval+1) == '\"' && *(sval+2) == '\0')
		        || !struncmp(sval, _(empty_val), strlen(_(empty_val))) 
			|| (*sval == '<'
			    && !struncmp(sval+1, _(empty_val), strlen(_(empty_val)))))
		      *sval = '\0';
		    else if(!struncmp(sval, _(no_val), strlen(_(no_val)))
		        || (*sval == '<'
			    && !struncmp(sval+1, _(no_val), strlen(_(no_val)))))
		      goto delete;

		    if((*cl)->var->is_list){
			if(*sval || !lval){
			    char **ltmp;
			    int    i;

			    i = 0;
			    for(tmp = sval; *tmp; tmp++)
			      if(*tmp == ',')
				i++;	/* conservative count of ,'s */

			    if(!i){
				ltmp    = (char **)fs_get(2 * sizeof(char *));
				ltmp[0] = cpystr(sval);
				ltmp[1] = NULL;
			    }
			    else
			      ltmp = parse_list(sval, i + 1,
					        look_for_backslash
						  ? PL_COMMAQUOTE : 0,
						NULL);

			    if(ltmp[0]){
				config_add_list(ps, cl, ltmp, &newval, after);
				if(after)
				  skip_to_next = 1;
			    }
			    else{
				q_status_message1(SM_ORDER, 0, 3,
					 _("Can't add %s to list"), _(empty_val));
				rv = ps->mangled_body = 0;
			    }

			    fs_give((void **)&ltmp);
			}
			else{
			    q_status_message1(SM_ORDER, 0, 3,
					 _("Can't add %s to list"), _(empty_val));
			}
		    }
		    else{
			if(flags&CF_NUMBER && sval[0]
			  && !(isdigit((unsigned char)sval[0])
			       || sval[0] == '-' || sval[0] == '+')){
			    q_status_message(SM_ORDER,3,3,
				  _("Entry must be numeric"));
			    i = 3; /* to keep loop going */
			    continue;
			}

			if(apval && *apval)
			  fs_give((void **)apval);

			if(!(olddefval && !strcmp(sval, olddefval))
			   || ((*cl)->var == &ps->vars[V_POST_CHAR_SET])
			   || want_to(_("Leave unset and use default "),
				      'y', 'y', NO_HELP, WT_FLUSH_IN) == 'n')
			  *apval = cpystr(sval);

			newval = &(*cl)->value;
		    }
		}
		else if(i == 1){
		    cmd_cancelled("Add");
		}
		else if(i == 3){
		    help = help == NO_HELP ? h_config_add : NO_HELP;
		    continue;
		}
		else if(i == 4){		/* no redraw, yet */
		    continue;
		}
		else if(i == 5){ /* change from/to prepend to/from append */
		    char tmpval[101];

		    after = after ? 0 : 1;
		    strncpy(tmpval, (*cl)->value, sizeof(tmpval));
		    tmpval[sizeof(tmpval)-1] = '\0';
		    removing_trailing_white_space(tmpval);
		    /* 33 is the number of chars other than the value */
		    maxwidth = MIN(80, ps->ttyo->screen_cols) - 15;
		    k = MIN(18, MAX(maxwidth-33,0));
		    if(utf8_width(tmpval) > k && k >= 3){
			(void) utf8_truncate(tmpval, k-3);
			strncat(tmpval, "...", sizeof(tmpval)-strlen(tmpval)-1);
			tmpval[sizeof(tmpval)-1] = '\0';
		    }

		    if(after)
		      snprintf(prompt, sizeof(prompt), _("Enter text to insert after \"%.*s\": "), k, tmpval);
		    else
		      snprintf(prompt, sizeof(prompt), _("Enter text to insert before \"%.*s\": "), k, tmpval);

		    continue;
		}
		else if(i == ctrl('P')){
		    if(sval[0])
		      numval = atoi(sval);
		    else{
		      if(pval)
			numval = atoi(pval);
		      else
			numval = lowrange + 1;
		    }

		    if(numval == lowrange){
			/*
			 * Protect user from repeating arrow key that
			 * causes message to appear over and over.
			 */
			if(++repeat_key > 0){
			    q_status_message1(SM_ORDER,3,3,
				_("Minimum value is %s"), comatose(lowrange));
			    repeat_key = -5;
			}
		    }
		    else
		      repeat_key = 0;

		    numval = MAX(numval - incr, lowrange);
		    snprintf(sval, oebufsize, "%d", numval);
		    sval[oebufsize-1] = '\0';
		    continue;
		}
		else if(i == ctrl('N')){
		    if(sval[0])
		      numval = atoi(sval);
		    else{
		      if(pval)
			numval = atoi(pval);
		      else
			numval = lowrange + 1;
		    }

		    if(numval == hirange){
			if(++repeat_key > 0){
			    q_status_message1(SM_ORDER,3,3,
				_("Maximum value is %s"), comatose(hirange));
			    repeat_key = -5;
			}
		    }
		    else
		      repeat_key = 0;

		    numval = MIN(numval + incr, hirange);
		    snprintf(sval, oebufsize, "%d", numval);
		    sval[oebufsize-1] = '\0';
		    continue;
		}

		break;
	    }
	}

	break;

      case MC_DELETE:				/* delete */
delete:
	if(!(*cl)->var->is_list
	    && apval && !*apval
	    && (*cl)->var->current_val.p){
	    char pmt[80];

	    snprintf(pmt, sizeof(pmt), _("Override default with %s"), _(empty_val2));
	    pmt[sizeof(pmt)-1] = '\0';
	    if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
		sval[0] = '\0';
		*apval = cpystr(sval);
		newval = &(*cl)->value;
		rv = ps->mangled_footer = 1;
	    }
	}
	else if((*cl)->var->is_list
		&& alval && !lval
		&& (*cl)->var->current_val.l){
	    char pmt[80];

	    snprintf(pmt, sizeof(pmt), _("Override default with %s"), _(empty_val2));
	    pmt[sizeof(pmt)-1] = '\0';
	    if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
		char **ltmp;

		sval[0] = '\0';
		ltmp    = (char **)fs_get(2 * sizeof(char *));
		ltmp[0] = cpystr(sval);
		ltmp[1] = NULL;
		config_add_list(ps, cl, ltmp, &newval, 0);
		fs_give((void **)&ltmp);
		rv = ps->mangled_body = 1;
	    }
	}
	else if(((*cl)->var->is_list && !lval)
		|| (!(*cl)->var->is_list && !pval)){
	    q_status_message(SM_ORDER, 0, 3, _("No set value to delete"));
	}
	else{
	    if((*cl)->var->is_fixed)
	        snprintf(prompt, sizeof(prompt), _("Delete (unused) %s from %s "),
		    (*cl)->var->is_list
		      ? (!*lval[(*cl)->varmem])
			  ? _(empty_val2)
			  : lval[(*cl)->varmem]
		      : (pval)
			  ? (!*pval)
			      ? _(empty_val2)
			      : pval
		 	  : "<NULL VALUE>",
		    (*cl)->var->name);
	    else
	        snprintf(prompt, sizeof(prompt), _("Really delete %s%s from %s "),
		    (*cl)->var->is_list ? "item " : "", 
		    (*cl)->var->is_list
		      ? int2string((*cl)->varmem + 1)
		      : (pval)
			  ? (!*pval)
			      ? _(empty_val2)
			      : pval
		 	  : "<NULL VALUE>",
		    (*cl)->var->name);

	    prompt[sizeof(prompt)-1] = '\0';


	    ps->mangled_footer = 1;
	    if(want_to(prompt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
		rv = 1;
		if((*cl)->var->is_list)
		  ps->mangled_body = 1;
		else
		  ps->mangled_footer = 1;

		if((*cl)->var->is_list){
		    if(lval[(*cl)->varmem])
		      fs_give((void **)&lval[(*cl)->varmem]);

		    config_del_list_item(cl, &newval);
		}
		else{
		    if(apval && *apval)
		      fs_give((void **)apval);

		    newval = &(*cl)->value;
		}
	    }
	    else
	      q_status_message(SM_ORDER, 0, 3, _("Value not deleted"));
	}

	break;

      case MC_EDIT:				/* edit/change list option */
	if(fixed_var((*cl)->var, NULL, NULL)){
	    break;
	}
	else if(((*cl)->var->is_list
		    && !lval
		    && (*cl)->var->current_val.l)
		||
		(!(*cl)->var->is_list
		    && !pval
		    && (*cl)->var->current_val.p)){

	    /*
	     * In non-list case, offer default value for editing.
	     */
	    if(!(*cl)->var->is_list
	       && (*cl)->var != &ps->vars[V_REPLY_INTRO]
	       && (*cl)->var->current_val.p[0]
	       && strcmp(VSTRING,(*cl)->var->current_val.p)){
		int quote_it;
		size_t len;

		olddefval = (char *) fs_get(strlen((*cl)->var->current_val.p)+3);

		if(!strncmp((*cl)->var->current_val.p,
			    DSTRING,
			    (len=strlen(DSTRING)))){
		    /* strip DSTRING and trailing paren */
		    strncpy(olddefval, (*cl)->var->current_val.p+len,
			    strlen((*cl)->var->current_val.p)-len-1);
		    olddefval[strlen((*cl)->var->current_val.p)-len-1] = '\0';
		}
		else{
		    /* quote it if there are trailing spaces */
		    quote_it = ((*cl)->var->current_val.p[strlen((*cl)->var->current_val.p)-1] == SPACE);
		    snprintf(olddefval, strlen((*cl)->var->current_val.p)+3, "%s%s%s", quote_it ? "\"" : "", (*cl)->var->current_val.p, quote_it ? "\"" : "");
		}

		olddefval[strlen((*cl)->var->current_val.p)+3-1] = '\0';
	    }

	    goto replace_text;
	}
	else if(((*cl)->var->is_list
		    && !lval
		    && !(*cl)->var->current_val.l)
		||
		(!(*cl)->var->is_list
		    && !pval
		    && !(*cl)->var->current_val.p)){
	    goto add_text;
	}
	else{
	    HelpType help;
	    char *clptr;

	    if(sval)
	      fs_give((void **)&sval);
	    if((*cl)->var->is_list){
		snprintf(prompt, sizeof(prompt), _("Change field %s list entry : "),
			(*cl)->var->name);
		prompt[sizeof(prompt)-1] = '\0';
 		clptr = lval[(*cl)->varmem] ? lval[(*cl)->varmem] : NULL;
	    }
	    else{
		if(flags & CF_NUMBER)
		  snprintf(prompt, sizeof(prompt), _("Change numeric field %s value : "), (*cl)->var->name);
		else
		  snprintf(prompt, sizeof(prompt), _("Change field %s value : "), (*cl)->var->name);

 		clptr = pval ? pval : NULL;
	    }

 	    oebufsize = clptr ? (int) MAX(MAXPATH, 50+strlen(clptr)) : MAXPATH;
 	    sval = (char *) fs_get(oebufsize * sizeof(char));
 	    snprintf(sval, oebufsize, "%s", clptr ? clptr : "");
	    sval[oebufsize-1] = '\0';

	    ps->mangled_footer = 1;
	    help = NO_HELP;
	    while(1){
		if(!(flags&CF_NUMBER))
		  ekey[0].ch = -1;

		oeflags = OE_APPEND_CURRENT;
		i = optionally_enter(sval, -FOOTER_ROWS(ps), 0, oebufsize,
				     prompt,
				     (ekey[0].ch != -1) ? ekey : NULL,
				     help, &oeflags);
		if(i == 0){
		    removing_leading_and_trailing_white_space(sval);
		    /*
		     * Coerce "" and <Empty Value> to empty string input.
		     * Catch <No Value Set> as a substitute for deleting.
		     */
		    if((*sval == '\"' && *(sval+1) == '\"' && *(sval+2) == '\0')
		        || !struncmp(sval, _(empty_val), strlen(_(empty_val))) 
			|| (*sval == '<'
			    && !struncmp(sval+1, _(empty_val), strlen(_(empty_val)))))
		      *sval = '\0';
		    else if(!struncmp(sval, _(no_val), strlen(_(no_val)))
			|| (*sval == '<'
			    && !struncmp(sval+1, _(no_val), strlen(_(no_val)))))
		      goto delete;

		    rv = 1;
		    if((*cl)->var->is_list)
		      ps->mangled_body = 1;
		    else
		      ps->mangled_footer = 1;

		    if((*cl)->var->is_list){
			char **ltmp = NULL;
			int    i;

			if(lval[(*cl)->varmem])
			  fs_give((void **)&lval[(*cl)->varmem]);

			i = 0;
			for(tmp = sval; *tmp; tmp++)
			  if(*tmp == ',')
			    i++;	/* conservative count of ,'s */

			if(i)
			  ltmp = parse_list(sval, i + 1,
					    look_for_backslash
					      ? PL_COMMAQUOTE : 0,
					    NULL);

			if(ltmp && !ltmp[0])		/* only commas */
			  goto delete;
			else if(!i || (ltmp && !ltmp[1])){  /* only one item */
			    lval[(*cl)->varmem] = cpystr(sval);
			    newval = &(*cl)->value;

			    if(ltmp && ltmp[0])
			      fs_give((void **)&ltmp[0]);
			}
			else if(ltmp){
			    /*
			     * Looks like the value was changed to a 
			     * list, so delete old value, and insert
			     * new list...
			     *
			     * If more than one item in existing list and
			     * current is end of existing list, then we
			     * have to delete and append instead of
			     * deleting and prepending.
			     */
			    if(((*cl)->varmem > 0 || lval[1])
			       && !(lval[(*cl)->varmem+1])){
				after = 1;
				skip_to_next = 1;
			    }

			    config_del_list_item(cl, &newval);
			    config_add_list(ps, cl, ltmp, &newval, after);
			}

			if(ltmp)
			  fs_give((void **)&ltmp);
		    }
		    else{
			if(flags&CF_NUMBER && sval[0]
			  && !(isdigit((unsigned char)sval[0])
			       || sval[0] == '-' || sval[0] == '+')){
			    q_status_message(SM_ORDER,3,3,
				  _("Entry must be numeric"));
			    continue;
			}

			if(apval && *apval)
			  fs_give((void **)apval);

			if(sval[0] && apval)
			  *apval = cpystr(sval);

			newval = &(*cl)->value;
		    }
		}
		else if(i == 1){
		    cmd_cancelled("Change");
		}
		else if(i == 3){
		    help = help == NO_HELP ? h_config_change : NO_HELP;
		    continue;
		}
		else if(i == 4){		/* no redraw, yet */
		    continue;
		}
		else if(i == ctrl('P')){
		    numval = atoi(sval);
		    if(numval == lowrange){
			/*
			 * Protect user from repeating arrow key that
			 * causes message to appear over and over.
			 */
			if(++repeat_key > 0){
			    q_status_message1(SM_ORDER,3,3,
				_("Minimum value is %s"), comatose(lowrange));
			    repeat_key = -5;
			}
		    }
		    else
		      repeat_key = 0;

		    numval = MAX(numval - incr, lowrange);
		    snprintf(sval, oebufsize, "%d", numval);
		    sval[oebufsize-1] = '\0';
		    continue;
		}
		else if(i == ctrl('N')){
		    numval = atoi(sval);
		    if(numval == hirange){
			if(++repeat_key > 0){
			    q_status_message1(SM_ORDER,3,3,
				_("Maximum value is %s"), comatose(hirange));
			    repeat_key = -5;
			}
		    }
		    else
		      repeat_key = 0;

		    numval = MIN(numval + incr, hirange);
		    snprintf(sval, oebufsize, "%d", numval);
		    sval[oebufsize-1] = '\0';
		    continue;
		}

		break;
	    }
	}

	break;

      case MC_SHUFFLE:
	if(!((*cl)->var && (*cl)->var->is_list)){
	    q_status_message(SM_ORDER, 0, 2,
			     _("Can't shuffle single-valued setting"));
	    break;
	}

	if(!alval)
	  break;

	curindex = (*cl)->varmem;
	previndex = curindex-1;
	nextindex = curindex+1;
	if(!*alval || !(*alval)[nextindex])
	  nextindex = -1;

	if((previndex < 0 && nextindex < 0) || !*alval){
	    q_status_message(SM_ORDER, 0, 3,
   _("Shuffle only makes sense when there is more than one value defined"));
	    break;
	}

	/* Move it up or down? */
	i = 0;
	ekey[i].ch      = 'u';
	ekey[i].rval    = 'u';
	ekey[i].name    = "U";
	ekey[i++].label = N_("Up");

	ekey[i].ch      = 'd';
	ekey[i].rval    = 'd';
	ekey[i].name    = "D";
	ekey[i++].label = N_("Down");

	ekey[i].ch = -1;
	deefault = 'u';

	if(previndex < 0){		/* no up */
	    ekey[0].ch = -2;
	    deefault = 'd';
	}
	else if(nextindex < 0)
	  ekey[1].ch = -2;	/* no down */

	snprintf(prompt, sizeof(prompt), "Shuffle %s%s%s ? ",
		(ekey[0].ch != -2) ? "UP" : "",
		(ekey[0].ch != -2 && ekey[1].ch != -2) ? " or " : "",
		(ekey[1].ch != -2) ? "DOWN" : "");
	help = (ekey[0].ch == -2) ? h_hdrcolor_shuf_down
				  : (ekey[1].ch == -2) ? h_hdrcolor_shuf_up
						       : h_hdrcolor_shuf;
	prompt[sizeof(prompt)-1] = '\0';

	i = radio_buttons(prompt, -FOOTER_ROWS(ps), ekey, deefault, 'x',
			   help, RB_NORM);

	switch(i){
	  case 'x':
	    cmd_cancelled("Shuffle");
	    return(rv);

	  case 'u':
	  case 'd':
	    break;
	}
		
	/* swap order */
	if(i == 'd'){
	    swap_val = (*alval)[curindex];
	    (*alval)[curindex] = (*alval)[nextindex];
	    (*alval)[nextindex] = swap_val;
	}
	else if(i == 'u'){
	    swap_val = (*alval)[curindex];
	    (*alval)[curindex] = (*alval)[previndex];
	    (*alval)[previndex] = swap_val;
	}
	else		/* can't happen */
	  break;

	/*
	 * Fix the conf line values.
	 */

	if((*cl)->value)
	  fs_give((void **)&(*cl)->value);

	(*cl)->value = pretty_value(ps, *cl);

	if(i == 'd'){
	    if((*cl)->next->value)
	      fs_give((void **)&(*cl)->next->value);

	    (*cl)->next->value = pretty_value(ps, (*cl)->next);
	    *cl = next_confline(*cl);
	}
	else{
	    if((*cl)->prev->value)
	      fs_give((void **)&(*cl)->prev->value);

	    (*cl)->prev->value = pretty_value(ps, (*cl)->prev);
	    *cl = prev_confline(*cl);
	}

	rv = ps->mangled_body = 1;
	break;

      case MC_EXIT:				/* exit */
	rv = config_exit_cmd(flags);
	break;

      default:
	rv = -1;
	break;
    }

    if(skip_to_next)
      *cl = next_confline(*cl);

    /*
     * At this point, if changes occurred, var->user_val.X is set.
     * So, fix the current_val, and handle special cases...
     *
     * NOTE: we don't worry about the "fixed variable" case here, because
     *       editing such vars should have been prevented above...
     */
    if(rv == 1){
	/*
	 * Now go and set the current_val based on user_val changes
	 * above.  Turn off command line settings...
	 */
	set_current_val((*cl)->var, TRUE, FALSE);
	fix_side_effects(ps, (*cl)->var, 0);

	/*
	 * Delay setting the displayed value until "var.current_val" is set
	 * in case current val gets changed due to a special case above.
	 */
	if(newval){
	    if(*newval)
	      fs_give((void **) newval);

	    *newval = pretty_value(ps, *cl);
	}

	exception_override_warning((*cl)->var);
    }

    if(sval)
      fs_give((void **) &sval);
 
    if(olddefval)
      fs_give((void **) &olddefval);
      
    return(rv);
}


int
config_exit_cmd(unsigned int flags)
{
    return(screen_exit_cmd(flags, "Configuration"));
}


int
simple_exit_cmd(unsigned int flags)
{
    return(2);
}


/*
 * screen_exit_cmd - basic config/flag screen exit logic
 */
int
screen_exit_cmd(unsigned int flags, char *cmd)
{
    if(flags & CF_CHANGES){
      switch(want_to(EXIT_PMT, 'y', 'x', h_config_undo, WT_FLUSH_IN)){
	case 'y':
	  q_status_message1(SM_ORDER,0,3,"%s changes saved", cmd);
	  return(2);

	case 'n':
	  q_status_message1(SM_ORDER,3,5,"No %s changes saved", cmd);
	  return(10);

	case 'x':  /* ^C */
	default :
	  q_status_message(SM_ORDER,3,5,"Changes not yet saved");
	  return(0);
      }
    }
    else
      return(2);
}


/*
 *
 */
void
config_add_list(struct pine *ps, CONF_S **cl, char **ltmp, char ***newval, int after)
{
    int	    items, i;
    char   *tmp, ***alval;
    CONF_S *ctmp;

    for(items = 0, i = 0; ltmp[i]; i++)		/* count list items */
      items++;

    alval = ALVAL((*cl)->var, ew);

    if(alval && (*alval)){
	if((*alval)[0] && (*alval)[0][0]){
	    /*
	     * Since we were already a list, make room
	     * for the new member[s] and fall thru to
	     * actually fill them in below...
	     */
	    for(i = 0; (*alval)[i]; i++)
	      ;

	    fs_resize((void **)alval, (i + items + 1) * sizeof(char *));

	    /*
	     * move the ones that will be bumped down to the bottom of the list
	     */
	    for(; i >= (*cl)->varmem + (after?1:0); i--)
	      (*alval)[i+items] = (*alval)[i];

	    i = 0;
	}
	else if(alval){
	    (*cl)->varmem = 0;
	    if(*alval)
	      free_list_array(alval);

	    *alval = (char **)fs_get((items+1)*sizeof(char *));
	    memset((void *)(*alval), 0, (items+1)*sizeof(char *));
	    (*alval)[0] = ltmp[0];
	    if(newval)
	      *newval = &(*cl)->value;

	    if((*cl)->value)
	      fs_give((void **)&(*cl)->value);

	    i = 1;
	}
    }
    else if(alval){
	/*
	 * since we were previously empty, we want
	 * to replace the first CONF_S's value with
	 * the first new value, and fill the other
	 * in below if there's a list...
	 *
	 * first, make sure we're at the beginning of this config
	 * section and dump the config lines for the default list,
	 * except for the first one, which we will over-write.
	 */
	*cl = (*cl)->varnamep; 
	while((*cl)->next && (*cl)->next->varnamep == (*cl)->varnamep)
	  snip_confline(&(*cl)->next);

	/*
	 * now allocate the new user_val array and fill in the first entry.
	 */
	*alval = (char **)fs_get((items+1)*sizeof(char *));
	memset((void *)(*alval), 0, (items+1) * sizeof(char *));
	(*alval)[(*cl)->varmem=0] = ltmp[0];
	if(newval)
	  *newval = &(*cl)->value;

	if((*cl)->value)
	  fs_give((void **)&(*cl)->value);

	i = 1;
    }

    /*
     * Make new cl's to fit in the new space.  Move the value from the current
     * line if inserting before it, else leave it where it is.
     */
    for(; i < items ; i++){
	(*alval)[i+(*cl)->varmem + (after?1:0)] = ltmp[i];
	tmp = (*cl)->value;
	new_confline(cl);
	if(after)
	  (*cl)->value   = NULL;
	else
	  (*cl)->value   = tmp;

	(*cl)->var       = (*cl)->prev->var;
	(*cl)->valoffset = (*cl)->prev->valoffset;
	(*cl)->varoffset = (*cl)->prev->varoffset;
	(*cl)->headingp  = (*cl)->prev->headingp;
	(*cl)->keymenu   = (*cl)->prev->keymenu;
	(*cl)->help      = (*cl)->prev->help;
	(*cl)->tool      = (*cl)->prev->tool;
	(*cl)->varnamep  = (*cl)->prev->varnamep;
	*cl		 = (*cl)->prev;
	if(!after)
	  (*cl)->value   = NULL;

	if(newval){
	    if(after)
	      *newval	 = &(*cl)->next->value;
	    else
	      *newval	 = &(*cl)->value;
	}
    }

    /*
     * now fix up varmem values and fill in new values that have been
     * left NULL
     */
    for(ctmp = (*cl)->varnamep, i = 0;
	(*alval)[i];
	ctmp = ctmp->next, i++){
	ctmp->varmem = i;
	if(!ctmp->value){
	    /* BUG:  We should be able to do this without the temp
	     * copy...  
	     */
	    char *ptmp = pretty_value(ps, ctmp);
	    ctmp->value = (ctmp->varnamep->flags & CF_PRINTER) ? printer_name(ptmp) : cpystr(ptmp);
	    fs_give((void **)&ptmp);
	}
    }
}


/*
 *
 */
void
config_del_list_item(CONF_S **cl, char ***newval)
{
    char   **bufp, ***alval;
    int	     i;
    CONF_S  *ctmp;

    alval = ALVAL((*cl)->var, ew);

    if((*alval)[(*cl)->varmem + 1]){
	for(bufp = &(*alval)[(*cl)->varmem];
	    (*bufp = *(bufp+1)) != NULL; bufp++)
	  ;

	if(*cl == (*cl)->varnamep){		/* leading value */
	    if((*cl)->value)
	      fs_give((void **)&(*cl)->value);

	    ctmp = (*cl)->next;
	    (*cl)->value = ctmp->value;
	    ctmp->value  = NULL;
	}
	else{
	    ctmp = *cl;			/* blast the confline */
	    *cl = (*cl)->next;
	    if(ctmp == opt_screen->top_line)
	      opt_screen->top_line = *cl;
	}

	snip_confline(&ctmp);

	for(ctmp = (*cl)->varnamep, i = 0;	/* now fix up varmem values */
	    (*alval)[i];
	    ctmp = ctmp->next, i++)
	  ctmp->varmem = i;
    }
    else if((*cl)->varmem){			/* blasted last in list */
	ctmp = *cl;
	*cl = (*cl)->prev;
	if(ctmp == opt_screen->top_line)
	  opt_screen->top_line = *cl;

	snip_confline(&ctmp);
    }
    else{					/* blasted last remaining */
	if(alval && *alval)
	  fs_give((void **)alval);

	*newval = &(*cl)->value;
    }
}


/*
 * feature list manipulation tool
 * 
 * 
 * returns:  -1 on unrecognized cmd, 0 if no change, 1 if change
 */
int
checkbox_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
{
    int  rv = 0;

    switch(cmd){
      case MC_TOGGLE:				/* mark/unmark feature */
	if((*cl)->var == &ps->vars[V_FEATURE_LIST]){
	    rv = 1;
	    toggle_feature_bit(ps, (*cl)->varmem, (*cl)->var, *cl, 0);
	}
	else
	  q_status_message(SM_ORDER | SM_DING, 3, 6,
			   "Programmer botch!  Unknown checkbox type.");

	break;

      case MC_EXIT:				 /* exit */
	rv = config_exit_cmd(flags);
	break;

      default :
	rv = -1;
	break;
    }

    return(rv);
}


/*
 * simple radio-button style variable handler
 */
int
radiobutton_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
{
    char     **apval;
    int	       rv = 0;
    NAMEVAL_S *rule = NULL;
#ifndef	_WINDOWS
    int        old_uc = 0, old_cs = 0;
    CONF_S    *ctmp;
#endif

    apval = APVAL((*cl)->var, ew);

    switch(cmd){
      case MC_CHOICE :				/* set/unset feature */

	if(fixed_var((*cl)->var, NULL, NULL)){
	    if(((*cl)->var->post_user_val.p || (*cl)->var->main_user_val.p)
	       && want_to(_("Delete old unused personal option setting"),
			  'y', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
		delete_user_vals((*cl)->var);
		q_status_message(SM_ORDER, 0, 3, _("Deleted"));
		rv = 1;
	    }

	    return(rv);
	}

	if(standard_radio_var(ps, (*cl)->var) || (*cl)->var == startup_ptr){
	    PTR_TO_RULEFUNC rulefunc;

#ifndef	_WINDOWS
	    if((*cl)->var == &ps->vars[V_COLOR_STYLE]){
		old_uc = pico_usingcolor();
		old_cs = ps->color_style;
	    }
#endif

	    if((*cl)->var->cmdline_val.p)
	      fs_give((void **)&(*cl)->var->cmdline_val.p);

	    if(apval && *apval)
	      fs_give((void **)apval);

	    rulefunc = rulefunc_from_var(ps, (*cl)->var);
	    if(rulefunc)
	      rule = (*rulefunc)((*cl)->varmem);

	    if(apval && rule)
	      *apval = cpystr(S_OR_L(rule));

	    cur_rule_value((*cl)->var, TRUE, TRUE);
	    set_radio_pretty_vals(ps, cl);

	    if((*cl)->var == &ps->vars[V_AB_SORT_RULE])
	      addrbook_redo_sorts();
	    else if((*cl)->var == &ps->vars[V_THREAD_DISP_STYLE]){
		clear_index_cache(ps->mail_stream, 0);
	    }
	    else if((*cl)->var == &ps->vars[V_THREAD_INDEX_STYLE]){
		MAILSTREAM *m;
		int         i;

		clear_index_cache(ps->mail_stream, 0);
		/* clear all hidden and collapsed flags */
		set_lflags(ps->mail_stream, ps->msgmap, MN_COLL | MN_CHID, 0);

		if(SEP_THRDINDX()
		   && SORT_IS_THREADED(ps->msgmap)
		   && unview_thread(ps, ps->mail_stream, ps->msgmap)){
		    ps->next_screen = mail_index_screen;
		    ps->view_skipped_index = 0;
		    ps->mangled_screen = 1;
		}

		if(SORT_IS_THREADED(ps->msgmap)
		   && (SEP_THRDINDX() || COLL_THRDS()))
		  collapse_threads(ps->mail_stream, ps->msgmap, NULL);

		for(i = 0; i < ps_global->s_pool.nstream; i++){
		    m = ps_global->s_pool.streams[i];
		    if(m)
		      sp_set_viewing_a_thread(m, 0);
		}

		adjust_cur_to_visible(ps->mail_stream, ps->msgmap);
	    }
#ifndef	_WINDOWS
	    else if((*cl)->var == &ps->vars[V_COLOR_STYLE]){
		if(old_cs != ps->color_style){
		    pico_toggle_color(0);
		    switch(ps->color_style){
		      case COL_NONE:
		      case COL_TERMDEF:
			pico_set_color_options(pico_trans_color() ? COLOR_TRANS_OPT : 0);
			break;
		      case COL_ANSI8:
			pico_set_color_options(COLOR_ANSI8_OPT|COLOR_TRANS_OPT);
			break;
		      case COL_ANSI16:
			pico_set_color_options(COLOR_ANSI16_OPT|COLOR_TRANS_OPT);
			break;
		      case COL_ANSI256:
			pico_set_color_options(COLOR_ANSI256_OPT|COLOR_TRANS_OPT);
			break;
		    }

		    if(ps->color_style != COL_NONE)
		      pico_toggle_color(1);
		}
	    
		if(pico_usingcolor())
		  pico_set_normal_color();

		if(!old_uc && pico_usingcolor()){

		    /*
		     * remove the explanatory warning line and a blank line
		     */

		    /* first find the first blank line */
		    for(ctmp = *cl; ctmp; ctmp = next_confline(ctmp))
		      if(ctmp->flags & CF_NOSELECT)
			break;

		    if(ctmp && ctmp->flags & CF_NOSELECT &&
		       ctmp->prev && !(ctmp->prev->flags & CF_NOSELECT) &&
		       ctmp->next && ctmp->next->flags & CF_NOSELECT &&
		       ctmp->next->next &&
		       ctmp->next->next->flags & CF_NOSELECT){
			ctmp->prev->next = ctmp->next->next;
			ctmp->next->next->prev = ctmp->prev;
			ctmp->next->next = NULL;
			free_conflines(&ctmp);
		    }

		    /* make all the colors selectable */
		    for(ctmp = *cl; ctmp; ctmp = next_confline(ctmp))
		      if(ctmp->flags & CF_POT_SLCTBL)
			ctmp->flags &= ~CF_NOSELECT;
		}
		else if(old_uc && !pico_usingcolor()){

		    /*
		     * add the explanatory warning line and a blank line
		     */

		    /* first find the existing blank line */
		    for(ctmp = *cl; ctmp; ctmp = next_confline(ctmp))
		      if(ctmp->flags & CF_NOSELECT)
			break;

		    /* add the explanatory warning line */
		    new_confline(&ctmp);
		    ctmp->help   = NO_HELP;
		    ctmp->flags |= CF_NOSELECT;
		    ctmp->value  = cpystr(COLORNOSET);

		    /* and add another blank line */
		    new_confline(&ctmp);
		    ctmp->flags |= (CF_NOSELECT | CF_B_LINE);

		    /* make all the colors non-selectable */
		    for(ctmp = *cl; ctmp; ctmp = next_confline(ctmp))
		      if(ctmp->flags & CF_POT_SLCTBL)
			ctmp->flags |= CF_NOSELECT;
		}

		clear_index_cache(ps->mail_stream, 0);
		ClearScreen();
		ps->mangled_screen = 1;
	    }
#endif

	    ps->mangled_body = 1;	/* BUG: redraw it all for now? */
	    rv = 1;
	}
	else if((*cl)->var == &ps->vars[V_SORT_KEY]){
	    SortOrder def_sort;
	    int       def_sort_rev;

	    def_sort_rev  = (*cl)->varmem >= (short) EndofList;
	    def_sort      = (SortOrder) ((*cl)->varmem - (def_sort_rev
								 * EndofList));
	    snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s", sort_name(def_sort),
		    (def_sort_rev) ? "/Reverse" : "");

	    if((*cl)->var->cmdline_val.p)
	      fs_give((void **)&(*cl)->var->cmdline_val.p);

	    if(apval){
		if(*apval)
		  fs_give((void **)apval);

		*apval = cpystr(tmp_20k_buf);
	    }

	    set_current_val((*cl)->var, TRUE, TRUE);
	    if(decode_sort(ps->VAR_SORT_KEY, &def_sort, &def_sort_rev) != -1){
		ps->def_sort     = def_sort;
		ps->def_sort_rev = def_sort_rev;
	    }

	    set_radio_pretty_vals(ps, cl);
	    ps->mangled_body = 1;	/* BUG: redraw it all for now? */
	    rv = 1;
	}
	else
	  q_status_message(SM_ORDER | SM_DING, 3, 6,
			   "Programmer botch!  Unknown radiobutton type.");

	break;

      case MC_EXIT:				/* exit */
	rv = config_exit_cmd(flags);
	break;

      default :
	rv = -1;
	break;
    }

    if(rv == 1)
      exception_override_warning((*cl)->var);
      
    return(rv);
}


/*
 * simple yes/no style variable handler
 */
int
yesno_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
{
    int   rv = 0, yes = 0;
    char *pval, **apval;

    pval =  PVAL((*cl)->var, ew);
    apval = APVAL((*cl)->var, ew);

    switch(cmd){
      case MC_TOGGLE:				/* toggle yes to no and back */
	if(fixed_var((*cl)->var, NULL, NULL)){
	    if(((*cl)->var->post_user_val.p || (*cl)->var->main_user_val.p)
	       && want_to(_("Delete old unused personal option setting"),
			  'y', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
		delete_user_vals((*cl)->var);
		q_status_message(SM_ORDER, 0, 3, _("Deleted"));
		rv = 1;
	    }

	    return(rv);
	}

	rv = 1;
	yes = ((pval && !strucmp(pval, yesstr)) ||
	       (!pval && (*cl)->var->current_val.p &&
	        !strucmp((*cl)->var->current_val.p, yesstr)));
	fs_give((void **)&(*cl)->value);

	if(apval){
	    if(*apval)
	      fs_give((void **)apval);

	    if(yes)
	      *apval = cpystr(nostr);
	    else
	      *apval = cpystr(yesstr);
	}

	set_current_val((*cl)->var, FALSE, FALSE);
	if((*cl)->value)
	  fs_give((void **)&(*cl)->value);

	(*cl)->value = pretty_value(ps, *cl);
	fix_side_effects(ps, (*cl)->var, 0);

	break;

      case MC_EXIT:				/* exit */
	rv = config_exit_cmd(flags);
	break;

      default :
	rv = -1;
	break;
    }

    return(rv);
}


/*
 * Manage display of the config/options menu body.
 */
void
update_option_screen(struct pine *ps, OPT_SCREEN_S *screen, Pos *cursor_pos)
{
    int		   dline = 0, w, save = '\0';
    CONF_S	  *top_line, *ctmp;
    char          *value;
    unsigned       got_width;
    int            want_width, first_width;
    char          *saveptr = NULL;

#ifdef _WINDOWS
    int		   last_selectable;
    mswin_beginupdate();
#endif
    if(screen == NULL || BODY_LINES(ps) < 1)
       return;

    opt_screen = screen;

    if(cursor_pos){
	cursor_pos->col = 0;
	cursor_pos->row = -1;		/* to tell us if we've set it yet */
    }

    /*
     * calculate top line of display for reframing if the current field
     * is off the display defined by screen->top_line...
     */
    if((ctmp = screen->top_line) != NULL)
      for(dline = BODY_LINES(ps);
	  dline && ctmp && ctmp != screen->current;
	  ctmp = next_confline(ctmp), dline--)
	;

    if(!ctmp || !dline){		/* force reframing */
	dline = 0;
	ctmp = top_line = first_confline(screen->current);
	do
	  if(((dline++)%BODY_LINES(ps)) == 0)
	    top_line = ctmp;
	while(ctmp != screen->current && (ctmp = next_confline(ctmp)));
    }
    else
      top_line = screen->top_line;

#ifdef _WINDOWS
    /*
     * Figure out how far down the top line is from the top and how many
     * total lines there are.  Dumb to loop every time thru, but
     * there aren't that many lines, and it's cheaper than rewriting things
     * to maintain a line count in each structure...
     */
    for(dline = 0, ctmp = prev_confline(top_line); ctmp; ctmp = prev_confline(ctmp))
      dline++;

    scroll_setpos(dline);
    last_selectable = dline;
    for(ctmp = next_confline(top_line); ctmp ; ctmp = next_confline(ctmp)){
      dline++;
      if (!(ctmp->flags & CF_NOSELECT))
	last_selectable = dline;
    }
    dline = last_selectable;
    scroll_setrange(BODY_LINES(ps), dline);
#endif

    /* mangled body or new page, force redraw */
    if(ps->mangled_body || screen->top_line != top_line)
      screen->prev = NULL;

    /* loop thru painting what's needed */
    for(dline = 0, ctmp = top_line;
	dline < BODY_LINES(ps);
	dline++, ctmp = next_confline(ctmp)){

	/*
	 * only fall thru painting if something needs painting...
	 */
	if(!(!screen->prev || ctmp == screen->prev || ctmp == screen->current
	     || ctmp == screen->prev->varnamep
	     || ctmp == screen->current->varnamep
	     || ctmp == screen->prev->headingp
	     || ctmp == screen->current->headingp))
	  continue;

	ClearLine(dline + HEADER_ROWS(ps));

	if(ctmp){
	    if(ctmp->flags & CF_B_LINE)
	      continue;

	    if(ctmp->varname && !(ctmp->flags & CF_INVISIBLEVAR)){
		if(ctmp == screen->current && cursor_pos)
		  cursor_pos->row  = dline + HEADER_ROWS(ps);

		if((ctmp == screen->current
		    || ctmp == screen->current->varnamep
		    || ctmp == screen->current->headingp)
		   && !(ctmp->flags & CF_NOHILITE))
		  StartInverse();

		if(ctmp->flags & CF_H_LINE){
		    MoveCursor(dline + HEADER_ROWS(ps), 0);
		    Write_to_screen(repeat_char(ps->ttyo->screen_cols, '-'));
		}

		if(ctmp->flags & CF_CENTERED){
		    int offset = ps->ttyo->screen_cols/2
				  - (utf8_width(ctmp->varname)/2);
		    MoveCursor(dline + HEADER_ROWS(ps),
			       (offset > 0) ? offset : 0);
		}
		else if(ctmp->varoffset)
		  MoveCursor(dline+HEADER_ROWS(ps), ctmp->varoffset);

		Write_to_screen(ctmp->varname);
		if((ctmp == screen->current
		    || ctmp == screen->current->varnamep
		    || ctmp == screen->current->headingp)
		   && !(ctmp->flags & CF_NOHILITE))
		  EndInverse();
	    }

	    value = (ctmp->flags & CF_INHERIT) ? INHERIT : ctmp->value;

	    if(value){
		char *p = NULL;
		int   i, j;

		memset(tmp_20k_buf, '\0',
		       (6*ps->ttyo->screen_cols + 1) * sizeof(char));
		if(ctmp == screen->current){
		    if(!(ctmp->flags & CF_DOUBLEVAR && ctmp->flags & CF_VAR2))
		      StartInverse();

		    if(cursor_pos)
		      cursor_pos->row  = dline + HEADER_ROWS(ps);
		}

		if(ctmp->flags & CF_H_LINE)
		  memset(tmp_20k_buf, '-',
			 ps->ttyo->screen_cols * sizeof(char));

		if(ctmp->flags & CF_CENTERED){
		    int offset = ps->ttyo->screen_cols/2
				  - (utf8_width(value)/2);
		    /* BUG: tabs screw us figuring length above */
		    if(offset > 0){
			char *q;

			p = tmp_20k_buf + offset;
			if(!*(q = tmp_20k_buf))
			  while(q < p)
			    *q++ = ' ';
		    }
		}
		else
		  p = tmp_20k_buf;

		/*
		 * Copy the value to a temp buffer expanding tabs, and
		 * making sure not to write beyond screen right...
		 */
		for(i = 0, j = ctmp->valoffset; value[i]; i++){
		    if(value[i] == ctrl('I')){
			do
			  *p++ = ' ';
			while((++j) & 0x07);
		    }
		    else{
			*p++ = value[i];
			j++;
		    }
		}

		if(ctmp == screen->current && cursor_pos){
		    if(ctmp->flags & CF_DOUBLEVAR && ctmp->flags & CF_VAR2)
		      cursor_pos->col = ctmp->val2offset;
		    else
		      cursor_pos->col = ctmp->valoffset;

		    if(ctmp->tool == radiobutton_tool
#ifdef	ENABLE_LDAP
		       || ctmp->tool==ldap_radiobutton_tool
#endif
		       || ctmp->tool==role_radiobutton_tool
		       || ctmp->tool==checkbox_tool
		       || (ctmp->tool==color_setting_tool &&
			   ctmp->valoffset != COLOR_INDENT))
		      cursor_pos->col++;
		}

		if(ctmp->flags & CF_DOUBLEVAR){
		    long l;

		    p = tmp_20k_buf;
		    first_width = ctmp->val2offset - ctmp->valoffset - SPACE_BETWEEN_DOUBLEVARS;
		    if((l=utf8_width(p)) > first_width && first_width >= 0){
			saveptr = utf8_count_forw_width(p, first_width, &got_width);
			/*
			 * got_width != first_width indicates there's a problem
			 * that should not happen. Ignore it.
			 */
			if(saveptr){
			    save = *saveptr;
			    *saveptr = '\0';
			}
		    }
		    else
		      save = '\0';

		    /*
		     * If this is a COLOR_BLOB line we do special coloring.
		     * The current object inverse hilite is only on the
		     * checkbox part, the exact format comes from the
		     * new_color_line function. If we change that we'll have
		     * to change this to get the coloring right.
		     */
		    if(p[0] == '(' && p[2] == ')' &&
		       p[3] == ' ' && p[4] == ' ' &&
		       (!strncmp(p+5, COLOR_BLOB, COLOR_BLOB_LEN)
		        || !strncmp(p+5, COLOR_BLOB_TRAN, COLOR_BLOB_LEN)
		        || !strncmp(p+5, COLOR_BLOB_NORM, COLOR_BLOB_LEN))){
			COLOR_PAIR  *lastc = NULL, *newc = NULL;

			MoveCursor(dline+HEADER_ROWS(ps), ctmp->valoffset);
			Write_to_screen_n(p, 3);
			if(!(ctmp->flags & CF_VAR2) && ctmp == screen->current)
			  EndInverse();

			Write_to_screen_n(p+3, 3);
			newc = new_color_pair(colorx(CFC_ICOLOR(ctmp)),
					      colorx(CFC_ICOLOR(ctmp)));
			if(newc){
			    lastc = pico_get_cur_color();
			    (void)pico_set_colorp(newc, PSC_NONE);
			    free_color_pair(&newc);
			}

			Write_to_screen_n(p+6, COLOR_BLOB_LEN-2);

			if(lastc){
			    (void)pico_set_colorp(lastc, PSC_NONE);
			    free_color_pair(&lastc);
			}

			Write_to_screen(p+6+COLOR_BLOB_LEN-2);
		    }
		    else{
			PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset, p);
			if(!(ctmp->flags & CF_VAR2) && ctmp == screen->current)
			  EndInverse();
		    }

		    if(saveptr)
		      *saveptr = save;

		    PutLine0(dline+HEADER_ROWS(ps),
			     ctmp->val2offset - SPACE_BETWEEN_DOUBLEVARS,
			     repeat_char(SPACE_BETWEEN_DOUBLEVARS, SPACE));

		    if(l > ctmp->val2offset - ctmp->valoffset && ctmp->val2offset - ctmp->valoffset >= 0)
		      p = saveptr + SPACE_BETWEEN_DOUBLEVARS;

		    if(p > tmp_20k_buf){
			if(ctmp->flags & CF_VAR2 && ctmp == screen->current)
			  StartInverse();

			if(p[0] == '(' && p[2] == ')' &&
			   p[3] == ' ' && p[4] == ' ' &&
			   (!strncmp(p+5, COLOR_BLOB, COLOR_BLOB_LEN)
			    || !strncmp(p+5, COLOR_BLOB_TRAN, COLOR_BLOB_LEN)
			    || !strncmp(p+5, COLOR_BLOB_NORM, COLOR_BLOB_LEN))){
			    COLOR_PAIR  *lastc = NULL, *newc = NULL;

			    MoveCursor(dline+HEADER_ROWS(ps), ctmp->val2offset);
			    Write_to_screen_n(p, 3);
			    if(ctmp->flags & CF_VAR2 && ctmp == screen->current)
			      EndInverse();

			    Write_to_screen_n(p+3, 3);
			    newc = new_color_pair(colorx(CFC_ICOLOR(ctmp)),
						  colorx(CFC_ICOLOR(ctmp)));
			    if(newc){
				lastc = pico_get_cur_color();
				(void)pico_set_colorp(newc, PSC_NONE);
				free_color_pair(&newc);
			    }

			    Write_to_screen_n(p+6, COLOR_BLOB_LEN-2);

			    if(lastc){
				(void)pico_set_colorp(lastc, PSC_NONE);
				free_color_pair(&lastc);
			    }

			    Write_to_screen(p+6+COLOR_BLOB_LEN-2);
			}
			else{
			    PutLine0(dline+HEADER_ROWS(ps),ctmp->val2offset,p);
			    if(ctmp->flags & CF_VAR2 && ctmp == screen->current)
			      EndInverse();
			}
		    }
		}
		else{
		    char       *q, *first_space, *sample, *ptr;
		    COLOR_PAIR *lastc, *newc;
		    int         invert;


		    if(ctmp->flags & CF_COLORSAMPLE &&
		       pico_usingcolor() &&
		       ((q = strstr(tmp_20k_buf, SAMPLE_LEADER)) ||
			(q = strstr(tmp_20k_buf, "Color"))) &&
		       (first_space = strindex(q, SPACE)) &&
		       (strstr(value, SAMP1) ||
		        strstr(value, SAMP2))){

			ptr = tmp_20k_buf;

			/* write out first part */
			*first_space = '\0';
			PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset,
				 ptr);
			*first_space = SPACE;
			ptr = first_space;

			if(ctmp == screen->current)
			  EndInverse();

			sample = skip_white_space(ptr);
			/* if there's enough room to put some sample up */
			save = *sample;
			*sample = '\0';
			w = utf8_width(tmp_20k_buf);
			*sample = save;
			if(ctmp->valoffset + w < ps->ttyo->screen_cols){

			    sample++;	/* for `[' at edge of sample */

			    save = *ptr;
			    *ptr = '\0';
			    w = utf8_width(tmp_20k_buf);
			    *ptr = save;

			    save = *sample;
			    *sample = '\0';
			    /* spaces and bracket before sample1 */
			    PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset+w, ptr);
			    *sample = save;

			    ptr = sample;

			    /* then the color sample */
			    if(ctmp->var == &ps->vars[V_VIEW_HDR_COLORS]
				|| ctmp->var == &ps->vars[V_INDEX_TOKEN_COLORS]){
				SPEC_COLOR_S *hc, *hcolors;

				lastc = newc = NULL;

				hcolors =
				  spec_colors_from_varlist(LVAL(ctmp->var, ew),
							   0);
				for(hc = hcolors, i=0; hc; hc = hc->next, i++)
				  if(CFC_ICUST(ctmp) == i)
				    break;

				if(hc && hc->fg && hc->fg[0] && hc->bg &&
				   hc->bg[0])
				  newc = new_color_pair(hc->fg, hc->bg);

				if(newc){
				    lastc = pico_get_cur_color();
				    (void)pico_set_colorp(newc, PSC_NONE);
				    free_color_pair(&newc);
				}

				if(hcolors)
				  free_spec_colors(&hcolors);


				/* print out sample1 */

				save = *ptr;
				*ptr = '\0';
				w = utf8_width(tmp_20k_buf);
				*ptr = save;

				want_width = MIN(utf8_width(SAMP1)-2, ps->ttyo->screen_cols - w - ctmp->valoffset);
				saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
				if(saveptr){
				    save = *saveptr;
				    *saveptr = '\0';
				}

				PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
				if(saveptr)
				  *saveptr = save;

				ptr = strindex(ptr, ']');

				if(lastc){
				    (void)pico_set_colorp(lastc, PSC_NONE);
				    free_color_pair(&lastc);
				}
			    }
			    else if(ctmp->var == &ps->vars[V_KW_COLORS]){
				KEYWORD_S    *kw;
				SPEC_COLOR_S *kw_col = NULL;

				lastc = newc = NULL;

				/* find keyword associated with this line */
				for(kw=ps->keywords, i=0; kw; kw=kw->next, i++)
				  if(CFC_ICUST(ctmp) == i)
				    break;

				if(kw)
				  kw_col =
				   spec_colors_from_varlist(LVAL(ctmp->var,ew),
							    0);

				/* color for this keyword */
				if(kw && kw_col
				   && ((kw->nick && kw->nick[0]
				        && (newc=hdr_color(kw->nick, NULL,
							   kw_col)))
				       ||
				       (kw->kw && kw->kw[0]
				        && (newc=hdr_color(kw->kw, NULL,
							   kw_col))))){
				    lastc = pico_get_cur_color();
				    (void)pico_set_colorp(newc, PSC_NONE);
				    free_color_pair(&newc);
				}

				if(kw_col)
				  free_spec_colors(&kw_col);

				/* print out sample1 */

				save = *ptr;
				*ptr = '\0';
				w = utf8_width(tmp_20k_buf);
				*ptr = save;

				want_width = MIN(utf8_width(SAMP1)-2, ps->ttyo->screen_cols - w - ctmp->valoffset);
				saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
				if(saveptr){
				    save = *saveptr;
				    *saveptr = '\0';
				}

				PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
				if(saveptr)
				  *saveptr = save;

				ptr = strindex(ptr, ']');

				if(lastc){
				    (void)pico_set_colorp(lastc, PSC_NONE);
				    free_color_pair(&lastc);
				}
			    }
			    else{
				lastc = NULL;
				invert = 0;
				newc = sample_color(ps, ctmp->var);
				if(newc){
				    if((lastc = pico_get_cur_color()) != NULL)
				      (void)pico_set_colorp(newc, PSC_NONE);

				    free_color_pair(&newc);
				}
				else if(var_defaults_to_rev(ctmp->var)){
				    if((newc = pico_get_rev_color()) != NULL){
					/*
					 * Note, don't have to free newc.
					 */
					if((lastc = pico_get_cur_color()) != NULL)
					  (void)pico_set_colorp(newc, PSC_NONE);
				    }
				    else{
					StartInverse();
					invert = 1;
				    }
				}

				if(ctmp->var==&ps->vars[V_SLCTBL_FORE_COLOR] &&

				   (F_OFF(F_SLCTBL_ITEM_NOBOLD, ps) ||
				    !(PVAL(ctmp->var,ew) &&
				      PVAL(ctmp->var+1,ew))))
				  StartBold();

				/* print out sample1 */

				save = *ptr;
				*ptr = '\0';
				w = utf8_width(tmp_20k_buf);
				*ptr = save;

				want_width = MIN(utf8_width(SAMP1)-2, ps->ttyo->screen_cols - w - ctmp->valoffset);
				saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
				if(saveptr){
				    save = *saveptr;
				    *saveptr = '\0';
				}

				PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
				if(saveptr)
				  *saveptr = save;

				ptr = strindex(ptr, ']');

				if(ctmp->var==&ps->vars[V_SLCTBL_FORE_COLOR] &&

				   (F_OFF(F_SLCTBL_ITEM_NOBOLD, ps) ||
				    !(PVAL(ctmp->var,ew) &&
				      PVAL(ctmp->var+1,ew))))
				  EndBold();

				if(lastc){
				    (void)pico_set_colorp(lastc, PSC_NONE);
				    free_color_pair(&lastc);
				}
				else if(invert)
				  EndInverse();
			    }

			    /*
			     * Finish sample1 with the right bracket.
			     */
			    save = *ptr;
			    *ptr = '\0';
			    w = utf8_width(tmp_20k_buf);
			    *ptr = save;
			    if(ctmp->valoffset + w < ps->ttyo->screen_cols){
				save = *(ptr+1);
				*(ptr+1) = '\0';
				PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
				*(ptr+1) = save;
				ptr++;
				w++;
			    }

			    /*
			     * Now check for an exception sample and paint it.
			     */
			    if(ctmp->valoffset + w + SBS + 1 < ps->ttyo->screen_cols && (q = strstr(ptr, SAMPEXC))){
				/* spaces + `[' */
				save = ptr[SBS+1];
				ptr[SBS+1] = '\0';
				PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
				ptr[SBS+1] = save;
				ptr += (SBS+1);

				/*
				 * Figure out what color to paint it.
				 * This only happens with normal variables,
				 * not with V_VIEW_HDR_COLORS.
				 */
				lastc = NULL;
				invert = 0;
				newc = sampleexc_color(ps, ctmp->var);
				if(newc){
				    if((lastc = pico_get_cur_color()) != NULL)
				      (void)pico_set_colorp(newc, PSC_NONE);

				    free_color_pair(&newc);
				}
				else if(var_defaults_to_rev(ctmp->var)){
				    if((newc = pico_get_rev_color()) != NULL){
					/*
					 * Note, don't have to free newc.
					 */
					if((lastc = pico_get_cur_color()) != NULL)
					  (void)pico_set_colorp(newc, PSC_NONE);
				    }
				    else{
					StartInverse();
					invert = 1;
				    }
				}

				if(ctmp->var==&ps->vars[V_SLCTBL_FORE_COLOR] &&

				   (F_OFF(F_SLCTBL_ITEM_NOBOLD, ps) ||
				    !(PVAL(ctmp->var,Post) &&
				      PVAL(ctmp->var+1,Post))))
				  StartBold();

				/* sample2 */
				save = *ptr;
				*ptr = '\0';
				w = utf8_width(tmp_20k_buf);
				*ptr = save;

				want_width = MIN(utf8_width(SAMPEXC)-2, ps->ttyo->screen_cols - w - ctmp->valoffset);
				saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
				if(saveptr){
				    save = *saveptr;
				    *saveptr = '\0';
				}

				PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
				if(saveptr)
				  *saveptr = save;

				ptr = strindex(ptr, ']');

				/* turn off bold and color */
				if(ctmp->var==&ps->vars[V_SLCTBL_FORE_COLOR] &&

				   (F_OFF(F_SLCTBL_ITEM_NOBOLD, ps) ||
				    !(PVAL(ctmp->var,Post) &&
				      PVAL(ctmp->var+1,Post))))
				  EndBold();

				if(lastc){
				    (void)pico_set_colorp(lastc, PSC_NONE);
				    free_color_pair(&lastc);
				}
				else if(invert)
				  EndInverse();

				/*
				 * Finish sample2 with the right bracket.
				 */
				save = *ptr;
				*ptr = '\0';
				w = utf8_width(tmp_20k_buf);
				*ptr = save;
				if(ctmp->valoffset + w < ps->ttyo->screen_cols){
				    save = *(ptr+1);
				    *(ptr+1) = '\0';
				    PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
				    *(ptr+1) = save;
				    ptr++;
				    w++;
				}
			    }

			    /* paint rest of the line if there is any left */
			    if(ctmp->valoffset + w < ps->ttyo->screen_cols && *ptr){
				want_width = ps->ttyo->screen_cols - w - ctmp->valoffset; 
				saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
				if(saveptr){
				    save = *saveptr;
				    *saveptr = '\0';
				}

				PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
				if(saveptr)
				  *saveptr = save;
			    }
			}
		    }
		    else{
			w = utf8_width(tmp_20k_buf);
			want_width = ps->ttyo->screen_cols - ctmp->valoffset; 
			if(w > want_width){
			    saveptr = utf8_count_forw_width(tmp_20k_buf, want_width, &got_width);
			    if(saveptr)
			      *saveptr = '\0';
			}

			PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset, tmp_20k_buf);
			if(ctmp == screen->current)
			  EndInverse();
		    }
		}
	    }
	}
    }

    ps->mangled_body = 0;
    screen->top_line = top_line;
    screen->prev     = screen->current;
#ifdef _WINDOWS
    mswin_endupdate();
#endif
}



/*
 * 
 */
void
print_option_screen(OPT_SCREEN_S *screen, char *prompt)
{
    CONF_S *ctmp;
    int     so_far;
    char    line[500];

    if(open_printer(prompt) == 0){
	for(ctmp = first_confline(screen->current);
	    ctmp;
	    ctmp = next_confline(ctmp)){

	    so_far = 0;
	    if(ctmp->varname && !(ctmp->flags & CF_INVISIBLEVAR)){

		snprintf(line, sizeof(line), "%*s%s", ctmp->varoffset, "",
			ctmp->varname);
		line[sizeof(line)-1] = '\0';
		print_text(line);
		so_far = ctmp->varoffset + utf8_width(ctmp->varname);
	    }

	    if(ctmp && ctmp->value){
		char *p = tmp_20k_buf;
		int   i, j, spaces;

		/* Copy the value to a temp buffer expanding tabs. */
		for(i = 0, j = ctmp->valoffset; ctmp->value[i]; i++){
		    if(ctmp->value[i] == ctrl('I')){
			do
			  *p++ = ' ';
			while((++j) & 0x07);
			      
		    }
		    else{
			*p++ = ctmp->value[i];
			j++;
		    }
		}

		*p = '\0';
		removing_trailing_white_space(tmp_20k_buf);

		spaces = MAX(ctmp->valoffset - so_far, 0);
		snprintf(line, sizeof(line), "%*s%s\n", spaces, "", tmp_20k_buf);
		line[sizeof(line)-1] = '\0';
		print_text(line);
	    }
	}

	close_printer();
    }
}



/*
 *
 */
void
option_screen_redrawer(void)
{
    ps_global->mangled_body = 1;
    update_option_screen(ps_global, opt_screen, (Pos *)NULL);
}



/*
 * pretty_value - given the line, return an
 *                alloc'd string for line's value...
 */
char *
pretty_value(struct pine *ps, CONF_S *cl)
{
    struct variable *v;

    v = cl->var;

    if(v == &ps->vars[V_FEATURE_LIST])
      return(checkbox_pretty_value(ps, cl));
    else if(standard_radio_var(ps, v) || v == startup_ptr)
      return(radio_pretty_value(ps, cl));
    else if(v == &ps->vars[V_SORT_KEY])
      return(sort_pretty_value(ps, cl));
    else if(v == &ps->vars[V_SIGNATURE_FILE])
      return(sigfile_pretty_value(ps, cl));
    else if(v == &ps->vars[V_USE_ONLY_DOMAIN_NAME])
      return(yesno_pretty_value(ps, cl));
    else if(color_holding_var(ps, v))
      return(color_pretty_value(ps, cl));
    else
      return(text_pretty_value(ps, cl));
}


char *
text_pretty_value(struct pine *ps, CONF_S *cl)
{
    char  tmp[6*MAX_SCREEN_COLS+20], *pvalnorm, **lvalnorm, *pvalexc, **lvalexc = NULL;
    char *p, *pval = NULL, **lval = NULL, lastchar = '\0';
    int   editing_except, fixed, uvalset, uvalposlen;
    unsigned got_width;
    int   comments, except_set, avail_width;
    int   norm_with_except = 0, norm_with_except_inherit = 0;
    int   inherit_line = 0;

    editing_except = (ew == ps_global->ew_for_except_vars);
    fixed = cl->var->is_fixed;
    if((ps_global->ew_for_except_vars != Main) && (ew == Main))
      norm_with_except++;	/* editing normal and except config exists */

    if(cl->var->is_list){
	lvalnorm   = LVAL(cl->var, Main);
	lvalexc    = LVAL(cl->var, ps_global->ew_for_except_vars);
	if(editing_except){
	    uvalset    = lvalexc != NULL;
	    uvalposlen = uvalset && lvalexc[0] && lvalexc[0][0];
	    lval       = lvalexc;
	}
	else{
	    uvalset    = lvalnorm != NULL;
	    uvalposlen = uvalset && lvalnorm[0] && lvalnorm[0][0];
	    lval       = lvalnorm;
	}

	except_set = lvalexc != NULL;
	comments = cl->var->current_val.l != NULL;
	if(norm_with_except && except_set && lvalexc[0] &&
	   !strcmp(lvalexc[0],INHERIT))
	  norm_with_except_inherit++;

	if(uvalset && !strcmp(lval[0], INHERIT)){
	    if(cl->varmem == 0){
		inherit_line++;
		comments = 0;
	    }
	}

	/* only add extra comments on last member of list */
	if(uvalset && !inherit_line && lval && lval[cl->varmem] &&
	   lval[cl->varmem + 1])
	  comments = 0;
    }
    else{
	pvalnorm   = PVAL(cl->var, Main);
	pvalexc    = PVAL(cl->var, ps_global->ew_for_except_vars);
	if(editing_except){
	    uvalset    = pvalexc != NULL;
	    uvalposlen = uvalset && *pvalexc;
	    pval       = pvalexc;
	}
	else{
	    uvalset    = pvalnorm != NULL;
	    uvalposlen = uvalset && *pvalnorm;
	    pval       = pvalnorm;
	}

	except_set = pvalexc != NULL;
	comments   = cl->var->current_val.p != NULL;
    }

    memset(tmp, 0, sizeof(tmp));
    p = tmp;
    *p = '\0';

    avail_width = ps->ttyo->screen_cols - cl->valoffset;

    if(fixed || !uvalset || !uvalposlen){
      p += utf8_to_width(p, "<", sizeof(tmp)-(p-tmp), avail_width, &got_width);
      avail_width -= got_width;
    }

    if(fixed){
      p += utf8_to_width(p, _(fixed_val), sizeof(tmp)-(p-tmp), avail_width, &got_width);
      avail_width -= got_width;
    }
    else if(!uvalset){
      p += utf8_to_width(p, _(no_val), sizeof(tmp)-(p-tmp), avail_width, &got_width);
      avail_width -= got_width;
    }
    else if(!uvalposlen){
      p += utf8_to_width(p, _(empty_val), sizeof(tmp)-(p-tmp), avail_width, &got_width);
      avail_width -= got_width;
    }
    else if(inherit_line){
      p += utf8_to_width(p, INHERIT, sizeof(tmp)-(p-tmp), avail_width, &got_width);
      avail_width -= got_width;
    }
    else{
	if(cl->var->is_list){
	  p += utf8_to_width(p, lval[cl->varmem], sizeof(tmp)-(p-tmp), avail_width, &got_width);
	  avail_width -= got_width;
	}
	else{
	  p += utf8_to_width(p, pval, sizeof(tmp)-(p-tmp), avail_width, &got_width);
	  avail_width -= got_width;
	}
    }

    if(comments && (fixed || !uvalset || (norm_with_except && except_set))){
	if(fixed || !uvalset){
	  p += utf8_to_width(p, ": using ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
	  avail_width -= got_width;
	}

	if(norm_with_except && except_set){
	    if(!uvalset){
	      p += utf8_to_width(p, "exception ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
	      avail_width -= got_width;
	    }
	    else if(!fixed){
		if(!uvalposlen){
		  p += utf8_to_width(p, ": ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
		  avail_width -= got_width;
		}
		else{
		    p += utf8_to_width(p, " (", sizeof(tmp)-(p-tmp), avail_width, &got_width);
		    avail_width -= got_width;
		}

		if(norm_with_except_inherit){
		  p += utf8_to_width(p, "added to by exception ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
		  avail_width -= got_width;
		}
		else{
		  p += utf8_to_width(p, "overridden by exception ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
		  avail_width -= got_width;
		}
	    }
	}

	if(avail_width >= 7){
	  if(cl->var == &ps_global->vars[V_POST_CHAR_SET]){
	    p += utf8_to_width(p, "most specific (see help)", sizeof(tmp)-(p-tmp), avail_width, &got_width);
	    avail_width -= got_width;
	  }
	  else{
	    sstrncpy(&p, "\"", sizeof(tmp)-(p-tmp));
	    avail_width--;
	    if(cl->var->is_list){
		char **the_list;

		the_list = cl->var->current_val.l;

		if(norm_with_except && except_set)
		  the_list = lvalexc;
		
		if(the_list && the_list[0] && !strcmp(the_list[0], INHERIT))
		  the_list++;

		for(lval = the_list; avail_width-(p-tmp) > 0 && *lval; lval++){
		    if(lval != the_list){
		      p += utf8_to_width(p, ",", sizeof(tmp)-(p-tmp), avail_width, &got_width);
		      avail_width -= got_width;
		    }

		    p += utf8_to_width(p, *lval, sizeof(tmp)-(p-tmp), avail_width, &got_width);
		    avail_width -= got_width;
		}
	    }
	    else{
	      p += utf8_to_width(p, cl->var->current_val.p, sizeof(tmp)-(p-tmp), avail_width, &got_width);
	      avail_width -= got_width;
	    }

	    if(p-tmp+2 < sizeof(tmp)){
		*p++ = '\"';
		*p = '\0';
	    }
	  }
	}
	else if(*(p-1) == SPACE)
	  *--p = '\0';
    }

    tmp[sizeof(tmp)-1] = '\0';

    if(fixed || !uvalset || !uvalposlen)
      lastchar = '>';
    else if(comments && norm_with_except && except_set)
      lastchar = ')';

    if(lastchar){
	if(p-tmp+2 < sizeof(tmp)){
	    *p++ = lastchar;
	    *p = '\0';
	}
    }

    tmp[sizeof(tmp)-1] = '\0';
    avail_width = ps->ttyo->screen_cols - cl->valoffset;

    if(utf8_width(tmp) < avail_width)
      snprintf(tmp+strlen(tmp), sizeof(tmp)-strlen(tmp), "%*s", avail_width-utf8_width(tmp), "");

    tmp[sizeof(tmp)-1] = '\0';

    return(cpystr(tmp));
}


char *
checkbox_pretty_value(struct pine *ps, CONF_S *cl)
{
    char             tmp[6*MAXPATH];
    char            *comment = NULL;
    int              indent, x, screen_width, need;
    int              longest_featname, longest_comment;
    int              nwidcomm;		/* name width with comment */
    int              nwidnocomm;	/* and without comment */
    FEATURE_S	    *feature;

    screen_width = (ps && ps->ttyo) ? ps->ttyo->screen_cols : 80;
    tmp[0] = '\0';

    longest_featname = longest_feature_name();
    longest_comment = longest_feature_comment(ps, ew);
    indent = feature_indent();

    nwidcomm = longest_featname;
    nwidnocomm = longest_featname + 2 + longest_comment;

    if((need = (indent + 5 + longest_featname + 2 + longest_comment) - screen_width) > 0){
	if(need < 10){
	    nwidcomm   -= need;
	    nwidnocomm -= need;
	}
	else{
	    longest_comment = 0;
	    nwidnocomm = longest_featname;
	}
    }

    feature = feature_list(cl->varmem);

    x = feature_gets_an_x(ps, cl->var, feature, &comment, ew);

    if(longest_comment && comment && *comment){
	utf8_snprintf(tmp, sizeof(tmp), "[%c]  %-*.*w  %-*.*w", x ? 'X' : ' ',
		      nwidcomm, nwidcomm,
		      pretty_feature_name(feature->name, nwidcomm),
		      longest_comment, longest_comment, comment ? comment : "");
    }
    else{
	utf8_snprintf(tmp, sizeof(tmp), "[%c]  %-*.*w", x ? 'X' : ' ',
		      nwidnocomm, nwidnocomm,
		      pretty_feature_name(feature->name, nwidnocomm));
    }

    return(cpystr(tmp));
}


int
longest_feature_name(void)
{
    static int lv = -1;
    int i, j;
    FEATURE_S	    *feature;

    if(lv < 0){
	for(lv = 0, i = 0; (feature = feature_list(i)); i++)
	  if(feature_list_section(feature)
	     && lv < (j = utf8_width(pretty_feature_name(feature->name, -1))))
	    lv = j;

	lv = MIN(lv, 100);
    }

    return(lv);
}


int
feature_indent(void)
{
    return(6);
}


char *
yesno_pretty_value(struct pine *ps, CONF_S *cl)
{
    char  tmp[6*MAXPATH], *pvalnorm, *pvalexc;
    char *p, *pval, lastchar = '\0';
    int   editing_except, fixed, norm_with_except, uvalset;
    int   curval, except_set;

    editing_except = (ew == ps_global->ew_for_except_vars);
    fixed = cl->var->is_fixed;
    if((ps_global->ew_for_except_vars == Main) ||
       (ew == ps_global->ew_for_except_vars))
      norm_with_except = 0;
    else
      norm_with_except = 1;	/* editing normal and except config exists */

    pvalnorm   = PVAL(cl->var, Main);
    pvalexc    = PVAL(cl->var, ps_global->ew_for_except_vars);
    if(editing_except){
	uvalset    = (pvalexc != NULL &&
		      (!strucmp(pvalexc,yesstr) || !strucmp(pvalexc,nostr)));
	pval       = pvalexc;
    }
    else{
	uvalset    = (pvalnorm != NULL &&
		      (!strucmp(pvalnorm,yesstr) || !strucmp(pvalnorm,nostr)));
	pval       = pvalnorm;
    }

    except_set = (pvalexc != NULL &&
		  (!strucmp(pvalexc,yesstr) || !strucmp(pvalexc,nostr)));
    curval  = (cl->var->current_val.p != NULL &&
	       (!strucmp(cl->var->current_val.p,yesstr) ||
	        !strucmp(cl->var->current_val.p,nostr)));

    p = tmp;
    *p = '\0';

    if(fixed || !uvalset)
      sstrncpy(&p, "<", sizeof(tmp)-(p-tmp));

    if(fixed)
      sstrncpy(&p, _(fixed_val), sizeof(tmp)-(p-tmp));
    else if(!uvalset)
      sstrncpy(&p, _(no_val), sizeof(tmp)-(p-tmp));
    else if(!strucmp(pval, yesstr))
      sstrncpy(&p, yesstr, sizeof(tmp)-(p-tmp));
    else
      sstrncpy(&p, nostr, sizeof(tmp)-(p-tmp));

    if(curval && (fixed || !uvalset || (norm_with_except && except_set))){
	if(fixed || !uvalset)
	  sstrncpy(&p, ": using ", sizeof(tmp)-(p-tmp));

	if(norm_with_except && except_set){
	    if(!uvalset)
	      sstrncpy(&p, "exception ", sizeof(tmp)-(p-tmp));
	    else if(!fixed){
		sstrncpy(&p, " (", sizeof(tmp)-(p-tmp));
		sstrncpy(&p, "overridden by exception ", sizeof(tmp)-(p-tmp));
	    }
	}

	sstrncpy(&p, "\"", sizeof(tmp)-(p-tmp));
	sstrncpy(&p, !strucmp(cl->var->current_val.p,yesstr) ? yesstr : nostr, sizeof(tmp)-(p-tmp));
	sstrncpy(&p, "\"", sizeof(tmp)-(p-tmp));
    }

    if(fixed || !uvalset)
      lastchar = '>';
    else if(curval && norm_with_except && except_set)
      lastchar = ')';

    if(lastchar && sizeof(tmp)-(p-tmp) > 1){
	*p++ = lastchar;
	*p = '\0';
    }

    tmp[sizeof(tmp)-1] = '\0';

    if(utf8_width(tmp) < ps->ttyo->screen_cols - cl->valoffset)
      snprintf(tmp+strlen(tmp), sizeof(tmp)-strlen(tmp),
	      "%*s", ps->ttyo->screen_cols - cl->valoffset - utf8_width(tmp), "");

    tmp[sizeof(tmp)-1] = '\0';

    return(cpystr(tmp));
}


char *
radio_pretty_value(struct pine *ps, CONF_S *cl)
{
    char  tmp[6*MAXPATH];
    char *pvalnorm, *pvalexc, *pval;
    int   editing_except_which_isnt_normal, editing_normal_which_isnt_except;
    int   fixed, is_set_for_this_level = 0, is_the_one, the_exc_one;
    int   i, j, lv = 0;
    NAMEVAL_S *rule = NULL, *f;
    PTR_TO_RULEFUNC rulefunc;
    struct variable *v;

    tmp[0] = '\0';
    v = cl->var;

    editing_except_which_isnt_normal = (ew == ps_global->ew_for_except_vars &&
					ew != Main);
    editing_normal_which_isnt_except = (ew == Main &&
					ew != ps_global->ew_for_except_vars);
    fixed = cl->var->is_fixed;
    pvalnorm = PVAL(v, Main);
    pvalexc  = PVAL(v, ps_global->ew_for_except_vars);

    rulefunc = rulefunc_from_var(ps, v);
    rule = rulefunc ? (*rulefunc)(cl->varmem) : NULL;

    /* find longest name */
    if(rulefunc)
      for(lv = 0, i = 0; (f = (*rulefunc)(i)); i++)
        if(lv < (j = utf8_width(f->name)))
	  lv = j;

    lv = MIN(lv, 100);

    if(editing_except_which_isnt_normal)
      pval = pvalexc;
    else
      pval = pvalnorm;

    if(pval)
      is_set_for_this_level++;

    if(fixed){
	pval = v->fixed_val.p;
	is_the_one = (pval && !strucmp(pval, S_OR_L(rule)));

	utf8_snprintf(tmp, sizeof(tmp), "(%c)  %-*.*w%s",
		is_the_one ? R_SELD : ' ',
		lv, lv, rule->name, is_the_one ? "   (value is fixed)" : "");
    }
    else if(is_set_for_this_level){
	is_the_one = (pval && !strucmp(pval, S_OR_L(rule)));
	the_exc_one = (editing_normal_which_isnt_except && pvalexc &&
		       !strucmp(pvalexc, S_OR_L(rule)));
	utf8_snprintf(tmp, sizeof(tmp), "(%c)  %-*.*w%s",
		is_the_one ? R_SELD : ' ',
		lv, lv, rule->name,
		(!is_the_one && the_exc_one) ? "   (value set in exceptions)" :
		 (is_the_one && the_exc_one) ? "   (also set in exceptions)" :
		  (is_the_one &&
		   editing_normal_which_isnt_except &&
		   pvalexc &&
		   !the_exc_one)             ? "   (overridden by exceptions)" :
					       "");
    }
    else{
	if(pvalexc){
	    is_the_one = !strucmp(pvalexc, S_OR_L(rule));
	    utf8_snprintf(tmp, sizeof(tmp), "(%c)  %-*.*w%s",
		    is_the_one ? R_SELD : ' ',
		    lv, lv, rule->name,
		    is_the_one ? "   (value set in exceptions)" : "");
	}
	else{
	    pval = v->current_val.p;
	    is_the_one = (pval && !strucmp(pval, S_OR_L(rule)));
	    utf8_snprintf(tmp, sizeof(tmp), "(%c)  %-*.*w%s",
		    is_the_one ? R_SELD : ' ',
		    lv, lv, rule->name,
		    is_the_one ? ((editing_except_which_isnt_normal && pvalnorm) ? "   (default from regular config)" : "   (default)") : "");
	}
    }

    tmp[sizeof(tmp)-1] = '\0';

    return(cpystr(tmp));
}


char *
sigfile_pretty_value(struct pine *ps, CONF_S *cl)
{
    if(cl && cl->var == &ps->vars[V_SIGNATURE_FILE] &&
       cl->prev && cl->prev->var == &ps->vars[V_LITERAL_SIG]){
	if(cl->prev->var->current_val.p){
	    cl->flags |= CF_NOSELECT;		/* side effect */
	    return(cpystr(_("<Ignored: using Literal-Signature instead>")));
	}
	else{
	    cl->flags &= ~CF_NOSELECT;
	    return(text_pretty_value(ps, cl));
	}
    }
    else
      return(cpystr(""));
}


char *
color_pretty_value(struct pine *ps, CONF_S *cl)
{
    char             tmp[6*MAXPATH];
    char            *p, *q;
    struct variable *v;
    int              is_index;

    tmp[0] = '\0';
    v = cl->var;

    if(v && color_holding_var(ps, v) &&
       (p=srchstr(v->name, "-foreground-color"))){

	is_index = !struncmp(v->name, "index-", 6);

	q = sampleexc_text(ps, v);
	utf8_snprintf(tmp, sizeof(tmp), "%c%.*s %sColor%*.50s %.20w%*s%.20w%.20w",
		islower((unsigned char)v->name[0])
					? toupper((unsigned char)v->name[0])
					: v->name[0],
		MIN(p-v->name-1,30), v->name+1,
		is_index ? "Symbol " : "",
		MAX(EQ_COL - COLOR_INDENT -1 - MIN(p-v->name-1,30)
			    - 6 - (is_index ? 7 : 0) - 1,0), "",
		sample_text(ps,v), *q ? SBS : 0, "", q,
		color_parenthetical(v));
    }

    tmp[sizeof(tmp)-1] = '\0';

    return(cpystr(tmp));
}


char *
sort_pretty_value(struct pine *ps, CONF_S *cl)
{
    return(generalized_sort_pretty_value(ps, cl, 1));
}


char *
generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok)
{
    char  tmp[6*MAXPATH];
    char *pvalnorm, *pvalexc, *pval;
    int   editing_except_which_isnt_normal, editing_normal_which_isnt_except;
    int   fixed, is_set_for_this_level = 0, is_the_one, the_exc_one;
    int   i, j, lv = 0;
    struct variable *v;
    SortOrder line_sort, var_sort, exc_sort;
    int       line_sort_rev, var_sort_rev, exc_sort_rev;

    tmp[0] = '\0';
    v = cl->var;

    editing_except_which_isnt_normal = (ew == ps_global->ew_for_except_vars &&
					ew != Main);
    editing_normal_which_isnt_except = (ew == Main &&
					ew != ps_global->ew_for_except_vars);
    fixed = cl->var->is_fixed;
    pvalnorm = PVAL(v, Main);
    pvalexc  = PVAL(v, ps_global->ew_for_except_vars);

    /* find longest value's name */
    for(lv = 0, i = 0; ps->sort_types[i] != EndofList; i++)
      if(lv < (j = utf8_width(sort_name(ps->sort_types[i]))))
	lv = j;
    
    lv = MIN(lv, 100);

    if(editing_except_which_isnt_normal)
      pval = pvalexc;
    else
      pval = pvalnorm;

    if(pval)
      is_set_for_this_level++;

    /* the config line we're talking about */
    if(cl->varmem >= 0){
	line_sort_rev = cl->varmem >= (short)EndofList;
	line_sort     = (SortOrder)(cl->varmem - (line_sort_rev * EndofList));
    }

    if(cl->varmem < 0){
	utf8_snprintf(tmp, sizeof(tmp), "(%c)  %-*w",
		(pval == NULL) ? R_SELD : ' ',
		lv, "Default");
    }
    else if(fixed){
	pval = v->fixed_val.p;
	decode_sort(pval, &var_sort, &var_sort_rev);
	is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort);

	utf8_snprintf(tmp, sizeof(tmp), "(%c)  %s%-*w%*s%s",
		is_the_one ? R_SELD : ' ',
		line_sort_rev ? "Reverse " : "",
		lv, sort_name(line_sort),
		line_sort_rev ? 0 : 8, "",
		is_the_one ? "   (value is fixed)" : "");
    }
    else if(is_set_for_this_level){
	decode_sort(pval, &var_sort, &var_sort_rev);
	is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort);
	decode_sort(pvalexc, &exc_sort, &exc_sort_rev);
	the_exc_one = (editing_normal_which_isnt_except && pvalexc &&
		       exc_sort_rev == line_sort_rev && exc_sort == line_sort);
	utf8_snprintf(tmp, sizeof(tmp), "(%c)  %s%-*w%*s%s",
		is_the_one ? R_SELD : ' ',
		line_sort_rev ? "Reverse " : "",
		lv, sort_name(line_sort),
		line_sort_rev ? 0 : 8, "",
		(!is_the_one && the_exc_one) ? "   (value set in exceptions)" :
		 (is_the_one && the_exc_one) ? "   (also set in exceptions)" :
		  (is_the_one &&
		   editing_normal_which_isnt_except &&
		   pvalexc &&
		   !the_exc_one)             ? "   (overridden by exceptions)" :
					       "");
    }
    else{
	if(pvalexc){
	    decode_sort(pvalexc, &exc_sort, &exc_sort_rev);
	    is_the_one = (exc_sort_rev == line_sort_rev &&
			  exc_sort == line_sort);
	    utf8_snprintf(tmp, sizeof(tmp), "( )  %s%-*w%*s%s",
		    line_sort_rev ? "Reverse " : "",
		    lv, sort_name(line_sort),
		    line_sort_rev ? 0 : 8, "",
		    is_the_one ? "   (value set in exceptions)" : "");
	}
	else{
	    pval = v->current_val.p;
	    decode_sort(pval, &var_sort, &var_sort_rev);
	    is_the_one = ((pval || default_ok) &&
			  var_sort_rev == line_sort_rev &&
			  var_sort == line_sort);
	    utf8_snprintf(tmp, sizeof(tmp), "(%c)  %s%-*w%*s%s",
		    is_the_one ? R_SELD : ' ',
		    line_sort_rev ? "Reverse " : "",
		    lv, sort_name(line_sort),
		    line_sort_rev ? 0 : 8, "",
		    is_the_one ? ((editing_except_which_isnt_normal && pvalnorm) ? "   (default from regular config)" : "   (default)") : "");
	}
    }

    return(cpystr(tmp));
}


COLOR_PAIR *
sample_color(struct pine *ps, struct variable *v)
{
    COLOR_PAIR *cp = NULL;
    char *pvalefg, *pvalebg;
    char *pvalmfg, *pvalmbg;

    pvalefg = PVAL(v, ew);
    pvalebg = PVAL(v+1, ew);
    pvalmfg = PVAL(v, Main);
    pvalmbg = PVAL(v+1, Main);
    if(v && color_holding_var(ps, v) &&
       srchstr(v->name, "-foreground-color")){
	if(pvalefg && pvalefg[0] && pvalebg && pvalebg[0])
	  cp = new_color_pair(pvalefg, pvalebg);
	else if(ew == Post && pvalmfg && pvalmfg[0] && pvalmbg && pvalmbg[0])
	  cp = new_color_pair(pvalmfg, pvalmbg);
	else if(v->global_val.p && v->global_val.p[0] &&
	        (v+1)->global_val.p && (v+1)->global_val.p[0])
	  cp = new_color_pair(v->global_val.p, (v+1)->global_val.p);
    }

    return(cp);
}


COLOR_PAIR *
sampleexc_color(struct pine *ps, struct variable *v)
{
    COLOR_PAIR *cp = NULL;
    char *pvalfg, *pvalbg;

    pvalfg = PVAL(v, Post);
    pvalbg = PVAL(v+1, Post);
    if(v && color_holding_var(ps, v) &&
       srchstr(v->name, "-foreground-color") &&
       pvalfg && pvalfg[0] && pvalbg && pvalbg[0])
	cp = new_color_pair(pvalfg, pvalbg);

    return(cp);
}


void
clear_feature(char ***l, char *f)
{
    char **list = l ? *l : NULL;
    int    count = 0;

    for(; list && *list; list++, count++){
	if(f && !strucmp(((!struncmp(*list,"no-",3)) ? *list + 3 : *list), f)){
	    fs_give((void **)list);
	    f = NULL;
	}

	if(!f)					/* shift  */
	  *list = *(list + 1);
    }

    /*
     * this is helpful to keep the array from growing if a feature
     * gets set and unset repeatedly
     */
    if(!f)
      fs_resize((void **)l, count * sizeof(char *));
}


/*
 *
 */
void
toggle_feature_bit(struct pine *ps, int index, struct variable *var, CONF_S *cl, int just_flip_value)
{
    FEATURE_S *f;
    int        og, on_before;
    char      *p, **vp;

    f = feature_list(index);

    og = test_old_growth_bits(ps, f->id);

    /*
     * if this feature is in the fixed set, or old-growth is in the fixed
     * set and this feature is in the old-growth set, don't alter it...
     */
    for(vp = var->fixed_val.l; vp && *vp; vp++){
	p = (struncmp(*vp, "no-", 3)) ? *vp : *vp + 3;
	if(!strucmp(p, f->name) || (og && !strucmp(p, "old-growth"))){
	    q_status_message(SM_ORDER, 3, 3,
			     _("Can't change value fixed by sys-admin."));
	    return;
	}
    }

    on_before = F_ON(f->id, ps);

    toggle_feature(ps, var, f, just_flip_value, ew);

    /*
     * Handle any alpine-specific features that need attention here. Features
     * that aren't alpine-specific should be handled in toggle_feature instead.
     */
    if(on_before != F_ON(f->id, ps))
      switch(f->id){
	case F_CMBND_ABOOK_DISP :
	  addrbook_reset();
	  break;

	case F_PRESERVE_START_STOP :
	  /* toggle raw mode settings to make tty driver aware of new setting */
	  PineRaw(0);
	  PineRaw(1);
	  break;

	case F_USE_FK :
	  ps->orig_use_fkeys = F_ON(F_USE_FK, ps);
	  ps->mangled_footer = 1;
          mark_keymenu_dirty();
	  break;

	case F_SHOW_SORT :
	  ps->mangled_header = 1;
	  break;

	case F_BLANK_KEYMENU :
	  if(F_ON(f->id, ps)){
	      FOOTER_ROWS(ps) = 1;
	      ps->mangled_body = 1;
	  }
	  else{
	      FOOTER_ROWS(ps) = 3;
	      ps->mangled_footer = 1;
	  }

          clearfooter(ps);
	  break;

	case F_ENABLE_INCOMING :
	  q_status_message(SM_ORDER | SM_DING, 3, 4,
	       "Folder List changes will take effect your next Alpine session.");
	  break;

#ifdef	_WINDOWS
	case F_SHOW_CURSOR :
	  mswin_showcaret(F_ON(f->id,ps));
	  break;

	case F_ENABLE_TRAYICON :
	  mswin_trayicon(F_ON(f->id,ps));
	  break;
#endif

#if !defined(DOS) && !defined(OS2)
	case F_ALLOW_TALK :
	  if(F_ON(f->id, ps))
	    allow_talk(ps);
	  else
	    disallow_talk(ps);

	  break;
#endif

	case F_PASS_CONTROL_CHARS :
	  ps->pass_ctrl_chars = F_ON(F_PASS_CONTROL_CHARS,ps_global) ? 1 : 0;
	  break;

#ifdef SMIME
	case F_USE_CERT_STORE_ONLY:
	  if(F_OFF(F_USE_CERT_STORE_ONLY, ps))
	  q_status_message(SM_ORDER | SM_DING, 3, 4,
	  "Disabling this feature should only be done for testing. Press \"?\" for help");
	  break;
#endif /* SMIME */

	case F_PASS_C1_CONTROL_CHARS :
	  ps->pass_c1_ctrl_chars = F_ON(F_PASS_C1_CONTROL_CHARS,ps_global) ? 1 : 0;
	  break;

#ifdef	MOUSE
	case F_ENABLE_MOUSE :
	  if(F_ON(f->id, ps)){
	    init_mouse();
	    if(!mouseexist())
	      q_status_message(SM_ORDER | SM_DING, 3, 4,
			  "Mouse tracking still off ($DISPLAY variable set?)");
	  }
	  else
	    end_mouse();

	  break;
#endif
      }

    if(just_flip_value){
	if(cl->value && cl->value[0])
	  cl->value[1] = (cl->value[1] == ' ') ? 'X' : ' ';
    }
    else{
	/*
	 * This fork is only called from the checkbox_tool, which has
	 * varmem set to index correctly and cl->var set correctly.
	 */
	if(cl->value)
	  fs_give((void **)&cl->value);

	cl->value = pretty_value(ps, cl);
    }
}


/*
 * new_confline - create new CONF_S zero it out, and insert it after current.
 *                NOTE current gets set to the new CONF_S too!
 */
CONF_S *
new_confline(CONF_S **current)
{
    CONF_S *p;

    p = (CONF_S *)fs_get(sizeof(CONF_S));
    memset((void *)p, 0, sizeof(CONF_S));
    if(current){
	if(*current){
	    p->next	     = (*current)->next;
	    (*current)->next = p;
	    p->prev	     = *current;
	    if(p->next)
	      p->next->prev = p;
	}

	*current = p;
    }

    return(p);
}


/*
 *
 */
void
snip_confline(CONF_S **p)
{
    CONF_S *q;

    /*
     * Be careful. We need this line because the 
     *   q->prev->next = ...
     * may change q itself if &q == &q->prev->next.
     * Then the use of q in the next line is wrong.
     * That's what happens if we pass in the address of
     * some ->next and use *p directly instead of q.
     */
    q = *p;

    if(q){
	/* Yank it from the linked list */
	if(q->prev)
	  q->prev->next = q->next;

	if(q->next)
	  q->next->prev = q->prev;

	/* Then free up it's memory */
	q->prev = q->next = NULL;
	free_conflines(&q);
    }
}

int 
get_confline_number(CONF_S *conf)
{
  int pos;
  CONF_S *p;

  for (p = first_confline(conf), pos = 0; p != conf; p = next_confline(p), pos++);

  return pos;
}


CONF_S *
set_confline_number(CONF_S *conf, int pos)
{
  CONF_S *p;
  int i;
  for(p = first_confline(conf), i = 0; p && i < pos; p=next_confline(p), i++);
  return p;
}


/*
 *
 */
void
free_conflines(CONF_S **p)
{
    if(*p){
	free_conflines(&(*p)->next);

	if((*p)->varname)
	  fs_give((void **) &(*p)->varname);

	if((*p)->value)
	  fs_give((void **) &(*p)->value);

	fs_give((void **) p);
    }
}


/*
 *
 */
CONF_S *
first_confline(CONF_S *p)
{
    while(p && p->prev)
      p = p->prev;

    return(p);
}


/*
 * First selectable confline.
 */
CONF_S *
first_sel_confline(CONF_S *p)
{
    for(p = first_confline(p); p && (p->flags&CF_NOSELECT); p=next_confline(p))
      ;/* do nothing */

    return(p);
}


/*
 *
 */
CONF_S *
last_confline(CONF_S *p)
{
    while(p && p->next)
      p = p->next;

    return(p);
}


/*
 *
 */
int
fixed_var(struct variable *v, char *action, char *name)
{
    char **lval;

    if(v && v->is_fixed
       && (!v->is_list
           || ((lval=v->fixed_val.l) && lval[0]
	       && strcmp(INHERIT, lval[0]) != 0))){
	q_status_message2(SM_ORDER, 3, 3,
			  "Can't %s sys-admin defined %s.",
			  action ? action : "change", name ? name : "value");
	return(1);
    }

    return(0);
}


void
exception_override_warning(struct variable *v)
{
    char **lval;

    /* if exceptions config file exists and we're not editing it */
    if(v && (ps_global->ew_for_except_vars != Main) && (ew == Main)){
	if((!v->is_list && PVAL(v, ps_global->ew_for_except_vars)) ||
	   (v->is_list && (lval=LVAL(v, ps_global->ew_for_except_vars)) &&
	    lval[0] && strcmp(INHERIT, lval[0]) != 0))
	  q_status_message1(SM_ORDER, 3, 3,
	   _("Warning: \"%s\" is overridden in your exceptions configuration"),
	      v->name);
    }
}


void
offer_to_fix_pinerc(struct pine *ps)
{
    struct variable *v;
    char             prompt[300];
    char            *p, *q;
    char           **list;
    char           **list_fixed;
    int              rv = 0, write_main = 0, write_post = 0;
    int              i, k, j, need, exc;
    char            *clear = ": delete it";
    char          ***plist;

    dprint((4, "offer_to_fix_pinerc()\n"));

    ps->fix_fixed_warning = 0;  /* so we only ask first time */

    if(ps->readonly_pinerc)
      return;

    set_titlebar(_("FIXING PINERC"), ps->mail_stream,
		 ps->context_current,
		 ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0, NULL);

    if(want_to(_("Some of your options conflict with site policy.  Investigate"),
	'y', 'n', NO_HELP, WT_FLUSH_IN) != 'y')
      return;
    
/* space want_to requires in addition to the string you pass in */
#define WANTTO_SPACE 6
    need = WANTTO_SPACE + utf8_width(clear);

    for(v = ps->vars; v->name; v++){
	if(!v->is_fixed ||
	   !v->is_user ||
	    v->is_obsolete ||
	    v == &ps->vars[V_FEATURE_LIST]) /* handle feature-list below */
	  continue;
	
	prompt[0] = '\0';
	
	if(v->is_list &&
	   (v->post_user_val.l || v->main_user_val.l)){
	    char **active_list;

	    active_list = v->post_user_val.l ? v->post_user_val.l
					     : v->main_user_val.l;
	    if(*active_list){
		snprintf(prompt, sizeof(prompt), _("Your setting for %s is "), v->name);
		prompt[sizeof(prompt)-1] = '\0';
		p = prompt + strlen(prompt);
		for(i = 0; active_list[i]; i++){
		    if(utf8_width(prompt) > ps->ttyo->screen_cols - need)
		      break;
		    if(i && sizeof(prompt)-(p-prompt) > 0)
		      *p++ = ',';

		    sstrncpy(&p, active_list[i], sizeof(prompt)-(p-prompt));
		    if(sizeof(prompt)-(p-prompt) > 0)
		      *p = '\0';

		    prompt[sizeof(prompt)-1] = '\0';
		}

		if(sizeof(prompt)-(p-prompt) > 0)
		  *p = '\0';
	    }
	    else
	      snprintf(prompt, sizeof(prompt), _("Your setting for %s is %s"), v->name, _(empty_val2));
	}
	else{
	    if(v->post_user_val.p || v->main_user_val.p){
		char *active_var;

		active_var = v->post_user_val.p ? v->post_user_val.p
						: v->main_user_val.p;
		if(*active_var){
		    snprintf(prompt, sizeof(prompt), _("Your setting for %s is %s"),
			v->name, active_var);
		}
		else{
		    snprintf(prompt, sizeof(prompt), _("Your setting for %s is %s"),
			v->name, _(empty_val2));
		}
	    }
	}

	prompt[sizeof(prompt)-1] = '\0';

	if(*prompt){
	    if(utf8_width(prompt) > ps->ttyo->screen_cols - need)
	      (void) utf8_truncate(prompt, ps->ttyo->screen_cols - need);

	    (void) strncat(prompt, clear, sizeof(prompt)-strlen(prompt)-1);
	    prompt[sizeof(prompt)-1] = '\0';
	    if(want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){
		if(v->is_list){
		    if(v->main_user_val.l)
		      write_main++;
		    if(v->post_user_val.l)
		      write_post++;
		}
		else{
		    if(v->main_user_val.p)
		      write_main++;
		    if(v->post_user_val.p)
		      write_post++;
		}

		if(delete_user_vals(v))
		  rv++;
	    }
	}
    }


    /*
     * As always, feature-list has to be handled separately.
     */
    exc = (ps->ew_for_except_vars != Main);
    v = &ps->vars[V_FEATURE_LIST];
    list_fixed = v->fixed_val.l;

    for(j = 0; j < 2; j++){
      plist = (j==0) ? &v->main_user_val.l : &v->post_user_val.l;
      list = *plist;
      if(list){
        for(i = 0; list[i]; i++){
	  p = list[i];
	  if(!struncmp(p, "no-", 3))
	    p += 3;
	  for(k = 0; list_fixed && list_fixed[k]; k++){
	    q = list_fixed[k];
	    if(!struncmp(q, "no-", 3))
	      q += 3;
	    if(!strucmp(q, p) && strucmp(list[i], list_fixed[k])){
	      snprintf(prompt, sizeof(prompt), "Your %s is %s%s, fixed value is %s",
		  p, p == list[i] ? _("ON") : _("OFF"),
		  exc ? ((plist == &v->main_user_val.l) ? ""
						        : " in postload-config")
		      : "",
		  q == list_fixed[k] ? _("ON") : _("OFF"));

	      prompt[sizeof(prompt)-1] = '\0';
	      if(utf8_width(prompt) > ps->ttyo->screen_cols - need)
		(void) utf8_truncate(prompt, ps->ttyo->screen_cols - need);

	      (void) strncat(prompt, clear, sizeof(prompt)-strlen(prompt)-1);
	      prompt[sizeof(prompt)-1] = '\0';
	      if(want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){
		  rv++;

		  if(plist == &v->main_user_val.l)
		    write_main++;
		  else
		    write_post++;

		  /*
		   * Clear the feature from the user's pinerc
		   * so that we'll stop bothering them when they
		   * start up Pine.
		   */
		  clear_feature(plist, p);

		  /*
		   * clear_feature scoots the list up, so if list[i] was
		   * the last one going in, now it is the end marker.  We
		   * just decrement i so that it will get incremented and
		   * then test == 0 in the for loop.  We could just goto
		   * outta_here to accomplish the same thing.
		   */
		  if(!list[i])
		    i--;
	      }
	    }
	  }
        }
      }
    }


    if(write_main)
      write_pinerc(ps, Main, WRP_NONE);
    if(write_post)
      write_pinerc(ps, Post, WRP_NONE);

    return;
}


/*
 * Adjust side effects that happen because variable changes values.
 *
 * Var->user_val should be set to the new value before calling this.
 */
void
fix_side_effects(struct pine *ps, struct variable *var, int revert)
{
    int    val = 0;
    char **v, *q, **apval;
    struct variable *vars = ps->vars;

    /* move this up here so we get the Using default message */
    if(var == &ps->vars[V_PERSONAL_NAME]){
	if(!(var->main_user_val.p ||
	     var->post_user_val.p) && ps->ui.fullname){
	    if(var->current_val.p)
	      fs_give((void **)&var->current_val.p);

	    var->current_val.p = cpystr(ps->ui.fullname);
	}
    }

    if(!revert
      && ((!var->is_fixed
	    && !var->is_list
	    && !(var->main_user_val.p ||
		 var->post_user_val.p)
	    && var->current_val.p)
	 ||
	 (!var->is_fixed
	    && var->is_list
	    && !(var->main_user_val.l ||
		 var->post_user_val.l)
	    && var->current_val.l)))
      q_status_message(SM_ORDER,0,3,_("Using default value"));

    if(var == &ps->vars[V_USER_DOMAIN]){
	char *p, *q;

	if(ps->VAR_USER_DOMAIN
	   && ps->VAR_USER_DOMAIN[0]
	   && (p = strrindex(ps->VAR_USER_DOMAIN, '@'))){
	    if(*(++p)){
		if(!revert)
		  q_status_message2(SM_ORDER, 3, 5,
		    _("User-Domain (%s) cannot contain \"@\"; using %s"),
		    ps->VAR_USER_DOMAIN, p);
		q = ps->VAR_USER_DOMAIN;
		while((*q++ = *p++) != '\0')
		  ;/* do nothing */
	    }
	    else{
		if(!revert)
		  q_status_message1(SM_ORDER, 3, 5,
		    _("User-domain (%s) cannot contain \"@\"; deleting"),
		    ps->VAR_USER_DOMAIN);

		if(ps->vars[V_USER_DOMAIN].post_user_val.p){
		    fs_give((void **)&ps->vars[V_USER_DOMAIN].post_user_val.p);
		    set_current_val(&vars[V_USER_DOMAIN], TRUE, TRUE);
		}

		if(ps->VAR_USER_DOMAIN
		   && ps->VAR_USER_DOMAIN[0]
		   && (p = strrindex(ps->VAR_USER_DOMAIN, '@'))){
		    if(ps->vars[V_USER_DOMAIN].main_user_val.p){
			fs_give((void **)&ps->vars[V_USER_DOMAIN].main_user_val.p);
			set_current_val(&vars[V_USER_DOMAIN], TRUE, TRUE);
		    }
		}
	    }
	}

	/*
	 * Reset various pointers pertaining to domain name and such...
	 */
	init_hostname(ps);
    }
    else if(var == &ps->vars[V_INBOX_PATH]){
	/*
	 * fixup the inbox path based on global/default values...
	 */
	init_inbox_mapping(ps->VAR_INBOX_PATH, ps->context_list);

	if(!strucmp(ps->cur_folder, ps->inbox_name) && ps->mail_stream
	   && strcmp(ps->VAR_INBOX_PATH, ps->mail_stream->mailbox)){
	    /*
	     * If we currently have "inbox" open and the mailbox name
	     * doesn't match, reset the current folder's name and
	     * remove the SP_INBOX flag.
	     */
	    strncpy(ps->cur_folder, ps->mail_stream->mailbox,
		    sizeof(ps->cur_folder)-1);
	    ps->cur_folder[sizeof(ps->cur_folder)-1] = '\0';
	    sp_set_fldr(ps->mail_stream, ps->cur_folder);
	    sp_unflag(ps->mail_stream, SP_INBOX);
	    ps->mangled_header = 1;
	}
	else if(sp_inbox_stream()
	    && strcmp(ps->VAR_INBOX_PATH, sp_inbox_stream()->original_mailbox)){
	    MAILSTREAM *m = sp_inbox_stream();

	    /*
	     * if we don't have inbox directly open, but have it
	     * open for new mail notification, close the stream like
	     * any other ordinary folder, and clean up...
	     */
	    if(m){
		sp_unflag(m, SP_PERMLOCKED | SP_INBOX);
		sp_set_fldr(m, m->mailbox);
		expunge_and_close(m, NULL, EC_NONE);
	    }
	}
    }
    else if(var == &ps->vars[V_INCCHECKTIMEO]){
	int old_value = ps->inc_check_timeout;

	if(SVAR_INC_CHECK_TIMEO(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else
	  ps->inc_check_timeout = old_value;

	if(!revert && (F_OFF(F_ENABLE_INCOMING, ps) || F_OFF(F_ENABLE_INCOMING_CHECKING, ps)))
	  q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
    }
    else if(var == &ps->vars[V_INCCHECKINTERVAL]){
	int old_value = ps->inc_check_interval;

	if(SVAR_INC_CHECK_INTERV(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else
	  ps->inc_check_interval = old_value;

	if(!revert && (F_OFF(F_ENABLE_INCOMING, ps) || F_OFF(F_ENABLE_INCOMING_CHECKING, ps)))
	  q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
    }
    else if(var == &ps->vars[V_INC2NDCHECKINTERVAL]){
	int old_value = ps->inc_second_check_interval;

	if(SVAR_INC_2NDCHECK_INTERV(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else
	  ps->inc_second_check_interval = old_value;

	if(!revert && (F_OFF(F_ENABLE_INCOMING, ps) || F_OFF(F_ENABLE_INCOMING_CHECKING, ps)))
	  q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
    }
    else if(var == &ps->vars[V_INCCHECKLIST]){
	if(ps->context_list && ps->context_list->use & CNTXT_INCMNG)
	  reinit_incoming_folder_list(ps, ps->context_list);

	if(!revert && (F_OFF(F_ENABLE_INCOMING, ps) || F_OFF(F_ENABLE_INCOMING_CHECKING, ps)))
	  q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
    }
    else if(var == &ps->vars[V_ADDRESSBOOK] ||
	    var == &ps->vars[V_GLOB_ADDRBOOK] ||
#ifdef	ENABLE_LDAP
	    var == &ps->vars[V_LDAP_SERVERS] ||
#endif
	    var == &ps->vars[V_ABOOK_FORMATS]){
	addrbook_reset();
    }
    else if(var == &ps->vars[V_INDEX_FORMAT]){
	reset_index_format();
	clear_index_cache(ps->mail_stream, 0);
    }
    else if(var == &ps->vars[V_DEFAULT_FCC] ||
	    var == &ps->vars[V_DEFAULT_SAVE_FOLDER]){
	init_save_defaults();
    }
    else if(var == &ps->vars[V_KW_BRACES] ||
	    var == &ps->vars[V_OPENING_SEP] ||
	    var == &ps->vars[V_ALT_ADDRS]){
	clear_index_cache(ps->mail_stream, 0);
    }
    else if(var == &ps->vars[V_KEYWORDS]){
	if(ps_global->keywords)
	  free_keyword_list(&ps_global->keywords);
	
	if(var->current_val.l && var->current_val.l[0])
	  ps_global->keywords = init_keyword_list(var->current_val.l);

	clear_index_cache(ps->mail_stream, 0);
    }
    else if(var == &ps->vars[V_INIT_CMD_LIST]){
	if(!revert)
	  q_status_message(SM_ASYNC, 0, 3,
	    _("Initial command changes will affect your next Alpine session."));
    }
    else if(var == &ps->vars[V_VIEW_HEADERS]){
	ps->view_all_except = 0;
	if(ps->VAR_VIEW_HEADERS)
	  for(v = ps->VAR_VIEW_HEADERS; (q = *v) != NULL; v++)
	    if(q[0]){
		char *p;

		removing_leading_white_space(q);
		/* look for colon or space or end */
		for(p = q; *p && !isspace((unsigned char)*p) && *p != ':'; p++)
		  ;/* do nothing */
		
		*p = '\0';
		if(strucmp(q, ALL_EXCEPT) == 0)
		  ps->view_all_except = 1;
	    }
    }
    else if(var == &ps->vars[V_OVERLAP]){
	int old_value = ps->viewer_overlap;

	if(SVAR_OVERLAP(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else
	  ps->viewer_overlap = old_value;
    }
#ifdef	SMIME
    else if(smime_related_var(ps, var)){
	smime_deinit();
    }
#endif	/* SMIME */
    else if(var == &ps->vars[V_MAXREMSTREAM]){
	int old_value = ps->s_pool.max_remstream;

	if(SVAR_MAXREMSTREAM(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert )
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else
	  ps->s_pool.max_remstream = old_value;

	dprint((9, "max_remstream goes to %d\n",
		ps->s_pool.max_remstream));
    }
#ifndef	_WINDOWS
    else if(var == &ps->vars[V_CHAR_SET]){
	char *err = NULL;

	if(F_ON(F_USE_SYSTEM_TRANS, ps)){
	    if(!revert)
	      q_status_message(SM_ORDER, 5, 5, _("This change has no effect because feature Use-System-Translation is on"));
	}
	else{
	    if(reset_character_set_stuff(&err) == -1)
	      alpine_panic(err ? err : "trouble with Character-Set");
	    else if(err){
		q_status_message(SM_ORDER | SM_DING, 3, 5, err);
		fs_give((void **) &err);
	    }
	}
    }
    else if(var == &ps->vars[V_KEY_CHAR_SET]){
	char *err = NULL;

	if(F_ON(F_USE_SYSTEM_TRANS, ps)){
	    if(!revert)
	      q_status_message(SM_ORDER, 5, 5, _("This change has no effect because feature Use-System-Translation is on"));
	}
	else{
	    if(reset_character_set_stuff(&err) == -1)
	      alpine_panic(err ? err : "trouble with Character-Set");
	    else if(err){
		q_status_message(SM_ORDER | SM_DING, 3, 5, err);
		fs_give((void **) &err);
	    }
	}
    }
#endif	/* ! _WINDOWS */
    else if(var == &ps->vars[V_POST_CHAR_SET]){
	update_posting_charset(ps, revert);
    }
    else if(var == &ps->vars[V_MARGIN]){
	int old_value = ps->scroll_margin;

	if(SVAR_MARGIN(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else
	  ps->scroll_margin = old_value;
    }
    else if(var == &ps->vars[V_DEADLETS]){
	int old_value = ps->deadlets;

	if(SVAR_DEADLETS(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else
	  ps->deadlets = old_value;
    }
    else if(var == &ps->vars[V_FILLCOL]){
	if(SVAR_FILLCOL(ps, ps->composer_fillcol, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
    }
    else if(var == &ps->vars[V_QUOTE_SUPPRESSION]){
	val = ps->quote_suppression_threshold;
	if(val < Q_SUPP_LIMIT && val > 0)
	  val = -val;

	if(ps->VAR_QUOTE_SUPPRESSION
	   && SVAR_QUOTE_SUPPRESSION(ps, val, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else{
	    if(val > 0 && val < Q_SUPP_LIMIT){
		if(!revert){
		    snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Ignoring Quote-Suppression-Threshold value of %s, see help"), ps->VAR_QUOTE_SUPPRESSION);
		    tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
		    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
		}
	    }
	    else{
		if(val < 0 && val != Q_DEL_ALL)
		  ps->quote_suppression_threshold = -val;
		else
		  ps->quote_suppression_threshold = val;
	    }
	}
    }
    else if(var == &ps->vars[V_STATUS_MSG_DELAY]){
	if(SVAR_MSGDLAY(ps, ps->status_msg_delay, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
    }
    else if(var == &ps->vars[V_ACTIVE_MSG_INTERVAL]){
	if(SVAR_ACTIVEINTERVAL(ps, ps->active_status_interval, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else{
	    busy_cue(_("Active Example"), NULL, 0);
	    sleep(5);
	    cancel_busy_cue(-1);
	}
    }
#if !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
    else if(var == &ps->vars[V_FIFOPATH]){
	init_newmailfifo(ps->VAR_FIFOPATH);
    }
#endif
    else if(var == &ps->vars[V_NMW_WIDTH]){
	int old_value = ps->nmw_width;

	if(SVAR_NMW_WIDTH(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert )
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else{
#ifdef _WINDOWS
	    if(old_value != ps->nmw_width)
	      mswin_setnewmailwidth(old_value);	/* actually the new value */
#endif
	    ps->nmw_width = old_value;
	}
    }
    else if(var == &ps->vars[V_TCPOPENTIMEO]){
	val = 30;
	if(!revert)
	  if(ps->VAR_TCPOPENTIMEO && SVAR_TCP_OPEN(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
	    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
    }
    else if(var == &ps->vars[V_TCPREADWARNTIMEO]){
	val = 15;
	if(!revert)
	  if(ps->VAR_TCPREADWARNTIMEO && SVAR_TCP_READWARN(ps,val,tmp_20k_buf, SIZEOF_20KBUF))
	    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
    }
    else if(var == &ps->vars[V_TCPWRITEWARNTIMEO]){
	val = 0;
	if(!revert)
	 if(ps->VAR_TCPWRITEWARNTIMEO && SVAR_TCP_WRITEWARN(ps,val,tmp_20k_buf, SIZEOF_20KBUF))
	    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
    }
    else if(var == &ps->vars[V_TCPQUERYTIMEO]){
	val = 60;
	if(!revert)
	  if(ps->VAR_TCPQUERYTIMEO && SVAR_TCP_QUERY(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
	    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
    }
    else if(var == &ps->vars[V_QUITQUERYTIMEO]){
	val = 0;
	if(!revert)
	  if(ps->VAR_QUITQUERYTIMEO && SVAR_QUIT_QUERY_TIMEO(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
	    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
    }
    else if(var == &ps->vars[V_RSHOPENTIMEO]){
	val = 15;
	if(!revert)
	  if(ps->VAR_RSHOPENTIMEO && SVAR_RSH_OPEN(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
	    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
    }
    else if(var == &ps->vars[V_SSHOPENTIMEO]){
	val = 15;
	if(!revert)
	  if(ps->VAR_SSHOPENTIMEO && SVAR_SSH_OPEN(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
	    q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
    }
    else if(var == &ps->vars[V_SIGNATURE_FILE]){
	if(ps->VAR_OPER_DIR && ps->VAR_SIGNATURE_FILE &&
	   is_absolute_path(ps->VAR_SIGNATURE_FILE) &&
	   !in_dir(ps->VAR_OPER_DIR, ps->VAR_SIGNATURE_FILE)){
	    char *e;
	    size_t l;

	    l = strlen(ps->VAR_OPER_DIR) + 100;
	    e = (char *) fs_get((l+1) * sizeof(char));
	    snprintf(e, l+1, _("Warning: Sig file can't be outside of %s"),
		    ps->VAR_OPER_DIR);
	    e[l] = '\0';
	    q_status_message(SM_ORDER, 3, 6, e);
	    fs_give((void **)&e);
	}
    }
    else if(var == &ps->vars[V_OPER_DIR]){
	if(ps->VAR_OPER_DIR && !ps->VAR_OPER_DIR[0]){
	    q_status_message(SM_ORDER, 3, 5, "Operating-dir is turned off.");
	    fs_give((void **)&ps->vars[V_OPER_DIR].current_val.p);
	    if(ps->vars[V_OPER_DIR].fixed_val.p)
	      fs_give((void **)&ps->vars[V_OPER_DIR].fixed_val.p);
	    if(ps->vars[V_OPER_DIR].global_val.p)
	      fs_give((void **)&ps->vars[V_OPER_DIR].global_val.p);
	    if(ps->vars[V_OPER_DIR].cmdline_val.p)
	      fs_give((void **)&ps->vars[V_OPER_DIR].cmdline_val.p);
	    if(ps->vars[V_OPER_DIR].post_user_val.p)
	      fs_give((void **)&ps->vars[V_OPER_DIR].post_user_val.p);
	    if(ps->vars[V_OPER_DIR].main_user_val.p)
	      fs_give((void **)&ps->vars[V_OPER_DIR].main_user_val.p);
	}
    }
    else if(var == &ps->vars[V_MAILCHECK]){
	int timeo = 15;
	if(SVAR_MAILCHK(ps, timeo, tmp_20k_buf, SIZEOF_20KBUF)){
	    set_input_timeout(15);
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else{
	    set_input_timeout(timeo);
	    if(get_input_timeout() == 0 && !revert){
		q_status_message(SM_ORDER, 4, 6,
    _("Warning: automatic new mail checking and mailbox checkpointing is disabled"));
		if(ps->VAR_INBOX_PATH && ps->VAR_INBOX_PATH[0] == '{')
		  q_status_message(SM_ASYNC, 3, 6,
    _("Warning: Mail-Check-Interval=0 may cause IMAP server connection to time out"));
	    }
	}
    }
    else if(var == &ps->vars[V_MAILCHECKNONCURR]){
	val = (int) ps->check_interval_for_noncurr;
	if(ps->VAR_MAILCHECKNONCURR
	   && SVAR_MAILCHKNONCURR(ps, val, tmp_20k_buf, SIZEOF_20KBUF)){
	    if(!revert)
	      q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	}
	else
	  ps->check_interval_for_noncurr = (time_t) val;
    }
    else if(var == &ps->vars[V_MAILDROPCHECK]){
	long rvl;

	rvl = 60L;
	if(ps->VAR_MAILDROPCHECK && SVAR_MAILDCHK(ps, rvl, tmp_20k_buf, SIZEOF_20KBUF))
	  q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	else{
	    if(rvl == 0L)
	      rvl = (60L * 60L * 24L * 100L);	/* 100 days */

	    if(rvl >= 60L)
	      mail_parameters(NULL, SET_SNARFINTERVAL, (void *) rvl);
	}
    }
    else if(var == &ps->vars[V_NNTPRANGE]){
	long rvl;

	rvl = 0L;
	if(ps->VAR_NNTPRANGE && SVAR_NNTPRANGE(ps, rvl, tmp_20k_buf, SIZEOF_20KBUF))
	  q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
	else{
	    if(rvl >= 0L)
	      mail_parameters(NULL, SET_NNTPRANGE, (void *) rvl);
	}
    }
    else if(var == &ps->vars[V_CUSTOM_HDRS] || var == &ps->vars[V_COMP_HDRS]){
	/* this will give warnings about headers that can't be changed */
	if(!revert && var->current_val.l && var->current_val.l[0])
	  customized_hdr_setup(NULL, var->current_val.l, UseAsDef);
    }
#if defined(DOS) || defined(OS2)
    else if(var == &ps->vars[V_FOLDER_EXTENSION]){
	mail_parameters(NULL, SET_EXTENSION,
			(void *)var->current_val.p);
    }
    else if(var == &ps->vars[V_NEWSRC_PATH]){
	if(var->current_val.p && var->current_val.p[0])
	  mail_parameters(NULL, SET_NEWSRC,
			  (void *)var->current_val.p);
    }
#endif
    else if(revert && standard_radio_var(ps, var)){

	cur_rule_value(var, TRUE, FALSE);
	if(var == &ps_global->vars[V_AB_SORT_RULE])
	  addrbook_redo_sorts();
	else if(var == &ps_global->vars[V_THREAD_INDEX_STYLE]){
	    clear_index_cache(ps_global->mail_stream, 0);
	    set_lflags(ps_global->mail_stream, ps_global->msgmap,
		       MN_COLL | MN_CHID, 0);
	    if(SORT_IS_THREADED(ps_global->msgmap)
	       && (SEP_THRDINDX() || COLL_THRDS()))
	      collapse_threads(ps_global->mail_stream, ps_global->msgmap, NULL);

	    adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
	}
#ifndef	_WINDOWS
	else if(var == &ps->vars[V_COLOR_STYLE]){
	    pico_toggle_color(0);
	    switch(ps->color_style){
	      case COL_NONE:
	      case COL_TERMDEF:
		pico_set_color_options(pico_trans_color() ? COLOR_TRANS_OPT : 0);
		break;
	      case COL_ANSI8:
		pico_set_color_options(COLOR_ANSI8_OPT|COLOR_TRANS_OPT);
		break;
	      case COL_ANSI16:
		pico_set_color_options(COLOR_ANSI16_OPT|COLOR_TRANS_OPT);
		break;
	      case COL_ANSI256:
		pico_set_color_options(COLOR_ANSI256_OPT|COLOR_TRANS_OPT);
		break;
	    }

	    if(ps->color_style != COL_NONE)
	      pico_toggle_color(1);
	
	    if(pico_usingcolor())
	      pico_set_normal_color();

	    clear_index_cache(ps_global->mail_stream, 0);
	    ClearScreen();
	    ps->mangled_screen = 1;
	}
#endif
    }
    else if(revert && var == &ps->vars[V_SORT_KEY]){
	int def_sort_rev;

	decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev);
	ps->def_sort_rev = def_sort_rev;
    }
    else if(var == &ps->vars[V_THREAD_MORE_CHAR] ||
            var == &ps->vars[V_THREAD_EXP_CHAR] ||
            var == &ps->vars[V_THREAD_LASTREPLY_CHAR]){

	if(var == &ps->vars[V_THREAD_LASTREPLY_CHAR] &&
	   !(var->current_val.p && var->current_val.p[0])){
	    if(var->current_val.p)
	      fs_give((void **) &var->current_val.p);
	    
	    q_status_message1(SM_ORDER, 3, 5,
		      _("\"%s\" can't be Empty, using default"), var->name);

	    apval = APVAL(var, ew);
	    if(*apval)
	      fs_give((void **)apval);

	    set_current_val(var, FALSE, FALSE);

	    if(!(var->current_val.p && var->current_val.p[0]
		 && !var->current_val.p[1])){
		if(var->current_val.p)
		  fs_give((void **) &var->current_val.p);
		
		var->current_val.p = cpystr(DF_THREAD_LASTREPLY_CHAR);
	    }
	}

	if(var == &ps->vars[V_THREAD_MORE_CHAR] ||
	   var == &ps->vars[V_THREAD_EXP_CHAR] ||
	   var == &ps->vars[V_THREAD_LASTREPLY_CHAR]){
	    if(var->current_val.p && var->current_val.p[0] &&
	       var->current_val.p[1]){
		q_status_message1(SM_ORDER, 3, 5,
			  "Only first character of \"%s\" is used",
			  var->name);
		var->current_val.p[1] = '\0';
	    }

	    if(var->main_user_val.p && var->main_user_val.p[0] &&
	       var->main_user_val.p[1])
	      var->main_user_val.p[1] = '\0';

	    if(var->post_user_val.p && var->post_user_val.p[0] &&
	       var->post_user_val.p[1])
	      var->post_user_val.p[1] = '\0';
	}

	clear_index_cache(ps_global->mail_stream, 0);
	set_need_format_setup(ps_global->mail_stream);
    }
    else if(var == &ps->vars[V_NNTP_SERVER]){
	    free_contexts(&ps_global->context_list);
	    init_folders(ps_global);
    }
    else if(var == &ps->vars[V_USE_ONLY_DOMAIN_NAME]){
	init_hostname(ps);
    }
    else if(var == &ps->vars[V_PRINTER]){
	if(!revert && ps->vars[V_PERSONAL_PRINT_COMMAND].is_fixed){
	    if(printer_value_check_and_adjust())
	      q_status_message1(SM_ORDER, 3, 5,
			        _("Can't set \"%s\" to that value, see Setup/Printer"),
			        pretty_var_name(var->name));
	}
    }
    else if(var == &ps->vars[V_NORM_FORE_COLOR] ||
	    var == &ps->vars[V_NORM_BACK_COLOR] ||
	    var == &ps->vars[V_REV_FORE_COLOR] ||
	    var == &ps->vars[V_REV_BACK_COLOR] ||
	    var == &ps->vars[V_TITLE_FORE_COLOR] ||
	    var == &ps->vars[V_TITLE_BACK_COLOR] ||
	    var == &ps->vars[V_TITLECLOSED_FORE_COLOR] ||
	    var == &ps->vars[V_TITLECLOSED_BACK_COLOR] ||
	    var == &ps->vars[V_STATUS_FORE_COLOR] ||
	    var == &ps->vars[V_STATUS_BACK_COLOR] ||
	    var == &ps->vars[V_KEYLABEL_FORE_COLOR] ||
	    var == &ps->vars[V_KEYLABEL_BACK_COLOR] ||
	    var == &ps->vars[V_KEYNAME_FORE_COLOR] ||
	    var == &ps->vars[V_KEYNAME_BACK_COLOR]){
	set_current_color_vals(ps);
	ClearScreen();
	ps->mangled_screen = 1;
    }
    else if(var == &ps->vars[V_KW_COLORS] ||
	    var == &ps->vars[V_INDEX_TOKEN_COLORS] ||
	    var == &ps->vars[V_IND_PLUS_FORE_COLOR] ||
	    var == &ps->vars[V_IND_IMP_FORE_COLOR]  ||
            var == &ps->vars[V_IND_DEL_FORE_COLOR]  ||
            var == &ps->vars[V_IND_ANS_FORE_COLOR]  ||
            var == &ps->vars[V_IND_NEW_FORE_COLOR]  ||
            var == &ps->vars[V_IND_UNS_FORE_COLOR]  ||
            var == &ps->vars[V_IND_HIPRI_FORE_COLOR]||
            var == &ps->vars[V_IND_LOPRI_FORE_COLOR]||
            var == &ps->vars[V_IND_ARR_FORE_COLOR]  ||
            var == &ps->vars[V_IND_REC_FORE_COLOR]  ||
            var == &ps->vars[V_IND_FWD_FORE_COLOR]  ||
	    var == &ps->vars[V_IND_OP_FORE_COLOR]   ||
	    var == &ps->vars[V_IND_FROM_FORE_COLOR] ||
	    var == &ps->vars[V_IND_SUBJ_FORE_COLOR] ||
            var == &ps->vars[V_IND_PLUS_BACK_COLOR] ||
            var == &ps->vars[V_IND_IMP_BACK_COLOR]  ||
            var == &ps->vars[V_IND_DEL_BACK_COLOR]  ||
            var == &ps->vars[V_IND_ANS_BACK_COLOR]  ||
            var == &ps->vars[V_IND_NEW_BACK_COLOR]  ||
            var == &ps->vars[V_IND_UNS_BACK_COLOR]  ||
            var == &ps->vars[V_IND_ARR_BACK_COLOR]  ||
            var == &ps->vars[V_IND_REC_BACK_COLOR]  ||
            var == &ps->vars[V_IND_FWD_BACK_COLOR]  ||
            var == &ps->vars[V_IND_OP_BACK_COLOR]   ||
            var == &ps->vars[V_IND_FROM_BACK_COLOR] ||
            var == &ps->vars[V_IND_SUBJ_BACK_COLOR]){
	clear_index_cache(ps_global->mail_stream, 0);
    }
    else if(var == score_act_global_ptr){
	int score;

	score = atoi(var->current_val.p);
	if(score < SCORE_MIN || score > SCORE_MAX){
	    q_status_message2(SM_ORDER, 3, 5,
			      _("Score Value must be in range %s to %s"),
			      comatose(SCORE_MIN), comatose(SCORE_MAX));
	    apval = APVAL(var, ew);
	    if(*apval)
	      fs_give((void **)apval);
	    
	    set_current_val(var, FALSE, FALSE);
	}
    }
    else if(var == scorei_pat_global_ptr || var == age_pat_global_ptr
	    || var == size_pat_global_ptr || var == cati_global_ptr){
	apval = APVAL(var, ew);
	if(*apval){
	    INTVL_S *iv;
	    iv = parse_intvl(*apval);
	    if(iv){
		fs_give((void **) apval);
		*apval = stringform_of_intvl(iv);
		free_intvl(&iv);
	    }
	    else
	      fs_give((void **) apval);
	}

	set_current_val(var, FALSE, FALSE);
    }
    else if(var == &ps->vars[V_FEATURE_LIST]){
	process_feature_list(ps, var->current_val.l, 0, 0, 0);
    }
    else if(var && var->name && !strcmp(var->name, XOAUTH2_FLOW)){
	if(var->current_val.p
	   && strucmp(var->current_val.p, "Authorize")
	   && strucmp(var->current_val.p, "Device")){
	      q_status_message1(SM_ORDER | SM_DING, 3, 3,
			        _("Invalid value \"%s\". Use \"Authorize\" or \"Device\""),
				var->current_val.p);
	}
    }
    else if(!revert && (var == &ps->vars[V_LAST_TIME_PRUNE_QUESTION] ||
		        var == &ps->vars[V_REMOTE_ABOOK_HISTORY] ||
		        var == &ps->vars[V_REMOTE_ABOOK_VALIDITY] ||
		        var == &ps->vars[V_USERINPUTTIMEO] ||
		        var == &ps->vars[V_NEWS_ACTIVE_PATH] ||
		        var == &ps->vars[V_NEWS_SPOOL_DIR] ||
		        var == &ps->vars[V_INCOMING_FOLDERS] ||
		        var == &ps->vars[V_FOLDER_SPEC] ||
		        var == &ps->vars[V_NEWS_SPEC] ||
		        var == &ps->vars[V_DISABLE_DRIVERS] ||
		        var == &ps->vars[V_DISABLE_AUTHS] ||
#ifdef DF_ENCRYPTION_RANGE
		        var == &ps->vars[V_ENCRYPTION_RANGE] ||
#endif /* DF_ENCRYPTION_RANGE */
#if !defined(_WINDOWS) || defined(ENABLE_WINDOWS_UNIXSSL_CERTS)
			var == &ps->vars[V_SSLCAPATH] ||
			var == &ps->vars[V_SSLCAFILE] ||
			var == &ps->vars[V_USERSSLCAPATH] ||
			var == &ps->vars[V_USERSSLCAFILE] ||
			var == &ps->vars[V_SSLCIPHERS] ||
#endif
		        var == &ps->vars[V_RSHPATH] ||
		        var == &ps->vars[V_RSHCMD] ||
		        var == &ps->vars[V_SSHCMD] ||
		        var == &ps->vars[V_SSHPATH])){
	q_status_message2(SM_ASYNC, 0, 3,
		      _("Changes%s%s will affect your next Alpine session."),
			  var->name ? " to " : "", var->name ? var->name : "");
    }

    if(!revert && (var == &ps->vars[V_TCPOPENTIMEO] ||
		   var == &ps->vars[V_TCPREADWARNTIMEO] ||
		   var == &ps->vars[V_TCPWRITEWARNTIMEO] ||
		   var == &ps->vars[V_TCPQUERYTIMEO] ||
		   var == &ps->vars[V_QUITQUERYTIMEO] ||
		   var == &ps->vars[V_RSHOPENTIMEO] ||
		   var == &ps->vars[V_SSHOPENTIMEO]))
      q_status_message(SM_ASYNC, 0, 3,
		       _("Timeout changes will affect your next Alpine session."));
}


/*
 * Compare saved user_val with current user_val to see if it changed.
 * If any have changed, change it back and take the appropriate action.
 */
void
revert_to_saved_config(struct pine *ps, SAVED_CONFIG_S *vsave, int allow_hard_to_config_remotely)
{
    struct variable *vreal;
    SAVED_CONFIG_S  *v;
    int i, n;
    int changed = 0;
    char *pval, **apval, **lval, ***alval;

    v = vsave;
    for(vreal = ps->vars; vreal->name; vreal++,v++){
	if(!save_include(ps, vreal, allow_hard_to_config_remotely))
	  continue;

	changed = 0;
	if(vreal->is_list){
	    lval  = LVAL(vreal, ew);
	    alval = ALVAL(vreal, ew);

	    if((v->saved_user_val.l && !lval)
	       || (!v->saved_user_val.l && lval))
	      changed++;
	    else if(!v->saved_user_val.l && !lval)
	      ;/* no change, nothing to do */
	    else
	      for(i = 0; v->saved_user_val.l[i] || lval[i]; i++)
		if((v->saved_user_val.l[i]
		      && (!lval[i]
			 || strcmp(v->saved_user_val.l[i], lval[i])))
		   ||
		     (!v->saved_user_val.l[i] && lval[i])){
		    changed++;
		    break;
		}
	    
	    if(changed){
		char  **list;

		if(alval){
		    if(*alval)
		      free_list_array(alval);
		
		    /* copy back the original one */
		    if(v->saved_user_val.l){
			list = v->saved_user_val.l;
			n = 0;
			/* count how many */
			while(list[n])
			  n++;

			*alval = (char **)fs_get((n+1) * sizeof(char *));

			for(i = 0; i < n; i++)
			  (*alval)[i] = cpystr(v->saved_user_val.l[i]);

			(*alval)[n] = NULL;
		    }
		}
	    }
	}
	else{
	    pval  = PVAL(vreal, ew);
	    apval = APVAL(vreal, ew);

	    if((v->saved_user_val.p &&
	        (!pval || strcmp(v->saved_user_val.p, pval))) ||
	       (!v->saved_user_val.p && pval)){
		/* It changed, fix it */
		changed++;
		if(apval){
		    /* free the changed value */
		    if(*apval)
		      fs_give((void **)apval);

		    if(v->saved_user_val.p)
		      *apval = cpystr(v->saved_user_val.p);
		}
	    }
	}

	if(changed){
	    if(vreal == &ps->vars[V_FEATURE_LIST])
	      set_feature_list_current_val(vreal);
	    else
	      set_current_val(vreal, TRUE, FALSE);

	    fix_side_effects(ps, vreal, 1);
	}
    }
}


SAVED_CONFIG_S *
save_config_vars(struct pine *ps, int allow_hard_to_config_remotely)
{
    struct variable *vreal;
    SAVED_CONFIG_S *vsave, *v;

    vsave = (SAVED_CONFIG_S *)fs_get((V_LAST_VAR+1)*sizeof(SAVED_CONFIG_S));
    memset((void *)vsave, 0, (V_LAST_VAR+1)*sizeof(SAVED_CONFIG_S));
    v = vsave;
    for(vreal = ps->vars; vreal->name; vreal++,v++){
	if(!save_include(ps, vreal, allow_hard_to_config_remotely))
	  continue;
	
	if(vreal->is_list){
	    int n, i;
	    char **list;

	    if(LVAL(vreal, ew)){
		/* count how many */
		n = 0;
		list = LVAL(vreal, ew);
		while(list[n])
		  n++;

		v->saved_user_val.l = (char **)fs_get((n+1) * sizeof(char *));
		memset((void *)v->saved_user_val.l, 0, (n+1)*sizeof(char *));
		for(i = 0; i < n; i++)
		  v->saved_user_val.l[i] = cpystr(list[i]);

		v->saved_user_val.l[n] = NULL;
	    }
	}
	else{
	    if(PVAL(vreal, ew))
	      v->saved_user_val.p = cpystr(PVAL(vreal, ew));
	}
    }

    return(vsave);
}


void
free_saved_config(struct pine *ps, SAVED_CONFIG_S **vsavep, int allow_hard_to_config_remotely)
{
    struct variable *vreal;
    SAVED_CONFIG_S  *v;

    if(vsavep && *vsavep){
	for(v = *vsavep, vreal = ps->vars; vreal->name; vreal++,v++){
	    if(!save_include(ps, vreal, allow_hard_to_config_remotely))
	      continue;
	    
	    if(vreal->is_list){  /* free saved_user_val.l */
		if(v && v->saved_user_val.l)
		  free_list_array(&v->saved_user_val.l);
	    }
	    else if(v && v->saved_user_val.p)
	      fs_give((void **)&v->saved_user_val.p);
	}

	fs_give((void **)vsavep);
    }
}


/*
 * Returns positive if any thing was actually deleted.
 */
int
delete_user_vals(struct variable *v)
{
    int rv = 0;

    if(v){
	if(v->is_list){
	    if(v->post_user_val.l){
		rv++;
		free_list_array(&v->post_user_val.l);
	    }
	    if(v->main_user_val.l){
		rv++;
		free_list_array(&v->main_user_val.l);
	    }
	}
	else{
	    if(v->post_user_val.p){
		rv++;
		fs_give((void **)&v->post_user_val.p);
	    }
	    if(v->main_user_val.p){
		rv++;
		fs_give((void **)&v->main_user_val.p);
	    }
	}
    }

    return(rv);
}


/*
 * ../pith/conf.c required function
 */
int
unexpected_pinerc_change(void)
{
    if(ps_global->ttyo) Writechar(BELL, 0);
    if(want_to("Unexpected pinerc change!  Overwrite with current config",
	       'n', 0, NO_HELP, WT_FLUSH_IN) == 'n'){
	return(-1);		/* abort pinerc write */
    }

    return(0);			/* overwrite */
}


#ifdef	_WINDOWS

/*----------------------------------------------------------------------
     MSWin scroll callback.  Called during scroll message processing.
	     


  Args: cmd - what type of scroll operation.
	scroll_pos - parameter for operation.  
			used as position for SCROLL_TO operation.

  Returns: TRUE - did the scroll operation.
	   FALSE - was not able to do the scroll operation.
 ----*/
int
config_scroll_callback (cmd, scroll_pos)
int	cmd;
long	scroll_pos;
{   
    switch (cmd) {
      case MSWIN_KEY_SCROLLUPLINE:
	config_scroll_down (scroll_pos);
	break;

      case MSWIN_KEY_SCROLLDOWNLINE:
	config_scroll_up (scroll_pos);
	break;

      case MSWIN_KEY_SCROLLUPPAGE:
	config_scroll_down (BODY_LINES(ps_global));
	break;

      case MSWIN_KEY_SCROLLDOWNPAGE:
	config_scroll_up (BODY_LINES(ps_global));
	break;

      case MSWIN_KEY_SCROLLTO:
	config_scroll_to_pos (scroll_pos);
	break;
    }

    option_screen_redrawer();
    fflush(stdout);

    return(TRUE);
}

#endif	/* _WINDOWS */
