#if	!defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: pico.c,v 4.121 1999/06/10 22:56:11 mikes Exp $";
#endif
/*
 * Program:	Main Pine Composer routines
 *
 *
 * Michael Seibel
 * Networks and Distributed Computing
 * Computing and Communications
 * University of Washington
 * Administration Builiding, AG-44
 * Seattle, Washington, 98195, USA
 * Internet: mikes@cac.washington.edu
 *
 * Please address all bugs and comments to "pine-bugs@cac.washington.edu"
 *
 *
 * Pine and Pico are registered 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, Pico, and Pilot software and its included text are Copyright
 * 1989-1998 by the University of Washington.
 * 
 * The full text of our legal notices is contained in the file called
 * CPYRIGHT, included with this distribution.
 *
 *
 * WEEMACS/PICO NOTES:
 *
 * 01 Nov 89 - MicroEmacs 3.6 vastly pared down and becomes a function call 
 * 	       weemacs() and plugged into the Pine mailer.  Lots of unused
 *	       MicroEmacs code laying around.
 *
 * 17 Jan 90 - weemacs() became weemacs() the composer.  Header editing
 *	       functions added.
 *
 * 09 Sep 91 - weemacs() renamed pico() for the PIne COmposer.
 *
 */


/*
 * This program is in public domain; written by Dave G. Conroy.
 * This file contains the main driving routine, and some keyboard processing
 * code, for the MicroEMACS screen editor.
 *
 * REVISION HISTORY:
 *
 * 1.0  Steve Wilhite, 30-Nov-85
 *      - Removed the old LK201 and VT100 logic. Added code to support the
 *        DEC Rainbow keyboard (which is a LK201 layout) using the the Level
 *        1 Console In ROM INT. See "rainbow.h" for the function key defs
 *      Steve Wilhite, 1-Dec-85
 *      - massive cleanup on code in display.c and search.c
 *
 * 2.0  George Jones, 12-Dec-85
 *      - Ported to Amiga.
 *
 * 3.0  Daniel Lawrence, 29-Dec-85
 *	16-apr-86
 *	- updated documentation and froze development for 3.6 net release
 */

/* make global definitions not external */
#define	maindef

#include	"headers.h"
#include	"ebind.h"	/* default key bindings */


int	func_init PROTO((void));
void	breplace PROTO((void *));
int	any_header_changes PROTO((void));
#ifdef	_WINDOWS
int	composer_file_drop PROTO((int, int, char *));
#endif


/*
 * function key mappings
 */
static int pfkm[12][2] = {
    { F1,  (CTRL|'G')},
    { F2,  (CTRL|'C')},
    { F3,  (CTRL|'X')},
    { F4,  (CTRL|'J')},
    { F5,  (CTRL|'R')},
    { F6,  (CTRL|'W')},
    { F7,  (CTRL|'Y')},
    { F8,  (CTRL|'V')},
    { F9,  (CTRL|'K')},
    { F10, (CTRL|'U')},
    { F11, (CTRL|'O')},
#ifdef	SPELLER
    { F12, (CTRL|'T')}
#else
    { F12, (CTRL|'D')}
#endif
};


/*
 * flag for the various functions in pico() to set when ready
 * for pico() to return...
 */
int      pico_all_done = 0;
jmp_buf  finstate;
char    *pico_anchor = NULL;
extern struct headerentry *headents;

/*
 * pico - the main routine for Pine's composer.
 *
 */
pico(pm)
PICO *pm;
{
    register int    c;
    register int    f;
    register int    n;
    char     bname[NBUFN];		/* buffer name of file to read */
    extern   struct on_display ods;
    int      checkpointcnt = 0, input = 0, cursor_shown;
    char     chkptfile[NLINE];

    Pmaster       = pm;
    gmode	  = MDWRAP;
    gmode        |= pm->pine_flags;	/* high 4 bits rsv'd for pine */

    alt_speller   = pm->alt_spell;
    pico_all_done = 0;
    km_popped     = 0;

    if(!vtinit())			/* Init Displays.      */
      return(COMP_CANCEL);

    strcpy(bname, "main");		/* default buffer name */
    edinit(bname);			/* Buffers, windows.   */

    if(InitMailHeader(pm))		/* init mail header structure */
      gmode &= ~(P_BODY | P_HEADEND);	/* flip off special header stuff */

    /* setup to process commands */
    lastflag = 0;			/* Fake last flags.     */
    curbp->b_mode |= gmode;		/* and set default modes*/

    pico_anchor = (char *)malloc((strlen(Pmaster->pine_anchor) + 1)
				 * sizeof(char));
    if(pico_anchor)
      strcpy(pico_anchor, Pmaster->pine_anchor);

    bindtokey(DEL, (gmode & P_DELRUBS) ? forwdel : backdel);

    if(pm->msgtext)
      breplace(pm->msgtext);

#ifdef	_WINDOWS
    cursor_shown = mswin_showcaret(1);	/* turn on for main window */
    mswin_allowpaste(MSWIN_PASTE_FULL);
    mswin_setscrollcallback (pico_scroll_callback);
#endif

    /* prepare for checkpointing */
    chkptfile[0] = '\0';
    chkptinit((*Pmaster->ckptdir)(chkptfile, NLINE), NLINE);
    if(gmode & P_CHKPTNOW)
      writeout(chkptfile, TRUE);

    pico_all_done = setjmp(finstate);	/* jump out of HUP handler ? */

    if(gmode & MDALTNOW){
	while(!pico_all_done){
	    if(((gmode & P_BODY) || !Pmaster->headents)
	       && alt_editor(0, 1) < 0)
	      break;			/* if problem, drop into pico */

	    if(Pmaster->headents){
		update();		/* paint screen, n' start editing... */
		HeaderEditor((gmode & (P_HEADEND | P_BODY)) ? 2 : 0, 0);
		gmode |= P_BODY;	/* make sure we enter alt ed next */
	    }
	    else
	      pico_all_done = COMP_EXIT;
	}
    }
    else if(!pico_all_done){
	if(gmode & P_BODY){		/* begin editing the header? */
	    ArrangeHeader();		/* line up pointers */
	    /*
	     * Move to the offset pine asked us to move to.
	     * Perhaps we should be checking to see if this is
	     * a reasonable number before moving.
	     */
	    if(Pmaster && Pmaster->edit_offset)
	      forwchar(FALSE, Pmaster->edit_offset);
	}
	else{
	    update();			/* paint screen, */
	    HeaderEditor((gmode & P_HEADEND) ? 2 : 0, 0);
	}
    }

    while(1){
	if(pico_all_done){
#ifdef	_WINDOWS
	    if(!cursor_shown)
	      mswin_showcaret(0);

	    mswin_allowpaste(MSWIN_PASTE_DISABLE);
	    mswin_setscrollcallback (NULL);
#endif
	    c = anycb() ? BUF_CHANGED : 0;
	    switch(pico_all_done){	/* prepare for/handle final events */
	      case COMP_EXIT :		/* already confirmed */
		packheader();
		c |= COMP_EXIT;
		break;

	      case COMP_CANCEL :	/* also already confirmed */
		packheader();
		c = COMP_CANCEL;
		break;

	      case COMP_GOTHUP:
		/* 
		 * pack up and let caller know that we've received a SIGHUP
		 */
		if(ComposerEditing)		/* expand addr if needed */
		  call_builder(&headents[ods.cur_e], NULL, NULL);

		packheader();
		c |= COMP_GOTHUP;
		break;

	      case COMP_SUSPEND :
	      default:			/* safest if internal error */
		/*
		 * If we're in the headers mark the current header line
		 * with start_here bit so caller knows where to reset.
		 * Also set the edit_offset, which is either the offset
		 * into this header line or the offset into the body.
		 * Packheader will adjust edit_offset for multi-line
		 * headers.
		 */
		if(ComposerEditing){		/* in the headers */
		    headents[ods.cur_e].start_here = 1;
		    Pmaster->edit_offset = ods.p_off;
		}
		else{
		    register LINE *clp;
		    register long  offset;

		    for(clp = lforw(curbp->b_linep), offset = 0L;
			clp != curwp->w_dotp;
			clp = lforw(clp))
		      offset += (llength(clp) + 1);

		    Pmaster->edit_offset = offset + curwp->w_doto;
		}

		packheader();
		c |= COMP_SUSPEND;
		break;
	    }

	    free(pico_anchor);
	    vttidy();			/* clean up tty modes */
	    zotdisplay();		/* blast display buffers */
	    zotedit();
	    unlink(chkptfile);
	    Pmaster = NULL;		/* blat global */

	    return(c);
	}

	if(km_popped){
	    km_popped--;
	    if(km_popped == 0) /* cause bottom three lines to be repainted */
	      curwp->w_flag |= WFHARD;
	}

	if(km_popped){  /* temporarily change to cause menu to be painted */
	    term.t_mrow = 2;
	    curwp->w_ntrows -= 2;
	    curwp->w_flag |= WFMODE;
	    movecursor(term.t_nrow-2, 0); /* clear status line, too */
	    peeol();
	}

	update();			/* Fix up the screen    */
	if(km_popped){
	    term.t_mrow = 0;
	    curwp->w_ntrows += 2;
	}

#ifdef	MOUSE
#ifdef  EX_MOUSE
	/* New mouse function for real mouse text seletion. */
	register_mfunc(mouse_in_pico, 2, 0, term.t_nrow - (term.t_mrow+1),
		       term.t_ncol);
#else
	mouse_in_content(KEY_MOUSE, -1, -1, -1, 0);
	register_mfunc(mouse_in_content, 2, 0, term.t_nrow - (term.t_mrow + 1),
		       term.t_ncol);
#endif
#endif
#ifdef	_WINDOWS
	mswin_setdndcallback (composer_file_drop);
	mswin_mousetrackcallback(pico_cursor);
#endif
	c = GetKey();
        if (term.t_nrow < 6 && c != NODATA){
            (*term.t_beep)();
            emlwrite("Please make the screen bigger.", NULL);
            continue;
        }

#ifdef	MOUSE
#ifdef  EX_MOUSE
	clear_mfunc(mouse_in_pico);
#else
	clear_mfunc(mouse_in_content);
#endif
#endif
#ifdef	_WINDOWS
	mswin_cleardndcallback ();
	mswin_mousetrackcallback(NULL);
#endif
	if(c == NODATA || time_to_check()){	/* new mail ? */
	    if((*Pmaster->newmail)(c == NODATA ? 0 : 2, 1) >= 0){
		int rv;

		if(km_popped){
		    term.t_mrow = 2;
		    curwp->w_ntrows -= 2;
		    curwp->w_flag |= WFHARD;
		    km_popped = 0;
		}

		clearcursor();
		mlerase();
		rv = (*Pmaster->showmsg)(c);
		ttresize();
		picosigs();	/* restore altered handlers */
		if(rv)		/* Did showmsg corrupt the display? */
		  PaintBody(0);	/* Yes, repaint */

		mpresf = 1;
		input = 0;
	    }

	    clearcursor();
	    movecursor(0, 0);
	}

	if(km_popped)
	  switch(c){
	    case NODATA:
	    case (CTRL|'L'):
	      km_popped++;
	      break;
	    
	    default:
	      mlerase();
	      break;
	  }

	if(c == NODATA)		/* no op, getkey timed out */
	  continue;
	else if(!input++)
	  (*Pmaster->keybinput)();

	if (mpresf != FALSE) {		/* message stay around only  */
	    if (mpresf++ > NMMESSDELAY)	/* so long! */
	      mlerase();
	}

	f = FALSE;			/* vestigial */
	n = 1;
					/* Do it.               */
	execute(normalize_cmd(c, pfkm, 2), f, n);
	if(++checkpointcnt >= CHKPTDELAY){
	    checkpointcnt = 0;
	    writeout(chkptfile, TRUE);
	}
    }
}

/*
 * Initialize all of the buffers and windows. The buffer name is passed down
 * as an argument, because the main routine may have been told to read in a
 * file by default, and we want the buffer name to be right.
 */

/*
 * For the pine composer, we don't want to take over the whole screen
 * for editing.  the first some odd lines are to be used for message 
 * header information editing.
 */
edinit(bname)
char    bname[];
{
    register BUFFER *bp;
    register WINDOW *wp;

    if(Pmaster)
      func_init();

    bp = bfind(bname, TRUE, BFWRAPOPEN);    /* First buffer         */
    wp = (WINDOW *) malloc(sizeof(WINDOW)); /* First window         */

    if (bp==NULL || wp==NULL){
	if(Pmaster)
	  return(0);
	else
	  exit(1);
    }

    curbp  = bp;                            /* Make this current    */
    wheadp = wp;
    curwp  = wp;
    wp->w_wndp  = NULL;                     /* Initialize window    */
    wp->w_bufp  = bp;
    bp->b_nwnd  = 1;                        /* Displayed.           */
    wp->w_linep = bp->b_linep;
    wp->w_dotp  = bp->b_linep;
    wp->w_doto  = 0;
    wp->w_markp = NULL;
    wp->w_marko = 0;
    bp->b_linecnt = -1;

    if(Pmaster){
	term.t_mrow = Pmaster->menu_rows;
	wp->w_toprow = ComposerTopLine = COMPOSER_TOP_LINE;
	wp->w_ntrows = term.t_nrow - COMPOSER_TOP_LINE - term.t_mrow;
	fillcol = Pmaster->fillcolumn;
	strcpy(opertree,
	       (Pmaster->oper_dir && strlen(Pmaster->oper_dir) < NLINE)
	         ? Pmaster->oper_dir : "");
    }
    else{
	if(sup_keyhelp)
	  term.t_mrow = 0;
	else
	  term.t_mrow = 2;

        wp->w_toprow = 2;
        wp->w_ntrows = term.t_nrow - 2 - term.t_mrow;
	if(userfillcol > 0)			/* set fill column */
	  fillcol = userfillcol;
	else
	  fillcol = term.t_ncol - 6;
    }

    /*
     * MDSCUR mode implies MDTREE mode with a opertree of home directory,
     * unless opertree has been set differently.
     */
    if((gmode & MDSCUR) && !opertree[0])
      strncpy(opertree, gethomedir(NULL), NLINE);

    if(*opertree)
      fixpath(opertree, NLINE);

    wp->w_force = 0;
    wp->w_flag  = WFMODE|WFHARD;            /* Full.                */
}


/*
 * This is the general command execution routine. It handles the fake binding
 * of all the keys to "self-insert". It also clears out the "thisflag" word,
 * and arranges to move it to the "lastflag", so that the next command can
 * look at it. Return the status of command.
 */
execute(c, f, n)
int c, f, n;
{
    register KEYTAB *ktp;
    register int    status;
    register int    i;

    ktp = (Pmaster) ? &keytab[0] : &pkeytab[0];

    while (ktp->k_fp != NULL) {
	if (ktp->k_code == c) {

	    if(lastflag&CFFILL){
		curwp->w_flag |= WFMODE;
		if(Pmaster == NULL)
		  sgarbk = TRUE;
	    }

	    thisflag = 0;
	    status   = (*ktp->k_fp)(f, n);
	    if((lastflag & CFFILL) && (thisflag ^ CFFILL))
	      fdelete();

	    lastflag = thisflag;

	    /*
	     * Reset flag saying wrap should open a new line whenever
	     * we execute a command (as opposed to just typing in text).
	     * However, if that command leaves us in the same line on the
	     * screen, then don't reset.
	     */
	    if(curwp->w_flag & (WFMOVE | WFHARD))
	      curbp->b_flag |= BFWRAPOPEN;    /* wrap should open new line */

	    return (status);
	}
	++ktp;
    }

    if(lastflag & CFFILL)		/* blat unusable fill data */
      fdelete();

    if (VALID_KEY(c)) {			/* Self inserting.      */

	if (n <= 0) {                   /* Fenceposts.          */
	    lastflag = 0;
	    return (n<0 ? FALSE : TRUE);
	}
	thisflag = 0;                   /* For the future.      */

	/* do the appropriate insertion */
	/* pico never does C mode, this is simple */
	status = linsert(n, c);

	/*
	 * Check to make sure we didn't go off of the screen
	 * with that character.  Take into account tab expansion.
	 * If so wrap the line...
	 */
	if(curwp->w_bufp->b_mode & MDWRAP){
	    register int j;
	    register int k;

	    for(i = j = k = 0; j < llength(curwp->w_dotp); j++, k++)
	      if(isspace((unsigned char)lgetc(curwp->w_dotp, j).c)){
		  if(lgetc(curwp->w_dotp, j).c == TAB)
		    while(k+1 & 0x07)
		      k++;
	      }
	      else if(k >= fillcol){
		  wrapword();
		  break;
	      }
	}

	lastflag = thisflag;
	return (status);
    }
    
    if(c&CTRL)
      emlwrite("\007Unknown Command: ^%c", (void *)(c&0xff));
    else
      emlwrite("\007Unknown Command", NULL);

    lastflag = 0;                           /* Fake last flags.     */
    return (FALSE);
}



/*
 * Fancy quit command, as implemented by Norm. If the any buffer has
 * changed do a write on that buffer and exit emacs, otherwise simply exit.
 */
quickexit(f, n)
int f, n;
{
    register BUFFER *bp;	/* scanning pointer to buffers */

    bp = bheadp;
    while (bp != NULL) {
	if ((bp->b_flag&BFCHG) != 0	/* Changed.             */
	    && (bp->b_flag&BFTEMP) == 0) {	/* Real.                */
	    curbp = bp;		/* make that buffer cur	*/
	    filesave(f, n);
	}
	bp = bp->b_bufp;			/* on to the next buffer */
    }
    wquit(f, n);                             /* conditionally quit   */
}



/* 
 * abort_composer - ask the question here, then go quit or 
 *                  return FALSE
 */
abort_composer(f, n)
int f, n;
{
    char *result;

    result = "";

    Pmaster->arm_winch_cleanup++;
    if(Pmaster->canceltest){
        if((((Pmaster->pine_flags & MDHDRONLY) && !any_header_changes())
	     || (!(Pmaster->pine_flags & MDHDRONLY) && !anycb()))
	  || (result = (*Pmaster->canceltest)(redraw_pico_for_callback))){
	    pico_all_done = COMP_CANCEL;
	    emlwrite(result, NULL);
	    Pmaster->arm_winch_cleanup--;
	    return(TRUE);
	}
	else{
	    emlwrite("Cancel Cancelled", NULL);
	    curwp->w_flag |= WFMODE;		/* and modeline so we  */
	    sgarbk = TRUE;			/* redraw the keymenu  */
	    pclear(term.t_nrow - 1, term.t_nrow + 1);
	    Pmaster->arm_winch_cleanup--;
	    return(FALSE);
	}
    }
    else switch(mlyesno(Pmaster->headents
	 ? "Cancel message (answering \"Yes\" will abandon your mail message)"
	 : (anycb() == FALSE)
	     ? "Cancel Edit (and abandon changes)"
	     : "Cancel Edit",
	 FALSE)){
      case TRUE:
	pico_all_done = COMP_CANCEL;
	return(TRUE);

      case ABORT:
	emlwrite("\007Cancel Cancelled", NULL);
	break;

      default:
	mlerase();
    }
    return(FALSE);
}


/*
 * suspend_composer - return to pine with what's been edited so far
 */
suspend_composer(f, n)
int f, n;
{
    if(Pmaster && Pmaster->headents)
      pico_all_done = COMP_SUSPEND;
    else
      (*term.t_beep)();
}



/*
 * Quit command. If an argument, always quit. Otherwise confirm if a buffer
 * has been changed and not written out. Normally bound to "C-X C-C".
 */
wquit(f, n)
int f, n;
{
    register int    s;

    if(Pmaster){
	char *prompt, *result;

	/* First, make sure there are no outstanding problems */ 
	if(AttachError()){
	    emlwrite("\007Problem with attachments!  Fix errors or delete attachments.", NULL);
	    return(FALSE);
	}

	/*
	 * if we're not in header, show some of it as we verify sending...
	 */
	display_for_send();
	packheader();
	Pmaster->arm_winch_cleanup++;
	if((!(Pmaster->pine_flags & MDHDRONLY) || any_header_changes())
	   && (result = (*Pmaster->exittest)(Pmaster->headents,
					     redraw_pico_for_callback))){
	    Pmaster->arm_winch_cleanup--;
	    if(sgarbf)
	      update();

	    lchange(WFHARD);			/* set update flags... */
	    curwp->w_flag |= WFMODE;		/* and modeline so we  */
	    sgarbk = TRUE;			/* redraw the keymenu  */
	    pclear(term.t_nrow - 2, term.t_nrow + 1);
	    if(*result)
	      emlwrite(result, NULL);
	}
	else{
	    Pmaster->arm_winch_cleanup--;
	    pico_all_done = COMP_EXIT;
	    return(TRUE);
	}
    }
    else{
        if (f != FALSE                          /* Argument forces it.  */
        || anycb() == FALSE                     /* All buffers clean.   */
						/* User says it's OK.   */
        || (s=mlyesno("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES)", -1)) == FALSE) {
                vttidy();
#if     defined(USE_TERMCAP) || defined(USE_TERMINFO) || defined(VMS)
		kbdestroy(kbesc);
#endif
                exit(0);
        }

	if(s == TRUE){
	    if(filewrite(0,1) == TRUE)
	      wquit(1, 0);
	}
	else if(s == ABORT){
	    emlwrite("Exit cancelled", NULL);
	    if(term.t_mrow == 0)
	      curwp->w_flag |= WFHARD;	/* cause bottom 3 lines to paint */
	}
        return(s);
    }

    return(FALSE);
}


/*
 * Has any editing been done to headers?
 */
any_header_changes()
{
    struct headerentry *he;

    for(he = Pmaster->headents; he->name != NULL; he++)
      if(he->dirty)
	break;
    
    return(he->name && he->dirty);
}


/*
 * Abort.
 * Beep the beeper. Kill off any keyboard macro, etc., that is in progress.
 * Sometimes called as a routine, to do general aborting of stuff.
 */
ctrlg(f, n)
int f, n;
{
    emlwrite("Cancelled", NULL);
    return (ABORT);
}


/* tell the user that this command is illegal while we are in
 *  VIEW (read-only) mode
 */
rdonly()
{
    (*term.t_beep)();
    emlwrite("Key illegal in VIEW mode", NULL);
    return(FALSE);
}



/*
 * reset all globals to their initial values
 */
func_init()
{
    extern int    vtrow;
    extern int    vtcol;
    extern int    lbound;

    /*
     * re-initialize global buffer type variables ....
     */
    fillcol = (term.t_ncol > 80) ? 77 : term.t_ncol - 6;
    eolexist = TRUE;
    revexist = FALSE;
    sgarbf = TRUE;
    mpresf = FALSE;
    mline_open = FALSE;
    ComposerEditing = FALSE;

    /*
     * re-initialize hardware display variables ....
     */
    vtrow = vtcol = lbound = 0;
    clearcursor();

    pat[0] = rpat[0] = '\0';
}


/*
 * pico_help - help function for standalone composer
 */
pico_help(text, title, i)
char *text[], *title;
int i;
{
    register    int numline = 0;
    char        **p;
 
    p = text;
    while(*p++ != NULL) 
      numline++;
    return(wscrollw(COMPOSER_TOP_LINE, term.t_nrow-1, text, numline));
}



/*
 * zotedit() - kills the buffer and frees all lines associated with it!!!
 */
zotedit()
{
    wheadp->w_linep = wheadp->w_dotp = wheadp->w_markp = NULL;
    bheadp->b_linep = bheadp->b_dotp = bheadp->b_markp = NULL;

    free((char *) wheadp);			/* clean up window */
    wheadp = NULL;
    curwp  = NULL;

    free((char *) bheadp);			/* clean up buffers */
    bheadp = NULL;
    curbp  = NULL;

    zotheader();				/* blast header lines */

    kdelete();					/* blast kill buffer */

}


#ifdef	MOUSE
/*
 * Generic mouse handling functions
 */
MENUITEM  menuitems[12];		/* key labels and functions */
MENUITEM *mfunc = NULL;			/* list of regional functions  */
mousehandler_t	mtrack;			/* mouse tracking handler */

/* last mouse position */
static  int levent = 0, lrow = 0, lcol = 0, doubleclick, lbutton, lflags;
#ifdef	DOS
static  clock_t lastcalled = 0;
#else
static	time_t	lastcalled = 0;
#endif
static	mousehandler_t lastf;


/*
 * register_mfunc - register the given function to get called
 * 		    on mouse events in the given display region
 */
register_mfunc(f, tlr, tlc, brr, brc)
mousehandler_t	f;
int		tlr, tlc, brr, brc;
{
    MENUITEM **mp;

    if(!mouseexist())
      return(FALSE);

    for(mp = &mfunc; *mp; mp = &(*mp)->next)
      ;

    *mp = (MENUITEM *)malloc(sizeof(MENUITEM));
    memset(*mp, 0, sizeof(MENUITEM));

    (*mp)->action = f;
    (*mp)->tl.r   = tlr;
    (*mp)->br.r   = brr;
    (*mp)->tl.c   = tlc;
    (*mp)->br.c   = brc;
    (*mp)->lbl.c  = (*mp)->lbl.r = 0;
    (*mp)->label  = "";
    return(TRUE);
}


/*
 * clear_mfunc - clear any previously set mouse function
 */
void
clear_mfunc(f)
mousehandler_t f;
{
    MENUITEM *mp, *tp;

    if(mp = mfunc){
	if(mp->action == f)
	  mfunc = mp->next;
	else
	  for(tp = mp; tp->next; tp = tp->next)
	    if(tp->next->action == f){
		mp = tp->next;
		tp->next = tp->next->next;
		break;
	    }

	if(mp){
	    mp->action = NULL;
	    free(mp);
	}
    }
}



#ifdef EX_MOUSE

void
clear_mtrack ()
{
    mtrack = NULL;
    mswin_allowmousetrack (FALSE);
}


void
register_mtrack (f)
mousehandler_t	f;
{
    if (f) {
	mtrack = f;
	mswin_allowmousetrack (TRUE);
    }
    else
	clear_mtrack ();
}


static void
move_dot_to (row, col)
    int row, col;
{
    LINE *lp;
    int	i;
    
    lp = curwp->w_linep;
    i = row - ((Pmaster) ? ComposerTopLine : 2);
    while(i-- && lp != curbp->b_linep)	/* count from top */
      lp = lforw(lp);
    curgoal = col;
    curwp->w_dotp = lp;			/* to new dot. */
    curwp->w_doto = getgoal(lp);
    curwp->w_flag |= WFMOVE;
}


/*
 * mouse_in_pico
 *
 * When the mouse goes down in the body we set the mark and start 
 * tracking.
 *
 * As the mouse moves we update the dot and redraw the screen.
 *
 * If the mouse moves above or below the pico body region of the 
 * screen we scroll the text and update the dot position.
 *
 * When the mouse comes up we clean up.  If the mouse did not
 * move, then we clear the mark and turn off the selection.
 *
 * Most of the mouse processing is handled here.  The exception is
 * mouse down in the header.  Can't call HeaderEditor() from here so
 * we send up the KEY_MOUSE character, which gets dispatched to
 * mousepress(), which _can_ call HeaderEditor().
 */
unsigned long
mouse_in_pico(mevent, row, col, button, flags)
    int	     mevent;
    int      row, col, button, flags;
{
    unsigned long	rv = 0;		/* Our return value. */
    int			trow, tcol;	/* translated row and col. */

    static int lheader = FALSE;		/* Mouse down was in header. */

  
    /*
     * What's up.
     */
    switch (mevent) {
    case M_EVENT_DOWN:
      if(button != M_BUTTON_LEFT)
	break;

	/* Ignore mouse down if not in pico body region. */
	if (row < 2 || row > term.t_nrow - (term.t_mrow+1)) {
	    clear_mtrack ();
	    break;
        }
	
	/* Detect double clicks.  Not that we do anything with em, just
	 * detect them. */
#ifdef	DOS
#ifdef	CLOCKS_PER_SEC
	doubleclick = (lrow == row && lcol == col
		       && clock() < (lastcalled + CLOCKS_PER_SEC/2));
#else
#ifdef	CLK_TCK
doubleclick = (lrow == row && lcol == col
		       && clock() < (lastcalled + CLK_TCK/2));
#else
	doubleclick = FALSE;
#endif
#endif
	lastcalled  = clock();
#else
	doubleclick = (lrow == row && lcol == col
		       && time(0) < (lastcalled + 2));
	lastcalled  = time(0);
#endif
	lheader	    = FALSE;	/* Rember mouse down position. */
	levent	    = mevent;
	lrow	    = row;
	lcol	    = col;
	lbutton     = button;
	lflags      = flags;
	
	/* Mouse down in body? */
	if (row < (Pmaster ? ComposerTopLine : 2)) {
	    /* Mouse down in message header -> no tracking, just remember
	     * where */
	    lheader = TRUE;
        }
	else {
	    /* Mouse down in message.
	     * If no shift key and an existing mark -> clear the mark.
	     * If shift key and no existing mark -> set mark before moving */
	    if (!(flags & M_KEY_SHIFT) && curwp->w_markp) 
		setmark (0,1);		/* this clears the mark. */
	    else if (flags & M_KEY_SHIFT && !curwp->w_markp)
		setmark (0,1);		/* while this sets the mark. */
	    
	    /* Reposition dot to mouse down. */
	    move_dot_to (row, col);
	    
	    /* Set the mark to dot if no existing mark. */
	    if (curwp->w_markp == NULL) 
		setmark (0,1);

	    /* Track mouse movement. */
	    register_mtrack (mouse_in_pico);
	    update ();
	    lheader = FALSE;		/* Just to be sure. */
        }
	break;
	
	
    case M_EVENT_TRACK:
	/* Mouse tracking. */
	if (lheader)			/* Ignore mouse movement in header. */
	    break;
    
	/* If above or below body, scroll body and adjust the row and col. */
	if (row < (Pmaster ? ComposerTopLine : 2)) {
	    /* Scroll text down screen and move dot to top left corner. */
	    scrollupline (0,1);
	    trow = (Pmaster) ? ComposerTopLine : 2;
	    tcol = 0;
        }
	else if (row > term.t_nrow - (term.t_mrow + 1)) {
	    /* Scroll text up screen and move dot to bottom right corner. */
	    scrolldownline (0,1);
	    trow = term.t_nrow - (term.t_mrow + 1);
	    tcol = term.t_ncol;
        }
	else {
	    trow = row;
	    tcol = col;
        }

	/* Move dot to target column. */
	move_dot_to (trow, tcol);
	
	/* Update screen. */
	update ();
	break;

	
    case M_EVENT_UP:
      if(button == M_BUTTON_RIGHT){
#ifdef	_WINDOWS
	  pico_popup();
#endif
	  break;
      }
      else if(button != M_BUTTON_LEFT)
	break;

	if (lheader) {
	    lheader = FALSE;
	    /* Last down in header. */
	    if (row == lrow && col == lcol) {
		/* Mouse up and down in same place in header.  Means the
		 * user want to edit the header.  Return KEY_MOUSE which
		 * will cause mousepress to be called, which will
		 * call HeaderEditor.  Can't call HeaderEditor from here
		 * because that would mess up layering. */
		if (curwp->w_marko)
		    setmark (0,1);
		rv = ((unsigned long)KEY_MOUSE << 16) | TRUE;
	    }
        }
	else {
	    /* If up at same place, clear mark */
	    if (curwp->w_markp == curwp->w_dotp && 
		    curwp->w_marko == curwp->w_doto) {
		setmark (0,1);
		curwp->w_flag |= WFMOVE;
	    }
	    clear_mtrack ();
	    update ();
        }
	break;    
    }
    
    return(rv);
}
#endif



/*
 * mouse_in_content - general mechanism used to pass recognized mouse
 *		      events in predefined region back thru the usual
 *		      keyboard input stream.  The actual return value
 *		      passed back from this function is set dynamically
 *		      via the "down" argument which is read when both the
 *		      "row" and "col" arguments are negative.
 */
unsigned long
mouse_in_content(mevent, row, col, button, flags)
    int      mevent;
    int      row, col, button, flags;
{
    unsigned long   rv = 0;
    static unsigned mouse_val = KEY_MOUSE;

    if(row == -1 && col == -1){
	mouse_val = mevent;			/* setting return value */
    }
    else {
	/* A real event. */
	levent = mevent;
	switch (mevent) {
	case M_EVENT_DOWN:
	    /* Mouse down does not mean anything, just keep track of 
	     * where it went down and if this is a double click. */
#ifdef	DOS
#ifdef	CLOCKS_PER_SEC
	    doubleclick = (lrow == row && lcol == col
			   && clock() < (lastcalled + CLOCKS_PER_SEC/2));
#else
#ifdef	CLK_TCK
	    doubleclick = (lrow == row && lcol == col
			   && clock() < (lastcalled + CLK_TCK/2));
#else
	    doubleclick = FALSE;
#endif
#endif
	    lastcalled	= clock();
#else
	    doubleclick = (lrow == row && lcol == col
			   && time(0) < (lastcalled + 2));
	    lastcalled  = time(0);
#endif
	    lrow	= row;
	    lcol	= col;
	    lbutton	= button;
	    lflags	= flags;
	    break;

	case M_EVENT_UP:
	    /* Mouse up.  If in the same position as it went down
	     * then we return the value set above, which goes into
	     * the character input stream, which gets processed as
	     * a mouse event by some upper layer, which calls to 
	     * mouse_get_last(). */
	    if (lrow == row && lcol == col) {
		rv = mouse_val;
		rv = (rv << 16) | TRUE;
	    }
	    break;
	    
	case M_EVENT_TRACK:
	    break;
	}
    }

    return(rv);
}


/*
 * mouse_get_last - Get last mouse event.
 *
 */
void
mouse_get_last(f, mp)
    mousehandler_t *f;
    MOUSEPRESS *mp;
{
    if (f != NULL)
        *f  = lastf;
    if (mp != NULL) {
	mp->mevent	= levent;
	mp->row		= lrow;
	mp->col		= lcol;
	mp->doubleclick = doubleclick;
	mp->button	= lbutton;
	mp->flags	= lflags;
    }
}



/*
 * register_key - register the given keystroke to accept mouse events
 */
void
register_key(i, rval, label, label_printer, row, col, len, kn, kl)
int       i;
unsigned  rval;
char     *label;
void      (*label_printer)();
int       row, col, len;
COLOR_PAIR *kn, *kl;
{
    if(i > 11)
      return;

    menuitems[i].val   = rval;
    menuitems[i].tl.r  = menuitems[i].br.r = row;
    menuitems[i].tl.c  = col;
    menuitems[i].br.c  = col + len;
    menuitems[i].lbl.r = menuitems[i].tl.r;
    menuitems[i].lbl.c = menuitems[i].tl.c;
    menuitems[i].label_hiliter = label_printer;
    if(menuitems[i].label){
	free(menuitems[i].label);
	menuitems[i].label = NULL;
    }
    if(menuitems[i].kncp)
        free_color_pair(&menuitems[i].kncp);
    if(menuitems[i].klcp)
        free_color_pair(&menuitems[i].klcp);
    if(kn)
      menuitems[i].kncp = new_color_pair(kn->fg, kn->bg);
    else 
      menuitems[i].kncp = NULL;
    if(kl)
      menuitems[i].klcp = new_color_pair(kl->fg, kl->bg);
    else 
      menuitems[i].klcp = NULL;    

    if(label
       && (menuitems[i].label =(char *)malloc((strlen(label)+1)*sizeof(char))))
      strcpy(menuitems[i].label, label);
}


int
mouse_on_key(row, col)
    int row, col;
{
    int i;

    for(i = 0; i < 12; i++)
      if(M_ACTIVE(row, col, &menuitems[i]))
	return(TRUE);
	
    return(FALSE);
}
#endif	/* MOUSE */


/* 
 * Below are functions for use outside pico to manipulate text
 * in a pico's native format (circular linked list of lines).
 *
 * The idea is to streamline pico use by making it fairly easy
 * for outside programs to prepare text intended for pico's use.
 * The simple char * alternative is messy as it requires two copies
 * of the same text, and isn't very economic in limited memory
 * situations (THANKS BELLEVUE-BILLY.).
 */
typedef struct picotext {
    LINE *linep;
    LINE *dotp;
    int doto;
    short crinread;
} PICOTEXT;

#define PT(X)	((PICOTEXT *)(X))

/*
 * pico_get - return window struct pointer used as a handle
 *            to the other pico_xxx routines.
 */
void *
pico_get()
{
   PICOTEXT *wp = NULL;
   LINE     *lp = NULL;

   if(wp = (PICOTEXT *)malloc(sizeof(PICOTEXT))){
       wp->crinread = 0;
       if((lp = lalloc(0)) == NULL){
	   free(wp);
	   return(NULL);
       }

       wp->dotp = wp->linep = lp->l_fp = lp->l_bp = lp;
       wp->doto = 0;
   }
   else
     emlwrite("Can't allocate space for text", NULL);

   return((void *)wp);
}

/*
 * pico_give - free resources and give up picotext struct
 */
void
pico_give(w)
void *w;
{
    register LINE *lp;
    register LINE *fp;

    fp = lforw(PT(w)->linep);
    while((lp = fp) != PT(w)->linep){
        fp = lforw(lp);
	free(lp);
    }
    free(PT(w)->linep);
    free((PICOTEXT *)w);
}

/*
 * pico_readc - return char at current point.  Up to calling routines
 *              to keep cumulative count of chars.
 */
int
pico_readc(w, c)
void          *w;
unsigned char *c;
{
    int rv     = 0;

    if(PT(w)->crinread){
	*c = '\012';				/* return LF */
	PT(w)->crinread = 0;
	rv++;
    }
    else if(PT(w)->doto < llength(PT(w)->dotp)){ /* normal char to return */
        *c = (unsigned char) lgetc(PT(w)->dotp, (PT(w)->doto)++).c;
	rv++;
    }
    else if(PT(w)->dotp != PT(w)->linep){ /* return line break */
	PT(w)->dotp = lforw(PT(w)->dotp);
	PT(w)->doto = 0;
#if	defined(DOS) || defined(OS2)
	*c = '\015';
	PT(w)->crinread++;
#else
	*c = '\012';				/* return local eol! */
#endif
	rv++;
    }						/* else no chars to return */

    return(rv);
}


/*
 * pico_writec - write a char into picotext and advance pointers.
 *               Up to calling routines to keep track of total chars
 *               written
 */
int
pico_writec(w, c)
void *w;
int   c;
{
    int   rv = 0;

    if(c == '\r')				/* ignore CR's */
      rv++;					/* so fake it */
    else if(c == '\n'){				/* insert newlines on LF */
	/*
	 * OK, if there are characters on the current line or 
	 * dotp is pointing to the delimiter line, insert a newline
	 * No here's the tricky bit; preserve the implicit EOF newline.
	 */
	if(lforw(PT(w)->dotp) == PT(w)->linep && PT(w)->dotp != PT(w)->linep){
	    PT(w)->dotp = PT(w)->linep;
	    PT(w)->doto = 0;
	}
	else{
	    register LINE *lp;

	    if((lp = lalloc(0)) == NULL){
		emlwrite("Can't allocate space for more characters",NULL);
		return(0);
	    }

	    if(PT(w)->dotp == PT(w)->linep){
		lforw(lp) = PT(w)->linep;
		lback(lp) = lback(PT(w)->linep);
		lforw(lback(lp)) = lback(PT(w)->linep) = lp;
	    }
	    else{
		lforw(lp) = lforw(PT(w)->dotp);
		lback(lp) = PT(w)->dotp;
		lback(lforw(lp)) = lforw(PT(w)->dotp) = lp;
		PT(w)->dotp = lp;
		PT(w)->doto = 0;
	    }
	}

	rv++;
    }
    else
      rv = geninsert(&PT(w)->dotp, &PT(w)->doto, PT(w)->linep, c, 0, 1, NULL);

    return((rv) ? 1 : 0);			/* return number written */
}


/*
 * pico_puts - just write the given string into the text
 */
int
pico_puts(w, s)
void *w;
char *s;
{
    while(*s != '\0')
      pico_writec(w, (int)*s++);
}


/*
 * pico_seek - position dotp and dot at requested location
 */
int
pico_seek(w, offset, orig)
void *w;
long  offset;
int   orig;
{
    register LINE *lp;

    PT(w)->crinread = 0;
    switch(orig){
      case 0 :				/* SEEK_SET */
	PT(w)->dotp = lforw(PT(w)->linep);
	PT(w)->doto = 0;
      case 1 :				/* SEEK_CUR */
	lp = PT(w)->dotp;
	while(lp != PT(w)->linep){
	    if(offset <= llength(lp)){
		PT(w)->doto = (int)offset;
		PT(w)->dotp = lp;
		break;
	    }

	    offset -= ((long)llength(lp)
#if defined(DOS) || defined(OS2)
		       + 2L);
#else
		       + 1L);
#endif
	    lp = lforw(lp);
	}
        break;

      case 2 :				/* SEEK_END */
	PT(w)->dotp = lback(PT(w)->linep);
	PT(w)->doto = llength(PT(w)->dotp);
	break;
      default :
        return(-1);
    }

    return(0);
}


/*
 * breplace - replace the current window's text with the given 
 *            LINEs
 */
void
breplace(w)
void *w;
{
    register LINE *lp;
    register LINE *fp;

    fp = lforw(curbp->b_linep);
    while((lp = fp) != curbp->b_linep){		/* blast old lines */
        fp = lforw(lp);
	free(lp);
    }
    free(curbp->b_linep);

    curbp->b_linep   = PT(w)->linep;			/* arrange pointers */

    curwp->w_linep   = lforw(curbp->b_linep);
    curwp->w_dotp    = lforw(curbp->b_linep);
    curwp->w_doto    = 0;
    curwp->w_markp   = curwp->w_imarkp = NULL;
    curwp->w_marko   = curwp->w_imarko = 0;

    curbp->b_dotp    = curwp->w_dotp;
    curbp->b_doto    = curbp->b_marko  = 0;
    curbp->b_markp   = NULL;
    curbp->b_linecnt = -1;

    curwp->w_flag |= WFHARD;
}


#ifdef	_WINDOWS
/*
 *
 */
int
composer_file_drop(x, y, filename)
    int   x, y;
    char *filename;
{
    int attached = 0;
    if((ComposerTopLine > 2 && x <= ComposerTopLine)
       || !LikelyASCII(filename)){
	AppendAttachment(filename, NULL, NULL);
	attached++;
    }
    else{
	setimark(FALSE, 1);
	ifile(filename);
	swapimark(FALSE, 1);
    }

    if(ComposerEditing){		/* update display */
	PaintBody(0);
    }
    else{
	refresh(0, 1);
	update();
    }

    if(attached)
      emlwrite("Attached dropped file \"%s\"", filename);
    else
      emlwrite("Inserted dropped file \"%s\"", filename);

    if(ComposerEditing){		/* restore cursor */
	HeaderPaintCursor();
    }
    else{
	curwp->w_flag |= WFHARD;
	update();
    }

    return(1);
}



int
pico_cursor(col, row)
    int  col;
    long row;
{
    return((row > 1 && row <= term.t_nrow - (term.t_mrow + 1))
	     ? MSWIN_CURSOR_IBEAM
	     : MSWIN_CURSOR_ARROW);
}
#endif /* _WINDOWS */
