/* * R : A Computer Langage for Statistical Data Analysis * Copyright (C) 1995, 1996 Robert Gentleman and Ross Ihaka * Copyright (C) 1998--2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* TODO - spreadsheet copy and paste? */ /* Use of strchr here is MBCS-safe */ #ifdef HAVE_CONFIG_H #include #endif #include "win-nls.h" #include #include #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; typedef enum {UNKNOWNN, NUMERIC, CHARACTER} CellType; static Rboolean 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) (min(((x<100 && nboxchars==0)?boxw[x]:box_w),WIDTH-boxw[0]-2*bwidth-2)) #define FIELDWIDTH 10 #define BUFSIZE 200 /* Local Function Definitions */ static void advancerect(int); static void bell(); static void cleararea(int, int, int, int, rgb); static void clearrect(void); static void closerect(void); static void clearwindow(void); static void de_closewin(void); static void copyarea(int, int, int, int); static void copyH(int, int, int); static void deredraw(void); static void eventloop(void); static void downlightrect(void); static void drawwindow(void); 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(void); static Rboolean initwin(void); static void jumppage(int); static void jumpwin(int, int); static void de_popupmenu(int, int, int); static void printlabs(void); static void printrect(int, int); static void printstring(char*, int, int, int, int); static void printelt(SEXP, int, int, int); static void setcellwidths(void); static dataeditor newdataeditor(); static void de_copy(control c); static void de_paste(control c); static void de_delete(control c); static menuitem de_mvw; static SEXP work, names, lens; static PROTECT_INDEX wpi, npi, lpi; static SEXP ssNA_STRING; static double ssNA_REAL; /* Global variables needed for the graphics */ static int box_w; /* width of a box */ static int boxw[100]; /* widths 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[BUFSIZE]; static char *bufp; static int bwidth; /* width of the border */ static int hwidth; /* width of header */ static int text_xoffset, text_yoffset; static field celledit; static Rboolean newcol, CellModified, CellEditable; static int xmaxused, ymaxused; static int oldWIDTH=0, oldHEIGHT=0; static int nboxchars=0; static int labdigs=4; static char labform[6]; static int xScrollbarScale=1, yScrollbarScale=1; #define WIN32_LEAN_AND_MEAN 1 #include /* for Sleep */ int mb_char_len(char *buf, int clength, wchar_t *wc); /* from console.c */ static void moveback() { int mb_len; wchar_t wc; if (clength > 0) { mb_len = mb_char_len(buf, clength-1, &wc); clength -= mb_len; bufp -= mb_len; printstring(buf, clength, crow, ccol, 1); } else bell(); } /* 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 */ /* 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_REAL and 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] = ssNA_REAL; else if (type == STRSXP) SET_STRING_ELT(tvec, j, STRING_ELT(ssNA_STRING, 0)); return (tvec); } static void eventloop() { while (R_de_up) { /* avoid consuming 100% CPU time here */ Sleep(10); R_ProcessEvents(); } } static void de_closewin_cend(void *data) { de_closewin(); } SEXP do_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]; nprotect = 0;/* count the PROTECT()s */ PROTECT_WITH_INDEX(work = duplicate(CAR(args)), &wpi); nprotect++; colmodes = CADR(args); tnames = getAttrib(work, R_NamesSymbol); if (TYPEOF(work) != VECSXP || TYPEOF(colmodes) != VECSXP) errorcall(call, G_("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 = 5; /* setup work, names, lens */ xmaxused = length(work); ymaxused = 0; PROTECT_WITH_INDEX(lens = allocVector(INTSXP, xmaxused), &lpi); nprotect++; if (isNull(tnames)) { PROTECT_WITH_INDEX(names = allocVector(STRSXP, xmaxused), &npi); for(i = 0; i < xmaxused; i++) { sprintf(clab, "var%d", i); SET_STRING_ELT(names, i, mkChar(clab)); } } else PROTECT_WITH_INDEX(names = duplicate(tnames), &npi); nprotect++; for (i = 0; i < xmaxused; i++) { int len = LENGTH(VECTOR_ELT(work, i)); INTEGER(lens)[i] = len; ymaxused = max(len, ymaxused); type = TYPEOF(VECTOR_ELT(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(work, i))) { if (type == NILSXP) type = REALSXP; SET_VECTOR_ELT(work, i, ssNewVector(type, 100)); } else if (!isVector(VECTOR_ELT(work, i))) errorcall(call, G_("invalid type for value")); else { if (TYPEOF(VECTOR_ELT(work, i)) != type) SET_VECTOR_ELT(work, i, coerceVector(VECTOR_ELT(work, i), type)); } } /* scale scrollbars as needed */ if (xmaxused > 10000) xScrollbarScale = xmaxused/1000; if (ymaxused > 10000) yScrollbarScale = ymaxused/1000; /* start up the window, more initializing in here */ if (initwin()) errorcall(call, G_("invalid device")); /* 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 = &de_closewin_cend; cntxt.cenddata = NULL; highlightrect(); eventloop(); endcontext(&cntxt); /* drop out unused columns */ for(i = 0, cnt = 0; i < xmaxused; i++) if(!isNull(VECTOR_ELT(work, i))) cnt++; if (cnt < xmaxused) { PROTECT(work2 = allocVector(VECSXP, cnt)); nprotect++; for(i = 0, j = 0; i < xmaxused; i++) { if(!isNull(VECTOR_ELT(work, i))) { SET_VECTOR_ELT(work2, j, VECTOR_ELT(work, i)); INTEGER(lens)[j] = INTEGER(lens)[i]; SET_STRING_ELT(names, j, STRING_ELT(names, i)); j++; } } REPROTECT(names = lengthgets(names, cnt), npi); } else work2 = work; for (i = 0; i < LENGTH(work2); i++) { len = INTEGER(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) { if (REAL(tvec)[j] != ssNA_REAL) REAL(tvec2)[j] = REAL(tvec)[j]; else REAL(tvec2)[j] = NA_REAL; } else if (TYPEOF(tvec) == STRSXP) { if (!streql(CHAR(STRING_ELT(tvec, j)), CHAR(STRING_ELT(ssNA_STRING, 0)))) SET_STRING_ELT(tvec2, j, STRING_ELT(tvec, j)); else SET_STRING_ELT(tvec2, j, NA_STRING); } else error(G_("dataentry: internal memory problem")); } SET_VECTOR_ELT(work2, i, tvec2); } } setAttrib(work2, R_NamesSymbol, names); UNPROTECT(nprotect); return work2; } /* Window Drawing Routines */ static rgb bbg; static void setcellwidths(void) { int i, w, dw; 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; } } } static void drawwindow(void) { /* might have resized */ setcellwidths(); nhigh = (HEIGHT - 2 * bwidth - hwidth - 3) / box_h; windowHeight = nhigh * box_h + 2 * bwidth + hwidth; oldWIDTH = WIDTH; oldHEIGHT = HEIGHT; clearwindow(); deredraw(); /* row/col 1 = pos 0 */ gchangescrollbar(de, VWINSB, (rowmin - 1)/yScrollbarScale, ymaxused/yScrollbarScale, max(nhigh/yScrollbarScale, 1), 0); gchangescrollbar(de, HWINSB, (colmin - 1)/xScrollbarScale, xmaxused/xScrollbarScale, max(nwide/xScrollbarScale, 1), 0); } static void doHscroll(int oldcol) { int i, dw; int oldnwide = nwide, oldwindowWidth = windowWidth; /* horizontal re-position */ setcellwidths(); 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)/xScrollbarScale, xmaxused/xScrollbarScale, max(nwide/xScrollbarScale, 1), 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 */ static 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 */ static 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(); } static 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) { static char clab[25]; if (col <= xmaxused) { /* don't use NA labels */ SEXP tmp = STRING_ELT(names, col - 1); if(tmp != NA_STRING) return(CHAR(tmp)); } sprintf(clab, "var%d", col); return clab; } static int get_col_width(int col) { int i, w = 0, w1, fw = FIELDWIDTH; char *strp; SEXP tmp, lab; if (nboxchars > 0) return nboxchars; if (col <= xmaxused) { tmp = VECTOR_ELT(work, col - 1); if (isNull(tmp)) return fw; /* don't use NA labels */ lab = STRING_ELT(names, col - 1); if(lab != NA_STRING) w = strlen(CHAR(lab)); else w = fw; PrintDefaults(R_NilValue); for (i = 0; i < INTEGER(lens)[col - 1]; i++) { strp = EncodeElement(tmp, i, 0, '.'); w1 = strlen(strp); if (w1 > w) w = w1; } if(w < 5) w = 5; if(w < 8) w++; if(w > 50) w = 50; return w; } return fw; } static CellType get_col_type(int col) { SEXP tmp; CellType res = UNKNOWNN; if (col <= xmaxused) { tmp = VECTOR_ELT(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(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 (xmaxused >= whichcol) { tmp = VECTOR_ELT(work, whichcol - 1); if (!isNull(tmp)) { len = min(rowmax, INTEGER(lens)[whichcol - 1]); for (i = (rowmin - 1); i < len; i++) printelt(tmp, i, i - rowmin + 2, col); } } } /* whichrow is absolute row no */ static void drawrow(int whichrow) { int i, src_x, src_y, 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, labform, 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); } for (i = colmin; i <= colmax; i++) { if (i > xmaxused) break; if (!isNull(tvec = VECTOR_ELT(work, i - 1))) if (whichrow <= INTEGER(lens)[i - 1]) 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. */ static 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_ELT(invec, vrow)), CHAR(STRING_ELT(ssNA_STRING, 0)))) { strp = EncodeElement(invec, vrow, 0, '.'); printstring(strp, strlen(strp), ssrow, sscol, 0); } } else error(G_("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 (xmaxused >= whichcol + colmin - 1) { tmp = VECTOR_ELT(work, whichcol + colmin - 2); if (!isNull(tmp) && (i = rowmin + whichrow - 2) < INTEGER(lens)[whichcol + colmin - 2] ) printelt(tmp, i, whichrow, whichcol); } else printstring("", 0, whichrow, whichcol, 0); } } static 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)/yScrollbarScale, ymaxused/yScrollbarScale, max(nhigh/yScrollbarScale, 1), 0); break; case DOWN: if (rowmax >= 65535) return; rowmin++; rowmax++; copyarea(0, hwidth + 2 * box_h, 0, hwidth + box_h); drawrow(rowmax); gchangescrollbar(de, VWINSB, (rowmin - 1)/yScrollbarScale, ymaxused/yScrollbarScale, max(nhigh/yScrollbarScale, 1), 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 */ static 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); } static void downlightrect(void) { printrect(2, 0); printrect(1, 1); } static void highlightrect(void) { printrect(2, 1); } static void getccol() { SEXP tmp, tmp2; int i, len, newlen, wcol, wrow; SEXPTYPE type; char clab[25]; wcol = ccol + colmin - 1; wrow = crow + rowmin - 1; if (wcol > xmaxused) { /* extend work, names and lens */ REPROTECT(work = lengthgets(work, wcol), wpi); REPROTECT(names = lengthgets(names, wcol), npi); for (i = xmaxused; i < wcol; i++) { sprintf(clab, "var%d", i + 1); SET_STRING_ELT(names, i, mkChar(clab)); } REPROTECT(lens = lengthgets(lens, wcol), lpi); xmaxused = wcol; } newcol = FALSE; if (isNull(VECTOR_ELT(work, wcol - 1))) { newcol = TRUE; SET_VECTOR_ELT(work, wcol - 1, ssNewVector(REALSXP, max(100, wrow))); INTEGER(lens)[wcol - 1] = 0; } if (!isVector(tmp = VECTOR_ELT(work, wcol - 1))) error(G_("internal type error in dataentry")); len = INTEGER(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(G_("internal type error in dataentry")); SET_VECTOR_ELT(work, wcol - 1, tmp2); } } /* 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(void) { SEXP cvec; int wcol = ccol + colmin - 1, wrow = rowmin + crow - 1, wrow0; *bufp = '\0'; if (CellModified || CellEditable) { if (CellEditable) { strncpy(buf, GA_gettext(celledit), BUFSIZE-1); clength = strlen(buf); hide(celledit); del(celledit); } getccol(); cvec = VECTOR_ELT(work, wcol - 1); wrow0 = INTEGER(lens)[wcol - 1]; if (wrow > wrow0) INTEGER(lens)[wcol - 1] = 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) SET_STRING_ELT(cvec, wrow - 1, mkChar(buf)); else REAL(cvec)[wrow - 1] = new; if (newcol & warn) { /* change mode to character */ SET_VECTOR_ELT(work, wcol - 1, coerceVector(cvec, STRSXP)); SET_STRING_ELT(VECTOR_ELT(work, wcol - 1), wrow - 1, mkChar(buf)); } } else { if (TYPEOF(cvec) == STRSXP) SET_STRING_ELT(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 = FALSE; 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; */ /* This version will only display BUFSIZE chars, but the maximum col width will not allow that many */ static void printstring(char *ibuf, int buflen, int row, int col, int left) { int x_pos, y_pos, bw, fw, bufw; char buf[BUFSIZE+1]; find_coords(row, col, &x_pos, &y_pos); if (col == 0) bw = boxw[0]; else bw = BOXW(col+colmin-1); cleararea(x_pos + 1, y_pos + 1, bw - 1, box_h - 1, (row==0 || col==0) ? bbg:p->bg); fw = min(BUFSIZE, (bw - 8)/FW); bufw = min(fw, buflen); strncpy(buf, ibuf, bufw); buf[bufw] = '\0'; if (buflen > fw) { if(left) { strncpy(buf, ibuf + buflen - fw, fw); buf[fw] = '\0'; *buf = '<'; } else { *(buf + fw - 1) = '>'; *(buf + fw) = '\0'; } } de_drawtext(x_pos + text_xoffset, y_pos - text_yoffset, buf); } static void clearrect(void) { 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... */ static void handlechar(char *text) { int c = text[0]; if ( c == '\033' ) { CellModified = FALSE; clength = 0; drawelt(crow, ccol); gsetcursor(de, ArrowCursor); return; } else { CellModified = TRUE; 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++ > 199) { warning(G_("dataentry: expression too long")); clength--; goto donehc; } *bufp++ = text[0]; printstring(buf, clength, crow, ccol, 1); return; donehc: bell(); } static void printlabs(void) { char clab[15], *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, labform, i); printstring(clab, strlen(clab), i - rowmin + 1, 0, 0); } } /* ================ GraphApp-specific ================ */ static void bell(void) { 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(void) { gfillrect(de, p->bg, rect(0, 0, WIDTH, HEIGHT)); } static void drawrectangle(int xpos, int ypos, int width, int height, int lwd, int fore) { /* only used on screen, so always fast */ gdrawrect(de, lwd, 0, (fore==1)? p->ufg: p->bg, rect(xpos, ypos, width, height), 1, PS_ENDCAP_SQUARE, PS_JOIN_BEVEL, 10); } static void de_drawtext(int xpos, int ypos, char *text) { gdrawstr(de, p->f, p->fg, pt(xpos, ypos), text); } /* Keypress callbacks */ static 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': moveback(); 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') { moveback(); } 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); } } static 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: moveback(); break; case ENTER: advancerect(DOWN); break; default: ; } } /* mouse callbacks */ static char *get_cell_text(void) { int wrow = rowmin + crow - 2, wcol = colmin + ccol - 1; char *prev = ""; SEXP tvec; if (wcol <= xmaxused) { tvec = VECTOR_ELT(work, wcol - 1); if (!isNull(tvec) && wrow < INTEGER(lens)[wcol - 1]) { 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_ELT(tvec, wrow)), CHAR(STRING_ELT(ssNA_STRING, 0)))) prev = EncodeElement(tvec, wrow, 0, '.'); } else error(G_("dataentry: internal memory error")); } } return prev; } static int online, clickline; static 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]) { highlightrect(); de_popupmenu(xw, yw, wcol); return; } else { /* in 0th column */ highlightrect(); bell(); } } else if (wrow > nhigh - 1 || wcol > nwide - 1) { /* off the grid */ highlightrect(); bell(); return; } 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(ccol + 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; addto(de); celledit = newfield_no_border(prev, rr); setbackground(celledit, p->bg); setforeground(celledit, p->ufg); settextfont(celledit, p->f); show(celledit); CellEditable = TRUE; } else if (buttons & LeftButton) { ccol = wcol; crow = wrow; } highlightrect(); return; } } static 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(); } } static void de_redraw(control c, rect r) { if (WIDTH != oldWIDTH || HEIGHT != oldHEIGHT) drawwindow(); else deredraw(); } static void deredraw(void) { int i; setcellwidths(); if(hwidth > 0) gfillrect(de, bbg, rect(0, 0, WIDTH, hwidth)); 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 (!isNull(work) I don't think it can be null */ for (i = colmin; i <= colmax; i++) drawcol(i); gfillrect(de, p->bg, rect(windowWidth+1, hwidth, WIDTH-windowWidth-1, HEIGHT - hwidth)); highlightrect(); } static void de_closewin(void) { closerect(); hide(de); del(de); } 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); copyrect(de, pt(dest_x, dest_y), rect(src_x, src_y, windowWidth - mx, windowHeight - my)); } static void copyH(int src_x, int dest_x, int width) { copyrect(de, pt(dest_x, hwidth), rect(src_x, hwidth, width, windowHeight - hwidth)); } static Rboolean initwin(void) { int i; rect r; de = newdataeditor(); if(!de) return TRUE; p = getdata(de); nboxchars = asInteger(GetOption(install("de.cellwidth"), R_GlobalEnv)); if (nboxchars == NA_INTEGER || nboxchars < 0) nboxchars = 0; if (nboxchars > 0) check(de_mvw); box_w = ((nboxchars >0)?nboxchars:FIELDWIDTH)*FW + 8; /* this used to presume 4 chars sufficed for row numbering */ labdigs = max(3, 1+floor(log10((double)ymaxused))); boxw[0] = (1+labdigs)*FW + 8; sprintf(labform, "%%%dd", labdigs); for(i = 1; i < 100; i++) boxw[i] = get_col_width(i)*FW + 8; box_h = FH + 4; text_xoffset = 5; text_yoffset = -3; setcellwidths(); nhigh = (HEIGHT - 2 * bwidth - hwidth - 3) / 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 = FALSE; bbg = dialog_bg(); /* 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/yScrollbarScale, max(nhigh/yScrollbarScale, 1), 0); setcellwidths(); gchangescrollbar(de, HWINSB, 0, xmaxused/xScrollbarScale, max(nwide/xScrollbarScale, 1), 0); show(de); show(de); /* a precaution, as PD reports transparent windows */ BringToTop(de, 0); R_de_up = TRUE; buf[BUFSIZE-1] = '\0'; return FALSE; } /* Menus */ static window wconf, devw; static radiobutton rb_num, rb_char; static label lwhat, lrb; static field varname; static int isnumeric, popupcol; static void popupclose(control c) { SEXP tvec; char buf[BUFSIZE], clab[25]; int i; buf[BUFSIZE-1] = '\0'; strncpy(buf, GA_gettext(varname), BUFSIZE-1); if(!strlen(buf)) { askok(G_("column names cannot be blank")); return; } if (popupcol > xmaxused) { /* extend work, names and lens */ REPROTECT(work = lengthgets(work, popupcol), wpi); REPROTECT(names = lengthgets(names, popupcol), npi); /* Last col name is set later */ for (i = xmaxused+1; i < popupcol - 1; i++) { sprintf(clab, "var%d", i + 1); SET_STRING_ELT(names, i, mkChar(clab)); } REPROTECT(lens = lengthgets(lens, popupcol), lpi); xmaxused = popupcol; } tvec = VECTOR_ELT(work, popupcol - 1); if(ischecked(rb_num) && !isnumeric) { if (isNull(tvec)) SET_VECTOR_ELT(work, popupcol - 1, ssNewVector(REALSXP, 100)); else SET_VECTOR_ELT(work, popupcol - 1, coerceVector(tvec, REALSXP)); } else if(ischecked(rb_char) && isnumeric) { if (isNull(tvec)) SET_VECTOR_ELT(work, popupcol - 1, ssNewVector(STRSXP, 100)); else SET_VECTOR_ELT(work, popupcol - 1, coerceVector(tvec, STRSXP)); } SET_STRING_ELT(names, popupcol - 1, mkChar(buf)); hide(wconf); del(wconf); } static void nm_hit_key(window w, int key) { if(key == '\n') popupclose(wconf); } static void de_popupmenu(int x_pos, int y_pos, int col) { char *blah; rect r = screen_coords(de); popupcol = colmin + col - 1; blah = get_col_name(popupcol); wconf = newwindow(G_("Variable editor"), rect(x_pos + r.x-150, y_pos + r.y-50, 300, 100), Titlebar | Closebox | Modal); setclose(wconf, popupclose); setbackground(wconf, bbg); lwhat = newlabel(G_("variable name"), rect(10, 22, 90, 20), AlignLeft); varname = newfield(blah, rect(100, 20, 120, 20)); lrb = newlabel(G_("type"), rect(50, 62, 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); setkeydown(wconf, nm_hit_key); show(wconf); } static void de_copy(control c) { copystringtoclipboard(get_cell_text()); } static void de_paste(control c) { char *p; closerect(); if ( clipboardhastext() && !getstringfromclipboard(buf, BUFSIZE-1) ) { /* set current cell to first line of clipboard */ CellModified = TRUE; if ((p = strchr(buf, '\n'))) *p = '\0'; clength = strlen(buf); bufp = buf + clength; closerect(); } highlightrect(); } static void de_delete(control c) { CellModified = TRUE; buf[0] = '\0'; clength = -1; bufp = buf + clength; closerect(); highlightrect(); } static void de_autosize(control c) { int col = ccol + colmin - 1; closerect(); if(col < 100) { boxw[col] = get_col_width(col)*FW + 8; deredraw(); } } static void de_stayontop(control c) { BringToTop(de, 2); } static void de_sbf(control c, int pos) { if (pos < 0) { /* horizontal */ colmin = 1 + (-pos*xScrollbarScale - 1); } else { rowmin = 1 + pos*yScrollbarScale; if(rowmin > ymaxused - nhigh + 2) rowmin = max(1,ymaxused - nhigh + 2); } drawwindow(); } static checkbox varwidths; static void vw_close(control c) { int x; if (ischecked(varwidths)) x = 0; else x = atoi(GA_gettext(varname)); /* 0 if error */ x = min(x, 50); if (x != nboxchars) { nboxchars = x; box_w = ((nboxchars >0)?nboxchars:FIELDWIDTH)*FW + 8; deredraw(); } hide(devw); del(devw); if (nboxchars > 0) check(de_mvw); else uncheck(de_mvw); addto(de); } static void vw_hit_key(window w, int key) { if(key == '\n') vw_close(w); } static void vw_callback(control c) { if (ischecked(varwidths)) disable(varname); else enable(varname); } static void de_popup_vw(void) { char blah[25]; devw = newwindow(G_("Cell width(s)"), rect(0, 0, 250, 60), Titlebar | Centered | Closebox | Modal); setclose(devw, vw_close); setbackground(devw, bbg); lwhat = newlabel(G_("Cell width"), rect(10, 20, 70, 20), AlignLeft); sprintf(blah, "%d", nboxchars); varname = newfield(blah, rect(80, 20, 40, 20)); varwidths = newcheckbox(G_("variable"), rect(150, 20, 80, 20), vw_callback); if (nboxchars == 0) { check(varwidths); disable(varname); } setkeydown(devw, vw_hit_key); show(devw); } static void menudecellwidth(control m) { de_popup_vw(); } static void deldataeditor(control m) { freeConsoleData(getdata(m)); } static void declose(control m) { de_closewin(); show(RConsole); R_de_up = FALSE; } static void deresize(console c, rect r) { if (((WIDTH == r.width) && (HEIGHT == r.height)) || (r.width == 0) || (r.height == 0) ) /* minimize */ return;; WIDTH = r.width; HEIGHT = r.height; } static void menudehelp(control m) { char s[] = GN_("Navigation.\n Keyboard: cursor keys move selection\n\tTab move right, Shift+Tab moves left\n\tPgDn or Ctrl+F: move down one screenful\n\tPgUp or Ctrl+B: move up one screenful\n\tHome: move to (1,1) cell\n\tEnd: show last rows of last column.\n Mouse: left-click in a cell, use the scrollbar(s).\n\nEditing.\n Type in the currently hightlighted cell\n Double-click in a cell for an editable field\n\nMisc.\n Ctrl-L redraws the screen, auto-resizing the columns\n Ctrl-C copies selected cell\n Ctrl-V pastes to selected cell\n Right-click menu for copy, paste, autosize currently selected column\n\n"); askok(G_(s)); } static MenuItem DePopup[28] = { {GN_("Help"), menudehelp, 0}, {"-", 0, 0}, {GN_("Copy selected cell"), de_copy, 0}, {GN_("Paste to selected cell"), de_paste, 0}, {GN_("Autosize column"), de_autosize, 0}, {"-", 0, 0}, {GN_("Stay on top"), de_stayontop, 0}, {"-", 0, 0}, {GN_("Close"), declose, 0}, LASTMENUITEM }; static void demenuact(control m) { /* use this to customize the menu */ } static void depopupact(control m) { /* use this to customize the menu */ if (ismdi()) disable(DePopup[6].m); else { if (isTopmost(de)) check(DePopup[6].m); else uncheck(DePopup[6].m); } } #define MCHECK(a) if (!(a)) {del(c);return NULL;} RECT *RgetMDIsize(); /* in rui.c */ static dataeditor newdataeditor(void) { ConsoleData p; int w, h, x, y; dataeditor c; menuitem m; p = newconsoledata((consolefn) ? consolefn : FixedFont, pagerrow, pagercol, 0, 0, consolefg, consoleuser, consolebg, DATAEDITOR, 0); if (!p) return NULL; w = WIDTH ; h = HEIGHT; if (ismdi()) { RECT *pR = RgetMDIsize(); x = (pR->right - w) / 3; x = x > 20 ? x:20; y = (pR->bottom - h) / 3; y = y > 20 ? y:20; } else { x = (devicewidth(NULL) - w) / 3; y = (deviceheight(NULL) - h) / 3 ; } c = (dataeditor) newwindow(G_("Data Editor"), rect(x, y, w, h), Document | StandardWindow | Menubar | VScrollbar | HScrollbar | TrackMouse); if (!c) { freeConsoleData(p); return NULL; } setdata(c, p); if(h == 0) HEIGHT = getheight(c); if(w == 0) WIDTH = getwidth(c); COLS = WIDTH / FW - 1; ROWS = HEIGHT / FH - 1; BORDERX = (WIDTH - COLS*FW) / 2; BORDERY = (HEIGHT - ROWS*FH) / 2; gsetcursor(c, ArrowCursor); setbackground(c, consolebg); if (ismdi() && (RguiMDI & RW_TOOLBAR)) { /* blank toolbar to stop windows jumping around */ int btsize = 24; control tb; addto(c); MCHECK(tb = newtoolbar(btsize + 4)); gsetcursor(tb, ArrowCursor); } MCHECK(gpopup(depopupact, DePopup)); MCHECK(m = newmenubar(demenuact)); MCHECK(newmenu(G_("File"))); /* MCHECK(m = newmenuitem("-", 0, NULL));*/ MCHECK(m = newmenuitem(G_("Close"), 0, declose)); newmdimenu(); MCHECK(newmenu(G_("Edit"))); MCHECK(m = newmenuitem(G_("Copy \tCTRL+C"), 0, de_copy)); MCHECK(m = newmenuitem(G_("Paste \tCTRL+V"), 0, de_paste)); MCHECK(m = newmenuitem(G_("Delete\tDEL"), 0, de_delete)); MCHECK(m = newmenuitem("-", 0, NULL)); MCHECK(de_mvw = newmenuitem(G_("Cell widths ..."), 0, menudecellwidth)); MCHECK(m = newmenu(G_("Help"))); MCHECK(newmenuitem(G_("Data editor"), 0, menudehelp)); setdata(c, p); setresize(c, deresize); setredraw(c, de_redraw); setdel(c, deldataeditor); setclose(c, declose); sethit(c, de_sbf); setkeyaction(c, de_ctrlkeyin); setkeydown(c, de_normalkeyin); setmousedown(c, de_mousedown); setmouseup(c, de_mouseup); return(c); }