#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: addrbook.c,v 4.149 1994/10/07 17:40:36 hubert Exp $";
#endif
/*----------------------------------------------------------------------

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

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

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

   Copyright 1989-1994  University of Washington

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

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

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

/*====================================================================== 
    addrbook.c
    display, browse and edit the address book.

    Support routines are in adrbklib.c.

The policy for changing the address book is to write it immediately 
after the change is made, so there is no idea of having to save the 
address book.
 ====*/


#include "headers.h"
#include "adrbklib.h"


/*
 * We could make every use of an AdrBk_Entry go through a function call
 * like adrbk_get_ae().  Instead,
 * we try to be smart and avoid the extra function calls by knowing when
 * the addrbook entry (often a variable named abe) is still valid, either
 * because we haven't called any functions that could invalidate it or because
 * we have locked it in the cache.  If we do lock it, we need to be careful
 * that it eventually gets unlocked.  That can be done by an explicit
 * adrbk_get_ae(Unlock) call, or it is done implicitly when the address book
 * is written out.  The reason it can get invalidated is that the abe that
 * we get returned to us is just a pointer to a cached addrbook entry, and
 * that entry can be flushed from the cache by other addrbook activity.
 * So we need to be careful to make sure the abe is certain to be valid
 * before using it.
 *
 * Data structures for the display of the address book.  There's one
 * of these structures per line on the screen.
 *
 * Types: Title -- The title line for the different address books.  It has
 *		   a ptr to the text of the Title line.
 *    ClickHere -- This is the line that says to click here to
 *                 expand.  It changes types into the individual expanded
 *                 components once it is expanded.  It doesn't have any data
 *                 other than an implicit title.
 *        Empty -- Line that says this is an empty addressbook.  No data.
 *       Simple -- A single addressbook entry.  It has a ptr to an AdrBk_Entry.
 *                 When it is displayed, the fields are:
 *                 <nickname>       <fullname>       <address or another nic>
 *     ListHead -- The head of an address list.  This has a ptr to an
 *		   AdrBk_Entry.
 *                 <blank line> followed by
 *                 <nickname>       <fullname>       "DISTRIBUTION LIST:"
 *      ListEnt -- The rest of an address list.  It has a pointer to its
 *		   ListHead element and a ptr (other) to this specific address
 *		   (not a ptr to another AdrBk_Entry).
 *                 <blank>          <blank>          <address or another nic>
 *         Text -- A ptr to text.  For example, the ----- lines and
 *		   whitespace lines.
 *   Beginnning -- The (imaginary) elements before the first real element
 *          End -- The (imaginary) elements after the last real element
 */
typedef enum {ClickHere, Empty, Title, Simple, ListHead, ListEnt,
						Text, Beginning, End} LineType;
/* each line is one of these structures */
typedef struct addrscrn_disp {
    LineType       type;
    union {
        struct {
            adrbk_cntr_t  ab_element_number; /* which addrbook entry     */
	    adrbk_cntr_t  ab_list_offset;    /* which member of the list */
        }addrbook_entry;
        char        *text_ptr;
    }union_to_save_space;
} AddrScrn_Disp;
#define txt union_to_save_space.text_ptr
#define elnum union_to_save_space.addrbook_entry.ab_element_number
#define l_offset union_to_save_space.addrbook_entry.ab_list_offset

typedef enum {DlcTitleBlankTop,
	      DlcTitleDashTop,
	      DlcTitle,
	      DlcTitleDashBottom,
	      DlcTitleBlankBottom,
	      DlcClickHere,
	      DlcEmpty,
	      DlcNoPermission,
	      DlcSimple,
	      DlcListHead,
	      DlcListEnt,
	      DlcListBlankTop,
	      DlcListBlankBottom,
	      DlcNotSet,
	      DlcBeginning,
	      DlcOneBeforeBeginning,
	      DlcTwoBeforeBeginning,
	      DlcEnd} DlCacheType;

typedef enum {Initialize, FirstEntry, LastEntry, ArbitraryStartingPoint,
	      DoneWithCache, FlushDlcFromCache, Lookup} DlMgrOps;
typedef enum {Warp, DontWarp} HyperType;
/*
 * The DlCacheTypes are the types that a dlcache element can be labeled.
 * The idea is that there needs to be enough information in the single
 * cache element by itself so that you can figure out what the next and
 * previous dl rows are by just knowing this one row.
 *
 * If there is only one addrbook, there are no DlcTitle* lines, and no
 * DlcClickHere lines.  The first line will be a DlcListHead or a DlcSimple.
 * If more than one addrbook, each has a title.  All but the first have
 * a DlcTitleBlankTop blank line before the title, and all of them have
 * the other 4 types of DlcTitle type lines.  An unexpanded book is a
 * DlcClickHere, an Empty book is a DlcEmpty.  Each DlcSimple is just a
 * single line.  Each list is a DlcListHead and some number of DlcListEnts
 * if there are any list members.  Two consecutive lists are
 * separated by a DlcListBlankBottom belonging to the first list.  A list
 * followed or preceded by a DlcSimple address row has a
 * DlcListBlank(Top or Bottom) separating it from the DlcSimple.  A DlcTitle
 * and a list are separated by a DlcTitleBlank*, not a DlcListBlank*.
 * Each cache element has an adrbk number associated with it.  All of the
 * DlcTitle* rows, including the DlcTitleBlankTop, go with the adrbk they
 * are the title for.  DlcListBlankTop's have the elnum of the list
 * they're above.  DlcListBlankBottom's have the elnum of the list
 * they're below.  Above the top row of the display list is a type
 * DlcBeginning row.  Below the bottom row of the display list is a
 * type DlcEnd row.
 */
typedef struct dl_cache {
    long         global_row; /* disp_list row number */
    DlCacheType  type;       /* type of this row */
    short        adrbk_num;  /* which address book we're related to */
    adrbk_cntr_t dlcelnum;   /* which elnum from that addrbook */
    adrbk_cntr_t dlcoffset;  /* offset in a list, only for ListEnt rows */
    AddrScrn_Disp dl;	     /* the actual dl that goes with this row */
} DL_CACHE_S;

typedef enum {LocalPersonal, LocalGlobal} AddrBookType;
typedef enum {TotallyClosed, /* hash tables not even set up yet               */
	      Closed,     /* data not read in, no display list                */
	      NoDisplay,  /* data is accessible, no display list              */
	      HalfOpen,   /* data not accessible, initial display list is set */
	      Open        /* data is accessible and display list is set       */
	     } OpenStatus;
/*
 * There is one of these per addressbook.
 */
typedef struct peraddrbook {
    AddrBookType        type;
    AccessType          access;
    OpenStatus          ostatus;
    char               *nickname,
		       *filename;
    AdrBk              *address_book; /* the address book handle             */
    int                 gave_parse_warnings;
} PerAddrBook;

/*
 * Just one of these.  This keeps track of the current state of
 * the screen and which addressbook we're looking at.  It is really just
 * global data (accessed only from this file).  It's all in one structure so
 * that it's easier to recognize as global.
 */
typedef struct addrscreenstate {
    PerAddrBook   *adrbks;       /* array of addrbooks                    */
    int		   initialized,  /* have we done at least simple init?    */
                   n_addrbk,     /* how many addrbooks are there          */
                   how_many_personals, /* how many of those are personal? */
                   cur,          /* current addrbook                      */
                   cur_row,      /* currently selected line               */
                   old_cur_row,  /* previously selected line              */
                   l_p_page;	 /* lines per (screen) page               */
    long           top_ent;      /* index in disp_list of top entry on screen */
    int            ro_warning;   /* whether or not to give warning        */
    int            no_op_possbl; /* user can't do anything with current conf */
} AddrScrState;

static AddrScrState as;

typedef enum {AddrBookScreen, SelectAddr, SelectNick} AddrBookArg;

typedef struct save_state_struct {
    AddrScrState *savep;
    OpenStatus   *stp;
    DL_CACHE_S   *dlc_to_warp_to;
} SAVE_STATE_S;

/*
 * Information used to paint and maintain a line on the TakeAddr screen
 */
typedef struct takeaddr_line {
    int	                  checked;  /* addr is selected                     */
    int	                  skip_it;  /* skip this one                        */
    char                 *strvalue; /* alloc'd value string                 */
    ADDRESS              *addr;     /* original ADDRESS this line came from */
    struct takeaddr_line *next, *prev;
} TA_S;

typedef enum {ListMode, SingleMode} TakeAddrScreenMode;

typedef struct takeaddress_screen {
    TakeAddrScreenMode mode;
    TA_S              *current,
                      *top_line;
} TA_SCREEN_S;

static TA_SCREEN_S *ta_screen;

/*
 * Jump back to this location if we discover that one of the open addrbooks
 * has been changed by some other process.
 *
 * The trouble_filename variable is usually NULL (no trouble) but may be
 * set if adrbklib detected trouble in an addrbook.lu file.  In that case,
 * trouble_filename will be set to the name of the addressbook
 * with the problem.  It is used to force a rebuild of the .lu file.
 */
jmp_buf addrbook_changed_unexpectedly;
char   *trouble_filename;


#ifdef ANSI
void           ab_compose_to_addr(void);
void           ab_goto_folder(int);
void           ab_print(void);
void           ab_resize(int);
long           ab_whereis(int *, int);
void           add_abook_entry(ADDRESS *, int);
char          *addr_book(AddrBookArg);
int            addr_book_delete(AdrBk *, int, long);
char          *addr_book_takeaddr(void);
char          *addr_lookup(char *, int *);
AdrBk_Entry   *addr_to_abe(ADDRESS *);
long           addr_to_list(AdrBk *, int, long);
int            adrbk_access(char *, int);
AdrBk_Entry   *adrbk_lookup_with_opens_by_nick(char *, int, int *);
int            adrbk_num_from_lineno(long);
AdrBk_Entry   *ae(long);
int            any_addrs_avail(long);
int            build_address_internal(char *, char **, char **, char **, int);
int            calculate_field_widths(int *);
void           cancel_warning(int, char *);
int            change_address(AdrBk *, long, adrbk_cntr_t *, int *, char **,
									int);
int            change_comment(AdrBk *, long, adrbk_cntr_t *, int *, int);
int            change_fcc(AdrBk *, long, adrbk_cntr_t *, int *, int);
int            change_fullname(AdrBk *, long, adrbk_cntr_t *, int *, int);
int            change_nickname(AdrBk *, long, adrbk_cntr_t *, int *, int);
PerAddrBook   *check_for_addrbook(char *);
void           create_abook_entry(char *, char *, char *, int);
void           create_abook_entry_frontend(char *, char *, char *, int);
void           create_abook_list(char **, int, int);
void           create_abook_list_frontend(char **, int, int);
long           create_list(AdrBk *, int, int *);
int            cur_addr_book(void);
DL_CACHE_S    *dlc_from_listmem(AdrBk *, a_c_arg_t, char *);
DL_CACHE_S    *dlc_next(DL_CACHE_S *, DL_CACHE_S *);
DL_CACHE_S    *dlc_prev(DL_CACHE_S *, DL_CACHE_S *);
AddrScrn_Disp *dlist(long);
DL_CACHE_S    *dlc_mgr(long, DlMgrOps, DL_CACHE_S *);
int            dlcs_from_same_abe(DL_CACHE_S *, DL_CACHE_S *);
void           display_book(int, int, int, int, int *);
long           do_edit_cmd(int, AdrBk *, int, long, int *);
void           done_with_dlc_cache(void);
int            dup_addrs(ADDRESS *, ADDRESS *);
void           dump_a_dlc_to_debug(char *, DL_CACHE_S *);
void           dump_some_debugging(char *);
int            edit_address(int, int, char *, char *, HelpType);
int            edit_comment(int, char *, char *, HelpType);
long           edit_a_field(AdrBk *, int, long, int *);
int            edit_fcc(int, char *, char *, HelpType);
int            edit_fullname(int, char *, char *, HelpType);
int            edit_nickname(AdrBk *, AddrScrn_Disp *, int, char *,
						char *, HelpType, int, int);
int            eliminate_dups_and_us(TA_S *);
void           empty_warning(void);
void           end_adrbks(void);
int            entry_is_clickable(long);
int            est_size(ADDRESS *);
ADDRESS       *expand_address(char *, char *, char *, int *, char **, char **,
						int);
void           fill_in_dl_field(DL_CACHE_S *);
TA_S          *fill_in_ta(TA_S **, ADDRESS *, int);
int            find_in_book(long, char *, long *, int *);
TA_S          *first_checked(TA_S *);
long           first_line(long);
long           first_selectable_line(long);
TA_S          *first_taline(TA_S *);
void           flush_dlc_from_cache(DL_CACHE_S *);
void           free_cache_array(DL_CACHE_S **, int);
void           free_list_of_checked(char ***);
void           free_taline(TA_S **);
int            funny_compare_dlcs(DL_CACHE_S *, DL_CACHE_S *);
DL_CACHE_S    *get_bottom_dl_of_adrbk(int, DL_CACHE_S *);
DL_CACHE_S    *get_dlc(long);
DL_CACHE_S    *get_first_dl_of_adrbk(int, DL_CACHE_S *);
DL_CACHE_S    *get_global_bottom_dlc(DL_CACHE_S *);
DL_CACHE_S    *get_global_top_dlc(DL_CACHE_S *);
int            illegal_chars(char *);
void           init_ab_if_needed(void);
int            init_addrbooks(OpenStatus, int, int, int);
void           init_abook(PerAddrBook *, OpenStatus);
void           initialize_dlc_cache(void);
int            is_addr(long);
int            is_empty(long);
int            is_talist_of_one(TA_S *);
int            line_is_selectable(long);
char         **list_of_checked(TA_S *);
char          *listmem(long);
adrbk_cntr_t   listmem_count_from_abe(AdrBk_Entry *);
char          *listmem_from_dl(AdrBk *, AddrScrn_Disp *);
int            matching_dlcs(DL_CACHE_S *, DL_CACHE_S *);
TA_S          *new_taline(TA_S **);
int            next_selectable_line(long, long *);
TA_S          *next_taline(TA_S *);
int            nickname_check(char *);
void           no_tabs_warning(void);
int            our_build_address(char *, char **, char **, char **, int);
void           paint_line(int, long, int, int *);
int            prev_selectable_line(long, long *);
TA_S          *prev_taline(TA_S *);
void           readonly_warning(int, char *);
void           redraw_addr_screen(void);
void           restore_state(SAVE_STATE_S *);
void           save_state(SAVE_STATE_S *);
int            search_book(long, int, long *, int *, int *);
int            search_in_one_line(AddrScrn_Disp *, AdrBk_Entry *, char *,
								char *);
PerAddrBook   *setup_for_addrbook_add(SAVE_STATE_S *, int);
long           simple_add(AdrBk *, int, int *);
int            ta_take_marked_addrs(int, TA_S *, int);
int            ta_take_single_addr(TA_S *, int);
int            ta_mark_all(TA_S *);
int            ta_unmark_all(TA_S *);
void           takeaddr_screen(struct pine *, TA_S *, int, TakeAddrScreenMode);
void           takeaddr_screen_redrawer_list(void);
void           takeaddr_screen_redrawer_single(void);
int            update_takeaddr_screen(struct pine *, TA_S *, TA_SCREEN_S *);
PerAddrBook   *use_this_addrbook(int);
int            warn_bad_addr(char *, int);
void           warp_to_dlc(DL_CACHE_S *, long);
void           warp_to_beginning(void);
void           warp_to_end(void);

#else  /* !ANSI */

void           ab_compose_to_addr();
void           ab_goto_folder();
void           ab_print();
void           ab_resize();
long           ab_whereis();
void           add_abook_entry();
char          *addr_book();
int            addr_book_delete();
char          *addr_book_takeaddr();
char          *addr_lookup();
AdrBk_Entry   *addr_to_abe();
long           addr_to_list();
int            adrbk_access();
AdrBk_Entry   *adrbk_lookup_with_opens_by_nick();
int            adrbk_num_from_lineno();
AdrBk_Entry   *ae();
int            any_addrs_avail();
int            build_address_internal();
int            calculate_field_widths();
void           cancel_warning();
int            change_address();
int            change_comment();
int            change_fcc();
int            change_fullname();
int            change_nickname();
PerAddrBook   *check_for_addrbook();
void           create_abook_entry();
void           create_abook_entry_frontend();
void           create_abook_list();
void           create_abook_list_frontend();
long           create_list();
int            cur_addr_book();
DL_CACHE_S    *dlc_from_listmem();
DL_CACHE_S    *dlc_next();
DL_CACHE_S    *dlc_prev();
AddrScrn_Disp *dlist();
DL_CACHE_S    *dlc_mgr();
int            dlcs_from_same_abe();
void           display_book();
long           do_edit_cmd();
void           done_with_dlc_cache();
int            dup_addrs();
void           dump_a_dlc_to_debug();
void           dump_some_debugging();
int            edit_address();
int            edit_comment();
long           edit_a_field();
int            edit_fcc();
int            edit_fullname();
int            edit_nickname();
int            eliminate_dups_and_us();
void           empty_warning();
void           end_adrbks();
int            entry_is_clickable();
int            est_size();
ADDRESS       *expand_address();
void           fill_in_dl_field();
TA_S          *fill_in_ta();
int            find_in_book();
TA_S          *first_checked();
long           first_line();
long           first_selectable_line();
TA_S          *first_taline();
void           flush_dlc_from_cache();
void           free_cache_array();
void           free_list_of_checked();
void           free_taline();
int            funny_compare_dlcs();
DL_CACHE_S    *get_bottom_dl_of_adrbk();
DL_CACHE_S    *get_dlc();
DL_CACHE_S    *get_first_dl_of_adrbk();
DL_CACHE_S    *get_global_bottom_dlc();
DL_CACHE_S    *get_global_top_dlc();
int            illegal_chars();
void           init_ab_if_needed();
int            init_addrbooks();
void           init_abook();
void           initialize_dlc_cache();
int            is_addr();
int            is_empty();
int            is_talist_of_one();
int            line_is_selectable();
char         **list_of_checked();
char          *listmem();
adrbk_cntr_t   listmem_count_from_abe();
char          *listmem_from_dl();
int            matching_dlcs();
TA_S          *new_taline();
int            next_selectable_line();
TA_S          *next_taline();
int            nickname_check();
void           no_tabs_warning();
int            our_build_address();
void           paint_line();
int            prev_selectable_line();
TA_S          *prev_taline();
void           readonly_warning();
void           redraw_addr_screen();
void           restore_state();
void           save_state();
int            search_book();
int            search_in_one_line();
PerAddrBook   *setup_for_addrbook_add();
long           simple_add();
int            ta_take_marked_addrs();
int            ta_take_single_addr();
int            ta_mark_all();
int            ta_unmark_all();
void           takeaddr_screen();
void           takeaddr_screen_redrawer_list();
void           takeaddr_screen_redrawer_single();
int            update_takeaddr_screen();
PerAddrBook   *use_this_addrbook();
int            warn_bad_addr();
void           warp_to_dlc();
void           warp_to_beginning();
void           warp_to_end();

#endif /* !ANSI */


/*
 * HEADER_LINES is the number of lines in the header
 * FOOTER_LINES is the number of lines in the footer
 * That is, we don't get to write in the HEADER_LINES lines at the top or the
 * FOOTER_LINES lines at the bottom.
 */
#define HEADER_LINES   2
#define FOOTER_LINES   3

#define CLICKHERE       "[ Select Here to See Expanded List ]"
#define NO_PERMISSION   "[ Permission Denied - No Read Access to File ]"
#define EMPTY           "[ Empty ]"
#define READONLY        "(ReadOnly)"
#define NOACCESS        "(Un-readable)"
#define DISTLIST        "DISTRIBUTION LIST:"

#define MAX_FCC     MAX_ADDRESS
#define MAX_COMMENT (10000)

#define DING    1
#define NO_DING 0

/*
 * These constants are supposed to be suitable for use as longs where the longs
 * are representing a line number or message number, since you can only have
 * about 65000 lines or messages at most (or about 2 billion in HUGE mode).
 * These constants aren't suitable for use with type adrbk_cntr_t.  There is
 * a constant called NO_NEXT in adrbklib.h which you probably want for that.
 */
#define NO_LINE         (2147483645L)
#define CHANGED_CURRENT (NO_LINE + 1L)


/*
 * Make sure addrbooks are minimally initialized.
 */
void
init_ab_if_needed()
{
    dprint(9, (debugfile, "- init_ab_if_needed -\n"));

    if(!as.initialized)
      (void)init_addrbooks(Closed, 0, 0, 1);
}


/*
 * Sets everything up to get started.
 *
 * Args: want_status      -- The desired OpenStatus for all addrbooks.
 *       reset_to_top_or_bot -- Forget about the old location and put cursor
 *                           at top.  If value is 1, reset to top, if value
 *                           is -1, reset to bottom, else, don't reset.
 *       open_if_only_one -- If want_status is HalfOpen and there is only
 *                           one addressbook, then promote want_status to Open
 *       ro_warning       -- Issue ReadOnly warning if set, also sets global
 *
 * Return: number of addrbooks.
 */
int
init_addrbooks(want_status, reset_to_top_or_bot, open_if_only_one, ro_warning)
OpenStatus want_status;
int        reset_to_top_or_bot,
           open_if_only_one,
	   ro_warning;
{
    register PerAddrBook *pab;
    char *q, **t;
    long line;

    dprint(2, (debugfile, "-- init_addrbooks(%s, %d, %d, %d) --\n",
		    want_status==Open ?
				"Open" :
				want_status==HalfOpen ?
					"HalfOpen" :
					want_status==NoDisplay ?
						"NoDisplay" :
						"Closed",
		    reset_to_top_or_bot, open_if_only_one, ro_warning));

    as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_LINES - HEADER_LINES;

    /* already been initialized */
    if(as.n_addrbk){
	int i;

	as.ro_warning = ro_warning;

	/*
	 * Special case.  If there is only one addressbook we start the
	 * user out with that open, just like we did when there was always
	 * only one addressbook.
	 *
	 * Also start out open if expanded-view-of-addrbooks feature is set.
	 */
	if(want_status == HalfOpen &&
	   ((open_if_only_one && as.n_addrbk == 1) ||
	    F_ON(F_EXPANDED_ADDRBOOKS, ps_global)))
	    want_status = Open;

	/* open to correct state */
	for(i = 0; i < as.n_addrbk; i++)
	  init_abook(&as.adrbks[i], want_status);

	if(reset_to_top_or_bot == 1)
	  warp_to_beginning();
	else if(reset_to_top_or_bot == -1)
	  warp_to_end();

	if(reset_to_top_or_bot == 1){
	    as.top_ent     = 0L;
	    line           = first_selectable_line(0L);
	    if(line == NO_LINE)
	      as.cur_row = 0L;
	    else
	      as.cur_row = line;
	    if(as.cur_row >= as.l_p_page)
	      as.top_ent += (as.cur_row - as.l_p_page + 1);
	    as.old_cur_row = as.cur_row;
	}
	else if(reset_to_top_or_bot == -1){
	    as.top_ent     = first_line(0L - (long)as.l_p_page/2L);
	    line           = first_selectable_line(as.top_ent);
	    if(line == NO_LINE)
	      as.cur_row = 0L;
	    else
	      as.cur_row = line - as.top_ent;
	    if(as.cur_row >= as.l_p_page)
	      as.top_ent += (as.cur_row - as.l_p_page + 1);
	    as.old_cur_row = as.cur_row;
	}

	dprint(9, (debugfile, "init_addrbooks: already initialized: %d books\n",
				    as.n_addrbk));
        return(as.n_addrbk);
    }

    /* there are no addrbooks */
    if(as.initialized && !as.n_addrbk)
      return 0;

    as.initialized = 1;

    as.ro_warning = ro_warning;
    as.no_op_possbl = 0;


    if((!ps_global->VAR_GLOB_ADDRBOOK ||
        !ps_global->VAR_GLOB_ADDRBOOK[0] ||
        !ps_global->VAR_GLOB_ADDRBOOK[0][0]) &&
       (!ps_global->VAR_ADDRESSBOOK ||
        !ps_global->VAR_ADDRESSBOOK[0] ||
        !ps_global->VAR_ADDRESSBOOK[0][0]))
	return 0;
    

    /* count addressbooks */
    as.how_many_personals = 0;
    if(ps_global->VAR_ADDRESSBOOK &&
       ps_global->VAR_ADDRESSBOOK[0] &&
       ps_global->VAR_ADDRESSBOOK[0][0])
	for(t = ps_global->VAR_ADDRESSBOOK; *t != NULL; t++)
	  as.how_many_personals++;
    as.n_addrbk = as.how_many_personals;
    if(ps_global->VAR_GLOB_ADDRBOOK &&
       ps_global->VAR_GLOB_ADDRBOOK[0] &&
       ps_global->VAR_GLOB_ADDRBOOK[0][0])
	for(t = ps_global->VAR_GLOB_ADDRBOOK; *t != NULL; t++)
	  as.n_addrbk++;

    if(want_status == HalfOpen &&
       ((open_if_only_one && as.n_addrbk == 1) ||
	F_ON(F_EXPANDED_ADDRBOOKS, ps_global)))
	want_status = Open;

    as.cur      = 0;
    as.top_ent = 0L;
    as.cur_row = 0L;

    /*
     * allocate array of PerAddrBooks
     * (we don't give this up until we exit Pine, but it's small)
     */
    as.adrbks       = (PerAddrBook *)fs_get(as.n_addrbk * sizeof(PerAddrBook));
    memset((void *)as.adrbks, 0, as.n_addrbk * sizeof(PerAddrBook));

    /* init PerAddrBook data */
    for(as.cur = 0; as.cur < as.n_addrbk; as.cur++){
	char *nickname = NULL,
	     *filename = NULL;
#ifdef DOS
	char book_path[MAXPATH+1];
#endif /* DOS */

	if(as.cur < as.how_many_personals)
	  q = ps_global->VAR_ADDRESSBOOK[as.cur];
	else
	  q = ps_global->VAR_GLOB_ADDRBOOK[as.cur - as.how_many_personals];

	pab = &as.adrbks[as.cur];
	
	/* Parse entry for optional nickname and filename */
	get_pair(q, &nickname, &filename);

        strcpy(tmp_20k_buf, filename);
	fs_give((void **)&filename);

        filename = tmp_20k_buf;
	if(nickname == NULL)
	  pab->nickname = cpystr(filename);
	else
	  pab->nickname = nickname;

	if(*filename == '~')
	  fnexpand(filename, 20000);

#ifdef DOS
	if(filename[0] == '\\' ||
	   (isalpha(filename[0]) && filename[1] == ':')){
	    strcpy(book_path, filename);
	}
	else{
	    int lc = last_cmpnt(ps_global->pinerc) - ps_global->pinerc;

	    strncpy(book_path, ps_global->pinerc, lc);
	    book_path[lc] = '\0';
	    strcat(book_path, filename);
	}

	pab->filename = cpystr(book_path);
#else /* !DOS */
	pab->filename = cpystr(filename);
#endif /* !DOS */


	if(as.cur < as.how_many_personals)
	  pab->type  = LocalPersonal;
	else
	  pab->type  = LocalGlobal;

	if(adrbk_access(pab->filename, ACCESS_EXISTS) == 0){
	    if(adrbk_access(pab->filename, EDIT_ACCESS) == 0)
	      pab->access = ReadWrite;
	    else if(adrbk_access(pab->filename, READ_ACCESS) == 0)
	      pab->access = ReadOnly;
	    else
	      pab->access = NoAccess;
	}
	else
	  pab->access = NoExists;

	/* global address books are forced readonly */
	if(as.cur >= as.how_many_personals && pab->access == ReadWrite)
	  pab->access = ReadOnly;

	pab->ostatus  = TotallyClosed;

	if(as.ro_warning &&
	    open_if_only_one &&
	    as.n_addrbk == 1 &&
	    want_status == Open){

	    if(pab->access == ReadOnly)
	      readonly_warning(NO_DING, NULL);
	    else if(pab->access == NoAccess)
	      q_status_message(0, 1, 3,
		    "AddressBook not accessible, permission denied");
	}

	/*
	 * and remember that the memset above initializes everything
	 * else to 0
	 */

	init_abook(pab, want_status);
    }

    /*
     * Have to reset_to_top in this case since this is the first open,
     * regardless of the value of the argument, since these values haven't been
     * set before here.
     */
    warp_to_beginning();
    as.top_ent     = 0L;
    line           = first_selectable_line(0L);
    if(line == NO_LINE)
      as.cur_row = 0L;
    else
      as.cur_row = line;
    if(as.cur_row >= as.l_p_page)
      as.top_ent += (as.cur_row - as.l_p_page + 1);
    as.old_cur_row = as.cur_row;

    return(as.n_addrbk);
}


/*
 * Might help a little to debug problems.
 */
void
dump_some_debugging(message)
char *message;
{
#ifdef DEBUG
    dprint(2, (debugfile, "- dump_some_debugging(%s) -\n", message));
    dprint(2, (debugfile, "initialized %d n_addrbk %d cur_row %d\n",
	as.initialized, as.n_addrbk, as.cur_row));
    dprint(2, (debugfile, "top_ent %ld ro_warning %d no_op_possbl %d\n",
	as.top_ent, as.ro_warning, as.no_op_possbl));
#endif /* DEBUG */
}


void
dump_a_dlc_to_debug(message, dlc)
char *message;
DL_CACHE_S *dlc;
{
#ifdef DEBUG
    char type[20];

    switch(dlc->type){
      case DlcTitleBlankTop:
	(void)strcpy(type, "TitleBlankTop");
	break;
      case DlcTitleDashTop:
	(void)strcpy(type, "TitleDashTop");
	break;
      case DlcTitle:
	(void)strcpy(type, "Title");
	break;
      case DlcTitleDashBottom:
	(void)strcpy(type, "TitleDashBottom");
	break;
      case DlcTitleBlankBottom:
	(void)strcpy(type, "TitleBlankBottom");
	break;
      case DlcClickHere:
	(void)strcpy(type, "ClickHere");
	break;
      case DlcEmpty:
	(void)strcpy(type, "Empty");
	break;
      case DlcNoPermission:
	(void)strcpy(type, "NoPermission");
	break;
      case DlcSimple:
	(void)strcpy(type, "Simple");
	break;
      case DlcListHead:
	(void)strcpy(type, "ListHead");
	break;
      case DlcListEnt:
	(void)strcpy(type, "ListEnt");
	break;
      case DlcListBlankTop:
	(void)strcpy(type, "ListBlankTop");
	break;
      case DlcListBlankBottom:
	(void)strcpy(type, "ListBlankBottom");
	break;
      case DlcNotSet:
	(void)strcpy(type, "NotSet");
	break;
      case DlcBeginning:
	(void)strcpy(type, "Beginning");
	break;
      case DlcOneBeforeBeginning:
	(void)strcpy(type, "OneBeforeBeginning");
	break;
      case DlcTwoBeforeBeginning:
	(void)strcpy(type, "TwoBeforeBeginning");
	break;
      case DlcEnd:
	(void)strcpy(type, "End");
	break;
    }

    dprint(2, (debugfile,
	"%s: type %s adrbk_num %d\n",
	message, type, (int)dlc->adrbk_num));
    dprint(2, (debugfile,
	"   global_row %ld dlcelnum %ld dlcoffset %ld\n",
	(long)dlc->global_row, (long)dlc->dlcelnum, (long)dlc->dlcoffset));

#endif /* DEBUG */
}


/*
 * Create addrbook_file.lu lookup file and exit.  This is for
 * use as a stand-alone creator of .lu files.
 */
void
just_update_lookup_file(addrbook_file, sort_rule_descrip)
char *addrbook_file;
char *sort_rule_descrip;
{
    int        i;
    int        sort_rule;
    NAMEVAL_S *v;
    AdrBk     *ab;
    char       warning[800];


    sort_rule = -1;
    for(i = 0; v = ab_sort_rules(i); i++){
       if(!strucmp(sort_rule_descrip, v->name)){
	   sort_rule = v->value;
	   break;
       }
    }
    if(sort_rule == -1){
	fprintf(stderr, "Sort rule %s unknown\n", sort_rule_descrip);
	exit(-1);
    }

    warning[0] = '\0';
    ab = adrbk_open(addrbook_file, NULL, warning, sort_rule, 1, 1);
    if(ab == NULL){
	if(*warning)
	  fprintf(stderr, "%s: %s\n", addrbook_file, warning);
	else
	  fprintf(stderr, "%s: %s\n",
		addrbook_file, error_description(errno));
	exit(-1);
    }

    if(!adrbk_is_in_sort_order(ab)){
	adrbk_set_nominal_cachesize(ab, (long)adrbk_count(ab));
	(void)adrbk_sort(ab, (a_c_arg_t)0, (adrbk_cntr_t *)NULL, 1);
    }

    exit(0);
}


/*
 * Something was changed in options screen, so need to start over.
 */
void
addrbook_reset()
{
    dprint(2, (debugfile, "- addrbook_reset -\n"));
    display_message('x');

    completely_done_with_adrbks();
}


/*
 * Returns 0 if access mode is ok, -1 if not.
 */
int
adrbk_access(filename, mode)
char *filename;
int   mode;
{
    char fbuf[501];

#ifdef DOS
    if(filename[0] == '\\' || (isalpha(filename[0]) && filename[1] == ':'))
#else /* !DOS */
    if(*filename == '/')
#endif /* !DOS */
      build_path(fbuf, NULL, filename);
    else
      build_path(fbuf, ps_global->home_dir, filename);
    
    return(can_access(fbuf, mode));
}


/*
 * Returns the index of the current address book.
 */
int
cur_addr_book()
{
    return(adrbk_num_from_lineno(as.top_ent + as.cur_row));
}


/*
 * Returns the index of the current address book.
 */
int
adrbk_num_from_lineno(lineno)
long lineno;
{
    DL_CACHE_S *dlc;

    dlc = get_dlc(lineno);

    return(dlc->adrbk_num);
}


void
end_adrbks()
{
    int i;

    dprint(2, (debugfile, "- end_adrbks -\n"));

    if(!as.initialized)
      return;

    for(i = 0; i < as.n_addrbk; i++)
      init_abook(&as.adrbks[i], Closed);
}


/*
 * Free and close everything.
 */
void
completely_done_with_adrbks()
{
    register PerAddrBook *pab;
    int i;

    dprint(2, (debugfile, "- completely_done_with_adrbks -\n"));

    if(!as.initialized)
      return;

    for(i = 0; i < as.n_addrbk; i++)
      init_abook(&as.adrbks[i], TotallyClosed);

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];

	if(pab->filename)
	  fs_give((void **)&pab->filename);

	if(pab->nickname)
	  fs_give((void **)&pab->nickname);
    }

    done_with_dlc_cache();

    if(as.adrbks)
      fs_give((void **)&as.adrbks);

    as.n_addrbk    = 0;
    as.initialized = 0;
}


/*
 * Save the screen state and the Open or Closed status of the addrbooks.
 */
void
save_state(state)
SAVE_STATE_S *state;
{
    int                 i;
    DL_CACHE_S         *dlc;

    dprint(9, (debugfile, "- save_state -\n"));

    if(as.n_addrbk == 0)
      return;

    /* allocate space for saving the screen structure and save it */
    state->savep    = (AddrScrState *)fs_get(sizeof(AddrScrState));
    *(state->savep) = as; /* copy the struct */


    /* allocate space for saving the ostatus for each addrbook */
    state->stp = (OpenStatus *)fs_get(as.n_addrbk * sizeof(OpenStatus));

    for(i = 0; i < as.n_addrbk; i++)
      (state->stp)[i] = as.adrbks[i].ostatus;


    state->dlc_to_warp_to = (DL_CACHE_S *)fs_get(sizeof(DL_CACHE_S));
    dlc = get_dlc(as.top_ent + as.cur_row);
    *(state->dlc_to_warp_to) = *dlc; /* copy the struct */
}


/*
 * Restore the state.
 *
 * Side effect: Flushes addrbook entry cache entries so they need to be
 * re-fetched afterwords.  This only applies to entries obtained since
 * the call to save_state.
 * Also flushes all dlc cache entries, so dlist calls need to be repeated.
 */
void
restore_state(state)
SAVE_STATE_S *state;
{
    int i;

    dprint(9, (debugfile, "- restore_state -\n"));

    if(as.n_addrbk == 0)
      return;

    as = *(state->savep);  /* put back cur_row and all that */

    /* restore addressbook OpenStatus to what it was before */
    for(i = 0; i < as.n_addrbk; i++)
      init_abook(&as.adrbks[i], (state->stp)[i]);

    /*
     * jump cache back to where we were
     */
    warp_to_dlc(state->dlc_to_warp_to, as.top_ent+as.cur_row);

    fs_give((void **)&state->dlc_to_warp_to);
    fs_give((void **)&state->stp);
    fs_give((void **)&state->savep);
}


/*
 * Initialize or re-initialize this address book.
 *
 *  Args: pab        -- the PerAddrBook ptr
 *       want_status -- desired OpenStatus for this address book
 */
void
init_abook(pab, want_status)
PerAddrBook *pab;
OpenStatus   want_status;
{
    register OpenStatus new_status;

    dprint(7, (debugfile, "- init_abook -\n"));
    dprint(7, (debugfile,
	    "    addrbook nickname = %s filename = %s want ostatus %s\n",
		pab->nickname ? pab->nickname : "<null>",
		pab->filename ? pab->filename : "<null>",
		want_status==Open ?
			    "Open" :
			    want_status==HalfOpen ?
				    "HalfOpen" :
				    want_status==NoDisplay ?
					    "NoDisplay" :
					    want_status==Closed ?
						"Closed" :
						"TotallyClosed"));
    dprint(7, (debugfile, "    ostatus was %s, want %s\n",
		pab->ostatus==Open ?
			    "Open" :
			    pab->ostatus==HalfOpen ?
				    "HalfOpen" :
				    pab->ostatus==NoDisplay ?
					    "NoDisplay" :
					pab->ostatus==Closed ?
					    "Closed" :
					    "TotallyClosed",
		want_status==Open ?
			    "Open" :
			    want_status==HalfOpen ?
				    "HalfOpen" :
				    want_status==NoDisplay ?
					    "NoDisplay" :
					    want_status==Closed ?
						"Closed" :
						"TotallyClosed"));

    new_status = want_status;  /* optimistic default */

    if(want_status == TotallyClosed && pab->address_book != NULL){
	adrbk_close(pab->address_book);
	pab->address_book = NULL;
    }
    /*
     * If we don't need it, release some addrbook memory by calling
     * adrbk_partial_close().
     */
    else if((want_status == Closed || want_status == HalfOpen) &&
	pab->address_book != NULL){
	adrbk_partial_close(pab->address_book);
    }
    /* If we want the addrbook read in and it hasn't been, do so */
    else if((want_status == Open || want_status == NoDisplay) &&
	pab->address_book == NULL){
	if(pab->access != NoAccess){
	    char warning[800]; /* place to put a warning */
	    int sort_rule;
	    int force_not_valid = 0;

	    warning[0] = '\0';
	    if(pab->access == ReadOnly)
		sort_rule = AB_SORT_RULE_NONE;
	    else
		sort_rule = ps_global->ab_sort_rule;
	    if(trouble_filename){
		if(pab->filename)
		  force_not_valid = 
			       strcmp(trouble_filename, pab->filename) == 0;
		else
		  force_not_valid = *trouble_filename == '\0';
		if(force_not_valid){
#ifdef notdef
		    /*
		     * I go back and forth on whether or not this should
		     * be here.  If the sys admin screws up and copies the
		     * wrong .lu file into place where a large global
		     * addressbook is, do we want it to rebuild for all
		     * the users or not?  With this commented out we're
		     * rebuilding for everybody, just like we would for
		     * a personal addressbook.  Most likely that will mean
		     * that it gets rebuilt in /tmp for each current user.
		     */
		    if(pab->type == LocalGlobal)
		      force_not_valid = 0;
#endif /* notdef */
		    fs_give((void **)&trouble_filename);
		}
	    }
	    pab->address_book = adrbk_open(pab->filename,
		   ps_global->home_dir, warning, sort_rule, 0, force_not_valid);

	    if(pab->address_book == NULL){
		pab->access = NoAccess;
		if(want_status == Open){
		    new_status = HalfOpen;  /* best we can do */
		    q_status_message1(1, 1, 4,
			  "\007Error opening/creating address book %s",
			  pab->nickname);
		    if(*warning)
			q_status_message2(1, 2, 4, "%s: %s",
			    as.n_addrbk > 1 ? pab->nickname : "addressbook",
			    warning);
		}
		else
		    new_status  = Closed;
		dprint(1, (debugfile, "Error opening address book %s: %s\n",
			  pab->nickname, error_description(errno)));
	    }
	    else{
		if(pab->access == NoExists)
		  pab->access = ReadWrite;

		/* 200 is out of the blue */
		(void)adrbk_set_nominal_cachesize(pab->address_book,
		    min((long)adrbk_count(pab->address_book), 200L));

		if(pab->access == ReadWrite){
#ifndef DOS
		    long old_size;
#endif /* !DOS */

		    /*
		     * For huge addrbooks, it really pays if you can make
		     * them read-only so that you skip adrbk_is_in_sort_order.
		     */
		    if(!adrbk_is_in_sort_order(pab->address_book)){
			q_status_message(0, 0, 0, "Sorting address book...");
			flush_status_messages();
/* DOS sorts will be very slow on large addrbooks */
#ifndef DOS
			old_size =
			    adrbk_set_nominal_cachesize(pab->address_book,
			    (long)adrbk_count(pab->address_book));
#endif /* !DOS */
			(void)adrbk_sort(pab->address_book,
			    (a_c_arg_t)0, (adrbk_cntr_t *)NULL, 0);
#ifndef DOS
			(void)adrbk_set_nominal_cachesize(pab->address_book,
			    old_size);
#endif /* !DOS */
			display_message('x');
		    }
		}

		new_status = want_status;
		dprint(1,
		      (debugfile,
		      "Address book %s (%s) opened with %ld items\n",
		       pab->nickname, pab->filename,
		       (long)adrbk_count(pab->address_book)));
		if(*warning){
		    dprint(1, (debugfile,
				 "Addressbook parse error in %s (%s): %s\n",
				 pab->nickname, pab->filename, warning));
		    if(!pab->gave_parse_warnings && want_status == Open){
			pab->gave_parse_warnings++;
			q_status_message2(1, 1, 4, "%s: %s",
			    as.n_addrbk > 1 ? pab->nickname : "addressbook",
			    warning);
		    }
		}
	    }
	}
	else{
	    if(want_status == Open){
		new_status = HalfOpen;  /* best we can do */
		q_status_message1(1, 1, 4,
	        "\007Insufficient file permissions for opening address book %s",
	        pab->nickname);
	    }
	    else
	      new_status = Closed;
	}
    }

    pab->ostatus = new_status;
}


/*
 * Returns the addrbook entry for this display row.
 */
AdrBk_Entry *
ae(row)
long row;
{
    PerAddrBook *pab;
    LineType type;
    AddrScrn_Disp *dl;

    dl = dlist(row);
    type = dl->type;
    if(!(type == Simple || type == ListHead || type == ListEnt))
      return((AdrBk_Entry *)NULL);

    pab = &as.adrbks[adrbk_num_from_lineno(row)];

    return(adrbk_get_ae(pab->address_book, (a_c_arg_t)dl->elnum, Normal));
}


/*
 * Returns a pointer to the member_number'th list member of the list
 * associated with this display line.
 */
char *
listmem(row)
long         row;
{
    PerAddrBook *pab;
    AddrScrn_Disp *dl;

    dl = dlist(row);
    if(dl->type != ListEnt)
      return((char *)NULL);

    pab = &as.adrbks[adrbk_num_from_lineno(row)];

    return(listmem_from_dl(pab->address_book, dl));
}


/*
 * Returns a pointer to the list member
 * associated with this display line.
 */
char *
listmem_from_dl(address_book, dl)
AdrBk *address_book;
AddrScrn_Disp *dl;
{
    AdrBk_Entry *abe;
    char **p = (char **)NULL;

    /* This shouldn't happen */
    if(dl->type != ListEnt)
      return((char *)NULL);

    abe = adrbk_get_ae(address_book, (a_c_arg_t)dl->elnum, Normal);

    /*
     * If we wanted to be more careful, We'd go through the list making sure
     * we don't pass the end.  We'll count on the caller being careful
     * instead.
     */
    if(abe->tag == List){
	p =  abe->addr.list;
	p += dl->l_offset;
    }

    return((p && *p) ? *p : (char *)NULL);
}


DL_CACHE_S *
dlc_from_listmem(address_book, elem, member_addr)
AdrBk       *address_book;
a_c_arg_t    elem;
char        *member_addr;
{
    AdrBk_Entry *abe;
    char **p;
    static DL_CACHE_S dlc;

    abe = adrbk_get_ae(address_book, elem, Normal);

    if(abe->tag == List){
	for(p = abe->addr.list; *p; p++){
	    if(strcmp(*p, member_addr) == 0)
	      break;
	}
    }
    dlc.type      = DlcListEnt;
    dlc.dlcelnum  = (adrbk_cntr_t)elem;
    if(abe->tag == List)
      dlc.dlcoffset = p - abe->addr.list;
    else
      dlc.dlcoffset = 0;

    return(&dlc);
}


/*
 * How many members in list?
 */
adrbk_cntr_t
listmem_count_from_abe(abe)
AdrBk_Entry *abe;
{
    char **p;

    if(abe->tag != List)
      return 0;

    for(p = abe->addr.list; p != NULL && *p != NULL; p++)
      ;/* do nothing */
    
    return((adrbk_cntr_t)(p - abe->addr.list));
}


/*
 * Return a ptr to the row'th line of the global disp_list.
 * Line numbers count up but you can't count on knowing which line number
 * goes with the first or the last row.  That is, row 0 is not necessarily
 * special.  It could be before the rows that make up the display list, after
 * them, or anywhere in between.
 * You can't tell what the last row is numbered, but a dl with type End is
 * returned when you get past the end.  You can't tell
 * what the number of the first row is, but 
 * if you go past the first row a dl of type Beginning will be returned.
 * Row numbers can be positive
 * or negative.  Their values have no meaning other than how they line up
 * relative to other row numbers.
 */
AddrScrn_Disp *
dlist(row)
long row;
{
    DL_CACHE_S        *dlc = (DL_CACHE_S *)NULL;

    dlc = get_dlc(row);

    if(dlc){
	fill_in_dl_field(dlc);
	return(&dlc->dl);
    }
    else{
	q_status_message(0, 5, 5,
	    "\007Bug in addrbook, not supposed to happen, re-syncing...");
	dprint(1,
	    (debugfile,
	"Bug in addrbook (null dlc in dlist(%ld), not supposed to happen\n",
	    row));
	/* jump back to a safe starting point */
	dump_some_debugging("panic_dlist");
	longjmp(addrbook_changed_unexpectedly, 1);
	/*NOTREACHED*/
    }
}


/*
 * This returns the actual dlc instead of the dl within the dlc.
 */
DL_CACHE_S *
get_dlc(row)
long row;
{
    dprint(11, (debugfile, "- get_dlc(%ld) -\n", row));

    return(dlc_mgr(row, Lookup, (DL_CACHE_S *)NULL));
}


void
initialize_dlc_cache()
{
    dprint(11, (debugfile, "- initialize_dlc_cache -\n"));

    (void)dlc_mgr(NO_LINE, Initialize, (DL_CACHE_S *)NULL);
}


void
done_with_dlc_cache()
{
    dprint(9, (debugfile, "- done_with_dlc_cache -\n"));

    (void)dlc_mgr(NO_LINE, DoneWithCache, (DL_CACHE_S *)NULL);
}


/*
 * Move to new_dlc and give it row number row_number_to_assign_it.
 * We copy the passed in dlc in case the caller passed us a pointer into
 * the cache.
 */
void
warp_to_dlc(new_dlc, row_number_to_assign_it)
DL_CACHE_S *new_dlc;
long row_number_to_assign_it;
{
    DL_CACHE_S dlc;

    dprint(9, (debugfile, "- warp_to_dlc(%ld) -\n", row_number_to_assign_it));

    dlc = *new_dlc;

    (void)dlc_mgr(row_number_to_assign_it, ArbitraryStartingPoint, &dlc);
}


/*
 * Move to first dlc and give it row number 0.
 */
void
warp_to_beginning()
{
    dprint(9, (debugfile, "- warp_to_beginning -\n"));

    (void)dlc_mgr(0L, FirstEntry, (DL_CACHE_S *)NULL);
}


/*
 * Move to last dlc and give it row number 0.
 */
void
warp_to_end()
{
    dprint(9, (debugfile, "- warp_to_end -\n"));

    (void)dlc_mgr(0L, LastEntry, (DL_CACHE_S *)NULL);
}


/*
 * This flushes all of the cache that is related to this address book
 * entry, (the entry that this cache element refers to).  Or, if this doesn't
 * refer to an address book entry, it flushes this single cache line.
 */
void
flush_dlc_from_cache(dlc_to_flush)
DL_CACHE_S *dlc_to_flush;
{
    dprint(11, (debugfile, "- flush_dlc_from_cache -\n"));

    (void)dlc_mgr(NO_LINE, FlushDlcFromCache, dlc_to_flush);
}


/*
 * Returns 1 if the dlc's match, 0 otherwise.
 */
int
matching_dlcs(dlc1, dlc2)
DL_CACHE_S *dlc1, *dlc2;
{
    if(!dlc1 || !dlc2 ||
        dlc1->type != dlc2->type ||
	dlc1->adrbk_num != dlc2->adrbk_num)
	return 0;

    switch(dlc1->type){

      case DlcSimple:
      case DlcListHead:
      case DlcListBlankTop:
      case DlcListBlankBottom:
	return(dlc1->dlcelnum == dlc2->dlcelnum);

      case DlcListEnt:
	return(dlc1->dlcelnum  == dlc2->dlcelnum &&
	       dlc1->dlcoffset == dlc2->dlcoffset);

      case DlcTitleBlankTop:
      case DlcTitleDashTop:
      case DlcTitle:
      case DlcTitleDashBottom:
      case DlcTitleBlankBottom:
      case DlcClickHere:
      case DlcEmpty:
      case DlcNoPermission:
	return 1;

      case DlcNotSet:
      case DlcBeginning:
      case DlcOneBeforeBeginning:
      case DlcTwoBeforeBeginning:
      case DlcEnd:
	return 0;
    }
    /*NOTREACHED*/
}


/*
 * Compare two dlc's to see which comes later in the display list.
 *
 * THERE ARE BIG RESTRICTIONS!  First, dlc1 must be either a DlcSimple or a
 * DlcListHead!  Second, these aren't just any dlc's.  Dlc1 is a dlc
 * that is going to be inserted into the addrbook.  Therefore, it can have
 * the same element number as something already in the addrbook.  If it
 * has the same elnum as dlc2, then dlc2 will soon have an elnum that is
 * one higher when the cache is flushed.  So in that case, dlc1 is earlier
 * in the display list than dlc2.  But, if dlc1 has an elnum that is one higher
 * than dlc2's you don't want to decrease it because it is already correct.
 * In other words, you can't just subtract one from the elnum and then
 * do a regular compare.  Hence, this weirdo function.
 *
 * Actually, it still isn't quite right because it could be that it really
 * does have the same elnum.  For example, dlc2 could be a ListEnt that
 * is a member of dlc1, a ListHead.  We'll pretend that can't happen and
 * note that the consequences of it happening are not serious (display is
 * still correct).
 *
 * Returns > 0 if dlc1 >  dlc2 (comes later in display list),
 * Returns < 0 if dlc1 <  dlc2 (comes earlier in display list),
 * (We never call this with dlc1 == dlc2.)
 */
int
funny_compare_dlcs(dlc1, dlc2)
DL_CACHE_S *dlc1, *dlc2;
{
    long test_elnum;

    if(!dlc2)
      return  1; /* arbitrarily, we shouldn't allow this to happen */
    if(!dlc1)
      return -1;

    switch(dlc2->type){

      case DlcBeginning:
      case DlcOneBeforeBeginning:
      case DlcTwoBeforeBeginning:
	return 1;

      case DlcEnd:
	return -1;
    }

    if(dlc1->adrbk_num != dlc2->adrbk_num)
      return(dlc1->adrbk_num - dlc2->adrbk_num);

    if(dlc1->type != DlcSimple && dlc1->type != DlcListHead)
      goto panic_abook_abort;

    switch(dlc2->type){

      case DlcSimple:
      case DlcListHead:
      case DlcListEnt:
      case DlcListBlankBottom:
      case DlcListBlankTop:
	/* this is the funny case discussed in the comments */
	test_elnum = dlc1->dlcelnum;
	if(test_elnum == dlc2->dlcelnum)
	  test_elnum--;
	return(test_elnum - dlc2->dlcelnum);

      case DlcTitleBlankTop:
      case DlcTitleDashTop:
      case DlcTitle:
      case DlcTitleDashBottom:
      case DlcTitleBlankBottom:
      case DlcNotSet:
	return 1;

      case DlcClickHere:
      case DlcEmpty:
      case DlcNoPermission:
      default:
	/* panic, not supposed to happen, fall through */
	break;
    }

panic_abook_abort:
    q_status_message(0, 5, 5,
	"\007Bug in addrbook, not supposed to happen, re-syncing...");
    dprint(1,
	(debugfile,
	"Bug in addrbook, not supposed to happen, re-sync\n"));
    /* jump back to a safe starting point */
    dump_some_debugging("panic_abook_abort");
    dump_a_dlc_to_debug("dlc1", dlc1);
    dump_a_dlc_to_debug("dlc2", dlc2);
    longjmp(addrbook_changed_unexpectedly, 1);
    /*NOTREACHED*/
}


/*
 * Returns 1 if the dlc's are related to the same addrbook entry, 0 otherwise.
 */
int
dlcs_from_same_abe(dlc1, dlc2)
DL_CACHE_S *dlc1, *dlc2;
{
    if(!dlc1 || !dlc2 ||
	dlc1->adrbk_num != dlc2->adrbk_num)
	return 0;

    switch(dlc1->type){

      case DlcSimple:
      case DlcListHead:
      case DlcListBlankTop:
      case DlcListBlankBottom:
      case DlcListEnt:
	switch(dlc2->type){
	  case DlcTitleBlankTop:
	  case DlcTitleDashTop:
	  case DlcTitle:
	  case DlcTitleDashBottom:
	  case DlcTitleBlankBottom:
	  case DlcClickHere:
	  case DlcEmpty:
	  case DlcNoPermission:
	  case DlcNotSet:
	  case DlcBeginning:
	  case DlcOneBeforeBeginning:
	  case DlcTwoBeforeBeginning:
	  case DlcEnd:
	    return 0;

	  case DlcSimple:
	  case DlcListHead:
	  case DlcListBlankTop:
	  case DlcListBlankBottom:
	  case DlcListEnt:
	    return(dlc1->dlcelnum == dlc2->dlcelnum);
	}
	break;

      case DlcTitleBlankTop:
      case DlcTitleDashTop:
      case DlcTitle:
      case DlcTitleDashBottom:
      case DlcTitleBlankBottom:
      case DlcClickHere:
      case DlcEmpty:
      case DlcNoPermission:
      case DlcNotSet:
      case DlcBeginning:
      case DlcOneBeforeBeginning:
      case DlcTwoBeforeBeginning:
      case DlcEnd:
	return 0;
    }
    /*NOTREACHED*/
}


/* data for the display list cache */
static DL_CACHE_S *cache_array = (DL_CACHE_S *)NULL;
static long        valid_low,
		   valid_high;
static int         index_of_low,
		   size_of_cache,
		   n_cached;

/*
 * Manage the display list cache.
 *
 * The cache is a circular array of DL_CACHE_S elements.  It always
 * contains a contiguous set of display lines.
 * The lowest numbered line in the cache is
 * valid_low, and the highest is valid_high.  Everything in between is
 * also valid.  Index_of_low is where to look
 * for the valid_low element in the circular array.
 *
 * We make calls to dlc_prev and dlc_next to get new entries for the cache.
 * We need a starting value before we can do that.
 *
 * This returns a pointer to a dlc for the desired row.  If you want the
 * actual display list line you call dlist(row) instead of dlc_mgr.
 */
DL_CACHE_S *
dlc_mgr(row, op, dlc_start)
long row;
DlMgrOps op;
DL_CACHE_S *dlc_start;
{
    int                new_index, known_index, next_index;
    DL_CACHE_S        *dlc = (DL_CACHE_S *)NULL;
    long               next_row;
    long               prev_row;


    if(op == Lookup){

	if(row >= valid_low && row <= valid_high){  /* already cached */

	    new_index = ((row - valid_low) + index_of_low) % size_of_cache;

	    dlc = &cache_array[new_index];

	}
	else if(row > valid_high){  /* row is past where we've looked */

	    known_index =
	      ((valid_high - valid_low) + index_of_low) % size_of_cache;
	    next_row    = valid_high + 1L;

	    /* we'll usually be looking for row = valid_high + 1 */
	    while(next_row <= row){

		new_index = (known_index + 1) % size_of_cache;

		dlc =
		  dlc_next(&cache_array[known_index], &cache_array[new_index]);

		/*
		 * This means somebody changed the file out from underneath
		 * us.  This would happen if dlc_next needed to ask for an
		 * abe to figure out what the type of the next row is, but
		 * adrbk_get_ae returned a NULL.  I don't think that can
		 * happen, but if it does...
		 */
		if(dlc->type == DlcNotSet){
		    dprint(1, (debugfile, "dlc_next returned DlcNotSet\n"));
		    dump_a_dlc_to_debug("dlc", dlc);
		    goto panic_abook_corrupt;
		}

		if(n_cached == size_of_cache){ /* replaced low cache entry */
		    valid_low++;
		    index_of_low = (index_of_low + 1) % size_of_cache;
		}
		else
		  n_cached++;
		valid_high++;

		next_row++;
		known_index = new_index; /* for next time through loop */
	    }
	}
	else if(row < valid_low){  /* row is back up the screen */

	    known_index = index_of_low;
	    prev_row = valid_low - 1L;

	    while(prev_row >= row){

		new_index = (known_index - 1 + size_of_cache) % size_of_cache;

		dlc =
		  dlc_prev(&cache_array[known_index], &cache_array[new_index]);

		if(dlc->type == DlcNotSet){
		    dprint(1, (debugfile, "dlc_prev returned DlcNotSet (1)\n"));
		    dump_a_dlc_to_debug("dlc", dlc);
		    goto panic_abook_corrupt;
		}

		if(n_cached == size_of_cache) /* replaced high cache entry */
		  valid_high--;
		else
		  n_cached++;

		valid_low--;
		index_of_low =
			    (index_of_low - 1 + size_of_cache) % size_of_cache;

		prev_row--;
		known_index = new_index;
	    }
	}
    }

    else if(op == Initialize){

	n_cached = 0;

	if(!cache_array || size_of_cache != 3 * as.l_p_page){
	    if(cache_array)
		free_cache_array(&cache_array, size_of_cache);
	    size_of_cache = 3 * as.l_p_page;
	    cache_array =
		(DL_CACHE_S *)fs_get(size_of_cache * sizeof(DL_CACHE_S));
	    memset((void *)cache_array, 0, size_of_cache * sizeof(DL_CACHE_S));
	}

	/* this will return NULL below and the caller should ignore that */
    }

    /*
     * Flush all rows for a particular addrbook entry from the cache, but
     * keep the cache alive and anchored in the same place.  The particular
     * entry is the one that dlc_start is one of the rows of.
     */
    else if(op == FlushDlcFromCache){
	long low_entry;

	if(dlc_start->type == DlcSimple ||
	   dlc_start->type == DlcListHead ||
	   dlc_start->type == DlcListEnt){
	    /* find this entry in cache */
	    next_row = dlc_start->global_row - 1;
	    for(; next_row >= valid_low; next_row--){
		next_index = ((next_row - valid_low) + index_of_low) %
		    size_of_cache;
		if(!dlcs_from_same_abe(dlc_start, &cache_array[next_index]))
		    break;
	    }
	    low_entry = next_row + 1L;
	}
	else{
	    low_entry = dlc_start->global_row;
	}
	/*
	 * If low_entry now points one past a ListBlankBottom, delete that,
	 * too, since it may not make sense anymore.
	 */
	if(low_entry > valid_low){
	    next_index = ((low_entry -1L - valid_low) + index_of_low) %
		size_of_cache;
	    if(cache_array[next_index].type == DlcListBlankBottom)
		low_entry--;
	}
	if(low_entry > valid_low){ /* invalidate everything >= this */
	    n_cached -= (valid_high - (low_entry - 1L));
	    valid_high = low_entry - 1L;
	}
	else{
	    /*
	     * This is the tough case.  That entry was the first thing cached,
	     * so we need to invalidate the whole cache.  However, we also
	     * need to keep at least one thing cached for an anchor, so
	     * we need to get the dlc before this one and it should be a
	     * dlc not related to this same addrbook entry.
	     */
	    known_index = index_of_low;
	    prev_row = valid_low - 1L;

	    for(;;){

		new_index = (known_index - 1 + size_of_cache) % size_of_cache;

		dlc =
		  dlc_prev(&cache_array[known_index], &cache_array[new_index]);

		if(dlc->type == DlcNotSet){
		    dprint(1, (debugfile, "dlc_prev returned DlcNotSet (2)\n"));
		    dump_a_dlc_to_debug("dlc", dlc);
		    goto panic_abook_corrupt;
		}

		valid_low--;
		index_of_low =
			    (index_of_low - 1 + size_of_cache) % size_of_cache;

		if(!dlcs_from_same_abe(dlc_start, dlc))
		  break;

		known_index = new_index;
	    }
	    n_cached = 1;
	    valid_high = valid_low;
	}
    }

    /*
     * We have to anchor ourselves at a first element.
     * Here's how we start at the top.
     */
    else if(op == FirstEntry){
	initialize_dlc_cache();
	n_cached++;
	dlc = &cache_array[0];
	dlc = get_global_top_dlc(dlc);
	dlc->global_row = row;
	index_of_low = 0;
	valid_low    = row;
	valid_high   = row;
    }

    /* And here's how we start from the bottom. */
    else if(op == LastEntry){
	initialize_dlc_cache();
	n_cached++;
	dlc = &cache_array[0];
	dlc = get_global_bottom_dlc(dlc);
	dlc->global_row = row;
	index_of_low = 0;
	valid_low    = row;
	valid_high   = row;
    }

    /*
     * And here's how we start from an arbitrary position in the middle.
     * We root the cache at display line row, so it helps if row is close
     * to where we're going to be starting so that things are easy to find.
     * The dl that goes with line row is dl_start from addrbook number
     * adrbk_num_start.
     */
    else if(op == ArbitraryStartingPoint){
	initialize_dlc_cache();
	n_cached++;
	dlc = &cache_array[0];

	*dlc = *dlc_start;
	dlc->global_row = row;
	dlc->dl.txt = NULL;

	index_of_low = 0;
	valid_low    = row;
	valid_high   = row;
    }

    else if(op == DoneWithCache){

	n_cached = 0;
	if(cache_array)
	  free_cache_array(&cache_array, size_of_cache);
    }

    return(dlc);

panic_abook_corrupt:
    q_status_message(0, 5, 5,
	"\007Addrbook changed by another process, re-syncing...");
    dprint(1, (debugfile,
	"addrbook changed while we had it open?, re-sync\n"));
    dprint(2, (debugfile,
	"valid_low=%ld valid_high=%ld index_of_low=%d size_of_cache=%d\n",
	valid_low, valid_high, index_of_low, size_of_cache));
    dprint(2, (debugfile,
	"n_cached=%d new_index=%d known_index=%d next_index=%d\n",
	n_cached, new_index, known_index, next_index));
    dprint(2, (debugfile,
	"next_row=%ld prev_row=%ld row=%ld\n", next_row, prev_row, row));
    /* jump back to a safe starting point */
    longjmp(addrbook_changed_unexpectedly, 1);
    /*NOTREACHED*/
}


void
free_cache_array(c_array, size)
DL_CACHE_S **c_array;
int size;
{
    DL_CACHE_S *dlc;
    int i;

    for(i = 0; i < size; i++){
	dlc = &(*c_array)[i];
	/* free any allocated space */
	switch(dlc->dl.type){
	  case Text:
	  case Title:
	    if(dlc->dl.txt)
	      fs_give((void **)&dlc->dl.txt);
	    break;
	}
    }
    fs_give((void **)c_array);
}


/*
 * Get the dlc element that comes before "old".  The function that calls this
 * function is the one that keeps a cache and checks in the cache before
 * calling here.  New is a passed in pointer to a buffer where we fill in
 * the answer.
 */
DL_CACHE_S *
dlc_prev(old, new)
DL_CACHE_S *old, *new;
{
    PerAddrBook  *pab;
    AdrBk_Entry  *abe;
    adrbk_cntr_t  list_count;

    new->adrbk_num  = -1;
    new->type       = DlcNotSet;
    pab = &as.adrbks[old->adrbk_num];

    switch(old->type){
      case DlcTitleBlankTop:
	new->adrbk_num = old->adrbk_num - 1;
	new = get_bottom_dl_of_adrbk(new->adrbk_num, new);
	break;

      case DlcTitleDashTop:
	if(old->adrbk_num == 0)
	  new->type = DlcOneBeforeBeginning;
	else
	  new->type = DlcTitleBlankTop;
	break;

      case DlcTitle:
	new->type = DlcTitleDashTop;
	break;

      case DlcTitleDashBottom:
	new->type = DlcTitle;
	break;

      case DlcTitleBlankBottom:
	new->type = DlcTitleDashBottom;
	break;

      case DlcClickHere:
      case DlcEmpty:
      case DlcNoPermission:
	if(as.n_addrbk == 1)
	  new->type = DlcOneBeforeBeginning;
	else
	  new->type = DlcTitleBlankBottom;
	break;

      case DlcSimple:
	if(old->dlcelnum == 0){
	  if(as.n_addrbk == 1)
	    new->type = DlcOneBeforeBeginning;
	  else
	    new->type = DlcTitleBlankBottom;
	}
	else{
	    new->dlcelnum = old->dlcelnum - 1;
	    abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum,
		Normal);
	    if(abe && abe->tag == Single)
	      new->type = DlcSimple;
	    else if(abe && abe->tag == List)
	      new->type = DlcListBlankBottom;
	}
	break;

      case DlcListHead:
	if(old->dlcelnum == 0){
	    if(as.n_addrbk == 1)
	      new->type = DlcOneBeforeBeginning;
	    else
	      new->type = DlcTitleBlankBottom;
	}
	else{
	    new->dlcelnum = old->dlcelnum - 1;
	    abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum,
		Normal);
	    if(abe && abe->tag == Single){
		new->type  = DlcListBlankTop;
		new->dlcelnum = old->dlcelnum;
	    }
	    else if(abe && abe->tag == List)
	      new->type  = DlcListBlankBottom;
	}
	break;

      case DlcListEnt:
	if(old->dlcoffset > 0){
	    new->type      = DlcListEnt;
	    new->dlcelnum  = old->dlcelnum;
	    new->dlcoffset = old->dlcoffset - 1;
	}
	else{
	    new->type     = DlcListHead;
	    new->dlcelnum = old->dlcelnum;
	}
	break;

      case DlcListBlankTop:  /* can only occur between a Simple and a List */
	new->type   = DlcSimple;
	new->dlcelnum  = old->dlcelnum - 1;
	break;

      case DlcListBlankBottom:
	new->dlcelnum = old->dlcelnum;
	abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum, Normal);
	list_count = listmem_count_from_abe(abe);
	if(list_count == 0)
	  new->type = DlcListHead;
	else{
	    new->type      = DlcListEnt;
	    new->dlcoffset = list_count - 1;
	}
	break;

      case DlcBeginning:
	new->type   = DlcBeginning;
	break;

      case DlcOneBeforeBeginning:
	new->type   = DlcTwoBeforeBeginning;
	break;

      case DlcTwoBeforeBeginning:
	new->type   = DlcBeginning;
	break;

      default:
	q_status_message(0, 5, 5,
	    "\007Bug in addrbook, not supposed to happen, re-syncing...");
	dprint(1,
	    (debugfile,
	    "Bug in addrbook, impossible case (%d) in dlc_prev, re-sync\n",
	    old->type));
	dump_a_dlc_to_debug("old", old);
	/* jump back to a safe starting point */
	longjmp(addrbook_changed_unexpectedly, 1);
	/*NOTREACHED*/
    }

    new->global_row = old->global_row - 1L;
    if(new->adrbk_num == -1)
      new->adrbk_num = old->adrbk_num;

    return(new);
}


/*
 * Get the dlc element that comes after "old".  The function that calls this
 * function is the one that keeps a cache and checks in the cache before
 * calling here.
 */
DL_CACHE_S *
dlc_next(old, new)
DL_CACHE_S *old, *new;
{
    PerAddrBook  *pab;
    AdrBk_Entry  *abe;
    adrbk_cntr_t  ab_count;
    adrbk_cntr_t  list_count;

    new->adrbk_num  = -1;
    new->type       = DlcNotSet;
    pab = &as.adrbks[old->adrbk_num];

    switch(old->type){
      case DlcTitleBlankTop:
	new->type = DlcTitleDashTop;
	break;

      case DlcTitleDashTop:
	new->type = DlcTitle;
	break;

      case DlcTitle:
	new->type = DlcTitleDashBottom;
	break;

      case DlcTitleDashBottom:
	new->type = DlcTitleBlankBottom;
	break;

      case DlcTitleBlankBottom:
	if(pab->ostatus != Open && pab->access != NoAccess)
	  new->type = DlcClickHere;
	else
	  new = get_first_dl_of_adrbk(old->adrbk_num, new);
	break;

      case DlcClickHere:
      case DlcEmpty:
      case DlcNoPermission:
	if(old->adrbk_num == as.n_addrbk - 1)  /* last addrbook */
	  new->type = DlcEnd;
	else{
	    new->adrbk_num = old->adrbk_num + 1;
	    new->type = DlcTitleBlankTop;
	}
	break;

      case DlcSimple:
	ab_count = adrbk_count(pab->address_book);
	if(old->dlcelnum == ab_count - 1){  /* last element of this addrbook */
	    if(old->adrbk_num == as.n_addrbk - 1)  /* last addrbook */
	      new->type = DlcEnd;
	    else{
		new->adrbk_num = old->adrbk_num + 1;
		new->type = DlcTitleBlankTop;
	    }
	}
	else{
	    new->dlcelnum = old->dlcelnum + 1;
	    abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum,
		Normal);
	    if(abe->tag == Single)
	      new->type = DlcSimple;
	    else if(abe->tag == List)
	      new->type = DlcListBlankTop;
	}
	break;

      case DlcListHead:
	new->dlcelnum = old->dlcelnum;
	abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum, Normal);
	list_count = listmem_count_from_abe(abe);
	if(list_count == 0){
	    ab_count = adrbk_count(pab->address_book);
	    if(old->dlcelnum == ab_count - 1){ /* last entry in this addrbook */
		if(old->adrbk_num == as.n_addrbk - 1)  /* last addrbook */
		  new->type = DlcEnd;
		else{
		    new->type = DlcTitleBlankTop;
		    new->adrbk_num = old->adrbk_num + 1;
		}
	    }
	    else
	      new->type = DlcListBlankBottom;
	}
	else{
	    new->type      = DlcListEnt;
	    new->dlcoffset = 0;
	}
	break;

      case DlcListEnt:
	new->dlcelnum = old->dlcelnum;
	abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum, Normal);
	list_count = listmem_count_from_abe(abe);
	if(old->dlcoffset == list_count - 1){  /* last member of list */
	    ab_count = adrbk_count(pab->address_book);
	    if(old->dlcelnum == ab_count - 1){  /* last entry in addrbook */
		if(old->adrbk_num == as.n_addrbk - 1)  /* last addrbook */
		  new->type = DlcEnd;
		else{
		    new->type = DlcTitleBlankTop;
		    new->adrbk_num = old->adrbk_num + 1;
		}
	    }
	    else
	      new->type = DlcListBlankBottom;
	}
	else{
	    new->type      = DlcListEnt;
	    new->dlcoffset = old->dlcoffset + 1;
	}
	break;

      case DlcListBlankTop:
	new->type   = DlcListHead;
	new->dlcelnum  = old->dlcelnum;
	break;

      case DlcListBlankBottom:
	new->dlcelnum = old->dlcelnum + 1;
	abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum, Normal);
	if(abe->tag == Single)
	  new->type = DlcSimple;
	else if(abe->tag == List)
	  new->type = DlcListHead;
	break;

      case DlcEnd:
	new->type = DlcEnd;
	break;

      case DlcOneBeforeBeginning:
	new = get_global_top_dlc(new);
	break;

      case DlcTwoBeforeBeginning:
	new->type = DlcOneBeforeBeginning;
	break;

      default:
	q_status_message(0, 5, 5,
	    "\007Bug in addrbook, not supposed to happen, re-syncing...");
	dprint(1,
	    (debugfile,
	    "Bug in addrbook, impossible case (%d) in dlc_next, re-sync\n",
	    old->type));
	dump_a_dlc_to_debug("old", old);
	/* jump back to a safe starting point */
	longjmp(addrbook_changed_unexpectedly, 1);
	/*NOTREACHED*/
    }

    new->global_row = old->global_row + 1L;
    if(new->adrbk_num == -1)
      new->adrbk_num = old->adrbk_num;

    return(new);
}


/*
 * Get the display line at the very top of whole addrbook screen display.
 *
 * If only one addrbook, no titles.
 */
DL_CACHE_S *
get_global_top_dlc(new)
DL_CACHE_S *new;  /* fill in answer here */
{
    if(as.n_addrbk > 1)
      new->type = DlcTitleDashTop;

    else /* only one addrbook, get top entry */
      new = get_first_dl_of_adrbk(0, new);

    new->adrbk_num  = 0;

    return(new);
}


/*
 * Get the last display line for the whole address book screen.
 * This gives us a way to start at the end and move back up.
 */
DL_CACHE_S *
get_global_bottom_dlc(new)
DL_CACHE_S *new;  /* fill in answer here */
{
    new->adrbk_num = as.n_addrbk - 1;

    new = get_bottom_dl_of_adrbk(new->adrbk_num, new);

    return(new);
}


/*
 * First dl in a particular addrbook, not counting title lines.
 */
DL_CACHE_S *
get_first_dl_of_adrbk(adrbk_num, new)
int         adrbk_num;
DL_CACHE_S *new;  /* fill in answer here */
{
    PerAddrBook  *pab;
    AdrBk_Entry  *abe;
    adrbk_cntr_t  ab_count;

    pab = &as.adrbks[adrbk_num];

    if(pab->access == NoAccess)
      new->type = DlcNoPermission;

    else{
	ab_count = adrbk_count(pab->address_book);
	if(ab_count == 0)
	  new->type = DlcEmpty;
	else{
	    new->dlcelnum = 0;
	    abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum,
		Normal);
	    if(abe->tag == Single)
	      new->type = DlcSimple;
	    else if(abe->tag == List)
	      new->type = DlcListHead;
	}
    }

    return(new);
}


/*
 * Find the last display line for addrbook number adrbk_num.
 */
DL_CACHE_S *
get_bottom_dl_of_adrbk(adrbk_num, new)
int         adrbk_num;
DL_CACHE_S *new;  /* fill in answer here */
{
    PerAddrBook  *pab;
    AdrBk_Entry  *abe;
    adrbk_cntr_t  ab_count;
    adrbk_cntr_t  list_count;

    pab = &as.adrbks[adrbk_num];

    if(pab->ostatus != Open){
	if(pab->access == NoAccess)
	  new->type = DlcNoPermission;
	else
	  new->type = DlcClickHere;
    }
    else{
	ab_count = adrbk_count(pab->address_book);
	if(ab_count == 0)
	  new->type = DlcEmpty;
	else{
	    new->dlcelnum = ab_count - 1;
	    abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum,
		Normal);
	    if(abe->tag == Single)
	      new->type = DlcSimple;
	    else if(abe->tag == List){
		list_count = listmem_count_from_abe(abe);
		if(list_count == 0)
		  new->type = DlcListHead;
		else{
		    new->type      = DlcListEnt;
		    new->dlcoffset = list_count - 1;
		}
	    }
	}
    }

    return(new);
}


/*
 * Uses information in new to fill in new->dl.
 */
void
fill_in_dl_field(new)
DL_CACHE_S *new;
{
    AddrScrn_Disp *dl;
    PerAddrBook   *pab;
    char buf[MAX_SCREEN_COLS + 1];
    char buf2[1024];
    int screen_width = ps_global->ttyo->screen_cols;
    int len;

    buf[MAX_SCREEN_COLS] = '\0';
    screen_width = min(MAX_SCREEN_COLS, screen_width);

    dl = &(new->dl);

    /* free any allocated space */
    switch(dl->type){
      case Text:
      case Title:
	if(dl->txt)
	  fs_give((void **)&dl->txt);
    }

    /* set up new dl */
    switch(new->type){
      case DlcTitleBlankTop:
      case DlcTitleBlankBottom:
      case DlcListBlankTop:
      case DlcListBlankBottom:
	dl->type = Text;
	dl->txt  = cpystr("");
	break;

      case DlcTitleDashTop:
      case DlcTitleDashBottom:
	/* line of dashes in txt field */
	dl->type = Text;
	memset((void *)buf, '-', screen_width * sizeof(char));
	buf[screen_width] = '\0';
	dl->txt = cpystr(buf);
	break;

      case DlcNoPermission:
	dl->type = Text;
	dl->txt  = cpystr(NO_PERMISSION);
	break;

      case DlcTitle:
	dl->type = Title;
	pab = &as.adrbks[new->adrbk_num];
        /* title for this addrbook */
        memset((void *)buf, SPACE, screen_width * sizeof(char));
	buf[screen_width] = '\0';
        sprintf(buf2, "%s AddressBook <%s>",
		    (new->adrbk_num < as.how_many_personals) ?
			"Personal" :
			"Global",
                    pab->nickname ? pab->nickname : pab->filename);
        len = strlen(buf2);
        strncpy(buf, buf2, len);
        if(as.ro_warning){
            char *q;

            if(pab->access == ReadOnly){
                if(screen_width - len - (int)strlen(READONLY) > 3){
                    q = buf + screen_width - strlen(READONLY);
                    strcpy(q, READONLY);
                }
            }
            else if(pab->access == NoAccess){
                if(screen_width - len - (int)strlen(NOACCESS) > 3){
                    q = buf + screen_width - strlen(NOACCESS);
                    strcpy(q, NOACCESS);
                }
            }
        }
	dl->txt = cpystr(buf);
	break;

      case DlcClickHere:
	dl->type = ClickHere;
	break;

      case DlcEmpty:
	dl->type = Empty;
	break;

      case DlcSimple:
	dl->type  = Simple;
	dl->elnum = new->dlcelnum;
	break;

      case DlcListHead:
	dl->type  = ListHead;
	dl->elnum = new->dlcelnum;
	break;

      case DlcListEnt:
	dl->type     = ListEnt;
	dl->elnum    = new->dlcelnum;
	dl->l_offset = new->dlcoffset;
	break;

      case DlcBeginning:
      case DlcOneBeforeBeginning:
      case DlcTwoBeforeBeginning:
	dl->type = Beginning;
	break;

      case DlcEnd:
	dl->type = End;
	break;

      default:
	q_status_message(0, 5, 5,
	    "\007Bug in addrbook, not supposed to happen, re-syncing...");
	dprint(1,
	    (debugfile,
	    "Bug in addrbook, impossible dflt in fill_in_dl (%d)\n",
	    new->type));
	dump_a_dlc_to_debug("new", new);
	/* jump back to a safe starting point */
	longjmp(addrbook_changed_unexpectedly, 1);
	/*NOTREACHED*/
    }
}


/*
 * Args: start_disp     --  line to start displaying on when redrawing, 0 is
 *	 		    the top_of_screen
 *       cur_line       --  current line number (0 is 1st line we display)
 *       old_line       --  old line number
 *       redraw         --  flag requesting redraw as opposed to update of
 *			    current line
 *
 * Result: lines painted on the screen
 *
 * It either redraws the screen from line "start_disp" down or
 * moves the cursor from one field to another.
 */
void
display_book(start_disp, cur_line, old_line, redraw, fld_width)
int  start_disp,
     cur_line,
     old_line,
     redraw;
int  fld_width[3];
{
    int screen_row, highlight;
    long global_row;

    dprint(9, (debugfile,
	"- display_book() -\n   top %d start %d cur_line %d redraw %d\n",
	as.top_ent, start_disp, cur_line, redraw));

    if(as.l_p_page <= 0)
      return;

    if(redraw){
        /*--- Repaint all of the screen or bottom part of screen ---*/
        global_row = as.top_ent + start_disp;
        for(screen_row = start_disp;
	    screen_row < as.l_p_page;
	    screen_row++, global_row++){

            highlight = (screen_row == cur_line);
            MoveCursor(screen_row + HEADER_LINES, 0);
            CleartoEOLN();
            paint_line(screen_row + HEADER_LINES, global_row,
		highlight, fld_width);
        }
    }
    else{

        /*--- Only update current, or move the cursor ---*/
        if(cur_line != old_line){

            /*--- Repaint old position to erase "cursor" ---*/
            paint_line(old_line + HEADER_LINES, as.top_ent + old_line,
                       0, fld_width);
        }

        /*--- paint the position with the cursor ---*/
        paint_line(cur_line + HEADER_LINES, as.top_ent + cur_line,
                   1, fld_width);
    }
    fflush(stdout);
}


/*
 * Paint a line on the screen
 *
 * Args: line    --  Line on screen to paint, 0 is first available line
 *       dl      --  The display list ptr for this line
 *     highlight --  Line should be highlighted
 *
 * Result: Line is painted
 *
 * The three field widths for the formatting are passed in.  There is an
 * implicit 2 spaces between the fields.
 *
 *    | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] |
 */
void
paint_line(line, global_row, highlight, fld_width)
int  line;
long global_row;
int  highlight;
int  fld_width[3];
{
    int   fld1_pos,
	  fld2_pos,
	  col,
	  screen_width;
    char  fld0_control[11],
	  fld1_control[11],
	  fld2_control[11],
	  full_control[11];
    AddrScrn_Disp *dl;
    AdrBk_Entry   *abe;

    dprint(10, (debugfile, "- paint_line(%d, %d) -\n", line, highlight));

    dl = dlist(global_row);

    screen_width = ps_global->ttyo->screen_cols;

    fld1_pos = min(fld_width[0] + 2, screen_width - 1);
    fld2_pos = min(fld1_pos + fld_width[1] + 2, screen_width - 1);

    sprintf(fld0_control, "%%-%d.%ds  ", fld_width[0], fld_width[0]);
    sprintf(fld1_control, "%%-%d.%ds  ", fld_width[1], fld_width[1]);
    sprintf(fld2_control, "%%-%d.%ds",   fld_width[2], fld_width[2]);
    sprintf(full_control, "%%-%d.%ds",   screen_width, screen_width);


    switch(dl->type){

      case Text:
      case Title:
	/* center it */
	col = (screen_width - (int)strlen(dl->txt))/2;
	if(col >= 0)
	  PutLine0(line, col, dl->txt);
	else
	  PutLine1(line, 0, full_control, dl->txt);
	break;

      case ClickHere:
	if(highlight)
	  StartInverse();
	col = (screen_width - (int)strlen(CLICKHERE))/2;
	if(col >= 0)
	  PutLine0(line, col, CLICKHERE);
	else
	  PutLine1(line, 0, full_control, CLICKHERE);
	if(highlight)
	  EndInverse();
	break;
    
      case Empty:
	if(highlight)
	  StartInverse();
	col = (screen_width - (int)strlen(EMPTY))/2;
	if(col >= 0)
	  PutLine0(line, col, EMPTY);
	else
	  PutLine1(line, 0, full_control, EMPTY);
	if(highlight)
	  EndInverse();
	break;

      case Simple:
	if(highlight)
	  StartInverse();
	PutLine1(line, 0, fld0_control, ae(global_row)->nickname);
	if(fld_width[1] != 0){
	    char *fullname;

	    abe = ae(global_row);
	    fullname = abe->fullname ? abe->fullname : "";
	    PutLine1(line, fld1_pos, fld1_control, fullname);
	}
	if(fld_width[2] != 0){
	    char *addr;

	    abe = ae(global_row);
	    addr = (abe->tag == Single && abe->addr.addr) ? abe->addr.addr : "";
	    if(highlight)
	      StartInverse();
	    PutLine1(line, fld2_pos, fld2_control, addr);
	}
	if(highlight)
	  EndInverse();
	break;
    
      case ListHead:
	{char *fullname;

	abe = ae(global_row);
	fullname = abe->fullname ? abe->fullname : "";
	if(highlight)
	  StartInverse();
	PutLine1(line, 0, fld0_control, abe->nickname);
	if(fld_width[1] != 0)
	  PutLine1(line, fld1_pos, fld1_control, fullname);
	if(fld_width[2] != 0)
	  PutLine1(line, fld2_pos, fld2_control, DISTLIST);
	if(highlight)
	  EndInverse();
	}
	break;
        
      case ListEnt:
	if(line == HEADER_LINES){
	  char temp[50];
	  char *fullname;

	  abe = ae(global_row);
	  fullname = abe->fullname ? abe->fullname : "";
	  sprintf(temp,"%.18s (continued)", fullname);
	  PutLine1(line, fld1_pos, fld1_control, temp);
	}
	if(fld_width[2] != 0){
	    char *lm;

	    lm = listmem(global_row) ? listmem(global_row) : "";
	    if(highlight)
	      StartInverse();
	    PutLine1(line, fld2_pos, fld2_control, lm);
	    if(highlight)
	      EndInverse();
	}
	break;

      case Beginning:
      case End:
        break;
    }
}


/* to see if it changed */
static int old_fld_width[3];
/*
 * Set field widths for the three columns of the display.  The idea is to
 * try to come up with something that works pretty well.  Getting it just
 * right isn't important.
 *
 * Args: fld_width -- a 3 element array in which the answer is returned
 *
 * Col1 and col2 are arrays which contain some widths useful for
 * formatting the screen.  The 0th element is the max
 * width in that column.  The 1st element is the max of the third largest
 * width in each addressbook (yup, strange).
 *
 * Returns 1 if the widths changed since the last time called.
 */
int
calculate_field_widths(fld_width)
int fld_width[3];
{
    int col1[5], col2[5];
    int space_left;
    int ret = 0;
    int i;
    PerAddrBook *pab;
    int max_nick = 0, max_full = 0, max_addr = 0,
        max_third_full = 0, max_third_addr = 0;
    WIDTH_INFO_S *widths;

    dprint(9, (debugfile, "- calculate_field_widths -\n"));

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab->ostatus == Open || pab->ostatus == NoDisplay){
	    widths = &pab->address_book->widths;
	    max_nick = max(max_nick, widths->max_nickname_width);
	    max_full = max(max_full, widths->max_fullname_width);
	    max_addr = max(max_addr, widths->max_addrfield_width);
	    max_third_full =
		    max(max_third_full, widths->third_biggest_fullname_width);
	    max_third_addr =
		    max(max_third_addr, widths->third_biggest_addrfield_width);
	}
    }

    col1[0] = max(max_full, 2);
    col2[0] = max(max_addr, 2);
    col1[1] = max(max_third_full, 2);
    col2[1] = max(max_third_addr, 2);
    col1[2] = 3;
    col2[2] = 3;
    col1[3] = 2;
    col2[3] = 2;
    col1[4] = 1;
    col2[4] = 1;

    space_left = ps_global->ttyo->screen_cols;

    /* All of the nickname field should be visible, and make it at least 3. */
    fld_width[0] = min(max(max_nick, 3), space_left);

    /*
     * The 2 is for two blank columns between nickname and next field.
     * Those blank columns are taken automatically in paint_line().
     */
    space_left -= (fld_width[0] + 2);

    if(space_left > 0){
	for(i = 0; i < 5; i++){
	    /* try fitting most of each field in if possible */
	    if(col1[i] + 2 + col2[i] <= space_left){
		int extra;

		extra = space_left - col1[i] - 2 - col2[i];
		/* try to stabilize nickname column shifts */
		if(i == 0 && fld_width[0] < 7 && extra >= (7 - fld_width[0])){
		    extra -= (7 - fld_width[0]);
		    space_left -= (7 - fld_width[0]);
		    fld_width[0] = 7;
		}
		fld_width[2] = col2[i] + extra/2;
		fld_width[1] = space_left - 2 - fld_width[2];
		break;
	    }
	}
	/*
	 * None of them would fit.  Toss fld2.
	 */
	if(i == 5){
	    fld_width[1] = space_left;
	    fld_width[2] = 0;
	}
    }
    else{
	fld_width[1] = 0;
	fld_width[2] = 0;
    }

    dprint(10, (debugfile, "Using %s choice: %d %d %d", enth_string(i+1),
	fld_width[0], fld_width[1], fld_width[2]));

    for(i = 0; i < 3; i++){
	if(fld_width[i] != old_fld_width[i]){
	    ret++;  /* Tell the caller the screen changed */
	    old_fld_width[i] = fld_width[i];
	}
    }

    dprint(10, (debugfile, " %s\n", ret?"(a change)":"(unchanged)"));

    return(ret);
}


void
redraw_addr_screen()
{
    long current_line;
    int fld_width[3];

    dprint(7, (debugfile, "- redraw_addr_screen -\n"));

    as.l_p_page     = ps_global->ttyo->screen_rows -FOOTER_LINES -HEADER_LINES;
    if(as.l_p_page <= 0)
      return;
    current_line    = as.cur_row + as.top_ent;
    as.top_ent      = current_line - as.l_p_page/2;
    as.cur_row      = current_line - as.top_ent;
    as.old_cur_row  = as.cur_row;

    (void)calculate_field_widths(fld_width);

    display_book(0, as.cur_row, -1, 1, fld_width);
}


/*
 * Little front end for address book screen so it can be called out
 * of the main command loop in pine.c
 */
void
addr_book_screen(pine_state)
struct pine *pine_state;
{
    dprint(1, (debugfile, "\n\n --- ADDR_BOOK_SCREEN ---\n\n"));

    mailcap_free(); /* free resources we won't be using for a while */

    if(setjmp(addrbook_changed_unexpectedly)){
	q_status_message(0, 5, 5, "Resetting address book...");
	dprint(1, (debugfile, "RESETTING address book... addr_book_screen!\n"));
	flush_status_messages();
	addrbook_reset();
    }

    (void)addr_book(AddrBookScreen);
    end_adrbks();
    pine_state->prev_screen = addr_book_screen;
}


/*ARGSUSED*/
/*
 * Call address book from message composer
 *
 * Args: error_mess -- pointer to return error messages in (unused here)
 *
 * Returns: pointer to returned address, or NULL if nothing returned
 */
char *
addr_book_compose(error_mess)
char **error_mess;
{
    char *p;

    dprint(1, (debugfile, "--- addr_book_compose ---\n"));

    if(setjmp(addrbook_changed_unexpectedly)){
	q_status_message(0, 5, 5, "Resetting address book...");
	dprint(1,
	    (debugfile, "RESETTING address book... addr_book_compose!\n"));
	flush_status_messages();
	addrbook_reset();
    }

    p = addr_book(SelectAddr);

    end_adrbks();

    return(p);
}


/*
 * Call address book from take address screen
 *
 * Returns: pointer to returned nickname, or NULL if nothing returned
 *
 * The caller is assumed to handle the closing of the addrbooks, so we
 * don't call end_adrbks().
 */
char *
addr_book_takeaddr()
{
    char *p;

    dprint(1, (debugfile, "- addr_book_takeaddr -\n"));

    if(setjmp(addrbook_changed_unexpectedly)){
	q_status_message(0, 5, 5, "Resetting address book...");
	dprint(1,
	    (debugfile, "RESETTING address book...addr_book_takeaddr!\n"));
	flush_status_messages();
	addrbook_reset();
    }

    p = addr_book(SelectNick);

    return(p);
}


static struct key ab_keys[] =
     {{"?","Help",0},       {"O","OTHER CMDS",0},   {NULL,NULL,0},
      {NULL,NULL,0},        {"P","PrevEntry",0},    {"N","NextEntry",0},
      {"-","PrevPage",0},   {"Spc","NextPage",0},   {"D","Delete",0},
      {"A","Add",0},        {"S","CreateList",0},   {NULL,NULL,0},
      {"?","Help",0},       {"O","OTHER CMDS",0},   {"Q","Quit",0},
      {"C","ComposeTo",0},  {"L","ListFldrs",0},    {"G","GotoFldr",0},
      {"I","Index",0},      {"W","WhereIs",0},      {"Y","prYnt",0},
      {NULL,NULL,0},        {NULL,NULL,0},          {NULL,NULL,0}};
static struct key_menu ab_keymenu =
	{sizeof(ab_keys)/(sizeof(ab_keys[0])*12), 0, 0,0,0,0, ab_keys};
#define OTHER_KEY  1
#define MAIN_KEY   2
#define EDIT_KEY   3
#define DELETE_KEY 8
#define ADD_KEY    9
#define CREATE_KEY 10
#define ADDTO_KEY  11


/*
 *  Main address book screen 
 *
 * Control loop for address book.  Commands are executed out of a big
 * switch and screen painting is done.
 * The argument controls whether or not it is called to return an address
 * to the composer, a nickname to the TakeAddr screen, or just for address
 * book maintenance.
 *
 * Args: style -- how we were called
 *
 * Return: might return a string for the composer to edit (if SelectAddr style)
 *         or a nickname (if SelectNick).
 */
char *
addr_book(style)
AddrBookArg style;
{
    int		     r, c, orig_c,
		     command_line,
		     did_delete_flag,
                     quit,           /* loop control                         */
		     current_changed_flag,  /* only current row needs update */
		     was_clickable_last_time, /* on CLICKHERE last time thru */
		     mangled_screen, /*                                      */
		     mangled_header, /* These track whether or not we        */
		     mangled_footer, /* need to repaint the various parts    */
		     mangled_body,   /* of the screen.                       */
		     start_disp,     /* Paint from this line down (0 is top) */
		     rdonly,         /* cur addrbook read only               */
		     empty,          /* cur addrbook empty                   */
		     noaccess,       /* cur addrbook permission denied       */
		     are_selecting,  /* called as ^T selector                */
                     warped;         /* we warped through hyperspace to a
				        new location in the display list     */
    long	     fl,
		     new_ent,
		     new_top_ent,    /* entry on top of screen after oper    */
		     new_line;       /* new line number after operation      */
    char            *title,
                    *addr;
    bitmap_t         bitmap;
    struct key_menu *km;
    OtherMenu        what;
    int              fld_width[3];
    PerAddrBook     *pab;


    dprint(1, (debugfile, "--- addr_book ---  (%s)\n",
			    style==SelectAddr     ? "SelectAddr" :
			     style==SelectNick     ? "SelectNick" :
			      style==AddrBookScreen ? "AddrBookScreen" :
						       "UnknownStyle"));

    km = &ab_keymenu;

    are_selecting = (style == SelectAddr || style == SelectNick);

    if(style == SelectAddr){
        /* Coming in from the composer, need to reset this stuff */
        get_windsize(ps_global->ttyo);
        init_signals();
        clear_cursor_pos();
        mark_status_dirty();
    }

    command_line = -1 * FOOTER_LINES;  /* third line from the bottom */
    title        = (style == AddrBookScreen) ? "ADDRESS BOOK" :
		    (style == SelectAddr)     ? "COMPOSER: SELECT ADDRESS" :
		     (style == SelectNick)     ? "TAKEADDR: SELECT NICKNAME" :
						  "UNKNOWN SCREEN STYLE!";
    what         = FirstMenu;

    if(!init_addrbooks(HalfOpen, 1, 1, !are_selecting)){
	q_status_message(1, 0, 3, "\007No Address Book Configured");
	if(!are_selecting)
	  ps_global->next_screen = ps_global->prev_screen;
        return NULL;
    }

    quit                     = 0;
    mangled_screen           = 1;
    mangled_header           = 1;
    mangled_body             = 1;
    mangled_footer           = 1;
    start_disp               = 0;
    current_changed_flag     = 0;
    was_clickable_last_time  = 0; /* doesn't matter what it is */

    c = 'x'; /* For display_message the first time through */


    do{
	ps_global->redrawer = redraw_addr_screen;

        if(new_mail(NULL, 0, c == NO_OP_IDLE ? 0 : 2) >= 0)
	  mangled_header++;

        if(streams_died())
          mangled_header++;

	if(mangled_screen){
	    ClearScreen();
	    mangled_header++;
	    mangled_body++;
	    mangled_footer++;
	    start_disp     = 0;
	    mangled_screen = 0;
	}

	if(mangled_header){
            set_titlebar(title, ps_global->mail_stream,
                         ps_global->context_current, ps_global->cur_folder,
                         ps_global->msgmap, 1,
                         are_selecting ? FolderName : MessageNumber, 0, 0);
	    mangled_header = 0;
	}

	if(mangled_body){
	    int old_cur;

	    if(calculate_field_widths(fld_width))
	      start_disp = 0;

	    display_book(start_disp,
			 as.cur_row,  
			 as.old_cur_row,  
			 1,
			 fld_width);
	    as.old_cur_row = as.cur_row;
	    mangled_body   = 0;
            start_disp     = 0;
	    old_cur        = as.cur;
	    as.cur         = cur_addr_book();
	    pab            = &as.adrbks[as.cur];
	    if(as.cur != old_cur)
	      q_status_message1(0, 0, 2, "Now in addressbook %s",
						pab->nickname);
	}
	/* current entry has been changed */
	else if(current_changed_flag){
	    int old_cur;
	    int need_redraw;

	    need_redraw = calculate_field_widths(fld_width);

	    /*---------- Update the current entry, (move or change) -------*/
	    display_book(as.cur_row,
			 need_redraw ? 0 : as.cur_row,  
			 as.old_cur_row,  
			 need_redraw,
			 fld_width);
	    as.old_cur_row       = as.cur_row;
	    current_changed_flag = 0;
	    old_cur              = as.cur;
	    as.cur               = cur_addr_book();
	    pab                  = &as.adrbks[as.cur];
	    if(as.cur != old_cur)
	      q_status_message1(0, 0, 2, "Now in addressbook %s",
						pab->nickname);
        }

	dprint(9, (debugfile, "addr_book: top of loop, addrbk %d top_ent %ld cur_row %d\n", as.cur, as.top_ent, as.cur_row));

	/*
	 * This is a check to catch the case where we move from a non-
	 * clickable row into a clickable row or vice versa.  That means
	 * the footer changes.
	 */
	if(!mangled_footer && ((was_clickable_last_time &&
		!entry_is_clickable(as.top_ent+as.cur_row)) ||
	   (!was_clickable_last_time &&
		entry_is_clickable(as.top_ent+as.cur_row))))
	    mangled_footer++;
	was_clickable_last_time = entry_is_clickable(as.top_ent+as.cur_row);

        if(mangled_footer){

	    setbitmap(bitmap);
	    if(are_selecting){
		km->how_many = 1;
		ab_keys[MAIN_KEY].name   = "E";
		ab_keys[MAIN_KEY].label  = "ExitSelect";
		ab_keys[EDIT_KEY].name   = "S";
		ab_keys[EDIT_KEY].label  = "[Select]";
		ab_keys[ADDTO_KEY].name  = "W";
		ab_keys[ADDTO_KEY].label = "WhereIs";
		clrbitn(OTHER_KEY, bitmap);
		clrbitn(DELETE_KEY, bitmap);
		clrbitn(ADD_KEY, bitmap);
		clrbitn(CREATE_KEY, bitmap);
	    }
	    else{
		km->how_many = 2;
		ab_keys[MAIN_KEY].name   = "M";
		ab_keys[MAIN_KEY].label  = "MainMenu";
		if(entry_is_clickable(as.top_ent+as.cur_row)){
		    ab_keys[EDIT_KEY].name   = "S";
		    ab_keys[EDIT_KEY].label  = "[Select]";
		    clrbitn(CREATE_KEY, bitmap);
		}
		else{
		    ab_keys[EDIT_KEY].name   = "E";
		    ab_keys[EDIT_KEY].label  = "[Edit]";
		}
		ab_keys[ADDTO_KEY].name  = "Z";
		ab_keys[ADDTO_KEY].label = "AddToList";
	    }

	    draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
						    -2, 0, what, 0);
	    mangled_footer = 0;
	    what           = SameTwelve;
	}

	rdonly   = (pab->access == ReadOnly);
	noaccess = (pab->access == NoAccess);
	empty    = is_empty(as.cur_row+as.top_ent);
	if(as.no_op_possbl){
	    q_status_message1(0, 0, 1,
		"No addrbook operations possible, use %s to exit",
		are_selecting ? "ExitSelect" : "MainMenu");
	}

	/*------------ display any status messages ------------------*/
	display_message(c);
	MoveCursor(max(0, ps_global->ttyo->screen_rows - FOOTER_LINES), 0);
	fflush(stdout);


	/*---------------- Get command and validate -------------------*/
	c = read_command();
        orig_c = c;

	if(c == ctrl('M') || c == ctrl('J')) /* set up default */
	  if(F_ON(F_USE_FK,ps_global))
	    c = PF4;
	  else
	    c = (are_selecting || entry_is_clickable(as.top_ent+as.cur_row))
									? 's'
									: 'e';
	if(c < 'z' && isupper(c))
	  c = tolower(c);

	if(km->which == 1 && c >= PF1 && c <= PF12)
          c = PF2OPF(c);

        c = validatekeys(c); 

        dprint(5, (debugfile, "Addrbook command :'%c' (%d)\n", c, c));


	/*------------- execute command ----------------*/
	switch(c){

            /*------------ Noop   (new mail check) --------------*/
          case NO_OP_IDLE:
	  case NO_OP_COMMAND: 
	    break;


            /*----------- Help -------------------*/
	  case PF1:
	  case OPF1:
	  case '?':
	  case ctrl('G'):
	    /*
	     * We need this next_screen test in order that helper() can
	     * have a return to Main Menu key.  If helper is called with
	     * a third argument of 1, that isn't one of the possibilities.
	     */
	    if(!are_selecting)
	      ps_global->next_screen = SCREEN_FUN_NULL;
	    if(style == SelectAddr)
              helper(h_use_address_book, "HELP ON ADDRESS BOOK", 1);
	    else if(style == SelectNick)
              helper(h_select_nickname, "HELP ON ADDRESS BOOK", 1);
            else
              helper(h_address_book, "HELP ON ADDRESS BOOK", 0);
	    if(!are_selecting && ps_global->next_screen != SCREEN_FUN_NULL)
              quit = 1;
	    mangled_screen++;
	    break;

             
            /*---------- display other key bindings ------*/
          case PF2:
          case OPF2:
          case 'o' :
            if(are_selecting)
              goto bleep;
            if(c == 'o')
	      warn_other_cmds();
            what = NextTwelve;
            mangled_footer++;
            break;


            /*------------- Back to main menu or exit to caller -------*/
	  case PF3:
	  case 'm':
	  case 'e':
	    if(c == 'm' && are_selecting)
	      goto bleep;
	    if(c == 'e' && !are_selecting)
	      goto edit;
	    if(!are_selecting)
              ps_global->next_screen = main_menu_screen;
	    quit = 1;
	    break;


            /*------------ Edit field or select -------------*/
	  case PF4:
select:
	    if(entry_is_clickable(as.top_ent+as.cur_row)){
		DL_CACHE_S *dlc_to_flush;

	        /*
		 * open this addrbook and fill in display list
		 */

		/* flush the CLICKHERE line from dlc cache */
		dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
		flush_dlc_from_cache(dlc_to_flush);
		init_abook(pab, Open);
		if(!are_selecting && pab->access == ReadOnly)
		  readonly_warning(NO_DING, pab->nickname);
		/* redraw from here down */
		start_disp = as.cur_row;
                mangled_body++;
		break;
	    }
	    if(are_selecting){
              /* Select an entry to mail to or a nickname to add to */
	      if(!any_addrs_avail(as.top_ent+as.cur_row)){
	          q_status_message(0, 1, 3,
        "\007No entries in address book. Use ExitSelect to leave address book");
	          break;
	      }
	      if(is_addr(as.top_ent+as.cur_row)){
		  char *error = "";
		  AddrScrn_Disp *dl;

		  dl = dlist(as.top_ent+as.cur_row);

		  if(style == SelectAddr){
		      if(dl->type == ListEnt)
			  (void)our_build_address(
					listmem_from_dl(pab->address_book, dl),
					&addr,
					&error,
					NULL,
					1);
		      else
			  /*
			   * This may get us the wrong lookup, but we're willing
			   * to live with that.  That is, if nickname is an
			   * entry in two of our addrbooks and we are in the
			   * second now, this lookup will get the first anyway,
			   * because our_build_address() just goes through all
			   * the addrbooks and takes the first.  We could
			   * call adrbk_lookup_by_nick on the current addrbook
			   * first and that would give us the correct address
			   * or another nickname to expand on further, but we
			   * would lose the fullname that way.  We could have
			   * our_build_address() just look in the current
			   * addrbook, but then the recursive expands would
			   * also be limited to that one addrbook.  This is
			   * probably the best.
			   */
			  (void)our_build_address(
				  ae(as.top_ent+as.cur_row)->nickname,
				  &addr,
				  &error,
				  NULL,
				  1);
		      if(*error){
			  q_status_message1(0, 0, 4, "%s", error);
			  display_message(NO_OP_COMMAND);
			  sleep(1);
		      }
		      return(addr);  /* Note, composer frees this */
		  }
		  else{
		      static char selected_nickname[MAX_NICKNAME + 1];

		      strncpy(selected_nickname,
			  ae(as.top_ent+as.cur_row)->nickname,
			  MAX_NICKNAME);
		      return(selected_nickname);
		  }
	      }
	      else{
	          q_status_message1(0, 1, 3, "No %s selected",
		      (style == SelectAddr) ? "address" : "nickname");
	          break;
	      }
	    }
edit:
	    if(!any_addrs_avail(as.top_ent+as.cur_row)){
                q_status_message(1, 1, 4, "No entries to edit");
                break;
            }
	    if(entry_is_clickable(as.top_ent+as.cur_row)){
                q_status_message(1, 1, 4,
		    "AddressBook not expanded, use \"S\" to expand");
		break;
	    }
	    if(rdonly){
		readonly_warning(NO_DING, NULL);
                break;
	    }
	    if(empty){
		empty_warning();
                break;
	    }
	    new_ent = edit_a_field(pab->address_book,
				       command_line,
				       as.cur_row+as.top_ent, &warped);
            mangled_footer++;
            if(new_ent == NO_LINE)
	      break;

	    /*----- Edit succeeded, now update the screen -----*/
            if(new_ent == CHANGED_CURRENT){
                /* changed only current entry */
                current_changed_flag++;
                break;
            }
	    /*
	     * warped means we got plopped down somewhere in the display
	     * list so that we don't know where we are relative to where
	     * we were before we warped.
	     */
	    if(warped){
		as.top_ent = first_line(new_ent - as.l_p_page/2);
		as.cur_row = new_ent - as.top_ent;
		start_disp = 0;
	    }
	    else{
		/* try to keep top_ent the same */
		as.cur_row = new_ent - as.top_ent;
		if(as.cur_row >= 0 && as.cur_row < as.l_p_page){
		    start_disp = min(as.old_cur_row, as.cur_row);
		}
		else{
		    new_top_ent = first_line(new_ent - as.l_p_page/2);
		    as.cur_row  = new_ent - new_top_ent;
		    as.top_ent  = new_top_ent;
		    start_disp  = 0;
		}
	    }
	    mangled_body++;
	    break;


            /*-------------- Move Right, or Down --------------*/
          case PF6:
          case ctrl('N'):
          case KEY_DOWN:
	  case ctrl('F'): 
	  case KEY_RIGHT:
          case '\t':
          case 'n':
	    if(any_addrs_avail(as.top_ent+as.cur_row)){

		r = next_selectable_line(as.cur_row+as.top_ent, &new_line);
		if(r == 0){
		    q_status_message(0, 0, 1, "Already on last line.");
		    break;
		}

                as.cur_row = new_line - as.top_ent;
                if(as.cur_row >=  as.l_p_page){
                    /*-- Changed pages --*/
                    as.top_ent += as.l_p_page;
                    as.cur_row -= as.l_p_page;
                    mangled_body++;
                }
		else
                  current_changed_flag++;
	    }
	    else
	      empty_warning();
	    break;


            /*------------------- Move Left, or Up ----------------*/
          case PF5:
          case ctrl('P'):
          case KEY_UP:
	  case ctrl('B'):
	  case KEY_LEFT:
	  case 'p':
	    if(any_addrs_avail(as.top_ent+as.cur_row)){

		r = prev_selectable_line(as.cur_row+as.top_ent, &new_line);
		if(r == 0){
		    q_status_message(0, 0, 1, "Already on first line.");
		    break;
		}
                as.cur_row = new_line - as.top_ent;
                if(as.cur_row < 0){
		    new_top_ent = first_line(as.top_ent - as.l_p_page);
                    as.cur_row += (as.top_ent - new_top_ent);
                    as.top_ent  = new_top_ent;
                    mangled_body++;
                }
		else
                  current_changed_flag++;
	    }
	    else
	      empty_warning();
	    break;


            /*------------- Page Up ----------------*/
	  case '-':
          case ctrl('Y'): 
	  case PF7:
	  case KEY_PGUP:
            /*------------- Page Down --------------*/
          case ' ':
          case ctrl('V'): 
          case '+':		    
	  case PF8:
	  case KEY_PGDN:
	    /* if Up */
	    if(c == '-' || c == ctrl('Y') || c == PF7 || c == KEY_PGUP){
		/* find first line on prev page */
		new_top_ent = first_line(as.top_ent - as.l_p_page);
		if(new_top_ent == NO_LINE)
		    break;
		/* find first selectable line */
		fl = first_selectable_line(new_top_ent);
		if(fl == NO_LINE)
		    break;
		if(as.top_ent == new_top_ent && as.cur_row == (fl-as.top_ent)){
		    q_status_message(0, 0, 1, "Already on first page.");
		    break;
		}
		if(as.top_ent == new_top_ent)
                    current_changed_flag++;
		else
		    as.top_ent = new_top_ent;
	    }
	    /* else Down */
	    else{
		/* find first selectable line on next page */
		fl = first_selectable_line(as.top_ent + as.l_p_page);
		if(fl == NO_LINE)
		    break;
		/* if there is another page, scroll */
		if(fl - as.top_ent >= as.l_p_page){
		    new_top_ent = as.top_ent + as.l_p_page;
		}
		/* on last page already */
		else{
		    new_top_ent = as.top_ent;
		    if(as.cur_row == (fl - as.top_ent)){ /* no change */
			q_status_message(0, 0, 1,
			    "Already on last page.");
			break;
		    }
		}
		if(as.top_ent == new_top_ent)
                    current_changed_flag++;
		else
		    as.top_ent = new_top_ent;
	    }

	    as.cur_row = fl - as.top_ent;
	    if(!current_changed_flag){
		mangled_body++;
		start_disp  = 0;
	    }
	    break;


	    /*------------- Delete item from addrbook ---------*/
	  case PF9:
	  case 'd': 
	    if(are_selecting)
	      goto bleep;
	    if(!any_addrs_avail(as.top_ent+as.cur_row)){
                q_status_message(1, 0, 4, "No entries to delete");
                break;
	    }
	    if(entry_is_clickable(as.top_ent+as.cur_row)){
                q_status_message(1, 1, 4,
		    "AddressBook not expanded, use \"S\" to expand");
		break;
	    }
	    if(rdonly){
		readonly_warning(NO_DING, NULL);
		break;
	    }
	    if(empty){
		empty_warning();
                break;
	    }
            did_delete_flag = addr_book_delete(pab->address_book,
					       command_line,
                                               as.cur_row+as.top_ent);
	    mangled_footer++;
	    if(did_delete_flag){
		/*
		 * In case the line we're now at is not a selectable
		 * field (i.e., it is a Text or Title field).
		 */
		new_line = first_selectable_line(as.cur_row+as.top_ent);
		if(new_line != NO_LINE && new_line != as.cur_row+as.top_ent){
                    as.cur_row = new_line - as.top_ent;
                    if(as.cur_row < 0){
                        as.top_ent -= as.l_p_page;
                        as.cur_row += as.l_p_page;
                    }
		    else if(as.cur_row >= as.l_p_page){
                        as.top_ent += as.l_p_page;
                        as.cur_row -= as.l_p_page;
		    }
		}
		start_disp = min(as.cur_row, as.old_cur_row);
	        mangled_body++;
	    }
            break;


            /*-------- Add a Simple entry to addrbook ------*/
          case PF10:
 	  case 'a':
            /*--------------- Create a new list -------------------*/
          case PF11:
          case 's':
            /*--------------- Add entry to existing list ----------*/
          case PF12:
          case 'z':
	    if(c == 's' &&
	        (are_selecting || entry_is_clickable(as.top_ent+as.cur_row)))
	        goto select;
	    /*------ Whereis (when selecting PF12 is Whereis)------*/
	    if(are_selecting && c == PF12){
		warped = 0;
		new_top_ent = ab_whereis(&warped, command_line);

		if(new_top_ent != NO_LINE){
		    if(warped || new_top_ent != as.top_ent){
			as.top_ent     = new_top_ent;
			start_disp     = 0;
			mangled_body++;
		    }
		    else
			current_changed_flag++;
		}
		mangled_footer++;
		break;
	    }
	    if(are_selecting)
	      goto bleep;
	    if(entry_is_clickable(as.top_ent+as.cur_row)){
                q_status_message(1, 1, 4,
		    "AddressBook not expanded, use \"S\" to expand");
		break;
	    }
	    if(rdonly){
		readonly_warning(NO_DING, NULL);
		break;
	    }
	    if(noaccess){
                q_status_message(1, 1, 4, "\007Permission Denied");
		break;
	    }
	    warped = 0;
	    new_ent = (c == 's' || c == PF11) ?
                           create_list(pab->address_book,
				       command_line,
				       &warped)
                    : (c == 'z' || c == PF12) ?
                           addr_to_list(pab->address_book,
                                        command_line,
                                        as.top_ent+as.cur_row)
                    :
                           simple_add(pab->address_book,
				      command_line,
				      &warped);
	    mangled_footer++;
            if(new_ent == NO_LINE){
                /*---- didn't succeed ----*/
		break;
	    }

	    /*----- Addition succeeded, now update the screen -----*/
	    if(warped){
		as.top_ent = first_line(new_ent - as.l_p_page/2);
		as.cur_row = new_ent - as.top_ent;
		start_disp = 0;
	    }
	    else{
		/* try to keep top_ent the same */
		as.cur_row = new_ent - as.top_ent;
		if(as.cur_row >= 0 && as.cur_row < as.l_p_page){
		    start_disp = min(as.old_cur_row, as.cur_row);
		}
		else{
		    new_top_ent = first_line(new_ent - as.l_p_page/2);
		    as.cur_row  = new_ent - new_top_ent;
		    as.top_ent  = new_top_ent;
		    start_disp  = 0;
		}
	    }
	    mangled_body++;
	    break;


            /*--------- QUIT pine -----------*/
          case OPF3:
	  case 'q':
            if(are_selecting)
              goto bleep;
            ps_global->next_screen = quit_screen;
	    quit = 1;
            break;


            /*--------- Compose -----------*/
          case OPF4:
	  case 'c':
            if(are_selecting)
              goto bleep;
	    ab_compose_to_addr();
	    mangled_screen++;
	    /* window size may have changed in composer */
	    ab_resize(0);
            break;


            /*--------- Folders -----------*/
          case OPF5:
	  case 'l':
            if(are_selecting)
              goto bleep;
            ps_global->next_screen = folder_screen;
	    quit = 1;
            break;


            /*---------- Open specific new folder ----------*/
          case OPF6:
	  case 'g':
            if(are_selecting || ps_global->nr_mode)
              goto bleep;
	    ab_goto_folder(command_line);
	    quit = 1;
            break;


            /*--------- Index -----------*/
          case OPF7:
	  case 'i':
            if(are_selecting)
              goto bleep;
            ps_global->next_screen = mail_index_screen;
	    quit = 1;
            break;


            /*----------- Where is (search) ----------------*/
	  case OPF8:
	  case 'w':
	  case ctrl('W'):

	    if(c == OPF8 && are_selecting)
	      goto bleep;

	    warped = 0;
	    new_top_ent = ab_whereis(&warped, command_line);

	    if(new_top_ent != NO_LINE){
		if(warped || new_top_ent != as.top_ent){
		    as.top_ent     = new_top_ent;
		    start_disp     = 0;
		    mangled_body++;
		}
		else
		    current_changed_flag++;
	    }
	    mangled_footer++;
	    break;


	    /*----------------- Print --------------------*/
	  case OPF9: 
	  case 'y':
	    ab_print();
	    mangled_screen++;
            break;


            /*------------ Suspend pine  (^Z) -------------*/
          case ctrl('Z'):
            if(!have_job_control())
                goto bleep;
            if(F_OFF(F_CAN_SUSPEND,ps_global)){
                q_status_message(1, 1, 4, 
                          "Pine suspension not enabled - see help text");
                break;
            }
	    else
                do_suspend(ps_global);
	    ab_resize(0);
            mangled_screen++;
	    break;


          case KEY_RESIZE:
          case ctrl('L'):
	    ab_resize(c == KEY_RESIZE);
            mangled_screen++;
	    break;


          case ctrl('C'):
	    if(!are_selecting)
	        goto bleep;
	    quit = 1;
	    break;


	  default:
          bleep:
	    bogus_command(orig_c, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
	    break;
	}
    }while(!quit);
    
    return NULL;
}


/*
 * Post a readonly addrbook warning.
 *
 * Args: bell -- Ring the bell
 *       name -- Include this addrbook name in warning.
 */
void
readonly_warning(bell, name)
int        bell;
char      *name;
{
    char warning[500];

    sprintf(warning, "%sAddressBook%s%s is Read Only",
	bell ? "\007" : "",
	name ? " " : "",
	name ? name : "");

    q_status_message(0, 1, 3, warning);
}


/*
 * Post an empty addrbook warning.
 */
void
empty_warning()
{
    q_status_message(0, 1, 3, "AddressBook is Empty");
}


/*
 * Post a cancellation warning.
 *
 * Args: bell -- Ring the bell
 *       what -- Text to display
 */
void
cancel_warning(bell, what)
int   bell;
char *what;
{
    char warning[500];

    sprintf(warning, "%sAddress book %s cancelled",
	bell ? "\007" : "",
	what);

    q_status_message(0, 0, 2, warning);
}


/*
 * Post a no tabs warning.
 */
void
no_tabs_warning()
{
    q_status_message(0, 2, 2, "Tabs not allowed in address book");
}


/*
 * Validate selected address with build_address, save addrbook state,
 * call composer, restore addrbook state.
 */
void
ab_compose_to_addr()
{
    int		   good_addr;
    char          *addr,
		  *error,
		  *fcc;
    AddrScrn_Disp *dl;
    AdrBk_Entry   *abe;
    SAVE_STATE_S   state;  /* For saving state of addrbooks temporarily */

    dprint(2, (debugfile, "- ab_compose_to_addr -\n"));

    save_state(&state);

    fcc  = NULL;
    addr = NULL;

    if(is_addr(as.top_ent+as.cur_row)){

	dl  = dlist(as.top_ent+as.cur_row);
	abe = ae(as.top_ent+as.cur_row);

	error = "";

	if(dl->type == ListEnt)
	  good_addr = (our_build_address(
		      listmem(as.top_ent+as.cur_row),
		      &addr,
		      &error,
		      &fcc,
		      0) >= 0);
	else
	  good_addr = (our_build_address(
		      abe->nickname,
		      &addr,
		      &error,
		      &fcc,
		      0) >= 0);
	if(*error){
	    q_status_message1(0, 1, 4, "%s", error);
	    display_message(NO_OP_COMMAND);
	}
	if(!good_addr)
	  fs_give((void **)&addr); /* relying on fs_give setting
					addr to NULL */
    }

    compose_mail(addr, fcc);

    restore_state(&state);

    if(addr)
      fs_give((void **)&addr);
    if(fcc)
      fs_give((void **)&fcc);
}


/*
 * Go to folder.
 *
 *       command_line -- The screen line on which to prompt
 */
void
ab_goto_folder(command_line)
int command_line;
{
    char *go_folder;
    CONTEXT_S *tc;

    dprint(2, (debugfile, "- ab_goto_folder -\n"));

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

    go_folder = broach_folder(command_line, 1, &tc);
#if	defined(DOS) && !defined(_WINDOWS)
    if(go_folder && *go_folder == '{' && coreleft() < 20000){
	q_status_message(0, 0, 2, "\007Not enough memory to open IMAP folder");
	go_folder = NULL;
    }
#endif /* !DOS */
    if(go_folder != NULL){
	dprint(9, (debugfile, "do_broach_folder (%s, %s)\n",
		 go_folder, tc->context));
	if(do_broach_folder(go_folder, tc) > 0)
	    /* so we go out and come back into the index */
	    ps_global->next_screen = mail_index_screen;
    }
}


/*
 * Execute whereis command.
 *
 * Returns value of the new top entry, or NO_LINE if cancelled.
 */
long
ab_whereis(warped, command_line)
int *warped;
int  command_line;
{
    int rc, wrapped = 0;
    long new_top_ent, new_line;

    dprint(2, (debugfile, "- ab_whereis -\n"));

    rc = search_book(as.top_ent+as.cur_row, command_line,
		    &new_line, &wrapped, warped);

    new_top_ent = NO_LINE;

    if(rc == -2)
      cancel_warning(NO_DING, "search");

    else if(rc == -1)
      q_status_message(0, 0, 2, "Word not found");

    else if(rc == 0){  /* search succeeded */

	if(wrapped)
	  q_status_message(0, 0, 2, "Search wrapped to beginning");

	/* know match is on the same page */
	if(!*warped &&
	    new_line >= as.top_ent &&
	    new_line < as.top_ent+as.l_p_page)
	    new_top_ent = as.top_ent;
	/* don't know whether it is or not, reset top_ent */
	else
	  new_top_ent = first_line(new_line - as.l_p_page/2);
	as.cur_row  = new_line - new_top_ent;
    }

    return(new_top_ent);
}


/*
 * Print out the display list.
 */
void
ab_print()
{
    AddrScrn_Disp *dl; 
    long lineno;
    AdrBk_Entry *abe;
    long save_line;
    DL_CACHE_S dlc_buf, *match_dlc;
    char *fullname, *addr, *lm; 

    if(open_printer("address books ") == 0){

	save_line = as.top_ent + as.cur_row;
	match_dlc = get_dlc(save_line);
	dlc_buf   = *match_dlc;
	match_dlc = &dlc_buf;

	warp_to_beginning();
	lineno = 0L;

	for(dl = dlist(lineno);
	    dl->type != End;
	    dl = dlist(++lineno)){
	    switch(dl->type){
	      case Simple:
		abe = ae(lineno);
		fullname = abe->fullname ? abe->fullname : "";
		addr = (abe->tag == Single && abe->addr.addr)
			    ? abe->addr.addr : "";
		print_text3("%-10.10s %-35.35s %s\n",
			    abe->nickname, fullname, addr);
		break;

	      case ListHead:
		abe = ae(lineno);
		fullname = abe->fullname ? abe->fullname : "";
		print_text3("%-10.10s %-35.35s %s\n",
		    abe->nickname, fullname, DISTLIST);
		break;

	      case ListEnt:
		lm = listmem(lineno) ? listmem(lineno) : "";
	print_text1("                                               %s\n", lm);
		break;

	      case ClickHere:
		print_text1("%s\n", CLICKHERE);
		break;

	      case Empty:
		print_text1("%s\n", EMPTY);
		break;

	      case Text:
	      case Title:
		print_text1("%s\n", dl->txt);
		break;
	    }
	}
	close_printer();

	/*
	 * jump cache back to where we started so that the next
	 * request won't cause us to page through the whole thing
	 */
	warp_to_dlc(match_dlc, save_line);
    }
}


/*
 * recalculate display parameters for window size change
 *
 * Args: clear_i_cache -- call clear_index_cache() if set
 */
void
ab_resize(clear_i_cache)
int clear_i_cache;
{
    long new_line;
    int  old_l_p_p;
    DL_CACHE_S dlc_buf, *dlc_restart;

    old_l_p_p   = as.l_p_page;
    as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_LINES - HEADER_LINES;

    dprint(9, (debugfile, "- ab_resize -\n    l_p_p was %d, now %d\n",
	old_l_p_p, as.l_p_page));

    if(as.l_p_page <= 0)
      return;

    new_line       = as.top_ent + as.cur_row;
    as.top_ent     = first_line(new_line - as.l_p_page/2);
    as.cur_row     = new_line - as.top_ent;
    as.old_cur_row = as.cur_row;

    /* need this to re-initialize Text and Title lines in display */
    /* get the old current line (which may be the wrong width) */
    dlc_restart = get_dlc(new_line);
    /* flush it from cache */
    flush_dlc_from_cache(dlc_restart);
    /* re-get it (should be right now) */
    dlc_restart = get_dlc(new_line);
    /* copy it to local storage */
    dlc_buf = *dlc_restart;
    dlc_restart = &dlc_buf;
    /* flush everything from cache and add that one line back in */
    warp_to_dlc(dlc_restart, new_line);
    if(clear_i_cache)
      clear_index_cache();
}


/*
 * Returns 0 if we know for sure that there are no
 * addresses available in any of the addressbooks.
 *
 * Easiest would be to start at 0 and go through the addrbook, but that will
 * be very slow for big addrbooks if we're not close to 0 already.  Instead,
 * starting_hint is a hint at a good place to start looking.
 */
int
any_addrs_avail(starting_hint)
long starting_hint;
{
    register AddrScrn_Disp *dl;
    long lineno;

    /*
     * Look from lineno backwards first, in hopes of finding it in cache.
     */
    lineno = starting_hint;
    for(dl=dlist(lineno);
	dl->type != Beginning;
	dl = dlist(--lineno)){
	switch(dl->type){
	  case Simple:
	  case ListEnt:
	  case ListHead:
	  case ClickHere:
	    return 1;
	}
    }

    /* search from here forward if we still don't know */
    lineno = starting_hint;
    for(dl=dlist(lineno);
	dl->type != End;
	dl = dlist(++lineno)){
	switch(dl->type){
	  case Simple:
	  case ListEnt:
	  case ListHead:
	  case ClickHere:
	    return 1;
	}
    }

    return 0;
}


/*
 * Returns 1 if this line is a clickable line.
 */
int
entry_is_clickable(lineno)
long lineno;
{
    register AddrScrn_Disp *dl;

    if((dl = dlist(lineno)) && dl->type == ClickHere)
      return 1;

    return 0;
}


/*
 * Returns 1 if an address or list is selected.
 */
int
is_addr(lineno)
long lineno;
{
    register AddrScrn_Disp *dl;

    if((dl = dlist(lineno)) && (dl->type == ListHead ||
					dl->type == ListEnt  ||
					dl->type == Simple))
	return 1;

    return 0;
}


/*
 * Returns 1 if type of line is Empty.
 */
int
is_empty(lineno)
long lineno;
{
    register AddrScrn_Disp *dl;

    if((dl = dlist(lineno)) && dl->type == Empty)
      return 1;

    return 0;
}


/*
 * Returns 1 if this line is of a type that can have a cursor on it.
 */
int
line_is_selectable(lineno)
long lineno;
{
    register AddrScrn_Disp *dl;

    if((dl = dlist(lineno)) && (dl->type == Text      ||
					dl->type == Title     ||
					dl->type == Beginning ||
					dl->type == End))
	return 0;

    return 1;
}


/*
 * Find the first selectable line greater than or equal to line.  That is, the
 * first line the cursor is allowed to start on.  (Cursor never points to
 * Text or Title lines.)
 * (If there are none >= line, it will find the highest one.)
 *
 * Returns the line number of the found line or NO_LINE if there isn't one.
 */
long
first_selectable_line(line)
long line;
{
    long lineno;
    register AddrScrn_Disp *dl;

    /* skip past non-selectable lines */
    for(lineno=line;
	(dl=dlist(lineno))->type == Text || dl->type == Title;
	lineno++)
	;/* do nothing */

    if(line_is_selectable(lineno))
      return(lineno);

    /*
     * There were no selectable lines from lineno on down.  Trying looking
     * back up the list.
     */
    for(lineno=line-1;
	dlist(lineno)->type != Beginning && !line_is_selectable(lineno);
	lineno--)
	;/* do nothing */

    if(line_is_selectable(lineno))
      return(lineno);

    /* no selectable lines at all */
    as.no_op_possbl++;
    return NO_LINE;
}


/*
 * Find the first line greater than or equal to line.  (Any line, not
 * necessarily selectable.)
 *
 * Returns the line number of the found line or NO_LINE if there is none.
 *
 * Warning:  This just starts at the passed in line and goes forward until
 * it runs into a line that isn't a Beginning line.  If the line passed in
 * is not in the dlc cache, it will have no way to know when it gets to the
 * real beginning.
 */
long
first_line(line)
long line;
{
    long lineno;

    for(lineno=line;
       dlist(lineno)->type == Beginning;
       lineno++)
	;/* do nothing */

    if(dlist(lineno)->type != End)
      return(lineno);
    else{
	as.no_op_possbl++;
	return(NO_LINE);
    }
}


/*
 * Find the line and field number of the next selectable line, keeping the
 * field about the same.
 *
 * Args: cur_line     -- The current line position (in global display list)
 *			 of cursor
 *       new_line     -- Return value: new line position
 *
 * Result: The new line number is set.
 *       The value 1 is returned if OK or 0 if there is no next line.
 */
int
next_selectable_line(cur_line, new_line)
long  cur_line;
long *new_line;
{
    register AddrScrn_Disp *dl;

    /* skip over non-selectable lines */
    for(cur_line++;
        (dl=dlist(cur_line))->type == Text || dl->type == Title;
	cur_line++)
	;/* do nothing */

    if(dl->type == End)
      return 0;

    *new_line = cur_line;
    return 1;
}


/*
 * Find the line and field number of the previous selectable line, keeping the
 * field about the same.
 *
 * Args: cur_line     -- The current line position (in global display list)
 *			 of cursor
 *       new_line     -- Return value: new line position
 *
 * Result: The new line number is set.
 *       The value 1 is returned if OK or 0 if there is no previous line.
 */
int
prev_selectable_line(cur_line, new_line)
long  cur_line;
long *new_line;
{
    register AddrScrn_Disp *dl;

    /* skip backwards over non-selectable lines */
    for(cur_line--;
	(dl=dlist(cur_line))->type == Text || dl->type == Title;
	cur_line--)
	;/* do nothing */

    if(dl->type == Beginning)
      return 0;

    *new_line = cur_line;

    return 1;
}


/*
 * Delete an entry from the address book
 *
 * Args: abook        -- The addrbook handle into access library
 *       command_line -- The screen line on which to prompt
 *       cur_line     -- The entry number in the display list
 *
 * Result: returns 1 if an entry was deleted, 0 if not.
 *
 * The main routine above knows what to repaint because it's always the
 * current entry that's deleted.  Here confirmation is asked of the user
 * and the appropriate adrbklib functions are called.
 */
int
addr_book_delete(abook, command_line, cur_line)
AdrBk *abook;
int    command_line;
long   cur_line;
{
    char   ch, *cmd, *dname;
    char   prompt[40+MAX_ADDRESS+1]; /* 40 is len of string constants below */
    int    rc = command_line; /* nuke warning about command_line unused */
    register AddrScrn_Disp *dl;
    PerAddrBook     *pab;
    AdrBk_Entry     *abe;
    DL_CACHE_S      *dlc_to_flush;

    dprint(2, (debugfile, "- addr_book_delete -\n"));

    dl  = dlist(cur_line);
    abe = adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Normal);

    switch(dl->type){
      case Simple:
        dname = abe->fullname;
        cmd   = "Really delete \"%.50s\"";
        break;

      case ListHead:
        dname = abe->fullname;
	cmd   = "Really delete ENTIRE list \"%.50s\"";
        break;

      case ListEnt:
        dname = listmem_from_dl(abook, dl);
	cmd   = "Really delete \"%.100s\" from list";
        break;
    } 

    dname = dname ? dname : "";
    cmd   = cmd   ? cmd   : "";

    sprintf(prompt, cmd, dname);
    ch = want_to(prompt, 'n', 'n', NO_HELP, 0, 0);
    if(ch == 'y'){
	dlc_to_flush = get_dlc(cur_line);
	if(dl->type == Simple || dl->type == ListHead){
	    /*--- Kill a single entry or an entire list ---*/
            rc = adrbk_delete(abook, (a_c_arg_t)dl->elnum);
	}
	else{
            /*---- Kill an entry out of a list ----*/
            rc = adrbk_listdel(abook, (a_c_arg_t)dl->elnum,
		    listmem_from_dl(abook, dl));
	}

	pab = &as.adrbks[as.cur];

	if(rc == 0){
	    q_status_message(0, 0, 3, "Entry deleted, address book updated");
            dprint(2, (debugfile, "Entry deleted from addr book\n"));
	    /*
	     * Remove deleted line and everything after it from
	     * the dlc cache.  Next time we try to access those lines they
	     * will get filled in with the right info.
	     */
	    flush_dlc_from_cache(dlc_to_flush);
            return 1;
        }
	else{
            q_status_message1(0, 1, 5, "\007Error writing address book: %s",
                                                   error_description(errno));
            dprint(1, (debugfile, "Error deleting entry from %s (%s): %s\n",
		pab->nickname, pab->filename, error_description(errno)));
        }
	return 0;
    }
    else{
	q_status_message(0, 0, 2, "Entry not deleted");
	return 0;
    }
}


/*
 *   Prompt for name, address, etc for a simple addition
 *
 * Args: abook        -- The address book handle for the addrbook access lib
 *       command_line -- The screen line to prompt on
 *       warped       -- We warped to a new part of the addrbook
 *
 * Result: address book possibly updated.  If address selection screen
 *       is called up returns fact that screen needs repainting.  Returns
 *       NO_LINE on failure or cancellation and line number of entry
 *       that was just added on success.
 *
 * This is only for adding a plain address book entry, and does nothing with
 * lists.
 */
long
simple_add(abook, command_line, warped)
AdrBk *abook;
int    command_line;
int   *warped;
{
    char         new_fullname[MAX_FULLNAME + 1],
		 new_address[MAX_ADDRESS + 1],
                 new_nickname[MAX_NICKNAME + 1];
    char        *prompt,
		*fname;
    int          rc, which_addrbook;
    adrbk_cntr_t new_entry_num;
    DL_CACHE_S   dlc_restart;
    DL_CACHE_S  *dlc, *dlc_above_top, *dlc_bottom_row;
    long         next_row, where;


    dprint(2, (debugfile, "- simple_add -\n"));

    if((long)abook->count >= MAX_ADRBK_SIZE){
	q_status_message(0, 2, 5,
	    "Address book is at maximum size. Addition cancelled.");
	dprint(2, (debugfile, "Addrbook at Max size, cancel addition\n"));
	return NO_LINE;
    }

    new_address[0]   = '\0';
    new_fullname[0]  = '\0';
    new_nickname[0]  = '\0';

    /*------ full name ------*/
    prompt = "New full name (Last, First): ";
    rc     = edit_fullname(command_line, new_fullname, prompt, h_oe_add_full);
    if(rc != 0)
      goto add_cancel;
            

    /*----- nickname ------*/
    prompt = "Enter new nickname (one word and easy to remember): ";
    rc     = edit_nickname(abook, (AddrScrn_Disp *)NULL, command_line,
	new_nickname, prompt, h_oe_add_nick, 0, 0);
    if(rc != 0)
      goto add_cancel;


    /*---- address ------*/
    prompt = "Enter new e-mail address: ";
    rc     = edit_address(command_line, 0, new_address, prompt, h_oe_add_addr);
    if(rc != 0)
      goto add_cancel;

            
    if(fname=addr_lookup(new_nickname, &which_addrbook)){
	q_status_message3(0, 3, 7, "Warning! %s exists in %s as %s",
	    new_nickname, as.adrbks[which_addrbook].nickname, fname);
	fs_give((void **)&fname);
    }

    dlc_above_top = get_dlc(as.top_ent - 1L);
    dlc_bottom_row = get_dlc(as.top_ent + as.l_p_page - 1L);

    /*---- write it into the file ----*/
    rc = adrbk_add(abook,
		   new_nickname,
		   new_fullname,
		   new_address,
		   NULL,
		   NULL,
		   Single,
		   &new_entry_num,
		   (int *)NULL);

    if(rc == -2 || rc == -3){
        q_status_message1(1, 1, 4, "\007Error updating address book: %s",
              rc == -2 ?  error_description(errno) : "Pine bug");
        dprint(1, (debugfile, "Error adding \"%s\": %s\n", new_nickname,
              rc == -2 ?  error_description(errno) : "Pine bug"));
        return NO_LINE;
    }
    else if(rc == -4){
	no_tabs_warning();
        return NO_LINE;
    }

    dlc_restart.type      = DlcSimple;
    dlc_restart.adrbk_num = as.cur;
    dlc_restart.dlcelnum  = new_entry_num;
    /*
     * If the following is true, the new entry is on the same page.
     * Notice that we haven't flushed the dl cache, so dlc_above_top and
     * dlc_bottom_row are the old values.  That's ok.  In this
     * case it is hard to flush the cache because we don't know where
     * to start flushing yet, but we can get away with not flushing it.
     */
    if(funny_compare_dlcs(&dlc_restart, dlc_above_top) > 0 &&
       funny_compare_dlcs(&dlc_restart, dlc_bottom_row) < 0){
	/*
	 * Now just go ahead and flush everything on the screen instead
	 * of trying to figure out what needs to be flushed.
	 */
	flush_dlc_from_cache(dlc_above_top);
	*warped = 0;
	/*
	 * Find the dlc which matches dlc_restart and return that value.
	 */
	next_row = as.top_ent - 1L;
	do{
	    next_row++;
	    dlc = get_dlc(next_row);
	}while(!matching_dlcs(&dlc_restart, dlc) && dlc->type != DlcEnd);
	where = next_row;
    }
    else{
	/* restart the display centered at the new entry */
	*warped = 1;
	warp_to_dlc(&dlc_restart, 0L);
	where = 0L;
    }

    q_status_message(0, 0, 2, "Addition complete. Address book updated.");
    dprint(2, (debugfile, "\"%s\" added to address book\n", new_nickname));
    return(where);
 
 add_cancel:
    cancel_warning(NO_DING, "addition");
    return NO_LINE;
}


/*
 *   Create a distribution list
 *
 * Args: abook          -- Handle into address book access library
 *       command_line   -- screen line to prompt on
 *
 * Result: Distribution list possibly created, 
 *         returns flag if screen was painted by the address grabber
 *         return value:  integer is entry number added
 *                        NO_LINE if nothing added
 *
 * Prompt for the description, then nickname, then up to MAX_ADDRESS
 * list members, which can be addresses or nicknames of other addresses or
 * lists.
 */
long
create_list(abook, command_line, warped)
AdrBk *abook;
int    command_line;
int   *warped;
{
    char         list_name[MAX_FULLNAME+1],
		 new_nickname[MAX_NICKNAME+1],
                 new_address[MAX_ADDRESS+1],
                *temp_list[MAX_NEW_LIST+1],
               **p,
		*q,
	        *prompt,
		*fname;
    adrbk_cntr_t new_entry_num;
    int          rc, which_addrbook;
    DL_CACHE_S   dlc_restart;
    DL_CACHE_S  *dlc, *dlc_above_top, *dlc_bottom_row;
    long         next_row, where;


    dprint(2, (debugfile, "- create_list -\n"));
    
    if((long)abook->count >= MAX_ADRBK_SIZE){
	q_status_message(0, 2, 5,
	    "Address book is at maximum size. List addition cancelled.");
	dprint(2, (debugfile, "Addrbook at Max size, cancel addition\n"));
	return NO_LINE;
    }

    list_name[0]     = '\0';
    new_nickname[0]  = '\0';
    new_address[0]   = '\0';

    /*------ name for list ------*/
    prompt = "Long name/description of new list: ";
    rc     = edit_fullname(command_line, list_name, prompt, h_oe_crlst_full);
    if(rc != 0)
      goto create_cancel;


    /*----- nickname ------*/
    prompt = "Enter list nickname (one word and easy to remember): ";
    rc     = edit_nickname(abook, (AddrScrn_Disp *)NULL, command_line,
	new_nickname, prompt, h_oe_crlst_nick, 0, 0);
    if(rc != 0)
      goto create_cancel;


    /*---- addresses ------*/
    for(p = temp_list; p < &temp_list[MAX_NEW_LIST]; p++){
	char prompt2[80];

        sprintf(prompt2, "Enter %s address or blank when done: ",
						enth_string(p - temp_list + 1));
        new_address[0] = '\0';
	rc = edit_address(command_line, 1, new_address, prompt2,
							       h_oe_crlst_addr);
	for(q = new_address; isspace(*q); ++q)
	  ;/* do nothing */

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

	if(rc != 0)
	    goto create_cancel;

        *p = cpystr(new_address);
    }

    if(fname=addr_lookup(new_nickname, &which_addrbook)){
	q_status_message3(0, 3, 7, "Warning! %s exists in %s as %s",
	    new_nickname, as.adrbks[which_addrbook].nickname, fname);
	fs_give((void **)&fname);
    }

    *p = NULL;

    dlc_above_top = get_dlc(as.top_ent - 1L);
    dlc_bottom_row = get_dlc(as.top_ent + as.l_p_page - 1L);

    rc = adrbk_add(abook,
		   new_nickname,
		   list_name,
		   NULL,
		   NULL,
		   NULL,
		   List,
		   &new_entry_num,
		   (int *)NULL);

    if(rc == 0){
	rc = adrbk_nlistadd(abook, (a_c_arg_t)new_entry_num, temp_list);
    }
    else if(rc == -2 || rc == -3){
        q_status_message1(1, 1, 4,"\007Error updating address book: %s",
                   rc == -2 ?  error_description(errno) : "Pine bug");
        dprint(1, (debugfile, "Error creating list \"%s\" in addrbook: %s\n",
               new_nickname, rc == -2 ? error_description(errno) :
               "pine bug"));
        return NO_LINE;
    }
    else if(rc == -4){
	no_tabs_warning();
        return NO_LINE;
    }

    dlc_restart.type      = DlcListHead;
    dlc_restart.adrbk_num = as.cur;
    dlc_restart.dlcelnum  = new_entry_num;
    /*
     * See comments in simple_add.
     */
    if(funny_compare_dlcs(&dlc_restart, dlc_above_top) > 0 &&
       funny_compare_dlcs(&dlc_restart, dlc_bottom_row) < 0){
        /*
         * Now just go ahead and flush everything on the screen instead
         * of trying to figure out what needs to be flushed.
         */
        flush_dlc_from_cache(dlc_above_top);
        *warped = 0;
        /*
         * Find the dlc which matches dlc_restart and return that value.
         */
        next_row = as.top_ent - 1L;
        do{
            next_row++;
            dlc = get_dlc(next_row);
        }while(!matching_dlcs(&dlc_restart, dlc) && dlc->type != DlcEnd);
        where = next_row;
    }
    else{
        /* restart the display centered at the new entry */
        *warped = 1;
        warp_to_dlc(&dlc_restart, 0L);
        where = 0L;
    }

    q_status_message1(0, 0, 2,
        "Addition of list %s complete. Address book updated.", new_nickname);
    dprint(2, (debugfile, "List addition to address book \"%s\"\n",
                                                          new_nickname));
    return(where);
 
create_cancel:
    cancel_warning(NO_DING, "list creation");
    return NO_LINE;
}


/*
 * Add entries to a distribution list
 *
 * Args: abook          -- Handle to address book for adrbklib access library
 *       command-line   -- Screen line number to prompt on 
 *       row            -- current line
 *
 * Result: flag set if screen was mangled, possible addition to list
 *         return value:  the row that we should move the cursor to.  If only
 *                        one added, it will move to that entry, otherwise,
 *                        it moves to the head of the list.
 *                        NO_LINE if nothing added
 */
long
addr_to_list(abook, command_line, row)
AdrBk         *abook;
int            command_line;
long           row;
{
    char           new_address[MAX_ADDRESS+1],
                   prompt[80],
	           edited_nick[MAX_NICKNAME + 1];
    int            rc,
		   how_many_added;
    AddrScrn_Disp *dl;
    AdrBk_Entry   *abe;
    DL_CACHE_S    *dlc_to_flush,
                  *dlc_match;
    long           orig_offset,
                   new_offset;
    char          *temp_list[MAX_NEW_LIST+1],
                 **p,
		  *match_it,
		  *q;
    adrbk_cntr_t   save_entry_num;

    dprint(2, (debugfile, "- addr_to_list -\n"));

    if(abook->count == 0){
        q_status_message(0, 1, 3,
			"No distribution lists. To create, use \"S\"");
        return NO_LINE;
    }

    dl = dlist(row);
    save_entry_num = dl->elnum; /* save because dlc cache is */
				/* flushed in edit_address() */

    if(dl->type != ListHead && dl->type != ListEnt){
        q_status_message(1, 1, 3,
    "Move cursor to list you wish to add to. Use \"A\" to create plain entry");
        return NO_LINE;
    }

    abe = adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Normal);

    (void)strncpy(edited_nick, abe->nickname, MAX_NICKNAME);
    edited_nick[MAX_NICKNAME] = '\0';

    how_many_added = 0;

    /*---- addresses ------*/
    for(p = temp_list; p < &temp_list[MAX_NEW_LIST]; p++){


        sprintf(prompt, "Enter %s address to add or blank when done: ",
					    enth_string(how_many_added + 1));
        new_address[0] = '\0';
	rc = edit_address(command_line, 1, new_address, prompt,
							   h_oe_adlst_addr);
	for(q = new_address; isspace(*q); ++q)
	  ;/* do nothing */

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

	if(rc != 0)
	  goto alist_cancel;

        *p = cpystr(new_address);
	how_many_added++;
	match_it = *p;
    }

    if(how_many_added == 0)
      goto alist_cancel;

    dlc_to_flush = get_dlc(row);
    if(dlc_to_flush->type == DlcListHead)
      orig_offset = -1L;
    else
      orig_offset = dlc_to_flush->dlcoffset;

    rc = adrbk_nlistadd(abook, (a_c_arg_t)save_entry_num, temp_list);

    /* flushes all cache entries related to this addrbook entry */
    flush_dlc_from_cache(dlc_to_flush);

    dl = dlist(row);  /* just an easy way to get the elnum */

    if(rc == -2){
        q_status_message1(1, 1, 3, "\007Error updating address book: %s",
                                                    error_description(errno));
        dprint(1, (debugfile, "Error adding to list \"%s\": %s\n",
                       edited_nick, error_description(errno)));
        return NO_LINE;
    }

    if(how_many_added == 1){
	/* align with new list entry */
	dlc_match = dlc_from_listmem(abook, (a_c_arg_t)dl->elnum, match_it);
	new_offset = dlc_match->dlcoffset;
	row += (new_offset - orig_offset);
    }
    else{
	/* align with ListHead when more than one added */
	row += (-1L - orig_offset);
    }

    q_status_message2(0, 0, 2,
        "Entr%s added to \"%s\". Address book updated.",
	how_many_added>1 ? "ies" : "y",
	edited_nick);
    dprint(2, (debugfile, "Addition to list \"%s\"\n", edited_nick));
    return(row);

alist_cancel:
    cancel_warning(NO_DING, "addition");
    return NO_LINE;
}


/*
 * Edit a nickname field.
 *
 * Args: abook     -- the addressbook handle
 *       dl        -- display list line (NULL if new entry)
 *    command_line -- line to prompt on
 *       orig      -- nickname to edit
 *       prompt    -- prompt
 *      this_help  -- help
 * return_existing -- changes the behavior when a user types in a nickname
 *                    which already exists in this abook.  If not set, it
 *                    will just keep looping until the user changes; if set,
 *                    it will return -8 to the caller and orig will be set
 *                    to the matching nickname.
 *    allow_ctrl_t -- allow the user to escape to the address books to
 *                    select a nickname
 *
 * Returns: -10 to cancel
 *          -9  no change
 *          -8  existing nickname chosen (only happens if return_existing set)
 *           0  new value copied into orig
 */
int
edit_nickname(abook, dl, command_line, orig, prompt, this_help,
						return_existing, allow_ctrl_t)
AdrBk         *abook;
AddrScrn_Disp *dl;
int            command_line;
char          *orig,
              *prompt;
HelpType       this_help;
int            return_existing,
               allow_ctrl_t;
{
    char         edit_buf[MAX_NICKNAME + 1];
    HelpType     help;
    int          rc;
    AdrBk_Entry *check, *passed_in_ae;
    ESCKEY_S     ekey[2];

    if(allow_ctrl_t){
	ekey[0].ch    = ctrl('T');
	ekey[0].rval  = 2;
	ekey[0].name  = "^T";
	ekey[0].label = "To AddrBk";

	ekey[1].ch    = -1;
    }
    else
      ekey[0].ch    = -1;

    edit_buf[MAX_NICKNAME] = '\0';
    strncpy(edit_buf, orig, MAX_NICKNAME);
    if(dl)
      passed_in_ae = adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Lock);
    else
      passed_in_ae = (AdrBk_Entry *)NULL;
    help  = NO_HELP;
    rc    = 0;
    check = NULL;
    do{
	/* display a message because adrbk_lookup_by_nick returned positive */
	if(check){
	    if(return_existing){
		strcpy(orig, edit_buf);
		if(passed_in_ae)
		  (void)adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Unlock);
		return -8;
	    }

            q_status_message1(0, 1, 3,
		    "Already an entry with nickname \"%s\"", edit_buf);
            display_message(NO_OP_COMMAND);
            sleep(2);
	}

	if(rc == 3)
          help = (help == NO_HELP ? this_help : NO_HELP);

	rc = optionally_enter(edit_buf, command_line, 0, MAX_NICKNAME, 1,
			  0, prompt, (ekey[0].ch != -1) ? ekey : NULL, help, 0);

	if(rc == 1)  /* ^C */
	  break;

	if(rc == 2){ /* ^T */
	    void (*redraw) () = ps_global->redrawer;
	    char *returned_nickname;

	    push_titlebar_state();
	    returned_nickname = addr_book_takeaddr();
	    if(returned_nickname)
	      strncpy(edit_buf, returned_nickname, MAX_NICKNAME);
	    ClearScreen();
	    pop_titlebar_state();
	    redraw_titlebar();
	    if(ps_global->redrawer = redraw) /* reset old value, and test */
	      (*ps_global->redrawer)();
	}
            
    }while(rc == 2 ||
	   rc == 3 ||
	   rc == 4 ||
	   nickname_check(edit_buf) ||
           ((check =
	       adrbk_lookup_by_nick(abook, edit_buf, (adrbk_cntr_t *)NULL)) &&
	     check != passed_in_ae));

    if(passed_in_ae)
      (void)adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Unlock);

    if(rc != 0)
      return -10;

    if(strcmp(edit_buf, orig) == 0) /* no change */
      return -9;
    
    strcpy(orig, edit_buf);
    return 0;
}


/*
 * Let user change a nickname field.
 *
 * Args: abook     -- the addressbook handle
 *       row       -- current line
 *       new       -- points to new entry on return
 *    command_line -- line to prompt on
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 */
int
change_nickname(abook, row, new_entry_num, resort_happened, command_line)
AdrBk         *abook;
long           row;
adrbk_cntr_t  *new_entry_num;
int           *resort_happened;
int            command_line;
{
    char     edit_buf[MAX_NICKNAME + 1];
    char    *prompt, *fname;
    int      rc, which_addrbook;
    AdrBk_Entry *abe;
    adrbk_cntr_t old_entry_num;
    adrbk_cntr_t save_adrbk_size;
    AddrScrn_Disp *dl;

    dprint(8, (debugfile, "- change_nickname -\n"));

    if((long)abook->count >= MAX_ADRBK_SIZE){
	q_status_message(0, 2, 5,
	    "Address book is at maximum size. Nickname change cancelled.");
	dprint(2, (debugfile, "Addrbook at Max size, nick change cancelled\n"));
	return -9;
    }

    dl  = dlist(row);
    abe = adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Lock);
    old_entry_num = dl->elnum;
    save_adrbk_size = abook->count;

    if(abe->nickname){
	edit_buf[MAX_NICKNAME] = '\0';
	strncpy(edit_buf, abe->nickname, MAX_NICKNAME);
    }
    else
      edit_buf[0] = '\0';
    prompt = "Edit Nickname: ";

    rc     = edit_nickname(abook, dl, command_line, edit_buf, prompt,
							h_oe_editab_nick, 0, 0);

    if(rc == 0){

	if(fname=addr_lookup(edit_buf, &which_addrbook)){
	    q_status_message3(0, 3, 7, "Warning! %s exists in %s as %s",
		edit_buf, as.adrbks[which_addrbook].nickname, fname);
	    fs_give((void **)&fname);
	}

	rc = adrbk_add(abook,
		       edit_buf,
		       abe->fullname,
		       abe->tag == List           ?
			   (char *)abe->addr.list :
			   abe->addr.addr,
		       abe->fcc,
		       abe->extra,
		       abe->tag,
		       new_entry_num,
		       resort_happened);

	/* this will have unlocked abe if successful */
	
	if(rc == 0){
	    /*
	     * count > saved_count means we changed the nickname, but
	     * so far we've just added the changed entry and left the
	     * old one in (because we may need it below).
	     */
	    if(abook->count > save_adrbk_size &&
		*new_entry_num <= old_entry_num)
		old_entry_num++;
	    abe = adrbk_get_ae(abook, (a_c_arg_t)old_entry_num, Normal);

	    /* If old one is a List, add list members back in to new one */
	    if(abe->tag == List && abe->addr.list)
		rc = adrbk_nlistadd(abook, (a_c_arg_t)(*new_entry_num),
			abe->addr.list);

	    /* if we changed the nickname, delete the old entry */
	    if(abook->count > save_adrbk_size){
		rc = adrbk_delete(abook, (a_c_arg_t)old_entry_num);
		if(old_entry_num <= *new_entry_num)
		  (*new_entry_num)--;
	    } 
	}
	else
	  (void)adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Unlock);
    }

    return(rc);
}


/*
 * Edit a fullname field.
 *
 * Args: command_line -- line to prompt on
 *          orig      -- fullname to edit
 *          prompt    -- prompt
 *         this_help  -- help
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 */
int
edit_fullname(command_line, orig, prompt, this_help)
int      command_line;
char    *orig,
        *prompt;
HelpType this_help;
{
    char     edit_buf[MAX_FULLNAME + 1];
    HelpType help;
    int      rc;

    edit_buf[MAX_FULLNAME] = '\0';
    strncpy(edit_buf, orig, MAX_FULLNAME);
    help = NO_HELP;
    rc   = 0;
    do{
	if(rc == 3)
	  help = (help == NO_HELP ? this_help : NO_HELP);

	rc = optionally_enter(edit_buf, command_line, 0, MAX_FULLNAME, 1,
                                  0, prompt, (ESCKEY_S *)NULL, help, 0);

    }while(rc == 3 || rc == 4);

    if(rc != 0)
      return -10;

    if(strcmp(edit_buf, orig) == 0) /* no change */
      return -9;
    
    strcpy(orig, edit_buf);
    return 0;
}


/*
 * Let user change a fullname field.  Can be a list or a simple address.
 *
 * Args: abook     -- the addressbook handle
 *       row       -- current line
 *       new       -- points to new entry on return
 *    command_line -- line to prompt on
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 */
int
change_fullname(abook, row, new_entry_num, resort_happened, command_line)
AdrBk         *abook;
long           row;
adrbk_cntr_t  *new_entry_num;
int           *resort_happened;
int            command_line;
{
    char  edit_buf[MAX_FULLNAME + 1];
    char *prompt;
    int   rc;
    AdrBk_Entry *abe;
    AddrScrn_Disp *dl;

    dprint(8, (debugfile, "- change_fullname -\n"));

    dl  = dlist(row);
    abe = adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Normal);

    if(abe->fullname){
	edit_buf[MAX_FULLNAME] = '\0';
	strncpy(edit_buf, abe->fullname, MAX_FULLNAME);
    }
    else
      edit_buf[0] = '\0';
    if(dl->type == ListHead || dl->type == ListEnt)
      prompt = "Edit Full name of list: ";
    else
      prompt = "Edit Full name: ";

    rc = edit_fullname(command_line, edit_buf, prompt, h_oe_editab_full);

    if(rc == 0)
	rc = adrbk_add(abook,
		       abe->nickname,
		       edit_buf,
		       abe->tag == List               ?
			       (char *)abe->addr.list :
		       abe->addr.addr,
		       abe->fcc,
		       abe->extra,
		       abe->tag,
		       new_entry_num,
		       resort_happened);

    return(rc);
}


/*
 * Edit an address field.
 *
 * Args: command_line -- line to prompt on
 *          spaces_ok -- spaces are allowed in address (because it is a full
 *                       address in a list, not just the actual address part of
 *                       a simple addrbook entry)
 *          orig      -- fullname to edit
 *          prompt    -- prompt
 *         this_help  -- help
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 *
 * Side effect: Can flush addrbook entry cache entries so they need to be
 * re-fetched afterwords.
 */
int
edit_address(command_line, spaces_ok, orig, prompt, this_help)
int      command_line,
	 spaces_ok;
char    *orig,
        *prompt;
HelpType this_help;
{
    char     edit_buf[MAX_ADDRESS + 1];
    HelpType help;
    int      rc;

    edit_buf[MAX_ADDRESS] = '\0';
    strncpy(edit_buf, orig, MAX_ADDRESS);
    help = NO_HELP;
    rc   = 0;
    do{
	if(rc == 3)
	  help = (help == NO_HELP ? this_help : NO_HELP);

	rc = optionally_enter(edit_buf, command_line, 0, MAX_ADDRESS, 1,
                                  0, prompt, (ESCKEY_S *)NULL, help, 0);
            
	if(rc == 1) /* ^C */
	  break;
            
    }while(rc == 3 ||
	   rc == 4 ||
	   warn_bad_addr(edit_buf, spaces_ok));

    if(rc != 0)
      return -10;

    if(strcmp(edit_buf, orig) == 0) /* no change */
      return -9;

    strcpy(orig, edit_buf);
    return 0;
}


/*
 * Change an address field, either a single address or a list member.
 *
 * Args: abook     -- the addressbook handle
 *       row       -- current line
 *   new_entry_num -- points to new entry on return
 *     addr_match  -- points to new (alloc'd) entry within list on return
 *    command_line -- line to prompt on
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 */
int
change_address(abook, row, new_entry_num, resort_happened, addr_match,
	       command_line)
AdrBk         *abook;
long           row;
adrbk_cntr_t  *new_entry_num;
int           *resort_happened;
char         **addr_match;
int            command_line;
{
    char     edit_buf[MAX_ADDRESS + 1];
    char    *prompt;
    HelpType this_help;
    int      spaces_ok;
    int      rc;
    AddrScrn_Disp *dl;
    AdrBk_Entry   *abe;

    dprint(8, (debugfile, "- change_address -\n"));

    dl  = dlist(row);
    abe = adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Lock);

    edit_buf[MAX_ADDRESS] = '\0';
    if(dl->type == ListEnt){
	if(listmem_from_dl(abook, dl))
	  strncpy(edit_buf, listmem_from_dl(abook, dl), MAX_ADDRESS);
	else
	  edit_buf[0] = '\0';
	prompt    = "Edit address in list: ";
	this_help = h_oe_editab_al;
	spaces_ok = 1;
    }
    else{
	if(abe->tag == Single && abe->addr.addr)
	  strncpy(edit_buf, abe->addr.addr, MAX_ADDRESS);
	else
	  edit_buf[0] = '\0';
	prompt    = "Edit address: ";
	this_help = h_oe_editab_addr;
	spaces_ok = 0;
    }

    rc = edit_address(command_line, spaces_ok, edit_buf, prompt, this_help);

    dl  = dlist(row);  /* edit_address may have flushed it from cache */

    if(rc == 0){
	if(dl->type == ListEnt){
	    rc = adrbk_listdel(abook, (a_c_arg_t)dl->elnum,
		    listmem_from_dl(abook, dl));
	    if(rc == 0){
		rc = adrbk_listadd(abook, (a_c_arg_t)dl->elnum, edit_buf);
		*addr_match = cpystr(edit_buf);
	    }
	    else
		(void)adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Unlock);
	    /*
	     * Entry num is unchanged, it's just the l_offset that might
	     * have changed.
	     */
	    *new_entry_num = dl->elnum;
	}
	else{
	    rc = adrbk_add(abook,
			   abe->nickname,
			   abe->fullname,
			   edit_buf,
			   abe->fcc,
			   abe->extra,
			   abe->tag,
			   new_entry_num,
			   resort_happened);

	    if(rc != 0)
	      (void)adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Unlock);
	}
    }
    return(rc);
}


/*
 * Edit the addressbook comment field of an address or list.
 *
 * Args: command_line -- line to prompt on
 *          orig      -- fullname to edit
 *          prompt    -- prompt
 *         this_help  -- help
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 */
int
edit_comment(command_line, orig, prompt, this_help)
int      command_line;
char    *orig,
        *prompt;
HelpType this_help;
{
    char     edit_buf[MAX_COMMENT + 1];
    HelpType help;
    int      rc;

    edit_buf[MAX_COMMENT] = '\0';
    strncpy(edit_buf, orig, MAX_COMMENT);
    help = NO_HELP;
    rc   = 0;
    do{
	if(rc == 3)
	  help = (help == NO_HELP ? this_help : NO_HELP);

	rc = optionally_enter(edit_buf, command_line, 0, MAX_COMMENT, 1,
                                  0, prompt, (ESCKEY_S *)NULL, help, 0);

    }while(rc == 3 || rc == 4);

    if(rc != 0)
      return -10;

    if(strcmp(edit_buf, orig) == 0) /* no change */
      return -9;

    strcpy(orig, edit_buf);
    return 0;
}


/*
 * Edit the addressbook comment field of an address or list.
 *
 * Args: abook     -- the addressbook handle
 *       row       -- current line
 *       new       -- points to new entry on return
 *    command_line -- line to prompt on
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 */
int
change_comment(abook, row, new_entry_num, resort_happened, command_line)
AdrBk         *abook;
long           row;
adrbk_cntr_t  *new_entry_num;
int           *resort_happened;
int            command_line;
{
    char     edit_buf[MAX_COMMENT + 1];
    char    *prompt;
    int      rc;
    AdrBk_Entry   *abe;

    dprint(8, (debugfile, "- change_comment -\n"));

    abe = ae(row);

    if(abe->extra){
	edit_buf[MAX_COMMENT] = '\0';
	strncpy(edit_buf, abe->extra, MAX_COMMENT);
    }
    else
      edit_buf[0] = '\0';
    prompt = "Comment: ";

    rc = edit_comment(command_line, edit_buf, prompt, h_oe_editab_comment);

    if(rc == 0)
	rc = adrbk_add(abook,
		       abe->nickname,
		       abe->fullname,
		       abe->tag == List ?
		           (char *)abe->addr.list :
		           abe->addr.addr,
		       abe->fcc,
		       edit_buf,
		       abe->tag,
		       new_entry_num,
		       resort_happened);

    return(rc);
}


/*
 * Edit the fcc field.
 *
 * Args: command_line -- line to prompt on
 *          orig      -- fullname to edit
 *          prompt    -- prompt
 *         this_help  -- help
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 */
int
edit_fcc(command_line, orig, prompt, this_help)
int      command_line;
char    *orig,
        *prompt;
HelpType this_help;
{
    char     edit_buf[MAX_FCC + 1],
             tmp[MAX_FCC + 1];
    HelpType help;
    int      rc, slp;

    edit_buf[MAX_FCC] = '\0';
    strncpy(edit_buf, orig, MAX_FCC);
    help = NO_HELP;
    rc   = 0;
    slp  = 0;
    do{
	/* display the message queued by expand_foldername() */
	if(slp){
            display_message(NO_OP_COMMAND);
	    sleep(1);
	}

	if(rc == 3)
	  help = (help == NO_HELP ? this_help : NO_HELP);

	rc = optionally_enter(edit_buf, command_line, 0, MAX_FCC, 1,
                                  0, prompt, (ESCKEY_S *)NULL, help, 0);

	if(rc == 1) /* ^C */
	  break;
            
	tmp[MAX_FCC] = '\0';
	strncpy(tmp, edit_buf, MAX_FCC);

    }while(rc == 3 ||
	   rc == 4 ||
	   (*tmp != '{' && (slp = !expand_foldername(tmp))));

    if(rc != 0)
      return -10;

    if(strcmp(edit_buf, orig) == 0) /* no change */
      return -9;

    strcpy(orig, edit_buf);
    return 0;
}


/*
 * Change the fcc field of an address or list.
 *
 * Args: abook     -- the addressbook handle
 *       row       -- current line
 *       new       -- points to new entry on return
 *    command_line -- line to prompt on
 *
 * Returns: -10 to cancel
 *          -9  no change
 *              else regular adrbk_add return values
 */
int
change_fcc(abook, row, new_entry_num, resort_happened, command_line)
AdrBk         *abook;
long           row;
adrbk_cntr_t  *new_entry_num;
int           *resort_happened;
int            command_line;
{
    char  edit_buf[MAX_FCC + 1];
    char *prompt;
    int   rc;
    AddrScrn_Disp *dl;
    AdrBk_Entry *abe;

    dprint(8, (debugfile, "- change_fcc -\n"));

    dl  = dlist(row);
    abe = adrbk_get_ae(abook, (a_c_arg_t)dl->elnum, Normal);

    if(abe->fcc){
	edit_buf[MAX_FCC] = '\0';
	strncpy(edit_buf, abe->fcc, MAX_FCC);
    }
    else
      edit_buf[0] = '\0';
    if(dl->type == ListHead || dl->type == ListEnt)
      prompt = "Edit Fcc for list: ";
    else
      prompt = "Edit Fcc: ";

    rc = edit_fcc(command_line, edit_buf, prompt, h_oe_editab_fcc);

    if(rc == 0)
      rc = adrbk_add(abook,
		       abe->nickname,
		       abe->fullname,
		       abe->tag == List ?
			   (char *)abe->addr.list :
			   abe->addr.addr,
		       edit_buf,
		       abe->extra,
		       abe->tag,
		       new_entry_num,
		       resort_happened);

    return(rc);
}


/*
 *  Edit any of the fields of an addrbook entry.
 *
 *  Args: abook        -- Handle into access library for open addrbook
 *        command_line -- Screen line number to prompt on
 *        cur_line     -- Current line in display list
 *        warped       -- Set this if we were warped to a new part of
 *                        the addrbook.
 *
 *  Result: Returns NO_LINE - cancelled or error
 *                  CHANGED_CURRENT - Simple change to current
 *                  else - line number of new entry
 *
 * This can edit any of the fields in the address book.
 * Re-sorting of the address book may be required because of an edit.
 */
long
edit_a_field(abook, command_line, cur_line, warped)
AdrBk *abook;
int    command_line;
long   cur_line;
int   *warped;
{
    int cmd;
    long rc;
    ESCKEY_S    choices[6];
    AddrScrn_Disp *dl;
    int i;
    static char *prompt = "Edit which field ? ";
    HelpType     help;

    dprint(9, (debugfile, "- edit_a_field -\n"));

    dl = dlist(cur_line);

    i = 0;
    choices[i].ch    = 'n';
    choices[i].rval  = 'n';
    choices[i].name  = "N";
    choices[i++].label = "Nickname";

    if(dl->type != ListHead){
	choices[i].ch    = 'a';
	choices[i].rval  = 'a';
	choices[i].name  = "A";
	choices[i++].label = "Address";
    }
    else{
	choices[i].ch    = -2;  /* skips field in radio_buttons */
	choices[i].rval  = 'f';
	choices[i].name  = "F";
	choices[i++].label = "Fullname";
    }

    choices[i].ch    = 'f';
    choices[i].rval  = 'f';
    choices[i].name  = "F";
    choices[i++].label = "Fullname";

    choices[i].ch    = 'g';
    choices[i].rval  = 'g';
    choices[i].name  = "G";
    choices[i++].label = "Fcc";

    choices[i].ch    = 'c';
    choices[i].rval  = 'c';
    choices[i].name  = "C";
    choices[i++].label = "Comment";

    choices[i].ch    = -1;


    help = h_ab_edit_a_field;

    cmd = radio_buttons(prompt,
		      command_line,
		      0,
		      choices,
		      (dl->type != ListHead) ? 'a' : 'f',
		      'x',
		      0,
		      help,
		      0);

    rc = do_edit_cmd(cmd, abook, command_line, cur_line, warped);

    return(rc);
}


/*
 * Fire up the selected edit in the status line.
 */
long
do_edit_cmd(cmd, abook, command_line, row, warped)
int            cmd;
AdrBk         *abook;
int            command_line;
long           row;
int           *warped;
{
    adrbk_cntr_t new_entry_num = NO_NEXT;
    int          resort_happened = 0;
    char        *addr_match    = NULL;
    int          rc;
    long         where;
    AddrScrn_Disp *dl;
    DL_CACHE_S   dlc_restart, *dlc_to_flush;
    DL_CACHE_S  *dlc, *dlc_new, *dlc_above_top, *dlc_bottom_row;
    long         next_row;

    dprint(2, (debugfile, "- do_edit_cmd(cmd=%d) -\n", cmd));

    *warped = 0;

    dl = dlist(row);
    dlc_to_flush = get_dlc(row);
    dlc_above_top  = get_dlc(as.top_ent - 1L);
    dlc_bottom_row = get_dlc(as.top_ent + as.l_p_page - 1L);

    switch(cmd){
      case 'g':  /* fcc */
	rc = change_fcc(abook, row, &new_entry_num, &resort_happened,
	    command_line);
	break;

      case 'c':  /* comment field */
	rc = change_comment(abook, row, &new_entry_num, &resort_happened,
	    command_line);
	break;

      case 'n':  /* nickname */
	rc = change_nickname(abook, row, &new_entry_num, &resort_happened,
	    command_line);
	break;

      case 'f':  /* fullname */
	rc = change_fullname(abook, row, &new_entry_num, &resort_happened,
	    command_line);
	break;

      case 'a':  /* address */
	rc = change_address(abook, row, &new_entry_num, &resort_happened,
	    &addr_match, command_line);
	break;

      case 'x':  /* ^C */
	rc = -10;
	break;
      
      default:
        q_status_message1(0, 0, 2,
	    "Can't happen, do_edit_cmd(cmd=%c)", (void *)cmd);
        dprint(1, (debugfile, "Can't happen, do_edit_cmd(cmd=%d cmd=%c)\n",
                   cmd, cmd));
	rc = -9;
	break;
    }

    if(rc == -9)  /* no change */
      return NO_LINE;

    if(rc == -10){ /* cancel */
	cancel_warning(NO_DING, "change");
	return NO_LINE;
    }

    if(rc == -2 || rc == -3){
        q_status_message1(1, 1, 3, "\007Error updating address book: %s",
                          rc == -2 ? error_description(errno) : "Pine bug");
        dprint(1, (debugfile, "Error editing address book %s\n",
                   rc == -2 ? error_description(errno) : "Pine bug"));
	if(addr_match != NULL)
	  fs_give((void **)&addr_match);

        return NO_LINE;
    }

    if(resort_happened){
	/* build a temporary dlc for the new_entry */
	dlc_restart.adrbk_num = as.cur;
	dlc_restart.dlcelnum  = new_entry_num;
	switch(dl->type){
	  case Simple:
	    dlc_restart.type = DlcSimple;
	    break;

	  case ListHead:
	  case ListEnt:
	    dlc_restart.type = DlcListHead;
	    break;
	}
	/*
	 * If this is true we try to keep the page on the screen the same
	 * (as opposed to jumping to the new location with it in the center
	 * of the screen).
	 */
	if(funny_compare_dlcs(&dlc_restart, dlc_above_top) > 0 &&
	   funny_compare_dlcs(&dlc_restart, dlc_bottom_row) < 0){
	    /*
	     * Now just go ahead and flush everything on the screen instead
	     * of trying to figure out what needs to be flushed.
	     */
	    flush_dlc_from_cache(dlc_above_top);
	    *warped = 0;
	    /*
	     * Find the dlc which matches dlc_restart and return that value.
	     */
	    next_row = as.top_ent - 1L;
	    do{
		next_row++;
		dlc = get_dlc(next_row);
	    }while(!matching_dlcs(&dlc_restart, dlc) && dlc->type != DlcEnd);
	    where = next_row;
	}
	else{
	    /* restart the display centered at the new entry */
	    *warped = 1;
	    warp_to_dlc(&dlc_restart, 0L);
	    where = 0L;
	}
    }
    else if(cmd == 'g' || cmd == 'c'){  /* no redrawing necessary */
	where = NO_LINE;
    }
    else if(cmd == 'a'){
	if(dl->type == Simple)
	  where = CHANGED_CURRENT;
	/* lists get sorted so things may have bounced around */
	else if(dl->type == ListEnt && *addr_match){
	    long old_offset;

	    old_offset = dlc_to_flush->dlcoffset;
	    *warped = 0;
	    flush_dlc_from_cache(dlc_to_flush);
	    dlc_new = dlc_from_listmem(abook, (a_c_arg_t)new_entry_num,
		addr_match);
	    where = row + (dlc_new->dlcoffset - old_offset);
	}
    }
    else if((cmd == 'n' || cmd == 'f') && dl->type == ListEnt){
	/* jump to the ListHead */
	*warped = 0;
	where = row - (dlc_to_flush->dlcoffset + 1L);
	flush_dlc_from_cache(dlc_to_flush);
    }
    else
      where = CHANGED_CURRENT;

    q_status_message(0, 0, 3, "Address book edited and updated");
    dprint(2, (debugfile, "Address book edited\n"));
    if(addr_match != NULL)
      fs_give((void **)&addr_match);

    return(where);
}


/*
 * Prompt user for search string and call find_in_book.
 *
 * Args: cur_line     -- The current line position (in global display list)
 *			 of cursor
 *       command_line -- The screen line to prompt on
 *       new_line     -- Return value: new line position
 *       wrapped      -- Wrapped to beginning of display, tell user
 *       warped       -- Warped to a new location in the addrbook
 *
 * Result: The new line number is set if the search is successful.
 *         Returns 0 if found, -1 if not, -2 if cancelled.
 *       
 */
int
search_book(cur_line, command_line, new_line, wrapped, warped)
long cur_line;
int  command_line;
long *new_line;
int  *wrapped,
     *warped;
{
    int         find_result, rc;
    static char search_string[MAX_SEARCH + 1] = { '\0' };
    char        prompt[MAX_SEARCH + 50], nsearch_string[MAX_SEARCH+1];
    HelpType	help;
    ESCKEY_S    ekey[3];

    dprint(7, (debugfile, "- search_book -\n"));

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

    ekey[0].ch    = ctrl('Y');
    ekey[0].rval  = 10;
    ekey[0].name  = "^Y";
    ekey[0].label = "First Adr";

    ekey[1].ch    = ctrl('V');
    ekey[1].rval  = 11;
    ekey[1].name  = "^V";
    ekey[1].label = "Last Adr";

    ekey[2].ch    = -1;

    while(1){
        rc = optionally_enter(nsearch_string, command_line, 0, MAX_SEARCH,
                              1, 0, prompt, ekey, help, 0);
        if(rc == 3){
            help = help == NO_HELP ? h_oe_searchab : NO_HELP;
            continue;
        }
	else if(rc == 10){
	    long nl;
	    *warped = 1;
	    warp_to_beginning();  /* go to top of addrbooks */
	    if((nl=first_selectable_line(0L)) != NO_LINE){
		*new_line = nl;
		q_status_message(0, 0, 2, "Searched to first entry");
		return 0;
	    }
	    else{
		q_status_message(0, 0, 2, "No entries");
		return -1;
	    }
	}
	else if(rc == 11){
	    long nl;
	    *warped = 1;
	    warp_to_end();  /* go to bottom */
	    if((nl=first_selectable_line(0L)) != NO_LINE){
		*new_line = nl;
		q_status_message(0, 0, 2, "Searched to last entry");
		return 0;
	    }
	    else{
		q_status_message(0, 0, 2, "No entries");
		return -1;
	    }
	}

        if(rc != 4)
          break;
    }

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

    if(nsearch_string[0] != '\0'){
        search_string[MAX_SEARCH] = '\0';
        strncpy(search_string, nsearch_string, MAX_SEARCH);
    }

    find_result =
	find_in_book(cur_line, search_string, new_line, wrapped);
    
    if(*wrapped)
      *warped = 1;

    return(find_result);
}


/*
 * return values of search_in_one_line are or'd combination of these
 */
#define MATCH_NICK      0x1  /* match in field 0 */
#define MATCH_FULL      0x2  /* match in field 1 */
#define MATCH_ADDR      0x4  /* match in field 2 */
#define MATCH_FCC       0x8  /* match in fcc field */
#define MATCH_COMMENT  0x10  /* match in comment field */
#define MATCH_BIGFIELD 0x20  /* match in one of the fields that crosses the
				whole screen, like a Title field */


/*
 * Search the display list for the given string.
 *
 * Args: cur_line     -- The current line position (in global display list)
 *			 of cursor
 *       string       -- String to search for
 *       new_line     -- Return value: new line position
 *       wrapped      -- Wrapped to beginning of display during search
 *
 * Result: The new line number is set if the search is successful.
 *         Returns 0 if found, -1 if not.
 */
int
find_in_book(cur_line, string, new_line, wrapped)
long  cur_line;
char *string;
long *new_line;
int  *wrapped;
{
    register AddrScrn_Disp *dl;
    long                    nl, nl_save;
    int			    fields;
    AdrBk_Entry            *abe;
    char                   *listaddr = NULL;
    DL_CACHE_S              *dlc,
			     dlc_save; /* a local copy */


    dprint(9, (debugfile, "- find_in_book -\n"));

    /*
     * Save info to allow us to get back to where we were if we can't find
     * the string.  Also used to stop our search if we wrap back to the
     * start and search forward.
     */
    nl_save = cur_line;
    dlc = get_dlc(nl_save);
    dlc_save = *dlc;

    *wrapped = 0;
    nl = cur_line + 1L;

    /* start with next line and search to the end of the disp_list */
    dl = dlist(nl);
    while(dl->type != End){
	if(dl->type == Simple ||
	   dl->type == ListHead ||
	   dl->type == ListEnt){
	    abe = ae(nl);
	    if(dl->type == ListEnt)
	      listaddr = listmem(nl);
	}
	else
	  abe = (AdrBk_Entry *)NULL;
	if(fields=search_in_one_line(dl, abe, listaddr, string))
	  goto found;

	dl = dlist(++nl);
    }


    /*
     * Wrap back to the start of the addressbook and search forward
     * from there.
     */
    warp_to_beginning();  /* go to top of addrbooks */
    nl = 0L;  /* line number is always 0 after warp_to_beginning */
    *wrapped = 1;

    dlc = get_dlc(nl);
    while(!matching_dlcs(&dlc_save, dlc) && dlc->type != DlcEnd){

	fill_in_dl_field(dlc);
	dl = &dlc->dl;

	if(dl->type == Simple ||
	   dl->type == ListHead ||
	   dl->type == ListEnt){
	    abe = ae(nl);
	    if(dl->type == ListEnt)
	      listaddr = listmem(nl);
	}
	else
	  abe = (AdrBk_Entry *)NULL;
	if(fields=search_in_one_line(dl, abe, listaddr, string))
	  goto found;

	dlc = get_dlc(++nl);
    }

    /* see if it is in the current line */
    fill_in_dl_field(dlc);
    dl = &dlc_save.dl;

    if(dl->type == Simple ||
       dl->type == ListHead ||
       dl->type == ListEnt){
	abe = ae(nl);
	if(dl->type == ListEnt)
	  listaddr = listmem(nl);
    }
    else
      abe = (AdrBk_Entry *)NULL;
    fields = search_in_one_line(dl, abe, listaddr, string);
    if((dl->type == Text || dl->type == Title) && dl->txt)
      fs_give((void **)&dl->txt);

    /* jump cache back to where we started */
    *wrapped = 0;
    warp_to_dlc(&dlc_save, nl_save);
    if(fields){
	*new_line = nl_save;
	return 0; /* because it was in current line */
    }
    else
      return -1;

found:
    if(fields & MATCH_FCC || fields & MATCH_COMMENT){
	*new_line = nl;
	if(!(fields & MATCH_COMMENT))
	  q_status_message(0, 1, 3, "Matched string in Fcc field");
	else if(!(fields & MATCH_FCC))
	  q_status_message(0, 1, 3, "Matched string in Comment field");
	else
	  q_status_message(0, 1, 3,
		"Matched string in Fcc and Comment fields");
    }
    /* skip to a selectable field */
    else if(!line_is_selectable(nl)){
	if((nl=first_selectable_line(nl+1)) != NO_LINE)
	  *new_line = nl;
    }
    else
      *new_line = nl;

    return 0;
}


/*
 * Look in line dl for string.
 *
 * Args: dl     -- the display list for this line
 *       abe    -- AdrBk_Entry if it is an address type
 *     listaddr -- list member if it is of type ListEnt
 *       string -- look for this string
 *
 * Result:  0   -- string not found
 *          Otherwise, a bitmask of which fields the string was found in.
 *      MATCH_NICK      0x1
 *      MATCH_FULL      0x2
 *      MATCH_ADDR      0x4
 *      MATCH_FCC       0x8
 *      MATCH_COMMENT  0x10
 *      MATCH_BIGFIELD 0x20
 */
int
search_in_one_line(dl, abe, listaddr, string)
AddrScrn_Disp *dl;
AdrBk_Entry   *abe;
char          *listaddr;
char          *string;
{
    register int c;
    int ret_val = 0;

    for(c = 0; c < 5; c++){
      switch(c){
	case 0:
	  switch(dl->type){
	    case Simple:
	    case ListHead:
	      if(srchstr(abe->nickname, string))
		ret_val |= MATCH_NICK;
	      break;

	    case Text:
	    case Title:
	      if(srchstr(dl->txt, string))
		ret_val |= MATCH_BIGFIELD;
	  }
	  break;

	case 1:
	  switch(dl->type){
	    case Simple:
	    case ListHead:
	      if(srchstr(abe->fullname, string))
		ret_val |= MATCH_FULL;
	  }
	  break;

	case 2:
	  switch(dl->type){
	    case Simple:
	      if(srchstr((abe->tag == Single) ? abe->addr.addr : NULL, string))
		ret_val |= MATCH_ADDR;
	      break;

	    case ListEnt:
	      if(srchstr(listaddr, string))
		ret_val |= MATCH_ADDR;
	      break;

	    case ClickHere:
	      if(srchstr(CLICKHERE, string))
		ret_val |= MATCH_BIGFIELD;
	      break;

	    case Empty:
	      if(srchstr(EMPTY, string))
		ret_val |= MATCH_BIGFIELD;
	      break;
	  }
	  break;

	case 3:  /* fcc */
	  switch(dl->type){
	    case Simple:
	    case ListHead:
	      if(srchstr(abe->fcc, string))
		ret_val |= MATCH_FCC;
	  }
	  break;

	case 4:  /* comment */
	  switch(dl->type){
	    case Simple:
	    case ListHead:
	      if(srchstr(abe->extra, string))
		ret_val |= MATCH_COMMENT;
	  }
	  break;
      }
    }

    return(ret_val);
}


/*
 * This is just here so that the create_abook_entry function can be put
 * into an overlay.  We can't do that unless the setjmp call is removed
 * from it.
 */
void
create_abook_entry_frontend(nickname, fullname, address, command_line)
char *nickname,
     *fullname,
     *address;
int   command_line;
{
    dprint(2, (debugfile, "-- create_abook_entry_frontend --\n"));

    if(setjmp(addrbook_changed_unexpectedly)){
	q_status_message(0, 5, 5, "Resetting address book...");
	dprint(1, (debugfile,
	    "RESETTING address book... create_abook_entry_frontend!\n"));
	flush_status_messages();
	addrbook_reset();
    }

    create_abook_entry(nickname, fullname, address, command_line);
}


/*
 * Add an entry to address book, called from outside address book.
 *
 * Args: nickname -- new nickname (can be null)
 *       fullname -- new fullname (can be null)
 *       address  -- new address (can be null)
 *   command_line -- line to prompt on
 *
 * Result: item is added to address book after editing by user
 *       error message queued if appropriate
 */
void
create_abook_entry(nickname, fullname, address, command_line)
char *nickname,
     *fullname,
     *address;
int   command_line;
{
    char         new_nickname[MAX_NICKNAME + 1],
                 new_fullname[MAX_FULLNAME + 1],
		 new_address[MAX_ADDRESS + 1];
    char         prompt[200],
		*fname;
    int          rc,
		 changing_existing = 0,
		 which_addrbook,
		 ans;
    SAVE_STATE_S state;
    AdrBk       *abook;
    PerAddrBook  *pab;
    AdrBk_Entry  *abe = (AdrBk_Entry *)NULL;
    adrbk_cntr_t  entry_num;

    dprint(2, (debugfile, "-- create_abook_entry --\n"));

    pab = setup_for_addrbook_add(&state, command_line);

    /* check we got it opened ok */
    if(pab == NULL || pab->address_book == NULL)
      goto create_abook_entry_cancel;

    abook = pab->address_book;

    /*----- nickname ------*/
    new_nickname[0] = '\0';
    if(nickname){
	strncpy(new_nickname, nickname, MAX_NICKNAME);
	new_nickname[MAX_NICKNAME] = '\0';
    }
    sprintf(prompt, "%s new nickname (one word and easy to remember): ",
	new_nickname[0] ? "Edit" : "Enter");
    rc = edit_nickname(abook, (AddrScrn_Disp *)NULL, command_line,
		new_nickname, prompt, h_oe_takenick, 1, 1);
    if(rc == -8){  /* this means an existing nickname was entered */

	abe = adrbk_lookup_by_nick(abook, new_nickname, &entry_num);
	if(!abe){  /* this shouldn't happen */
	    q_status_message1(0, 1, 3, "Already an entry %s in address book!",
		new_nickname);
	    goto create_abook_entry_cancel;
	}

	sprintf(prompt, "Entry %s (%s) exists, replace", new_nickname,
	    abe->fullname ? abe->fullname : "<no long name>");
	ans = want_to(prompt, 'n', 'n', NO_HELP, 0, 0);
	if(ans != 'y')
	  goto create_abook_entry_cancel;
	changing_existing++;
    }
    else if(rc != 0)
      goto create_abook_entry_cancel;
    
    if(!changing_existing && (long)abook->count >= MAX_ADRBK_SIZE){
	q_status_message(0, 2, 5,
	    "Address book is at maximum size. TakeAddr cancelled.");
	dprint(2, (debugfile, "Addrbook at Max size, TakeAddr cancelled\n"));
	goto create_abook_entry_cancel;
    }

    /*------ full name ------*/
    new_fullname[0] = '\0';
    if(fullname){
	strncpy(new_fullname, fullname, MAX_FULLNAME);
	new_fullname[MAX_FULLNAME] = '\0';
    }
    sprintf(prompt, "%s new Full Name%s: ",
	new_fullname[0] ? "Edit" : "Enter",
	new_fullname[0] ? "" : " (Last, First)");
    rc = edit_fullname(command_line, new_fullname, prompt, h_oe_takename);
    if(rc != 0 && rc != -9)
      goto create_abook_entry_cancel;

    /*------ address ------*/
    new_address[0] = '\0';
    if(address){
	strncpy(new_address, address, MAX_ADDRESS);
	new_address[MAX_ADDRESS] = '\0';
    }
    sprintf(prompt, "%s new e-mail address: ",
	new_address[0] ? "Edit" : "Enter");
    rc = edit_address(command_line, 0, new_address, prompt, h_oe_takeaddr);
    if(rc != 0 && rc != -9)
	goto create_abook_entry_cancel;

    /*
     * We may also want to check when changing_existing is set.  In that
     * case, we would look only in the other addrbooks, not the one we
     * know it is in.  We don't have a routine to do that right now and it
     * may not be worth it.  It isn't as serious since the duplicates already
     * exist in that case, whereas we're adding a new duplicate in the
     * case below.  The user probably wants to override the system value.
     */
    if(!changing_existing &&
	(fname = addr_lookup(new_nickname, &which_addrbook))){
	q_status_message3(0, 3, 7, "Warning! %s exists in %s as %s",
	    new_nickname, as.adrbks[which_addrbook].nickname, fname);
	fs_give((void **)&fname);
    }

    
    /*---- write it into the file ----*/
    rc = adrbk_add(abook,
		   new_nickname,
		   new_fullname,
		   new_address,
		   NULL,
		   NULL,
		   Single,
		   (adrbk_cntr_t *)NULL,
		   (int *)NULL);

    restore_state(&state);

    switch(rc){
      case 0:
	q_status_message3(0, 0, 3, "%s added. Address book %s%supdated",
	    new_nickname,
	    (as.n_addrbk > 1) ? pab->nickname : "",
	    (as.n_addrbk > 1) ? " " : "");
        dprint(2, (debugfile, "Added \"%s\",\"%s\",\"%s\" to \"%s\"\n",
	    new_nickname, new_fullname, new_address, pab->filename));
	    break;

      case -3:
      case -2:
        q_status_message1(1, 1, 4, "\007Error updating address book: %s",
               rc == -2 ? error_description(errno) : "Pine bug");
        dprint(1, (debugfile, "Error adding \"%s\",\"%s\",\"%s\" to %s: %s\n",
	    new_nickname, new_fullname, new_address, pab->filename,
		rc == -2 ? error_description(errno) : "Pine bug"));
        break;

      case -4:
	no_tabs_warning();
        break;
    }

    return;

create_abook_entry_cancel:
    cancel_warning(NO_DING, "addition");
    restore_state(&state);
}


/*
 * Add an entry to address book, called from outside address book.
 * It is for capturing addresses off incoming mail.
 * This is a front end for create_abook_entry.
 * It is also used for replacing an existing entry and for adding a single
 * new address to an existing list.
 * See also create_abook_list.
 *
 * Args: addr     -- ADDRESS to be added
 *       strvalue -- ADDRESS in printed out form for adding to lists
 *   command_line -- line to prompt on
 *
 * Result: item is added to one of the address books,
 *       an error message is queued if appropriate.
 */
void
add_abook_entry(addr, command_line)
ADDRESS *addr;
int      command_line;
{
    char new_fullname[MAX_FULLNAME + 1],
	 new_address[MAX_ADDRESS + 1];

    dprint(2, (debugfile, "-- add_abook_entry --\n"));

    /*-- rearrange full name (Last, First) ---*/
    new_fullname[0]              = '\0';
    new_fullname[MAX_FULLNAME]   = '\0';
    new_fullname[MAX_FULLNAME-1] = '\0';
    if(addr->personal != NULL){
	if(strindex(addr->personal, ',') != NULL){
	    int add_quotes = 0;
	    char *nf;

	    nf = new_fullname;
	    /*
	     * We'll get this wrong if it is already quoted but the quote
	     * doesn't start right at the beginning.
	     */
	    if(addr->personal[0] != '"'){
		add_quotes++;
		*nf++ = '"';
	    }
	    strncpy(nf, addr->personal, MAX_FULLNAME-2);
	    if(add_quotes)
	      strcat(nf, "\"");
	}
	else if(strindex(addr->personal, ' ') == NULL){  /* leave single word */
	    strncpy(new_fullname, addr->personal, MAX_FULLNAME);
	}
	else{
	    char *p, *q, *r;

	    /* switch to Last, First */
	    p = addr->personal;
	    while((q = strindex(p, ' ')) != NULL)
	      p = q + 1;
	    for(q = p, r = new_fullname; *q; *r++ = *q++)
	      ;/* do nothing */
	    *r++ = ',';
	    *r++ = ' ';
	    for(q = addr->personal; q < p; *r++ = *q++)
	      ;/* do nothing */
	    *r = '\0';
	    for(r--; r >= new_fullname && isspace(*r); *r-- = '\0')
	      ;/* do nothing */
	}
    }

    /* initial value for new address */
    new_address[0] = '\0';
    new_address[MAX_ADDRESS] = '\0';
    if(addr->mailbox)
      strncpy(new_address, addr->mailbox, MAX_ADDRESS);
    if(addr->host && addr->host[0] != '\0'){
	strncat(new_address, "@", MAX_ADDRESS - strlen(new_address));
	strncat(new_address, addr->host, MAX_ADDRESS - strlen(new_address));
    }

    create_abook_entry_frontend(NULL, new_fullname,
	new_address, command_line);
}


void
create_abook_list_frontend(new_entries, how_many, command_line)
char      **new_entries;
int         how_many;
int         command_line;
{
    dprint(2, (debugfile, "-- create_abook_list_frontend --\n"));

    if(setjmp(addrbook_changed_unexpectedly)){
	q_status_message(0, 5, 5, "Resetting address book...");
	dprint(1, (debugfile,
	    "RESETTING address book... create_abook_list_frontend!\n"));
	flush_status_messages();
	addrbook_reset();
    }

    create_abook_list(new_entries, how_many, command_line);
}


/*
 * Add a list to address book, called from outside address book.
 * It is also used for adding to an existing list.
 * It is for capturing addresses off incoming mail.
 * See also add_abook_entry.
 *
 * Args: new_entries -- a list of addresses to add to a list or to form
 *                      a new list with
 *       how_many    -- how many in new_entries list
 *      command_line -- line to prompt on
 *
 * Result: item is added to one of the address books,
 *       an error message is queued if appropriate.
 */
void
create_abook_list(new_entries, how_many, command_line)
char      **new_entries;
int         how_many;
int         command_line;
{
    char          new_nickname[MAX_NICKNAME + 1],
                  new_fullname[MAX_FULLNAME + 1];
    char          prompt[200],
		 *fname;
    int           rc,
		  list_add = 0,
		  which_addrbook,
		  ans;
    AdrBk        *abook;
    SAVE_STATE_S  state;
    PerAddrBook  *pab;
    AdrBk_Entry  *abe = (AdrBk_Entry *)NULL;
    adrbk_cntr_t  entry_num = NO_NEXT;

    dprint(2, (debugfile, "-- create_abook_list --\n"));

    pab = setup_for_addrbook_add(&state, command_line);

    /* check we got it opened ok */
    if(pab == NULL || pab->address_book == NULL)
      goto create_abook_list_cancel;

    abook = pab->address_book;

    /*----- nickname ------*/
    sprintf(prompt,
      "Enter new or existing list nickname (one word and easy to remember): ");
    new_nickname[0] = '\0';
    rc = edit_nickname(abook, (AddrScrn_Disp *)NULL, command_line,
		new_nickname, prompt, h_oe_takenick, 1, 1);
    if(rc == -8){  /* this means an existing nickname was entered */

	abe = adrbk_lookup_by_nick(abook, new_nickname, &entry_num);
	if(!abe){  /* this shouldn't happen */
	    q_status_message1(0, 1, 3, "Already an entry %s in address book!",
		new_nickname);
	    goto create_abook_list_cancel;
	}

	if(abe->tag == Single){  /* single address entry */
	    sprintf(prompt, "Entry %s (%s) exists, replace", new_nickname,
		abe->fullname ? abe->fullname : "<no long name>");
	    ans = want_to(prompt, 'n', 'n', NO_HELP, 0, 0);
	    if(ans != 'y')
		goto create_abook_list_cancel;
	    /* have to delete Single and add List below */
            rc = adrbk_delete(abook, (a_c_arg_t)entry_num);
	    if(rc != 0){
		q_status_message1(0, 2, 5, "\007Error writing address book: %s",
                                                   error_description(errno));
		goto create_abook_list_cancel;
	    }
	}
	else if(abe->tag == List){
	    ESCKEY_S    choices[6];
	    HelpType    help;

	    choices[0].ch    = 'r';
	    choices[0].rval  = 'r';
	    choices[0].name  = "R";
	    choices[0].label = "Replace";
	    choices[1].ch    = 'a';
	    choices[1].rval  = 'a';
	    choices[1].name  = "A";
	    choices[1].label = "Add";
	    choices[2].ch    = -1;

	    sprintf(prompt,
		"List %s (%s) exists, replace or add addresses to it ? ",
		new_nickname,
		abe->fullname ? abe->fullname : "<no long name>");

	    help = h_oe_take_or_replace;

	    ans = radio_buttons(prompt,
			      command_line,
			      0,
			      choices,
			      'a',
			      'x',
			      0,
			      help,
			      0);

	    if(ans == 'a')
	      list_add++;
	    else if(ans == 'r'){
		rc = adrbk_delete(abook, (a_c_arg_t)entry_num);
		if(rc != 0){
		    q_status_message1(0, 1, 5,
			"\007Error writing address book: %s",
		        error_description(errno));
		    goto create_abook_list_cancel;
		}
	    }
	    else
	      goto create_abook_list_cancel;
	}
    }
    else if(rc != 0)
      goto create_abook_list_cancel;

    if((long)abook->count >= MAX_ADRBK_SIZE){
	q_status_message(0, 1, 5,
	    "Address book is at maximum size. TakeAddr cancelled.");
	dprint(2, (debugfile, "Addrbook at Max size, TakeAddr cancelled\n"));
	goto create_abook_list_cancel;
    }

    if(!list_add){  /* don't edit full name for list additions */
	/*------ full name ------*/
	new_fullname[0] = '\0';
	sprintf(prompt, "Enter new List Full Name: ");
	rc = edit_fullname(command_line, new_fullname, prompt, h_oe_crlst_full);
	if(rc != 0 && rc != -9)
	  goto create_abook_list_cancel;
    }

    if(!list_add &&
	(fname = addr_lookup(new_nickname, &which_addrbook))){
	q_status_message3(0, 3, 7, "Warning! %s exists in %s as %s",
	    new_nickname, as.adrbks[which_addrbook].nickname, fname);
	fs_give((void **)&fname);
    }

    
    /*---- write it into the file ----*/
    /* we have to create new list head */
    if(!list_add){
	rc = adrbk_add(abook,
		       new_nickname,
		       new_fullname,
		       NULL,
		       NULL,
		       NULL,
		       List,
		       &entry_num,
		       (int *)NULL);
    }
    /* add entries to (now) existing list */
    rc = adrbk_nlistadd(abook, (a_c_arg_t)entry_num, new_entries);

    restore_state(&state);

    switch(rc){
      case 0:
	if(list_add)
	  q_status_message4(0, 0, 2,
		"Entr%s added to \"%s\". Address book %s%supdated.",
		how_many > 1 ? "ies" : "y",
		new_nickname,
		(as.n_addrbk > 1) ? pab->nickname : "",
		(as.n_addrbk > 1) ? " " : "");
	else
	  q_status_message3(0, 0, 3, "%s added. Address book %s%supdated",
		new_nickname,
		(as.n_addrbk > 1) ? pab->nickname : "",
		(as.n_addrbk > 1) ? " " : "");
        dprint(2, (debugfile, "Added \"%s\",\"%s\" to \"%s\"\n",
	    new_nickname, new_fullname, pab->filename));
	    break;

      case -3:
      case -2:
        q_status_message1(1, 1, 4, "\007Error updating address book: %s",
               rc == -2 ? error_description(errno) : "Pine bug");
        dprint(1, (debugfile, "Error adding \"%s\",\"%s\" to %s: %s\n",
	    new_nickname, new_fullname, pab->filename,
		rc == -2 ? error_description(errno) : "Pine bug"));
        break;

      case -4:
	no_tabs_warning();
        break;
    }

    return;

create_abook_list_cancel:
    cancel_warning(NO_DING, "addition");
    restore_state(&state);
}


/*
 * Prep addrbook for TakeAddr add operation.
 *
 * Arg: savep -- Address of a pointer to save addrbook state in.
 *      stp   -- Address of a pointer to save addrbook state in.
 *
 * Returns: a PerAddrBook pointer, or NULL.
 */
PerAddrBook *
setup_for_addrbook_add(state, command_line)
SAVE_STATE_S  *state;
int	       command_line;
{
    PerAddrBook  *pab;

    init_ab_if_needed();
    save_state(state);

    if(as.n_addrbk == 0){
        q_status_message(0, 1, 3, "Can't open address book!");
        return NULL;
    }
    else
      pab = use_this_addrbook(command_line);
    
    if(!pab)
      return NULL;

    /* initialize addrbook so we can add to it */
    init_abook(pab, Open);

    if(pab->ostatus != Open){
        q_status_message(0, 1, 3, "Can't open address book!");
        return NULL;
    }
    if(pab->access != ReadWrite){
	if(pab->access == ReadOnly)
	  readonly_warning(NO_DING, NULL);
	else if(pab->access == NoAccess)
	  q_status_message(0, 1, 3,
		"AddressBook not accessible, permission denied");
        return NULL;
    }

    return(pab);
}


/*
 *  Interface to address book lookups for callers outside or inside this file.
 *
 * Args: nickname       -- The nickname to look up
 *       which_addrbook -- If matched, addrbook number it was found in.
 *
 * Result: returns NULL or the corresponding full name.  The fullname is
 * allocated here so the caller must free it.
 *
 * This opens the address books if they haven't been opened and restores
 * them to the state they were in upon entry.
 */
char *
addr_lookup(nickname, which_addrbook)
char *nickname;
int  *which_addrbook;
{
    AdrBk_Entry  *abe;
    SAVE_STATE_S  state;
    char         *fullname;

    dprint(9, (debugfile, "- addr_lookup -\n"));

    init_ab_if_needed();
    save_state(&state);

    abe = adrbk_lookup_with_opens_by_nick(nickname, 0, which_addrbook);

    fullname = (abe && abe->fullname) ? cpystr(abe->fullname) : NULL;

    restore_state(&state);

    return(fullname);
}


/*
 * Warn about bad characters in address.
 *
 * Args:  string   -- the address to check
 *       spaces_ok -- don't check for spaces
 *
 * Returns: 0 - ok
 *          1 - bad chars, try again
 *
 * Side effect: Can flush addrbook entry cache entries so they need to be
 * re-fetched afterwords.
 */
int
warn_bad_addr(string, spaces_ok)
char *string;
int   spaces_ok;
{
    int ic;
    int bad_addr;
    char *error = "";
    char *addr;

    if((ic = illegal_chars(string)) != 0){
	if(!(ic&0x08) && spaces_ok && ic == 0x1)
	  ;/* do nothing */
	else{
	    if(ic&0x8)
	      q_status_message(0, 1, 1,
			      "unbalanced quotes in addresses not allowed");
	    else
	      q_status_message1(0, 1, 1,
			      "unquoted %ss not allowed in addresses",
			      ic&0x2 ? "comma" : ic&0x4 ? "paren" : "space");

	    display_message(NO_OP_COMMAND);
	    sleep(1);
	    return 1;
	}
    }

    bad_addr = (our_build_address(string, &addr, &error, NULL, 1) >= 0) ? 0 : 1;
    fs_give((void **)&addr);

    if(*error){
        q_status_message1(0, 1, 1, "%s", error);
	display_message(NO_OP_COMMAND);
	sleep(3);
    }
    return(bad_addr);
}


/*
 * Looks for illegal characters in addresses.  Don't have to worry about
 * \ quoting since addr-specs can't use that kind of quoting.
 * 
 * Args:  string -- the address to check
 *
 * Returns: 0 -- ok
 *       else, bitwise or of
 *          0x1 -- found a space
 *          0x2 -- found a comma
 *          0x4 -- found a (
 *          0x8 -- unbalanced "'s 
 *
 * (Should probably generalize this a little to take an argument telling
 *  what to look for.  I'm going to put that off for now.)
 */
int
illegal_chars(string)
char *string;
{
  register char *p;
  register int   in_quotes = 0,
                 ret = 0;

  for(p=string; p && *p; p++){
    if(*p == '"')
      in_quotes = !in_quotes;
    else if(!in_quotes){
      switch(*p){
	case SPACE:
	  ret |= 0x1;
	  break;
	case ',':
	  ret |= 0x2;
	  break;
	case '(':
	  ret |= 0x4;
	  break;
      }
    }
  }

  if(in_quotes)
    ret |= 0x8;

  return ret;
}


/*
 * These chars in nicknames will mess up parsing.
 *
 * Returns 0 if ok, 1 if not.
 * Prints an error status message on error.
 */
int
nickname_check(nickname)
char *nickname;
{
    register char *t;

    if((t = strindex(nickname, ' ')) ||
       (t = strindex(nickname, ',')) ||
       (t = strindex(nickname, '"')) ||
       (t = strindex(nickname, ';')) ||
       (t = strindex(nickname, ':')) ||
       (t = strindex(nickname, '@')) ||
       (t = strindex(nickname, '(')) ||
       (t = strindex(nickname, ')')) ||
       (t = strindex(nickname, '\\')) ||
       (t = strindex(nickname, '[')) ||
       (t = strindex(nickname, ']')) ||
       (t = strindex(nickname, '<')) ||
       (t = strindex(nickname, '>'))){
	char s[4];
	s[0] = '"';
	s[1] = *t;
	s[2] = '"';
	s[3] = '\0';
	q_status_message1(0, 1, 2, "%s not allowed in nicknames",
	    *t == ' ' ?
		"Blank spaces" :
		*t == ',' ?
		    "Commas" :
		    *t == '"' ?
			"Quotes" :
			s);
	display_message(NO_OP_COMMAND);
	(void)sleep((unsigned)1);
	return 1;
    }
    return 0;
}


/*
 * This is like build_address() only it doesn't close
 * everything down when it is done, and it doesn't open addrbooks that
 * are already open.  Other than that, it has the same functionality.
 * It opens addrbooks that haven't been opened and saves and restores the
 * addrbooks open states (if save_and_restore is set).
 *
 * Args: to                    -- the address to attempt expanding
 *       full_to               -- a pointer to result
 *       error                 -- a pointer to an error message, if non-null
 *       fcc                   -- a pointer to returned fcc, if non-null
 *       save_and_restore      -- restore addrbook states when finished
 *
 * Results:    0 -- address is ok
 *            -1 -- address is not ok
 * full_to will contains the expanded address on success, or a copy to to
 *         on failure
 * *error  will point to an error message on failure it it is non-null
 *
 * Side effect: Can flush addrbook entry cache entries so they need to be
 * re-fetched afterwords.
 */
int
our_build_address(to, full_to, error, fcc, save_and_restore)
char *to,
    **full_to,
    **error,
    **fcc;
int   save_and_restore;
{
    int ret;

    dprint(7, (debugfile, "- our_build_address -  (to=%s)\n", to?to:"nul"));

    if(!to){
	if(full_to)
	  *full_to = cpystr("");
	ret = 0;
    }
    else
      ret = build_address_internal(to, full_to, error, fcc, save_and_restore);

    dprint(8, (debugfile, "   our_build_address says %s address\n",
	ret ? "BAD" : "GOOD"));

    return(ret);
}


/*
 * This is the build_address used by the composer to check for an address
 * in the addrbook.
 *
 * Args: to      -- the passed in line to parse
 *       full_to -- Address of a pointer to return the full address in.
 *		    This will be allocated here and freed by the caller.
 *       error   -- Address of a pointer to return an error message in.
 *                  This is not allocated so should not be freed by the caller.
 *       fcc     -- Address of a pointer to return the fcc in.
 *		    This will be allocated here and freed by the caller.
 *
 * Result:  0 is returned if address was OK, 
 *         -1 if address wasn't OK.
 *
 * Side effect: Can flush addrbook entry cache entries so they need to be
 * re-fetched afterwords.
 */
int
build_address(to, full_to, error, fcc)
char *to,
    **full_to,
    **error,
    **fcc;
{
    register char *p;
    int ret_val;

    dprint(2, (debugfile, "- build_address -\n"));

    /* check to see if to string is empty to avoid work */
    for(p = to; p && *p && isspace(*p); p++)
      ;/* do nothing */
    if(!p || !*p){
	if(full_to)
	  *full_to = cpystr(to ? to : "");  /* because pico does a strcmp() */
	return 0;
    }

    if(error != NULL)
      *error = (char *)NULL;

    clear_cursor_pos();

    /*
     * If we end up jumping back here because somebody else changed one of
     * our addrbooks out from underneath us, we may well leak some memory.
     * That's probably ok since this will be very rare.
     */
    if(setjmp(addrbook_changed_unexpectedly)){
	if(full_to && *full_to)
	  fs_give((void **)full_to);
	if(fcc && *fcc)
	  fs_give((void **)fcc);
	q_status_message(0, 2, 5, "Resetting address book...");
	dprint(1, (debugfile,
	    "RESETTING address book... build_address(%s)!\n", to));
	flush_status_messages();
	addrbook_reset();
    }

    ret_val = build_address_internal(to, full_to, error, fcc, 1);

    if(error != NULL && *error == NULL)
      *error = cpystr("");

    return(ret_val);
}


/*
 * Given an address, expand it based on address books, local domain, etc.
 * This will open addrbooks if needed before checking (actually one of
 * its children will open them).
 *
 * Args: to       -- The given address to expand
 *       full_to  -- Returned value after parsing to.
 *       error    -- This gets pointed at error message, if any
 *       fcc      -- Returned value of fcc for first addr in to
 *       save_and_restore -- restore addrbook state when done
 *
 * Result:  0 is returned if address was OK, 
 *         -1 if address wasn't OK.
 * The address is expanded, fully-qualified, and personal name added.
 *
 * Input may have more than one address separated by commas.
 *
 * Side effect: Can flush addrbook entry cache entries so they need to be
 * re-fetched afterwords.
 */
int
build_address_internal(to, full_to, error, fcc, save_and_restore)
char *to,
    **full_to,
    **error,
    **fcc;
int   save_and_restore;
{
    ADDRESS *a;
    int      loop, i;
    int      tried_route_addr_hack = 0;
    int      estimated_size;
    char     tmp[MAX_ADDR_FIELD + 3];
    static char *fcc_last = NULL;
    SAVE_STATE_S  state;
    PerAddrBook  *pab;

    dprint(8, (debugfile, "- build_address_internal -\n"));

    init_ab_if_needed();
    if(save_and_restore)
      save_state(&state);

start:
    loop = 0;
    ps_global->c_client_error[0] = '\0';

    a = expand_address(to, ps_global->maildomain,
		       F_OFF(F_QUELL_LOCAL_LOOKUP, ps_global)
			 ? ps_global->maildomain : NULL,
		       &loop, fcc, error, 0);

    /*
     * If the address is a route-addr, expand_address() will have rejected
     * it unless it was enclosed in brackets, since route-addrs can't stand
     * alone.  Try it again with brackets.  We should really be checking
     * each address in the list of addresses instead of assuming there is
     * only one address, but we don't want to have this function know
     * all about parsing rfc822 addrs.
     */
    if(!tried_route_addr_hack &&
        ps_global->c_client_error[0] != '\0' &&
	to[0] == '@'){

	tried_route_addr_hack++;

	/* add brackets to whole thing */
	strcat(strcat(strcpy(tmp, "<"), to), ">");

	loop = 0;
	ps_global->c_client_error[0] = '\0';

	/* try it */
	a = expand_address(tmp, ps_global->maildomain,
			   F_OFF(F_QUELL_LOCAL_LOOKUP, ps_global)
			     ? ps_global->maildomain : NULL,
			   &loop, fcc, error, 0);

	/* if no error this time, use it (even replace to with it) */
	if(ps_global->c_client_error[0] == '\0'){
	    strcpy(to, tmp);
	    goto ok;
	}
	else  /* go back and use what we had before, so we get the error */
	  goto start;
    }

    if(save_and_restore)
      restore_state(&state);

    /*
     * Clear references so that Addrbook Entry caching in adrbklib.c
     * is allowed to throw them out of cache.
     */
    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab->ostatus == Open || pab->ostatus == NoDisplay)
	  adrbk_clearrefs(pab->address_book);
    }

    if(ps_global->c_client_error[0] != '\0'){
        /* Parse error.  Return string as is and error message */
	if(full_to)
	  *full_to = cpystr(to);
        if(error != NULL)
          *error = cpystr(ps_global->c_client_error);
        dprint(2, (debugfile,
	    "build_address_internal returning parse error: %s\n",
                   ps_global->c_client_error));
        return -1;
    }

    /*
     * If there's a loop in the addressbook, we modify the address and
     * send an error back, but we still return 0.
     */
ok:
    if(loop){
        if(error != NULL)
          *error = cpystr("Loop or Duplicate detected in addressbook!");
    }


    estimated_size = est_size(a);
    if(full_to){
	*full_to       = (char *)fs_get((size_t)estimated_size);
	(*full_to)[0]  = '\0';
	rfc822_write_address(*full_to, a);
    }

    /*
     * This first condition means that addressbook fcc's override the
     * fcc-rule.
     */
    if(fcc && !*fcc){
	if(ps_global->fcc_rule == FCC_RULE_LAST){
	    if(fcc_last)
	      *fcc = cpystr(fcc_last);
	}
	else{
	    if(ps_global->fcc_rule == FCC_RULE_RECIP &&
		get_uname(a ? a->mailbox : NULL, tmp, MAX_ADDR_FIELD + 3))
	      *fcc = cpystr(tmp);
	    else
	      *fcc = cpystr(ps_global->VAR_DEFAULT_FCC);

	    if(fcc_last)
	      fs_give((void **)&fcc_last);
	    fcc_last = cpystr(*fcc);
	}
    }

    mail_free_address(&a);

    return 0;
}


/*
 * Expand an address string against the address books, local names, and domain.
 *
 * Args: a_string -- the address string to parse and expand
 *     userdomain -- domain the user is in
 *    localdomain -- domain of the password file (usually same as userdomain)
 *  loop_detected -- pointer to an int we set if we detect a loop in the
 *		     address books (or a duplicate in a list)
 *       fcc      -- Returned value of fcc for first addr in a_string
 *
 * Result: An adrlist of expanded addresses is returned
 *
 * If the localdomain is NULL, then no lookup against the password file will
 * be done.
 */
ADDRESS *
expand_address(a_string, userdomain, localdomain, loop_detected, fcc, error,
    recursing)
char   *a_string,
       *userdomain,
       *localdomain;
int    *loop_detected;
char  **fcc;
char  **error;
int     recursing;
{
    int          domain_length, length;
    ADDRESS     *adr, *a, *a_tail, *adr2, *a2, *a_temp;
    AdrBk_Entry *abe;
    char        *list, *l1, **l2;
    char        *tmp_a_string;
    static char *fakedomain;

    dprint(9, (debugfile, "- expand_address -  (%s)\n", a_string));

    /*
     * We use the domain "@" to detect an unqualified address.  If it comes
     * back from rfc822_parse_adrlist with the host part set to "@", then
     * we know it must have been unqualified (so we should look it up in the
     * addressbook).  Later, we also use a c-client hack.  If an ADDRESS has
     * a host part that begins with @ then rfc822_write_address()
     * will write only the local part and leave off the @domain part.
     *
     * We also malloc enough space here so that we can strcpy over host below.
     */
    if(!recursing){
	int domain_length;

	domain_length = max(localdomain != NULL ? strlen(localdomain) : 0,
			    userdomain != NULL ? strlen(userdomain)  : 0);
	fakedomain = (char *)fs_get(domain_length + 1);
	memset((void *)fakedomain, '@', (size_t)domain_length);
	fakedomain[domain_length] = '\0';

	if(fcc)
	  *fcc = NULL;
    }

    adr = NULL;

    tmp_a_string = cpystr(a_string); /* next func feels free to destroy input */
    rfc822_parse_adrlist(&adr, tmp_a_string, fakedomain);
    fs_give((void **)&tmp_a_string);

    for(a = adr, a_tail = adr; a;){

        if(a->host && a->host[0] == '@'){
            /*
             * Hostname is "@" indicating name wasn't qualified.
             * Need to look up in address book, and the password file.
             * If no match then fill in the local domain for host
             */
            abe = adrbk_lookup_with_opens_by_nick(a->mailbox,
						  !recursing,
						  (int *)NULL);

            if(abe == NULL){
		/* normal case */
	        if(F_OFF(F_COMPOSE_REJECTS_UNQUAL, ps_global)){
		    if(localdomain != NULL && a->personal == NULL){
			/* lookup in passwd file for local full name */
			a->personal = local_name_lookup(a->mailbox); 

			strcpy(a->host, a->personal == NULL ? userdomain :
							      localdomain);
		    }
		    else
		      strcpy(a->host, userdomain);
		}
		else{
		    if(error != NULL && *error == (char *)NULL){
			char ebuf[200];

			sprintf(ebuf, "%s not in addressbook", a->mailbox);
			*error = cpystr(ebuf);
		    }
		}

                /*--- Move to next address in list -----*/
                a_tail = a;
                a = a->next;

            }
	    else{
                /*
                 * There was a match in the address book.  We have to do a lot
                 * here because the item from the address book might be a 
                 * distribution list.  Treat the string just like an address
                 * passed in to parse and recurse on it.  Then combine
                 * the personal names from address book.  Lastly splice
                 * result into address list being processed
                 */

		/*
		 * Easy case for fcc.  This is a nickname that has
		 * an fcc associated with it.
		 */
		if(a == adr && fcc && !*fcc && abe->fcc && abe->fcc[0])
		  *fcc = cpystr(abe->fcc);

                if(recursing && abe->referenced){
                     /*---- caught an address loop! ----*/
                    fs_give(((void **)&a->host));
		    (*loop_detected)++;
                    a->host = cpystr("***address-loop-or_duplicate***");
                    continue;
                }
                abe->referenced++;   /* For address loop detection */
                if(abe->tag == List){
                    length = 0;
                    for(l2 = abe->addr.list; *l2; l2++)
                        length += (strlen(*l2) + 1);
                    list = (char *)fs_get(length + 1);
                    l1 = list;
                    for(l2 = abe->addr.list; *l2; l2++){
                        if(l1 != list)
                          *l1++ = ',';
                        strcpy(l1, *l2);
                        l1 += strlen(l1);
                    }
                    adr2 = expand_address(list, userdomain, localdomain,
						loop_detected, fcc, error, 1);
                    fs_give((void **)&list);
                }
                else if(abe->tag == Single){
                    if(strcmp(abe->addr.addr, a->mailbox)){
                        adr2 = expand_address(abe->addr.addr, userdomain,
				    localdomain, loop_detected, fcc, error, 1);
                    }
		    else{
                        /*
			 * A loop within plain single entry is ignored.
			 * Set up so later code thinks we expanded.
			 */
                        adr2          = mail_newaddr();
                        adr2->mailbox = cpystr(a->mailbox);
                        adr2->host    = cpystr(userdomain);
                        adr2->adl     = cpystr(a->adl);
                    }
                }
                if(adr2 == NULL){
                    /* expanded to nothing, hack out of list */
                    a_temp = a;
                    if(a == adr){
                        adr    = a->next;
                        a      = adr;
                        a_tail = adr;
                    }
		    else{
                        a_tail->next = a->next;
                        a            = a->next;
                    }
                    mail_free_address(&a_temp);
                    continue;
                }
                /*
                 * Personal names:  If the expanded address has a personal
                 * name, tack the full name from the address book on in front.
                 * This mainly occurs with a distribution list where the
                 * list has a full name, and the first person in the list also
                 * has a full name.  Also, avoid the case where a name might
		 * get doubled by getting expanded against the address book
		 * and against the passwd file.  This happens if the address
		 * in the entry is just the unqualified login in the passwd
		 * file.
		 *
		 * This algorithm doesn't work very well if lists are
		 * included within lists, but it's not clear what would
		 * be better.
                 */
                if(adr2->personal && abe->fullname &&
                   strcmp(adr2->mailbox, abe->addr.addr)){
                    /* combine list name and personal name */
                    char *name = (char *)fs_get(strlen(adr2->personal) +
                                      strlen(abe->fullname) + 5);
                    sprintf(name, "%s -- %s", abe->fullname, adr2->personal);
                    fs_give((void **)&adr2->personal);
                    adr2->personal = name;
                }
		else
                  adr2->personal = abe->fullname
				      ? cpystr(adrbk_formatname(abe->fullname))
				      : cpystr("");

                /* splice new list into old list */
                for(a2 = adr2; a2->next != NULL; a2 = a2->next)
		  ;/* do nothing */
                a2->next = a->next;
                if(a == adr){
                    adr    = adr2;
                    a_tail = a2;
                }
		else
                  a_tail->next = adr2;

                /* advance to next item, and free replaced ADDRESS */
                a_tail       = a2;
                a_temp       = a;
                a            = a->next;
                a_temp->next = NULL;  /* So free won't do whole list */
                mail_free_address(&a_temp);
            }

        }
	else{
            /* Already fully qualified hostname -- nothing to do */
            a_tail = a;
            a      = a->next;
        }

	/* if it's first addr in list and fcc hasn't been set yet */
	if(a_tail == adr && fcc && !*fcc){
	    AdrBk_Entry *abe2;

	    /*
	     * This looks for the addressbook entry that matches the given
	     * address.  It looks through all the addressbooks
	     * looking for an exact match and then returns that entry.
	     */
	    abe2 = addr_to_abe(a_tail);
	    if(abe2 && abe2->fcc && abe2->fcc[0])
	      *fcc = cpystr(abe2->fcc);
	}
    }

    if(!recursing)
      fs_give((void **)&fakedomain);

    return(adr);
}


/*
 * Look through addrbooks for nickname, opening addrbooks first
 * if necessary.  It is assumed that the caller will restore the
 * state of the addrbooks if desired.
 *
 * Args: nickname       -- the nickname to lookup
 *       clearrefs      -- clear reference bits before lookup
 *       which_addrbook -- If matched, addrbook number it was found in.
 *
 * Results: A pointer to an AdrBk_Entry is returned, or NULL if not found.
 * Stop at first match (so order of addrbooks is important).
 */
AdrBk_Entry *
adrbk_lookup_with_opens_by_nick(nickname, clearrefs, which_addrbook)
char *nickname;
int   clearrefs;
int  *which_addrbook;
{
    AdrBk_Entry *abe = (AdrBk_Entry *)NULL;
    int i;
    PerAddrBook *pab;

    dprint(5, (debugfile, "- adrbk_lookup_with_opens_by_nick(%s) -\n",
	nickname));

    for(i = 0; i < as.n_addrbk; i++){

	pab = &as.adrbks[i];

	if(pab->ostatus != Open && pab->ostatus != NoDisplay)
	  init_abook(pab, NoDisplay);

	if(clearrefs)
	  adrbk_clearrefs(pab->address_book);

	abe = adrbk_lookup_by_nick(pab->address_book,
		    nickname,
		    (adrbk_cntr_t *)NULL);
	if(abe)
	  break;
    }

    if(abe && which_addrbook)
      *which_addrbook = i;

    return(abe);
}


/*
 * Find the addressbook entry that matches the argument address.
 * Searches through all addressbooks looking for the match.
 * Opens addressbooks if necessary.  It is assumed that the caller
 * will restore the state of the addrbooks if desired.
 *
 * Args: addr -- the address we're trying to match
 *
 * Returns:  NULL -- no match found
 *           abe  -- a pointer to the addrbook entry that matches
 */
AdrBk_Entry *
addr_to_abe(addr)
ADDRESS *addr;
{
    register PerAddrBook *pab;
    int adrbk_number;
    AdrBk_Entry *abe;
    char abuf[MAX_ADDR_FIELD+1];

    if(!(addr && addr->mailbox))
      return (AdrBk_Entry *)NULL;

    strncpy(abuf, addr->mailbox, MAX_ADDR_FIELD);
    if(addr->host && addr->host[0])
      strncat(strncat(abuf, "@", MAX_ADDR_FIELD), addr->host, MAX_ADDR_FIELD);

    /* for each addressbook */
    for(adrbk_number = 0; adrbk_number < as.n_addrbk; adrbk_number++){

	pab = &as.adrbks[adrbk_number];

	if(pab->ostatus != Open && pab->ostatus != NoDisplay)
	  init_abook(pab, NoDisplay);

	abe = adrbk_lookup_by_addr(pab->address_book,
				   abuf,
				   (adrbk_cntr_t *)NULL);
	if(abe)
	  return(abe);
    }

    return NULL;
}


/*
 * Turn a list of address structures into a formatted string
 *
 * Args: adrlist -- An adrlist
 * Result:  malloced, comma seperated list of addresses
 */
char *
addr_list_string(adrlist)
ADDRESS *adrlist;
{
    int               len;
    char             *string, *s;
    register ADDRESS *a;

    if(!adrlist)
      return(cpystr(""));
    
    len = 0;
    for(a = adrlist; a; a = a->next)
      len += (strlen(addr_string(a)) + 2);

    string = (char *)fs_get(len);
    s      = string;
    s[0]   = '\0';

    for(a = adrlist; a; a = a->next){

	strcpy(s, addr_string(a));
	s += strlen(s);

	if(a->next){
	    *s++ = ',';
	    *s++ = ' ';
	}
    }

    return(string);
}


/*
 * Format an address structure into a string
 *
 * Args: addr -- Single ADDRESS structure to turn into a string
 *
 * Result:  Returns pointer to internal static formatted string.
 * Just uses the c-client call to do this.
 */
char *
addr_string(addr)
ADDRESS *addr;
{
    ADDRESS tmp_addr;
    static char string[MAX_ADDR_EXPN+1];

    string[0] = '\0';
    tmp_addr = *addr;
    tmp_addr.next = NULL;
    rfc822_write_address(string, &tmp_addr);
    return(string);
}


/*
 * Check to see if address is that of the current user running pine
 *
 * Args: a  -- Address to check
 *       ps -- The pine_state structure
 *
 * Result: returns 1 if it matches, 0 otherwise.
 *
 * The mailbox must match the user name and (the hostname must match or
 * the full name must match).  In matching the hostname, matches occur if
 * the hostname on the message is blank, or if it matches the local
 * hostname, the full hostname with qualifying domain, or the qualifying
 * domain without a specific host.  Note, there is a very small chance
 * that we will err on the non-conservative side here.  That is, we may
 * decide two addresses are the same even though they are different
 * (because we do case-insensitive compares on the mailbox).  That might
 * cause a reply not to be sent to somebody because they look like they
 * are us.  This should be very, very rare.
 */
int
address_is_us(a, ps)
ADDRESS     *a;
struct pine *ps;
{
    if(!a || a->mailbox == NULL)
      return 0;

    /* at least LHS must match */
    if(strucmp(a->mailbox, ps->VAR_USER_ID) == 0

                      &&
       /* and either personal name matches or hostname matches */

    /* personal name matches if it's not there or if it actually matches */
   ((a->personal == NULL || strucmp(a->personal , ps->VAR_PERSONAL_NAME) == 0)
                              ||
    /* hostname matches if it's not there, */
    (a->host == NULL ||
       /* or if hostname and userdomain (the one user sets) match exactly, */
      ((ps->userdomain && a->host && strucmp(a->host,ps->userdomain) == 0) ||

              /*
               * or if(userdomain is either not set or it is set to be
               * the same as the localdomain or hostname) and (the hostname
               * of the address matches either localdomain or hostname)
               */
             ((ps->userdomain == NULL ||
               strucmp(ps->userdomain, ps->localdomain) == 0 ||
               strucmp(ps->userdomain, ps->hostname) == 0) &&
              (strucmp(a->host, ps->hostname) == 0 ||
               strucmp(a->host, ps->localdomain) == 0))))))
        return 1;

    return 0;
}


/*
 * Compare the two addresses, and return true if they're the same,
 * false otherwise
 *
 * Args: a -- First address for comparison
 *       b -- Second address for comparison
 *
 * Result: returns 1 if it matches, 0 otherwise.
 */
int
address_is_same(a, b)
    ADDRESS *a, *b;
{
    return(a && b && strucmp(a->mailbox,b->mailbox) == 0
           && strucmp(a->host,b->host) == 0);
}


/*
 * Compute an upper bound on the size of the array required by
 * rfc822_write_address for this list of addresses.
 *
 * Args: adrlist -- The address list.
 *
 * Returns -- an integer giving the upper bound
 */
int
est_size(adrlist)
ADDRESS *adrlist;
{
    ADDRESS *b;
    register int cnt = 0;

    for(b = adrlist; b; b = b->next){
	cnt   += (b->personal ? strlen(b->personal) : 0);
	cnt   += (b->mailbox  ? strlen(b->mailbox)  : 0);
	cnt   += (b->adl      ? strlen(b->adl)      : 0);
	cnt   += (b->host     ? strlen(b->host)     : 0);
	/*
	 * add room for:
         *   possible single space between fullname and addr
         *   left and right brackets
         *   @ sign
         *   possible : for route addr
         *   , <space>
	 *
	 * So I really think that adding 7 is enough.  Instead, I'll add 10.
	 */
	cnt   += 10;
    }
    return(max(cnt, 200));  /* just making sure */
}


/*
 * Interact with user to figure out which address book they want to add a
 * new entry (TakeAddr) to.
 *
 * Args: command_line -- just the line to prompt on
 *
 * Results: returns a pab pointing to the selected addrbook, or NULL.
 */
PerAddrBook *
use_this_addrbook(command_line)
int command_line;
{
    HelpType   help;
    int        rc;
    ESCKEY_S   ekey[3];
    PerAddrBook  *pab, *the_only_pab;
#define MAX_ABOOK 1000
    int        i, abook_num, count_read_write;
    char       addrbook[MAX_ABOOK + 1],
               prompt[MAX_ABOOK + 81];

    dprint(8, (debugfile, "\n - use_this_addrbook -\n"));

    /* check for only one ReadWrite addrbook */
    count_read_write = 0;
    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	/*
	 * NoExists is counted, too, so the user can add to an empty
	 * addrbook the first time.
	 */
	if(pab->access == ReadWrite || pab->access == NoExists){
	    count_read_write++;
	    the_only_pab = &as.adrbks[i];
	}
    }

    /* only one usable addrbook, use it */
    if(count_read_write == 1)
      return(the_only_pab);

    /* no addrbook to write to */
    if(count_read_write == 0){
	q_status_message1(0, 1, 2, "\007No %sAddressbook to Take to!",
	    (as.n_addrbk > 0) ? "writable " : "");
	return NULL;
    }

    /* start with the first addrbook */
    abook_num = 0;
    pab       = &as.adrbks[abook_num];

    /* set up extra command option keys */
    rc = 0;

    ekey[rc].ch      = ctrl('P');
    ekey[rc].rval    = 10;
    ekey[rc].name    = "^P";
    ekey[rc++].label = "Prev AddrBook";

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

    ekey[rc].ch = -1;

    strncpy(addrbook, pab->nickname, MAX_ABOOK);
    addrbook[MAX_ABOOK] = '\0';
    sprintf(prompt, "Take to which addrbook : %s",
	(pab->access == ReadOnly || pab->access == NoAccess) ?
	    "[ReadOnly] " : "");
    help = NO_HELP;
    ps_global->mangled_footer = 1;
    do{
	if(!pab){
            q_status_message1(0, 1, 1,
		    "No addressbook \"%s\"", addrbook);
            display_message(NO_OP_COMMAND);
            sleep(2);
	}

	if(rc == 3)
          help = (help == NO_HELP ? h_oe_chooseabook : NO_HELP);

	rc = optionally_enter(addrbook, command_line, 0, MAX_ABOOK, 1,
                                  0, prompt, ekey, help, 0);

	if(rc == 1) /* ^C */
	  break;

	if(rc == 10){			/* Previous addrbook */
	    if(--abook_num < 0)
	      abook_num = as.n_addrbk - 1;

	    pab = &as.adrbks[abook_num];
	    strncpy(addrbook, pab->nickname, MAX_ABOOK);
	    sprintf(prompt, "Take to which addrbook : %s",
		(pab->access == ReadOnly || pab->access == NoAccess) ?
		    "[ReadOnly] " : "");
	}
	else if(rc == 11){			/* Next addrbook */
	    if(++abook_num > as.n_addrbk - 1)
	      abook_num = 0;

	    pab = &as.adrbks[abook_num];
	    strncpy(addrbook, pab->nickname, MAX_ABOOK);
	    sprintf(prompt, "Take to which addrbook : %s",
		(pab->access == ReadOnly || pab->access == NoAccess) ?
		    "[ReadOnly] " : "");
	}

    }while(rc == 2 || rc == 3 || rc == 4 || rc == 10 || rc == 11 || rc == 12 ||
           !(pab = check_for_addrbook(addrbook)));
            
    if(rc != 0)
      return NULL;

    return(pab);
}


/*
 * Return a pab pointer to the addrbook which corresponds to the argument.
 * 
 * Args: addrbook -- the string representing the addrbook.
 *
 * Results: returns a PerAddrBook pointer for the referenced addrbook, NULL
 *          if none.  First the nicknames are checked and then the filenames.
 *          This must be one of the existing addrbooks.
 */
PerAddrBook *
check_for_addrbook(addrbook)
char *addrbook;
{
    register int i;
    register PerAddrBook *pab;

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(strucmp(pab->nickname, addrbook) == 0)
	  break;
    }

    if(i < as.n_addrbk)
      return(pab);

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(strucmp(pab->filename, addrbook) == 0)
	  break;
    }

    if(i < as.n_addrbk)
      return(pab);
    
    return NULL;
}


static struct key takeaddr_keys_listmode[] = 
       {{"?","Help",0},	       {NULL,NULL,0},        {"E","ExitTake",0},
	{"T","[Take]",0},      {"P","Prev",0},       {"N","Next", 0},
        {"-","PrevPage",0},    {"Spc","NextPage",0}, {"X","Set/Unset",0},
	{"A","SetAll",0},      {"U","UnSetAll",0},   {"S","SingleMode",0}};
static struct key_menu takeaddr_keymenu_listmode = {sizeof(takeaddr_keys_listmode)/(sizeof(takeaddr_keys_listmode[0])*12), 0, 0, 0, 0, 0, takeaddr_keys_listmode};

static struct key takeaddr_keys_singlemode[] = 
       {{"?","Help",0},	       {NULL,NULL,0},        {"E","ExitTake",0},
	{"T","[Take]",0},      {"P","Prev",0},       {"N","Next", 0},
        {"-","PrevPage",0},    {"Spc","NextPage",0}, {NULL,NULL,0},
	{NULL,NULL,0},         {NULL,NULL,0},        {"L","ListMode",0}};
static struct key_menu takeaddr_keymenu_singlemode = {sizeof(takeaddr_keys_singlemode)/(sizeof(takeaddr_keys_singlemode[0])*12), 0, 0, 0, 0, 0, takeaddr_keys_singlemode};


/*
 * Screen for selecting which addresses to Take to address book.
 * Called from outside of address book.
 *
 * Args:      ps -- Pine state
 *       adrlist -- Screen is formed from this adrlist.
 *
 * Result: an address book may be updated
 */
void
takeaddr_screen(ps, ta_list, how_many_selected, mode)
struct pine *ps;
TA_S *ta_list;
int how_many_selected;
TakeAddrScreenMode mode;
{
    int		  orig_ch, dline, give_warn_message;
    int           ch = 'x',
                  done = 0,
		  directly_to_take = 0;
    TA_S	 *current = NULL, *ctmp = NULL;
    TA_SCREEN_S   screen;
    int           command_line;

    command_line = -1 * FOOTER_LINES;  /* third line from the bottom */

    screen.current = screen.top_line = NULL;
    screen.mode    = mode;

    if(ta_list == NULL){
	q_status_message(0, 0, 1, "No addresses to take, cancelled");
	return;
    }

    if(screen.mode == ListMode)
	q_status_message(0, 0, 1,
	    "List mode: Use \"X\" to mark addresses to be included in list");
    else
	q_status_message(0, 0, 1,
	    "Single mode: Use \"P\" or \"N\" to select desired address");

    current	       = first_taline(ta_list);
    ps->mangled_screen = 1;
    ta_screen	       = &screen;

    if(screen.mode == SingleMode && is_talist_of_one(current))
	directly_to_take++;

    while(!done){

	if(screen.mode == ListMode)
	  ps->redrawer = takeaddr_screen_redrawer_list;
	else
	  ps->redrawer = takeaddr_screen_redrawer_single;

	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(NULL, 0,
	    ch==NO_OP_IDLE ? 0 : ch==NO_OP_COMMAND ? 1 : 2) >= 0)
	  ps->mangled_header = 1;

	if(ps->mangled_header){
	    if(screen.mode == ListMode)
                set_titlebar("TAKE ADDRESS SCREEN (List Mode)",
                             ps->mail_stream, ps->context_current,
                             ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0);
            else
                set_titlebar("TAKE ADDRESS SCREEN (Single Mode)",
                             ps->mail_stream, ps->context_current,
                             ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0);

	    ps->mangled_header = 0;
	}

	dline = update_takeaddr_screen(ps, current, &screen);

	/*---- This displays new mail notification, or errors ---*/
        display_message(ch);

	/*---- Redraw footer ----*/
	if(ps->mangled_footer){
	    bitmap_t bitmap;

	    setbitmap(bitmap);
	    ps->mangled_footer = 0;
	    if(screen.mode == ListMode)
		draw_keymenu(&takeaddr_keymenu_listmode, bitmap,
		    ps->ttyo->screen_cols, -2, 0, FirstMenu, 0);
	    else
		draw_keymenu(&takeaddr_keymenu_singlemode, bitmap,
		    ps->ttyo->screen_cols, -2, 0, FirstMenu, 0);
	}

        /*------ Read the command from the keyboard ----*/
	MoveCursor(max(0, ps->ttyo->screen_rows - 3), 0);

	if(directly_to_take)
	  ch = orig_ch = 't';
	else
	  ch = orig_ch = read_command();

        if(ch <= 0xff && isupper(ch))
          ch = tolower(ch);

	switch(ch){
	  case '?':				/* help! */
	  case PF1:
	  case ctrl('G'):
	    helper(h_takeaddr_screen, "HELP FOR TAKE ADDRESS SCREEN", 1);
	    ps->mangled_screen = 1;
	    break;

	  case 'e':				/* exit takeaddr screen */
	  case PF3:
	  case ctrl('C'):
	    cancel_warning(NO_DING, "addition");
	    done++;
	    break;

	  case 't':  /* take */
	  case PF4:
	  case ctrl('M'):
	  case ctrl('J'):
	    if(screen.mode == ListMode)
	      done = ta_take_marked_addrs(how_many_selected,
			   first_taline(current), command_line);
	    else
	      done = ta_take_single_addr(current, command_line);
	    break;

	  case 'n':				/* next list element */
	  case PF6:
	  case '\t':
	  case ctrl('F'):
	  case KEY_RIGHT:
	  case ctrl('N'):
	  case KEY_DOWN:
	    if(ctmp = next_taline(current))
	      current = ctmp;
	    else
	      q_status_message(0, 0, 1, "Already on last line.");
	    break;

	  case 'p':				/* previous list element */
	  case PF5:
	  case ctrl('B'):
	  case KEY_LEFT:
	  case ctrl('P'):			/* up arrow */
	  case KEY_UP:
	    if(ctmp = prev_taline(current))
	      current = ctmp;
	    else
	      q_status_message(0, 0, 1, "Already on first line.");
	    break;

	  case '+':				/* page forward */
	  case ' ':
	  case PF8:
	  case ctrl('V'):
	    give_warn_message = 1;
	    while(dline++ < ps->ttyo->screen_rows - FOOTER_LINES){
	        if(ctmp = next_taline(current)){
		    current = ctmp;
		    give_warn_message = 0;
		}
	        else
		    break;
	    }
	    if(give_warn_message)
	      q_status_message(0, 0, 1, "Already on last page.");
	    break;

	  case '-':				/* page backward */
	  case PF7:
	  case ctrl('Y'):
	    /* move to top of screen */
	    give_warn_message = 1;
	    while(dline-- > HEADER_LINES){
	        if(ctmp = prev_taline(current)){
		    current = ctmp;
		    give_warn_message = 0;
		}
	        else
		    break;
	    }
	    /* page back one screenful */
	    while(++dline < ps->ttyo->screen_rows - FOOTER_LINES){
	        if(ctmp = prev_taline(current)){
		    current = ctmp;
		    give_warn_message = 0;
		}
	        else
		    break;
	    }
	    if(give_warn_message)
	      q_status_message(0, 0, 1, "Already on first page.");
	    break;

	  case ctrl('L'):
          case KEY_RESIZE:
	    ClearScreen();
	    ps->mangled_screen = 1;
	    break;

	  case 'x':  /* select or unselect this addr */
	  case PF9:
	    if(screen.mode != ListMode)
	      goto bleep;
	    current->checked = 1 - current->checked;  /* flip it */
	    how_many_selected += (current->checked ? 1 : -1);
	    break;

	  case 'a':  /* select all */
	  case PF10:
	    if(screen.mode != ListMode)
	      goto bleep;
	    how_many_selected = ta_mark_all(first_taline(current));
	    ps->mangled_body = 1;
	    break;

	  case 'u':  /* unselect all */
	  case PF11:
	    if(screen.mode != ListMode)
	      goto bleep;
	    how_many_selected = ta_unmark_all(first_taline(current));
	    ps->mangled_body = 1;
	    break;

	  case 's':  /* switch to SingleMode */
	  case 'l':  /* switch to ListMode */
	  case PF12:
	    if(screen.mode == ListMode && ch == 'l'){
		q_status_message(0, 0, 1,
		   "Already in ListMode.  Press \"S\" for Single entry mode.");
		break;
	    }
	    if(screen.mode == SingleMode && ch == 's'){
		q_status_message(0, 0, 1,
		   "Already in SingleMode.  Press \"L\" for List entry mode.");
		break;
	    }
	    if(screen.mode == ListMode){
		screen.mode = SingleMode;
		q_status_message(0, 0, 1,
		  "Single mode: Use \"P\" or \"N\" to select desired address");
	    }
	    else{
		screen.mode = ListMode;
		q_status_message(0, 0, 1,
	    "List mode: Use \"X\" to mark addresses to be included in list");
		if(how_many_selected <= 1){
		    how_many_selected = ta_unmark_all(first_taline(current));
		    current->checked = 1;
		    how_many_selected++;
		}
	    }
	    ps->mangled_screen = 1;
	    break;

	  default:
	  bleep:
	    bogus_command(orig_ch, F_ON(F_USE_FK, ps) ? "F1" : "?");

	  case NO_OP_IDLE:			/* simple timeout */
	  case NO_OP_COMMAND:
	    break;
	}
    }

    /* clean up */
    if(current)
      while(current->prev)
	current = current->prev;
    while(current){
	ctmp = current->next;
	free_taline(&current);
	current = ctmp;
    }

    ps->mangled_screen = 1;
    ClearScreen();
}


/*
 * skips over the elements with skip_it set
 */
TA_S *
prev_taline(current)
TA_S *current;
{
    TA_S *p;

    if(!current)
      return NULL;

    p = current->prev;
    while(p && p->skip_it)
      p = p->prev;
    
    return(p);
}


/*
 * skips over the elements with skip_it set
 */
TA_S *
next_taline(current)
TA_S *current;
{
    TA_S *p;

    if(!current)
      return NULL;

    p = current->next;
    while(p && p->skip_it)
      p = p->next;
    
    return(p);
}


/*
 * Call the addrbook functions which add the checked addresses.
 *
 * Args: how_many_selected -- how many addresses are checked
 *                  f_line -- the first ta line
 *
 * Returns: 1 -- we're done, caller should return
 *          0 -- we're not done
 */
int
ta_take_marked_addrs(how_many_selected, f_line, command_line)
int   how_many_selected;
TA_S *f_line;
int   command_line;
{
    char **new_list;

    if(how_many_selected == 0){
	q_status_message(0, 0, 3,
  "No addresses marked for taking. Use ExitTake to leave TakeAddr screen");
	return 0;
    }

    new_list = list_of_checked(f_line);
    create_abook_list_frontend(new_list,
	how_many_selected, command_line);
    free_list_of_checked(&new_list);

    return 1;
}


int
ta_take_single_addr(current, command_line)
TA_S *current;
int   command_line;
{
    add_abook_entry(current->addr, command_line);

    return 1;
}


/*
 * Mark all of the addresses with a check.
 *
 * Args: f_line -- the first ta line
 *
 * Returns the number of lines checked.
 */
int
ta_mark_all(f_line)
TA_S *f_line;
{
    TA_S *ctmp;
    int   how_many_selected = 0;

    for(ctmp = f_line; ctmp; ctmp = next_taline(ctmp)){
	ctmp->checked = 1;
	how_many_selected++;
    }
    return(how_many_selected);
}


/*
 * Does the takeaddr list consist of a single address?
 *
 * Args: f_line -- the first ta line
 *
 * Returns 1 if only one address and 0 otherwise.
 */
int
is_talist_of_one(f_line)
TA_S *f_line;
{
    if(f_line == NULL)
      return 0;

    /* there is at least one, see if there are two */
    if(next_taline(f_line) != NULL)
      return 0;
    
    return 1;
}


/*
 * Turn off all of the check marks.
 *
 * Args: f_line -- the first ta line
 *
 * Returns the number of lines checked (0).
 */
int
ta_unmark_all(f_line)
TA_S *f_line;
{
    TA_S *ctmp;

    for(ctmp = f_line; ctmp; ctmp = ctmp->next)
      ctmp->checked = 0;

    return 0;
}


/*
 * Manage display of the Take Address screen.
 *
 * Args:     ps -- pine state
 *      current -- the current TA line
 *       screen -- the TA screen
 */
int
update_takeaddr_screen(ps, current, screen)
struct pine  *ps;
TA_S	     *current;
TA_SCREEN_S  *screen;
{
    int	   dline,
           return_line = HEADER_LINES;
    TA_S  *top_line,
          *ctmp;
    int    longest;
    char buf1[MAX_SCREEN_COLS + 1];
    char buf2[MAX_SCREEN_COLS + 1];

    /* calculate top line of display */
    dline = 0;
    ctmp = top_line = first_taline(current);
    do
      if(((dline++) % (ps->ttyo->screen_rows
			  - HEADER_LINES
			  - FOOTER_LINES)   ) == 0)
	  top_line = ctmp;
    while(ctmp != current && (ctmp = next_taline(ctmp)))
      ;/* do nothing */

    /* mangled body or new page, force redraw */
    if(ps->mangled_body || screen->top_line != top_line)
        screen->current = NULL;
    
    /* find length of longest line for nicer formatting */
    longest = 0;
    for(dline = 0, ctmp = top_line;
	dline < ps->ttyo->screen_rows - FOOTER_LINES - HEADER_LINES;
	dline++, ctmp = next_taline(ctmp)){
	int len;

	if(ctmp && ctmp->strvalue){
	    if(longest < (len=strlen(ctmp->strvalue)))
	      longest = len;
	}
    }
#define LENGTH_OF_THAT_STRING 5   /* "[X]  " */
    longest = min(longest, ps->ttyo->screen_cols);

    /* loop thru painting what's needed */
    for(dline = 0, ctmp = top_line;
	dline < ps->ttyo->screen_rows - FOOTER_LINES - HEADER_LINES;
	dline++, ctmp = next_taline(ctmp)){

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

	MoveCursor(dline + HEADER_LINES, 0);
	CleartoEOLN();

	if(ctmp && ctmp->strvalue){
	    char *p;
	    int   i, j;

	    p = buf1;
	    if(ctmp == current){
		return_line = dline + HEADER_LINES;
		StartInverse();
	    }

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

	    *p = '\0';

	    /* mark lines which have check marks */
	    if(ctmp == current){
		if(screen->mode == ListMode)
		    sprintf(buf2, "[%c]  %-*.*s",
			ctmp->checked ? 'X' : SPACE,
			longest, longest, buf1);
		else
		    sprintf(buf2, "     %-*.*s",
			longest, longest, buf1);
	    }
	    else{
		if(screen->mode == ListMode)
		    sprintf(buf2, "[%c]  %s",
			ctmp->checked ? 'X' : SPACE, buf1);
		else
		    sprintf(buf2, "     %s", buf1);
	    }

	    PutLine0(dline + HEADER_LINES, 1, buf2);

	    if(ctmp == current)
	      EndInverse();
	}
    }

    ps->mangled_body = 0;
    screen->top_line = top_line;
    screen->current  = current;
    return(return_line);
}


void
takeaddr_screen_redrawer_list()
{
    bitmap_t	 bitmap;

    ClearScreen();

    set_titlebar("TAKE ADDRESS SCREEN (List Mode)",
                 ps_global->mail_stream, ps_global->context_current,
                 ps_global->cur_folder, ps_global->msgmap,
                 1, FolderName, 0, 0);

    ps_global->mangled_body = 1;
    (void)update_takeaddr_screen(ps_global, ta_screen->current, ta_screen);

    setbitmap(bitmap);
    draw_keymenu(&takeaddr_keymenu_listmode, bitmap,
		 ps_global->ttyo->screen_cols, -2, 0, FirstMenu, 0);
}


void
takeaddr_screen_redrawer_single()
{
    bitmap_t	 bitmap;

    ClearScreen();


    ps_global->mangled_body = 1;
    (void)update_takeaddr_screen(ps_global, ta_screen->current, ta_screen);

    set_titlebar("TAKE ADDRESS SCREEN (Single Mode)",
                 ps_global->mail_stream, ps_global->context_current,
                 ps_global->cur_folder, ps_global->msgmap,
                 1, FolderName, 0, 0);

    setbitmap(bitmap);
    draw_keymenu(&takeaddr_keymenu_singlemode, bitmap,
		 ps_global->ttyo->screen_cols, -2, 0, FirstMenu, 0);
}


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

    p = (TA_S *)fs_get(sizeof(TA_S));
    memset((void *)p, 0, sizeof(TA_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
free_taline(p)
    TA_S **p;
{
    if(p){
	if((*p)->addr)
	  mail_free_address(&(*p)->addr);

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

	if((*p)->prev)
	  (*p)->prev->next = (*p)->next;

	if((*p)->next)
	  (*p)->next->prev = (*p)->prev;

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


/*
 * Return the first TakeAddr line.
 *
 * Args: q -- any line in the list
 */
TA_S *
first_taline(q)
TA_S *q;
{
    TA_S *first;

    if(q == NULL)
      return NULL;

    first = NULL;
    /* back up to the head of the list */
    while(q){
	first = q;
	q = prev_taline(q);
    }

    /*
     * If first->skip_it, that means we were already past the first
     * legitimate line, so we have to look in the other direction.
     */
    if(first->skip_it)
      first = next_taline(first);
    
    return(first);
}


/*
 * Find the first TakeAddr line which is checked, beginning with the
 * passed in line.
 *
 * Args: head -- A passed in TakeAddr line, usually will be the first
 *
 * Result: returns a pointer to the first checked line.
 *         NULL if no such line
 */
TA_S *
first_checked(head)
TA_S *head;
{
    TA_S *p;

    p = head;

    for(p = head; p; p = next_taline(p))
      if(p->checked && !p->skip_it)
	break;

    return(p);
}


/*
 * Form a list of strings which are addresses to go in a list.
 * These are entries in a list, so can be full rfc822 addresses.
 * The strings are allocated here.
 *
 * Args: head -- A passed in TakeAddr line, usually will be the first
 *
 * Result: returns an allocated array of pointers to allocated strings
 */
char **
list_of_checked(head)
TA_S *head;
{
    TA_S  *p;
    int    count;
    char **list, **cur;

    /* first count them */
    for(p = head, count = 0; p; p = next_taline(p))
        if(p->checked && !p->skip_it)
	    count++;
    
    /* allocate pointers */
    list = (char **)fs_get((count + 1) * sizeof(char *));
    memset((void *)list, 0, (count + 1) * sizeof(char *));

    cur = list;

    /* allocate and point to address strings */
    for(p = head; p; p = next_taline(p))
        if(p->checked && !p->skip_it){
	    *cur = cpystr(p->strvalue);
	    cur++;
	}

    return(list);
}


/*
 * Free the list allocated above.
 *
 * Args: list -- The address of the list that was returned above.
 */
void
free_list_of_checked(list)
char ***list;
{
    char **p;

    for(p = *list; *p; p++)
      fs_give((void **)p);

    fs_give((void **)list);
}


/*
 * Execute command to take addresses out of message and put in the address book
 *
 * Args: state  -- pine state
 *       msgmap -- the MessageMap
 *       agg    -- this is aggregate operation if set
 *
 * Result: The entry is added to an address book.
 */     
void
cmd_take_addr(state, msgmap, agg)
struct pine *state;
MSGNO_S     *msgmap;
int          agg;
{
    long      i;
    ADDRESS  *addr,
	     *a;
    ENVELOPE *env;
    int       how_many_selected;
    TA_S     *current;

    dprint(2, (debugfile, "\n - taking address into address book - \n"));

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

    if(mn_get_total(msgmap) < 1L){
	q_status_message(0, 0, 3, "\007No messages to take the address from");
	return;
    }

    state->mangled_footer = 1;

    how_many_selected = 0;
    current = NULL;

    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
	env = mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
				  NULL);
	if(!env){
	    q_status_message(1, 1, 4,
	  "\007Can't take address into address book. Error accessing folder");
	    break;
	}

	for(a = env->from; a; a = a->next){
	    addr = copyaddr(a);
	    current = fill_in_ta(&current, addr, agg);
	    if(agg)
	      how_many_selected++;
	}
	for(a = env->reply_to; a; a = a->next){
	    addr = copyaddr(a);
	    current = fill_in_ta(&current, addr, 0);
	}
	for(a = env->to; a; a = a->next){
	    addr = copyaddr(a);
	    current = fill_in_ta(&current, addr, 0);
	}
	for(a = env->cc; a; a = a->next){
	    addr = copyaddr(a);
	    current = fill_in_ta(&current, addr, 0);
	}
    }

    current = first_taline(current);

    how_many_selected -= eliminate_dups_and_us(current);

    takeaddr_screen(state, current, how_many_selected,
	agg ? ListMode : SingleMode);

    if(agg)
      restore_selected(msgmap);
}


ADDRESS *
copyaddr(a)
ADDRESS *a;
{
    ADDRESS *new;

    new = mail_newaddr();
    if(a->personal)
      new->personal = cpystr(a->personal);
    if(a->adl)
      new->adl      = cpystr(a->adl);
    if(a->mailbox)
      new->mailbox  = cpystr(a->mailbox);
    if(a->host)
      new->host     = cpystr(a->host);
    new->next = NULL;

    return(new);
}


TA_S *
fill_in_ta(old_current, addr, checked)
TA_S	**old_current;
ADDRESS  *addr;
int       checked;
{
    TA_S	*new_current;

    /* c-client convention for group syntax, which we don't want to deal with */
    if(addr->mailbox && !addr->host)
      return(*old_current);

    new_current           = new_taline(old_current);
    new_current->skip_it  = 0;
    new_current->checked  = checked;
    new_current->addr     = copyaddr(addr);
    new_current->strvalue = cpystr(addr_string(addr));

    return(new_current);
}


/*
 * Look for dups in list and mark them so we'll skip them.
 *
 * We also throw out any addresses that are us, since we won't usually
 * want to take ourselves to the addrbook.  On the otherhand, if there
 * is nothing but us, we leave them.
 *
 * Returns the number of dups eliminated that were also selected.
 */
int
eliminate_dups_and_us(list)
TA_S	*list;
{
    ADDRESS *a, *b;
    TA_S   *ta, *tb;
    int eliminated = 0;

    /* toss dupes */
    for(ta = list; ta; ta = ta->next){

	if(ta->skip_it) /* already tossed */
	  continue;

	a = ta->addr;

	/* Check addr "a" versus tail of the list */
	for(tb = ta->next; tb; tb = tb->next){
	    b = tb->addr;
	    if(dup_addrs(a, b)){
		if(ta->checked || !(tb->checked)){
		    /* throw out b */
		    if(tb->checked)
			eliminated++;
		    tb->skip_it = 1;
		}
		else{ /* tb->checked */
		    /* throw out a */
		    ta->skip_it = 1;
		    break;
		}
	    }
	}
    }

    /* check whether all remaining addrs are us */
    for(ta = list; ta; ta = ta->next){

	if(ta->skip_it) /* already tossed */
	  continue;

	a = ta->addr;

	if(!address_is_us(a, ps_global))
	  break;
    }

    /* if at least one address that isn't us, remove us from the list */
    if(ta){
	for(ta = list; ta; ta = ta->next){

	    if(ta->skip_it) /* already tossed */
		continue;

	    a = ta->addr;

	    if(address_is_us(a, ps_global)){
		if(ta->checked)
		  eliminated++;
		/* throw out a */
		ta->skip_it = 1;
	    }
	}
    }
    return(eliminated);
}


/*
 * Returns 1 if x and y match, 0 if not.
 */
int
dup_addrs(x, y)
ADDRESS *x, *y;
{
    return(x && y && strucmp(x->mailbox, y->mailbox) == 0
           &&  strucmp(x->host, y->host) == 0);
}
