//spreadwin.cpp, (c)2000-2008 by R. Lackner
//
//    This file is part of RLPlot.
//
//    RLPlot 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.
//
//    RLPlot 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 RLPlot; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
#include "rlplot.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>

#include <fcntl.h>				//file open flags
#include <sys/stat.h>			//I/O flags

#ifdef _WINDOWS
	#include <io.h>					//for read/write
#else
	#define O_BINARY 0x0
	#include <unistd.h>
#endif

extern const LineDEF GrayLine;
extern GraphObj *CurrGO, *TrackGO;			//Selected Graphic Objects
extern EditText *CurrText;
extern char *LoadFile;
extern char TmpTxt[];
extern Default defs;
extern UndoObj Undo;

static ReadCache *Cache = 0L;
static TextDEF ssText;
static char *szRlpData = "RLPlot data";

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Get item from *.csv file
bool GetItemCSV(char *Text, int cbText)
{
	char c;
	int i;

	for (i = 0,	*Text = 0; i < cbText; ) {
		c = Cache->Getc();
		switch(c) {
		case ',':			//column separator
			Text[i] = 0;
			return true;
		case 0x0a:			//end of line: mark by false return but text o.k.
			Text[i] = 0;
			return false;
		default:
			if(c > 0x20) Text[i++] = c;	//printable character
			else if(i >0 && c == 0x20) Text[i++] = c;
			else if(!c && Cache->IsEOF()) {
				Text[i] = 0;
				return false;
				}
			else Text[i] = 0;	//ignore non printing characters
			}
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// process a memory block (i.e. clipboard data) as if file input
int ProcMemData(GraphObj *g, unsigned char *ptr, bool dispatch)
{
	int i, RetVal = FF_UNKNOWN, nt, nl, nc, ns;

	if(ptr) {
		for(i = nt = nl = nc = ns = 0; ptr[i] && nl<100; i++) {
			switch(ptr[i]) {
			case 0x09:				//tab
				nt++;
				break;
			case 0x0a:				//LF
				nl++;
				break;
			case ',':
				nc++;
				break;
			case ' ':
				ns++;
				break;
				}
			}
		if(dispatch && i && !nt && !nl) {
			if(CurrText){
				g->Command(CMD_SETFOCUS, 0L, 0L);
				for(i = 0; ptr[i]; i++)	CurrText->AddChar(ptr[i], i? 0L : Undo.cdisp, 0L);
				g->Command(CMD_REDRAW, 0L, 0L);
				}
			}
		else if(nt) RetVal = FF_TSV;
		else if(nl && ptr[0] == '<') RetVal = FF_XML;
		else if(nc == nl && defs.DecPoint[0] == ',') RetVal = FF_TSV;
		else if(nl && nc && 0 == (nc % nl)) RetVal = FF_CSV;
		else if(nl && ns && 0 == (ns % nl)) RetVal = FF_SSV;
		else if(nl) RetVal = FF_TSV;
		if(dispatch) switch(RetVal) {
		case FF_CSV:	g->Command(CMD_PASTE_CSV, ptr, 0L);	break;
		case FF_TSV:	g->Command(CMD_PASTE_TSV, ptr, 0L);	break;
		case FF_SSV:	g->Command(CMD_PASTE_SSV, ptr, 0L);	break;
		case FF_XML:	g->Command(CMD_PASTE_XML, ptr, 0L); break;
			}
		}
	return RetVal;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This graphic object displays a spreadsheet
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SpreadWin:public GraphObj{
public:
	anyOutput *w;
	POINT ssOrg;
	RECT currRC;

	SpreadWin(GraphObj *par, DataObj *Data);
	~SpreadWin();
	void DoPlot(anyOutput *target);
	bool Command(int cmd, void *tmpl, anyOutput *o);

	bool ShowGrid(int CellWidth, int CellHeight, int FirstWidth, POINT *cpos);
	void MarkButtons(char *rng, POINT *cp = 0L);
	bool PrintData(anyOutput *o);
	void WriteGraphXML(unsigned char **ptr, long *cbd);

private:
	bool is_modified, bDoColWidth;
	char *filename;
	ssButton **cButtons, **rButtons;
	ssButton *aButton;
	POINT cpos;
	DataObj *d;
	int NumGraphs, CurrCol;
	Graph **g;
	RECT rc_line;
	POINT line[2];
};

SpreadWin::SpreadWin(GraphObj *par, DataObj *Data):GraphObj(par, Data)
{
	d = Data;	g = 0L;		ssOrg.x =  ssOrg.y = 0;		NumGraphs = 0;
	filename=0L;	aButton = 0L;
	w = 0L;		cButtons = rButtons = 0L;
	if(w = NewDispClass(this)){
		w->hasHistMenu = true;
		ssText.RotBL = ssText.RotCHAR = 0.0;
		ssText.fSize = 0.0f;
		ssText.iSize = w->un2iy(defs.GetSize(SIZE_CELLTEXT));
		ssText.Align = TXA_VCENTER | TXA_HLEFT;		ssText.Mode = TXM_TRANSPARENT;
		ssText.Style = TXS_NORMAL;					ssText.ColBg = 0x00e8e8e8L;
		ssText.ColTxt = 0x00000000L;				ssText.text = 0L;
		ssText.Font = FONT_HELVETICA;				w->SetTextSpec(&ssText);
		w->SetMenu(MENU_SPREAD);					w->FileHistory();
		w->Erase(0x00e8e8e8L);						w->Caption(szRlpData, false);
		d->ri->SetDefWidth(w->un2ix(defs.GetSize(SIZE_CELLWIDTH)));
		d->ri->SetHeight(w->un2iy(defs.GetSize(SIZE_CELLTEXT)/defs.ss_txt) + 2);
		d->ri->SetFirstWidth(32);
		}
	else if(d &&  d->ri) {
		d->ri->SetHeight(19);	d->ri->SetDefWidth(76);	d->ri->SetFirstWidth(32);
		}
	Id = GO_SPREADDATA;
	is_modified = bDoColWidth = false;
}

SpreadWin::~SpreadWin()
{
	int i;

	if(parent) {
		if(cButtons) {
			for(i = 0; cButtons[i]; i++) if(cButtons[i]) delete(cButtons[i]);
			free(cButtons);
			}
		if(rButtons) {
			for(i = 0; rButtons[i]; i++) if(rButtons[i]) delete(rButtons[i]);
			free(rButtons);
			}
		if (aButton) delete(aButton);
		if (w) delete w;
		if (g && NumGraphs) {
			for(i = 0; i < NumGraphs; i++) if(g[i]) delete(g[i]);
			free (g);
			}
		if(filename) free(filename);	filename=0L;
		}
}

void
SpreadWin::DoPlot(anyOutput *o)
{
	if(!(o->ActualSize(&currRC)))return;
	o->StartPage();
	d->Command(CMD_DOPLOT, (void*)this, o);
	o->EndPage();
}

bool
SpreadWin::Command(int cmd, void *tmpl, anyOutput *o)
{
	char *Name;
	Graph *g2;
	int i, j, k;
	bool bRet;
	MouseEvent *mev;
	POINT p1, p2;

	if(d) {
		if(!o) o = w;
		switch(cmd) {
		case CMD_CURRPOS:
			if(tmpl && cButtons && rButtons) {
				int ac = 1, na =  0;
				RECT urc;
				if(((POINT*)tmpl)->x != cpos.x) {
					for(cpos.x = ((POINT*)tmpl)->x, i = 0; cButtons[i]; i++) {
						cButtons[i]->Command(CMD_SELECT, (cpos.x == (i+ssOrg.x)) ? &ac : &na, w);
						}
					urc.left = cButtons[0]->rDims.left;		urc.bottom = cButtons[0]->rDims.bottom;
					urc.top = cButtons[0]->rDims.top;		urc.right = urc.left + d->ri->GetWidth(i+ssOrg.x) * (i-1);
					w->UpdateRect(&urc, false);
					}
				if(((POINT*)tmpl)->y != cpos.y) {
					for(cpos.y = ((POINT*)tmpl)->y, i = 0; rButtons[i]; i++) {
						rButtons[i]->Command(CMD_SELECT, (cpos.y == (i+ssOrg.y)) ? &ac : &na, w);
						}
					urc.left = rButtons[0]->rDims.left;		urc.right = rButtons[0]->rDims.right;
					urc.top = rButtons[0]->rDims.top;		urc.bottom = urc.top + d->ri->GetHeight(i+ssOrg.y) * (i-1);
					w->UpdateRect(&urc, false);
					}
				}
			else return false;
			return true;
		case CMD_CAN_CLOSE:
			HideTextCursor();
			if(is_modified == true) {
				is_modified=false;
				if(Undo.isEmpty(0L)) return true;
				i = YesNoCancelBox("The spreadsheet or a graph has been modified!\n\nDo you want to save it now?");
				if(i == 2) return false;
				else if(i == 1) return Command(CMD_SAVEAS, tmpl, o);
				}
			return true;
		case CMD_MRK_DIRTY:
			if(!is_modified) {
				o->Caption(filename && filename[0]?filename : szRlpData, true);
				}
			return is_modified = true;
		case CMD_WRITE_GRAPHS:
			if (g && NumGraphs) WriteGraphXML((unsigned char**)tmpl, (long*)o);
			return true;
		case CMD_DROP_GRAPH:
			if(!tmpl) return false;				if(o) o->FileHistory();
			if(g && NumGraphs) {
				if(g = (Graph**)realloc(g, (NumGraphs+2) * sizeof(Graph*)))
					g[NumGraphs++] = (Graph *)tmpl;
				else return false;
				}
			else {
				if(g = (Graph **)calloc(2, sizeof(Graph*))){
					g[0] = (Graph *)tmpl;		NumGraphs = 1;
					}
				}
			for(i = j = 0; i < NumGraphs; i++) {
				if(g[i]) {
					g[j] = g[i];			g[j]->parent = this;
					g[j]->Command(CMD_SET_DATAOBJ, (void*)d, 0L);
					j++;
					}
				}
			NumGraphs = j;	g[j-1]->DoPlot(0L);
			return true;
		case CMD_NEWGRAPH:
			if((g2 = new Graph(this, d, 0L, 0)) && g2->PropertyDlg() && 
				Command(CMD_DROP_GRAPH, g2, o))return Command(CMD_REDRAW, 0L, o);
			else if(g2) DeleteGO(g2);
			Undo.SetDisp(w);
			return false;
		case CMD_NEWPAGE:
			if((g2 = new Page(this, d)) && 
				Command(CMD_DROP_GRAPH, g2, o))return Command(CMD_REDRAW, 0L, o);
			else if(g2) DeleteGO(g2);
			Undo.SetDisp(w);
			return false;
		case CMD_DELGRAPH:
			if (g && NumGraphs) {
				for(i = 0; i < NumGraphs; i++) if(g[i]) DeleteGO(g[i]);
				free (g);
				}
			g = 0L;			NumGraphs = 0;		Undo.Flush();
			return true;
		case CMD_DELOBJ:
			i = j = 0;
			if(g && tmpl) for(; i < NumGraphs; i++) {
				if(g[i] == (Graph*) tmpl) {
					DeleteGO(g[i]);
					}
				else (g[j++] = g[i]);
				}
			if(g && j < i) g[j] = 0L;			NumGraphs = j;
			return true;
		case CMD_SAVE:
			if(o) o->MouseCursor(MC_WAIT, false);
			if(d->WriteData(0L)) {
				o->Caption(filename && filename[0]?filename : szRlpData, false);
				is_modified=false;
				if(o) o->MouseCursor(MC_ARROW, false);
				return true;
				}
			if(o) o->MouseCursor(MC_ARROW, true);
		case CMD_SAVEAS:
			is_modified=false;
			if((Name = SaveDataAsName(filename)) && Name[0]){
				if(o) o->FileHistory();
				if(Name && d->WriteData(Name)) {
					o->Caption(filename && filename[0]?filename : szRlpData, false);
					if(filename) free(filename);
					filename = (char*)memdup(Name, (int)strlen(Name)+1, 0);
					}
				else return false;
				}
			else return false;
			return true;
		case CMD_DROPFILE:
			if(!Command(CMD_CAN_CLOSE, 0L, o)) return false;
			if(IsRlpFile((char*)tmpl)) return OpenGraph(this, (char*)tmpl, 0L, false);
			else if(d->ReadData((char*)tmpl, 0L, FF_UNKNOWN)){
				if(filename) free(filename);
				filename = (char*)memdup(tmpl, (int)strlen((char*)tmpl)+1, 0);
				o->Caption(filename && filename[0]?filename : szRlpData, is_modified = false);
				return Command(CMD_SETSCROLL, 0L, w);
				}
			else ErrorBox("The selected file is not valid\nor not accessible!\n");
			return false;
		case CMD_OPEN:
			if(!Command(CMD_CAN_CLOSE, 0L, o)) return false;
			Undo.KillDisp(o);		Undo.SetDisp(o);
			if((Name = OpenDataName(filename)) && Name[0]){
				if(o) o->FileHistory();
				if(IsRlpFile(Name)) return OpenGraph(this, Name, 0L, false);
				else if(d->ReadData(Name, 0L, FF_UNKNOWN)){
					if(filename) free(filename);
					filename = (char*)memdup(Name, (int)strlen(Name)+1, 0);
					return Command(CMD_SETSCROLL, 0L, w);
					}
				}
			return false;
		case CMD_ADDROWCOL:
			if(DoSpShSize(d, this)) DoPlot(o);
			return true;
		case CMD_COL_MOUSE:
			if(o && cButtons && rButtons && (mev = (MouseEvent*)tmpl) && mev->y > o->MenuHeight) {
				for(i = 0; cButtons[i]; i++){
					if(bDoColWidth) {
						o->MouseCursor(MC_COLWIDTH, false);
						}
					else if(mev->x > cButtons[i]->rDims.left && mev->x < cButtons[i]->rDims.right+2){
						if(mev->x > cButtons[i]->rDims.right-4) {
							switch(mev->Action) {
							case MOUSE_LBDOWN:
								CurrCol = i;					line[0].x = line[1].x = mev->x;
								line[0].y = o->MenuHeight;		line[1].y = currRC.bottom;
								d->Command(CMD_TOOLMODE, tmpl, o);
								o->MouseCursor(MC_COLWIDTH, false);
								return bDoColWidth = true;
								}
							o->MouseCursor(MC_COLWIDTH, false);
							}
						else o->MouseCursor(MC_ARROW, false);
						return false;
						}
					else o->MouseCursor(MC_ARROW, false);
					if(mev->Action == MOUSE_MOVE && bDoColWidth && (mev->StateFlags & 0x01)) {
						rc_line.left = line[0].x - 2;		rc_line.right = line[1].x + 2;
						rc_line.top = line[0].y - 2;		rc_line.bottom = line[1].y +2;
						o->UpdateRect(&rc_line, false);
						if(mev->x < (cButtons[CurrCol]->rDims.left +20))mev->x = cButtons[CurrCol]->rDims.left +20;
						k = mev->x - cButtons[CurrCol]->rDims.right;
						for(j = CurrCol; cButtons[j] && cButtons[j]->rDims.left < (currRC.right-k); j++) {
							cButtons[j]->rDims.right += k;	cButtons[j]->rDims.left += j > CurrCol ? k : 0;
							cButtons[j]->DoPlot(o);			o->UpdateRect(&cButtons[j]->rDims, false);
							cButtons[j]->rDims.right -= k;	cButtons[j]->rDims.left -= j > CurrCol ? k : 0;
							}
						line[0].x = line[1].x = mev->x;
						line[0].y = o->MenuHeight;			line[1].y = currRC.bottom;
						o->ShowLine(line, 2, 0x00a0a0a0);
						return true;
						}
					if(mev->Action == MOUSE_LBUP && bDoColWidth) {
						bDoColWidth = false;
						k = line[0].x - cButtons[CurrCol]->rDims.left;
						if(abs(k - cButtons[CurrCol]->rDims.right + cButtons[CurrCol]->rDims.left)>3) {
							rlp_strcpy(TmpTxt, 40, mkRangeRef(0, CurrCol + ssOrg.x, 0, CurrCol + ssOrg.x));
							d->ri = new RangeInfo(1, TmpTxt, d->ri);
							d->ri->SetWidth(k >=20 ? k : 20);
							}
						d->Command(CMD_REDRAW, 0L, o);
						return true;
						}
					else bDoColWidth = false;
					}
				}
			else o->MouseCursor(MC_ARROW, false);
			return false;
		case CMD_MOUSE_EVENT:
			if(!(mev =(MouseEvent*)tmpl)) return false;
			if(bDoColWidth)return Command(CMD_COL_MOUSE, tmpl, o);
			p1.x = mev->x;		p1.y = mev->y;
			if(mev->y < o->MenuHeight) o->MouseCursor(MC_ARROW, false);
			else if(o && cButtons && rButtons) {
				if(mev->x < d->ri->GetFirstWidth() && mev->y < (o->MenuHeight + d->ri->GetHeight(-1)) && aButton) {
					aButton->Command(cmd, tmpl, o);
					}
				else if(mev->x < d->ri->GetFirstWidth() && rButtons) {
					if(!(d->mpos2dpos(&p1, &p2, false)))return false;
					p2.x -= ssOrg.x;		p2.y -= ssOrg.y;
					if (p2.y < 0 || p2.y >= (d->r_disp-1)) return false;
					if(rButtons[p2.y]) rButtons[p2.y]->Command(cmd, tmpl, o);
					}
				else if(mev->y < (o->MenuHeight + d->ri->GetHeight(-1)) && cButtons) {
					if(!(d->mpos2dpos(&p1, &p2, false)))return false;
					p2.x -= ssOrg.x;		p2.y -= ssOrg.y;
					if (p2.x < 0 || p2.x >= d->c_disp) return false;
					if(cButtons[p2.x]) cButtons[p2.x]->Command(cmd, tmpl, o);
					}
				else if(o->MrkMode == MRK_SSB_DRAW) o->HideMark();
				}
			return	d->Command(cmd, tmpl, o);
		case CMD_PASTE_TSV:		case CMD_PASTE_CSV:		case CMD_PASTE_SSV:
			Undo.DataObject(this, w, d, 0L, 0L);
		case CMD_COPY_SYLK:		case CMD_ADDCHAR:		case CMD_SHIFTUP:
		case CMD_COPY_TSV:		case CMD_COPY_XML:		case CMD_QUERY_COPY:
		case CMD_TAB:			case CMD_SHTAB:			case CMD_SHIFTDOWN:
		case CMD_CURRLEFT:		case CMD_CURRIGHT:		case CMD_CURRUP:
		case CMD_CURRDOWN:		case CMD_SHIFTRIGHT:		case CMD_POS_FIRST:
		case CMD_POS_LAST:		case CMD_SHIFTLEFT:		case CMD_DELETE:
		case CMD_TOOLMODE:		case CMD_FILLRANGE:		case CMD_CUT:
		case CMD_PASTE_XML:		case CMD_DELROW:		case CMD_INSROW:
		case CMD_INSCOL:		case CMD_DELCOL:		case CMD_UNDO:
		case CMD_SHPGUP:		case CMD_SHPGDOWN:		case CMD_HIDEMARK:
			bDoColWidth = false;	bRet = d->Command(cmd, tmpl, o);
			if(cmd == CMD_UNDO && is_modified && Undo.isEmpty(0L)) {
				w->Caption(filename && filename[0]?filename : szRlpData, is_modified = false);
				}
			return bRet;
		case CMD_MENUHEIGHT:
			if(w) w->MenuHeight = defs.iMenuHeight;
			bDoColWidth = true;
		case CMD_REDRAW:
			Undo.SetDisp(w);					d->Command(cmd, tmpl, o);
			return true;
		case CMD_MOUSECURSOR:
			if(o)o->MouseCursor(MC_ARROW, false);	
			return true;
		case CMD_SETSCROLL:
			HideTextCursor();					if(!(o->ActualSize(&currRC)))return false;
			k = (currRC.bottom-currRC.top)/d->ri->GetHeight(-1);
			d->GetSize(&i, &j);			o->MouseCursor(MC_WAIT, true);
			o->SetScroll(true, 0, j, k, ssOrg.y);	k = (currRC.right-currRC.left)/d->ri->GetWidth(-1);
			o->SetScroll(false, 0, i, k, ssOrg.x);	DoPlot(o);
			o->MouseCursor(MC_ARROW, true);
			return true;
		case CMD_PAGEUP:		case CMD_PAGEDOWN:
			k = (currRC.bottom-currRC.top)/d->ri->GetHeight(-1);
			k = k > 3 ? k-2 : 1;					p1.x = d->ri->GetFirstWidth() + 2;
			p1.y = d->ri->GetHeight(-1) + 2;
			if(CurrText){
				p1.x = CurrText->GetX()+2;	p1.y = CurrText->GetY()+12;
				}
			d->GetSize(&i, &j);
			if(cmd == CMD_PAGEUP) ssOrg.y = ssOrg.y > k ? ssOrg.y-k : 0;
			else ssOrg.y = ssOrg.y < j-k*2 ? ssOrg.y+k : j > k ? j-k : 0;
			Command(CMD_SETSCROLL, tmpl, o);
			CurrText = 0L;		d->Select(&p1);
			return true;
		case CMD_SETHPOS:
			ssOrg.x = *((int*)tmpl) >= 0 ? *((long*)tmpl) : 0L;
			return Command(CMD_SETSCROLL, tmpl, o);
		case CMD_SETVPOS:
			ssOrg.y = *((int*)tmpl) >= 0 ? *((long*)tmpl) : 0L;
			return Command(CMD_SETSCROLL, tmpl, o);
		case CMD_SETFOCUS:
			Undo.SetDisp(w);
			return true;
		case CMD_KILLFOCUS:
			return true;
		case CMD_TEXTSIZE:
			if(tmpl && *((int*)tmpl) != ssText.iSize) {
				Undo.ValInt(this, &ssText.iSize, UNDO_CONTINUE);
				ssText.iSize = o->un2iy(defs.GetSize(SIZE_CELLTEXT));
				}
			return true;
		case CMD_CONFIG:
			if(defs.PropertyDlg()) return Command(CMD_REDRAW, 0L, o);
			return false;
		case CMD_NONE:
			return true;
		case CMD_PRINT:
			return PrintData(o);
			}
		}
	return false;
}

bool
SpreadWin::ShowGrid(int CellWidth, int CellHeight, int FirstWidth, POINT *cp)
{
	int i, c, nr, nc, cx, cw, ac = 1, na = 0;
	RECT rc;
	char text[20];
	TextDEF ButtText;
	bool redim = bDoColWidth;

	w->HideMark();
	cpos.x = cp->x;			cpos.y = cp->y;
	if(d->ri->GetHeight(-1) != CellHeight || d->ri->GetWidth(-1) != CellWidth || d->ri->GetFirstWidth() != FirstWidth) redim = true;
	if(redim){
		d->ri->SetHeight(CellHeight);		d->ri->SetDefWidth(CellWidth);		d->ri->SetFirstWidth(FirstWidth);
		if(cButtons) {
			for(i = 0; cButtons[i]; i++) if(cButtons[i]) delete(cButtons[i]);
			free(cButtons);
			}
		if(rButtons) {
			for(i = 0; rButtons[i]; i++) if(rButtons[i]) delete(rButtons[i]);
			free(rButtons);
			}
		if(aButton) delete(aButton);		aButton = 0L;
		cButtons = rButtons = 0L;
		}
	if(!aButton) aButton = new ssButton(this, 0, w->MenuHeight, FirstWidth, CellHeight);
	memcpy(&ButtText, &ssText, sizeof(TextDEF));
	ButtText.Align = TXA_HCENTER | TXA_VCENTER;
	w->GetSize(&rc);
	if(!cButtons) {
		c = d->c_disp;		if(c < 40 || c > 200) d->c_disp = c = 40;
		cButtons = (ssButton **)calloc(c, sizeof(ssButton*));
		for(i = 0, cx = FirstWidth; i < (c-1); i++) {
			cw = d->ri->GetWidth(i+ssOrg.x);
			cButtons[i] = new ssButton(this, cx, w->MenuHeight, cw+1, CellHeight);
			cx += cw;
			}
		}
	else {
		for(i = 0, cx = FirstWidth; cButtons[i]; i++) {
			cw = d->ri->GetWidth(i+ssOrg.x);
			cButtons[i]->rDims.left = cx;		cButtons[i]->rDims.right = cx + cw + 1;
			cx += cw;
			}
		}
	if(!rButtons) {
 		c = d->r_disp;			if(c < 60 || c > 300) d->c_disp = c = 60;
		rButtons = (ssButton**)calloc(c, sizeof(ssButton*));
		for(i = 0; i < (c-1); i++) rButtons[i] = 
			new ssButton(this, 0, i*CellHeight+CellHeight+w->MenuHeight, 
			FirstWidth, CellHeight);
		}
	d->GetSize(&nc, &nr);
	if(rButtons) for(i = 0; rButtons[i]; i++) {
#ifdef USE_WIN_SECURE
		sprintf_s(text, 20, "%d", i+1+ssOrg.y);
#else
		sprintf(text, "%d", i+1+ssOrg.y);
#endif
		if(rButtons[i]) {
			rButtons[i]->Command(CMD_SETTEXTDEF, &ButtText, w);
			rButtons[i]->Command(CMD_SETTEXT, text, w);
			rButtons[i]->Command(CMD_SELECT, (cpos.y == (i+ssOrg.y)) ? &ac : &na, w);
			}
		}
	if(cButtons) for(i = 0; cButtons[i]; i++) {
		if(cButtons[i]) {
			cButtons[i]->Command(CMD_SETTEXTDEF, &ButtText, w);
			cButtons[i]->Command(CMD_SETTEXT, Int2ColLabel(i+ssOrg.x, true), w);
			cButtons[i]->Command(CMD_SELECT, (cpos.x == (i+ssOrg.x)) ? &ac : &na, w);
			}
		}
	w->SetTextSpec(&ssText);
	if(aButton) aButton->DoPlot(w);
	return true;
}

void
SpreadWin::MarkButtons(char *rng, POINT *cp)
{
	int i, r, c, ncb, nrb, *r_idx, *c_idx;
	AccRange *mr;

	if(!rButtons || !cButtons) return;
	for(ncb = 0; cButtons[ncb]; ncb++);		ncb--;
	for(nrb = 0; rButtons[nrb]; nrb++);		nrb--;
	if(!nrb || !ncb || !(r_idx=(int*)calloc(nrb,sizeof(int))) || !(c_idx=(int*)calloc(ncb,sizeof(int)))) return;
	if(rng && rng[0] && (mr = new AccRange(rng)) && mr->GetFirst(&c, &r)) {
		mr->NextCol(&c);
		do {
			if((i = c - ssOrg.x) >= 0 && i < ncb) c_idx[i] = 1; 
			}while(mr->NextCol(&c));
		mr->GetFirst(&c, &r);			mr->NextRow(&r);
		do {
			if((i = r - ssOrg.y) >= 0 && i < nrb) r_idx[i] = 1; 
			}while(mr->NextRow(&r));
		delete mr;
		}
	for(i = 0; i < ncb; i++) cButtons[i]->Command(CMD_SETSTYLE, &c_idx[i], w); 
	for(i = 0; i < nrb; i++) rButtons[i]->Command(CMD_SETSTYLE, &r_idx[i], w);
	if(cp) {
		c_idx[0] = r_idx[0] = 0;	c_idx[1] = r_idx[1] = 1;
		r = (cp->y - ssOrg.y);		c = cp->x -ssOrg.x;
		for(i = 0; i < ncb; i++) cButtons[i]->Command(CMD_SELECT, c == i ? &c_idx[1] : &c_idx[0], w); 
		for(i = 0; i < nrb; i++) rButtons[i]->Command(CMD_SELECT, r == i ? &r_idx[1] : &r_idx[0], w);
		}
	free(r_idx);		free(c_idx);
}

bool
SpreadWin::PrintData(anyOutput *o)
{
	int i, j, k, l, pgw, pfw, pcw, pch, rpp, cpp, nc, nr, ix, iy, cpages;
	int row, col, width, height, ipad, mode;
	double scale;
	RECT rc, margin, margin_first;
	POINT pp_pos, ss_pos, grid[3];
	LineDEF Line1, Line2;
	TextDEF td, tdp;
	bool bContinue;
	time_t ti = time(0L);
	anyResult res;

	Line1.patlength = Line2.patlength = 1.0;
	Line1.color = Line2.color = 0x00808080;		//gray gridlines
	Line1.pattern = Line2.pattern = 0x0;		//solid lines
	switch(defs.cUnits) {
	case 1:		Line1.width = 0.01;			break;
	case 2:		Line1.width = 0.003937;		break;
	default:	Line1.width = 0.1;			break;
		}
#ifdef _WINDOWS
	scale = 1.0/_SQRT2;
#else
	scale = 0.9;
#endif
	Line2.width = Line1.width * 3.0;
	d->GetSize(&nc, &nr);
	if(!(o->StartPage())) return false;
	pfw = iround(o->hres * ((double)d->ri->GetFirstWidth())/w->hres * scale);
	pcw = iround(o->hres * ((double)d->ri->GetWidth(-1))/w->hres * scale);
	pch = iround((o->vres * ((double)d->ri->GetHeight(-1))/w->vres) * scale + o->vres/20.0);
	o->ActualSize(&rc);
	tdp.ColTxt = 0x0;	tdp.ColBg = 0x00ffffffL; 
	tdp.fSize = tdp.RotBL = tdp.RotCHAR = 0.0;	tdp.Align = TXA_HRIGHT | TXA_VCENTER;
	tdp.Mode = TXM_TRANSPARENT;
	tdp.Style = TXS_NORMAL;				tdp.Font = FONT_HELVETICA;	tdp.text = 0L;
	memcpy(&td, &ssText, sizeof(TextDEF));	
	td.Align = TXA_HCENTER | TXA_VCENTER;
	td.Style = TXS_NORMAL;	td.iSize = 0;
#ifdef _WINDOWS
	tdp.iSize = iround(o->hres/6.0);
	td.fSize = defs.GetSize(SIZE_CELLTEXT);
#else
	tdp.iSize = iround(o->hres/7.5);
	td.fSize = defs.GetSize(SIZE_CELLTEXT)*.8;
#endif
	margin.left = iround(o->hres);	margin.right = iround(o->hres/2.0);
	margin.top = margin.bottom = iround(o->hres);
	memcpy(&margin_first, &margin, sizeof(RECT));
	pp_pos.x = margin.left;		pp_pos.y = margin.top;
	ss_pos.x = 0;				ss_pos.y = 0;		cpages = 1;
	rpp = (rc.bottom - margin.top - margin.bottom - pch)/pch;
	mode = 0;
	if((nr * pch) < ((rc.bottom - rc.top - margin.top - margin.bottom)>>1)) mode = 1;
	do {
		k = (rc.right - margin.left - margin.right - pfw);
		for(i = pgw = 0; pgw < k && (i+ss_pos.x) < nc; i++ ){
			pgw += (pcw = iround(o->hres * ((double)d->ri->GetWidth(i+ss_pos.x))/w->hres * scale));
			if(i >=(nc-1)) break;
			}
		if(pgw < k) {
			cpp = i+1;
			if(!mode && !ss_pos.x && pgw < (k>>1)) mode = 2;
			}
		else {
			cpp = i-1;		pgw -= pcw;
			}
		mode = mode;
		pp_pos.x = margin.left;		pp_pos.y = margin.top;
		k = (ss_pos.x + cpp) > nc ? nc-ss_pos.x : cpp;
		l = (ss_pos.y + rpp) > nr ? nr-ss_pos.y : rpp;
		grid[0].y = margin.top +pch; grid[1].y = grid[0].y + l * pch;
		o->SetLine(&Line2);
		grid[0].x = grid[1].x = pp_pos.x;		o->oSolidLine(grid);
		grid[0].x = grid[1].x = pp_pos.x+pfw;	o->oSolidLine(grid);
		grid[0].x = margin.left+pfw;	grid[1].x = grid[0].x + pgw;
		o->SetLine(&Line2);
		grid[0].y = grid[1].y = pp_pos.y;		o->oSolidLine(grid);
		grid[0].y = grid[1].y = pp_pos.y+pch;	o->oSolidLine(grid);
		o->SetLine(&Line2);
		td.Align = TXA_HCENTER | TXA_VCENTER;	o->SetTextSpec(&td);
		grid[0].y = margin.top;	 grid[1].y = grid[0].y + pch;
		iy = margin.top + (pch >>1);	grid[0].x = grid[1].x = pp_pos.x + pfw;
		for(i = 0, ix = pp_pos.x + pfw; i <= k; i++) {			//column headers
			pcw = iround(o->hres * ((double)d->ri->GetWidth(i+ss_pos.x))/w->hres * scale);
			o->oSolidLine(grid);		grid[0].x = grid[1].x = ix + pcw;
			if(i < k) o->oTextOut(ix + (pcw >>1), iy, Int2ColLabel(i+ss_pos.x, true), 0);
			ix += pcw;
			}
		td.Align = TXA_HRIGHT | TXA_VCENTER;	
		o->SetTextSpec(&td);	ix = margin.left + pfw - iround(o->hres/20.0);
		grid[0].x = margin.left;		grid[1].x = grid[0].x + pfw;
		for(i = 0; i <= l; i++) {			//row labels
			grid[0].y = grid[1].y = pp_pos.y + pch + i * pch;
			o->oSolidLine(grid);		iy = grid[0].y + (pch >>1);
#ifdef USE_WIN_SECURE
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "%d", i+1+ss_pos.y);
#else
			sprintf(TmpTxt, "%d", i+1+ss_pos.y);
#endif
			if(i < l) o->oTextOut(ix, iy, TmpTxt, 0);
			}
		ipad = iround(o->hres/30.0);
		grid[0].x = margin.left+pfw + ipad;
		for(i = 0; i < k; i++, grid[0].x += pcw) {			//spreadsheet data
			pcw = iround(o->hres * ((double)d->ri->GetWidth(i+ss_pos.x))/w->hres * scale);
			for (j = 0; j < l; j++) {
				row = j+ss_pos.y;		col = i+ss_pos.x;
				if(row >= 0 && row < d->cRows && col >= 0  && col < d->cCols && d->etRows[row][col]) {
					d->etRows[row][col]->GetResult(&res, false);
					TranslateResult(&res);
					td.Align = TXA_HLEFT | TXA_VCENTER;
					ix = grid[0].x;
					switch (res.type) {
					case ET_VALUE:
						ix = ix + pcw - ( ipad<<1 );
						td.Align = TXA_HRIGHT | TXA_VCENTER;
						rlp_strcpy(TmpTxt, TMP_TXT_SIZE, res.text);
						fit_num_rect(o, pcw - ipad, TmpTxt);
						Int2Nat(TmpTxt);			break;
					case ET_BOOL:	case ET_DATE:	case ET_TIME:	case ET_DATETIME:
						ix = ix + pcw - ( ipad<<1 );
						td.Align = TXA_HRIGHT | TXA_VCENTER;
					case ET_TEXT:	case ET_UNKNOWN:
						if(res.text&& strlen(res.text) < 40) 
							rlp_strcpy(TmpTxt, TMP_TXT_SIZE, res.text[0] != '\'' ? res.text : res.text +1);
						else if(res.text) rlp_strcpy(TmpTxt, TMP_TXT_SIZE, "#SIZE");
						else TmpTxt[0] = 0;	
						do {
							o->oGetTextExtent(TmpTxt, (int)strlen(TmpTxt), &width, &height);
							if(width > (pcw + iround(o->hres/20.0))) TmpTxt[strlen(TmpTxt)-1] = 0;
							}while(width > (pcw + ipad));
						break;
					case ET_ERROR:
						rlp_strcpy(TmpTxt, TMP_TXT_SIZE, "#ERROR");	break;
					default:
						rlp_strcpy(TmpTxt, TMP_TXT_SIZE, "#VALUE");	break;
						}
					iy = pp_pos.y + pch + (pch>>1) + j * pch;
					o->SetTextSpec(&td);			o->oTextOut(ix, iy, TmpTxt, 0);
					grid[0].y = grid[1].y = iy+(pch>>1);	grid[2].y = grid[1].y - pch;
					grid[0].x -= ipad;		grid[1].x = grid[2].x = grid[0].x + pcw;
					o->SetLine(&Line1);		o->oPolyline(grid, 3, 0L);
					grid[0].x += ipad;
					}
				}
			}
		//prepare for next table
		ss_pos.x += k;			bContinue = false;
		if(ss_pos.x >= nc) {ss_pos.x = 0; ss_pos.y += l; }
		if(ss_pos.y < nr) {
			ix = pfw + pgw + iround(o->hres/3.5);
			iy = (l+2)*pch;
			if(mode == 2 && (margin.left + ix + pfw + pgw) < (rc.right-margin.right)) {
				margin.left += pfw + k*pcw + iround(o->hres/3.5);
				bContinue = true;
				}
			else if(mode == 1 && (margin.top + iy + pch + l*pch) < (rc.bottom - margin.bottom)) {
				margin.top += iy;	margin.left = margin_first.left;
				bContinue = true;
				}
			else {
				tdp.Align = TXA_HRIGHT | TXA_VCENTER; o->SetTextSpec(&tdp);
#ifdef USE_WIN_SECURE
				sprintf_s(TmpTxt, TMP_TXT_SIZE, "page %d", cpages++);
#else
				sprintf(TmpTxt, "page %d", cpages++);
#endif
				o->oTextOut(rc.right-margin.right, rc.bottom-(margin.bottom>>1), TmpTxt, 0);
				tdp.Align = TXA_HCENTER | TXA_VCENTER; o->SetTextSpec(&tdp);
#ifdef USE_WIN_SECURE
				ctime_s(TmpTxt, 50, &ti);	TmpTxt[24] = 0;
#else
				sprintf(TmpTxt, "%s", ctime(&ti));	TmpTxt[24] = 0;
#endif
				o->oTextOut((rc.right-rc.left)>>1, rc.bottom-(margin.bottom>>1), TmpTxt, 0);
				tdp.Align = TXA_HLEFT | TXA_VCENTER; o->SetTextSpec(&tdp);
				i = rlp_strcpy(TmpTxt, TMP_TXT_SIZE, "RLPlot ");
				rlp_strcpy(TmpTxt+i, TMP_TXT_SIZE-i, SZ_VERSION);
				o->oTextOut(margin_first.left, rc.bottom-(margin.bottom>>1), TmpTxt, 0);
				memcpy(&margin, &margin_first, sizeof(RECT));
				bContinue = true;
				o->Eject();
				}
			}
		}while(bContinue);
	tdp.Align = TXA_HRIGHT | TXA_VCENTER; o->SetTextSpec(&tdp);
#ifdef USE_WIN_SECURE
	sprintf_s(TmpTxt, TMP_TXT_SIZE, "page %d", cpages++);
#else
	sprintf(TmpTxt, "page %d", cpages++);
#endif
	o->oTextOut(rc.right-margin.right, rc.bottom-(margin.bottom>>1), TmpTxt, 0);
	tdp.Align = TXA_HCENTER | TXA_VCENTER; o->SetTextSpec(&tdp);
#ifdef USE_WIN_SECURE
	ctime_s(TmpTxt, 50, &ti);	TmpTxt[24] = 0;
#else
	sprintf(TmpTxt, "%s", ctime(&ti));	TmpTxt[24] = 0;
#endif
	o->oTextOut((rc.right-rc.left)>>1, rc.bottom-(margin.bottom>>1), TmpTxt, 0);
	tdp.Align = TXA_HLEFT | TXA_VCENTER; o->SetTextSpec(&tdp);
	i = rlp_strcpy(TmpTxt, TMP_TXT_SIZE, "RLPlot ");
	rlp_strcpy(TmpTxt+i, TMP_TXT_SIZE-i, SZ_VERSION);
	o->oTextOut(margin_first.left, rc.bottom-(margin.bottom>>1), TmpTxt, 0);
	o->EndPage();
	return true;
}

void 
SpreadWin::WriteGraphXML(unsigned char **ptr, long *cbd)
{
	unsigned char *pg;
	long cb = 0, size = 0, newsize;
	int i;

	for(i = 0; i < NumGraphs; i++) if(g[i]) {
		if((pg = (unsigned char*)GraphToMem(g[i], &cb)) && cb) {
			newsize = *cbd + cb + 100;
			*ptr = (unsigned char*)realloc(*ptr, newsize);
			*cbd += rlp_strcpy((char*)(*ptr)+*cbd, 20, "<Graph><![CDATA[\n");
			memcpy(*ptr+ *cbd, pg, cb);		*cbd += cb;
			*cbd += rlp_strcpy((char*)(*ptr)+*cbd, 20, "]]>\n</Graph>\n");
			if(pg) free(pg);		pg = 0L;			cb = 0;
			}
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This data object is a spreadsheet
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SpreadData:public DataObj{
typedef struct _pos_info {
	POINT ssOrg, currpos;
	void *CurrText;};

public:
	SpreadData(GraphObj *par);
	~SpreadData();
	bool Init(int nRows, int nCols);
	bool mpos2dpos(POINT *mp, POINT *dp, bool can_scroll);
	bool Select(POINT *p);
	void MarkRange(char *range, POINT *cp = 0L);
	void HideMark(bool cclp);
	bool WriteData(char *FileName);
	bool AddCols(int nCols);
	bool AddRows(int nRows);
	bool ChangeSize(int nCols, int nRows, bool bUndo);
	bool DeleteCols();
	bool InsertCols();
	bool DeleteRows();
	bool InsertRows();
	void DoPlot(anyOutput *o);
	bool DelRange();
	bool PasteRange(int cmd, char *txt);
	bool InitCopy(int cmd, void *tmpl, anyOutput *o);
	bool SavePos();
	bool Command(int cmd, void *tmpl, anyOutput *o);
	bool ReadData(char *FileName, unsigned char *buffer, int type);

	bool ReadXML(char *file, unsigned char *buffer, int type, DWORD undo_flags = 0L);
	bool ReadTSV(char *file, unsigned char *buffer, int type);
	bool MemList(unsigned char **ptr, int type);

private:
	int CellHeight, CellWidth, FirstWidth, mrk_offs;
	RECT cp_src_rec;			//bounding rectangle for copy range
	bool bActive, new_mark, bCopyCut, bUpdate, isRowMark, isColMark;
	POINT currpos, currpos2;
	anyOutput *w;
	SpreadWin *Disp;
	GraphObj *g_parent;
	EditText *et_racc;
	char *m_range, *c_range;	//mark and copy ranges
	char *err_msg, *last_err;	//error message
	char *rlw_file;				//use this name for save
	_pos_info pos_info;			//save position settings
};

SpreadData::SpreadData(GraphObj *par)
{
	Disp = 0L;	m_range = 0L;	c_range = 0L;	w = 0L;		err_msg=last_err=rlw_file =  0L;
	g_parent = par;		CellWidth = 76;		CellHeight = 19;	FirstWidth = 32;
	et_racc = 0L;	currpos.x = currpos.y = mrk_offs = 0;
	r_disp = 60;	c_disp = 40;
	bActive = bCopyCut = bUpdate = isRowMark = isColMark = false;
	cp_src_rec.left = cp_src_rec.right = cp_src_rec.top = cp_src_rec.bottom = 0;
	if(defs.IniFile && FileExist(defs.IniFile)) {
		OpenGraph(0L, defs.IniFile, 0L, false);
		}
	pos_info.currpos.x = currpos.x;		pos_info.currpos.y = currpos.y;
	pos_info.CurrText = CurrText;
}

SpreadData::~SpreadData()
{
	FlushData();
	if(Disp) delete Disp;			Disp = 0L;
	if(m_range) free(m_range);		m_range = 0L;
	if(c_range) free(c_range);		c_range = 0L;
	if(rlw_file) free(rlw_file);	rlw_file = 0L;
}

bool
SpreadData::Init(int nRows, int nCols)
{
	int i, j;
	RECT rc;
	RangeInfo *o_ri;

	cRows = nRows;	cCols = nCols;				currpos.x = currpos.y = 0;
	new_mark = bCopyCut = false;				if(w && m_range) HideMark(true);
	bUpdate = true;
	while(ri->Type()) {
		o_ri = ri;		ri = ri->Next();		delete(o_ri);
		}
	if(!Disp) {
		Disp = new SpreadWin(g_parent, this);
		w = Disp->w;
		if(w) {
			CellWidth = w->un2ix(defs.GetSize(SIZE_CELLWIDTH));
#ifdef _WINDOWS
			CellHeight = w->un2iy(defs.GetSize(SIZE_CELLTEXT)/defs.ss_txt) + 2;
#else
			CellHeight = w->un2iy(defs.GetSize(SIZE_CELLTEXT)/(defs.ss_txt *0.7)) + 2;
#endif
			if(CellHeight < 12)CellHeight = 19;		if(CellWidth < 40)CellWidth = 76;
			FirstWidth = 32;						w->GetSize(&rc);
			r_disp = (rc.bottom-rc.top)/CellHeight;
			c_disp = (rc.right-rc.left)/CellWidth+1;
			}
		else return false;
		pos_info.ssOrg.x = Disp->ssOrg.x;		pos_info.ssOrg.y = Disp->ssOrg.y;
		pos_info.CurrText = CurrText;
		}
	if(etRows)FlushData();
	etRows = (EditText ***)calloc (cRows, sizeof(EditText **));
	if(etRows) for(i = 0; i < cRows; i++) {
		etRows[i] = (EditText **)calloc(cCols, sizeof(EditText *));
		if(etRows[i]) for(j = 0; j < cCols; j++) {
#ifdef _DEBUG
			char text[20];
#ifdef USE_WIN_SECURE
			sprintf_s (text, 20, "%.2f", i*10.0 + j);
#else
			sprintf (text, "%.2f", i*10.0 + j);
#endif
			etRows[i][j] = new EditText(this, text, i, j);
#else
			etRows[i][j] = new EditText(this, 0L, i, j);
#endif
			}
		}
	if (LoadFile) {
		rlp_strcpy(TmpTxt, TMP_TXT_SIZE, LoadFile);	//we will reenter by recursion !
		free(LoadFile);
		LoadFile = 0L;
		Disp->Command(CMD_DROPFILE, TmpTxt, w);
		}
	else DoPlot(w);
	return true;
}

bool
SpreadData::mpos2dpos(POINT *mp, POINT *dp, bool can_scroll)
{
	int mx;

	CurrGO = 0L;
	dp->y = (mp->y - w->MenuHeight - CellHeight)/CellHeight + Disp->ssOrg.y;
	dp->x = Disp->ssOrg.x;
	for(mx = mp->x - ri->GetFirstWidth() - ri->GetWidth(dp->x); mx > 0; dp->x++, mx -= ri->GetWidth(dp->x));
	if(can_scroll) {
		if(dp->x < cCols && mp->x < (ri->GetFirstWidth()+10) && mp->x > ri->GetFirstWidth() && Disp->ssOrg.x >0) {
			Disp->ssOrg.x -= 1;
			if(CurrText) CurrText->Update(2, w, mp);		CurrText = 0L;
			Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		if(mp->y < (w->MenuHeight + CellHeight+9) && mp->y > (w->MenuHeight + CellHeight) && Disp->ssOrg.y >0) {
			Disp->ssOrg.y -= 1;
			if(CurrText) CurrText->Update(2, w, mp);		CurrText = 0L;
			Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		if(mp->x > (Disp->currRC.right-w->MenuHeight-10) && Disp->ssOrg.x < (cCols-2)) {
			Disp->ssOrg.x += 1;
			if(CurrText) CurrText->Update(2, w, mp);		CurrText = 0L;
			Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		if(mp->y > (Disp->currRC.bottom-10) && Disp->ssOrg.y < (cRows-5)) {
			Disp->ssOrg.y += 1;
			do {
				mp->y -= CellHeight;
			} while(mp->y > (Disp->currRC.bottom-CellHeight));
			if(CurrText) CurrText->Update(2, w, mp);		CurrText = 0L;
			Disp->Command(CMD_SETSCROLL, 0L, w);			CurrText = 0L;
			}
		}
	if(dp->y >= cRows) dp->y = cRows-1;					if(dp->x >= cCols) dp->x = cCols-1;
	return true;
}

bool
SpreadData::Select(POINT *p)
{
	if(CurrText && CurrText->isInRect(p)){
		CurrText->Update(1, w, p);
		Disp->Command(CMD_CURRPOS, &currpos, w);
		if(CurrText->isFormula() && CurrText->hasMark()) et_racc = CurrText;
		return true;
		}
	if(!(mpos2dpos(p, &currpos, false)))return false;
	if(currpos.y < cRows && currpos.x < cCols && currpos.y >= 0 && currpos.x >= 0) {
		if(etRows[currpos.y][currpos.x]) {
			if(etRows[currpos.y][currpos.x] == CurrText) CurrText->Update(1, w, p);
			else {
				if(CurrText) CurrText->Update(2, w, p);
				CurrText = etRows[currpos.y][currpos.x];
				DoPlot(w);
				CurrText->Update(1, w, p);
				}
			Disp->Command(CMD_CURRPOS, &currpos, w);
			return true;
			}
		}
	if(CurrText) CurrText->Update(2, w, p);		CurrText = 0L;
	return false;
}

void
SpreadData::MarkRange(char *range, POINT *cp)
{
	AccRange *nr, *oldr;
	int r, c, cb;

	oldr = nr = 0L;
	if(m_range && range && !strcmp(m_range, range)) return;		//no change
	if(m_range) oldr = new AccRange(m_range);
	if(range) nr = new AccRange(range);
	if(oldr && nr && oldr->GetFirst(&c, &r)) {
		for( ; oldr->GetNext(&c, &r); ) {
			if(r >= Disp->ssOrg.y && r < (r_disp +Disp->ssOrg.y) && r < cRows && c >= Disp->ssOrg.x 
				&& c < (c_disp + Disp->ssOrg.x) && c < cCols && !nr->IsInRange(c, r) && etRows[r] && etRows[r][c]){
				etRows[r][c]->Mark(w, etRows[r][c] != CurrText ? 0 : 1);
				}
			}
		}
	if(nr && nr->GetFirst(&c, &r)) {
		for( ; nr->GetNext(&c, &r); ) {
			if(r >= Disp->ssOrg.y && r < (r_disp +Disp->ssOrg.y) && c >= Disp->ssOrg.x 
				&& c < (c_disp + Disp->ssOrg.x) && r < cRows && c < cCols)
				etRows[r][c]->Mark(w, etRows[r][c] != CurrText ? 2 : 3);
			}
		}
	if(range && (m_range = (char*)realloc(m_range, cb = ((int)strlen(range)+6)))) rlp_strcpy(m_range, cb, range);
	else if (m_range) m_range[0] = 0;
	if(oldr) delete(oldr);	if(nr) delete(nr);
	new_mark = true;		Disp->MarkButtons(m_range, cp);
}

void
SpreadData::HideMark(bool cclp)
{
	if(cclp && c_range && c_range != m_range){
		free(c_range);	c_range = 0L;
		}
	if(m_range){
		free(m_range);	m_range = 0L;
		DoPlot(w);
		}
	if(cclp) EmptyClip();		new_mark = false;
	Disp->MarkButtons(m_range, 0L);
}

bool
SpreadData::WriteData(char *FileName)
{
	FILE *File;
	int i, j;
	unsigned char *buff = 0L;
	anyResult res;
	bool bErr = false;

	if(!cRows || !cCols || !etRows) return false;
	if(!FileName) FileName = rlw_file;
	if(FileName && FileName[0]) BackupFile(FileName);
	else return false;
#ifdef USE_WIN_SECURE
	if(fopen_s(&File, FileName, "w")) {
#else
	if(!(File = fopen(FileName, "w"))) {
#endif
		ErrorBox("An error occured during write,\n\nplease try again!");
		return false;
		}
	HideMark(true);
	i = (int)strlen(FileName);
	//test for xml extension
	if(!strcmp(".xml", FileName+i-4) || !strcmp(".XML", FileName+i-4)) {
		MemList(&buff, FF_XML);
		if(buff){
			if(fprintf(File, "%s", buff) <= 0) bErr = true;
			free(buff);					fclose(File);
			return true;
			}
		return false;
		}
	//test for tsv extension
	if(!strcmp(".tsv", FileName+i-4) || !strcmp(".TSV", FileName+i-4)) {
		MemList(&buff, FF_TSV);
		if(buff){
			if(fprintf(File, "%s", buff) <= 0) bErr = true;
			free(buff);					fclose(File);
			return true;
			}
		return false;
		}
	//test for rlw extension
	if(!strcmp(".rlw", FileName+i-4) || !strcmp(".RLW", FileName+i-4)) {
		MemList(&buff, FF_RLW);
		if(buff){
			if(rlw_file != FileName) {
				if(rlw_file) free(rlw_file);
				rlw_file = (char*)memdup(FileName, (int)strlen(FileName)+1, 0);
				}
			if(fprintf(File, "%s", buff) <= 0) bErr = true;
			free(buff);					fclose(File);
			return true;
			}
		return false;
		}
	//test for slk extension
	if(!strcmp(".slk", FileName+i-4) || !strcmp(".SLK", FileName+i-4)) {
		MemList(&buff, FF_SYLK);
		if(buff){
			if(fprintf(File, "%s", buff) <= 0) bErr = true;
			free(buff);					fclose(File);
			return true;
			}
		return false;
		}
	//else write csv
	for(i = 0; i < cRows; i++) {
		for(j = 0; j < cCols; j++) {
			if(etRows[i][j]) {
				etRows[i][j]->GetResult(&res, false);		TranslateResult(&res);
				switch(res.type) {
				case ET_TEXT:
					fprintf(File, "\"%s\"", res.text);
					break;
				case ET_VALUE:		case ET_DATE:	case ET_TIME:
				case ET_DATETIME:	case ET_BOOL:
					fprintf(File, "%s", res.text);
					break;
					}
				}
			if(j < (cCols-1)) fprintf(File, ", ");
			}
		if(fprintf(File, "\n") <= 0) bErr = true;
		}
	fclose(File);
	if(bErr) ErrorBox("An error occured during write,\n\nplease try again!");
	return true;
}

bool
SpreadData::ReadData(char *FileName, unsigned char *buffer, int type)
{
	int i, j;
	char ItemText[20];
	bool success;

	if(FileName) {		//read disk file
		if(!Disp->Command(CMD_CAN_CLOSE, 0L, 0L))return false;
		if(0 == strcmp(".xml", FileName+strlen(FileName)-4) ||
			0 == strcmp(".XML", FileName+strlen(FileName)-4) ||
			0 == strcmp(".rlw", FileName+strlen(FileName)-4) ||
			0 == strcmp(".RLW", FileName+strlen(FileName)-4) ||
			IsXmlFile(FileName)){
			Disp->Command(CMD_DELGRAPH, 0L, w);
			if(ReadXML(FileName, buffer, type, 0L)){
				if(0 == strcmp(".rlw", FileName+strlen(FileName)-4) ||
					0 == strcmp(".RLW", FileName+strlen(FileName)-4)) {
					if(rlw_file) free(rlw_file);
					rlw_file = (char*)memdup(FileName, (int)strlen(FileName)+1, 0);
					}
				return true;
				}
			return false;
			}
		if(0 == strcmp(".tsv", FileName+strlen(FileName)-4) ||
			0 == strcmp(".TSV", FileName+strlen(FileName)-4)){
			return ReadTSV(FileName, buffer, type);
			}
		if(!(Cache = new ReadCache())) return false;
		if(! Cache->Open(FileName)) {
			delete Cache;
			i = rlp_strcpy(TmpTxt, TMP_TXT_SIZE, "Error open data file\n\"");
			i += rlp_strcpy(TmpTxt+i, TMP_TXT_SIZE-i, FileName);
			i += rlp_strcpy(TmpTxt+i, TMP_TXT_SIZE-i, "\"\n");
			ErrorBox(TmpTxt);
			return false;
			}
		if(!Init(1, 1)) goto ReadError;
		}
	else if(buffer) {	//read memory buffer
		switch(type) {
		case FF_TSV:
			return ReadTSV(FileName, buffer, type);
		case FF_XML:
			return ReadXML(FileName, buffer, type, 0L);
		case FF_CSV:
			if(!(Cache = new MemCache(buffer))) return false;
			break;
		default:
			ErrorBox("Read from buffer with\nunknown format failed.");
			return false;
			}
		}
	else return false;
	i = j = 0;
	do {
		if((success = GetItemCSV(ItemText, sizeof(ItemText)-1)) || ItemText[0]){
			if(j >= cCols && !AddCols(j+1)) goto ReadError;
			if(i >= cRows && !AddRows(i+1)) goto ReadError;
			if(!etRows[i][j]) goto ReadError;
			if(ItemText[0] == '"') {
				rmquot(ItemText);
				etRows[i][j]->SetText(ItemText);
				etRows[i][j]->Update(20, 0L, 0L);
				}
			else if(ItemText[0]){
				etRows[i][j]->SetText(ItemText);
				etRows[i][j]->Update(10, 0L, 0L);
				}
			else etRows[i][j]->SetText("");
			j++;
			}
		if(!success && !Cache->IsEOF()) {i++; j = 0;}	//eol
		}while (ItemText[0] || !Cache->IsEOF());		//eof
	Cache->Close();		delete Cache;		Cache = 0L;
	Disp->ssOrg.x = Disp->ssOrg.y = 0;
	bUpdate = true;
	return true;

ReadError:
	Cache->Close();		delete Cache;		Cache = 0L;
	return false;

}

bool
SpreadData::AddCols(int nCols)
{
	EditText **NewRow;
	int i, j;

	if (nCols <= cCols || !etRows || !etRows[0] || !etRows[0][cCols-1]) return false;
	for(i = 0; i < cRows; i++) {
		if(NewRow = (EditText **)realloc(etRows[i], nCols * sizeof(EditText*))){
			for(j = cCols; j < nCols; j++) {
				NewRow[j] = new EditText(this, 0L, i, j);
				}
			etRows[i] = NewRow;
			}
		else return false;						//memory allocation error
		}
	cCols = nCols;
	return true;
}

bool
SpreadData::AddRows(int nRows)
{
	int i, j;
	EditText ***NewRows;

	if (nRows <= cRows || !etRows || !etRows[cRows-1] || !etRows[cRows-1][0] ) return false;
	NewRows = (EditText ***)realloc(etRows, nRows * sizeof(EditText **));
	if(NewRows) etRows = NewRows;
	else return false;					//memory allocation error
	for(i = cRows; i < nRows; i++){
		etRows[i] = (EditText **)calloc(cCols, sizeof(EditText *));
		if(etRows[i]) {
			for(j = 0; j < cCols; j++) etRows[i][j] = new EditText(this, 0L, i, j);
			}
		else {							//memory allocation error
			cRows = i-1;
			return false;
			}
		}
	cRows = nRows;
	return true;
}

bool
SpreadData::ChangeSize(int nCols, int nRows, bool bUndo)
{
	int i, j;
	bool RetVal = true;
	
	if(nCols == cCols && nRows == cRows) return true;
	if(c_range){
		free(c_range);	c_range = 0L;	EmptyClip();
		}
	if(bUndo) Undo.DataObject(Disp, w, this, 0L, 0L);
	if(nRows && nRows < cRows) {
		for (i = nRows; i < cRows; i++) {
			if(etRows[i]) for (j = 0; j < cCols; j++) {
				if(etRows[i][j]) delete etRows[i][j];
				etRows[i][j] = NULL;
				}
			free(etRows[i]);
			etRows[i] = NULL;
			}
		cRows = nRows;
		}
	if(nCols && nCols < cCols) {
		for (i = 0; i < cRows; i++) {
			for (j = nCols; j < cCols; j++) {
				if(etRows[i][j]) delete etRows[i][j];
				etRows[i][j] = NULL;
				}
			}
		cCols = nCols;
		}
	if(nCols > cCols) if(!AddCols(nCols))RetVal = false;
	if(RetVal && nRows > cRows) if(!AddRows(nRows))RetVal = false;
	if(w) {
#ifdef USE_WIN_SECURE
		sprintf_s(TmpTxt, TMP_TXT_SIZE, "%d00", nRows);
#else
		sprintf(TmpTxt, "%d00", nRows);
#endif
		w->oGetTextExtent(TmpTxt, 0, &i, &j);
		if(i > FirstWidth) FirstWidth = i;
		Disp->Command(CMD_SETSCROLL, 0L, w);
		}
	return RetVal;
}

bool
SpreadData::DeleteCols()
{
	int i, j, r0, c0, c1, c2, cn, dc, nc;
	RECT rc;
	lfPOINT *cs;

	AccRange * ar;
	if(!isColMark || !m_range || !m_range[0]){
		InfoBox("No columns selected!");
		return false;
		}
	if(c_range){
		free(c_range);	c_range = 0L;	EmptyClip();
		}
	Undo.DataObject(Disp, w, this, 0L, 0L);
	Undo.String(0L, &m_range, UNDO_CONTINUE);
	if(!(ar = new AccRange(m_range))) return false;
	ar->BoundRec(&rc);
	if(!(cs = (lfPOINT*)malloc((rc.right-rc.left+2)*sizeof(lfPOINT)))) {
		delete ar;			return false;
		}
	if(!(ar->GetFirst(&c0, &r0))) {
		free(cs);			delete ar;			return false;
		}
	ar->NextCol(&c1);		cn = c1;			nc = 0;
	Disp->Command(CMD_MRK_DIRTY, 0L, 0L);
	do {
		for(c0 = c1 = c2 = cn; ar->NextCol(&c2); ) {
			if(c2 > c1+1 || c2 < c0) break;
			c1 = c2;
			}
		dc = c1-c0+1;		cn = c2;
		cs[nc].fx = c0;		cs[nc].fy = c1;		nc++;
		}while(c2!=c1);
	SortFpArray(nc, cs);	nc--;
	do {
		c0 = (int)cs[nc].fx;	c1 = (int)cs[nc].fy;	dc = c1-c0+1;
		for(i = 0; i < cRows; i++) {
			for(j = c0+dc; j <cCols; j++) {
				if(etRows && etRows[i] && etRows[i][j] && etRows[i][j-dc]) {
					switch(etRows[i][j]->type) {
					default:
						etRows[i][j-dc]->SetText(etRows[i][j]->text);
						break;
					case ET_VALUE:
						etRows[i][j-dc]->SetText(etRows[i][j]->text);
						etRows[i][j-dc]->Value = etRows[i][j]->Value; 
						etRows[i][j-dc]->type = ET_VALUE;
						break;
					case ET_FORMULA:
						MoveFormula(this, etRows[i][j]->text, TmpTxt, TMP_TXT_SIZE, -dc, 0, -1, c0);
						etRows[i][j-dc]->SetText(TmpTxt);	etRows[i][j]->type = ET_FORMULA;
						break;
						}
					}
				}
			}
		for(i = 0; i < cRows; i++) {
			if(etRows[i]){
				for(j = cCols-dc; j < cCols; j++) if(etRows[i][j]) {
					delete(etRows[i][j]);
					etRows[i][j] = 0L;
					}
				}
			}
		cCols -= dc;
		MoveFormula(this, m_range, TmpTxt, TMP_TXT_SIZE, -dc, 0, -1, c0+1);		free(m_range);
		m_range = (char*)memdup(TmpTxt, (int)strlen(TmpTxt)+1, 0);	nc--;
		for(i = 0; i < c0; i++) for(j = 0; j < c0; j++) {
			if(etRows && etRows[i] && etRows[i][j] && etRows[i][j]->type == ET_FORMULA) {
				MoveFormula(this, etRows[i][j]->text, TmpTxt, TMP_TXT_SIZE, -dc, 0, -1, c0);
				etRows[i][j]->SetText(TmpTxt);			etRows[i][j]->type = ET_FORMULA;
				}
			}
		}while(nc >= 0);
	delete ar;
	Disp->Command(CMD_SETSCROLL, 0L, w);		Disp->Command(CMD_MRK_DIRTY, 0L, w);
	return true;
}

bool
SpreadData::InsertCols()
{
	int i, j, r0, c0, c1, c2, cn, dc, nc;
	RECT rc;
	lfPOINT *cs;

	AccRange * ar;
	if(!isColMark || !m_range || !m_range[0]){
		InfoBox("No columns selected!");
		return false;
		}
	if(c_range){
		free(c_range);	c_range = 0L;	EmptyClip();
		}
	Undo.DataObject(Disp, w, this, 0L, 0L);
	Undo.String(0L, &m_range, UNDO_CONTINUE);
	if(!(ar = new AccRange(m_range))) return false;
	ar->BoundRec(&rc);
	if(!(cs = (lfPOINT*)malloc((rc.right-rc.left+2)*sizeof(lfPOINT)))) {
		delete ar;			return false;
		}
	if(!(ar->GetFirst(&c0, &r0))) {
		free(cs);			delete ar;			return false;
		}
	ar->NextCol(&c1);		cn = c1;			nc = 0;
	do {
		for(c0 = c1 = c2 = cn; ar->NextCol(&c2); ) {
			if(c2 > c1+1 || c2 < c0) break;
			c1 = c2;
			}
		dc = c1-c0+1;		cn = c2;
		cs[nc].fx = c0;		cs[nc].fy = c1;		nc++;
		}while(c2!=c1);
	SortFpArray(nc, cs);	nc--;	Disp->Command(CMD_MRK_DIRTY, 0L, w);
	do {
		c0 = (int)cs[nc].fx;	c1 = (int)cs[nc].fy;	dc = c1-c0+1;
		if(AddCols(cCols + dc)) for(i = 0; i < cRows; i++) {
			for(j = cCols-1; j >= c0+dc; j--) {
				if(etRows && etRows[i] && etRows[i][j] && etRows[i][j-dc]) {
					switch(etRows[i][j-dc]->type) {
					default:
						etRows[i][j]->SetText(etRows[i][j-dc]->text);
						break;
					case ET_VALUE:
						etRows[i][j]->SetText(etRows[i][j-dc]->text);
						etRows[i][j]->Value = etRows[i][j-dc]->Value; 
						etRows[i][j]->type = ET_VALUE;
						break;
					case ET_FORMULA:
						MoveFormula(this, etRows[i][j-dc]->text, TmpTxt, TMP_TXT_SIZE, dc, 0, -1, c0);
						etRows[i][j]->SetText(TmpTxt);	etRows[i][j]->type = ET_FORMULA;
						break;
						}
					etRows[i][j-dc]->SetText("");
					}
				}
			}
		MoveFormula(this, m_range, TmpTxt, TMP_TXT_SIZE, dc, 0, -1, c0);		free(m_range);
		m_range = (char*)memdup(TmpTxt, (int)strlen(TmpTxt)+1, 0);	nc--;
		for(i = 0; i < c0; i++) for(j = 0; j < c0; j++) {
			if(etRows && etRows[i] && etRows[i][j] && etRows[i][j]->type == ET_FORMULA) {
				MoveFormula(this, etRows[i][j]->text, TmpTxt, TMP_TXT_SIZE, dc, 0, -1, c0);
				etRows[i][j]->SetText(TmpTxt);			etRows[i][j]->type = ET_FORMULA;
				}
			}
		}while(nc >= 0);
	delete ar;
	Disp->Command(CMD_SETSCROLL, 0L, w);		Disp->Command(CMD_MRK_DIRTY, 0L, w);
	return true;
}

bool 
SpreadData::DeleteRows()
{
	int i, j, r0, c0, r1, r2, rn, dr, nr;
	AccRange * ar;
	RECT rc;
	lfPOINT *rs;

	if(!isRowMark || !m_range || !m_range[0]){
		InfoBox("No rows selected!");
		return false;
		}
	if(c_range){
		free(c_range);	c_range = 0L;	EmptyClip();
		}
	Undo.DataObject(Disp, w, this, 0L, 0L);
	Undo.String(0L, &m_range, UNDO_CONTINUE);
	if(!(ar = new AccRange(m_range))) return false;
	ar->BoundRec(&rc);
	if(!(rs = (lfPOINT*)malloc((rc.bottom-rc.top+2)*sizeof(lfPOINT)))) {
		delete ar;			return false;
		}
	if(!(ar->GetFirst(&c0, &r0))) {
		free(rs);			delete ar;			return false;
		}
	ar->NextRow(&r1);		rn = r1;			nr = 0;
	Disp->Command(CMD_MRK_DIRTY, 0L, w);
	do {
		for(r0 = r1 = r2 = rn;ar->NextRow(&r2); ) {
			if(r2 > r1+1 || r2 < r0) break;
			r1 = r2;
			}
		dr = r1-r0+1;		rn = r2;
		rs[nr].fx = r0;		rs[nr].fy = r1;		nr++;
		}while(r2!=r1);
	SortFpArray(nr, rs);	nr--;
	do {
		r0 = (int)rs[nr].fx;	r1 = (int)rs[nr].fy;	dr = r1-r0+1;
		for(i = r0+dr; i < cRows; i++) {
			for(j = 0; j < cCols; j++) {
				if(etRows && etRows[i-dr] && etRows[i-dr][j] && etRows[i] && etRows[i][j]) {
					switch(etRows[i][j]->type) {
					default:
						etRows[i-dr][j]->SetText(etRows[i][j]->text);
						break;
					case ET_VALUE:
						etRows[i-dr][j]->SetText(etRows[i][j]->text);
						etRows[i-dr][j]->Value = etRows[i][j]->Value; 
						etRows[i-dr][j]->type = ET_VALUE;
						break;
					case ET_FORMULA:
						MoveFormula(this, etRows[i][j]->text, TmpTxt, TMP_TXT_SIZE, 0, -dr, r0, -1);
						etRows[i-dr][j]->SetText(TmpTxt);	etRows[i-dr][j]->type = ET_FORMULA;
						break;
						}
					}
				}
			}
		for(i = cRows-1; i >= cRows-dr; i--) {
			if(etRows[i]){
				for(j = 0; j < cCols; j++) if(etRows[i][j]) delete(etRows[i][j]);
				free(etRows[i]);
				}
			}
		cRows -= dr;
		MoveFormula(this, m_range, TmpTxt, TMP_TXT_SIZE, 0, -dr, r0+1, -1);		free(m_range);
		m_range = (char*)memdup(TmpTxt, (int)strlen(TmpTxt)+1, 0);	nr--;
		for(i = 0; i < r0; i++) for(j = 0; j < cCols; j++) {
			if(etRows && etRows[i] && etRows[i][j] && etRows[i][j]->type == ET_FORMULA) {
				MoveFormula(this, etRows[i][j]->text, TmpTxt, TMP_TXT_SIZE, 0, -dr, r0, -1);
				etRows[i][j]->SetText(TmpTxt);			etRows[i][j]->type = ET_FORMULA;
				}
			}
		}while(nr >= 0);
	delete ar;
	if(w) {
		Disp->Command(CMD_SETSCROLL, 0L, w);			Disp->Command(CMD_MRK_DIRTY, 0L, w);
		}
	return true;
}

bool
SpreadData::InsertRows()
{
	int i, j, r0, c0, r1, r2, rn, dr, nr;
	AccRange * ar;
	RECT rc;
	lfPOINT *rs;

	if(!isRowMark || !m_range || !m_range[0]){
		InfoBox("No rows selected!");
		return false;
		}
	if(c_range){
		free(c_range);	c_range = 0L;	EmptyClip();
		}
	Undo.DataObject(Disp, w, this, 0L, 0L);
	Undo.String(0L, &m_range, UNDO_CONTINUE);
	if(!(ar = new AccRange(m_range))) return false;
	ar->BoundRec(&rc);
	if(!(rs = (lfPOINT*)malloc((rc.bottom-rc.top+2)*sizeof(lfPOINT)))) {
		delete ar;			return false;
		}
	if(!(ar->GetFirst(&c0, &r0))) {
		free(rs);			delete ar;			return false;
		}
	ar->NextRow(&r1);		rn = r1;			nr = 0;
	Disp->Command(CMD_MRK_DIRTY, 0L, w);
	do {
		for(r0 = r1 = r2 = rn;ar->NextRow(&r2); ) {
			if(r2 > r1+1 || r2 < r0) break;
			r1 = r2;
			}
		dr = r1-r0+1;		rn = r2;
		rs[nr].fx = r0;		rs[nr].fy = r1;		nr++;
		}while(r2!=r1);
	SortFpArray(nr, rs);	nr--;
	do {
		r0 = (int)rs[nr].fx;	r1 = (int)rs[nr].fy;	dr = r1-r0+1;
		if(AddRows(cRows + dr)) for(i = cRows-1; i >= r0+dr; i--) {
			for(j = 0; j < cCols; j++) {
				if(etRows && etRows[i-dr] && etRows[i-dr][j] && etRows[i] && etRows[i][j]) {
					switch(etRows[i-dr][j]->type) {
					default:
						etRows[i][j]->SetText(etRows[i-dr][j]->text);
						break;
					case ET_VALUE:
						etRows[i][j]->SetText(etRows[i-dr][j]->text);
						etRows[i][j]->Value = etRows[i-dr][j]->Value; 
						etRows[i][j]->type = ET_VALUE;
						break;
					case ET_FORMULA:
						MoveFormula(this, etRows[i-dr][j]->text, TmpTxt, TMP_TXT_SIZE, 0, dr, r0, -1);
						etRows[i][j]->SetText(TmpTxt);	etRows[i][j]->type = ET_FORMULA;
						break;
						}
					etRows[i-dr][j]->SetText("");
					}
				}
			}
		MoveFormula(this, m_range, TmpTxt, TMP_TXT_SIZE, 0, dr, r0, -1);		free(m_range);
		m_range = (char*)memdup(TmpTxt, (int)strlen(TmpTxt)+1, 0);	nr--;
		for(i = 0; i < r0; i++) for(j = 0; j < cCols; j++) {
			if(etRows && etRows[i] && etRows[i][j] && etRows[i][j]->type == ET_FORMULA) {
				MoveFormula(this, etRows[i][j]->text, TmpTxt, TMP_TXT_SIZE, 0, dr, r0, -1);
				etRows[i][j]->SetText(TmpTxt);			etRows[i][j]->type = ET_FORMULA;
				}
			}
		}while(nr >= 0);
	delete ar;
	if(w) {
#ifdef USE_WIN_SECURE
		sprintf_s(TmpTxt, TMP_TXT_SIZE, "%d00", cRows);
#else
		sprintf(TmpTxt, "%d00", cRows);
#endif
		w->oGetTextExtent(TmpTxt, 0, &i, &j);
		if(i > FirstWidth) FirstWidth = i;
		Disp->Command(CMD_SETSCROLL, 0L, w);			Disp->Command(CMD_MRK_DIRTY, 0L, w);
		}
	return true;
}

void
SpreadData::DoPlot(anyOutput *o)
{
	RECT rc;
	int i, j, r, c;
	AccRange *ar;
	double v;

	if(!w || !Disp) return;
	w->Erase(0x00e8e8e8L);					if(!(w->ActualSize(&rc)))return;
	w->SetTextSpec(&ssText);				et_racc = 0L;
	r_disp = (rc.bottom-rc.top)/CellHeight;
	for(c = Disp->ssOrg.x, j=rc.left, c_disp = 1; c < cCols && j <= (rc.right-rc.left) && c_disp < cCols; c++, c_disp++) {
		j += ri->GetWidth(c);
		}
	if(j >= (rc.right-rc.left))c_disp--;
	Disp->ShowGrid(CellWidth, CellHeight, FirstWidth, &currpos);
	rc.top = w->MenuHeight;					rc.bottom = rc.top + CellHeight;
	LockData(false, false);
	if(bUpdate) {
		for(i = cRows-1; i >= 0; i--) {
			for(j = cCols-1; j >= 0; j--) etRows[i][j]->GetValue(&v);
			}
		bUpdate = false;
		}
	for(i = 0; i <= (cRows - Disp->ssOrg.y) && i <= r_disp; i++) {
		rc.left = ri->GetFirstWidth();		rc.right = rc.left + ri->GetWidth(Disp->ssOrg.x);
		rc.top += CellHeight;				rc.bottom += CellHeight;
		for(j = 0; j <= (cCols - Disp->ssOrg.x) && j <= c_disp; j++) {
			r = i + Disp->ssOrg.y;	c = j + Disp->ssOrg.x; 
			if(r < cRows && r >= 0 && c < cCols && c >=0 && etRows[r][c]){
				etRows[r][c]->SetRec(&rc);	etRows[r][c]->Update(2, 0L, 0L);
				etRows[r][c]->Redraw(w, false);
				}
			rc.left += ri->GetWidth(Disp->ssOrg.x+j);
			rc.right += ri->GetWidth(Disp->ssOrg.x+j+1);
			}
		}
	if(CurrText && CurrText->row >= Disp->ssOrg.y && CurrText->row < (Disp->ssOrg.y + r_disp)
			&& CurrText->col >= Disp->ssOrg.x && CurrText->col < (Disp->ssOrg.x +c_disp))
			CurrText->Update(1, w, 0L);
	if(m_range && (ar = new AccRange(m_range)) && ar->GetFirst(&c, &r)) {
		for( ; ar->GetNext(&c, &r); ) {
			if(r >= Disp->ssOrg.y && r < (r_disp + Disp->ssOrg.y) && c >= Disp->ssOrg.x 
				&& r < cRows && c < cCols
				&& c < (c_disp + Disp->ssOrg.x) && etRows[r] && etRows[r][c])
				etRows[r][c]->Mark(w, etRows[r][c] != CurrText ? 2 : 3);
			}
		delete (ar);
		}
	LockData(false, false);
	if(c_range) InitCopy(0, 0L, w);			//move animated rectangle
	if(!(w->ActualSize(&rc))) return;		
	rc.bottom += CellHeight;		w->UpdateRect(&rc, false);
	if(err_msg && (!last_err || strcmp(err_msg,last_err))) {
		ErrorBox(last_err = err_msg);
		}
}

bool
SpreadData::DelRange()
{
	AccRange *ar;
	int r, c;
	RECT rec;

	if(m_range && (ar = new AccRange(m_range)) && ar->GetFirst(&c, &r)) {
		Disp->Command(CMD_MRK_DIRTY, 0L, w);
		ar->BoundRec(&rec);
		Undo.DataObject(Disp, w, this, &rec, 0L);
		while(ar->GetNext(&c, &r)) {
			if(r >= 0 && r < cRows && c >= 0 && c < cCols){
				if(etRows[r][c] && etRows[r][c]->text) etRows[r][c]->SetText("");
				}
			}
		delete (ar);	HideTextCursor();
		}
	HideMark(false);
	if(CurrText) CurrText->Update(1, w, 0L);
	bCopyCut = false;
	return true;
}

bool
SpreadData::PasteRange(int cmd, char *txt)
{
	AccRange *cr;
	int i, r, c;
	RECT mrk_range;

	if(new_mark && m_range && (cr = new AccRange(m_range)) && cr->GetFirst(&c, &r)) {
		cr->BoundRec(&mrk_range);	Disp->Command(CMD_MRK_DIRTY, 0L, w);
		for(i = 0 ; cr->GetNext(&c, &r); i++) if(c >= 0 && c < cCols && r >= 0 && r < cRows){
			currpos.x = c;	currpos.y = r;
			switch(cmd){
			case CMD_PASTE_TSV: ReadTSV(0L, (unsigned char*)txt, FF_TSV);		break;
			case CMD_PASTE_SSV: ReadTSV(0L, (unsigned char*)txt, FF_SSV);		break;
			case CMD_PASTE_XML: ReadXML(0L, (unsigned char*)txt, FF_XML, i ? UNDO_CONTINUE : 0L); break;
			case CMD_PASTE_CSV: ReadData(0L, (unsigned char*)txt, FF_CSV);		break;
				}
			if((mrk_range.right - mrk_range.left) == (cp_src_rec.right - cp_src_rec.left) &&
				(mrk_range.bottom - mrk_range.top) == (cp_src_rec.bottom - cp_src_rec.top)) break;
			if((mrk_range.right - mrk_range.left) == 1 && (cp_src_rec.right - cp_src_rec.left) > 1) break;
			if((mrk_range.bottom - mrk_range.top) == 1 && (cp_src_rec.bottom - cp_src_rec.top) > 1) break;
			}
		delete cr;		return true;
		}
	else switch(cmd){
		case CMD_PASTE_TSV:
			return ReadTSV(0L, (unsigned char*)txt, FF_TSV);
		case CMD_PASTE_SSV:
			return ReadTSV(0L, (unsigned char*)txt, FF_SSV);
		case CMD_PASTE_XML:
			return ReadXML(0L, (unsigned char*)txt, FF_XML, 0L);
		case CMD_PASTE_CSV:
			return ReadData(0L, (unsigned char*)txt, FF_CSV);
		}
	return bCopyCut = false;
}

bool
SpreadData::InitCopy(int cmd, void *tmpl, anyOutput *o)
{
	int r, c;
	AccRange *ar;
	RECT rc_band, rcCopy;
	bool bRet = false;

	rcCopy.left = rcCopy.top = 0;
	rcCopy.bottom = cRows-1;		rcCopy.right = cCols-1;
	if(cmd) {
		bCopyCut = (cmd == CMD_CUT);
		new_mark = false;
		if(m_range && m_range[0]) {
			if(c_range) free(c_range);
			c_range = (char*)memdup(m_range, (int)strlen(m_range)+1, 0);
			if(c_range && (ar = new AccRange(c_range))) {
				ar->BoundRec(&rcCopy);
				delete ar;			bRet = true;;
				}
			}
		else if (CurrText) {
			if(CurrText->hasMark()) return CurrText->Command(CMD_COPY, o, 0L);
			else {
				if(m_range = (char*)realloc(m_range, 40*sizeof(char)))
					rlp_strcpy(m_range, 40, mkRangeRef(currpos.y, currpos.x, currpos.y, currpos.x));
				DoPlot(o);
				return InitCopy(cmd, tmpl, o);
				}
			}
		}
	if(bRet || !cmd) {		//calculate animated mark
		if(c_range && (ar = new AccRange(c_range))) {
			ar->BoundRec(&rcCopy);			delete ar;
			}
		if(rcCopy.right < Disp->ssOrg.x || rcCopy.bottom < Disp->ssOrg.y) {
			HideCopyMark();			return bRet;
			}
		c = rcCopy.left >= Disp->ssOrg.x ? rcCopy.left : Disp->ssOrg.x;
		r = rcCopy.top >= Disp->ssOrg.y ? rcCopy.top : Disp->ssOrg.y;
		while(!etRows[r] && r) r--;			while(!etRows[r][c] && c) c--;
		if(etRows[r][c]){
			rc_band.left = etRows[r][c]->GetX()-1;	rc_band.top = etRows[r][c]->GetY()-1;
			}
		else return bRet;
		c = rcCopy.right < (Disp->ssOrg.x + c_disp) ? rcCopy.right : Disp->ssOrg.x + c_disp;
		r = rcCopy.bottom < (Disp->ssOrg.y + r_disp)? rcCopy.bottom : Disp->ssOrg.y + r_disp;
		if(r >= cRows) r = cRows-1;			if(c >= cCols) c = cRows -1;
		if(etRows[r][c]){
			rc_band.right = etRows[r][c]->GetX()+ri->GetWidth(c);
			rc_band.bottom = etRows[r][c]->GetY()+CellHeight;
			if(rc_band.left >= rc_band.right) rc_band.right = rc_band.left + CellWidth;
			if(rc_band.top >= rc_band.bottom) rc_band.bottom = rc_band.top + CellHeight;
			ShowCopyMark(o, &rc_band, 1);
			}
		}
	return bRet;
}

bool
SpreadData::SavePos()
{
	bool bRet = false;

	if(pos_info.currpos.x != currpos.x || pos_info.currpos.y != pos_info.currpos.y
		|| CurrText != pos_info.CurrText) {
		Undo.Point(Disp, &currpos, w, UNDO_CONTINUE);
		Undo.VoidPtr(Disp, (void**)&CurrText, 0L, 0L, UNDO_CONTINUE);
		}
	if(pos_info.ssOrg.x != Disp->ssOrg.x || pos_info.ssOrg.y != Disp->ssOrg.y) {
		Swap(pos_info.ssOrg.x, Disp->ssOrg.x );		Swap(pos_info.ssOrg.y, Disp->ssOrg.y );
		Undo.Point(Disp, &Disp->ssOrg, w, UNDO_CONTINUE);
		Swap(pos_info.ssOrg.x, Disp->ssOrg.x );		Swap(pos_info.ssOrg.y, Disp->ssOrg.y );
		}
	pos_info.currpos.x = currpos.x;		pos_info.currpos.y = currpos.y;
	pos_info.ssOrg.x = Disp->ssOrg.x;		pos_info.ssOrg.y = Disp->ssOrg.y;
	pos_info.CurrText = CurrText;
	return bRet;
}

bool
SpreadData::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	static int move_cr = CMD_CURRDOWN;
	MouseEvent *mev;
	POINT p, cp;
	RECT rc;
	
	if(!o) o = w;
	switch(cmd) {
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		p.x = mev->x;				p.y = mev->y;
		if((mev->StateFlags & 1) || mev->Action == MOUSE_LBUP || p.y < (w->MenuHeight+CellHeight)) {
			if(!(mpos2dpos(&p, &cp, (mev->StateFlags & 1) == 1))) return false;
			if(cp.y >= cRows || cp.x >= cCols || cp.y < 0 || cp.x < 0) return false;
			}
		else cp.x = cp.y = 0;
		switch (mev->Action) {
		case MOUSE_LBDOWN:
			if(m_range && (mev->StateFlags & 0x18)) mrk_offs = (int)strlen(m_range);
			else mrk_offs = 0;
			bActive = true;		new_mark = false;
			if(!et_racc && CurrText && !CurrText->isInRect(&p)){
				CurrText->Update(2, w, &p);			 CurrText = 0L;	
				DoPlot(w);
				}
			if(p.x < FirstWidth) cp.x = 0;
			if(p.y < (w->MenuHeight+CellHeight)) cp.y = 0;
			currpos.y = currpos2.y = cp.y;
			currpos.x = currpos2.x = cp.x;
		case MOUSE_MOVE:
			if(p.y < (w->MenuHeight+CellHeight)) {
				if(Disp->Command(CMD_COL_MOUSE, tmpl, w)) return true;
				}
			else w->MouseCursor(MC_ARROW, false);
		case MOUSE_LBDOUBLECLICK:
			if(!bActive) return false;
			if(!m_range && !CurrText && cp.y < cRows && cp.x < cCols && cp.y >= 0 && cp.x >= 0)
				CurrText = etRows[cp.y][cp.x];
			if(mev->Action == MOUSE_MOVE && (mev->StateFlags & 1) 
				&& !(CurrText && CurrText->isInRect(&p))) {
				//mark rectangular range
				if(!et_racc && !m_range && CurrText) {
					CurrText->Update(2, w, &p);		 CurrText = 0L;	
					}
				if(mrk_offs && m_range && m_range[0]){
					mrk_offs = rlp_strcpy(TmpTxt, mrk_offs+1, m_range);
					}
				else mrk_offs = 0;
				if(p.x < FirstWidth || p.y < (w->MenuHeight+CellHeight)) {
					if(mrk_offs && TmpTxt[mrk_offs-1] != ';')TmpTxt[mrk_offs++] = ';';
					rlp_strcpy(TmpTxt+mrk_offs, TMP_TXT_SIZE - mrk_offs, mkRangeRef(
						p.y < (w->MenuHeight+CellHeight) ? 0 : currpos.y, p.x < FirstWidth ? 0 : currpos.x,
						p.y < (w->MenuHeight+CellHeight) ? cRows : cp.y, p.x < FirstWidth ? cCols-1 : cp.x));
					}
				else {
					if(mrk_offs && TmpTxt[mrk_offs-1] != ';')TmpTxt[mrk_offs++] = ';';
					rlp_strcpy(TmpTxt+mrk_offs, TMP_TXT_SIZE - mrk_offs, mkRangeRef(currpos.y, currpos.x, cp.y, cp.x));
					}
				if(!CurrText || et_racc)MarkRange(TmpTxt, &cp);
				return true;
				}
			if(mev->Action == MOUSE_LBDOUBLECLICK) bActive = false;
			if(!(mev->StateFlags & 1)) return false;
			if(CurrText && CurrText->isInRect(&p)) {
				return CurrText->Command(CMD_MOUSE_EVENT, o, (DataObj *)mev);
				}
			if(etRows[cp.y][cp.x]) 
				return etRows[cp.y][cp.x]->Command(CMD_MOUSE_EVENT, o, (DataObj *)mev);
			return false;
		case MOUSE_LBUP:
			if(bActive){
				if(p.y < (w->MenuHeight+CellHeight)) {
					if(Disp->Command(CMD_COL_MOUSE, tmpl, w)) return true;
					}
				isRowMark = p.x < FirstWidth;
				isColMark = p.y < (w->MenuHeight+CellHeight);
				if(isRowMark || isColMark) {
					if(p.x < FirstWidth) {
						currpos.x = 0;	cp.x = cCols-1;
						}
					if(p.y < (w->MenuHeight+CellHeight)) {
						currpos.y = 0;	cp.y = cRows-1;
						}
					if(mrk_offs && m_range && m_range[0]){
						mrk_offs = rlp_strcpy(TmpTxt, mrk_offs+1, m_range);
						}
					else mrk_offs = 0;
					if(mrk_offs && TmpTxt[mrk_offs-1] != ';')TmpTxt[mrk_offs++] = ';';
					rlp_strcpy(TmpTxt+mrk_offs, TMP_TXT_SIZE - mrk_offs, mkRangeRef(currpos.y, currpos.x, cp.y, cp.x));
					MarkRange(TmpTxt);			currpos2.x = cp.x;		currpos2.y = cp.y + 1;
					}
				else if((m_range) && !new_mark) HideMark(false);
				if(et_racc && !et_racc->isInRect(&p)) {
					CurrText = et_racc;
					if(m_range && m_range[0] && (currpos.x != cp.x || currpos.y != cp.y)) {
						CurrText->Command(CMD_ADDTXT, o, (DataObj*) m_range);
						}
					else {
						m_range = (char*)realloc(m_range, 40*sizeof(char));
						rlp_strcpy(m_range, 40, mkCellRef(currpos.y, currpos.x));
						CurrText->Command(CMD_ADDTXT, o, (DataObj*) m_range);
						}
					}
				else if(m_range) {
					CurrText = etRows[currpos.y][currpos.x];	CurrText->Update(1, w, &p);
					DoPlot(w);
					currpos2.x = cp.x;		currpos2.y = cp.y;
					}
				else return Select(&p);
				}
			}
		break;
	case CMD_PASTE_TSV:		case CMD_PASTE_XML:	case CMD_PASTE_CSV:	case CMD_PASTE_SSV:
		bUpdate = true;		Disp->Command(CMD_MRK_DIRTY, 0L, 0L);
		if(PasteRange(cmd, (char*)tmpl)) 
			return Disp->Command(CMD_SETSCROLL, 0L, w);
		return false;
	case CMD_HIDEMARK:
		if(c_range){
			free(c_range);	c_range = 0L;	return true;
			}
		return false;
	case CMD_GETFILENAME:
		if(!tmpl) return false;
		if(rlw_file && rlw_file[0]) {
			if(rlp_strcpy((char*)tmpl, TMP_TXT_SIZE, rlw_file))return true;
			else return false;
			}
		return false;
	case CMD_ETRACC:
		if(et_racc && tmpl && ((EditText*)tmpl)->parent != this) HideMark(false);
		et_racc = (EditText*) tmpl;
		if(CurrText && CurrText->parent != this) CurrText = 0L;
		return true;
	case CMD_CURRPOS:
		if(tmpl) {
			if(((POINT*)tmpl)->x < 0 && ((POINT*)tmpl)->y < 0) {
				((POINT*)tmpl)->x = currpos.x;		((POINT*)tmpl)->y = currpos.y;
				return true;
				}
			}
		return false;
	case CMD_UNLOCK:
		HideMark(false);
		currpos2.x = currpos.x;		currpos2.y = currpos.y;
		break;
	case CMD_ERROR:
		if(err_msg && err_msg[0] && tmpl && *((char*)tmpl)) {
			if(!(strcmp((char*)tmpl, "parse error")))return false;	//parse error has lowest priority
			}
		if(!tmpl && err_msg && err_msg[0] && strcmp(err_msg, "parse error")) return false;
		err_msg = (char*)tmpl;
		break;
	case CMD_UPDATE:
		bUpdate = true;
		break;
	case CMD_SAVEPOS:
		return SavePos();
	case CMD_CLEAR_ERROR:
		err_msg = last_err = 0L;
		break;
	case CMD_TOOLMODE:						//ESC pressed
		HideMark(true);
		if(CurrText){
			CurrText->Update(2, w, 0L);		CurrText->Update(1, w, 0L);
			}
		et_racc = 0L;						w->MouseCursor(MC_ARROW, true);
		break;
	case CMD_UPDHISTORY:
		if(w) w->FileHistory();
		break;
	case CMD_MRK_DIRTY:
		move_cr = CMD_CURRDOWN;
		err_msg = last_err = 0L;
		if(Disp) return Disp->Command(cmd, tmpl, o);
		return false;
	case CMD_ADDCHAR:
		if(CurrText){
			if(currpos.y < Disp->ssOrg.y) {
				Disp->ssOrg.y = currpos.y;					Disp->Command(CMD_SETSCROLL, 0L, w);
				}
			else if(currpos.y > (Disp->ssOrg.y + r_disp -3)) {
				Disp->ssOrg.y = currpos.y - (r_disp-3);
				if(Disp->ssOrg.y < 0) Disp->ssOrg.y = 0;	Disp->Command(CMD_SETSCROLL, 0L, w);
				}
			if(currpos.x < Disp->ssOrg.x) {
				Disp->ssOrg.x = currpos.x;					Disp->Command(CMD_SETSCROLL, 0L, w);
				}
			else if(currpos.x > (Disp->ssOrg.x + c_disp -3) && (CurrText->GetRX()+ 10) > Disp->currRC.right ) {
				Disp->ssOrg.x = currpos.x - (c_disp-3);
				if(Disp->ssOrg.x < 0) Disp->ssOrg.x = 0;	Disp->Command(CMD_SETSCROLL, 0L, w);
				}
			currpos2.x = currpos.x;		currpos2.y = currpos.y;
			i = *((int*)tmpl);
			Disp->Command(CMD_CURRPOS, &currpos, w);
			if(i == 27) return Command(CMD_TOOLMODE, tmpl, o);
			if(i !=3 && i != 22 && i != 24 && i != 26) HideMark(true);	//Do not hide upon ^C, ^V, ^X, ^Z
			if(i == 13) return CurrText->Command(move_cr, w, this);
			switch(i) {
			case 8: return CurrText->Command(CMD_BACKSP, w, this);	//Backspace
			default: return CurrText->AddChar(*((int*)tmpl), w, Disp);
				}
			}
		else {
			currpos.x = currpos2.x = Disp->ssOrg.x;			currpos.y = currpos2.y = Disp->ssOrg.y;
			if(etRows[currpos.x] && (CurrText = etRows[currpos.x][currpos.y]))
				CurrText->Update(1, w, &p);;
			}
		Disp->Command(CMD_CURRPOS, &currpos, w);
		break;
	case CMD_SHIFTUP:
		if(Disp->ssOrg.y && currpos2.y <= Disp->ssOrg.y) {
			Disp->ssOrg.y --;						Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		currpos2.y -= 2;
	case CMD_SHIFTDOWN:
		if(cmd == CMD_SHIFTDOWN && r_disp > 3 && (currpos2.y-Disp->ssOrg.y) >= (r_disp-3)) {
			Disp->ssOrg.y ++;						Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		currpos2.y ++;
		if(currpos2.y >= cRows) currpos2.y --;		if(currpos2.y < 0) currpos2.y ++;
		//mark rectangular range
		rlp_strcpy(TmpTxt, 40, mkRangeRef(currpos.y, currpos.x, currpos2.y, currpos2.x));
		MarkRange(TmpTxt);							HideTextCursor();
		break;
	case CMD_SHIFTRIGHT:	case CMD_SHIFTLEFT:
		if(!m_range && CurrText && CurrText->Command(cmd, w, this)) break;
		if(cmd == CMD_SHIFTLEFT && c_disp > 3 && Disp->ssOrg.x && (currpos2.x-Disp->ssOrg.x) < 1) {
			Disp->ssOrg.x --;						Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		if(cmd == CMD_SHIFTRIGHT && c_disp > 3 && (currpos2.x-Disp->ssOrg.x) >= (c_disp-3)) {
			Disp->ssOrg.x ++;						Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		if(cmd == CMD_SHIFTLEFT) currpos2.x --;
		else currpos2.x ++;
		if(currpos2.x >= cCols) currpos2.x --;		if(currpos2.x < 0) currpos2.x ++;
		//mark rectangular range
		rlp_strcpy(TmpTxt, 40, mkRangeRef(currpos.y, currpos.x, currpos2.y, currpos2.x));
		MarkRange(TmpTxt);							HideTextCursor();
		break;
	case CMD_SHPGUP:
		if(Disp->ssOrg.y >0) {
			Disp->ssOrg.y -= (r_disp-2);
			if(Disp->ssOrg.y < 0) Disp->ssOrg.y = 0;
			Disp->Command(CMD_SETSCROLL, 0L, w);
			currpos2.y -= r_disp;					if(currpos2.y < 0) currpos2.y = 0;
			rlp_strcpy(TmpTxt, 40, mkRangeRef(currpos.y, currpos.x, currpos2.y, currpos2.x));
			MarkRange(TmpTxt);							HideTextCursor();
			}
		break;
	case CMD_SHPGDOWN:
		i = (Disp->ssOrg.y + 2*r_disp) < cRows ? r_disp-2 : cRows-r_disp - Disp->ssOrg.y+3;
		if(i > 0) {
			Disp->ssOrg.y += i;						Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		if(currpos2.y < (cRows-1)) {
			currpos2.y += r_disp;					if(currpos2.y >= cRows) currpos2.y = cRows-1;
			//mark rectangular range
			rlp_strcpy(TmpTxt, 40, mkRangeRef(currpos.y, currpos.x, currpos2.y, currpos2.x));
			MarkRange(TmpTxt);							HideTextCursor();
			}
		break;
	case CMD_CURRIGHT:		case CMD_CURRDOWN:
		move_cr = cmd;		w->ActualSize(&rc);
	case CMD_CURRLEFT:		case CMD_CURRUP:	case CMD_POS_FIRST:		case CMD_POS_LAST:
		if(cmd == CMD_CURRUP && Disp->ssOrg.y && CurrText && currpos.y == Disp->ssOrg.y) {
			Disp->ssOrg.y --;		currpos.y --;
			DoPlot(o);
			}
		if(cmd == CMD_CURRLEFT && Disp->ssOrg.x && CurrText && (!CurrText->Cursor()) && (currpos.x == Disp->ssOrg.x)) {
			Disp->ssOrg.x --;		currpos.x --;	
			CurrText->Update(2, o, 0L);
			Disp->Command(CMD_CURRPOS, &currpos, w);
			if(etRows && etRows[currpos.y] && etRows[currpos.y][currpos.x]) 
				CurrText = etRows[currpos.y][currpos.x];
			if(CurrText){
				CurrText->Command(CMD_POS_LAST, o, this);		CurrText->Update(1, o, 0L);
				}
			DoPlot(o);		return false;
			}
		if(CurrText && cmd == CMD_CURRIGHT && c_disp > 3 && ((currpos.x-Disp->ssOrg.x) >= (c_disp-1) || CurrText->GetRX() >= (rc.right-rc.left-10))) {
			Disp->ssOrg.x ++;		currpos.y ++;			DoPlot(o);
			CurrText->Command(CMD_POS_FIRST, o, this);
			}
		if(cmd == CMD_CURRDOWN && r_disp > 3 && (currpos.y-Disp->ssOrg.y) >= (r_disp-3)) {
			Disp->ssOrg.y ++;		currpos.y ++;			DoPlot(o);
			}
		Disp->Command(CMD_CURRPOS, &currpos, w);
		HideMark(false);
		currpos2.x = currpos.x;								currpos2.y = currpos.y;
		if(CurrText)return CurrText->Command(cmd, w, this);
		else {
			currpos.x = currpos2.x = Disp->ssOrg.x;			currpos.y = currpos2.y = Disp->ssOrg.y;
			if(etRows[currpos.y] && (CurrText = etRows[currpos.y][currpos.x]))
				CurrText->Update(1, w, &p);;
			}
		return false;
	case CMD_SHTAB:
		if(currpos.y >= cRows) currpos.y = cRows-1;
		if(currpos.x == Disp->ssOrg.x && Disp->ssOrg.x > 0) {
			Disp->ssOrg.x -= 1;
			Disp->Command(CMD_SETSCROLL, 0L, w);
			}
		if(currpos.x > Disp->ssOrg.x && etRows[currpos.y][currpos.x]) {
			currpos.x -=1;
			}
	case CMD_TAB:
		if(currpos.y >= cRows) currpos.y = cRows-1;
		if(cmd == CMD_TAB && currpos.x < (cCols-1) && etRows[currpos.y][currpos.x]) {
			if((FirstWidth+(currpos.x - Disp->ssOrg.x +2)*CellWidth) > Disp->currRC.right){
				Disp->ssOrg.x += 1;
				Disp->Command(CMD_SETSCROLL, 0L, w);
				}
			currpos.x +=1;
			}
		if(CurrText) CurrText->Update(2, w, &p);
		CurrText = etRows[currpos.y][currpos.x];
		if(CurrText){
			CurrText->Update(1, w, &p);
			CurrText->Command(cmd == CMD_TAB ? CMD_POS_FIRST : CMD_POS_LAST, o, this);
			}
		Disp->Command(CMD_CURRPOS, &currpos, w);
		return true;
	case CMD_UNDO:
		if(w) {
			w->MouseCursor(MC_WAIT, true);
			Undo.Restore(true, w);
			w->MouseCursor(MC_ARROW, true);
			if(et_racc) {
				CurrText = et_racc;
				CurrText->Update(1, w, 0L);
				}
			}
		
		return true;
	case CMD_DELETE:
		if(m_range) DelRange();
		else if(CurrText) return CurrText->Command(cmd, o, this);
		Disp->Command(CMD_CURRPOS, &currpos, w);
		return true;
	case CMD_QUERY_COPY:		case CMD_CUT:
		et_racc = 0L;
		return InitCopy(cmd, tmpl, w);
	case CMD_GET_CELLDIMS:
		if(tmpl) {
			((int*)tmpl)[0] = FirstWidth;	((int*)tmpl)[1] = CellWidth;
			((int*)tmpl)[2] = CellHeight;
			}
		break;
	case CMD_SET_CELLDIMS:
		if(tmpl && (FirstWidth != ((int*)tmpl)[0] || CellWidth != ((int*)tmpl)[1] || CellHeight != ((int*)tmpl)[2])) {
			Undo.ValInt(Disp, &FirstWidth, UNDO_CONTINUE);	Undo.ValInt(Disp, &CellWidth, UNDO_CONTINUE);
			Undo.ValInt(Disp, &CellHeight, UNDO_CONTINUE);	FirstWidth = ((int*)tmpl)[0];
			CellWidth = ((int*)tmpl)[1];					CellHeight = ((int*)tmpl)[2];
			}
		break;
	case CMD_TEXTSIZE:
		if(tmpl) return Disp->Command(cmd, tmpl, o);
		return false;
	case CMD_COPY_TSV:
		return MemList((unsigned char**)tmpl, FF_TSV);
	case CMD_COPY_SYLK:
		return MemList((unsigned char**)tmpl, FF_SYLK);
	case CMD_COPY_XML:
		return MemList((unsigned char**)tmpl, FF_XML);
	case CMD_MENUHEIGHT:
	case CMD_DOPLOT:	case CMD_REDRAW:
		if(CurrText) CurrText->Update(2, 0L, 0L);
		if(etRows && etRows[currpos.y] && currpos.y < cRows && currpos.x < cCols) 
			if(CurrText = etRows[currpos.y][currpos.x]) CurrText->Update(1,0L, 0L);
		DoPlot(o);
		break;
	case CMD_FILLRANGE:
		Undo.SetDisp(w);
		if(FillSsRange(this, &m_range, Disp)) Disp->Command(CMD_MRK_DIRTY, 0L, o);
		DoPlot(o);
		Undo.SetDisp(w);
		break;
	case CMD_GETMARK:
		if(tmpl && m_range && m_range[0]) {
			*((char**)tmpl) = m_range;
			return true;
			}
		return false;
	case CMD_INSROW:
		return InsertRows();
	case CMD_INSCOL:
		return InsertCols();
	case CMD_DELROW:
		return DeleteRows();
	case CMD_DELCOL:
		return DeleteCols();
	}
	return true;
}

bool
SpreadData::ReadXML(char *file, unsigned char *buffer, int type, DWORD undo_flags)
{
	int i, row, col, tag, cpgr, spgr, ufl = 0;
	bool bContinue, bRet = false, bUndo_done = false;
	ReadCache *XMLcache;
	POINT pt, mov;
	char TmpTxt[1024], *tmp_range;
	unsigned char *pgr = 0L;
	RECT rc_undo;

	if(file) {
		if(!(XMLcache = new ReadCache())) return false;
		if(! XMLcache->Open(file)) {
			delete XMLcache;
			i = rlp_strcpy(TmpTxt, 40, "Error open file\n\"");		i = rlp_strcpy(TmpTxt+i, 200, file);
			rlp_strcpy(TmpTxt+i, 2, "\"");							ErrorBox(TmpTxt);
			return false;
			}
		bUndo_done = true;
		if(!Init(1, 1)) goto XMLError;
		etRows[0][0]->SetText("");
		}
	else if(buffer && type == FF_XML){
		if(!(XMLcache = new MemCache(buffer))) return false;
		if(buffer && bCopyCut) {
			tmp_range = m_range;	m_range = c_range;
			c_range = 0L;			DelRange();
			m_range = tmp_range;
			}
		}
	else return false;
	pt.x = pt.y = mov.x = mov.y = 0;
	cp_src_rec.left = cp_src_rec.right = cp_src_rec.top = cp_src_rec.bottom = 0;
	do {
		row = col = 0;
		do {
			TmpTxt[0] = XMLcache->Getc();
			}while(TmpTxt[0] && TmpTxt[0] != '<');
		for(i = 1; i < 5; TmpTxt[i++] = XMLcache->Getc());
		TmpTxt[i] = 0;
		if(!strcmp("<cell", TmpTxt)){
			if(!bUndo_done) {
				rc_undo.left = currpos.x;
				rc_undo.right = cp_src_rec.right - cp_src_rec.left + currpos.x;
				rc_undo.top = currpos.y;
				rc_undo.bottom = cp_src_rec.bottom - cp_src_rec.top + currpos.y;
				if(ufl == 3) Undo.DataObject(Disp, w, this, &rc_undo, undo_flags);
				bUndo_done = true;
				}
 			tag = 1;
			}
		else if(!strcmp("<pos1", TmpTxt)) tag = 2;
		else if(!strcmp("<pos2", TmpTxt)) tag = 3;
		else if(!strcmp("<RLPl", TmpTxt)) {
			do {
				TmpTxt[i++] = XMLcache->Getc();
				}while(TmpTxt[i-1] > 31 && TmpTxt[i-1] != '>');
			TmpTxt[i] = 0;
			row = col = 0;
			if(TmpTxt[17] == '=' && TmpTxt[13] == 'r') {
#ifdef USE_WIN_SECURE
				sscanf_s(TmpTxt+19, "%d", &row);
#else
				sscanf(TmpTxt+19, "%d", &row);
#endif
				}
			else break;
			for(i = 20; TmpTxt[i] && TmpTxt[i] != '='; i++);
#ifdef USE_WIN_SECURE
			sscanf_s(TmpTxt+i+2, "%d", &col);
#else
			sscanf(TmpTxt+i+2, "%d", &col);
#endif
			if(row && col) {
				AddCols(col);		AddRows(row);
				}
			row = col = -1;
			}
		else if(!strcmp("<Grap", TmpTxt)){
			while(XMLcache->Getc() != '[');
			pgr = (unsigned char*)malloc(spgr = 1000);
			pgr[0] = '[';	cpgr = 1;
			do {
				pgr[cpgr++] = XMLcache->Getc();
				if(cpgr >= spgr) pgr = (unsigned char*)realloc(pgr, spgr +=1000);
				}while(!(pgr[cpgr-1] == '<' && pgr[cpgr-2] == 0x0a));
			pgr[cpgr-2] = 0;
			while(XMLcache->Getc() != 0x0a);
			if(cpgr >20)OpenGraph(Disp, 0L, pgr, false);
			free(pgr);			tag = 0;
			}
		else tag = 0;
		if(tag) {
			do {
				TmpTxt[0] = XMLcache->Getc();
				}while(TmpTxt[0] && TmpTxt[0] != '"');
			if (TmpTxt[0]) for (i =0; i <10 && ('"' != (TmpTxt[i] =XMLcache->Getc())); i++){
				TmpTxt[i+1] = 0;
				row = (int)atoi(TmpTxt);
				}
			do {
				TmpTxt[0] = XMLcache->Getc();
				}while(TmpTxt[0] && TmpTxt[0] != '"');
			if (TmpTxt[0]) for (i =0; i <10 && ('"' != (TmpTxt[i] =XMLcache->Getc())); i++){
				TmpTxt[i+1] = 0;
				col = (int)atoi(TmpTxt);
				}
			if(tag ==2) {
				mov.x = col;				mov.y = row;
				cp_src_rec.left = col;		cp_src_rec.top = row;
				ufl |= 1;
				}
			else if(tag ==3) {
				cp_src_rec.right = col;		cp_src_rec.bottom = row;
				ufl |= 2;
				}
			else if(row && col) do {
				do {
					TmpTxt[0] = XMLcache->Getc();
					}while(TmpTxt[0] && TmpTxt[0] != '<');
				for(i = 1; i < 6; TmpTxt[i++] = XMLcache->Getc());
				TmpTxt[i] = 0;
				if(bContinue =(0 == strcmp("<text>", TmpTxt))) {
					for(i = 0; i < 1023; i++) {
						if(!(TmpTxt[i] =XMLcache->Getc())) break;
						if(i >1 && TmpTxt[i-2] == '<' && TmpTxt[i-1] == '/' && TmpTxt[i] == 't') break;
						}
					i -=2;		TmpTxt[i] = 0;
					//xml indices start at 1:1 !
					row += currpos.y-1;	col += currpos.x-1;
					if(row >= cRows)AddRows(row+1);
					if(col >= cCols)AddCols(col+1);
					if(i && etRows[row] && etRows[row][col]) {
						if(TmpTxt[0] == '=' && (currpos.x || currpos.y || mov.x || mov.y)) {
							MoveFormula(this, TmpTxt, TmpTxt, TMP_TXT_SIZE, currpos.x-mov.x, currpos.y-mov.y, -1, -1);
							}
						etRows[row][col]->SetText(TmpTxt);
						etRows[row][col]->Update(20, 0L, &pt);
						}
					}
				}while(!bContinue);
			}
		}while(!XMLcache->IsEOF());
	bRet = true;
XMLError:
	XMLcache->Close();
	delete XMLcache;
	bCopyCut = false;
	return bRet;
}

bool
SpreadData::ReadTSV(char *file, unsigned char *buffer, int type)
{
	int i, row, col;
	char c;
	bool bRet = false;
	ReadCache *TSVcache;
	POINT pt;

	if(file) {
		if(!(TSVcache = new ReadCache())) return false;
		if(! TSVcache->Open(file)) {
			delete TSVcache;
			i = rlp_strcpy(TmpTxt, 200, "Error open file\n\"");
			i += rlp_strcpy(TmpTxt+i, 200-i, file);
			i += rlp_strcpy(TmpTxt+i, 200-i, "\"\n");
			ErrorBox(TmpTxt);
			return false;
			}
		if(!Init(1, 1)) goto TSVError;
		etRows[0][0]->SetText("");
		}
	else if(buffer && (type == FF_TSV || type == FF_SSV)) {
		if(!(TSVcache = new MemCache(buffer))) return false;
		}
	else return false;
	row = currpos.y;	col = currpos.x;
	pt.x = pt.y = 0;
	do {
		do {
			TmpTxt[0] = TSVcache->Getc();
			switch(TmpTxt[0]) {
			case 0x0d:					//CR
			case 0x0a:					//LF
				if(col == currpos.x) break;
				row ++;			col = currpos.x;		break;
			case 0x09:					//tab
				col ++;			break;
			case ' ':
				if(type == FF_SSV) col ++;				break;
				}
			}while(TmpTxt[0] && ((unsigned char)TmpTxt[0] < 33));
		for(i = 1; ((unsigned char)(TmpTxt[i] = c = TSVcache->Getc()))>= (type == FF_SSV ? 33 : 32); i++)
		if(i >= 4094) i = 4094;
		if(TmpTxt[0] && row >= cRows)AddRows(row+1);
		if(TmpTxt[0] && col >= cCols)AddCols(col+1);
		TmpTxt[i] = 0;
		if(TmpTxt[0] && etRows[row] && etRows[row][col]) {
			etRows[row][col]->SetText(TmpTxt);
			etRows[row][col]->Update(20, 0L, &pt);
			switch(c) {
			case 0x0d:					//CR
			case 0x0a:					//LF
				row ++;			col = currpos.x;		break;
			case 0x09:					//tab
				col ++;			break;
			case ' ':
				if(type == FF_SSV) col ++;				break;
				}
			}
		}while(!TSVcache->IsEOF());
	bRet = true;
TSVError:
	TSVcache->Close();
	delete TSVcache;
	return bRet;
}

static void sylk_cell_ref(char** ptr, int *cbd, int *size, char *first, char* trail, int row, int col)
{
	if(first && first[0]) add_to_buff(ptr, cbd, size, first, 0);
	add_to_buff(ptr, cbd, size, "Y", 1);	add_int_to_buff(ptr, cbd, size, row+1, false, 0);
	add_to_buff(ptr, cbd, size, ";X", 2);	add_int_to_buff(ptr, cbd, size, col+1, false, 0);
	if(trail && trail[0]) add_to_buff(ptr, cbd, size, trail, 0);
}

bool 
SpreadData::MemList(unsigned char **ptr, int type)
{
	int i, j, nc, nl; 
	int cbd = 0, size;
	bool bLimit = true;
	AccRange *ar;
	anyResult res;
	RECT rcCopy;

	if(!(*ptr = (unsigned char *)malloc(size = 10000)))return false;
	if (c_range &&  c_range[0]) {
		ar = new AccRange(c_range);			ar->BoundRec(&rcCopy);
		if(bCopyCut) Undo.DataObject(Disp, w, this, &rcCopy, 0L);
		}
	else {
		ar = 0L;	rcCopy.left = rcCopy.top = 0;
		rcCopy.right = cCols-1;				rcCopy.bottom = cRows-1;
		}
	if(rcCopy.left < 0) rcCopy.left = 0;	if(rcCopy.right >= cCols) rcCopy.right = cCols-1;
	if(rcCopy.top < 0) rcCopy.top = 0;		if(rcCopy.bottom >= cRows) rcCopy.bottom = cRows-1;
	if(type == FF_SYLK) cbd = rlp_strcpy((char*)*ptr, size, "ID;PWXL;N;E\n"
		"P;Pdd/mm/yyyy\nP;Phh:mm:ss\nP;Pdd/mm/yyyy hh:mm:ss\n");
	else if(type == FF_XML) {
		cbd = rlp_strcpy((char*)*ptr, 100, "<?xml version=\"1.0\"?><!DOCTYPE spreadsheet-snippet>"
			"<spreadsheet-snippet rows=\"");
		add_int_to_buff((char**)ptr, &cbd, &size, cRows, false, 0);
		add_to_buff((char**)ptr, &cbd, &size, "\" columns=\"", 11);
		add_int_to_buff((char**)ptr, &cbd, &size, cCols, false, 0);
		add_to_buff((char**)ptr, &cbd, &size, "\">\n", 3);
		if(rcCopy.left || rcCopy.top || rcCopy.bottom || rcCopy.right){
			add_to_buff((char**)ptr, &cbd, &size, " <pos1 row=\"", 12);
			add_int_to_buff((char**)ptr, &cbd, &size, rcCopy.top, false, 0);
			add_to_buff((char**)ptr, &cbd, &size, "\" column=\"", 10);
			add_int_to_buff((char**)ptr, &cbd, &size, rcCopy.left, false, 0);
			add_to_buff((char**)ptr, &cbd, &size, "\"></pos1>\n <pos2 row=\"", 22);
			add_int_to_buff((char**)ptr, &cbd, &size, rcCopy.bottom, false, 0);
			add_to_buff((char**)ptr, &cbd, &size, "\" column=\"", 10);
			add_int_to_buff((char**)ptr, &cbd, &size, rcCopy.right, false, 0);
			add_to_buff((char**)ptr, &cbd, &size, "\"></pos2>\n", 10);
			}
		}
	else if(type == FF_RLW) {
		cbd = rlp_strcpy((char*)*ptr, size, "<?xml version=\"1.0\"?><!DOCTYPE RLPlot-workbook>\n<RLPlot-data rows=\"");
		add_int_to_buff((char**)ptr, &cbd, &size, cRows, false, 0);
		add_to_buff((char**)ptr, &cbd, &size, "\" columns=\"", 11);
		add_int_to_buff((char**)ptr, &cbd, &size, cCols, false, 0);
		add_to_buff((char**)ptr, &cbd, &size, "\">\n", 3);
		}
	for(nl =0, i = rcCopy.top; i <= rcCopy.bottom; i++, nl++) {
		for(nc = 0, j = rcCopy.left; j <= rcCopy.right; j++, nc++) {
			switch (type) {
			case FF_TSV:
				if(nl || nc) add_to_buff((char**)ptr, &cbd, &size, nc ? (char*)"\t" : (char*)"\n", 1);
				if(etRows[i] && etRows[i][j]){
					if((ar && ar->IsInRange(j,i)) || !ar) {
						etRows[i][j]->GetResult(&res, false);
						TranslateResult(&res);
						add_to_buff((char**)ptr, &cbd, &size, res.text, 0);
						}
					}
				break;
			case FF_SYLK:
				if(etRows[i] && etRows[i][j] && etRows[i][j]->text && ((ar && ar->IsInRange(j,i)) || !ar)){
					etRows[i][j]->GetResult(&res, false);
					TranslateResult(&res);
					switch(res.type) {
					case ET_VALUE:	case ET_BOOL:
						sylk_cell_ref((char**) ptr, &cbd, &size, "C;", ";K", nl, nc);
						add_to_buff((char**)ptr, &cbd, &size, res.text, 0);
						break;
					case ET_DATE:
						sylk_cell_ref((char**) ptr, &cbd, &size, "F;P0;FG0G;", "\nC;K", nl, nc);
						add_dbl_to_buff((char**)ptr, &cbd, &size, res.value+1, false);
						break;
					case ET_DATETIME:
						sylk_cell_ref((char**) ptr, &cbd, &size, "F;P2;FG0G;", "\nC;K", nl, nc);
						add_dbl_to_buff((char**)ptr, &cbd, &size, res.value+1, false);
						break;
					case ET_TIME:
						sylk_cell_ref((char**) ptr, &cbd, &size, "F;P1;FG0G;", "\nC;K", nl, nc);
						add_dbl_to_buff((char**)ptr, &cbd, &size, res.value, false);
						break;
					case ET_TEXT:
						sylk_cell_ref((char**) ptr, &cbd, &size, "C;", ";K\"", nl, nc);
						add_to_buff((char**)ptr, &cbd, &size, res.text, 0);
						add_to_buff((char**)ptr, &cbd, &size, "\"", 1);
						break;
						}
					add_to_buff((char**)ptr, &cbd, &size, "\n", 1);
					if(bCopyCut) etRows[i][j]->SetText("");
					}
				break;
			case FF_RLW:	case FF_XML:
				if(etRows[i] && etRows[i][j] && etRows[i][j]->text && ((ar && ar->IsInRange(j,i)) || !ar)
					&& etRows[i][j]->text[0]){
					add_to_buff((char**)ptr, &cbd, &size, " <cell row=\"", 12);
					add_int_to_buff((char**)ptr, &cbd, &size, nl+1, false, 0);
					add_to_buff((char**)ptr, &cbd, &size, "\" column=\"", 10);
					add_int_to_buff((char**)ptr, &cbd, &size, nc+1, false, 0);
					add_to_buff((char**)ptr, &cbd, &size, "\">\n  <text>", 11);
					add_to_buff((char**)ptr, &cbd, &size, etRows[i][j]->text, 0);
					add_to_buff((char**)ptr, &cbd, &size, "</text>\n </cell>\n", 17);
					if(bCopyCut) etRows[i][j]->SetText("");
					}
				break;
				}
			}
		}
	if(type == FF_SYLK) {
		if(!bLimit) {
			add_to_buff((char**)ptr, &cbd, &size, "C;Y", 3);
			add_int_to_buff((char**)ptr, &cbd, &size, i+2, false, 0);
			add_to_buff((char**)ptr, &cbd, &size, ";X1;K\"long strings were"
			" truncated to 256 characters due to limitations of the SYLK format!\"\n", 92);
			}
		add_to_buff((char**)ptr, &cbd, &size, "E\n", 2);
		}
	else if(type == FF_TSV) add_to_buff((char**)ptr, &cbd, &size, "\n", 1);
	else if(type == FF_XML) add_to_buff((char**)ptr, &cbd, &size, "</spreadsheet-snippet>\n", 23);
	else if(type == FF_RLW){
		Disp->Command(CMD_WRITE_GRAPHS, ptr, (anyOutput*)&cbd);
		//note: cbd may be greater than size !
		add_to_buff((char**)ptr, &cbd, &size, "</RLPlot-data>\n", 15);
		}
	if(ar) delete ar;
	if(bCopyCut && type == FF_XML || type == FF_SYLK || type == FF_RLW){
		bCopyCut = false;
		DoPlot(w);
		}
	return true;
}

void SpreadMain(bool show)
{
	static SpreadData *w = 0L;

	if(show) {
		if(w = new SpreadData(0L)) w->Init(50, 10);
		do_formula(w, 0L);			//init mfcalc
		}
	else if (w) delete(w);
}
