The R Project SVN R

Rev

Rev 9095 | Blame | Last modification | View Log | Download | RSS feed

/*
 *  R : A Computer Langage for Statistical Data Analysis
 *  Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
 *  Copyright (C) 1998--2000  Robert Gentleman, Ross Ihaka and the
 *                            R Development 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* TODO
   - spreadsheet copy and paste?
 */

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

#include "Defn.h"
#include "Print.h"

#include "graphapp/ga.h"
#include "console.h"
#include "consolestructs.h"
#include "rui.h"

static dataeditor de;
static ConsoleData p;

extern int R_de_up;

#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) (x<100?boxw[x]:box_w)

#define FIELDWIDTH 10

/* Local Function Definitions */

static void advancerect(int);
static void bell();
static void cleararea(int, int, int, int, rgb);
static void clearrect();
static void closerect();
static void clearwindow();
void de_closewin();
static void copyarea(int, int, int, int);
static void copyH(int, int, int);
static void deredraw();
static void eventloop();
static void downlightrect();
static void drawwindow();
static void drawcol(int);
/* static void de_drawline(int, int, int, int);*/
static void de_drawtext(int, int, char *);
static void drawrectangle(int, int, int, int, int, int);
static void drawrow(int);
static void find_coords(int, int, int*, int*);
static void handlechar(char*);
static void highlightrect();
static int  initwin();
static void jumppage(int);
static void jumpwin(int, int);
static void de_popupmenu(int, int, int);
static void printlabs();
static void printrect(int, int);
static void printstring(char*, int, int, int, int);
static void printelt(SEXP, int, int, int);
void de_copy(control c);
void de_paste(control c);


static SEXP inputlist;  /* each element is a vector for that row */
static SEXP ssNA_STRING;
static double ssNA_REAL;


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] = ssNA_REAL;
    else if (type == STRSXP)
        STRING(tvec)[j] = STRING(ssNA_STRING)[0];
    LEVELS(tvec) = 0;
    return (tvec);
}
/* Global variables needed for the graphics */

static int box_w;                       /* width of a box */
static int boxw[100];                   /* widthes of cells */
static int box_h;                       /* height of a box */
static int windowWidth;                 /* current width of the window */
static int windowHeight;                /* current height of the window */
static int currentexp;                  /* boolean: whether an cell is active */
static int crow;                        /* current row */
static int ccol;                        /* current column */
static int nwide, nhigh;
static int colmax, colmin, rowmax, rowmin;
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 char buf[30];
static char *bufp;
static int bwidth;          /* width of the border */
static int hwidth;          /* width of header  */
static int text_xoffset, text_yoffset;
static int CellModified;
static int CellEditable;
static field celledit;
static int newcol;
static int xmaxused, ymaxused;
static int oldWIDTH=0, oldHEIGHT=0;


char *demenuitems[20];


void R_ProcessEvents(); /* in system.c */

static void eventloop()
{
    while (R_de_up) R_ProcessEvents();
}

SEXP do_dataentry(SEXP call, SEXP op, SEXP args, SEXP rho)
{
    SEXP tvec2, tvec, colmodes, indata;
    SEXPTYPE type;
    int i, j,len, nprotect, tmp;
    RCNTXT cntxt;

    nprotect = 0;/* count the PROTECT()s */
    PROTECT(indata = VectorToPairList(CAR(args))); nprotect++;
    PROTECT(colmodes = VectorToPairList(CADR(args))); nprotect++;

    if (!isList(indata) || !isList(colmodes))
    errorcall(call, "invalid argument");

    /* initialize the constants */

    bufp = buf;
    ne = 0;
    currentexp = 0;
    nneg = 0;
    ndecimal = 0;
    clength = 0;
    ccol = 1;
    crow = 1;
    colmin = 1;
    rowmin = 1;
    ssNA_REAL = -NA_REAL;
    tvec = allocVector(REALSXP, 1);
    REAL(tvec)[0] = ssNA_REAL;
    PROTECT(ssNA_STRING = coerceVector(tvec, STRSXP)); nprotect++;
    bwidth = 0;
    hwidth = 0;

    /* setup inputlist  */

    if (indata != R_NilValue) {
    xmaxused = 0; ymaxused = 0;
    PROTECT(inputlist = duplicate(indata)); nprotect++;
    for (tvec = inputlist, tvec2 = colmodes;
         tvec != R_NilValue;
         tvec = CDR(tvec), tvec2 = CDR(tvec2)) {
        type = TYPEOF(CAR(tvec)); xmaxused++;
        if (CAR(tvec2) != R_NilValue)
        type = str2type(CHAR(STRING(CAR(tvec2))[0]));
        if (type != STRSXP)
        type = REALSXP;
        if (CAR(tvec) == R_NilValue) {
        if (type == NILSXP)
            type = REALSXP;
        CAR(tvec) = ssNewVector(type, 100);
        TAG(tvec) = install("var1");
        LEVELS(CAR(tvec)) = 0;
        }
        else if (!isVector(CAR(tvec)))
        errorcall(call, "invalid type for value");
        else {
        if (TYPEOF(CAR(tvec)) != type)
            CAR(tvec) = coerceVector(CAR(tvec), type);
        tmp = LEVELS(CAR(tvec)) = LENGTH(CAR(tvec));
        ymaxused = max(tmp, ymaxused);
        }
    }
    }
    else if (colmodes == R_NilValue ) {
    PROTECT(inputlist = allocList(1)); nprotect++;
    CAR(inputlist) = ssNewVector(REALSXP, 100);
    TAG(inputlist) = install("var1");
    LEVELS(CAR(inputlist)) = 0;
    }
    else {
    errorcall(call, "invalid parameter(s) ");
    }


    /* start up the window, more initializing in here */
    if (initwin())
    errorcall(call, "invalid device");

    /* set up a context which will close the window if there is an error */
    begincontext(&cntxt, 8, R_NilValue, R_NilValue, R_NilValue, R_NilValue);
    cntxt.cend = &de_closewin;

    highlightrect();

    eventloop();

    endcontext(&cntxt);

    /* drop out unused columns */
    i = 0;
    for (tvec = inputlist; tvec != R_NilValue; tvec = CDR(tvec))
    if (CAR(tvec) == R_NilValue) {
        if (i == 0)
        inputlist = CDR(inputlist);
        else {
        tvec2 = nthcdr(inputlist, (i - 1));
        SETCDR(tvec2, CDR(tvec));
        }
    }
    else
        i++;

    for (tvec = inputlist; tvec != R_NilValue; tvec = CDR(tvec)) {
    len = LEVELS(CAR(tvec));
    if (LENGTH(CAR(tvec)) != len) {
        tvec2 = ssNewVector(TYPEOF(CAR(tvec)), len);
        PROTECT(tvec);
        for (j = 0; j < len; j++)
        if (TYPEOF(CAR(tvec)) == REALSXP) {
            if (REAL(CAR(tvec))[j] != ssNA_REAL)
            REAL(tvec2)[j] = REAL(CAR(tvec))[j];
            else
            REAL(tvec2)[j] = NA_REAL;
        } else if (TYPEOF(CAR(tvec)) == STRSXP) {
            if (!streql(CHAR(STRING(CAR(tvec))[j]),
                CHAR(STRING(ssNA_STRING)[0])))
            STRING(tvec2)[j] = STRING(CAR(tvec))[j];
            else
            STRING(tvec2)[j] = NA_STRING;
        } else
            error("dataentry: internal memory problem");
        CAR(tvec) = tvec2;
        UNPROTECT(1);
    }
    }

    UNPROTECT(nprotect);
    return PairToVectorList(inputlist);
}

/* Window Drawing Routines */

static rgb bbg;


void drawwindow()
{
    int i, w, dw;

    /* might have resized */
    windowWidth = w = 2*bwidth + boxw[0] + BOXW(colmin);
    nwide = 2;
    for (i = 2; i < 100; i++) { /* 100 on-screen columns cannot occur */
    dw = BOXW(i + colmin - 1);
    if((w += dw) > WIDTH) {
        nwide = i;
        windowWidth = w - dw;
        break;
    }
    }
    nhigh = (HEIGHT - 2 * bwidth - hwidth) / box_h;
    windowHeight = nhigh * box_h + 2 * bwidth;
    oldWIDTH = WIDTH;
    oldHEIGHT = HEIGHT;
    
    clearwindow();

    gfillrect(de, bbg, rect(0, 0, windowWidth, box_h));
    gfillrect(de, bbg, rect(0, 0, boxw[0], windowHeight));
    
    for (i = 1; i < nhigh; i++)
    drawrectangle(0, hwidth + i * box_h, boxw[0], box_h, 1, 1);
     /* so row 0 and col 0 are reserved for labels */
    colmax = colmin + (nwide - 2);
    rowmax = rowmin + (nhigh - 2);
    printlabs();
    if (inputlist != R_NilValue)
    for (i = colmin; i <= colmax; i++) drawcol(i);
    /* row/col 1 = pos 0 */
    gchangescrollbar(de, VWINSB, rowmin-1, ymaxused, nhigh, 0);
    gchangescrollbar(de, HWINSB, colmin-1, xmaxused, nwide, 0);
    highlightrect();
}

void doHscroll(int oldcol)
{
    int i, w, dw;
    int oldnwide = nwide, oldwindowWidth = windowWidth;

    /* horizontal re-position */
    windowWidth = w = 2*bwidth + boxw[0] + BOXW(colmin);
    nwide = 2;
    for (i = 2; i < 100; i++) {
    dw = BOXW(i + colmin - 1);
    if((w += dw) > WIDTH) {
        nwide = i;
        windowWidth = w - dw;
        break;
    }
    }
    colmax = colmin + (nwide - 2);
    if (oldcol < colmin) { /* drop oldcol...colmin-1 */
    dw = boxw[0];
    for (i = oldcol; i < colmin; i++) dw += BOXW(i);
    copyH(dw, boxw[0], oldwindowWidth - dw + 1);
    dw = oldwindowWidth - BOXW(oldcol) + 1;
    cleararea(dw, hwidth, WIDTH-dw, HEIGHT, p->bg);
    /* oldnwide includes the row labels */
    for (i = oldcol+oldnwide-1; i <= colmax; i++) drawcol(i);
    } else {
    /* move one or more cols left */
    dw = BOXW(colmin);
    copyH(boxw[0], boxw[0] + dw, windowWidth - dw + 1);
    dw = windowWidth + 1;
    cleararea(dw, hwidth, WIDTH-dw, HEIGHT, p->bg);
    drawcol(colmin);
    }
    gchangescrollbar(de, HWINSB, colmin-1, xmaxused, nwide, 0);
    highlightrect();
}

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

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

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

void jumpwin(int wcol, int wrow)
{
    if (wcol < 0 || wrow < 0) {
    bell();
    return;
    }
    closerect();
    if (colmin != wcol || rowmin != wrow) {
    colmin = wcol;
    rowmin = wrow;
    deredraw();
    } else highlightrect();
}

void advancerect(int which)
{

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

    closerect();

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

    highlightrect();
}

static char *get_col_name(int col)
{
    SEXP tmp;
    static char clab[15];
    if (col <= length(inputlist)) {
    tmp = nthcdr(inputlist, col - 1);
    if (TAG(tmp) != R_NilValue)
        return CHAR(PRINTNAME(TAG(tmp)));
    }
    sprintf(clab, "var%d", col);
    return clab;
}

static int get_col_width(int col)
{
    int i, w = 0, w1;
    char *strp;
    SEXP tmp;
    if (col <= length(inputlist)) {
    tmp = nthcdr(inputlist, col - 1);
    if (tmp == R_NilValue) return FIELDWIDTH;
    PrintDefaults(R_NilValue);
    if (TAG(tmp) != R_NilValue)
        w = strlen(CHAR(PRINTNAME(TAG(tmp))));
    else w = FIELDWIDTH;
    tmp = CAR(tmp);
    PrintDefaults(R_NilValue);
    for (i = 0; i < (int)LEVELS(tmp); i++) {
        strp = EncodeElement(tmp, i, 0);
        w1 = strlen(strp);
        if (w1 > w) w = w1;
    }
    if(w < 5) w = 5;
    if(w < 8) w++;
    return w;
    }
    return FIELDWIDTH;
}

typedef enum {UNKNOWNN, NUMERIC, CHARACTER} CellType;

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

    if (col <= length(inputlist)) {
    tmp = CAR(nthcdr(inputlist, 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 */
void drawcol(int whichcol)
{
    int i, src_x, src_y, len, col = whichcol - colmin + 1, bw = BOXW(whichcol);
    char *clab;
    SEXP tmp;

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

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

   if (length(inputlist) >= whichcol) {
    tmp = nthcdr(inputlist, whichcol - 1);
    if (CAR(tmp) != R_NilValue) {
        len = min(rowmax, LEVELS(CAR(tmp)) );
        for (i = (rowmin - 1); i < len; i++)
        printelt(CAR(tmp), i, i - rowmin + 2, col);
    }
    }
}


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

    find_coords(row, 0, &src_x, &src_y);
    cleararea(src_x, src_y, windowWidth, box_h, (whichrow > 0)?p->bg:bbg);
    drawrectangle(src_x, src_y, boxw[0], box_h, 1, 1);

    sprintf(rlab, "%4d", whichrow);
    printstring(rlab, strlen(rlab), row, 0, 0);

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

    lenip = length(inputlist);
    for (i = colmin; i <= colmax; i++) {
    if (i > lenip) break;
    tvec = CAR(nthcdr(inputlist, i - 1));
    if (tvec != R_NilValue)
        if (whichrow <= (int)LEVELS(tvec))
        printelt(tvec, whichrow - 1, row, i - colmin + 1);
    }
}


/* 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. */

void printelt(SEXP invec, int vrow, int ssrow, int sscol)
{
    char *strp;
    PrintDefaults(R_NilValue);
    if (TYPEOF(invec) == REALSXP) {
    if (REAL(invec)[vrow] != ssNA_REAL) {
        strp = EncodeElement(invec, vrow, 0);
        printstring(strp, strlen(strp), ssrow, sscol, 0);
    }
    }
    else if (TYPEOF(invec) == STRSXP) {
    if (!streql(CHAR(STRING(invec)[vrow]), CHAR(STRING(ssNA_STRING)[0]))) {
        strp = EncodeElement(invec, vrow, 0);
        printstring(strp, strlen(strp), ssrow, sscol, 0);
    }
    }
    else
    error("dataentry: internal memory error");
}


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

    if (whichrow == 0) {
    clab = get_col_name(whichcol + colmin - 1);
    printstring(clab, strlen(clab), 0, whichcol, 0);
    } else {
    if (length(inputlist) >= whichcol + colmin - 1) {
        tmp = nthcdr(inputlist, whichcol + colmin - 2);
        if (CAR(tmp) != R_NilValue &&
        (i = rowmin + whichrow - 2) < (int)LEVELS(CAR(tmp)) )
        printelt(CAR(tmp), i, whichrow, whichcol);
    } else
    printstring("", 0, whichrow,  whichcol, 0);
    }
}

void jumppage(int dir)
{
    int i, w, oldcol, wcol;

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

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

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

void highlightrect()
{
    printrect(2, 1);
}


static SEXP getccol()
{
    SEXP tmp, tmp2;
    int i, len, newlen, wcol, wrow;
    SEXPTYPE type;
    char cname[10];

    wcol = ccol + colmin - 1;
    wrow = crow + rowmin - 1;
    if (length(inputlist) < wcol)
    inputlist = listAppend(inputlist,
                   allocList(wcol - length(inputlist)));
    tmp = nthcdr(inputlist, wcol - 1);
    newcol = 0;
    if (CAR(tmp) == R_NilValue) {
    newcol = 1;
    xmaxused = wcol;
    len = max(100, wrow);
    CAR(tmp) = ssNewVector(REALSXP, len);
    if (TAG(tmp) == R_NilValue) {
        sprintf(cname, "var%d", wcol);
        TAG(tmp) = install(cname);
    }
    }
    if (!isVector(CAR(tmp)))
    error("internal type error in dataentry");
    len = LENGTH(CAR(tmp));
    type = TYPEOF(CAR(tmp));
    if (len < wrow) {
    for (newlen = len * 2 ; newlen < wrow ; newlen *= 2)
        ;
    tmp2 = ssNewVector(type, newlen);
    for (i = 0; i < len; i++)
        if (type == REALSXP)
        REAL(tmp2)[i] = REAL(CAR(tmp))[i];
        else if (type == STRSXP)
        STRING(tmp2)[i] = STRING(CAR(tmp))[i];
        else
        error("internal type error in dataentry");
    LEVELS(tmp2) = LEVELS(CAR(tmp));
    CAR(tmp) = tmp2;
    }
    return (tmp);
}

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

extern double R_strtod(char *c, char **end); /* in coerce.c */

void closerect()
{
    SEXP cvec, c0vec, tvec;
    int wcol = ccol + colmin - 1, wrow = rowmin + crow - 1, wrow0;

    *bufp = '\0';

    if (CellModified || CellEditable) {
    if (CellEditable) {
        strcpy(buf, gettext(celledit));
        clength = strlen(buf);
        hide(celledit);
        del(celledit);
    }
    c0vec = getccol();
    cvec = CAR(c0vec);
    wrow0 = (int)LEVELS(cvec);
    if (wrow > wrow0) LEVELS(cvec) = wrow;
    ymaxused = max(ymaxused, wrow);
    if (clength != 0) {
        /* do it this way to ensure NA, Inf, ...  can get set */
        char *endp;
        double new = R_strtod(buf, &endp);
        int warn = !isBlankString(endp);
        if (TYPEOF(cvec) == STRSXP) {
        tvec = allocString(strlen(buf));
        strcpy(CHAR(tvec), buf);
        STRING(cvec)[wrow - 1] = tvec;
        } else
        REAL(cvec)[wrow - 1] = new;
        if (newcol & warn) {
        /* change mode to character */
        int levs = LEVELS(cvec);
        cvec = CAR(c0vec) = coerceVector(cvec, STRSXP);
        LEVELS(cvec) = levs;
        tvec = allocString(strlen(buf));
        strcpy(CHAR(tvec), buf);
        STRING(cvec)[wrow - 1] = tvec;
        }
    } else {
        if (TYPEOF(cvec) == STRSXP)
        STRING(cvec)[wrow - 1] = NA_STRING;
        else
        REAL(cvec)[wrow - 1] = NA_REAL;
    }
    drawelt(crow, ccol);  /* to get the cell scrolling right */
    if(wrow > wrow0) drawcol(wcol); /* to fill in NAs */
    }
    CellEditable = CellModified = 0;

    downlightrect();
    gsetcursor(de, ArrowCursor);

    ndecimal = 0;
    nneg = 0;
    ne = 0;
    currentexp = 0;
    clength = 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; */

void printstring(char *ibuf, int buflen, int row, int col, int left)
{
    int x_pos, y_pos, bw, fw;
    char buf[45], *pc = buf;

    find_coords(row, col, &x_pos, &y_pos);
    if (col == 0) bw = boxw[0]; else bw = BOXW(col+colmin-1);
    cleararea(x_pos + 2, y_pos + 2, bw - 3, box_h - 3,
          (row==0 || col==0) ? bbg:p->bg);
    strncpy(buf, ibuf, buflen);
    buf[buflen] = '\0';
    fw = (bw - 8)/FW;
    if (buflen > fw) {
    if(left) {
        pc += buflen - fw;
        *pc = '<';
    } else {
        *(pc + fw - 1) = '>';
        *(pc + fw) = '\0';
    }
    }
    de_drawtext(x_pos + text_xoffset, y_pos - text_yoffset, pc);
}

void clearrect()
{
    int x_pos, y_pos;
    find_coords(crow, ccol, &x_pos, &y_pos);
    cleararea(x_pos, y_pos, BOXW(ccol+colmin-1), box_h, p->bg);
}

/* 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... */

void handlechar(char *text)
{
    int c = text[0];

    if ( c == '\033' ) {
    CellModified = 0;
    clength = 0;
    drawelt(crow, ccol);
    gsetcursor(de, ArrowCursor);
    return;
    } else {
    CellModified = 1;
    gsetcursor(de, TextCursor);
    }

    if (clength == 0) {
    switch(get_col_type(ccol + colmin - 1)) {
    case NUMERIC:
        currentexp = 1;
        break;
    default:
        currentexp = 2;
    }
    clearrect();
    highlightrect();
    }

    if (currentexp == 1)    /* we are parsing a number */
    switch (c) {
    case '-':
        if (nneg == 0)
        nneg++;
        else
        goto donehc;
        break;
    case '.':
        if (ndecimal == 0)
        ndecimal++;
        else
        goto donehc;
        break;
    case 'e':
    case 'E':
        if (ne == 0) {
        nneg = ndecimal = 0;    /* might have decimal in exponent */
        ne++;
        }
        else
        goto donehc;
        break;
    default:
        if (!isdigit((int)text[0]))
        goto donehc;
        break;
    }

    if (clength++ > 29) {
    warning("dataentry: expression too long");
    clength--;
    goto donehc;
    }

    *bufp++ = text[0];
    printstring(buf, clength, crow, ccol, 1);
    return;

 donehc:
    bell();
}

void printlabs()
{
    char clab[10], *p;
    int i;

    for (i = colmin; i <= colmax; i++) {
    p = get_col_name(i);
    printstring(p, strlen(p), 0, i - colmin + 1, 0);
    }
    for (i = rowmin; i <= rowmax; i++) {
    sprintf(clab, "%4d", i);
    printstring(clab, strlen(clab), i - rowmin + 1, 0, 0);
    }
}

              /* ================ Windows-specific ================ */

static void bell()
{
    gabeep();
}

static void cleararea(int xpos, int ypos, int width, int height, rgb col)
{
    gfillrect(de, col, rect(xpos, ypos, width, height));
}

static void clearwindow()
{
    gfillrect(de, p->bg, rect(0, 0, WIDTH, HEIGHT));
}

#if 0
static void de_drawline(int fromx, int fromy, int tox, int toy)
{
    gdrawline(de, 1, 0, p->ufg, pt(fromx, fromy), pt(tox, toy));
}
#endif

static void drawrectangle(int xpos, int ypos, int width, int height,
              int lwd, int fore)
{
    gdrawrect(de, lwd, 0, (fore==1)? p->ufg: p->bg,
          rect(xpos, ypos, width, height));
}

static void de_drawtext(int xpos, int ypos, char *text)
{
    gdrawstr(de, p->f, p->fg, pt(xpos, ypos), text);
}

/* Keypress callbacks */

void de_normalkeyin(control c, int k)
{
    int i, st;
    char text[1];

    st = ggetkeystate();
    if ((p->chbrk) && (k == p->chbrk) &&
    ((!p->modbrk) || ((p->modbrk) && (st == p->modbrk)))) {
    p->fbrk(c);
    return;
    }
    if (st & CtrlKey) {
    switch (k + 'A' - 1) {
    case 'B':
        i = rowmin - nhigh + 2;
        jumpwin(colmin, max(i, 1));
        break;
    case 'F':
        jumpwin(colmin, rowmax);
        break;
    case 'H':
        if (clength > 0) {
        clength--;
        bufp--;
        printstring(buf, clength, crow, ccol, 1);
        } else bell();
        break;
    case 'I':
        if (st & ShiftKey) advancerect(LEFT);
        else advancerect(RIGHT);
        break;
    case 'N':
    case 'J':
        advancerect(DOWN);
        break;
    case 'C':
        de_copy(de);
        break;
    case 'V':
        de_paste(de);
        break;
    case 'L':
        for (i = colmin; i < colmax; i++)
        if (i < 100) boxw[i] = get_col_width(i)*FW + 8;
        drawwindow();
        break;
    default:
        bell();
    }
    } else if(k == '\b') {
        if (clength > 0) {
        clength--;
        bufp--;
        printstring(buf, clength, crow, ccol, 1);
        } else bell();
    } else if(k == '\n' || k == '\r') {
        advancerect(DOWN);
    } else if(k == '\t') {
    if (st & ShiftKey) advancerect(LEFT);
    else advancerect(RIGHT);
    } else {
    text[0] = k;
    handlechar(text);
    }

}

void de_ctrlkeyin(control c, int key)
{
    int st, i;

    st = ggetkeystate();
    if ((p->chbrk) && (key == p->chbrk) &&
    ((!p->modbrk) || ((p->modbrk) && (st == p->modbrk)))) {
    p->fbrk(c);
    return;
    }
    switch (key) {
    case HOME:
    jumpwin(1, 1);
    downlightrect();
    crow = ccol = 1;
    highlightrect();
    break;
    case END:
    i = ymaxused - nhigh + 2;
    jumpwin(xmaxused, max(i, 1));
    downlightrect();
    crow = ymaxused - rowmin + 1;
    ccol = 1;
    highlightrect();
    break;
    case PGUP:
    i = rowmin - nhigh + 2;
    jumpwin(colmin, max(i, 1));
    break;
    case PGDN:
    jumpwin(colmin, rowmax);
    break;
    case LEFT:
    advancerect(LEFT);
    break;
    case RIGHT:
    advancerect(RIGHT);
    break;
    case UP:
    advancerect(UP);
    break;
    case DOWN:
    advancerect(DOWN);
    break;
    case DEL:
    if (clength > 0) {
        clength--;
        bufp--;
        printstring(buf, clength, crow, ccol, 1);
    } else bell();
    break;
     case ENTER:
     advancerect(DOWN);
     break;
    default:
    ;
    }
}

/* mouse callbacks */

static char *get_cell_text()
{
    int  wrow = rowmin + crow - 2;
    char *prev = "";
    SEXP tvec;

    if (ccol <= length(inputlist)) {
    tvec = CAR(nthcdr(inputlist, ccol - 1));
    if (tvec != R_NilValue && wrow < (int)LEVELS(tvec)) {
        PrintDefaults(R_NilValue);
        if (TYPEOF(tvec) == REALSXP) {
        if (REAL(tvec)[wrow] != ssNA_REAL)
            prev = EncodeElement(tvec, wrow, 0);
        } else if (TYPEOF(tvec) == STRSXP) {
        if (!streql(CHAR(STRING(tvec)[wrow]),
                CHAR(STRING(ssNA_STRING)[0])))
            prev = EncodeElement(tvec, wrow, 0);
        } else error("dataentry: internal memory error");
    }
    }
    return prev;
}

static int online, clickline;

void de_mousedown(control c, int buttons, point xy)
{
    int xw, yw, wcol=0, wrow, i, w;

    if (buttons & LeftButton) {
    xw = xy.x;
    yw = xy.y;

    closerect();

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

    if (yw < hwidth + bwidth) {
        /* too high */
        return;
    }
    /* translate to box coordinates */

    wrow = (yw - bwidth - hwidth) / box_h;

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

    /* see if we selected a line */
    w = bwidth;
    online = 0;
    for (i = 0; i <= nwide; i++) {
        if(i == 0) w += boxw[0]; else w += BOXW(i+colmin-1);
        if (abs(w - xw) <= 2) {
        online = 1;
        clickline = i; /* between cols i and i+1 */
        highlightrect();
        gsetcursor(de, HandCursor);
        return;
        }
    }

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

    if (yw < hwidth + bwidth + box_h) {
        if (xw > bwidth + boxw[0]) {
        de_popupmenu(xw, yw, wcol);
        return;
        } else {
        /* in 0th column */
        highlightrect();
        bell();
        }
    } else if (buttons & DblClick) {
        int x, y, bw;
        char *prev;
        rect rr;

        ccol = wcol;
        crow = wrow;
        highlightrect();
        find_coords(crow, ccol, &x, &y);
        bw = BOXW(crow+colmin-1);
        rr = rect(x + text_xoffset, y - text_yoffset - 1,
              bw - text_xoffset - 2,
              box_h - text_yoffset - 2);
        prev = get_cell_text();
        if (strlen(prev) * FW > bw)
        rr.width = (strlen(prev) + 2) * FW;
        celledit = newfield_no_border(prev, rr);
        setbackground(celledit, p->bg);
        setforeground(celledit, p->ufg);
        settextfont(celledit, p->f);
        show(celledit);
        CellEditable = 1;
    } else if (buttons & LeftButton) {
        ccol = wcol;
        crow = wrow;
    }
    highlightrect();
    return;
    }
}

void de_mouseup(control c, int buttons, point xy)
{
    int xw, bw, i, w;

    if (online) {
    xw = xy.x;
    w = bwidth + boxw[0];
    for(i = 1; i < clickline; i++) w+= BOXW(i+colmin-1);
    bw = xw - w;
    if (bw < FW*4 + 8) bw = FW*4 + 8;
    if (bw > FW*50) bw = FW*50;
    if(clickline < 100) boxw[clickline] = bw;
    gsetcursor(de, ArrowCursor);
    deredraw();
    }
}

void de_redraw(control c, rect r)
{
    deredraw();
}

static void deredraw()
{
    int i, w, dw;

    if (WIDTH != oldWIDTH || HEIGHT != oldHEIGHT) {
    drawwindow();
    return;
    }
    windowWidth = w = 2*bwidth + boxw[0] + BOXW(colmin);
    nwide = 2;
    for (i = 2; i < 100; i++) { /* 100 on-screen columns cannot occur */
    dw = BOXW(i + colmin - 1);
    if((w += dw) > WIDTH) {
        nwide = i;
        windowWidth = w - dw;
        break;
    }
    }

    gfillrect(de, bbg, rect(0, 0, windowWidth, box_h));
    gfillrect(de, bbg, rect(0, 0, boxw[0], windowHeight));

    for (i = 1; i < nhigh; i++)
    drawrectangle(0, hwidth + i * box_h, boxw[0], box_h, 1, 1);
    colmax = colmin + (nwide - 2);
    rowmax = rowmin + (nhigh - 2);
    printlabs();
    if (inputlist != R_NilValue)
    for (i = colmin; i <= colmax; i++) drawcol(i);
    gfillrect(de, p->bg, rect(windowWidth+1, 0, WIDTH-windowWidth-1, HEIGHT));
    highlightrect();
}

void de_closewin()
{
    closerect();
    hide(de);
    del(de);
}

#include <windows.h>
extern HDC get_context(dataeditor);

static void copyarea(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);
    HDC dc = get_context(de);
    BitBlt(dc, dest_x, dest_y,
       windowWidth - mx, windowHeight - my,
       dc, src_x, src_y, SRCCOPY);
}

static void copyH(int src_x, int dest_x, int width)
{
    HDC dc = get_context(de);
    BitBlt(dc, dest_x, hwidth, width, windowHeight - hwidth,
       dc, src_x, hwidth, SRCCOPY);
}

static int  initwin()
{
    int i, w, dw;
    rect r;

    demenuitems[0] = "";
    de = newdataeditor();
    if(!de) return 1;
    p = getdata(de);

    box_w = FIELDWIDTH*FW + 8;
    boxw[0] = 5*FW + 8;
    for(i = 1; i < 100; i++) boxw[i] = get_col_width(i)*FW + 8;
    box_h = FH + 4;
    text_xoffset = 5;
    text_yoffset = -3;
    windowWidth = w = 2*bwidth + boxw[0] + boxw[1];
    nwide = 2;
    for (i = 2; i < 100; i++) {
    if((w += boxw[i]) > WIDTH) {
        nwide = i;
        windowWidth = w - boxw[i];
        break;
    }
    }
    nhigh = (HEIGHT - 2 * bwidth - hwidth) / box_h;
    windowHeight = nhigh * box_h + 2 * bwidth - hwidth;
    r = getrect(de);
    r.width = windowWidth + 3;
    r.height = windowHeight + 3;
    resize(de, r);

    CellModified = CellEditable = 0;
    bbg = myGetSysColor(COLOR_BTNFACE);
    /* set the active cell to be the upper left one */
    crow = 1;
    ccol = 1;
    /* drawwindow(); done as repaint but
       decide if we need scrollbars here to avoid flashing*/
    nhigh = (HEIGHT - 2 * bwidth - hwidth) / box_h;
    gchangescrollbar(de, VWINSB, 0, ymaxused, nhigh, 0);
    windowWidth = w = 2*bwidth + boxw[0] + BOXW(colmin);
    nwide = 2;
    for (i = 2; i < 100; i++) {
    dw = BOXW(i + colmin - 1);
    if((w += dw) > WIDTH) {
        nwide = i;
        windowWidth = w - dw;
        break;
    }
    }
    gchangescrollbar(de, HWINSB, 0, xmaxused, nwide, 0);    
    show(de);
    R_de_up = 1;
    return 0;
}

/* Menus */

static window wconf;
static radiobutton rb_num, rb_char;
static label lwhat, lrb;
static field varname;
static int isnumeric, popupcol;

static void popupclose(control c)
{
    SEXP tvec;
    int levs;
    char buf[30];

    strcpy(buf, gettext(varname));
    if(!strlen(buf)) {
    askok("column names cannot be blank");
    return;
    }
    if (length(inputlist) < popupcol) {
    inputlist =
        listAppend(inputlist,
               allocList((popupcol - length(inputlist))));
    }
    tvec = nthcdr(inputlist, popupcol - 1);
    if(ischecked(rb_num) && !isnumeric) {
    if (CAR(tvec) == R_NilValue) CAR(tvec) = ssNewVector(REALSXP, 100);
    levs = LEVELS(CAR(tvec));
    CAR(tvec) = coerceVector(CAR(tvec), REALSXP);
    LEVELS(CAR(tvec)) = levs;

    } else if(ischecked(rb_char) && isnumeric) {
    if (CAR(tvec) == R_NilValue) CAR(tvec) = ssNewVector(STRSXP, 100);
    levs = LEVELS(CAR(tvec));
    CAR(tvec) = coerceVector(CAR(tvec), STRSXP);
    LEVELS(CAR(tvec)) = levs;
    }
    TAG(tvec) = install(buf);
    hide(wconf);
    del(wconf);
    addto(de);
    /* drawwindow(); forced by redraw */
}

void getscreenrect(control, rect *);

static void de_popupmenu(int x_pos, int y_pos, int col)
{
    char *blah;
    rect r;

    popupcol = colmin + col - 1;
    blah = get_col_name(popupcol);
    getscreenrect(de, &r);
    wconf = newwindow("Variable editor",
              rect(x_pos + r.x-150, y_pos + r.y-50, 300, 100),
              Titlebar | Modal | Closebox);
    setclose(wconf, popupclose);
    setbackground(wconf, LightGrey);
    lwhat = newlabel("variable name", rect(10, 20, 90, 20), AlignLeft);
    varname = newfield(blah, rect(100, 20, 120, 20));
    lrb = newlabel("type", rect(50, 60, 50, 20), AlignLeft);
    rb_num = newradiobutton("numeric", rect(100, 60 , 80, 20), NULL);
    rb_char = newradiobutton("character", rect(180, 60 , 80, 20), NULL);
    isnumeric = (get_col_type(popupcol) == NUMERIC);
    if (isnumeric) check(rb_num); else check(rb_char);
    show(wconf);
}

void de_copy(control c)
{
    HGLOBAL hglb;
    char *s, *cell;
    int ll;

    cell = get_cell_text();
    ll = strlen(cell) + 1;
    if (!(hglb = GlobalAlloc(GHND, ll))){
        R_ShowMessage("Insufficient memory: cell not copied to the clipboard");
    return;
    }
    if (!(s = (char *)GlobalLock(hglb))){
        R_ShowMessage("Insufficient memory: cell not copied to the clipboard");
    return;
    }
    strcpy(s, cell);
    GlobalUnlock(hglb);
    if (!OpenClipboard(NULL) || !EmptyClipboard()) {
        R_ShowMessage("Unable to open the clipboard");
        GlobalFree(hglb);
        return;
    }
    SetClipboardData(CF_TEXT, hglb);
    CloseClipboard();
}

void de_paste(control c)
{
    HGLOBAL hglb;
    char *p, *pc;

    closerect();
    if ( OpenClipboard(NULL) &&
         (hglb = GetClipboardData(CF_TEXT)) &&
         (pc = (char *)GlobalLock(hglb))) {
    /* set current cell to first line of pc */
    CellModified = 1;
    strncpy(buf, pc, 29);
        GlobalUnlock(hglb);
    CloseClipboard();
    buf[30] = '\0';
    if ((p = strchr(buf, '\n'))) *p = '\0';
    clength = strlen(buf);
    bufp = buf + clength;
    closerect();
    }
}

void de_autosize(control c)
{
    int col = ccol + colmin - 1;
    if(col < 100) {
    boxw[col] = get_col_width(col)*FW + 8;
    deredraw();
    }
}

void de_sbf(control c, int pos)
{
    if (pos < 0) { /* horizontal */
    colmin = 1 + (-pos - 1);
    } else {
    rowmin = 1 + pos;
    if(rowmin > ymaxused - nhigh + 2) rowmin = ymaxused - nhigh + 2;
    }
    drawwindow();
}