/*
 *  R : A Computer Langage for Statistical Data Analysis
 *  Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
 *  Copyright (C) 1998--2013  The R Core Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, a copy is available at
 *  http://www.r-project.org/Licenses/
 */

/* <UTF8>
   Used XTextWidth and XDrawText, so need to use fontsets

   Also needed input context.
*/

/* The version for R 2.1.0 is partly based on patches by
   Ei-ji Nakama <nakama@ki.rim.or.jp> for use with Japanese fonts. */

#define DPRINTS(x) printf(#x "=[%s]\n", x)
#define DPRINTX(x) printf(#x "=%x\n", x)
#define DPRINTD(x) printf(#x "=%d\n", x)

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define R_USE_SIGNALS 1
#include <Defn.h>
#include <stdlib.h>
#include <Rinternals.h>
#include <R_ext/Parse.h>  /* parsing is used in handling escape codes */


#ifndef _Xconst
#define _Xconst const
#endif
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>
#include <X11/Intrinsic.h>

#include <Print.h>
/* For the input handlers of the event loop mechanism: */
#include <R_ext/eventloop.h>
#include <R_ext/RS.h>           /* for CallocCharBuf */
# define USE_FONTSET 1
/* In theory we should do this, but it works less well
# ifdef X_HAVE_UTF8_STRING
#  define HAVE_XUTF8TEXTEXTENTS 1
#  define HAVE_XUTF8DRAWSTRING 1
#  define HAVE_XUTF8DRAWIMAGESTRING 1
# endif */

#ifndef HAVE_KEYSYM
#define KeySym int
#endif

#define DEEvent XEvent

typedef enum { UP, DOWN, LEFT, RIGHT } DE_DIRECTION;

typedef enum {UNKNOWNN, NUMERIC, CHARACTER} CellType;

/* EXPORTS : */
SEXP in_RX11_dataentry(SEXP call, SEXP op, SEXP args, SEXP rho);

/* Global variables needed for the graphics */
static Display *iodisplay = NULL;
static XContext deContext;
static int nView = 0; /* number of open data windows */
static int fdView = -1;

typedef struct {
    Window iowindow;
    GC iogc;
    XFontStruct *font_info;
    SEXP work, names, lens;
    PROTECT_INDEX wpi, npi, lpi;
    int box_w;                       /* width of a box */
    int boxw[100];                   /* widths of cells */
    int box_h;                       /* height of a box */
    int windowWidth;                 /* current width of the window */
    int fullwindowWidth;
    int windowHeight;                /* current height of the window */
    int fullwindowHeight;
    int crow;                        /* current row */
    int ccol;                        /* current column */
    int nwide, nhigh;
    int colmax, colmin, rowmax, rowmin;
    int bwidth;			/* width of the border */
    int hht;			/* height of header  */
    int text_offset;
    int nboxchars;
    int xmaxused, ymaxused;
    char labform[6];
    Rboolean isEditor;
    Atom prot;
} destruct, *DEstruct;

/* Local Function Definitions */

static void advancerect(DEstruct, DE_DIRECTION);
static int  CheckControl(DEEvent *);
static int  CheckShift(DEEvent *);
static int  checkquit(int);
static void clearrect(DEstruct);
static void closerect(DEstruct);
static void clearwindow(DEstruct);
static void closewin(DEstruct);
static void copycell(DEstruct);
static void doControl(DEstruct, DEEvent*);
static int  doMouseDown(DEstruct, DEEvent*);
static void doSpreadKey(DEstruct, int, DEEvent*);
static void downlightrect(DEstruct);
static void drawwindow(DEstruct);
static void drawcol(DEstruct, int);
static void drawrow(DEstruct, int);
static void eventloop(DEstruct);
static void find_coords(DEstruct, int, int, int*, int*);
static int  findcell(DEstruct);
static char *GetCharP(DEEvent*);
static KeySym GetKey(DEEvent*);
static void handlechar(DEstruct, char *);
static void highlightrect(DEstruct);
static Rboolean initwin(DEstruct, const char *);
static void jumppage(DEstruct, DE_DIRECTION);
static void jumpwin(DEstruct, int, int);
static void pastecell(DEstruct, int, int);
static void popdownmenu(DEstruct);
static void popupmenu(DEstruct, int, int, int, int);
static void printlabs(DEstruct);
static void printrect(DEstruct, int, int);
static void printstring(DEstruct, const char*, int, int, int, int);
static void printelt(DEstruct, SEXP, int, int, int);
static void RefreshKeyboardMapping(DEEvent*);
static void cell_cursor_init(DEstruct);

/* Functions to hide Xlib calls */
static void bell(void);
static void cleararea(DEstruct, int, int, int, int);
static void copyH(DEstruct, int, int, int);
static void copyarea(DEstruct, int, int, int, int);
static void doConfigure(DEstruct, DEEvent *ioevent);
static void drawrectangle(DEstruct, int, int, int, int, int, int);
static void drawtext(DEstruct, int, int, char*, int);
static void RefreshKeyboardMapping(DEEvent *ioevent);
static void Rsync(DEstruct);
static int textwidth(DEstruct, const char*, int);
static int WhichEvent(DEEvent ioevent);

static void R_ProcessX11Events(void *data);


static const char *get_col_name(DEstruct, int col);
static int  get_col_width(DEstruct, int col);
static CellType get_col_type(DEstruct, int col);
static void calc_pre_edit_pos(DEstruct DE);
static int last_wchar_bytes(char *);
static SEXP ssNewVector(SEXPTYPE, int);
static SEXP ssNA_STRING;


/* only used in the editor */
static Atom _XA_WM_PROTOCOLS = 0;
static Window menuwindow, menupanes[4];
static Rboolean CellModified;
static int box_coords[6];
static int currentexp;                  /* whether an cell is active */
static int ndecimal;                    /* count decimal points */
static int ne;                          /* count exponents */
static int nneg;			/* indicate whether its a negative */
static int clength;                     /* number of characters currently entered */
static int inSpecial;

#define BOOSTED_BUF_SIZE    201
static char buf[BOOSTED_BUF_SIZE];	/* boosted to allow for MBCS */
static char *bufp;
static char copycontents[sizeof(buf)+1] ;

/* The next few and used only for the editor in MBCS locales */
static Status           status;
static XFontSet         font_set = NULL;
static XFontStruct	**fs_list;
static int		font_set_cnt;
static char             fontset_name[]="-*-fixed-medium-r-*-*-*-120-*-*-*-*-*-*";
static XIM		ioim;
static XIMStyle         ioim_style;
static XIMStyles        *ioim_styles;

/*
 * XIM:
 * OverTheSpot XIMPreeditPosition | XIMStatusArea;
 * OffTheSpot  XIMPreeditArea     | XIMStatusArea;
 * Root        XIMPreeditNothing  | XIMStatusNothing;
 */
static XIMStyle preedit_styles[] = {
    XIMPreeditPosition,
    XIMPreeditArea,
    XIMPreeditNothing,
    XIMPreeditNone,
    (XIMStyle)NULL,
};
static XIMStyle status_styles[] = {
    XIMStatusArea,
    XIMStatusNothing,
    XIMStatusNone,
    (XIMStyle)NULL,
};
static XIC ioic = NULL;

#ifndef max
#define max(a, b) (((a)>(b))?(a):(b))
#endif
#ifndef min
#define min(a, b) (((a)<(b))?(a):(b))
#endif
#define BOXW(x) (min(((x<100 && DE->nboxchars==0)?DE->boxw[x]:DE->box_w), DE->fullwindowWidth-DE->boxw[0]-2*DE->bwidth-2))

/*
  Underlying assumptions (for this version R >= 1.8.0)

  The data are stored in a list `work', with unused columns having
  NULL entries.  The names for the list are in `names', which should
  have a name for all displayable columns (up to xmaxused).
  The *used* lengths of the columns are in `lens': this needs only be
  set for non-NULL columns.

  If the list was originally length(0), that should work with
  0 pre-defined cols.  (It used to have 1 pre-defined numeric column.)

  All row and col numbers are 1-based.

  BDR May 2003
 */

/*
   The spreadsheet function returns a list of vectors. The types of
   these vectors can be specified by the user as can their names. It
   the names are specified they are set during initialization. The
   user can change these via a menu interface, they can also change
   the type.

   The vectors are created too long and if they need to be increased
   this is done by using the next higher power of 2. They start 100
   long.  Vectors are initialized to NA when they are created so that
   NA is returned for any cell that was not set by the user.  We use
   a special type of NA to distinguish this from user-supplied NAs.

   In Macintosh we needed to call the main event loop to get
   events. This ensures that the spreadsheet interacts well with the
   other windows. Under X windows we let the window manager handle
   those sorts of details.

 */

static char *menu_label[] =
{
    " Real",
    " Character",
    "Change Name ",
};

/*
   ssNewVector is just an interface to allocVector but it lets us
   set the fields to NA. We need to have a special NA for reals and
   strings so that we can differentiate between uninitialized elements
   in the vectors and user supplied NA's; hence ssNA_STRING
 */

static SEXP ssNewVector(SEXPTYPE type, int vlen)
{
    SEXP tvec;
    int j;

    tvec = allocVector(type, vlen);
    for (j = 0; j < vlen; j++)
	if (type == REALSXP)
	    REAL(tvec)[j] = NA_REAL;
	else if (type == STRSXP)
	    SET_STRING_ELT(tvec, j, ssNA_STRING);
    return (tvec);
}

static void closewin_cend(void *data)
{
    DEstruct DE = (DEstruct) data;
    closewin(DE);
}

SEXP in_RX11_dataentry(SEXP call, SEXP op, SEXP args, SEXP rho)
{
    SEXP colmodes, tnames, tvec, tvec2, work2;
    SEXPTYPE type;
    int i, j, cnt, len, nprotect;
    RCNTXT cntxt;
    char clab[25];
    char *title = "R Data Editor";
    destruct DE1;
    DEstruct DE = &DE1;

    nprotect = 0;/* count the PROTECT()s */
    PROTECT_WITH_INDEX(DE->work = duplicate(CAR(args)), &DE->wpi); nprotect++;
    colmodes = CADR(args);
    tnames = getAttrib(DE->work, R_NamesSymbol);

    if (TYPEOF(DE->work) != VECSXP || TYPEOF(colmodes) != VECSXP)
	errorcall(call, "invalid argument");

    /* initialize the constants */

    bufp = buf;
    ne = 0;
    currentexp = 0;
    nneg = 0;
    ndecimal = 0;
    clength = 0;
    inSpecial = 0;
    DE->ccol = 1;
    DE->crow = 1;
    DE->colmin = 1;
    DE->rowmin = 1;
    PROTECT(ssNA_STRING = duplicate(NA_STRING));
    nprotect++;
    DE->bwidth = 5;
    DE->hht = 30;
    DE->isEditor = TRUE;

    /* setup work, names, lens  */
    DE->xmaxused = length(DE->work); DE->ymaxused = 0;
    PROTECT_WITH_INDEX(DE->lens = allocVector(INTSXP, DE->xmaxused), &DE->lpi);
    nprotect++;

    if (isNull(tnames)) {
	PROTECT_WITH_INDEX(DE->names = allocVector(STRSXP, DE->xmaxused),
			   &DE->npi);
	for(i = 0; i < DE->xmaxused; i++) {
	    sprintf(clab, "var%d", i);
	    SET_STRING_ELT(DE->names, i, mkChar(clab));
	}
    } else
	PROTECT_WITH_INDEX(DE->names = duplicate(tnames), &DE->npi);
    nprotect++;
    for (i = 0; i < DE->xmaxused; i++) {
	int len = LENGTH(VECTOR_ELT(DE->work, i));
	INTEGER(DE->lens)[i] = len;
	DE->ymaxused = max(len, DE->ymaxused);
	type = TYPEOF(VECTOR_ELT(DE->work, i));
	if (LENGTH(colmodes) > 0 && !isNull(VECTOR_ELT(colmodes, i)))
	    type = str2type(CHAR(STRING_ELT(VECTOR_ELT(colmodes, i), 0)));
	if (type != STRSXP) type = REALSXP;
	if (isNull(VECTOR_ELT(DE->work, i))) {
	    if (type == NILSXP) type = REALSXP;
	    SET_VECTOR_ELT(DE->work, i, ssNewVector(type, 100));
	} else if (!isVector(VECTOR_ELT(DE->work, i)))
	    errorcall(call, "invalid type for value");
	else {
	    if (TYPEOF(VECTOR_ELT(DE->work, i)) != type)
		SET_VECTOR_ELT(DE->work, i,
			       coerceVector(VECTOR_ELT(DE->work, i), type));
	}
    }


    /* start up the window, more initializing in here */
    if (initwin(DE, title))
	errorcall(call, "unable to start data editor");

    /* set up a context which will close the window if there is an error */
    begincontext(&cntxt, CTXT_CCODE, R_NilValue, R_BaseEnv, R_BaseEnv,
		 R_NilValue, R_NilValue);
    cntxt.cend = &closewin_cend;
    cntxt.cenddata = (void *) DE;

    highlightrect(DE);

    cell_cursor_init(DE);

    eventloop(DE);

    endcontext(&cntxt);
    closewin(DE);
    if(nView == 0) {
	if(fdView >= 0) { /* might be open after viewers, but unlikely */
	    removeInputHandler(&R_InputHandlers,
			       getInputHandler(R_InputHandlers,fdView));
	    fdView = -1;
	}
	if(font_set) {
	    XFreeFontSet(iodisplay, font_set);
	    font_set = NULL;
	}
	XCloseDisplay(iodisplay);
	iodisplay = NULL;
    }

    /* drop out unused columns */
    for(i = 0, cnt = 0; i < DE->xmaxused; i++)
	if(!isNull(VECTOR_ELT(DE->work, i))) cnt++;
    if (cnt < DE->xmaxused) {
	PROTECT(work2 = allocVector(VECSXP, cnt)); nprotect++;
	for(i = 0, j = 0; i < DE->xmaxused; i++) {
	    if(!isNull(VECTOR_ELT(DE->work, i))) {
		SET_VECTOR_ELT(work2, j, VECTOR_ELT(DE->work, i));
		INTEGER(DE->lens)[j] = INTEGER(DE->lens)[i];
		SET_STRING_ELT(DE->names, j, STRING_ELT(DE->names, i));
		j++;
	    }
	}
	REPROTECT(DE->names = lengthgets(DE->names, cnt), DE->npi);
    } else work2 = DE->work;

    for (i = 0; i < LENGTH(work2); i++) {
	len = INTEGER(DE->lens)[i];
	tvec = VECTOR_ELT(work2, i);
	if (LENGTH(tvec) != len) {
	    tvec2 = ssNewVector(TYPEOF(tvec), len);
	    for (j = 0; j < len; j++) {
		if (TYPEOF(tvec) == REALSXP) {
			REAL(tvec2)[j] = REAL(tvec)[j];
		} else if (TYPEOF(tvec) == STRSXP) {
		    if (STRING_ELT(tvec, j) != ssNA_STRING)
			SET_STRING_ELT(tvec2, j, STRING_ELT(tvec, j));
		    else
			SET_STRING_ELT(tvec2, j, NA_STRING);
		} else
		    error("dataentry: internal memory problem");
	    }
	    SET_VECTOR_ELT(work2, i, tvec2);
	}
    }

    setAttrib(work2, R_NamesSymbol, DE->names);
    UNPROTECT(nprotect);
    return work2;
}

static void dv_closewin_cend(void *data)
{
    DEstruct DE = (DEstruct) data;
    R_ReleaseObject(DE->lens);
    R_ReleaseObject(DE->work);
    closewin(DE);
    free(DE);
    nView--;
}

SEXP in_R_X11_dataviewer(SEXP call, SEXP op, SEXP args, SEXP rho)
{
    SEXP stitle;
    SEXPTYPE type;
    int i, nprotect;
    RCNTXT cntxt;
    DEstruct DE = (DEstruct) malloc(sizeof(destruct));

    nView++;

    nprotect = 0;/* count the PROTECT()s */
    DE->work = CAR(args);
    DE->names = getAttrib(DE->work, R_NamesSymbol);

    if (TYPEOF(DE->work) != VECSXP)
	errorcall(call, "invalid argument");
    stitle = CADR(args);
    if (!isString(stitle) || LENGTH(stitle) != 1)
	errorcall(call, "invalid argument");

    /* initialize the constants */

    bufp = buf;
    ne = 0;
    currentexp = 0;
    nneg = 0;
    ndecimal = 0;
    clength = 0;
    inSpecial = 0;
    DE->ccol = 1;
    DE->crow = 1;
    DE->colmin = 1;
    DE->rowmin = 1;
    DE->bwidth = 5;
    DE->hht = 10;
    DE->isEditor = FALSE;

    /* setup work, names, lens  */
    DE->xmaxused = length(DE->work); DE->ymaxused = 0;
    PROTECT_WITH_INDEX(DE->lens = allocVector(INTSXP, DE->xmaxused), &DE->lpi);
    nprotect++;

    for (i = 0; i < DE->xmaxused; i++) {
	int len = LENGTH(VECTOR_ELT(DE->work, i));
	INTEGER(DE->lens)[i] = len;
	DE->ymaxused = max(len, DE->ymaxused);
	type = TYPEOF(VECTOR_ELT(DE->work, i));
	if (type != STRSXP && type != REALSXP)
	    errorcall(call, "invalid argument");
    }


    /* start up the window, more initializing in here */
    if (initwin(DE, CHAR(STRING_ELT(stitle, 0))))
	errorcall(call, "unable to start data viewer");

    /* set up a context which will close the window if there is an error */
    begincontext(&cntxt, CTXT_CCODE, R_NilValue, R_BaseEnv, R_BaseEnv,
		 R_NilValue, R_NilValue);
    cntxt.cend = &dv_closewin_cend;
    cntxt.cenddata = (void *) DE;

    highlightrect(DE);

    cell_cursor_init(DE);

    if(fdView < 0) {
	fdView = ConnectionNumber(iodisplay);
	addInputHandler(R_InputHandlers, fdView,
			R_ProcessX11Events, XActivity);
    }

    drawwindow(DE);

    R_PreserveObject(DE->work); /* also preserves names */
    R_PreserveObject(DE->lens);
    UNPROTECT(nprotect);
    return R_NilValue;
}

/* Window Drawing Routines */

static void setcellwidths(DEstruct DE)
{
    int i, w, dw;

    DE->windowWidth = w = 2*DE->bwidth + DE->boxw[0] + BOXW(DE->colmin);
    DE->nwide = 2;
    for (i = 2; i < 100; i++) { /* 100 on-screen columns cannot occur */
	dw = BOXW(i + DE->colmin - 1);
	if((w += dw) > DE->fullwindowWidth ||
	   (!DE->isEditor && i > DE->xmaxused - DE->colmin + 1)) {
	    DE->nwide = i;
	    DE->windowWidth = w - dw;
	    break;
	}
    }
}

static void drawwindow(DEstruct DE)
{
    int i, st;
    XWindowAttributes attribs;

    /* if there is an active cell enter the data in it */
    /*
     * case colname input Expose not use.
     * closerect();
     */

    /* now set up the window with the new dimensions */
    XGetWindowAttributes(iodisplay, DE->iowindow, &attribs);
    DE->bwidth = attribs.border_width;
    DE->fullwindowWidth = attribs.width;
    DE->fullwindowHeight = attribs.height;
    setcellwidths(DE);
    DE->nhigh = (DE->fullwindowHeight - 2 * DE->bwidth - DE->hht) / DE->box_h;
    DE->windowHeight = DE->nhigh * DE->box_h + 2 * DE->bwidth;

    clearwindow(DE);


    for (i = 1; i < DE->nhigh; i++)
	drawrectangle(DE, 0, DE->hht + i * DE->box_h, DE->boxw[0], DE->box_h,
		      1, 1);
     /* so row 0 and col 0 are reserved for labels */
    DE->colmax = DE->colmin + (DE->nwide - 2);
    DE->rowmax = DE->rowmin + (DE->nhigh - 2);
    printlabs(DE);
    for (i = DE->colmin; i <= DE->colmax; i++) drawcol(DE, i);

    if(DE->isEditor) {
	/* draw the quit etc boxes */

	i = textwidth(DE, "Quit", 4);
	box_coords[0] = st = DE->fullwindowWidth - 6 - DE->bwidth;
	box_coords[1] = st - i;
	drawrectangle(DE, st - i, 3, i + 4, DE->hht - 6, 1, 1);
	drawtext(DE, st + 2 - i, DE->hht - 7, "Quit", 4);

	box_coords[4] = st = st - 5*i;
	i = textwidth(DE, "Paste", 5);
	box_coords[5] = st - i;
	drawrectangle(DE, st - i, 3, i + 4, DE->hht - 6, 1, 1);
	drawtext(DE, st + 2 - i, DE->hht - 7, "Paste", 5);

	box_coords[2] = st = st - 2*i;
	i = textwidth(DE, "Copy", 4);
	box_coords[3] = st - i;
	drawrectangle(DE, st - i, 3, i + 4, DE->hht - 6, 1, 1);
	drawtext(DE, st + 2 - i, DE->hht - 7, "Copy", 4);
    }

    highlightrect(DE);

    Rsync(DE);

}

static void doHscroll(DEstruct DE, int oldcol)
{
    int i, dw;
    int oldnwide = DE->nwide, oldwindowWidth = DE->windowWidth;

    /* horizontal re-position */
    setcellwidths(DE);
    DE->colmax = DE->colmin + (DE->nwide - 2);
    if (oldcol < DE->colmin) { /* drop oldcol...colmin - 1 */
	dw = DE->boxw[0];
	for (i = oldcol; i < DE->colmin; i++) dw += BOXW(i);
	copyH(DE, dw, DE->boxw[0], oldwindowWidth - dw + 1);
	dw = oldwindowWidth - BOXW(oldcol) + 1;
	cleararea(DE, dw, DE->hht, DE->fullwindowWidth-dw,
		  DE->fullwindowHeight);
	/* oldnwide includes the row labels */
	for (i = oldcol+oldnwide-1; i <= DE->colmax; i++) drawcol(DE, i);
    } else {
	/* move one or more cols left */
	dw = BOXW(DE->colmin);
	copyH(DE, DE->boxw[0], DE->boxw[0] + dw, DE->windowWidth - dw + 1);
	dw = DE->windowWidth + 1;
	cleararea(DE, dw, DE->hht, DE->fullwindowWidth-dw,
		  DE->fullwindowHeight);
	drawcol(DE, DE->colmin);
    }

    highlightrect(DE);
    cell_cursor_init(DE);

    Rsync(DE);
}

/* find_coords finds the coordinates of the upper left corner of the
   given cell on the screen: row and col are on-screen coords */

static void find_coords(DEstruct DE,
			int row, int col, int *xcoord, int *ycoord)
{
    int i, w;
    w = DE->bwidth;
    if (col > 0) w += DE->boxw[0];
    for(i = 1; i < col; i ++) w += BOXW(i + DE->colmin - 1);
    *xcoord = w;
    *ycoord = DE->bwidth + DE->hht + DE->box_h * row;
}

/* draw the window with the top left box at column wcol and row wrow */

static void jumpwin(DEstruct DE, int wcol, int wrow)
{
    if (wcol < 0 || wrow < 0) {
	bell();
	return;
    }
    closerect(DE);
    if (DE->colmin != wcol || DE->rowmin != wrow) {
	DE->colmin = wcol;
	DE->rowmin = wrow;
	closerect(DE);
	drawwindow(DE);
    } else highlightrect(DE);
}

static void advancerect(DEstruct DE, DE_DIRECTION which)
{

    /* if we are in the header, changing a name then only down is
       allowed */
    if (DE->crow < 1 && which != DOWN) {
	bell();
	return;
    }

    closerect(DE);

    switch (which) {
    case UP:
	if (DE->crow == 1) {
	    if (DE->rowmin == 1)
		bell();
	    else
		jumppage(DE, UP);
	} else
	    DE->crow--;
	break;
    case DOWN:
	if (!DE->isEditor && DE->crow+DE->rowmin > DE->ymaxused) {
	    bell();
	    break;
	}
	if (DE->crow == (DE->nhigh - 1))
	    jumppage(DE, DOWN);
	else
	    DE->crow++;
	break;
    case RIGHT:
	if (!DE->isEditor && DE->ccol+DE->colmin > DE->xmaxused) {
	    bell();
	    break;
	}
	if (DE->ccol == (DE->nwide - 1))
	    jumppage(DE, RIGHT);
	else
	    DE->ccol++;
	break;
    case LEFT:
	if (DE->ccol == 1) {
	    if (DE->colmin == 1)
		bell();
	    else
		jumppage(DE, LEFT);
	} else
	    DE->ccol--;
	break;
    default:
	UNIMPLEMENTED("advancerect");
    }

    highlightrect(DE);

    cell_cursor_init(DE);
}

static void cell_cursor_init(DEstruct DE)
{
    int i, whichrow = DE->crow + DE->rowmin - 1,
	whichcol = DE->ccol + DE->colmin -1;
    SEXP tmp;

    memset(buf,0,sizeof(buf));

    if (DE->crow == 0 ){
	strncpy(buf,
		get_col_name(DE, whichcol),
		BOOSTED_BUF_SIZE-1);
    } else {
	if (length(DE->work) >= whichcol) {
	    tmp = VECTOR_ELT(DE->work, whichcol - 1);
	    if (tmp != R_NilValue &&
		(i = whichrow - 1) < LENGTH(tmp) ) {
		PrintDefaults();
		if (TYPEOF(tmp) == REALSXP) {
		    strncpy(buf, EncodeElement(tmp, i, 0, '.'),
			    BOOSTED_BUF_SIZE-1);
		} else if (TYPEOF(tmp) == STRSXP) {
		    if (STRING_ELT(tmp, i) != ssNA_STRING)
			strncpy(buf, EncodeElement(tmp, i, 0, '.'),
				BOOSTED_BUF_SIZE-1);
		}
	    }
	}
    }
    buf[BOOSTED_BUF_SIZE-1] = '\0';
    clength = (int) strlen(buf);
    bufp = buf + clength;
}

static const char *get_col_name(DEstruct DE, int col)
{
    static char clab[25];
    int nwrote;
    if (col <= DE->xmaxused) {
	/* don't use NA labels */
	SEXP tmp = STRING_ELT(DE->names, col - 1);
	if(tmp != NA_STRING) return(CHAR(tmp));
    }
    nwrote = snprintf(clab, 25, "var%d", col);
    if (nwrote >= 25)
	error("get_col_name: column number too big to stringify");
    return (const char *)clab;
}

static int get_col_width(DEstruct DE, int col)
{
    int i, w = 0, w1;
    const char *strp;
    SEXP tmp, lab;

    if (DE->nboxchars > 0) return DE->box_w;
    if (col <= DE->xmaxused) {
	tmp = VECTOR_ELT(DE->work, col - 1);
	if (isNull(tmp)) return DE->box_w;
	/* don't use NA labels */
	lab = STRING_ELT(DE->names, col - 1);
	if(lab != NA_STRING) strp = CHAR(lab); else strp = "var12";
	PrintDefaults();

	w = textwidth(DE, strp, (int) strlen(strp));
	for (i = 0; i < INTEGER(DE->lens)[col - 1]; i++) {
	    strp = EncodeElement(tmp, i, 0, '.');
	    w1 = textwidth(DE, strp, (int) strlen(strp));
	    if (w1 > w) w = w1;
	}
	if(w < 0.5*DE->box_w) w = (int) (0.5*DE->box_w);
	if(w < 0.8*DE->box_w) w+= (int) (0.1*DE->box_w);
	if(w > 600) w = 600;
	return w+8;
    }
    return DE->box_w;
}

static CellType get_col_type(DEstruct DE, int col)
{
    SEXP tmp;
    CellType res = UNKNOWNN;

    if (col <= DE->xmaxused) {
	tmp = VECTOR_ELT(DE->work, col - 1);
	if(TYPEOF(tmp) == REALSXP) res = NUMERIC;
	if(TYPEOF(tmp) == STRSXP) res = CHARACTER;
    }
    return res;
}


/* whichcol is absolute col no, col is position on screen */
static void drawcol(DEstruct DE, int whichcol)
{
    int i, src_x, src_y, len, col = whichcol - DE->colmin + 1,
	bw = BOXW(whichcol);
    const char *clab;
    SEXP tmp;

    find_coords(DE, 0, col, &src_x, &src_y);
    cleararea(DE, src_x, src_y, bw, DE->windowHeight);
    for (i = 0; i < DE->nhigh; i++)
	drawrectangle(DE, src_x, DE->hht + i * DE->box_h, bw, DE->box_h, 1, 1);

    /* now fill it in if it is active */
    clab = get_col_name(DE, whichcol);
    printstring(DE, clab, (int) strlen(clab), 0, col, 0);

   if (DE->xmaxused >= whichcol) {
	tmp = VECTOR_ELT(DE->work, whichcol - 1);
	if (!isNull(tmp)) {
	    len = min(DE->rowmax, INTEGER(DE->lens)[whichcol - 1]);
	    for (i = (DE->rowmin - 1); i < len; i++)
		printelt(DE, tmp, i, i - DE->rowmin + 2, col);
	}
    }
    Rsync(DE);
}


/* whichrow is absolute row no */
static void drawrow(DEstruct DE, int whichrow)
{
    int i, src_x, src_y, row = whichrow - DE->rowmin + 1, w;
    char rlab[15];
    SEXP tvec;

    find_coords(DE, row, 0, &src_x, &src_y);
    cleararea(DE, src_x, src_y, DE->windowWidth, DE->box_h);
    drawrectangle(DE, src_x, src_y, DE->boxw[0], DE->box_h, 1, 1);

    sprintf(rlab, DE->labform, whichrow);
    printstring(DE, rlab, (int)strlen(rlab), row, 0, 0);

    w = DE->bwidth + DE->boxw[0];
    for (i = DE->colmin; i <= DE->colmax; i++) {
	drawrectangle(DE, w, src_y, BOXW(i), DE->box_h, 1, 1);
	w += BOXW(i);
    }

    for (i = DE->colmin; i <= DE->colmax; i++) {
	if (i > DE->xmaxused) break;
	if (!isNull(tvec = VECTOR_ELT(DE->work, i - 1)))
	    if (whichrow <= INTEGER(DE->lens)[i - 1])
		printelt(DE, tvec, whichrow - 1, row, i - DE->colmin + 1);
    }

    Rsync(DE);
}

/* printelt: print the correct value from vector[vrow] into the
   spreadsheet in row ssrow and col sscol */

/* WARNING: This has no check that you're not beyond the end of the
   vector. Caller must check. */

static void printelt(DEstruct DE, SEXP invec, int vrow, int ssrow, int sscol)
{
    const char *strp;
    PrintDefaults();
    if (TYPEOF(invec) == REALSXP) {
	strp = EncodeElement(invec, vrow, 0, '.');
	printstring(DE ,strp, (int) strlen(strp), ssrow, sscol, 0);
    }
    else if (TYPEOF(invec) == STRSXP) {
	if (STRING_ELT(invec, vrow) != ssNA_STRING) {
	    strp = EncodeElement(invec, vrow, 0, '.');
	    printstring(DE ,strp, (int) strlen(strp), ssrow, sscol, 0);
	}
    }
    else
	error("dataentry: internal memory error");
}


static void drawelt(DEstruct DE, int whichrow, int whichcol)
{
    int i;
    const char *clab;
    SEXP tmp;

    if (whichrow == 0) {
	clab = get_col_name(DE, whichcol + DE->colmin - 1);
	printstring(DE ,clab, (int) strlen(clab), 0, whichcol, 0);
    } else {
	if (DE->xmaxused >= whichcol + DE->colmin - 1) {
	    tmp = VECTOR_ELT(DE->work, whichcol + DE->colmin - 2);
	    if (!isNull(tmp) && (i = DE->rowmin + whichrow - 2) <
		INTEGER(DE->lens)[whichcol + DE->colmin - 2] )
		printelt(DE, tmp, i, whichrow, whichcol);
	} else
	    printstring(DE, "", 0, whichrow,  whichcol, 0);
    }

    Rsync(DE);
}

static void jumppage(DEstruct DE, DE_DIRECTION dir)
{
    int i, w, oldcol, wcol;

    switch (dir) {
    case UP:
	DE->rowmin--;
	DE->rowmax--;
	copyarea(DE, 0, DE->hht + DE->box_h, 0, DE->hht + 2 * DE->box_h);
	drawrow(DE, DE->rowmin);
	break;
    case DOWN:
	if (DE->rowmax >= 65535) return;
	DE->rowmin++;
	DE->rowmax++;
	copyarea(DE, 0, DE->hht + 2 * DE->box_h, 0, DE->hht + DE->box_h);
	drawrow(DE, DE->rowmax);
	break;
    case LEFT:
	DE->colmin--;
	doHscroll(DE, DE->colmin+1);
	break;
    case RIGHT:
	oldcol = DE->colmin;
	wcol = DE->colmin + DE->ccol + 1; /* column to be selected */
	/* There may not be room to fit the next column in */
	w = DE->fullwindowWidth - DE->boxw[0] - BOXW(DE->colmax + 1);
	for (i = DE->colmax; i >= oldcol; i--) {
	    w -= BOXW(i);
	    if(w < 0) {
		DE->colmin = i + 1;
		break;
	    }
	}
	DE->ccol = wcol - DE->colmin;
	doHscroll(DE, oldcol);
	break;
    }
}
/* draw a rectangle, used to highlight/downlight the current box */

static void printrect(DEstruct DE, int lwd, int fore)
{
    int x, y;
    find_coords(DE, DE->crow, DE->ccol, &x, &y);
    drawrectangle(DE, x + lwd - 1, y + lwd - 1,
		  BOXW(DE->ccol+DE->colmin-1) - lwd + 1,
		  DE->box_h - lwd + 1, lwd, fore);
    Rsync(DE);
}

static void downlightrect(DEstruct DE)
{
    printrect(DE, 2, 0);
    printrect(DE, 1, 1);
}

static void highlightrect(DEstruct DE)
{
    printrect(DE, 2, 1);
}


static Rboolean getccol(DEstruct DE)
{
    SEXP tmp, tmp2;
    int i, len, newlen, wcol, wrow;
    SEXPTYPE type;
    char clab[25];
    Rboolean newcol = FALSE;

    wcol = DE->ccol + DE->colmin - 1;
    wrow = DE->crow + DE->rowmin - 1;
    if (wcol > DE->xmaxused) {
	/* extend work, names and lens */
	REPROTECT(DE->work = lengthgets(DE->work, wcol), DE->wpi);
	REPROTECT(DE->names = lengthgets(DE->names, wcol), DE->npi);
	for (i = DE->xmaxused; i < wcol; i++) {
	    sprintf(clab, "var%d", i + 1);
	    SET_STRING_ELT(DE->names, i, mkChar(clab));
	}
	REPROTECT(DE->lens = lengthgets(DE->lens, wcol), DE->lpi);
	DE->xmaxused = wcol;
    }
    if (isNull(VECTOR_ELT(DE->work, wcol - 1))) {
	newcol = TRUE;
	SET_VECTOR_ELT(DE->work, wcol - 1,
		       ssNewVector(REALSXP, max(100, wrow)));
	INTEGER(DE->lens)[wcol - 1] = 0;
    }
    if (!isVector(tmp = VECTOR_ELT(DE->work, wcol - 1)))
	error("internal type error in dataentry");
    len = INTEGER(DE->lens)[wcol - 1];
    type = TYPEOF(tmp);
    if (len < wrow) {
	for (newlen = max(len * 2, 10) ; newlen < wrow ; newlen *= 2)
	    ;
	tmp2 = ssNewVector(type, newlen);
	for (i = 0; i < len; i++)
	    if (type == REALSXP)
		REAL(tmp2)[i] = REAL(tmp)[i];
	    else if (type == STRSXP)
		SET_STRING_ELT(tmp2, i, STRING_ELT(tmp, i));
	    else
		error("internal type error in dataentry");
	SET_VECTOR_ELT(DE->work, wcol - 1, tmp2);
    }
    return newcol;
}

static SEXP processEscapes(SEXP x)
{
    SEXP newval, pattern, replacement, expr;
    ParseStatus status;

    /* We process escape sequences in a scalar string by escaping
       unescaped quotes, then quoting the whole thing and parsing it.  This
       is supposed to be equivalent to the R code

       newval <- gsub(perl=TRUE, "(?<!\\\\)((\\\\\\\\)*)\"", "\\1\\\\\"", x)
       newval <- sub('(^.*$)', '"\1"', newval)
       newval <- eval(parse(text=newval))

       We do it this way to avoid extracting the escape handling
       code from the parser.  We need it in C code because this may be executed
       numerous times from C in dataentry.c */

    PROTECT( pattern = mkString("(?<!\\\\)((\\\\\\\\)*)\"") );
    PROTECT( replacement = mkString("\\1\\\\\"") );
    PROTECT( expr = lang5(install("gsub"), ScalarLogical(1), pattern, replacement, x) );
    SET_TAG( CDR(expr), install("perl") );

    PROTECT( newval = eval(expr, R_BaseEnv) );
    PROTECT( pattern = mkString("(^.*$)") );
    PROTECT( replacement = mkString("\"\\1\"") );
    PROTECT( expr = lang4(install("sub"), pattern, replacement, newval) );
    PROTECT( newval = eval(expr, R_BaseEnv) );
    PROTECT( expr = R_ParseVector( newval, 1, &status, R_NilValue) );

    /* We only handle the first entry. If this were available more generally,
       we'd probably want to loop over all of expr */

    if (status == PARSE_OK && length(expr))
	PROTECT( newval = eval(VECTOR_ELT(expr, 0), R_BaseEnv) );
    else
	PROTECT( newval = R_NilValue );  /* protect just so the count doesn't change */
    UNPROTECT(10);
    return newval;
}

/* close up the entry to a cell, put the value that has been entered
   into the correct place and as the correct type */

static void closerect(DEstruct DE)
{
    SEXP cvec;
    int i, wcol = DE->ccol + DE->colmin - 1,
	wrow = DE->rowmin + DE->crow - 1, wrow0;
    char clab[25];
    Rboolean newcol;

    *bufp = '\0';

    /* first check to see if anything has been entered */
    if (CellModified) {
	if (DE->crow == 0) {
	    if (clength != 0) {
		/* then we are entering a new column name */
		if (DE->xmaxused < wcol) {
		    /* extend work, names and lens */
		    REPROTECT(DE->work = lengthgets(DE->work, wcol), DE->wpi);
		    REPROTECT(DE->names = lengthgets(DE->names, wcol),
			      DE->npi);
		    for (i = DE->xmaxused; i < wcol - 1; i++) {
			sprintf(clab, "var%d", i + 1);
			SET_STRING_ELT(DE->names, i, mkChar(clab));
		    }
		    REPROTECT(DE->lens = lengthgets(DE->lens, wcol), DE->lpi);
		    DE->xmaxused = wcol;
		}
		SET_STRING_ELT(DE->names, wcol - 1, mkChar(buf));
		printstring(DE ,buf, (int) strlen(buf), 0, wcol, 0);
	    } else {
		sprintf(buf, "var%d", DE->ccol);
		printstring(DE ,buf, (int) strlen(buf), 0, wcol, 0);
	    }
	} else {
	    newcol = getccol(DE);
	    cvec = VECTOR_ELT(DE->work, wcol - 1);
	    wrow0 = INTEGER(DE->lens)[wcol - 1];
	    if (wrow > wrow0) INTEGER(DE->lens)[wcol - 1] = wrow;
	    DE->ymaxused = max(DE->ymaxused, wrow);
	    if (clength != 0) {
		/* do it this way to ensure NA, Inf, ...  can get set */
		char *endp;
		double new = R_strtod(buf, &endp);
		Rboolean warn = !isBlankString(endp);
		if (TYPEOF(cvec) == STRSXP) {
		    SEXP newval;
		    PROTECT( newval = mkString(buf) );
		    PROTECT( newval = processEscapes(newval) );
		    if (TYPEOF(newval) == STRSXP && length(newval) == 1)
			SET_STRING_ELT(cvec, wrow - 1, STRING_ELT(newval, 0));
		    else
			warning("dataentry: parse error on string");
		    UNPROTECT(2);
		} else
		    REAL(cvec)[wrow - 1] = new;
		if (newcol && warn) {
		    /* change mode to character */
		    SEXP tmp = coerceVector(cvec, STRSXP);
		    SET_STRING_ELT(tmp, wrow - 1, mkChar(buf));
		    SET_VECTOR_ELT(DE->work, wcol - 1, tmp);
		}
	    } else {
		if (TYPEOF(cvec) == STRSXP)
		    SET_STRING_ELT(cvec, wrow - 1, NA_STRING);
		else
		    REAL(cvec)[wrow - 1] = NA_REAL;
	    }
	    drawelt(DE, DE->crow, DE->ccol); /* to get the cell scrolling right */
	    if(wrow > wrow0) drawcol(DE, wcol); /* to fill in NAs */
	}
    }
    CellModified = FALSE;

    downlightrect(DE);

    ndecimal = 0;
    nneg = 0;
    ne = 0;
    currentexp = 0;
    clength = 0;
    inSpecial = 0;
    bufp = buf;
}

/* print a null terminated string, check to see if it is longer than
   the print area and print it, left adjusted if necessary; clear the
   area of previous text; */

/* This version will only display 200 chars, but the maximum col width
   will not allow that many */
static void printstring(DEstruct DE, const char *ibuf, int buflen, int row,
			int col, int left)
{
    int i, x_pos, y_pos, bw, bufw;
    char pbuf[BOOSTED_BUF_SIZE];
    int wcsbufw,j;
    wchar_t wcspbuf[BOOSTED_BUF_SIZE], *wcspc = wcspbuf;
    wchar_t wcs[BOOSTED_BUF_SIZE];
    char    s[BOOSTED_BUF_SIZE];
    wchar_t *w_p;
    char    *p;
    int cnt;

    find_coords(DE, row, col, &x_pos, &y_pos);
    if (col == 0) bw = DE->boxw[0]; else bw = BOXW(col+DE->colmin-1);
    cleararea(DE, x_pos + 2, y_pos + 2, bw - 3, DE->box_h - 3);
    bufw = (buflen > BOOSTED_BUF_SIZE-1) ? BOOSTED_BUF_SIZE-1 : buflen;
    strncpy(pbuf, ibuf, bufw);
    pbuf[bufw] = '\0';

    p = pbuf;
    wcsbufw = (int) mbsrtowcs(wcspbuf, (const char **)&p, bufw, NULL);
    wcspbuf[wcsbufw]=L'\0';
    if(left) {
	for (i = wcsbufw; i > 1; i--) {
	    for(j=0;*(wcspc+j)!=L'\0';j++)wcs[j]=*(wcspc+j);
	    wcs[j]=L'\0';
	    w_p=wcs;
	    cnt = (int) wcsrtombs(s,(const wchar_t **)&w_p,sizeof(s)-1,NULL);
	    s[cnt]='\0';
	    if (textwidth(DE, s, (int) strlen(s)) < (bw - DE->text_offset)) break;
	    *(++wcspc) = L'<';
	}
    } else {
	for (i = wcsbufw; i > 1; i--) {
	    for(j=0;*(wcspc+j)!=L'\0';j++)wcs[j]=*(wcspc+j);
	    wcs[j]=L'\0';
	    w_p=wcs;
	    cnt = (int) wcsrtombs(s,(const wchar_t **)&w_p,sizeof(s)-1,NULL);
	    s[cnt]='\0';
	    if (textwidth(DE, s, (int) strlen(s)) < (bw - DE->text_offset)) break;
	    *(wcspbuf + i - 2) = L'>';
	    *(wcspbuf + i - 1) = L'\0';
	}
    }
    for(j=0;*(wcspc+j)!=L'\0';j++) wcs[j]=*(wcspc+j);
    wcs[j]=L'\0';
    w_p=wcs;
    cnt = (int) wcsrtombs(s,(const wchar_t **)&w_p,sizeof(s)-1,NULL);

    drawtext(DE, x_pos + DE->text_offset, y_pos + DE->box_h - DE->text_offset,
	     s, cnt);

    Rsync(DE);
}

static void clearrect(DEstruct DE)
{
    int x_pos, y_pos;

    find_coords(DE, DE->crow, DE->ccol, &x_pos, &y_pos);
    cleararea(DE, x_pos, y_pos, BOXW(DE->ccol+DE->colmin-1), DE->box_h);
    Rsync(DE);
}

/* handlechar has to be able to parse decimal numbers and strings,
   depending on the current column type, only printing characters
   should get this far */

/* --- Not true! E.g. ESC ends up in here... */

#include <rlocale.h>

/* <FIXME> This is not correct for stateful MBCSs, but that's hard to
   do as we get a char at a time */
static void handlechar(DEstruct DE, char *text)
{
    int c = text[0], j;
    wchar_t wcs[BOOSTED_BUF_SIZE];

    memset(wcs,0,sizeof(wcs));

    if ( c == '\033' ) { /* ESC */
	CellModified = FALSE;
	clength = 0;
	bufp = buf;
	drawelt(DE, DE->crow, DE->ccol);
	cell_cursor_init(DE);
	return;
    } else
	CellModified = TRUE;

    if (clength == 0) {

	if (DE->crow == 0)	                        /* variable name */
	    currentexp = 3;
	else
	    switch(get_col_type(DE, DE->ccol + DE->colmin - 1)) {
	    case NUMERIC:
		currentexp = 1;
		break;
	    default:
		currentexp = 2;
	    }
	clearrect(DE);
	highlightrect(DE);
    }

    /* NA number? */
    if (get_col_type(DE, DE->ccol + DE->colmin - 1) == NUMERIC) {
	/* input numeric for NA of buffer , suppress NA etc.*/
	if(strcmp(buf, "NA") == 0 || strcmp(buf, "NaN") == 0 ||
	   strcmp(buf, "Inf") == 0 || strcmp(buf, "-Inf") == 0) {
	    buf[0] = '\0';
	    clength = 0;
	    bufp = buf;
	}
    }

    if (currentexp == 1) {	/* we are parsing a number */
	char *mbs = text;
	int i, cnt = (int)mbsrtowcs(wcs, (const char **)&mbs, (int) strlen(text)+1, NULL);

	for(i = 0; i < cnt; i++) {
	    switch (wcs[i]) {
	    case L'-':
		if (nneg == 0) nneg++; else goto donehc;
	    break;
	    case L'.':
		if (ndecimal == 0) ndecimal++; else goto donehc;
	    break;
	    case L'e':
	    case L'E':
		if (ne == 0) {
		    nneg = ndecimal = 0;	/* might have decimal in exponent */
		    ne++;
		} else goto donehc;
	    break;
	    case L'N':
		if(nneg) goto donehc;
	    case L'I':
		inSpecial++;
	    break;
	    default:
		if (!inSpecial && !iswdigit(wcs[i])) goto donehc;
		break;
	    }
	}
    }
    if (currentexp == 3) {
	char *mbs = text;
	int i, cnt = (int) mbsrtowcs(wcs, (const char **)&mbs, (int) strlen(text)+1, NULL);
	for(i = 0; i < cnt; i++) {
	    if (iswspace(wcs[i])) goto donehc;
	    if (clength == 0 && wcs[i] != L'.' && !iswalpha(wcs[i]))
		goto donehc;
	    else if (wcs[i] != L'.' && !iswalnum(wcs[i])) goto donehc;
	}
    }

    if (clength+strlen(text) > BOOSTED_BUF_SIZE - MB_CUR_MAX - 1) {
	warning("dataentry: expression too long");
	goto donehc;
    }

    /* as originally written, this left an undefined byte at
       the end of bufp, followed by a zero byte; luckily, the storage
       pointed to by bufp had already been zeroed, so the undefined
       byte was in fact zero.  */
    strcpy(bufp, text);
    bufp += (j = (int) strlen(text));
    clength += j;
    printstring(DE, buf, clength, DE->crow, DE->ccol, 1);
    return;

 donehc:
    bell();
}

static void printlabs(DEstruct DE)
{
    char clab[15];
    const char *p;
    int i;

    for (i = DE->colmin; i <= DE->colmax; i++) {
	p = get_col_name(DE, i);
	printstring(DE, p, (int) strlen(p), 0, i - DE->colmin + 1, 0);
    }
    for (i = DE->rowmin; i <= DE->rowmax; i++) {
	sprintf(clab, DE->labform, i);
	printstring(DE, clab, (int) strlen(clab), i - DE->rowmin + 1, 0, 0);
    }
}

	       /* ================ X11-specific ================ */

/* find out whether the button click was in the quit box */
static int checkquit(int xw)
{
    if (xw > box_coords[1] && xw < box_coords[0]) return 1;
    if (xw > box_coords[3] && xw < box_coords[2]) return 2;
    if (xw > box_coords[5] && xw < box_coords[4]) return 3;
    return 0;
}

/* when a buttonpress event happens find the square that is being
   pointed to if the pointer is in the header we need to see if the
   quit button was pressed and if so quit. This is done by having
   findcell return an int which is one if we should quit and zero
   otherwise */

static int findcell(DEstruct DE)
{

    int xw, yw, xr, yr, wcol=0, wrow, i, w;
    unsigned int keys;
    Window root, child;

    closerect(DE);
    XQueryPointer(iodisplay, DE->iowindow, &root, &child,
		  &xr, &yr, &xw, &yw, &keys);

    if (keys & Button1Mask) { /* left click */

	/* check to see if the click was in the header */

	if (yw < DE->hht + DE->bwidth) {
	    i =  checkquit(xw);
	    if (i == 1) return 1;
	    else if (i == 2) copycell(DE);
	    else if (i == 3) pastecell(DE, DE->crow, DE->ccol);
	    return 0;
	}


	/* see if it is in the row labels */
	if (xw < DE->bwidth + DE->boxw[0]) {
	    bell();
	    highlightrect(DE);
	    return 0;
	}
	/* translate to box coordinates */
	wrow = (yw - DE->bwidth - DE->hht) / DE->box_h;
	w = DE->bwidth + DE->boxw[0];
	for (i = 1; i <= DE->nwide; i++)
	    if((w += BOXW(i+DE->colmin-1)) > xw) {
		wcol = i;
		break;
	    }

	/* next check to see if it is in the column labels */

	if (yw < DE->hht + DE->bwidth + DE->box_h) {
	    if (xw > DE->bwidth + DE->boxw[0])
		popupmenu(DE, xr, yr, wcol, wrow);
	    else {
		highlightrect(DE);
		bell();
	    }
	} else if (wrow > DE->nhigh - 1 || wcol > DE->nwide -1) {
		/* off the grid */
		highlightrect(DE);
		bell();
	} else if (wcol != DE->ccol || wrow != DE->crow) {
	    DE->ccol = wcol;
	    DE->crow = wrow;
	}
    }
    if (keys & Button2Mask) { /* Paste */
	int row, col = 0;

	if (yw < DE->hht + DE->bwidth || xw < DE->bwidth + DE->boxw[0])
	    return 0;

	/* translate to box coordinates */
	row = (yw - DE->bwidth - DE->hht) / DE->box_h;
	w = DE->bwidth + DE->boxw[0];
	for (i = 1; i <= DE->nwide; i++)
	    if ((w += BOXW(i+DE->colmin-1)) > xw) {
		col = i;
		break;
	    }
	pastecell(DE, row, col);
    }
    highlightrect(DE);
    return 0;
}


/* Event Loop Functions */
#define mouseDown	ButtonPress
#define keyDown		KeyPress
#define activateEvt	MapNotify
#define updateEvt	Expose

static void eventloop(DEstruct DE)
{
    int done;
    DEEvent ioevent;
    caddr_t temp;

    done = 0;
    while (done == 0) {
	XNextEvent(iodisplay, &ioevent);
	XFindContext(iodisplay, ioevent.xany.window, deContext, &temp);
	if ((DEstruct) temp != DE) { /* so a View window */
	    if (WhichEvent(ioevent) == Expose)
		drawwindow((DEstruct) temp);
	} else {
	    if (XFilterEvent(&ioevent, None)){
		if(ioic){
		    XSetICFocus(ioic);
		    if (ioim_style & XIMPreeditPosition)
			calc_pre_edit_pos(DE);
		}
		continue;
	    }

	    switch (WhichEvent(ioevent)) {
	    case keyDown:/* KeyPress */
		doSpreadKey(DE, 0, &ioevent);
		break;
	    case Expose:
		while(XCheckTypedEvent(iodisplay, Expose, &ioevent))
		    ;
		/*
		 * XIM on  - KeyPress - Expose
		 * XIM off - KeyPress - KeyRelease
		 * colname change XIM on mode. type Backspace.
		 */
		if(DE->crow == 0){
		    drawwindow(DE);
		    printstring(DE, buf, clength, DE->crow, DE->ccol, 1);
		} else {
		    closerect(DE);
		    drawwindow(DE);
		    cell_cursor_init(DE);
		}
		break;
	    case activateEvt:/* MapNotify */
		closerect(DE);
		drawwindow(DE);
		cell_cursor_init(DE);
		break;
	    case mouseDown:/* ButtonPress */
		if(DE->isEditor) {
		    done  = doMouseDown(DE, &ioevent);
		    cell_cursor_init(DE);
		}
		break;
	    case MappingNotify:
		RefreshKeyboardMapping(&ioevent);
		break;
	    case ConfigureNotify:
		while(XCheckTypedEvent(iodisplay, ConfigureNotify, &ioevent))
		    ;
		doConfigure(DE, &ioevent);
		cell_cursor_init(DE);
		break;
	    case ClientMessage:
		if(ioevent.xclient.message_type == _XA_WM_PROTOCOLS
		   && ioevent.xclient.data.l[0] == DE->prot) {
		    /* user clicked on `close' aka `destroy' */
		    done = 1;
		}
		break;
	    }
	}
    }
}

static void R_ProcessX11Events(void *data)
{
    caddr_t temp;
    DEstruct DE = NULL;
    DEEvent ioevent;
    int done = 0;

    while (nView && XPending(iodisplay)) {
	XNextEvent(iodisplay, &ioevent);
	XFindContext(iodisplay, ioevent.xany.window, deContext, &temp);
	DE = (DEstruct) temp;
	switch (WhichEvent(ioevent)) {
	case keyDown:/* KeyPress */
	    doSpreadKey(DE, 0, &ioevent);
	    break;
	case Expose:
	    while(XCheckTypedEvent(iodisplay, Expose, &ioevent))
		;
	    drawwindow(DE);
	    break;
	case MappingNotify:
	    RefreshKeyboardMapping(&ioevent);
	    break;
	case ConfigureNotify:
	    while(XCheckTypedEvent(iodisplay, ConfigureNotify, &ioevent))
		;
	    doConfigure(DE, &ioevent);
	    cell_cursor_init(DE);
	    break;
	case activateEvt:/* MapNotify */
	    break;
	case ClientMessage:
	    if(ioevent.xclient.message_type == _XA_WM_PROTOCOLS
	       && ioevent.xclient.data.l[0] == DE->prot) {
		/* user clicked on `close' aka `destroy' */
		done = 1;
	    }
	    break;
	}
    }
    if(done) {
	R_ReleaseObject(DE->lens);
	R_ReleaseObject(DE->work);
	closewin(DE);
	free(DE);
	nView--;
	if(nView == 0) {
	    /* NB: this is removing the handler that is currently
	       being used: only OK to free here in R > 2.8.0 */
	    removeInputHandler(&R_InputHandlers,
			       getInputHandler(R_InputHandlers,fdView));
	    fdView = -1;
	    if(font_set) {
		XFreeFontSet(iodisplay, font_set);
		font_set = NULL;
	    }
	    XCloseDisplay(iodisplay);
	    iodisplay = NULL;
	}

    }
}

static int doMouseDown(DEstruct DE, DEEvent * event)
{
    return findcell(DE);
}

static void doSpreadKey(DEstruct DE, int key, DEEvent * event)
{
    KeySym iokey;
    char *text = "";

    iokey = GetKey(event);
    if(DE->isEditor) text = GetCharP(event);

    if (CheckControl(event))
	doControl(DE, event);
    else if ((iokey == XK_Return)  || (iokey == XK_KP_Enter) ||
	     (iokey == XK_Linefeed)|| (iokey == XK_Down))
	advancerect(DE, DOWN);
    else if (iokey == XK_Left)
	advancerect(DE, LEFT);
    else if (iokey == XK_Right)
	advancerect(DE, RIGHT);
    else if (iokey == XK_Up)
	advancerect(DE, UP);
#ifdef XK_Page_Up
    else if (iokey == XK_Page_Up) {
	int i = DE->rowmin - DE->nhigh + 2;
	jumpwin(DE, DE->colmin, max(1, i));
	cell_cursor_init(DE);
    }
#elif defined(XK_Prior)
    else if (iokey == XK_Prior) {
	int i = DE->rowmin - DE->nhigh + 2;
	jumpwin(DE, DE->colmin, max(1, i));
	cell_cursor_init(DE);
    }
#endif
#ifdef XK_Page_Down
    else if (iokey == XK_Page_Down) {
	if(DE->isEditor)
	    jumpwin(DE, DE->colmin, DE->rowmax);
	else {
	    int i = DE->ymaxused - DE->nhigh + 2;
            jumpwin(DE, DE->colmin, min(i, DE->rowmax));
	}
	cell_cursor_init(DE);
    }
#elif defined(XK_Next)
    else if (iokey == XK_Next) {
	if(DE->isEditor)
	    jumpwin(DE, DE->colmin, DE->rowmax);
	else {
	    int i = DE->ymaxused - DE->nhigh + 2;
            jumpwin(DE, DE->colmin, min(i, DE->rowmax));
	}
	cell_cursor_init(DE);
    }
#endif
    else if (DE->isEditor && (iokey == XK_BackSpace || iokey == XK_Delete)) {
	if (clength > 0) {
	    int last_w ;
	    last_w = last_wchar_bytes(NULL);
	    clength -= last_w;
	    bufp -= last_w;
	    *bufp = '\0';
	    CellModified = TRUE;
	    printstring(DE, buf, clength, DE->crow, DE->ccol, 1);
	} else bell();
    }
    else if (iokey == XK_Tab) {
	if(CheckShift(event)) advancerect(DE, LEFT);
	else advancerect(DE, RIGHT);
    }
    else if (iokey == XK_Home) {
	jumpwin(DE, 1, 1);
	downlightrect(DE);
	DE->crow = DE->ccol = 1;
	highlightrect(DE);
	cell_cursor_init(DE);
    }
    else if (iokey == XK_End) {
	int i = DE->ymaxused - DE->nhigh + 2, j, w = 0 ;
	/* Try to work out which cols we can fit in */
	for(j = DE->xmaxused;j >= 0; j--) {
	    w += BOXW(j);
	    if(w > DE->fullwindowWidth) break;
	}
	jumpwin(DE, min(1 + max(j, 0), DE->xmaxused), max(i, 1));
	downlightrect(DE);
	DE->crow = DE->ymaxused - DE->rowmin + 1;
	DE->ccol = DE->xmaxused - DE->colmin + 1;
	highlightrect(DE);
	cell_cursor_init(DE);
    }
    else if (IsModifierKey(iokey)) {
    }
    else if(DE->isEditor) {
	handlechar(DE, text);
    }
}


static int WhichEvent(DEEvent ioevent)
{
    return ioevent.type;
}

static KeySym GetKey(DEEvent * event)
{
    char text[1];
    KeySym iokey;

    XLookupString((XKeyEvent *)event, text, 1, &iokey, NULL);
    return iokey;
}

static char *GetCharP(DEEvent * event)
{
    static char text[BOOSTED_BUF_SIZE];
    KeySym iokey;

    memset(text,0,sizeof(text));

    if(mbcslocale) {
#ifdef HAVE_XUTF8LOOKUPSTRING
	if(utf8locale)
	    Xutf8LookupString(ioic, (XKeyEvent *)event,
			      text, sizeof(text) - clength,
			      &iokey, &status);
	else
#endif
	    XmbLookupString(ioic, (XKeyEvent *)event,
			    text, sizeof(text) - clength,
			    &iokey, &status);
	/* FIXME check the return code */
	if(status == XBufferOverflow)
	    warning("dataentry: expression too long");
    } else
	XLookupString((XKeyEvent *)event,
		      text, sizeof(text) - clength,
		      &iokey, NULL);
    return text;
}

static int CheckControl(DEEvent * event)
{
    return (*event).xkey.state & ControlMask;
}

static int CheckShift(DEEvent * event)
{
    return (*event).xkey.state & ShiftMask;
}

static void doControl(DEstruct DE, DEEvent * event)
{
    int i;
    char text[1];
    KeySym iokey;

    (*event).xkey.state = 0;
    XLookupString((XKeyEvent *)event, text, 1, &iokey, NULL);
    /* one row overlap when scrolling: top line <--> bottom line */
    switch (text[0]) {
	case 'b':
	    i = DE->rowmin - DE->nhigh + 2;
	    jumpwin(DE, DE->colmin, max(1, i));
	    break;
	case 'f':
	    jumpwin(DE, DE->colmin, DE->rowmax);
	    break;
	case 'l':
	    closerect(DE);
	    for (i = 1 ; i <= min(100, DE->xmaxused); i++)
		DE->boxw[i] = get_col_width(DE, i);
	    closerect(DE);
	    drawwindow(DE);
	    break;
    }
    cell_cursor_init(DE);
}


static void doConfigure(DEstruct DE, DEEvent * event)
{
    if ((DE->fullwindowWidth != (*event).xconfigure.width) ||
	(DE->fullwindowHeight != (*event).xconfigure.height)) {
	closerect(DE);
	drawwindow(DE);
    }
}

static void RefreshKeyboardMapping(DEEvent * event)
{
    XRefreshKeyboardMapping((XMappingEvent *)event);
}

/* Initialize/Close Windows */

void closewin(DEstruct DE)
{
    XFreeGC(iodisplay, DE->iogc);
    if(mbcslocale  && DE->isEditor) {
	XDestroyIC(ioic);
	XCloseIM(ioim);
    }
    XDestroyWindow(iodisplay, DE->iowindow);
    /* XCloseDisplay(iodisplay); */
    Rsync(DE);
}

#define USE_Xt 1

#ifdef USE_Xt
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
typedef struct gx_device_X_s {
    Pixel background, foreground, borderColor;
    Dimension borderWidth;
    String geometry;
} gx_device_X;

/* (String) casts are here to suppress warnings about discarding `const' */
#define RINIT(a,b,t,s,o,it,n)\
  {(String)(a), (String)(b), (String)t, sizeof(s),\
   XtOffsetOf(gx_device_X, o), (String)it, (n)}
#define rpix(a,b,o,n)\
  RINIT(a,b,XtRPixel,Pixel,o,XtRString,(XtPointer)(n))
#define rdim(a,b,o,n)\
  RINIT(a,b,XtRDimension,Dimension,o,XtRImmediate,(XtPointer)(n))
#define rstr(a,b,o,n)\
  RINIT(a,b,XtRString,String,o,XtRString,(char*)(n))

static XtResource x_resources[] = {
    rpix(XtNforeground, XtCForeground, foreground, "XtDefaultForeground"),
    rpix(XtNbackground, XtCBackground, background, "XtDefaultBackground"),
    rstr(XtNgeometry, XtCGeometry, geometry, NULL),
};

static const int x_resource_count = XtNumber(x_resources);
static gx_device_X xdev;
#endif

/* NB: Keep this in sync with similar handler in devX11.c */
static int R_X11Err(Display *dsp, XErrorEvent *event)
{
    char buff[1000];
    /* for tcl/tk */
    if (event->error_code == BadWindow) return 0;

    XGetErrorText(dsp, event->error_code, buff, 1000);
    warning(_("X11 protocol error: %s"), buff);
    return 0;
}


static int R_X11IOErr(Display *dsp)
{
    error("X11 fatal IO error: please save work and shut down R");
    return 0; /* but should never get here */
}

/* set up the window, print the grid and column/row labels */

static Rboolean initwin(DEstruct DE, const char *title) /* TRUE = Error */
{
    int i, twidth, w, minwidth, labdigs;
    int ioscreen;
    unsigned long iowhite, ioblack;
    char digits[] = "123456789.0";
    char             *font_name="9x15";
    Window root;
    XEvent ioevent;
    XSetWindowAttributes winattr;
    XWindowAttributes attribs;
    XSizeHints *hint;
    unsigned long fevent=0UL;
    int j,k;
    XVaNestedList   xva_nlist;
    XPoint xpoint;

    strcpy(copycontents, "");

    if (!XSupportsLocale ())
	warning("locale not supported by Xlib: some X ops will operate in C locale");
    if (!XSetLocaleModifiers ("")) warning("X cannot set locale modifiers");

    if(!iodisplay) {
	if ((iodisplay = XOpenDisplay(NULL)) == NULL) {
	    warning("unable to open display");
	    return TRUE;
	}
	deContext = XUniqueContext();
	XSetErrorHandler(R_X11Err);
	XSetIOErrorHandler(R_X11IOErr);
    }


    /* Get Font Loaded if we can */

    if(mbcslocale) {
	int  missing_charset_count;
	char **missing_charset_list;
	char *def_string;
	char opt_fontset_name[512];

	/* options("X11fonts")[1] read font name */
	SEXP opt = GetOption1(install("X11fonts"));
	if(isString(opt)) {
	    const char *s = CHAR(STRING_ELT(opt, 0));
	    sprintf(opt_fontset_name, s, "medium", "r", 12);
	} else strcpy(opt_fontset_name, fontset_name);

	if(font_set == NULL) {
	    font_set = XCreateFontSet(iodisplay, opt_fontset_name,
				      &missing_charset_list,
				      &missing_charset_count, &def_string);
	    if (missing_charset_count) XFreeStringList(missing_charset_list);
	}
	if (font_set == NULL) {
	    warning("unable to create fontset %s", opt_fontset_name);
	    return TRUE; /* ERROR */
	}
    } else {
	DE->font_info = XLoadQueryFont(iodisplay, font_name);
	if (DE->font_info == NULL) {
	    warning("unable to load font %s", font_name);
	    return TRUE; /* ERROR */
	}
    }

    /* find out how wide the input boxes should be and set up the
       window size defaults */

    DE->nboxchars = asInteger(GetOption1(install("de.cellwidth")));
    if (DE->nboxchars == NA_INTEGER || DE->nboxchars < 0) DE->nboxchars = 0;

    twidth = textwidth(DE, digits, (int) strlen(digits));

    if (DE->nboxchars > 0) twidth = (twidth * DE->nboxchars)/10;
    DE->box_w = twidth + 4;
    if(mbcslocale) {
	XFontSetExtents *extent = XExtentsOfFontSet(font_set);
	char **ml;
	DE->box_h = (extent->max_logical_extent.height)
	    + (extent->max_logical_extent.height / 5) + 4;
	font_set_cnt = XFontsOfFontSet(font_set, &fs_list, &ml);
	DE->text_offset = 2 + fs_list[0]->max_bounds.descent;
    } else {
	DE->box_h = DE->font_info->max_bounds.ascent
	    + DE->font_info->max_bounds.descent + 4;
	DE->text_offset = 2 + DE->font_info->max_bounds.descent;
    }
    DE->windowHeight = 26 * DE->box_h + DE->hht + 2;
    /* this used to presume 4 chars sufficed for row numbering */
    labdigs = max(3, 1+ (int) floor(log10((double)DE->ymaxused)));
    sprintf(DE->labform, "%%%dd", labdigs);
    DE->boxw[0] = (int)( 0.1*labdigs*textwidth(DE, "0123456789", 10)) +
	textwidth(DE, " ", 1) + 8;
    for(i = 1; i < 100; i++) DE->boxw[i] = get_col_width(DE, i);

    /* try for a window width that covers all the columns, or is around
       800 pixels */
    w = DE->windowWidth = 0;
    for(i = 0; i <= DE->xmaxused; i++) {
	w += DE->boxw[i];
	if(w > 800) {
	    DE->windowWidth = w - DE->boxw[i];
	    break;
	}
    }
    if(DE->windowWidth == 0) DE->windowWidth = w;
    DE->windowWidth += 2;
    /* allow enough width for buttons */
    minwidth = (int)(7.5 * textwidth(DE, "Paste", 5));
    if(DE->windowWidth < minwidth) DE->windowWidth = minwidth;

    ioscreen = DefaultScreen(iodisplay);
    iowhite = WhitePixel(iodisplay, ioscreen);
    ioblack = BlackPixel(iodisplay, ioscreen);


    hint = XAllocSizeHints();

    hint->x = 0;
    hint->y = 0;
    hint->width = DE->windowWidth;
    hint->height = DE->windowHeight;
    hint->flags = PPosition | PSize;
    /*
     * not necessary?
    hints.flags = InputHint;
    hints.input = True;
    */
    root = DefaultRootWindow(iodisplay);

#ifdef USE_Xt
    {
	XtAppContext app_con;
	Widget toplevel;
	Display *xtdpy;
	int zero = 0;

	XtToolkitInitialize();
	app_con = XtCreateApplicationContext();
	/* XtAppSetFallbackResources(app_con, x_fallback_resources);*/
	xtdpy = XtOpenDisplay(app_con, NULL, "r_dataentry", "R_dataentry",
			      NULL, 0, &zero, NULL);
	toplevel = XtAppCreateShell(NULL, "R_dataentry",
				    applicationShellWidgetClass,
				    xtdpy, NULL, 0);
	XtGetApplicationResources(toplevel, (XtPointer) &xdev,
				  x_resources,
				  x_resource_count,
				  NULL, 0);
	XtDestroyWidget(toplevel);
	XtCloseDisplay(xtdpy);
	XtDestroyApplicationContext(app_con);
	if (xdev.geometry != NULL) {
	    char gstr[40];
	    int bitmask;

	    sprintf(gstr, "%dx%d+%d+%d", hint->width,
		    hint->height, hint->x, hint->y);
	    bitmask = XWMGeometry(iodisplay, DefaultScreen(iodisplay),
				  xdev.geometry, gstr,
				  1,
				  hint,
				  &hint->x, &hint->y,
				  &hint->width, &hint->height,
				  &hint->win_gravity);

	    if (bitmask & (XValue | YValue))
		hint->flags |= USPosition;
	    if (bitmask & (WidthValue | HeightValue))
		hint->flags |= USSize;
	}
	ioblack = xdev.foreground;
	iowhite = xdev.background;
    }
#endif
    if ((DE->iowindow = XCreateSimpleWindow(
	     iodisplay,
	     root,
	     hint->x,
	     hint->y,
	     hint->width,
	     hint->height,
	     DE->bwidth,
	     ioblack,
	     iowhite)) == 0) {
	warning("unable to open window for data editor");
	return TRUE;
    }

    /*
    XSetStandardProperties(iodisplay, DE->iowindow, ioname, ioname, None,
			   (char **)NULL, 0, iohint);
    */
    XSetWMNormalHints(iodisplay, DE->iowindow, hint);
    XFree(hint);


    winattr.backing_store = WhenMapped;
    XChangeWindowAttributes(iodisplay, DE->iowindow, CWBackingStore,
			    &winattr);

    /* set up protocols so that window manager sends */
    /* me an event when user "destroys" window */
    if(!_XA_WM_PROTOCOLS)
	_XA_WM_PROTOCOLS = XInternAtom(iodisplay, "WM_PROTOCOLS", 0);
    DE->prot = XInternAtom(iodisplay, "WM_DELETE_WINDOW", 0);
    XSetWMProtocols(iodisplay, DE->iowindow, &DE->prot, 1);
    /*
     * not necessary
    XSetWMHints(iodisplay, DE->iowindow, &hints);
     */

    DE->iogc = XCreateGC(iodisplay, DE->iowindow, 0, 0);

    if(mbcslocale && DE->isEditor) {
	ioim = XOpenIM(iodisplay, NULL, NULL, NULL);
	if(!ioim) {
	    XDestroyWindow(iodisplay, DE->iowindow);
	    XCloseDisplay(iodisplay);
	    warning("unable to open X Input Method");
	    return TRUE;
	}

	/* search supported input style */
	XGetIMValues(ioim, XNQueryInputStyle, &ioim_styles,NULL);
	for(i = 0; i < ioim_styles->count_styles; i++) {
	    for(j = 0; preedit_styles[j]; j++){
		for(k = 0; status_styles[k]; k++){
		    ioim_style = (preedit_styles[j] | status_styles[k]);
		    if( ioim_styles->supported_styles[i] == ioim_style) {
			goto loop_out;
		    }
		}
	    }
	}
    loop_out:

	/* create input context */
	xpoint.x = 0; xpoint.y=0;
	xva_nlist = XVaCreateNestedList(0, XNFontSet, font_set,
					XNSpotLocation, &xpoint, NULL);

	ioic = XCreateIC(ioim,
			 XNInputStyle, ioim_style,
			 XNClientWindow,DE->iowindow,
			 XNFocusWindow,DE->iowindow,
			 XNPreeditAttributes, xva_nlist,
			 XNStatusAttributes, xva_nlist,
			 NULL);
	XFree(xva_nlist);
	if(!ioic) {
	    XCloseIM(ioim);
	    XDestroyWindow(iodisplay, DE->iowindow);
	    XCloseDisplay(iodisplay);
	    warning("unable to open X Input Context");
	    return TRUE;
	}

	/* get XIM processes event. */
	XGetICValues(ioic, XNFilterEvents, &fevent, NULL);
    }

    if(!mbcslocale)
	XSetFont(iodisplay, DE->iogc, DE->font_info->fid);

    XSetBackground(iodisplay, DE->iogc, iowhite);
    XSetForeground(iodisplay, DE->iogc, ioblack);
    XSetLineAttributes(iodisplay, DE->iogc, 1, LineSolid, CapRound,
		       JoinRound);

    /*
    XSelectInput(iodisplay, DE->iowindow,
		 ButtonPressMask | KeyPressMask
		 | ExposureMask | StructureNotifyMask | fevent);
    */

    XSelectInput(iodisplay, DE->iowindow,
		 ButtonPressMask
		 | KeyPressMask
		 | StructureNotifyMask
		 | ExposureMask
		 | EnterWindowMask
		 | LeaveWindowMask
		 | fevent);
    XMapRaised(iodisplay, DE->iowindow);

    /* now set up the menu-window, for now use the same text
       dimensions as above */

    /* font size consideration */
    for(i = 0; i < (sizeof(menu_label)/sizeof(char *)); i++)
	twidth = (twidth<textwidth(DE, menu_label[i],(int) strlen(menu_label[i]))) ?
	    textwidth(DE, menu_label[i],(int) strlen(menu_label[i])) : twidth;

    menuwindow = XCreateSimpleWindow(iodisplay, root, 0, 0, twidth,
				     4 * DE->box_h, 2, ioblack, iowhite);
    for (i = 0; i < 4; i++) {
	menupanes[i] = XCreateSimpleWindow(iodisplay, menuwindow, 0,
					   DE->box_h * i, twidth, DE->box_h,
					   1, ioblack, iowhite);
	XSelectInput(iodisplay, menupanes[i],
		     ButtonPressMask | ButtonReleaseMask | ExposureMask
		     );
    }

    /* XMapSubwindows(iodisplay, menuwindow); */


    XStoreName(iodisplay, DE->iowindow, title);
    winattr.override_redirect = True;
    XChangeWindowAttributes(iodisplay, menuwindow,
			    CWBackingStore | CWOverrideRedirect, &winattr);
    Rsync(DE);

    /* this next sequence makes sure the window is up and ready before
       you start drawing in it */

    XNextEvent(iodisplay, &ioevent);
    if (ioevent.xany.type == Expose) {
	while (ioevent.xexpose.count)
	    XNextEvent(iodisplay, &ioevent);
    }
    XGetWindowAttributes(iodisplay, DE->iowindow, &attribs);
    DE->bwidth = attribs.border_width;
    DE->fullwindowWidth = attribs.width;
    DE->fullwindowHeight = attribs.height;


    /* set the active rectangle to be the upper left one */
    DE->crow = 1;
    DE->ccol = 1;
    CellModified = FALSE;
    XSaveContext(iodisplay, DE->iowindow, deContext, (caddr_t) DE);
    return FALSE;/* success */
}

/* MAC/X11 BASICS */

static void bell()
{
    XBell(iodisplay, 20);
}

static void cleararea(DEstruct DE, int xpos, int ypos, int width, int height)
{
    XClearArea(iodisplay, DE->iowindow, xpos, ypos, width, height, 0);
}

static void clearwindow(DEstruct DE)
{
    XClearWindow(iodisplay, DE->iowindow);
}

static void copyarea(DEstruct DE, int src_x, int src_y, int dest_x, int dest_y)
{
    int mx = max(src_x, dest_x), my = max(src_y, dest_y);
    XCopyArea(iodisplay, DE->iowindow, DE->iowindow, DE->iogc,
	      src_x, src_y,
	      DE->fullwindowWidth - mx, DE->fullwindowHeight - my,
	      dest_x, dest_y);
    Rsync(DE);
}

static void copyH(DEstruct DE, int src_x, int dest_x, int width)
{
    XCopyArea(iodisplay, DE->iowindow, DE->iowindow, DE->iogc,
	      src_x+DE->bwidth, DE->hht,
	      width, DE->windowHeight+1, dest_x+DE->bwidth, DE->hht);
}

static void drawrectangle(DEstruct DE,
			  int xpos, int ypos, int width, int height,
			  int lwd, int fore)
{
#ifdef USE_Xt
    if (fore == 0)
	XSetForeground(iodisplay, DE->iogc, xdev.background);
    else
	XSetForeground(iodisplay, DE->iogc, xdev.foreground);
#else
    if (fore == 0)
	XSetForeground(iodisplay, DE->iogc,
		       WhitePixel(iodisplay, DefaultScreen(iodisplay)));
    else
	XSetForeground(iodisplay, DE->iogc,
		       BlackPixel(iodisplay, DefaultScreen(iodisplay)));
#endif
    XSetLineAttributes(iodisplay, DE->iogc, lwd, LineSolid,
		       CapRound, JoinRound);
    XDrawRectangle(iodisplay, DE->iowindow, DE->iogc, xpos, ypos,
		   width, height);
}

static void drawtext(DEstruct DE, int xpos, int ypos, char *text, int len)
{
    if(mbcslocale)
#ifdef HAVE_XUTF8DRAWIMAGESTRING
	if(utf8locale)
	    Xutf8DrawImageString(iodisplay, DE->iowindow, font_set,
				 DE->iogc, xpos, ypos,text, len);
	else
#endif
	    XmbDrawImageString(iodisplay, DE->iowindow, font_set,
			       DE->iogc, xpos, ypos,text, len);
    else
	XDrawImageString(iodisplay, DE->iowindow, DE->iogc,
			 xpos, ypos, text, len);
    Rsync(DE);
}

static void Rsync(DEstruct DE)
{
    XSync(iodisplay, 0);
}

static int textwidth(DEstruct DE, const char *text, int nchar)
{
    int ans;
    char *buf = CallocCharBuf(nchar);
    strncpy(buf, text, nchar);
    if(mbcslocale) {
#ifdef HAVE_XUTF8TEXTESCAPEMENT
	if (utf8locale)
	    ans = Xutf8TextEscapement(font_set, buf, nchar);
	else
#endif
	    ans = XmbTextEscapement(font_set, buf, nchar);
	Free(buf);
	return ans;
    }
    ans = XTextWidth(DE->font_info, buf, nchar);
    Free(buf);
    return ans;
}

/* Menus */

void popupmenu(DEstruct DE, int x_pos, int y_pos, int col, int row)
{
    int i, button, popupcol = col + DE->colmin - 1;
    const char *name;
    char clab[20];
    XEvent event;
    Window selected_pane;
    SEXP tvec;

    XMoveWindow(iodisplay, menuwindow, x_pos, y_pos);
    XMapSubwindows(iodisplay, menuwindow);
    XMapRaised(iodisplay, menuwindow);

    /* now fill in the menu panes with the correct information */

    if (popupcol > DE->xmaxused) {
	/* extend work, names and lens */
	REPROTECT(DE->work = lengthgets(DE->work, popupcol), DE->wpi);
	REPROTECT(DE->names = lengthgets(DE->names, popupcol), DE->npi);
	for (i = DE->xmaxused+1; i < popupcol; i++) {
	    sprintf(clab, "var%d", i + 1);
	    SET_STRING_ELT(DE->names, i, mkChar(clab));
	}
	REPROTECT(DE->lens = lengthgets(DE->lens, popupcol), DE->lpi);
	DE->xmaxused = popupcol;
    }
    tvec = VECTOR_ELT(DE->work, popupcol - 1);
    name = CHAR(STRING_ELT(DE->names, popupcol - 1));
    if(mbcslocale)
#ifdef HAVE_XUTF8DRAWSTRING
	if(utf8locale)
	    Xutf8DrawString(iodisplay,
			    menupanes[0],
			    font_set, DE->iogc, 3, DE->box_h - 3, name,
			    (int) strlen(name));
	else
#endif
	    XmbDrawString(iodisplay,
			  menupanes[0],
			  font_set, DE->iogc, 3, DE->box_h - 3, name,
			  (int) strlen(name));
    else
	XDrawString(iodisplay,
		    menupanes[0], DE->iogc, 3, DE->box_h - 3, name,
		    (int) strlen(name));
    for (i = 1; i < 4; i++)
	if(mbcslocale)
#ifdef HAVE_XUTF8DRAWSTRING
	    if(utf8locale)
		Xutf8DrawString(iodisplay,
				menupanes[i],
				font_set, DE->iogc, 3, DE->box_h - 3,
				menu_label[i - 1], (int) strlen(menu_label[i - 1]));
	    else
#endif
		XmbDrawString(iodisplay,
			      menupanes[i],
			      font_set, DE->iogc, 3, DE->box_h - 3,
			      menu_label[i - 1], (int) strlen(menu_label[i - 1]));
	else
	    XDrawString(iodisplay,
			menupanes[i], DE->iogc, 3, DE->box_h - 3,
			menu_label[i - 1], (int) strlen(menu_label[i - 1]));

    if (isNull(tvec) || TYPEOF(tvec) == REALSXP)
	if(mbcslocale)
#ifdef HAVE_XUTF8DRAWSTRING
	    if(utf8locale)
		Xutf8DrawString(iodisplay,
				menupanes[1],
				font_set, DE->iogc, 0, DE->box_h - 3,
				"*", 1);
	    else
#endif
		XmbDrawString(iodisplay,
			      menupanes[1],
			      font_set, DE->iogc, 0, DE->box_h - 3,
			      "*", 1);
	else
	    XDrawString(iodisplay, menupanes[1], DE->iogc, 0, DE->box_h - 3,
			"*", 1);
    else
	if(mbcslocale)
#ifdef HAVE_XUTF8DRAWSTRING
	    if(utf8locale)
		Xutf8DrawString(iodisplay,
				menupanes[2],
				font_set, DE->iogc, 0, DE->box_h - 3,
				"*", 1);
	    else
#endif
		XmbDrawString(iodisplay,
			      menupanes[2],
			      font_set, DE->iogc, 0, DE->box_h - 3,
			      "*", 1);
	else
	    XDrawString(iodisplay, menupanes[2], DE->iogc, 0, DE->box_h - 3,
			"*", 1);

/*
  start an event loop; we're looking for a button press and a button
  release in the same window
*/

    while (1) {
	XNextEvent(iodisplay, &event);

	/* event is processed with input method */

	if (event.type == ButtonPress) {
	    button = event.xbutton.button;
	    selected_pane = event.xbutton.window;
	    for (i = 0; selected_pane != menupanes[i]; i++)
		if (i >= 3) goto done;
	    while (1) {
		while (XCheckTypedEvent(iodisplay, ButtonPress, &event));
		XMaskEvent(iodisplay, ButtonReleaseMask, &event);
		if (event.xbutton.button == button)
		    break;
	    }
	    if (selected_pane == event.xbutton.window) {
		for (i = 0; selected_pane != menupanes[i]; i++);
		switch (i) {
		case 0:
		    bell();
		    break;
		case 1:
		    if (isNull(tvec))
			SET_VECTOR_ELT(DE->work, popupcol - 1,
				       ssNewVector(REALSXP, 100));
		    else
			SET_VECTOR_ELT(DE->work, popupcol - 1,
				       coerceVector(tvec, REALSXP));
		    goto done;
		case 2:
		    if (isNull(tvec))
			SET_VECTOR_ELT(DE->work, popupcol - 1,
				       ssNewVector(STRSXP, 100));
		    else {
			SET_VECTOR_ELT(DE->work, popupcol - 1,
				       coerceVector(tvec, STRSXP));
		    }

		    goto done;
		case 3:
		    closerect(DE);
		    DE->ccol = col;
		    DE->crow = 0;
		    clearrect(DE);
		    goto done;
		}
	    }
	}
	/* this doesn't work and perhaps I should move it up to the
	   main control loop */
	else if (event.type == Expose) {
	    if (event.xexpose.window == menuwindow) {
		XDrawString(iodisplay, menupanes[0], DE->iogc, 3,
			    DE->box_h - 3, name, (int) strlen(name));
		for (i = 1; i < 4; i++)
		    XDrawString(iodisplay, menupanes[i], DE->iogc, 3,
				DE->box_h - 3,
				menu_label[i - 1], (int) strlen(menu_label[i - 1]));
	    }
	}
    }
done:
    popdownmenu(DE);
    highlightrect(DE);
}

void popdownmenu(DEstruct DE)
{
    XUnmapWindow(iodisplay, menuwindow);
    XUnmapSubwindows(iodisplay, menuwindow);
}

static void copycell(DEstruct DE)
{
  /*
   * whichrow = crow + colmin - 1 => whichrow = crow + rowmin - 1
   *                   ^^^                             ^^^
   */
    int i, whichrow = DE->crow + DE->rowmin - 1,
	whichcol = DE->ccol + DE->colmin -1;
    SEXP tmp;

    if (whichrow == 0) {
	/* won't have  cell here */
    } else {
	strcpy(copycontents, "");
	if (length(DE->work) >= whichcol) {
	    tmp = VECTOR_ELT(DE->work, whichcol - 1);
	    if (tmp != R_NilValue &&
		(i = whichrow - 1) < LENGTH(tmp) ) {
		PrintDefaults();
		if (TYPEOF(tmp) == REALSXP) {
			strncpy(copycontents, EncodeElement(tmp, i, 0, '.'),
				BOOSTED_BUF_SIZE-1);
			copycontents[BOOSTED_BUF_SIZE-1]='\0';
		} else if (TYPEOF(tmp) == STRSXP) {
		    if (STRING_ELT(tmp, i) != ssNA_STRING) {
			strncpy(copycontents, EncodeElement(tmp, i, 0, '.'),
				BOOSTED_BUF_SIZE-1);
			copycontents[BOOSTED_BUF_SIZE-1]='\0';
		    }
		}
	    }
	}
    }
    highlightrect(DE);
}

static void pastecell(DEstruct DE, int row, int col)
{
    downlightrect(DE);
    DE->crow = row; DE->ccol = col;
    if (strlen(copycontents)) {
	strcpy(buf, copycontents);
	clength = (int) strlen(copycontents);
	bufp = buf + clength;
	CellModified = TRUE;
    }
    closerect(DE);
    highlightrect(DE);
}

static void calc_pre_edit_pos(DEstruct DE)
{
    XVaNestedList   xva_nlist;
    XPoint          xpoint;
    int i;
    int w;

    xpoint.x = (short) DE->boxw[0];
    for (i = 1; i < DE->ccol; i++)
	xpoint.x += BOXW(DE->colmin + i - 1);
#ifdef HAVE_XUTF8TEXTESCAPEMENT
    if(utf8locale)
	w = Xutf8TextEscapement(font_set, buf, clength);
    else
#endif
	w = XmbTextEscapement(font_set, buf, clength);
    xpoint.x += (w > BOXW(DE->colmin + DE->ccol - 1)) ?
	BOXW(DE->colmin + DE->ccol - 1) : w;
    xpoint.x += DE->text_offset;
    xpoint.y = (short)(DE->hht + (DE->crow+1) * DE->box_h - DE->text_offset);

    /*
      <FIXME>
      I may depend on implementation of XIM, but I do not obey,
      setting value, and investigation in various implementation
      system is need.
      It is only a problem in an appearance.
    */
    xva_nlist = XVaCreateNestedList(0,
				    XNSpotLocation, &xpoint,
				    XNFontSet, font_set,
				    NULL);
    XSetICValues(ioic, XNPreeditAttributes, xva_nlist, NULL);

    XFree(xva_nlist);
    return;
}

/* last character bytes */
static int last_wchar_bytes(char *str)
{
    wchar_t   wcs[BOOSTED_BUF_SIZE];
    mbstate_t mb_st;
    int cnt;
    char last_mbs[8];
    char *mbs;
    size_t bytes;

    mbs = (str == NULL) ? buf : str;

    memset(wcs, 0 ,sizeof(wcs));
    memset(&mb_st,0, sizeof(mbstate_t));

    if((int)-1 == (cnt = (int)mbsrtowcs(wcs, (const char **)&mbs,
					(int) strlen(mbs), &mb_st))) {
	return 0;
    }
    if(wcs[0] == L'\0') return 0;

    memset(last_mbs, 0, sizeof(last_mbs));
    bytes = wcrtomb(last_mbs, wcs[cnt-1], &mb_st); /* -Wall */
    return (int) bytes;
}

