The R Project SVN R

Rev

Rev 25365 | Rev 28840 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
13974 hornik 1
/*
2
 *  R : A Computer Langage for Statistical Data Analysis
3
 *  Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
24255 ripley 4
 *  Copyright (C) 1998--2003  Robert Gentleman, Ross Ihaka and the
13974 hornik 5
 *                            R Development Core Team
6
 *
7
 *  This program is free software; you can redistribute it and/or modify
8
 *  it under the terms of the GNU General Public License as published by
9
 *  the Free Software Foundation; either version 2 of the License, or
10
 *  (at your option) any later version.
11
 *
12
 *  This program is distributed in the hope that it will be useful,
13
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 *  GNU General Public License for more details.
16
 *
17
 *  You should have received a copy of the GNU General Public License
18
 *  along with this program; if not, write to the Free Software
19
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
 */
21
 
22
#ifdef HAVE_CONFIG_H
23
#include <config.h>
24
#endif
25
 
26
#include <stdlib.h>
27
 
28
#include "Defn.h"
29
#include "Print.h"
30
 
31
/* don't use X11 function prototypes (which tend to ...): */
32
#define NeedFunctionPrototypes 0
33
#include <X11/X.h>
34
#include <X11/Xlib.h>
35
#include <X11/Xutil.h>
36
#include <X11/keysym.h>
37
#include <X11/cursorfont.h>
38
 
39
#define KeySym int
40
#define DEEvent XEvent
41
 
42
typedef enum { UP, DOWN, LEFT, RIGHT } DE_DIRECTION;
43
 
44
typedef enum {UNKNOWNN, NUMERIC, CHARACTER} CellType;
45
 
46
/* EXPORTS : */
47
SEXP RX11_dataentry(SEXP call, SEXP op, SEXP args, SEXP rho);
48
 
49
/* Local Function Definitions */
50
 
51
static void advancerect(DE_DIRECTION);
52
static int  CheckControl(DEEvent*);
53
static int  CheckShift(DEEvent*);
54
static int  checkquit(int);
55
static void clearrect(void);
56
static void clearwindow(void);
57
static void closerect(void);
58
static void closewin(void);
59
static void copycell(void);
60
static void doControl(DEEvent*);
61
static int  doMouseDown(DEEvent*);
62
static void eventloop(void);
63
static void doSpreadKey(int, DEEvent*);
64
static void downlightrect(void);
65
static void drawwindow(void);
66
static void drawcol(int);
67
static void drawrow(int);
68
static void find_coords(int, int, int*, int*);
69
static int  findcell(void);
70
static char GetCharP(DEEvent*);
71
static KeySym GetKey(DEEvent*);
72
static void handlechar(char*);
73
static void highlightrect(void);
74
static Rboolean initwin(void);
75
static void jumppage(DE_DIRECTION);
76
static void jumpwin(int, int);
77
static void pastecell(int, int);
78
static void popdownmenu(void);
79
static void popupmenu(int, int, int, int);
80
static void printlabs(void);
81
static void printrect(int, int);
82
static void printstring(char*, int, int, int, int);
83
static void printelt(SEXP, int, int, int);
84
static void RefreshKeyboardMapping(DEEvent*);
85
 
86
/* Functions to hide Xlib calls */
87
static void bell(void);
88
static void cleararea(int, int, int, int);
89
static void copyH(int, int, int);
90
static void copyarea(int, int, int, int);
91
static void doConfigure(DEEvent *ioevent);
92
#if 0
93
static void drawline(int, int, int, int);
94
#endif
95
static void drawrectangle(int, int, int, int, int, int);
96
static void drawtext(int, int, char*, int);
97
static int  NextEvent(DEEvent *ioevent);
98
static void RefreshKeyboardMapping(DEEvent *ioevent);
99
static void Rsync(void);
100
static int textwidth(char*, int);
101
static int WhichEvent(DEEvent ioevent);
102
 
24274 ripley 103
static SEXP work, names, lens;
104
static PROTECT_INDEX wpi, npi, lpi;
105
static SEXP ssNA_STRING;
106
static double ssNA_REAL;
13974 hornik 107
 
108
/* Global variables needed for the graphics */
109
 
110
static int box_w;                       /* width of a box */
111
static int boxw[100];
112
static int box_h;                       /* height of a box */
113
static int windowWidth;                 /* current width of the window */
114
static int fullwindowWidth;
115
static int windowHeight;                /* current height of the window */
116
static int fullwindowHeight;
117
static int currentexp;                  /* boolean: whether an cell is active */
118
static int crow;                        /* current row */
119
static int ccol;                        /* current column */
120
static int nwide, nhigh;
121
static int colmax, colmin, rowmax, rowmin;
122
static int ndecimal;                    /* count decimal points */
123
static int ne;                          /* count exponents */
124
static int nneg;			/* indicate whether its a negative */
125
static int clength;                     /* number of characters currently entered */
126
static char buf[30];
127
static char *bufp;
128
static int bwidth;			/* width of the border */
129
static int hwidth;			/* width of header  */
130
static int text_offset;
131
 
132
static SEXP inputlist;  /* each element is a vector for that row */
133
static SEXP ssNewVector(SEXPTYPE, int);
134
static SEXP ssNA_STRING;
135
static double ssNA_REAL;
136
 
137
static Atom _XA_WM_PROTOCOLS, protocol;
138
 
139
static Rboolean newcol, CellModified;
140
static int nboxchars;
141
static int xmaxused, ymaxused;
142
static int box_coords[6];
143
static char copycontents[30] = "";
24271 ripley 144
static int labdigs=4;
145
static char labform[6];
13974 hornik 146
 
147
 
148
/* Xwindows Globals */
149
 
150
static Display          *iodisplay;
151
static Window           iowindow, menuwindow, menupanes[4];
152
static GC               iogc;
153
static XSizeHints       iohint;
154
static char             *font_name="9x15";
155
static XFontStruct      *font_info;
156
 
157
#define mouseDown 	ButtonPress
158
#define keyDown		KeyPress
159
#define activateEvt	MapNotify
160
#define updateEvt	Expose
161
 
162
#ifndef max
163
#define max(a, b) (((a)>(b))?(a):(b))
164
#endif
165
#ifndef min
166
#define min(a, b) (((a)<(b))?(a):(b))
167
#endif
168
#define BOXW(x) (min(((x<100 && nboxchars==0)?boxw[x]:box_w), fullwindowWidth-boxw[0]-2*bwidth-2))
169
 
24274 ripley 170
/*
171
  Underlying assumptions (for this version R >= 1.8.0)
13974 hornik 172
 
24274 ripley 173
  The data are stored in a list `work', with unused columns having
174
  NULL entries.  The names for the list are in `names', which should
175
  have a name for all displayable columns (up to xmaxused). 
176
  The *used* lengths of the columns are in `lens': this needs only be
177
  set for non-NULL columns.
178
 
179
  If the list was originally length(0), that should work with 
180
 
181
 
182
  All row and col numbers are 1-based.
183
 
184
  BDR May 2003
185
 */
186
 
13974 hornik 187
/*
188
   The spreadsheet function returns a list of vectors. The types of
189
   these vectors can be specified by the user as can their names. It
190
   the names are specified they are set during initialization. The
191
   user can change these via a menu interface, they can also change
192
   the type.
193
 
194
   The vectors are created too long and if they need to be increased
195
   this is done by using the next higher power of 2. They start 100
196
   long. To cut them to the correct length for return you need to know
197
   the largest row number that was assigned to. LEVELS (sxpinfo.gp) is
198
   used to keep track of this, separately for each vector. Vectors are
199
   initialized to NA when they are created so that NA is returned for
200
   any cell that was not set by the user.  So that coercion back and
201
   forth maintains values of ssNA_REAL and ssNA_STRING I have set
202
   ssNA_STRING to be coerceVector(ssNA_REAL), very weird but easy.
203
 
24733 ripley 204
   In Macintosh we needed to call the main event loop to get
13974 hornik 205
   events. This ensures that the spreadsheet interacts well with the
206
   other windows. Under X windows we let the window manager handle
207
   those sorts of details.
208
 
209
 */
210
 
211
static char *menu_label[] =
212
{
213
    "Real",
214
    "Character",
215
    "Change Name",
216
};
217
 
218
/*
219
   ssNewVector is just an interface to allocVector but it lets us
220
   set the fields to NA. We need to have a special NA for reals and
221
   strings so that we can differentiate between uninitialized elements
222
   in the vectors and user supplied NA's; hence ssNA_REAL and ssNA_STRING
223
 */
224
 
225
static SEXP ssNewVector(SEXPTYPE type, int vlen)
226
{
227
    SEXP tvec;
228
    int j;
229
 
230
    tvec = allocVector(type, vlen);
231
    for (j = 0; j < vlen; j++)
232
	if (type == REALSXP)
233
	    REAL(tvec)[j] = ssNA_REAL;
234
	else if (type == STRSXP)
235
	    SET_STRING_ELT(tvec, j, STRING_ELT(ssNA_STRING, 0));
236
    SETLEVELS(tvec, 0);
237
    return (tvec);
238
}
239
 
14561 luke 240
static void closewin_cend(void *data)
241
{
242
    closewin();
243
}
244
 
13974 hornik 245
SEXP RX11_dataentry(SEXP call, SEXP op, SEXP args, SEXP rho)
246
{
24274 ripley 247
    SEXP colmodes, tnames, tvec, tvec2, work2;
13974 hornik 248
    SEXPTYPE type;
24274 ripley 249
    int i, j, cnt, len, nprotect;
13974 hornik 250
    RCNTXT cntxt;
24274 ripley 251
    char clab[25];
13974 hornik 252
 
253
    nprotect = 0;/* count the PROTECT()s */
24274 ripley 254
    PROTECT_WITH_INDEX(work = duplicate(CAR(args)), &wpi); nprotect++;
255
    colmodes = CADR(args);
256
    tnames = getAttrib(work, R_NamesSymbol);
13974 hornik 257
 
24274 ripley 258
    if (TYPEOF(work) != VECSXP || TYPEOF(colmodes) != VECSXP)
13974 hornik 259
	errorcall(call, "invalid argument");
260
 
24274 ripley 261
    /* initialize the constants */
13974 hornik 262
 
263
    bufp = buf;
264
    ne = 0;
265
    currentexp = 0;
266
    nneg = 0;
267
    ndecimal = 0;
268
    clength = 0;
269
    ccol = 1;
270
    crow = 1;
271
    colmin = 1;
272
    rowmin = 1;
273
    ssNA_REAL = -NA_REAL;
274
    tvec = allocVector(REALSXP, 1);
275
    REAL(tvec)[0] = ssNA_REAL;
276
    PROTECT(ssNA_STRING = coerceVector(tvec, STRSXP)); nprotect++;
277
    bwidth = 5;
278
    hwidth = 30;
279
 
24274 ripley 280
    /* setup work, names, lens  */
281
    xmaxused = length(work); ymaxused = 0;
282
    PROTECT_WITH_INDEX(lens = allocVector(INTSXP, xmaxused), &lpi);
283
    nprotect++;
13974 hornik 284
 
24274 ripley 285
    if (isNull(tnames)) {
286
	PROTECT_WITH_INDEX(names = allocVector(STRSXP, xmaxused), &npi);
287
	for(i = 0; i < xmaxused; i++) {
288
	    sprintf(clab, "var%d", i);
289
	    SET_STRING_ELT(names, i, mkChar(clab));
13974 hornik 290
	}
24274 ripley 291
    } else
292
	PROTECT_WITH_INDEX(names = duplicate(tnames), &npi);
293
    nprotect++;
294
    for (i = 0; i < xmaxused; i++) {
295
	int len = LENGTH(VECTOR_ELT(work, i));
296
	INTEGER(lens)[i] = len;
297
	ymaxused = max(len, ymaxused);
298
	type = TYPEOF(VECTOR_ELT(work, i));
25365 ripley 299
	if (LENGTH(colmodes) > 0 && !isNull(VECTOR_ELT(colmodes, i)))
24274 ripley 300
	    type = str2type(CHAR(STRING_ELT(VECTOR_ELT(colmodes, i), 0)));
301
	if (type != STRSXP) type = REALSXP;
302
	if (isNull(VECTOR_ELT(work, i))) {
303
	    if (type == NILSXP) type = REALSXP;
304
	    SET_VECTOR_ELT(work, i, ssNewVector(type, 100));
305
	} else if (!isVector(VECTOR_ELT(work, i)))
306
	    errorcall(call, "invalid type for value");
307
	else {
308
	    if (TYPEOF(VECTOR_ELT(work, i)) != type)
309
		SET_VECTOR_ELT(work, i, 
310
			       coerceVector(VECTOR_ELT(work, i), type));
311
	}
13974 hornik 312
    }
313
 
314
 
315
    /* start up the window, more initializing in here */
316
    if (initwin())
317
	errorcall(call, "invalid device");
318
 
319
    /* set up a context which will close the window if there is an error */
14560 luke 320
    begincontext(&cntxt, CTXT_CCODE, R_NilValue, R_NilValue, R_NilValue,
23463 luke 321
		 R_NilValue, R_NilValue);
14561 luke 322
    cntxt.cend = &closewin_cend;
323
    cntxt.cenddata = NULL;
13974 hornik 324
 
325
    highlightrect();
326
 
327
    eventloop();
328
 
329
    endcontext(&cntxt);
330
    closewin();
331
 
332
    /* drop out unused columns */
24274 ripley 333
    for(i = 0, cnt = 0; i < xmaxused; i++)
334
	if(!isNull(VECTOR_ELT(work, i))) cnt++;
335
    if (cnt < xmaxused) {
336
	PROTECT(work2 = allocVector(VECSXP, cnt)); nprotect++;
337
	for(i = 0, j = 0; i < xmaxused; i++) {
338
	    if(!isNull(VECTOR_ELT(work, i))) {
339
		SET_VECTOR_ELT(work2, j, VECTOR_ELT(work, i));
340
		INTEGER(lens)[j] = INTEGER(lens)[i];
341
		SET_STRING_ELT(names, j, STRING_ELT(names, i));
342
		j++;
13974 hornik 343
	    }
344
	}
24274 ripley 345
	REPROTECT(names = lengthgets(names, cnt), npi);
346
    } else work2 = work;
13974 hornik 347
 
24274 ripley 348
    for (i = 0; i < LENGTH(work2); i++) {
349
	len = INTEGER(lens)[i];
350
	tvec = VECTOR_ELT(work2, i);
351
	if (LENGTH(tvec) != len) {
352
	    tvec2 = ssNewVector(TYPEOF(tvec), len);
353
	    for (j = 0; j < len; j++) {
354
		if (TYPEOF(tvec) == REALSXP) {
355
		    if (REAL(tvec)[j] != ssNA_REAL)
356
			REAL(tvec2)[j] = REAL(tvec)[j];
13974 hornik 357
		    else
358
			REAL(tvec2)[j] = NA_REAL;
24274 ripley 359
		} else if (TYPEOF(tvec) == STRSXP) {
360
		    if (!streql(CHAR(STRING_ELT(tvec, j)),
13974 hornik 361
				CHAR(STRING_ELT(ssNA_STRING, 0))))
24274 ripley 362
			SET_STRING_ELT(tvec2, j, STRING_ELT(tvec, j));
13974 hornik 363
		    else
364
			SET_STRING_ELT(tvec2, j, NA_STRING);
365
		} else
366
		    error("dataentry: internal memory problem");
24274 ripley 367
	    }
368
	    SET_VECTOR_ELT(work2, i, tvec2);
13974 hornik 369
	}
370
    }
371
 
24274 ripley 372
    setAttrib(work2, R_NamesSymbol, names);    
13974 hornik 373
    UNPROTECT(nprotect);
24274 ripley 374
    return work2;
13974 hornik 375
}
376
 
377
 
378
/* Window Drawing Routines */
379
 
380
static void setcellwidths(void)
381
{
382
    int i, w, dw;
383
 
384
    windowWidth = w = 2*bwidth + boxw[0] + BOXW(colmin);
385
    nwide = 2;
386
    for (i = 2; i < 100; i++) { /* 100 on-screen columns cannot occur */
387
	dw = BOXW(i + colmin - 1);
388
	if((w += dw) > fullwindowWidth) {
389
	    nwide = i;
390
	    windowWidth = w - dw;
391
	    break;
392
	}
393
    }
394
}
395
 
396
static void drawwindow(void)
397
{
398
    int i, st;
399
    XWindowAttributes attribs;
400
 
401
    /* if there is an active cell enter the data in it */
402
    closerect();
403
 
404
    /* now set up the window with the new dimensions */
405
    XGetWindowAttributes(iodisplay, iowindow, &attribs);
406
    bwidth = attribs.border_width;
407
    fullwindowWidth = attribs.width;
408
    fullwindowHeight = attribs.height;
409
    setcellwidths();
410
    nhigh = (fullwindowHeight - 2 * bwidth - hwidth) / box_h;
411
    windowHeight = nhigh * box_h + 2 * bwidth;
412
 
413
    clearwindow();
414
 
415
 
416
    for (i = 1; i < nhigh; i++)
417
	drawrectangle(0, hwidth + i * box_h, boxw[0], box_h, 1, 1);
418
     /* so row 0 and col 0 are reserved for labels */
419
    colmax = colmin + (nwide - 2);
420
    rowmax = rowmin + (nhigh - 2);
421
    printlabs();
422
    if (inputlist != R_NilValue)
423
	for (i = colmin; i <= colmax; i++) drawcol(i);
424
 
425
    /* draw the quit etc boxes */
426
 
427
    i = textwidth("Quit", 4);
428
    box_coords[0] = st = fullwindowWidth - 6 - bwidth;
429
    box_coords[1] = st - i;
430
    drawrectangle(st - i, 3, i + 4, hwidth - 6, 1, 1);
431
    drawtext(st + 2 - i, hwidth - 7, "Quit", 4);
432
 
433
    box_coords[4] = st = st - 5*i;
434
    i = textwidth("Paste", 5);
435
    box_coords[5] = st - i;
436
    drawrectangle(st - i, 3, i + 4, hwidth - 6, 1, 1);
437
    drawtext(st + 2 - i, hwidth - 7, "Paste", 5);
438
 
439
    box_coords[2] = st = st - 2*i;
440
    i = textwidth("Copy", 4);
441
    box_coords[3] = st - i;
442
    drawrectangle(st - i, 3, i + 4, hwidth - 6, 1, 1);
443
    drawtext(st + 2 - i, hwidth - 7, "Copy", 4);
444
 
445
    highlightrect();
446
 
447
    Rsync();
448
 
449
}
450
 
451
static void doHscroll(int oldcol)
452
{
453
    int i, dw;
454
    int oldnwide = nwide, oldwindowWidth = windowWidth;
455
 
456
    /* horizontal re-position */
457
    setcellwidths();
458
    colmax = colmin + (nwide - 2);
24274 ripley 459
    if (oldcol < colmin) { /* drop oldcol...colmin - 1 */
13974 hornik 460
	dw = boxw[0];
461
	for (i = oldcol; i < colmin; i++) dw += BOXW(i);
462
	copyH(dw, boxw[0], oldwindowWidth - dw + 1);
463
	dw = oldwindowWidth - BOXW(oldcol) + 1;
464
	cleararea(dw, hwidth, fullwindowWidth-dw, fullwindowHeight);
465
	/* oldnwide includes the row labels */
466
	for (i = oldcol+oldnwide-1; i <= colmax; i++) drawcol(i);
467
    } else {
468
	/* move one or more cols left */
469
	dw = BOXW(colmin);
470
	copyH(boxw[0], boxw[0] + dw, windowWidth - dw + 1);
471
	dw = windowWidth + 1;
472
	cleararea(dw, hwidth, fullwindowWidth-dw, fullwindowHeight);
473
	drawcol(colmin);
474
    }
475
 
476
    highlightrect();
477
 
478
    Rsync();
479
}
480
 
481
/* find_coords finds the coordinates of the upper left corner of the
482
   given cell on the screen: row and col are on-screen coords */
483
 
484
static void find_coords(int row, int col, int *xcoord, int *ycoord)
485
{
486
    int i, w;
487
    w = bwidth;
488
    if (col > 0) w += boxw[0];
489
    for(i = 1; i < col; i ++) w += BOXW(i + colmin - 1);
490
    *xcoord = w;
491
    *ycoord = bwidth + hwidth + box_h * row;
492
}
493
 
494
/* draw the window with the top left box at column wcol and row wrow */
495
 
496
static void jumpwin(int wcol, int wrow)
497
{
498
    if (wcol < 0 || wrow < 0) {
499
	bell();
500
	return;
501
    }
502
    closerect();
503
    if (colmin != wcol || rowmin != wrow) {
504
	colmin = wcol;
505
	rowmin = wrow;
506
	drawwindow();
507
    } else highlightrect();
508
}
509
 
510
static void advancerect(DE_DIRECTION which)
511
{
512
 
513
    /* if we are in the header, changing a name then only down is
514
       allowed */
515
    if (crow < 1 && which != DOWN) {
516
	bell();
517
	return;
518
    }
519
 
520
    closerect();
521
 
522
    switch (which) {
523
    case UP:
524
	if (crow == 1) {
525
	    if (rowmin == 1)
526
		bell();
527
	    else
528
		jumppage(UP);
529
	} else
530
	    crow--;
531
	break;
532
    case DOWN:
533
	if (crow == (nhigh - 1))
534
	    jumppage(DOWN);
535
	else
536
	    crow++;
537
	break;
538
    case RIGHT:
539
	if (ccol == (nwide - 1))
540
	    jumppage(RIGHT);
541
	else
542
	    ccol++;
543
	break;
544
    case LEFT:
545
	if (ccol == 1) {
546
	    if (colmin == 1)
547
		bell();
548
	    else
549
		jumppage(LEFT);
550
	} else
551
	    ccol--;
552
	break;
553
    default:
554
	UNIMPLEMENTED("advancerect");
555
    }
556
 
557
    highlightrect();
558
}
559
 
560
static char *get_col_name(int col)
561
{
24274 ripley 562
    static char clab[25];
563
    if (col <= xmaxused) {
564
	/* don't use NA labels */
565
	SEXP tmp = STRING_ELT(names, col - 1);
566
	if(tmp != NA_STRING) return(CHAR(tmp));
13974 hornik 567
    }
568
    sprintf(clab, "var%d", col);
569
    return clab;
570
}
571
 
572
static int get_col_width(int col)
573
{
574
    int i, w = 0, w1;
575
    char *strp;
24274 ripley 576
    SEXP tmp, lab;
13974 hornik 577
 
578
    if (nboxchars > 0) return box_w;
24274 ripley 579
    if (col <= xmaxused) {
580
	tmp = VECTOR_ELT(work, col - 1);
581
	if (isNull(tmp)) return box_w;
582
	/* don't use NA labels */
583
	lab = STRING_ELT(names, col - 1);
584
	if(lab != NA_STRING) strp = CHAR(lab); else strp = "var12";
13974 hornik 585
	PrintDefaults(R_NilValue);
586
	w = textwidth(strp, strlen(strp));
24274 ripley 587
	for (i = 0; i < INTEGER(lens)[col - 1]; i++) {
13974 hornik 588
	    strp = EncodeElement(tmp, i, 0);
589
	    w1 = textwidth(strp, strlen(strp));
590
	    if (w1 > w) w = w1;
591
	}
592
	if(w < 0.5*box_w) w = 0.5*box_w;
593
	if(w < 0.8*box_w) w+= 0.1*box_w;
594
	if(w > 600) w = 600;
595
	return w+8;
596
    }
597
    return box_w;
598
}
599
 
600
static CellType get_col_type(int col)
601
{
602
    SEXP tmp;
603
    CellType res = UNKNOWNN;
604
 
24274 ripley 605
    if (col <= xmaxused) {
606
	tmp = VECTOR_ELT(work, col - 1);
13974 hornik 607
	if(TYPEOF(tmp) == REALSXP) res = NUMERIC;
608
	if(TYPEOF(tmp) == STRSXP) res = CHARACTER;
609
    }
610
    return res;
611
}
612
 
613
 
614
/* whichcol is absolute col no, col is position on screen */
615
static void drawcol(int whichcol)
616
{
617
    int i, src_x, src_y, len, col = whichcol - colmin + 1, bw = BOXW(whichcol);
618
    char *clab;
619
    SEXP tmp;
620
 
621
    find_coords(0, col, &src_x, &src_y);
622
    cleararea(src_x, src_y, bw, windowHeight);
623
    for (i = 0; i < nhigh; i++)
624
	drawrectangle(src_x, hwidth + i * box_h, bw, box_h, 1, 1);
625
 
626
    /* now fill it in if it is active */
627
    clab = get_col_name(whichcol);
628
    printstring(clab, strlen(clab), 0, col, 0);
629
 
24274 ripley 630
   if (xmaxused >= whichcol) {
631
	tmp = VECTOR_ELT(work, whichcol - 1);
632
	if (!isNull(tmp)) {
633
	    len = min(rowmax, INTEGER(lens)[whichcol - 1]);
13974 hornik 634
	    for (i = (rowmin - 1); i < len; i++)
24274 ripley 635
		printelt(tmp, i, i - rowmin + 2, col);
13974 hornik 636
	}
637
    }
638
    Rsync();
639
}
640
 
641
 
642
/* whichrow is absolute row no */
643
static void drawrow(int whichrow)
644
{
24274 ripley 645
    int i, src_x, src_y, row = whichrow - rowmin + 1, w;
13974 hornik 646
    char rlab[15];
647
    SEXP tvec;
648
 
649
    find_coords(row, 0, &src_x, &src_y);
650
    cleararea(src_x, src_y, windowWidth, box_h);
651
    drawrectangle(src_x, src_y, boxw[0], box_h, 1, 1);
652
 
24271 ripley 653
    sprintf(rlab, labform, whichrow);
13974 hornik 654
    printstring(rlab, strlen(rlab), row, 0, 0);
655
 
656
    w = bwidth + boxw[0];
657
    for (i = colmin; i <= colmax; i++) {
658
	drawrectangle(w, src_y, BOXW(i), box_h, 1, 1);
659
	w += BOXW(i);
660
    }
661
 
662
    for (i = colmin; i <= colmax; i++) {
24274 ripley 663
	if (i > xmaxused) break;
664
	if (!isNull(tvec = VECTOR_ELT(work, i - 1)))
665
	    if (whichrow <= INTEGER(lens)[i - 1])
666
		printelt(tvec, whichrow - 1, row, i - colmin + 1);
13974 hornik 667
    }
668
 
669
    Rsync();
670
}
671
 
672
/* printelt: print the correct value from vector[vrow] into the
673
   spreadsheet in row ssrow and col sscol */
674
 
675
/* WARNING: This has no check that you're not beyond the end of the
676
   vector. Caller must check. */
677
 
678
static void printelt(SEXP invec, int vrow, int ssrow, int sscol)
679
{
680
    char *strp;
681
    PrintDefaults(R_NilValue);
682
    if (TYPEOF(invec) == REALSXP) {
683
	if (REAL(invec)[vrow] != ssNA_REAL) {
684
	    strp = EncodeElement(invec, vrow, 0);
685
	    printstring(strp, strlen(strp), ssrow, sscol, 0);
686
	}
687
    }
688
    else if (TYPEOF(invec) == STRSXP) {
689
	if (!streql(CHAR(STRING_ELT(invec, vrow)),
690
		    CHAR(STRING_ELT(ssNA_STRING, 0)))) {
691
	    strp = EncodeElement(invec, vrow, 0);
692
	    printstring(strp, strlen(strp), ssrow, sscol, 0);
693
	}
694
    }
695
    else
696
	error("dataentry: internal memory error");
697
}
698
 
699
 
700
static void drawelt(int whichrow, int whichcol)
701
{
702
    int i;
703
    char *clab;
704
    SEXP tmp;
705
 
706
    if (whichrow == 0) {
707
	clab = get_col_name(whichcol + colmin - 1);
708
	printstring(clab, strlen(clab), 0, whichcol, 0);
709
    } else {
24274 ripley 710
	if (xmaxused >= whichcol + colmin - 1) {
711
	    tmp = VECTOR_ELT(work, whichcol + colmin - 2);
712
	    if (!isNull(tmp) && (i = rowmin + whichrow - 2) < 
713
		INTEGER(lens)[whichcol + colmin - 2] )
714
		printelt(tmp, i, whichrow, whichcol);
13974 hornik 715
	} else
716
	    printstring("", 0, whichrow,  whichcol, 0);
717
    }
718
 
719
    Rsync();
720
}
721
 
722
static void jumppage(DE_DIRECTION dir)
723
{
724
    int i, w, oldcol, wcol;
725
 
726
    switch (dir) {
727
    case UP:
728
	rowmin--;
729
	rowmax--;
730
	copyarea(0, hwidth + box_h, 0, hwidth + 2 * box_h);
731
	drawrow(rowmin);
732
	break;
733
    case DOWN:
24255 ripley 734
	if (rowmax >= 65535) return;
13974 hornik 735
	rowmin++;
736
	rowmax++;
737
	copyarea(0, hwidth + 2 * box_h, 0, hwidth + box_h);
738
	drawrow(rowmax);
739
	break;
740
    case LEFT:
741
	colmin--;
742
	doHscroll(colmin+1);
743
	break;
744
    case RIGHT:
745
	oldcol = colmin;
746
	wcol = colmin + ccol + 1; /* column to be selected */
747
        /* There may not be room to fit the next column in */
748
	w = fullwindowWidth - boxw[0] - BOXW(colmax + 1);
749
	for (i = colmax; i >= oldcol; i--) {
750
	    w -= BOXW(i);
751
	    if(w < 0) {
752
		colmin = i + 1;
753
		break;
754
	    }
755
	}
756
	ccol = wcol - colmin;
757
	doHscroll(oldcol);
758
	break;
759
    }
760
}
761
/* draw a rectangle, used to highlight/downlight the current box */
762
 
763
static void printrect(int lwd, int fore)
764
{
765
    int x, y;
766
    find_coords(crow, ccol, &x, &y);
24274 ripley 767
    drawrectangle(x + lwd - 1, y + lwd - 1,
13974 hornik 768
		  BOXW(ccol+colmin-1) - lwd + 1,
769
		  box_h - lwd + 1, lwd, fore);
770
    Rsync();
771
}
772
 
773
static void downlightrect(void)
774
{
775
    printrect(2, 0);
776
    printrect(1, 1);
777
}
778
 
779
static void highlightrect(void)
780
{
781
    printrect(2, 1);
782
}
783
 
24274 ripley 784
 
785
static void getccol()
13974 hornik 786
{
787
    SEXP tmp, tmp2;
788
    int i, len, newlen, wcol, wrow;
789
    SEXPTYPE type;
24274 ripley 790
    char clab[25];
13974 hornik 791
 
792
    wcol = ccol + colmin - 1;
793
    wrow = crow + rowmin - 1;
24274 ripley 794
    if (wcol > xmaxused) {
795
	/* extend work, names and lens */
796
	REPROTECT(work = lengthgets(work, wcol), wpi);
797
	REPROTECT(names = lengthgets(names, wcol), npi);
798
	for (i = xmaxused; i < wcol; i++) {
799
	    sprintf(clab, "var%d", i + 1);
800
	    SET_STRING_ELT(names, i, mkChar(clab));
801
	}
802
	REPROTECT(lens = lengthgets(lens, wcol), lpi);
803
	xmaxused = wcol;
804
    }
13974 hornik 805
    newcol = FALSE;
24274 ripley 806
    if (isNull(VECTOR_ELT(work, wcol - 1))) {
13974 hornik 807
	newcol = TRUE;
24274 ripley 808
	SET_VECTOR_ELT(work, wcol - 1, ssNewVector(REALSXP, max(100, wrow)));
809
	INTEGER(lens)[wcol - 1] = 0;
13974 hornik 810
    }
24274 ripley 811
    if (!isVector(tmp = VECTOR_ELT(work, wcol - 1)))
13974 hornik 812
	error("internal type error in dataentry");
24274 ripley 813
    len = INTEGER(lens)[wcol - 1];
814
    type = TYPEOF(tmp);
13974 hornik 815
    if (len < wrow) {
816
	for (newlen = max(len * 2, 10) ; newlen < wrow ; newlen *= 2)
817
	    ;
818
	tmp2 = ssNewVector(type, newlen);
819
	for (i = 0; i < len; i++)
820
	    if (type == REALSXP)
24274 ripley 821
		REAL(tmp2)[i] = REAL(tmp)[i];
13974 hornik 822
	    else if (type == STRSXP)
24274 ripley 823
		SET_STRING_ELT(tmp2, i, STRING_ELT(tmp, i));
13974 hornik 824
	    else
825
		error("internal type error in dataentry");
24274 ripley 826
	SET_VECTOR_ELT(work, wcol - 1, tmp2);
13974 hornik 827
    }
828
}
829
 
830
/* close up the entry to a cell, put the value that has been entered
831
   into the correct place and as the correct type */
832
 
833
static void closerect(void)
834
{
24274 ripley 835
    SEXP cvec;
836
    int i, wcol = ccol + colmin - 1, wrow = rowmin + crow - 1, wrow0;
837
    char clab[25];
13974 hornik 838
 
839
    *bufp = '\0';
840
 
841
    /* first check to see if anything has been entered */
842
    if (CellModified) {
843
	if (crow == 0) {
844
	    if (clength != 0) {
845
		/* then we are entering a new column name */
24274 ripley 846
		if (xmaxused < wcol) {
847
		    /* extend work, names and lens */
848
		    REPROTECT(work = lengthgets(work, wcol), wpi);
849
		    REPROTECT(names = lengthgets(names, wcol), npi);
850
		    for (i = xmaxused; i < wcol - 1; i++) {
851
			sprintf(clab, "var%d", i + 1);
852
			SET_STRING_ELT(names, i, mkChar(clab));
853
		    }
854
		    REPROTECT(lens = lengthgets(lens, wcol), lpi);
855
		    xmaxused = wcol;
856
		}
857
		SET_STRING_ELT(names, wcol - 1, mkChar(buf));
13974 hornik 858
		printstring(buf, strlen(buf), 0, wcol, 0);
859
	    } else {
860
		sprintf(buf, "var%d", ccol);
861
		printstring(buf, strlen(buf), 0, wcol, 0);
862
	    }
863
	} else {
24274 ripley 864
	    getccol();
865
	    cvec = VECTOR_ELT(work, wcol - 1);
866
	    wrow0 = INTEGER(lens)[wcol - 1];
867
	    if (wrow > wrow0) INTEGER(lens)[wcol - 1] = wrow;
868
	    ymaxused = max(ymaxused, wrow);
869
	    if (clength != 0) {
870
		/* do it this way to ensure NA, Inf, ...  can get set */
871
		char *endp;
872
		double new = R_strtod(buf, &endp);
873
		Rboolean warn = !isBlankString(endp);
874
		if (TYPEOF(cvec) == STRSXP)
875
		    SET_STRING_ELT(cvec, wrow - 1, mkChar(buf));
876
		else
877
		    REAL(cvec)[wrow - 1] = new;
878
		if (newcol & warn) {
879
		    /* change mode to character */
880
		    SET_VECTOR_ELT(work, wcol - 1, coerceVector(cvec, STRSXP));
881
		    SET_STRING_ELT(VECTOR_ELT(work, wcol - 1), wrow - 1, 
882
				   mkChar(buf));
883
		}
884
	    } else {
885
		if (TYPEOF(cvec) == STRSXP)
886
		    SET_STRING_ELT(cvec, wrow - 1, NA_STRING);
887
		else
888
		    REAL(cvec)[wrow - 1] = NA_REAL;
24255 ripley 889
	    }
24274 ripley 890
	    drawelt(crow, ccol);  /* to get the cell scrolling right */
891
	    if(wrow > wrow0) drawcol(wcol); /* to fill in NAs */
24255 ripley 892
	}
13974 hornik 893
    }
894
    CellModified = FALSE;
895
 
896
    downlightrect();
897
 
898
    ndecimal = 0;
899
    nneg = 0;
900
    ne = 0;
901
    currentexp = 0;
902
    clength = 0;
903
    bufp = buf;
904
}
905
 
906
/* print a null terminated string, check to see if it is longer than
907
   the print area and print it, left adjusted if necessary; clear the
908
   area of previous text; */
909
 
910
/* This version will only display 200 chars, but the maximum col width
911
   will not allow that many */
912
static void printstring(char *ibuf, int buflen, int row, int col, int left)
913
{
914
    int i, x_pos, y_pos, bw, bufw;
915
    char pbuf[201], *pc = pbuf;
916
 
917
    find_coords(row, col, &x_pos, &y_pos);
918
    if (col == 0) bw = boxw[0]; else bw = BOXW(col+colmin-1);
919
    cleararea(x_pos + 2, y_pos + 2, bw - 3, box_h - 3);
920
    bufw = (buflen > 200) ? 200 : buflen;
921
    strncpy(pbuf, ibuf, bufw);
922
    if(left) {
923
	for (i = bufw; i > 1; i--) {
924
	    if (textwidth(pc, i) < (bw - text_offset)) break;
925
	    *(++pc) = '<';
926
	}
927
    } else {
928
	for (i = bufw; i > 1; i--) {
929
	    if (textwidth(pbuf, i) < (bw - text_offset)) break;
930
	    *(pbuf + i - 2) = '>';
931
	}
932
    }
933
    drawtext(x_pos + text_offset, y_pos + box_h - text_offset, pc, i);
934
    Rsync();
935
}
936
 
937
static void clearrect(void)
938
{
939
    int x_pos, y_pos;
940
 
941
    find_coords(crow, ccol, &x_pos, &y_pos);
942
    cleararea(x_pos, y_pos, BOXW(ccol+colmin-1), box_h);
943
    Rsync();
944
}
945
 
946
/* handlechar has to be able to parse decimal numbers and strings,
947
   depending on the current column type, only printing characters
948
   should get this far */
949
 
950
/* --- Not true! E.g. ESC ends up in here... */
951
 
952
static void handlechar(char *text)
953
{
954
    int c = text[0];
955
 
956
    if ( c == '\033' ) {
957
	CellModified = FALSE;
958
	clength = 0;
26830 ripley 959
        bufp = buf;
13974 hornik 960
	drawelt(crow, ccol);
961
	return;
962
    } else
963
	CellModified = TRUE;
964
 
965
    if (clength == 0) {
966
 
967
	if (crow == 0)	                        /* variable name */
968
	    currentexp = 3;
969
	else
970
	    switch(get_col_type(ccol + colmin - 1)) {
971
	    case NUMERIC:
972
		currentexp = 1;
973
		break;
974
	    default:
975
		currentexp = 2;
976
	    }
977
	clearrect();
978
	highlightrect();
979
    }
980
 
981
    if (currentexp == 1)	/* we are parsing a number */
982
	switch (c) {
983
	case '-':
984
	    if (nneg == 0)
985
		nneg++;
986
	    else
987
		goto donehc;
988
	    break;
989
	case '.':
990
	    if (ndecimal == 0)
991
		ndecimal++;
992
	    else
993
		goto donehc;
994
	    break;
995
	case 'e':
996
	case 'E':
997
	    if (ne == 0) {
998
		nneg = ndecimal = 0;	/* might have decimal in exponent */
999
		ne++;
1000
	    }
1001
	    else
1002
		goto donehc;
1003
	    break;
1004
	default:
1005
	    if (!isdigit((int)text[0]))
1006
		goto donehc;
1007
	    break;
1008
	}
1009
    if (currentexp == 3) {
1010
	if (isspace(c))
1011
	    goto donehc;
1012
	if (clength == 0) {
1013
	    if (c != '.' && !isalpha(c))
1014
		goto donehc;
1015
	    else if (c != '.' && !isalnum(c))
1016
		goto donehc;
1017
	}
1018
    }
1019
 
1020
    if (clength++ > 29) {
1021
	warning("dataentry: expression too long");
1022
	clength--;
1023
	goto donehc;
1024
    }
1025
 
1026
    *bufp++ = text[0];
1027
    printstring(buf, clength, crow, ccol, 1);
1028
    return;
1029
 
1030
 donehc:
1031
    bell();
1032
}
1033
 
1034
static void printlabs(void)
1035
{
24274 ripley 1036
    char clab[15], *p;
13974 hornik 1037
    int i;
1038
 
1039
    for (i = colmin; i <= colmax; i++) {
1040
	p = get_col_name(i);
1041
	printstring(p, strlen(p), 0, i - colmin + 1, 0);
1042
    }
1043
    for (i = rowmin; i <= rowmax; i++) {
24271 ripley 1044
	sprintf(clab, labform, i);
13974 hornik 1045
	printstring(clab, strlen(clab), i - rowmin + 1, 0, 0);
1046
    }
1047
}
1048
 
1049
               /* ================ X11-specific ================ */
1050
 
1051
/* find out whether the button click was in the quit box */
1052
static int checkquit(int xw)
1053
{
1054
    if (xw > box_coords[1] && xw < box_coords[0]) return 1;
1055
    if (xw > box_coords[3] && xw < box_coords[2]) return 2;
1056
    if (xw > box_coords[5] && xw < box_coords[4]) return 3;
1057
    return 0;
1058
}
1059
 
1060
/* when a buttonpress event happens find the square that is being
1061
   pointed to if the pointer is in the header we need to see if the
1062
   quit button was pressed and if so quit. This is done by having
1063
   findcell return an int which is zero if we should quit and one
1064
   otherwise */
1065
 
1066
static int findcell(void)
1067
{
1068
 
1069
    int xw, yw, xr, yr, wcol=0, wrow, i, w;
1070
    unsigned int keys;
1071
    Window root, child;
1072
 
1073
    closerect();
1074
    XQueryPointer(iodisplay, iowindow, &root, &child,
1075
		  &xr, &yr, &xw, &yw, &keys);
1076
 
1077
    if (keys & Button1Mask) { /* left click */
1078
 
1079
	/* check to see if the click was in the header */
1080
 
1081
	if (yw < hwidth + bwidth) {
1082
	    i =  checkquit(xw);
1083
	    if (i == 1) return 1;
1084
	    else if (i == 2) copycell();
1085
	    else if (i == 3) pastecell(crow, ccol);
1086
	    return 0;
1087
	}
1088
 
1089
 
1090
	/* see if it is in the row labels */
1091
	if (xw < bwidth + boxw[0]) {
1092
	    bell();
1093
	    highlightrect();
1094
	    return 0;
1095
	}
1096
	/* translate to box coordinates */
1097
	wrow = (yw - bwidth - hwidth) / box_h;
1098
	w = bwidth + boxw[0];
1099
	for (i = 1; i <= nwide; i++)
1100
	    if((w += BOXW(i+colmin-1)) > xw) {
1101
		wcol = i;
1102
		break;
1103
	    }
1104
 
1105
	/* next check to see if it is in the column labels */
1106
 
1107
	if (yw < hwidth + bwidth + box_h) {
1108
	    if (xw > bwidth + boxw[0])
1109
		popupmenu(xr, yr, wcol, wrow);
1110
	    else {
1111
		highlightrect();
1112
		bell();
1113
	    }
1114
	} else if (wrow > nhigh - 1 || wcol > nwide -1) {
1115
		/* off the grid */
1116
		highlightrect();
1117
		bell();
1118
	} else if (wcol != ccol || wrow != crow) {
1119
	    ccol = wcol;
1120
	    crow = wrow;
1121
	}
1122
    }
1123
    if (keys & Button2Mask) { /* Paste */
1124
	int row, col = 0;
1125
 
1126
	if (yw < hwidth + bwidth || xw < bwidth + boxw[0]) return 0;
1127
 
1128
	/* translate to box coordinates */
1129
	row = (yw - bwidth - hwidth) / box_h;
1130
	w = bwidth + boxw[0];
1131
	for (i = 1; i <= nwide; i++)
1132
	    if ((w += BOXW(i+colmin-1)) > xw) {
1133
		col = i;
1134
		break;
1135
	    }
1136
	pastecell(row, col);
1137
    }
1138
    highlightrect();
1139
    return 0;
1140
}
1141
 
1142
 
1143
/* Event Loop Functions */
1144
 
1145
static void eventloop(void)
1146
{
1147
    int done;
1148
    DEEvent ioevent;
1149
 
1150
 
1151
    done = 0;
1152
    while (done == 0) {
1153
	if (NextEvent(&ioevent)) {
1154
	    switch (WhichEvent(ioevent)) {
1155
	    case activateEvt:
1156
		drawwindow();
1157
 		break;
1158
	    case mouseDown:
1159
		done  = doMouseDown(&ioevent);
1160
		break;
1161
	    case keyDown:
1162
		doSpreadKey(0, &ioevent);
1163
		break;
1164
	    case MappingNotify:
1165
		RefreshKeyboardMapping(&ioevent);
1166
		break;
1167
	    case ConfigureNotify:
1168
		doConfigure(&ioevent);
1169
		break;
1170
	    case ClientMessage:
1171
		if(ioevent.xclient.message_type == _XA_WM_PROTOCOLS
1172
		   && ioevent.xclient.data.l[0] == protocol) {
1173
		    /* user clicked on `close' aka `destroy' */
1174
		       done = 1;
1175
		}
1176
		break;
1177
	    }
1178
	}
1179
    }
1180
}
1181
 
1182
static int doMouseDown(DEEvent * event)
1183
{
1184
    return findcell();
1185
}
1186
 
1187
static void doSpreadKey(int key, DEEvent * event)
1188
{
1189
    KeySym iokey;
1190
    char text[1];
1191
 
1192
    iokey = GetKey(event);
1193
    text[0] = GetCharP(event);
1194
 
1195
    if (CheckControl(event))
1196
	doControl(event);
1197
    else if ((iokey == XK_Return)  || (iokey == XK_KP_Enter) || 
1198
	     (iokey == XK_Linefeed)|| (iokey == XK_Down))
1199
	advancerect(DOWN);
1200
    else if (iokey == XK_Left)
1201
	advancerect(LEFT);
1202
    else if (iokey == XK_Right)
1203
	advancerect(RIGHT);
1204
    else if (iokey == XK_Up)
1205
	advancerect(UP);
22718 ripley 1206
#ifdef XK_Page_Up
1207
    else if (iokey == XK_Page_Up) {
13974 hornik 1208
	int i = rowmin - nhigh + 2;
1209
	jumpwin(colmin, max(1, i));
1210
    }
22718 ripley 1211
#elif defined(XK_Prior)
1212
    else if (iokey == XK_Prior) {
13974 hornik 1213
	int i = rowmin - nhigh + 2;
1214
	jumpwin(colmin, max(1, i));
1215
    }
22718 ripley 1216
#endif
1217
#ifdef XK_Page_Down
13974 hornik 1218
    else if (iokey == XK_Page_Down)
1219
	jumpwin(colmin, rowmax);
22718 ripley 1220
#elif defined(XK_Next)
1221
    else if (iokey == XK_Next)
1222
	jumpwin(colmin, rowmax);
13974 hornik 1223
#endif
1224
    else if ((iokey == XK_BackSpace) || (iokey == XK_Delete)) {
1225
	if (clength > 0) {
1226
	    clength--;
1227
	    bufp--;
1228
	    printstring(buf, clength, crow, ccol, 1);
1229
	} else bell();
1230
    }
1231
    else if (iokey == XK_Tab) {
1232
	if(CheckShift(event)) advancerect(LEFT);
1233
	else advancerect(RIGHT);
1234
    }
1235
    else if (iokey == XK_Home) {
1236
	jumpwin(1, 1);
1237
	downlightrect();
1238
	crow = ccol = 1;
1239
	highlightrect();
1240
    }
1241
    else if (iokey == XK_End) {
1242
	int i = ymaxused - nhigh + 2;
1243
	jumpwin(xmaxused, max(i, 1));
1244
	downlightrect();
1245
	crow = ymaxused - rowmin + 1;
1246
	ccol = 1;
1247
	highlightrect();
1248
    }
1249
    else if (IsModifierKey(iokey)) {
1250
    }
1251
    else
1252
	handlechar(text);
1253
}
1254
 
1255
static int NextEvent(DEEvent * ioevent)
1256
{
1257
    XNextEvent(iodisplay, ioevent);
1258
    return 1;
1259
}
1260
 
1261
static int WhichEvent(DEEvent ioevent)
1262
{
1263
    return ioevent.type;
1264
}
1265
 
1266
static KeySym GetKey(DEEvent * event)
1267
{
1268
    char text[1];
1269
    KeySym iokey;
1270
 
22319 ripley 1271
    XLookupString(event, text, 1, &iokey, 0);
13974 hornik 1272
    return iokey;
1273
}
1274
 
1275
static char GetCharP(DEEvent * event)
1276
{
1277
    char text[1];
1278
    KeySym iokey;
1279
 
1280
    XLookupString(event, text, 1, &iokey, 0);
1281
    return text[0];
1282
}
1283
 
1284
static int CheckControl(DEEvent * event)
1285
{
1286
    return (*event).xkey.state & ControlMask;
1287
}
1288
 
1289
static int CheckShift(DEEvent * event)
1290
{
1291
    return (*event).xkey.state & ShiftMask;
1292
}
1293
 
1294
static void doControl(DEEvent * event)
1295
{
1296
    int i;
1297
    char text[1];
1298
    KeySym iokey;
1299
 
1300
    (*event).xkey.state = 0;
1301
    XLookupString(event, text, 1, &iokey, 0);
1302
    /* one row overlap when scrolling: top line <--> bottom line */
1303
    switch (text[0]) {
1304
	case 'b':
1305
	    i = rowmin - nhigh + 2;
1306
	    jumpwin(colmin, max(1, i));
1307
	    break;
1308
	case 'f':
1309
	    jumpwin(colmin, rowmax);
1310
	    break;
1311
	case 'l':
1312
	    closerect();
1313
	    for (i = 1 ; i <= min(100, xmaxused); i++)
1314
		boxw[i] = get_col_width(i);
1315
	    drawwindow();
1316
	    break;
1317
    }
1318
}
1319
 
1320
 
1321
static void doConfigure(DEEvent * event)
1322
{
1323
    if ((fullwindowWidth != (*event).xconfigure.width) ||
1324
	(fullwindowHeight != (*event).xconfigure.height))
1325
	drawwindow();
1326
}
1327
 
1328
static void RefreshKeyboardMapping(DEEvent * event)
1329
{
1330
    XRefreshKeyboardMapping(event);
1331
}
1332
 
1333
/* Initialize/Close Windows */
1334
 
1335
void closewin(void)
1336
{
1337
    XFreeGC(iodisplay, iogc);
1338
    XDestroyWindow(iodisplay, iowindow);
1339
    XCloseDisplay(iodisplay);
1340
}
1341
 
1342
static int R_X11Err(Display *dsp, XErrorEvent *event)
1343
{
1344
    char buff[1000];
1345
    XGetErrorText(dsp, event->error_code, buff, 1000);
1346
    warning("X11 protocol error: %s", buff);
1347
    return 0;
1348
}
1349
 
1350
static int R_X11IOErr(Display *dsp)
1351
{
1352
    error("X11 fatal IO error: please save work and shut down R");
1353
    return 0; /* but should never get here */
1354
}
1355
 
1356
/* set up the window, print the grid and column/row labels */
1357
 
1358
static Rboolean initwin(void) /* TRUE = Error */
1359
{
1360
    int i, twidth, w, minwidth;
1361
    int ioscreen;
1362
    unsigned long iowhite, ioblack;
1363
    char ioname[] = "R DataEntryWindow";
1364
    char digits[] = "123456789.0";
1365
    Window root;
1366
    XEvent ioevent;
1367
    XSetWindowAttributes winattr;
1368
    XWindowAttributes attribs;
1369
 
1370
    if ((iodisplay = XOpenDisplay(NULL)) == NULL) 
1371
	return TRUE;
1372
    XSetErrorHandler(R_X11Err);
1373
    XSetIOErrorHandler(R_X11IOErr);
1374
 
1375
    /* Get Font Loaded if we can */
1376
 
1377
    font_info = XLoadQueryFont(iodisplay, font_name);
1378
    if (font_info == NULL) 
1379
	return TRUE; /* ERROR */
1380
 
1381
    /* find out how wide the input boxes should be and set up the
1382
       window size defaults */
1383
 
1384
    nboxchars = asInteger(GetOption(install("de.cellwidth"), R_GlobalEnv));
1385
    if (nboxchars == NA_INTEGER || nboxchars < 0) nboxchars = 0;
1386
 
1387
    twidth = textwidth(digits, strlen(digits));
1388
    if (nboxchars > 0) twidth = (twidth * nboxchars)/10;
1389
    box_w = twidth + 4;
1390
    box_h = font_info->max_bounds.ascent
1391
	+ font_info->max_bounds.descent + 4;
1392
    text_offset = 2 + font_info->max_bounds.descent;
1393
    windowHeight = 26 * box_h + hwidth + 2;
24271 ripley 1394
    /* this used to presume 4 chars sufficed for row numbering */
1395
    labdigs = max(3, 1+floor(log10((double)ymaxused)));
1396
    sprintf(labform, "%%%dd", labdigs);
1397
    boxw[0] = 0.1*labdigs*textwidth("0123456789", 10) + textwidth(" ", 1) + 8;
13974 hornik 1398
    for(i = 1; i < 100; i++) boxw[i] = get_col_width(i);
1399
    /* try for a window width that covers all the columns, or is around
1400
       800 pixels */
1401
    w = windowWidth = 0;
1402
    for(i = 0; i <= xmaxused; i++) {
1403
	w += boxw[i];
1404
	if(w > 800) {
1405
	    windowWidth = w - boxw[i];
1406
	    break;
1407
	}
1408
    }
1409
    if(windowWidth == 0) windowWidth = w;
1410
    windowWidth += 2;
1411
    /* allow enough width for buttons */
1412
    minwidth = 7.5 * textwidth("Paste", 5);
1413
    if(windowWidth < minwidth) windowWidth = minwidth;
1414
 
1415
    ioscreen = DefaultScreen(iodisplay);
1416
    iowhite = WhitePixel(iodisplay, ioscreen);
1417
    ioblack = BlackPixel(iodisplay, ioscreen);
1418
 
1419
 
1420
    iohint.x = 0;
1421
    iohint.y = 0;
1422
    iohint.width = windowWidth;
1423
    iohint.height = windowHeight;
1424
    iohint.flags = PPosition | PSize;
1425
    root = DefaultRootWindow(iodisplay);
1426
 
1427
    if ((iowindow = XCreateSimpleWindow(
1428
	iodisplay,
1429
	root,
1430
	iohint.x,
1431
	iohint.y,
1432
	iohint.width,
1433
	iohint.height,
1434
	bwidth,
1435
	ioblack,
1436
	iowhite)) == 0)
1437
	return TRUE;
1438
 
1439
    XSetStandardProperties(iodisplay, iowindow, ioname, ioname, None,
1440
			   ioname, 0, &iohint);
1441
 
1442
    winattr.backing_store = Always;
1443
    XChangeWindowAttributes(iodisplay, iowindow, CWBackingStore, &winattr);
1444
 
1445
    /* set up protocols so that window manager sends */
1446
    /* me an event when user "destroys" window */
1447
    _XA_WM_PROTOCOLS = XInternAtom(iodisplay, "WM_PROTOCOLS", 0);
1448
    protocol = XInternAtom(iodisplay, "WM_DELETE_WINDOW", 0);
1449
    XSetWMProtocols(iodisplay, iowindow, &protocol, 1);
1450
 
1451
 
1452
    iogc = XCreateGC(iodisplay, iowindow, 0, 0);
1453
    XSetFont(iodisplay, iogc, font_info->fid);
1454
    XSetBackground(iodisplay, iogc, iowhite);
1455
    XSetForeground(iodisplay, iogc, BlackPixel(iodisplay,
1456
					       DefaultScreen(iodisplay)));
1457
    XSetLineAttributes(iodisplay, iogc, 1, LineSolid, CapRound, JoinRound);
1458
 
1459
    XSelectInput(iodisplay, iowindow,
1460
		 ButtonPressMask | KeyPressMask
1461
		 | ExposureMask | StructureNotifyMask);
1462
    XMapRaised(iodisplay, iowindow);
1463
 
1464
 
1465
    /* now set up the menu-window, for now use the same text
1466
       dimensions as above */
1467
 
1468
    menuwindow = XCreateSimpleWindow(iodisplay, root, 0, 0, twidth,
1469
				     4 * box_h, 2, ioblack, iowhite);
1470
    for (i = 0; i < 4; i++) {
1471
	menupanes[i] = XCreateSimpleWindow(iodisplay, menuwindow, 0,
1472
					   box_h * i, twidth, box_h,
1473
					   1, ioblack, iowhite);
1474
	XSelectInput(iodisplay, menupanes[i],
1475
		     ButtonPressMask | ButtonReleaseMask | ExposureMask);
1476
    }
1477
 
1478
    /* XMapSubwindows(iodisplay, menuwindow); */
1479
 
1480
 
1481
    winattr.override_redirect = True;
1482
    XChangeWindowAttributes(iodisplay, menuwindow,
1483
			    CWBackingStore | CWOverrideRedirect, &winattr);
1484
    Rsync();
1485
 
1486
    /* this next sequence makes sure the window is up and ready before
1487
       you start drawing in it */
1488
 
1489
    XNextEvent(iodisplay, &ioevent);
1490
    if (ioevent.xany.type == Expose) {
1491
	while (ioevent.xexpose.count)
1492
	    XNextEvent(iodisplay, &ioevent);
1493
    }
1494
    XGetWindowAttributes(iodisplay, iowindow, &attribs);
1495
    bwidth = attribs.border_width;
1496
    fullwindowWidth = attribs.width;
1497
    fullwindowHeight = attribs.height;
1498
 
1499
 
1500
    /* set the active rectangle to be the upper left one */
1501
    crow = 1;
1502
    ccol = 1;
1503
    CellModified = FALSE;
1504
    return FALSE;/* success */
1505
}
1506
 
1507
/* MAC/X11 BASICS */
1508
 
1509
static void bell(void)
1510
{
1511
    XBell(iodisplay, 20);
1512
}
1513
 
1514
static void cleararea(int xpos, int ypos, int width, int height)
1515
{
1516
    XClearArea(iodisplay, iowindow, xpos, ypos, width, height, 0);
1517
}
1518
 
1519
static void clearwindow(void)
1520
{
1521
    XClearWindow(iodisplay, iowindow);
1522
}
1523
 
1524
static void copyarea(int src_x, int src_y, int dest_x, int dest_y)
1525
{
1526
    int mx = max(src_x, dest_x), my = max(src_y, dest_y);
1527
    XCopyArea(iodisplay, iowindow, iowindow, iogc, src_x, src_y,
1528
	      fullwindowWidth - mx, fullwindowHeight - my,
1529
	      dest_x, dest_y);
1530
    Rsync();
1531
}
1532
 
1533
static void copyH(int src_x, int dest_x, int width)
1534
{
1535
    XCopyArea(iodisplay, iowindow, iowindow, iogc, src_x+bwidth, hwidth,
1536
	      width, windowHeight+1, dest_x+bwidth, hwidth);
1537
}
1538
 
1539
#if 0
1540
static void drawline(int fromx, int fromy, int tox, int toy)
1541
{
1542
    XDrawLine(iodisplay, iowindow, iogc, fromx, fromy, tox, toy);
1543
}
1544
#endif
1545
 
1546
static void drawrectangle(int xpos, int ypos, int width, int height,
1547
			  int lwd, int fore)
1548
{
1549
    if (fore == 0)
1550
	XSetForeground(iodisplay, iogc, WhitePixel(iodisplay,
1551
						   DefaultScreen(iodisplay)));
1552
    else
1553
	XSetForeground(iodisplay, iogc, BlackPixel(iodisplay,
1554
						   DefaultScreen(iodisplay)));
1555
    XSetLineAttributes(iodisplay, iogc, lwd, LineSolid, CapRound, JoinRound);
1556
    XDrawRectangle(iodisplay, iowindow, iogc, xpos, ypos, width, height);
1557
}
1558
 
1559
static void drawtext(int xpos, int ypos, char *text, int len)
1560
{
1561
    XDrawImageString(iodisplay, iowindow, iogc, xpos,
1562
		     ypos, text, len);
1563
    Rsync();
1564
}
1565
 
1566
static void Rsync()
1567
{
1568
    XSync(iodisplay, 0);
1569
}
1570
 
1571
static int textwidth(char *text, int nchar)
1572
{
1573
    int t1;
1574
 
1575
    t1 = XTextWidth(font_info, text, nchar);
1576
    return t1;
1577
}
1578
 
1579
/* Menus */
1580
 
1581
void popupmenu(int x_pos, int y_pos, int col, int row)
1582
{
24274 ripley 1583
    int i, button, popupcol = col + colmin - 1;
1584
    char *name, clab[20];
13974 hornik 1585
    XEvent event;
1586
    Window selected_pane;
1587
    SEXP tvec;
1588
 
1589
    XMapSubwindows(iodisplay, menuwindow);
1590
    XMapRaised(iodisplay, menuwindow);
1591
    XMoveWindow(iodisplay, menuwindow, x_pos, y_pos);
1592
 
1593
    /* now fill in the menu panes with the correct information */
1594
 
24274 ripley 1595
    if (popupcol > xmaxused) {
1596
	/* extend work, names and lens */
1597
	REPROTECT(work = lengthgets(work, popupcol), wpi);
1598
	REPROTECT(names = lengthgets(names, popupcol), npi);
1599
	for (i = xmaxused+1; i < popupcol; i++) {
1600
	    sprintf(clab, "var%d", i + 1);
1601
	    SET_STRING_ELT(names, i, mkChar(clab));
1602
	}
1603
	REPROTECT(lens = lengthgets(lens, popupcol), lpi);
1604
	xmaxused = popupcol;
1605
    }
1606
    tvec = VECTOR_ELT(work, popupcol - 1);
1607
    name = CHAR(STRING_ELT(names, popupcol - 1));
13974 hornik 1608
    XDrawString(iodisplay,
1609
		menupanes[0], iogc, 3, box_h - 3, name, strlen(name));
1610
    for (i = 1; i < 4; i++)
1611
	XDrawString(iodisplay,
1612
		    menupanes[i], iogc, 3, box_h - 3,
1613
		    menu_label[i - 1], strlen(menu_label[i - 1]));
24274 ripley 1614
    if (isNull(tvec) || TYPEOF(tvec) == REALSXP)
13974 hornik 1615
	XDrawString(iodisplay, menupanes[1], iogc, box_w - 20, box_h - 3,
1616
		    "X", 1);
1617
    else
1618
	XDrawString(iodisplay, menupanes[2], iogc, box_w - 20, box_h - 3,
1619
		    "X", 1);
1620
 
1621
/*
1622
  start an event loop; we're looking for a button press and a button
1623
  release in the same window
1624
*/
1625
 
1626
    while (1) {
1627
	XNextEvent(iodisplay, &event);
1628
	if (event.type == ButtonPress) {
1629
	    button = event.xbutton.button;
1630
	    selected_pane = event.xbutton.window;
1631
	    for (i = 0; selected_pane != menupanes[i]; i++)
1632
		if (i >= 4) goto done;
1633
	    while (1) {
1634
		while (XCheckTypedEvent(iodisplay, ButtonPress, &event));
1635
		XMaskEvent(iodisplay, ButtonReleaseMask, &event);
1636
		if (event.xbutton.button == button)
1637
		    break;
1638
	    }
1639
	    if (selected_pane == event.xbutton.window) {
1640
		for (i = 0; selected_pane != menupanes[i]; i++);
1641
		switch (i) {
1642
		case 0:
1643
		    bell();
1644
		    break;
1645
		case 1:
24274 ripley 1646
		    if (isNull(tvec)) 
1647
			SET_VECTOR_ELT(work, popupcol - 1, 
1648
				       ssNewVector(REALSXP, 100));
1649
		    else
1650
			SET_VECTOR_ELT(work, popupcol - 1,
1651
				       coerceVector(tvec, REALSXP));
13974 hornik 1652
		    goto done;
1653
		case 2:
24274 ripley 1654
		    if (isNull(tvec)) 
1655
			SET_VECTOR_ELT(work, popupcol - 1, 
1656
				       ssNewVector(STRSXP, 100));
1657
		    else
1658
			SET_VECTOR_ELT(work, popupcol - 1, 
1659
				       coerceVector(tvec, STRSXP));
13974 hornik 1660
		    goto done;
1661
		case 3:
1662
		    closerect();
1663
		    ccol = col;
1664
		    crow = 0;
1665
		    clearrect();
1666
		    goto done;
1667
		}
1668
	    }
1669
	}
1670
        /* this doesn't work and perhaps I should move it up to the
1671
           main control loop */
1672
	else if (event.type == Expose) {
1673
	    if (event.xexpose.window == menuwindow) {
1674
		XDrawString(iodisplay, menupanes[0], iogc, 3, box_h - 3,
1675
			    name, strlen(name));
1676
		for (i = 1; i < 4; i++)
1677
		    XDrawString(iodisplay, menupanes[i], iogc, 3, box_h - 3,
1678
				menu_label[i - 1], strlen(menu_label[i - 1]));
1679
	    }
1680
	}
1681
    }
1682
 done:
1683
    popdownmenu();
1684
    highlightrect();
1685
}
1686
 
1687
void popdownmenu(void)
1688
{
1689
    XUnmapWindow(iodisplay, menuwindow);
1690
    XUnmapSubwindows(iodisplay, menuwindow);
1691
}
1692
 
1693
static void copycell(void)
1694
{
1695
    int i, whichrow = crow + colmin - 1, whichcol = ccol + colmin -1;
1696
    SEXP tmp;
1697
 
1698
    if (whichrow == 0) {
1699
	/* won't have  cell here */
1700
    } else {
1701
	strcpy(copycontents, "");
1702
	if (length(inputlist) >= whichcol) {
1703
	    tmp = CAR(nthcdr(inputlist, whichcol - 1));
1704
	    if (tmp != R_NilValue &&
1705
		(i = whichrow - 1) < (int)LEVELS(tmp) ) {
1706
		PrintDefaults(R_NilValue);
1707
		if (TYPEOF(tmp) == REALSXP) {
1708
		    if (REAL(tmp)[i] != ssNA_REAL)
1709
			strcpy(copycontents, EncodeElement(tmp, i, 0));
1710
		} else if (TYPEOF(tmp) == STRSXP) {
1711
		    if (!streql(CHAR(STRING_ELT(tmp, i)),
1712
				CHAR(STRING_ELT(ssNA_STRING, 0))))
1713
			strcpy(copycontents, EncodeElement(tmp, i, 0));
1714
		}
1715
	    }
1716
	}
1717
    }
1718
    highlightrect();
1719
}
1720
 
1721
static void pastecell(int row, int col)
1722
{
1723
    downlightrect();
1724
    crow = row; ccol = col;
1725
    if (strlen(copycontents)) {
1726
	strcpy(buf, copycontents);
1727
	clength = strlen(copycontents);
1728
	bufp = buf + clength;
1729
	CellModified = TRUE;
1730
    }
1731
    closerect();
1732
    highlightrect();
1733
}