/* * R : A Computer Langage for Statistical Data Analysis * Copyright (C) 1995, 1996 Robert Gentleman and Ross Ihaka * Copyright (C) 1998--2007 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, a copy is available at * http://www.r-project.org/Licenses/ */ /* 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" typedef enum {UNKNOWNN, NUMERIC, CHARACTER} CellType; /* Used to check if eventloop needs to be run */ 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 && DE->nboxchars == 0) ? DE->boxw[x] : DE->box_w), DE->p->w - DE->boxw[0] - 2*DE->bwidth - 2)) #define FIELDWIDTH 10 #define BUFSIZE 200 typedef struct { dataeditor de; ConsoleData p; 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 windowHeight; /* current height of the window */ int currentexp; /* boolean: whether an cell is active */ int crow; /* current row */ int ccol; /* current column */ int nwide, nhigh; int colmax, colmin, rowmax, rowmin; int ndecimal; /* count decimal points */ int ne; /* count exponents */ int nneg; /* indicate whether its a negative */ int clength; /* number of characters currently entered */ int inSpecial; char buf[BUFSIZE]; char *bufp; int bwidth; /* width of the border */ int hwidth; /* width of header */ int text_xoffset, text_yoffset; field celledit; Rboolean CellModified, CellEditable; int xmaxused, ymaxused; int oldWIDTH, oldHEIGHT; int nboxchars; char labform[6]; int xScrollbarScale, yScrollbarScale; SEXP work, names, lens; PROTECT_INDEX wpi, npi, lpi; menuitem de_mvw; SEXP ssNA_STRING; Rboolean isEditor; } destruct, *DEstruct; /* Local Function Definitions */ static void advancerect(DEstruct, int); static void bell(); static void cleararea(DEstruct, int, int, int, int, rgb); static void clearrect(DEstruct); static void closerect(DEstruct); static void clearwindow(DEstruct); static void de_closewin(DEstruct); static void copyarea(DEstruct, int, int, int, int); static void copyH(DEstruct, int, int, int); static void deredraw(DEstruct); static void downlightrect(DEstruct); static void drawwindow(DEstruct); static void drawcol(DEstruct, int); /* static void de_drawline(int, int, int, int);*/ static void de_drawtext(DEstruct, int, int, char *); static void drawrectangle(DEstruct, int, int, int, int, int, int); static void drawrow(DEstruct, int); static void find_coords(DEstruct, int, int, int*, int*); static void handlechar(DEstruct, char*); static void highlightrect(DEstruct); static Rboolean initwin(DEstruct, const char *); static void jumppage(DEstruct, int); static void jumpwin(DEstruct, int, int); static void de_popupmenu(DEstruct, 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 setcellwidths(DEstruct); static dataeditor newdataeditor(DEstruct, const char *); static void de_copy(control c); static void de_paste(control c); static void de_delete(control c); #define WIN32_LEAN_AND_MEAN 1 #include /* for Sleep */ extern int mb_char_len(char *, int, wchar_t *); /* from console.c */ static void moveback(DEstruct DE) { int mb_len; wchar_t wc; if (DE->clength > 0) { mb_len = mb_char_len(DE->buf, DE->clength-1, &wc); DE->clength -= mb_len; DE->bufp -= mb_len; printstring(DE, DE->buf, DE->clength, DE->crow, DE->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 strings so that we can differentiate between uninitialized elements in the vectors and user supplied NA's; hence ssNA_STRING */ static SEXP ssNewVector(DEstruct DE, 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, DE->ssNA_STRING); return (tvec); } static void de_closewin_cend(void *DE) { de_closewin((DEstruct) DE); } 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]; destruct DE1; DEstruct DE = &DE1; checkArity(op, args); DE->isEditor = TRUE; 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, G_("invalid argument")); /* initialize the constants */ DE->bufp = DE->buf; DE->ne = 0; DE->currentexp = 0; DE->nneg = 0; DE->ndecimal = 0; DE->clength = 0; DE->inSpecial = 0; DE->ccol = 1; DE->crow = 1; DE->colmin = 1; DE->rowmin = 1; PROTECT(DE->ssNA_STRING = duplicate(NA_STRING)); nprotect++; DE->bwidth = 0; DE->hwidth = 5; /* 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(DE, type, 100)); } else if (!isVector(VECTOR_ELT(DE->work, i))) errorcall(call, G_("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)); } } /* scale scrollbars as needed */ if (DE->xmaxused > 10000) DE->xScrollbarScale = DE->xmaxused/1000; if (DE->ymaxused > 10000) DE->yScrollbarScale = DE->ymaxused/1000; /* start up the window, more initializing in here */ if (initwin(DE, G_("Data Editor"))) errorcall(call, G_("invalid device")); R_de_up = TRUE; /* 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 = (void *)DE; highlightrect(DE); while (R_de_up) { /* avoid consuming 100% CPU time here */ Sleep(10); R_ProcessEvents(); } endcontext(&cntxt); /* 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(DE, 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) != DE->ssNA_STRING) 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, DE->names); UNPROTECT(nprotect); return work2; } /* Window Drawing Routines */ static rgb bbg; 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->p->w || (!DE->isEditor && i > DE->xmaxused - DE->colmin + 1) ) { DE->nwide = i; DE->windowWidth = w - dw; break; } } } static void drawwindow(DEstruct DE) { /* might have resized */ setcellwidths(DE); DE->nhigh = (DE->p->h - 2 * DE->bwidth - DE->hwidth - 3) / DE->box_h; if(!DE->isEditor && DE->nhigh > DE->ymaxused+1) DE->nhigh = DE->ymaxused+1; DE->windowHeight = DE->nhigh * DE->box_h + 2 * DE->bwidth + DE->hwidth; DE->oldWIDTH = DE->p->w; DE->oldHEIGHT = DE->p->h; clearwindow(DE); deredraw(DE); /* row/col 1 = pos 0 */ gchangescrollbar(DE->de, VWINSB, (DE->rowmin - 1)/DE->yScrollbarScale, DE->ymaxused/DE->yScrollbarScale, max(DE->nhigh/DE->yScrollbarScale, 1), 0); gchangescrollbar(DE->de, HWINSB, (DE->colmin - 1)/DE->xScrollbarScale, DE->xmaxused/DE->xScrollbarScale, max(DE->nwide/DE->xScrollbarScale, 1), 0); } 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->hwidth, DE->p->w-dw, DE->p->h, DE->p->bg); /* 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->hwidth, DE->p->w-dw, DE->p->h, DE->p->bg); drawcol(DE, DE->colmin); } gchangescrollbar(DE->de, HWINSB, (DE->colmin - 1)/DE->xScrollbarScale, DE->xmaxused/DE->xScrollbarScale, max(DE->nwide/DE->xScrollbarScale, 1), 0); highlightrect(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->hwidth + 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; deredraw(DE); gchangescrollbar(DE->de, VWINSB, (DE->rowmin - 1)/DE->yScrollbarScale, DE->ymaxused/DE->yScrollbarScale, max(DE->nhigh/DE->yScrollbarScale, 1), 0); gchangescrollbar(DE->de, HWINSB, (DE->colmin - 1)/DE->xScrollbarScale, DE->xmaxused/DE->xScrollbarScale, max(DE->nwide/DE->xScrollbarScale, 1), 0); } else highlightrect(DE); } static void advancerect(DEstruct DE, int 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->crow == (DE->nhigh - 1)) jumppage(DE, DOWN); else DE->crow++; break; case RIGHT: 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); } static const char *get_col_name(DEstruct DE, int col) { static char clab[25]; if (col <= DE->xmaxused) { /* don't use NA labels */ SEXP tmp = STRING_ELT(DE->names, col - 1); if(tmp != NA_STRING) return(CHAR(tmp)); } sprintf(clab, "var%d", col); return clab; } static int get_col_width(DEstruct DE, int col) { int i, w = 0, w1, fw = FIELDWIDTH; const char *strp; SEXP tmp, lab; if (DE->nboxchars > 0) return DE->nboxchars; if (col <= DE->xmaxused) { tmp = VECTOR_ELT(DE->work, col - 1); if (isNull(tmp)) return fw; /* don't use NA labels */ lab = STRING_ELT(DE->names, col - 1); if(lab != NA_STRING) w = strlen(CHAR(lab)); else w = fw; PrintDefaults(R_NilValue); for (i = 0; i < INTEGER(DE->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(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, DE->p->bg); cleararea(DE, src_x, src_y, bw, DE->box_h, bbg); for (i = 0; i < DE->nhigh; i++) drawrectangle(DE, src_x, DE->hwidth + 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, 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); } } } /* 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, (whichrow > 0) ? DE->p->bg : bbg); drawrectangle(DE, src_x, src_y, DE->boxw[0], DE->box_h, 1, 1); sprintf(rlab, DE->labform, whichrow); printstring(DE, rlab, 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); } } /* 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(R_NilValue); if (TYPEOF(invec) == REALSXP) { strp = EncodeElement(invec, vrow, 0, '.'); printstring(DE, strp, strlen(strp), ssrow, sscol, 0); } else if (TYPEOF(invec) == STRSXP) { if (STRING_ELT(invec, vrow) != DE->ssNA_STRING) { strp = EncodeElement(invec, vrow, 0, '.'); printstring(DE, strp, strlen(strp), ssrow, sscol, 0); } } else error(G_("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, 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); } } static void jumppage(DEstruct DE, int dir) { int i, w, oldcol, wcol; switch (dir) { case UP: DE->rowmin--; DE->rowmax--; copyarea(DE, 0, DE->hwidth + DE->box_h, 0, DE->hwidth + 2 * DE->box_h); drawrow(DE, DE->rowmin); gchangescrollbar(DE->de, VWINSB, (DE->rowmin - 1)/DE->yScrollbarScale, DE->ymaxused/DE->yScrollbarScale, max(DE->nhigh/DE->yScrollbarScale, 1), 0); break; case DOWN: if (DE->rowmax >= 65535) return; DE->rowmin++; DE->rowmax++; copyarea(DE, 0, DE->hwidth + 2 * DE->box_h, 0, DE->hwidth + DE->box_h); drawrow(DE, DE->rowmax); gchangescrollbar(DE->de, VWINSB, (DE->rowmin - 1)/DE->yScrollbarScale, DE->ymaxused/DE->yScrollbarScale, max(DE->nhigh/DE->yScrollbarScale, 1), 0); 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->p->w - 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); } static void downlightrect(DEstruct DE) { printrect(DE, 2, 0); printrect(DE, 1, 1); } static void highlightrect(DEstruct DE) { if(DE->isEditor) printrect(DE, 2, 1); else printrect(DE, 1, 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(DE, REALSXP, max(100, wrow))); INTEGER(DE->lens)[wcol - 1] = 0; } if (!isVector(tmp = VECTOR_ELT(DE->work, wcol - 1))) error(G_("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(DE, 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(DE->work, wcol - 1, tmp2); } return newcol; } /* 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 wcol = DE->ccol + DE->colmin - 1, wrow = DE->rowmin + DE->crow - 1, wrow0; Rboolean newcol; *(DE->bufp) = '\0'; if (DE->CellModified || DE->CellEditable) { if (DE->CellEditable) { strncpy(DE->buf, GA_gettext(DE->celledit), BUFSIZE-1); DE->clength = strlen(DE->buf); hide(DE->celledit); del(DE->celledit); } 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 (DE->clength != 0) { /* do it this way to ensure NA, Inf, ... can get set */ char *endp; double new = R_strtod(DE->buf, &endp); int warn = !isBlankString(endp); if (TYPEOF(cvec) == STRSXP) SET_STRING_ELT(cvec, wrow - 1, mkChar(DE->buf)); 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(DE->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 */ } DE->CellEditable = DE->CellModified = FALSE; downlightrect(DE); gsetcursor(DE->de, ArrowCursor); DE->ndecimal = 0; DE->nneg = 0; DE->ne = 0; DE->currentexp = 0; DE->clength = 0; DE->inSpecial = 0; DE->bufp = DE->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(DEstruct DE, const char *ibuf, int buflen, int row, int col, int left) { int x_pos, y_pos, bw, fw, bufw; char buf[BUFSIZE+1]; 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 + 1, y_pos + 1, bw - 1, DE->box_h - 1, (row==0 || col==0) ? bbg:DE->p->bg); fw = min(BUFSIZE, (bw - 8)/(DE->p->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(DE, x_pos + DE->text_xoffset, y_pos - DE->text_yoffset, buf); } 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, DE->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(DEstruct DE, char *text) { int c = text[0]; if ( c == '\033' ) { DE->CellModified = FALSE; DE->clength = 0; drawelt(DE, DE->crow, DE->ccol); gsetcursor(DE->de, ArrowCursor); return; } else { DE->CellModified = TRUE; gsetcursor(DE->de, TextCursor); } if (DE->clength == 0) { switch(get_col_type(DE, DE->ccol + DE->colmin - 1)) { case NUMERIC: DE->currentexp = 1; break; default: DE->currentexp = 2; } clearrect(DE); highlightrect(DE); } if (DE->currentexp == 1) /* we are parsing a number */ switch (c) { case '-': if (DE->nneg == 0) DE->nneg++; else goto donehc; break; case '.': if (DE->ndecimal == 0) DE->ndecimal++; else goto donehc; break; case 'e': case 'E': if (DE->ne == 0) { DE->nneg = DE->ndecimal = 0; /* might have decimal in exponent */ DE->ne++; } else goto donehc; break; case 'N': if(DE->nneg) goto donehc; case 'I': DE->inSpecial++; break; default: if (!DE->inSpecial && !isdigit((int)text[0])) goto donehc; break; } if (DE->clength++ > 199) { warning(G_("dataentry: expression too long")); DE->clength--; goto donehc; } *(DE->bufp)++ = text[0]; printstring(DE, DE->buf, DE->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, strlen(p), 0, i - DE->colmin + 1, 0); } for (i = DE->rowmin; i <= DE->rowmax; i++) { sprintf(clab, DE->labform, i); printstring(DE, clab, strlen(clab), i - DE->rowmin + 1, 0, 0); } } /* ================ GraphApp-specific ================ */ static void bell(void) { gabeep(); } static void cleararea(DEstruct DE, int xpos, int ypos, int width, int height, rgb col) { gfillrect(DE->de, col, rect(xpos, ypos, width, height)); } static void clearwindow(DEstruct DE) { gfillrect(DE->de, DE->p->bg, rect(0, 0, DE->p->w, DE->p->h)); } static void drawrectangle(DEstruct DE, int xpos, int ypos, int width, int height, int lwd, int fore) { /* only used on screen, so always fast */ gdrawrect(DE->de, lwd, 0, (fore==1)? DE->p->ufg: DE->p->bg, rect(xpos, ypos, width, height), 1, PS_ENDCAP_SQUARE, PS_JOIN_BEVEL, 10); } static void de_drawtext(DEstruct DE, int xpos, int ypos, char *text) { gdrawstr(DE->de, DE->p->f, DE->p->fg, pt(xpos, ypos), text); } /* Keypress callbacks */ static void de_normalkeyin(control c, int k) { int i, st; char text[1]; DEstruct DE = getdata(c); st = ggetkeystate(); if ((DE->p->chbrk) && (k == DE->p->chbrk) && ((!DE->p->modbrk) || ((DE->p->modbrk) && (st == DE->p->modbrk)))) { DE->p->fbrk(c); return; } if (st & CtrlKey) { switch (k + 'A' - 1) { case 'B': i = DE->rowmin - DE->nhigh + 2; jumpwin(DE, DE->colmin, max(i, 1)); break; case 'F': jumpwin(DE, DE->colmin, DE->rowmax); break; case 'H': moveback(DE); break; case 'I': if (st & ShiftKey) advancerect(DE, LEFT); else advancerect(DE, RIGHT); break; case 'N': case 'J': advancerect(DE, DOWN); break; case 'C': de_copy(DE->de); break; case 'V': de_paste(DE->de); break; case 'L': for (i = DE->colmin; i < DE->colmax; i++) if (i < 100) DE->boxw[i] = get_col_width(DE, i)*(DE->p->fw) + 8; drawwindow(DE); break; default: bell(); } } else if(k == '\b') { moveback(DE); } else if(k == '\n' || k == '\r') { advancerect(DE, DOWN); } else if(k == '\t') { if (st & ShiftKey) advancerect(DE, LEFT); else advancerect(DE, RIGHT); } else { text[0] = k; handlechar(DE, text); } } static void de_ctrlkeyin(control c, int key) { int st, i; DEstruct DE = getdata(c); st = ggetkeystate(); if ((DE->p->chbrk) && (key == DE->p->chbrk) && ((!DE->p->modbrk) || ((DE->p->modbrk) && (st == DE->p->modbrk)))) { DE->p->fbrk(c); return; } switch (key) { case HOME: jumpwin(DE, 1, 1); downlightrect(DE); DE->crow = DE->ccol = 1; highlightrect(DE); break; case END: i = DE->ymaxused - DE->nhigh + 2; if(DE->isEditor) jumpwin(DE, DE->xmaxused, max(i, 1)); else { /* Try to work out which cols we can fit in */ int j, w = 0; for(j = DE->xmaxused;j >= 0; j--) { w += BOXW(j); if(w > DE->p->w) break; } jumpwin(DE, min(j+2, DE->xmaxused), max(i, 1)); } downlightrect(DE); DE->crow = DE->ymaxused - DE->rowmin + 1; DE->ccol = 1; highlightrect(DE); break; case PGUP: i = DE->rowmin - DE->nhigh + 2; jumpwin(DE, DE->colmin, max(i, 1)); break; case PGDN: i = DE->ymaxused - DE->nhigh + 2; if(DE->isEditor) jumpwin(DE, DE->colmin, DE->rowmax); else jumpwin(DE, DE->colmin, min(i, DE->rowmax)); break; case LEFT: advancerect(DE, LEFT); break; case RIGHT: advancerect(DE, RIGHT); break; case UP: advancerect(DE, UP); break; case DOWN: advancerect(DE, DOWN); break; case DEL: moveback(DE); break; case ENTER: advancerect(DE, DOWN); break; default: ; } } /* mouse callbacks */ static const char *get_cell_text(DEstruct DE) { int wrow = DE->rowmin + DE->crow - 2, wcol = DE->colmin + DE->ccol - 1; const char *prev = ""; SEXP tvec; if (wcol <= DE->xmaxused) { tvec = VECTOR_ELT(DE->work, wcol - 1); if (!isNull(tvec) && wrow < INTEGER(DE->lens)[wcol - 1]) { PrintDefaults(R_NilValue); if (TYPEOF(tvec) == REALSXP) { prev = EncodeElement(tvec, wrow, 0, '.'); } else if (TYPEOF(tvec) == STRSXP) { if (STRING_ELT(tvec, wrow) != DE->ssNA_STRING) 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; DEstruct DE; if (buttons & LeftButton) { DE = getdata(c); xw = xy.x; yw = xy.y; closerect(DE); /* check to see if the click was in the header */ if (yw < DE->hwidth + DE->bwidth) { /* too high */ return; } /* translate to box coordinates */ wrow = (yw - DE->bwidth - DE->hwidth) / DE->box_h; /* see if it is in the row labels */ if (xw < DE->bwidth + DE->boxw[0]) { bell(); highlightrect(DE); return; } w = DE->bwidth + DE->boxw[0]; for (i = 1; i <= DE->nwide; i++) if((w += BOXW(i+DE->colmin-1)) > xw) { wcol = i; break; } /* see if we selected a line */ w = DE->bwidth; online = 0; for (i = 0; i <= DE->nwide; i++) { if(i == 0) w += DE->boxw[0]; else w += BOXW(i+DE->colmin-1); if (abs(w - xw) <= 2) { online = 1; clickline = i; /* between cols i and i+1 */ highlightrect(DE); gsetcursor(DE->de, HandCursor); return; } } /* next check to see if it is in the column labels */ if (yw < DE->hwidth + DE->bwidth + DE->box_h) { if (xw > DE->bwidth + DE->boxw[0]) { highlightrect(DE); de_popupmenu(DE, xw, yw, wcol); return; } else { /* in 0th column */ highlightrect(DE); bell(); } } else if (wrow > DE->nhigh - 1 || wcol > DE->nwide - 1) { /* off the grid */ highlightrect(DE); bell(); return; } else if (buttons & DblClick) { int x, y, bw; const char *prev; rect rr; DE->ccol = wcol; DE->crow = wrow; highlightrect(DE); find_coords(DE, DE->crow, DE->ccol, &x, &y); bw = BOXW(DE->ccol + DE->colmin - 1); rr = rect(x + DE->text_xoffset, y - DE->text_yoffset - 1, bw - DE->text_xoffset - 2, DE->box_h - DE->text_yoffset - 2); prev = get_cell_text(DE); if (strlen(prev) * (DE->p->fw) > bw) rr.width = (strlen(prev) + 2) * (DE->p->fw); addto(DE->de); DE->celledit = newfield_no_border(prev, rr); setbackground(DE->celledit, DE->p->bg); setforeground(DE->celledit, DE->p->ufg); settextfont(DE->celledit, DE->p->f); show(DE->celledit); DE->CellEditable = TRUE; } else if (buttons & LeftButton) { DE->ccol = wcol; DE->crow = wrow; } highlightrect(DE); return; } } static void de_mouseup(control c, int buttons, point xy) { int xw, bw, i, w; DEstruct DE; if (online) { DE = getdata(c); xw = xy.x; w = DE->bwidth + DE->boxw[0]; for(i = 1; i < clickline; i++) w+= BOXW(i+DE->colmin-1); bw = xw - w; if (bw < (DE->p->fw)*4 + 8) bw = (DE->p->fw)*4 + 8; if (bw > (DE->p->fw)*50) bw = (DE->p->fw)*50; if(clickline < 100) DE->boxw[clickline] = bw; gsetcursor(DE->de, ArrowCursor); deredraw(DE); } } static void de_redraw(control c, rect r) { DEstruct DE = getdata(c); if (DE->p->w != DE->oldWIDTH || DE->p->h != DE->oldHEIGHT) drawwindow(DE); else deredraw(DE); } static void deredraw(DEstruct DE) { int i; setcellwidths(DE); if(DE->hwidth > 0) gfillrect(DE->de, bbg, rect(0, 0, DE->p->w, DE->hwidth)); gfillrect(DE->de, bbg, rect(0, 0, DE->boxw[0], DE->windowHeight)); for (i = 1; i < DE->nhigh; i++) drawrectangle(DE, 0, DE->hwidth + i * DE->box_h, DE->boxw[0], DE->box_h, 1, 1); if(DE->isEditor) { DE->colmax = DE->colmin + (DE->nwide - 2); DE->rowmax = DE->rowmin + (DE->nhigh - 2); } else { DE->colmax = min(DE->xmaxused, DE->colmin + (DE->nwide - 2)); DE->rowmax = min(DE->ymaxused, DE->rowmin + (DE->nhigh - 2)); } printlabs(DE); for (i = DE->colmin; i <= DE->colmax; i++) drawcol(DE,i); gfillrect(DE->de, DE->p->bg, rect(DE->windowWidth+1, DE->hwidth, DE->p->w - DE->windowWidth-1, DE->p->h - DE->hwidth)); highlightrect(DE); } static void de_closewin(DEstruct DE) { closerect(DE); hide(DE->de); del(DE->de); } 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); copyrect(DE->de, pt(dest_x, dest_y), rect(src_x, src_y, DE->windowWidth - mx, DE->windowHeight - my)); } static void copyH(DEstruct DE, int src_x, int dest_x, int width) { copyrect(DE->de, pt(dest_x, DE->hwidth), rect(src_x, DE->hwidth, width, DE->windowHeight - DE->hwidth)); } static Rboolean initwin(DEstruct DE, const char *title) { int i, labdigs; rect r; DE->de = newdataeditor(DE, title); if(!DE->de) return TRUE; DE->oldWIDTH = DE->oldHEIGHT = 0; DE->nboxchars = 5; DE->xScrollbarScale = DE->yScrollbarScale = 1; DE->nboxchars = asInteger(GetOption(install("de.cellwidth"), R_GlobalEnv)); if (DE->nboxchars == NA_INTEGER || DE->nboxchars < 0) DE->nboxchars = 0; if (DE->nboxchars > 0) check(DE->de_mvw); DE->box_w = ((DE->nboxchars >0)?DE->nboxchars:FIELDWIDTH)*(DE->p->fw) + 8; /* this used to presume 4 chars sufficed for row numbering */ labdigs = max(3, 1+floor(log10((double)DE->ymaxused))); DE->boxw[0] = (1+labdigs)*(DE->p->fw) + 8; sprintf(DE->labform, "%%%dd", labdigs); for(i = 1; i < 100; i++) DE->boxw[i] = get_col_width(DE, i) * (DE->p->fw) + 8; DE->box_h = (DE->p->fh) + 4; DE->text_xoffset = 5; DE->text_yoffset = -3; setcellwidths(DE); DE->nhigh = (DE->p->h - 2 * DE->bwidth - DE->hwidth - 3) / DE->box_h; if(!DE->isEditor && DE->nhigh > DE->ymaxused+1) DE->nhigh = DE->ymaxused+1; DE->windowHeight = DE->nhigh * DE->box_h + 2 * DE->bwidth + DE->hwidth; r = getrect(DE->de); r.width = DE->windowWidth + 3; r.height = DE->windowHeight + 3; resize(DE->de, r); DE->CellModified = DE->CellEditable = FALSE; bbg = dialog_bg(); /* set the active cell to be the upper left one */ DE->crow = 1; DE->ccol = 1; /* drawwindow(); done as repaint but decide if we need scrollbars here to avoid flashing*/ DE->nhigh = (DE->p->h - 2 * DE->bwidth - DE->hwidth) / DE->box_h; gchangescrollbar(DE->de, VWINSB, 0, DE->ymaxused/DE->yScrollbarScale, max(DE->nhigh/DE->yScrollbarScale, 1), 0); setcellwidths(DE); gchangescrollbar(DE->de, HWINSB, 0, DE->xmaxused/DE->xScrollbarScale, max(DE->nwide/DE->xScrollbarScale, 1), 0); show(DE->de); show(DE->de); /* a precaution, as PD reports transparent windows */ BringToTop(DE->de, 0); DE->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; DEstruct DE = getdata(c); buf[BUFSIZE-1] = '\0'; strncpy(buf, GA_gettext(varname), BUFSIZE-1); if(!strlen(buf)) { askok(G_("column names cannot be blank")); return; } 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); /* Last col name is set later */ for (i = DE->xmaxused+1; i < popupcol - 1; 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); if(ischecked(rb_num) && !isnumeric) { if (isNull(tvec)) SET_VECTOR_ELT(DE->work, popupcol - 1, ssNewVector(DE, REALSXP, 100)); else SET_VECTOR_ELT(DE->work, popupcol - 1, coerceVector(tvec, REALSXP)); } else if(ischecked(rb_char) && isnumeric) { if (isNull(tvec)) SET_VECTOR_ELT(DE->work, popupcol - 1, ssNewVector(DE, STRSXP, 100)); else SET_VECTOR_ELT(DE->work, popupcol - 1, coerceVector(tvec, STRSXP)); } SET_STRING_ELT(DE->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(DEstruct DE, int x_pos, int y_pos, int col) { const char *blah; rect r = screen_coords(DE->de); popupcol = DE->colmin + col - 1; blah = get_col_name(DE, 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(DE, popupcol) == NUMERIC); if (isnumeric) check(rb_num); else check(rb_char); setkeydown(wconf, nm_hit_key); setdata(wconf, DE); /* for popupclose, nm_hit_key */ show(wconf); } static void de_copy(control c) { DEstruct DE = getdata(c); copystringtoclipboard(get_cell_text(DE)); } static void de_paste(control c) { char *p; DEstruct DE = getdata(c); closerect(DE); if ( clipboardhastext() && !getstringfromclipboard(DE->buf, BUFSIZE-1) ) { /* set current cell to first line of clipboard */ DE->CellModified = TRUE; if ((p = strchr(DE->buf, '\n'))) *p = '\0'; DE->clength = strlen(DE->buf); DE->bufp = DE->buf + DE->clength; closerect(DE); } highlightrect(DE); } static void de_delete(control c) { DEstruct DE = getdata(c); DE->CellModified = TRUE; DE->buf[0] = '\0'; DE->clength = 0; DE->bufp = DE->buf + DE->clength; closerect(DE); highlightrect(DE); } static void de_autosize(control c) { DEstruct DE = getdata(c); int col = DE->ccol + DE->colmin - 1; closerect(DE); if(col < 100) { DE->boxw[col] = get_col_width(DE,col)*(DE->p->fw) + 8; deredraw(DE); } } static void de_stayontop(control c) { DEstruct DE = getdata(c); BringToTop(DE->de, 2); } static void de_sbf(control c, int pos) { DEstruct DE = getdata(c); if (pos < 0) { /* horizontal */ DE->colmin = min(DE->xmaxused, -pos*DE->xScrollbarScale); } else { DE->rowmin = 1 + pos*DE->yScrollbarScale; if(DE->rowmin > DE->ymaxused - DE->nhigh + 2) DE->rowmin = max(1,DE->ymaxused - DE->nhigh + 2); } drawwindow(DE); } static checkbox varwidths; static void vw_close(control c) { int x; DEstruct DE = getdata(c); if (ischecked(varwidths)) x = 0; else x = atoi(GA_gettext(varname)); /* 0 if error */ x = min(x, 50); if (x != DE->nboxchars) { DE->nboxchars = x; DE->box_w = ((DE->nboxchars >0)?DE->nboxchars:FIELDWIDTH)*(DE->p->fw) + 8; deredraw(DE); } hide(devw); del(devw); if (DE->nboxchars > 0) check(DE->de_mvw); else uncheck(DE->de_mvw); addto(DE->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(DEstruct DE) { char blah[25]; devw = newwindow(G_("Cell width(s)"), rect(0, 0, 250, 60), Titlebar | Centered | Closebox | Modal); setdata(devw, DE); setclose(devw, vw_close); setbackground(devw, bbg); lwhat = newlabel(G_("Cell width"), rect(10, 20, 70, 20), AlignLeft); sprintf(blah, "%d", DE->nboxchars); varname = newfield(blah, rect(80, 20, 40, 20)); varwidths = newcheckbox(G_("variable"), rect(150, 20, 80, 20), vw_callback); if (DE->nboxchars == 0) { check(varwidths); disable(varname); } setkeydown(devw, vw_hit_key); show(devw); } static void menudecellwidth(control m) { de_popup_vw(getdata(m)); } static void deldataeditor(control m) { DEstruct DE = getdata(m); freeConsoleData(DE->p); } static void declose(control m) { DEstruct DE = getdata(m); de_closewin(DE); show(RConsole); R_de_up = FALSE; } static void deresize(console c, rect r) { DEstruct DE = getdata(c); if (((DE->p->w == r.width) && (DE->p->h == r.height)) || (r.width == 0) || (r.height == 0) ) /* minimize */ return;; DE->p->w = r.width; DE->p->h = 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) { DEstruct DE = getdata(m); /* use this to customize the menu */ if (ismdi()) disable(DePopup[6].m); else { if (isTopmost(DE->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(DEstruct DE, const char *title) { int w, h, x, y; dataeditor c; menuitem m; DE->p = newconsoledata((consolefn) ? consolefn : FixedFont, pagerrow, pagercol, 0, 0, consolefg, consoleuser, consolebg, DATAEDITOR, 0); if (!DE->p) return NULL; w = DE->p->w ; h = DE->p->h; 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(title, rect(x, y, w, h), Document | StandardWindow | Menubar | VScrollbar | HScrollbar | TrackMouse); if (!c) { freeConsoleData(DE->p); return NULL; } setdata(c, DE); if(h == 0) DE->p->h = getheight(c); if(w == 0) DE->p->w = getwidth(c); (DE->p->cols) = DE->p->w / (DE->p->fw) - 1; (DE->p->rows) = DE->p->h / (DE->p->fh) - 1; DE->p->right = (DE->p->w - (DE->p->cols)*(DE->p->fw)) / 2; DE->p->top = (DE->p->h - (DE->p->rows)*(DE->p->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); } if(DE->isEditor) { MCHECK(m = gpopup(depopupact, DePopup)); setdata(m, DE); setdata(DePopup[2].m, DE); setdata(DePopup[3].m, DE); setdata(DePopup[4].m, DE); setdata(DePopup[6].m, DE); setdata(DePopup[8].m, DE); } MCHECK(m = newmenubar(demenuact)); MCHECK(newmenu(G_("File"))); /* MCHECK(m = newmenuitem("-", 0, NULL));*/ MCHECK(m = newmenuitem(G_("Close"), 0, declose)); setdata(m, DE); if(DE->isEditor) { newmdimenu(); MCHECK(newmenu(G_("Edit"))); MCHECK(m = newmenuitem(G_("Copy \tCTRL+C"), 0, de_copy)); setdata(m, DE); MCHECK(m = newmenuitem(G_("Paste \tCTRL+V"), 0, de_paste)); setdata(m, DE); MCHECK(m = newmenuitem(G_("Delete\tDEL"), 0, de_delete)); setdata(m, DE); MCHECK(m = newmenuitem("-", 0, NULL)); MCHECK(m = DE->de_mvw = newmenuitem(G_("Cell widths ..."), 0, menudecellwidth)); setdata(m, DE); MCHECK(m = newmenu(G_("Help"))); MCHECK(newmenuitem(G_("Data editor"), 0, menudehelp)); } setdata(c, DE); /* Why the repeat? */ setresize(c, deresize); setredraw(c, de_redraw); setdel(c, deldataeditor); setclose(c, declose); sethit(c, de_sbf); setkeyaction(c, de_ctrlkeyin); if(DE->isEditor) { setkeydown(c, de_normalkeyin); setmousedown(c, de_mousedown); setmouseup(c, de_mouseup); } return(c); } static void dv_closewin_cend(void *data) { DEstruct DE = (DEstruct) data; R_ReleaseObject(DE->lens); R_ReleaseObject(DE->work); de_closewin(DE); free(DE); } SEXP do_dataviewer(SEXP call, SEXP op, SEXP args, SEXP rho) { SEXP stitle; SEXPTYPE type; int i, nprotect; RCNTXT cntxt; DEstruct DE = (DEstruct) malloc(sizeof(destruct)); checkArity(op, args); DE->isEditor = FALSE; nprotect = 0;/* count the PROTECT()s */ DE->work = CAR(args); DE->names = getAttrib(DE->work, R_NamesSymbol); if (TYPEOF(DE->work) != VECSXP) errorcall(call, G_("invalid argument")); stitle = CADR(args); if (!isString(stitle) || LENGTH(stitle) != 1) errorcall(call, G_("invalid argument")); /* initialize the constants */ DE->bufp = DE->buf; DE->ne = 0; DE->currentexp = 0; DE->nneg = 0; DE->ndecimal = 0; DE->clength = 0; DE->inSpecial = 0; DE->ccol = 1; DE->crow = 1; DE->colmin = 1; DE->rowmin = 1; PROTECT(DE->ssNA_STRING = duplicate(NA_STRING)); nprotect++; DE->bwidth = 0; DE->hwidth = 5; /* setup 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, G_("invalid argument")); } /* scale scrollbars as needed */ if (DE->xmaxused > 10000) DE->xScrollbarScale = DE->xmaxused/1000; if (DE->ymaxused > 10000) DE->yScrollbarScale = DE->ymaxused/1000; /* start up the window, more initializing in here */ if (initwin(DE, CHAR(STRING_ELT(stitle, 0)))) 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 = &dv_closewin_cend; cntxt.cenddata = (void *)DE; R_PreserveObject(DE->work); /* also preserves names */ R_PreserveObject(DE->lens); UNPROTECT(nprotect); return R_NilValue; }