//UtilObj.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 <math.h>
#include <ctype.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

Default defs;

static LineDEF ETbgnn = {0.0, 1.0, 0x00e8e8e8L, 0L};
static LineDEF ETbgna = {0.0, 1.0, 0x00ffffffL, 0L};
static LineDEF ETbgmn = {0.0, 1.0, 0x00cbcbcbL, 0L};
static LineDEF ETbgma = {0.0, 1.0, 0x00ffffc0L, 0L};
static LineDEF yLine = {0.0, 1.0, 0x0000ffffL, 0L};
extern const LineDEF BlackLine = {0.0, 1.0, 0x00000000L, 0L};
extern const LineDEF GrayLine = {0.0, 1.0, 0x00c0c0c0L, 0L};

static FillDEF ETfbnn = {FILL_NONE, 0x00e8e8e8L, 1.0, NULL, 0x00ffffffL};
static FillDEF ETfbna = {FILL_NONE, 0x00ffffffL, 1.0, NULL, 0x00ffffffL};
static FillDEF ETfbmn = {FILL_NONE, 0x00e8cbcbL, 1.0, NULL, 0x00ffffffL};
static FillDEF ETfbma = {FILL_NONE, 0x00ffffc0L, 1.0, NULL, 0x00ffffffL};
static FillDEF yFill = {FILL_NONE, 0x0000ffffL, 1.0, NULL, 0x0000ffffL};

extern char TmpTxt[500];
extern unsigned long cObsW;				//count objects written
extern GraphObj *CurrGO, *TrackGO;		//Selected Graphic Objects
extern dragHandle *CurrHandle;
extern UndoObj Undo;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Process fields with user input text
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EditText *CurrText = 0L, *scroll_et = 0;
int scroll_dist = 0;

EditText::EditText(void *par, char *msg, int r, int c)
{
	loc.x = loc.y = crb.x = rb.x = crb.y = rb.y = 0;	Value = 0.0;
	row = r;	col = c;	disp = 0L;		text = 0L;
	CursorPos = length = Align = 0;		type = ET_UNKNOWN;		TextCol=0x00000000L;
	bgLine = &ETbgnn;					bgFill = &ETfbnn;		parent = par;
	if(msg && msg[0]) {
		SetText(msg);		FindType();
		}
	else {
		Align = TXA_VCENTER | TXA_HRIGHT;
		type = ET_UNKNOWN;
		}
	m1 = m2 = -1;						//cursor positions track marks
	ftext = 0L;							//store formula text result here
}

EditText::~EditText()
{
	HideCopyMark();
	if(CurrText == this)	CurrText = 0L;
	if(text) free(text);	text = 0L;
//	if(ftext) free(ftext);	ftext = 0L;
}

bool
EditText::AddChar(int ci, anyOutput *Out, void *data_obj)
{
	unsigned char byte1, byte2, c, *tmp;
	POINT MyPos;
	int i;

	if(ci < 254 && ci > 31) c = (char)ci;
	else if(ci == 27) {						//Esc
		m1 = m2 = -1;		Redraw(Out, true);		return true;
		}
	else return false;
	Undo.TextCell(this, Out, text, &CursorPos, &m1, &m2, parent, 0L);
	if(parent) {
		((DataObj*)parent)->Command(CMD_MRK_DIRTY, 0L, 0L);
		((DataObj*)parent)->Command(CMD_SAVEPOS, 0L, 0L);
		}
	bgLine = &ETbgna; bgFill = &ETfbna; TextCol = 0x00000000L;
	if(text)length = (int)strlen(text);
	else length = 0;
	if(text) tmp = (unsigned char *)realloc(text, length+2);
	else tmp = (unsigned char *)calloc(2, sizeof(unsigned char));
	if(!tmp) return false;
	text = (char*)tmp;
	byte1 = byte2 = 0;
	//replace mark by character if mark exists
	if(hasMark()) {			//delete marked part of text
			if(m1 > m2) Swap(m1, m2);
			if(m2 >= (short int)strlen(text)) text[m1] = 0;
			else rlp_strcpy(text+m1, TMP_TXT_SIZE, text+m2);
			CursorPos = m1;
			m1 = m2 = -1;
			}
	byte1 = text[CursorPos];
	i = CursorPos;
	text[i++] = c;
	while(byte1) {
		byte2 = byte1;			byte1 = text[i];			text[i++] = byte2;
		}
	text[i] = byte1;			CursorPos++;				type = ET_UNKNOWN;
	Redraw(Out, true);
	MyPos.y = ((loc.y +crb.y)>>1);
	MyPos.x = Align & TXA_HRIGHT ? crb.x - 2 : loc.x + 2;
	if(Out)Out->TextCursor(text, MyPos, (POINT *) NULL, &CursorPos, 
		scroll_et == this ? scroll_dist : scroll_dist=0);
	set_etracc();
	return true;
}

void
EditText::Update(int select, anyOutput *Out, POINT *MousePos)
{
	POINT MyPos;

	if(!parent && !disp) disp = Out;
	if(select != 1 && select != 5) m1 = m2 = -1;		//no mark;
	switch(select) {
		case 0:							//just redraw with current settings
			Redraw(Out, true);
			break;
		case 5:							//dialog control
			if(!text)Align = TXA_VCENTER | TXA_HLEFT;
		case 1:							//active spread sheet cell with cursor
			if((type & 0xff) == ET_FORMULA) Align = TXA_VCENTER | TXA_HLEFT;
			if(!text && !(text = (char *) calloc(10, sizeof(char))))return;
			if(CursorPos > (int)strlen(text)) CursorPos = (int)strlen(text);
			if(MousePos && (type & 0xff) == ET_TEXT && (text && text[0] == '\'' && (bgLine == &ETbgnn || bgLine == &ETbgmn))) {
				MousePos->x += 4;
				}
			bgLine = &ETbgna; bgFill = &ETfbna; TextCol = 0x00000000L;
			if(Out) {
				Redraw(Out, true);
				MyPos.y = ((loc.y +crb.y)>>1);
				MyPos.x = Align & TXA_HRIGHT ? crb.x - 4 : loc.x + 4;
				if(MousePos && MousePos->x && MousePos->y) {
					Out->TextCursor(text, MyPos, MousePos,&CursorPos, 
					scroll_et == this ? scroll_dist : scroll_dist=0);
					}
				else if(select ==1) Out->TextCursor(text, MyPos, NULL, &CursorPos, 
					scroll_et == this ? scroll_dist : (scroll_dist=0)+2);
				set_etracc();
				}
			break;
		case 2:							//inactive spreadsheet cell
			if(CurrText == this) {
				FindType();
				}
			if(crb.x > rb.x) {
				crb.x = rb.x;	crb.y = rb.y;
				}
			bgLine = &ETbgnn; bgFill = &ETfbnn; TextCol = 0x00000000L;
			if(Out) Redraw(Out, true);
			break;
		case 10:						//value filled in by external app.
			if(text && text[0]) {
				type = ET_VALUE;
				Align = TXA_VCENTER | TXA_HRIGHT;
#ifdef USE_WIN_SECURE
				sscanf_s(text, "%lf", &Value);
#else
				sscanf(text, "%lf", &Value);
#endif
				}
			break;
		case 20:						//update value only
			FindType();
			break;
		}
}

bool
EditText::Redraw(anyOutput *Out, bool display)
{
	RECT rc;
	POINT MyPos;
	char *txt, tmptxt[500];
	int i, w, h, o_crbx;
	bool b_clip = false;
	anyOutput *opc;
	anyResult cres;
	POINT grid[3];

	if(!parent && disp) Out = disp;
	if((type & ET_NODRAW_EMPTY) == ET_NODRAW_EMPTY && CurrText != this){
		type &= ~ET_NODRAW;
		return true;
		}
	if(loc.x <1 || rb.x < 1 || loc.y <1 || rb.y <1) return false;
	o_crbx = crb.x;			crb.x = rb.x;				crb.y = rb.y;
	if (Out) {
		if (m1 >m2) Swap(m1, m2);
		if(((type & 0xff) == ET_UNKNOWN) && text && text[0] && (bgLine == &ETbgnn || bgLine == &ETbgmn)) FindType();
		Out->TxtSet.Align = Align;		Out->TxtSet.ColTxt = TextCol;
		Out->TxtSet.ColBg = bgLine->color;
		if(text && text[0]) {
			Out->oGetTextExtent(text, (int)strlen(text), &w, &h);
			if(CurrText == this && parent && col >= 0) {
				for(i = col+1; (crb.x - loc.x) < (w+(h>>1)); i++) {
					crb.x += ((DataObj*)parent)->ri->GetWidth(i);
					}
				if(o_crbx > loc.x && o_crbx > crb.x && o_crbx < 4000) crb.x = o_crbx;
				}
			else if((crb.x - loc.x) < (w+(h>>1))) b_clip = true;
			}
		Out->SetFill(bgFill);		Out->SetLine(bgLine);
		rc.left = loc.x;			rc.right = crb.x;
		rc.top = loc.y+1;			rc.bottom = crb.y-2;
		Out->oRectangle(loc.x, loc.y, crb.x-1, crb.y-1);
		if(!text || !text[0]){
			if((type & 0xff) == ET_VALUE){
#ifdef USE_WIN_SECURE
				sprintf_s(tmptxt, 500, "%g", Value);
#else
				sprintf(tmptxt, "%g", Value);
#endif
				}
			else if((type & 0xff) == ET_BOOL) {
#ifdef USE_WIN_SECURE
				sprintf_s(tmptxt, 500, Value != 0.0 ? "true" : "false");
#else
				sprintf(tmptxt, Value != 0.0 ? "true" : "false");
#endif
				}
			else tmptxt[0] = 0;
			if(tmptxt[0] && (text = (char*)realloc(text, strlen(tmptxt)+2))) rlp_strcpy(text, 500, tmptxt);
			CursorPos = 0;
			}
		if(ftext) free(ftext);		ftext = 0L;
		if(text && text[0]){
			if(bgLine == &ETbgnn || bgLine == &ETbgmn) {
				Out->TxtSet.Align = TXA_HLEFT | TXA_VCENTER;
				GetResult(&cres, false);				TranslateResult(&cres);
				Value = cres.value;
				switch (cres.type) {
				case ET_VALUE:
					Out->TxtSet.Align = TXA_HRIGHT | TXA_VCENTER;
					b_clip = false;				rlp_strcpy(tmptxt, 500, cres.text);
					fit_num_rect(Out, rb.x - loc.x, tmptxt);
					Int2Nat(tmptxt);			break;
				case ET_BOOL:	case ET_DATE:	case ET_TIME:	case ET_DATETIME:
				case ET_TEXT:
					Out->TxtSet.Align = cres.type == ET_TEXT ? 
						TXA_HLEFT | TXA_VCENTER : TXA_HRIGHT | TXA_VCENTER;
					i = (int)strlen(cres.text)+2;
					if(ftext = (char*)realloc(ftext, i > 20 ? i : 20))rlp_strcpy(ftext, i, cres.text);
					if(cres.text && i < sizeof(tmptxt)) 
						rlp_strcpy(tmptxt, 500, cres.text[0] != '\'' ? cres.text : cres.text +1);
					else if(cres.text)rlp_strcpy(tmptxt, 500, "#SIZE");
					else tmptxt[0] = 0;			
					Out->oGetTextExtent(tmptxt, (int)strlen(tmptxt), &w, &h);
					b_clip = (crb.x - loc.x) < (w+(h>>1)) ? true : false;
					break;
				case ET_ERROR:
					rlp_strcpy(tmptxt, 500, "#ERROR");	break;
				default: 
					rlp_strcpy(tmptxt, 500, "#VALUE");	break;
					}
				txt = tmptxt;
				}
			else txt = text;
			if(b_clip && parent && col >= 0 && row >=0) {
				Out->oGetTextExtent(txt, (int)strlen(txt), &w, &h);
				for(i = col+1; (crb.x - loc.x) < (w+(h>>1)); i++) {
					if(((DataObj*)parent)->isEmpty(row, i)) {
						crb.x += ((DataObj*)parent)->ri->GetWidth(i);
						((DataObj*)parent)->etRows[row][i]->type |= ET_NODRAW;
						}
					else break;
					}
				if((crb.x - loc.x) >= (w+(h>>1))) b_clip = false;
				else rc.right = crb.x;
				}
			MyPos.y = (loc.y+rb.y)>>1;
			if(Out->TxtSet.Align & TXA_HRIGHT) {	//right justified text
				MyPos.x = crb.x-4;
				}
			else {									//left justified text
				MyPos.x = loc.x+4;
				}
			if(b_clip && (opc = NewBitmapClass(w+22, rb.y-loc.y, Out->hres, Out->vres))){
				if(scroll_et != this || parent) {
					scroll_et = this;	scroll_dist = 0;
					}
				opc->Erase(bgFill->color);
				opc->SetTextSpec(&Out->TxtSet);		opc->TxtSet.Align = TXA_HLEFT | TXA_VCENTER;
				opc->oTextOut(4,(rb.y-loc.y)>>1, txt, (int)strlen(txt));
				if(!parent && CursorPos) {
					Out->oGetTextExtent(txt, CursorPos, &w, &h);
					while((scroll_dist + w)>(rc.right-rc.left-10)) scroll_dist -=10;
					while((scroll_dist + w)<12) scroll_dist +=10;
					if(scroll_dist >0) scroll_dist=0;
					}
				else scroll_dist=0;
				Out->CopyBitmap(rc.left+1, rc.top+1, opc, 1-scroll_dist, 1, 
					rc.right-rc.left-4, rc.bottom-rc.top-2, false);
				DelBitmapClass(opc);
				}
			else {
				if(display && hasMark() && mx1 > loc.x && mx2 < crb.x) {
					Out->SetFill(&yFill);		Out->SetLine(&yLine);
					Out->oRectangle(mx1, rc.top, mx2, rc.bottom);
					Out->SetFill(bgFill);		Out->SetLine(bgLine);
					}
				scroll_dist = 0;
				Out->oTextOut(MyPos.x, MyPos.y, txt, 0);
				if(display && hasMark() && mx1 > loc.x && mx2 < crb.x) {
					rc.left = mx1;		rc.right = mx2;
					Out->CopyBitmap(mx1, rc.top, Out, mx1, rc.top, mx2-mx1, rc.bottom-rc.top, true);
					Out->MrkMode = MRK_NONE;
					}
				}
			}
		Out->SetLine((LineDEF*)&GrayLine);
		grid[0].x = loc.x;					grid[0].y = grid[1].y = crb.y-1;
		grid[1].x = grid[2].x = crb.x-1;	grid[2].y = loc.y-1;
		Out->oPolyline(grid, 3, 0L);
		if(display) {
			if(!(Out->UpdateRect(&rc, false))) return false;
			}
		return true;
	}
	return false;
}

void
EditText::Mark(anyOutput *Out, int mark)
{
	LineDEF *ol = bgLine;
	FillDEF *of = bgFill;
	DWORD ocol = TextCol;

	m1 = m2 = -1;
	if(!parent) return;
	switch (mark){
	case 0:				//normal not active
		bgLine = &ETbgnn; bgFill = &ETfbnn; TextCol = 0x00000000L;
		break;
	case 1:				//normal active
		bgLine = &ETbgna; bgFill = &ETfbna; TextCol = 0x00000000L;
		break;
	case 2:				//mark not active
		bgLine = &ETbgmn; bgFill = &ETfbmn; TextCol = 0x00c00000L;
		break;
	case 3:				//mark active
		bgLine = &ETbgma; bgFill = &ETfbma; TextCol = 0x00ff0000L;
		break;
		}
	if(!mark || mark == 2) {
		loc.y--;	rb.y++;
		}
	Redraw(Out, true);
	if(!mark || mark == 2) {
		loc.y++;	rb.y--;
		}
	bgLine = ol;	bgFill = of;	TextCol = ocol;
}

bool
EditText::Command(int cmd, anyOutput *Out, void *data_obj)
{
	int i, j, k, w, h;
	POINT MyPos;
	MouseEvent *mev;
	static RECT rMark;
	bool bRet;
	char *tag1, *tag2;
	unsigned char *pt;

	MyPos.y = ((loc.y+crb.y)>>1);
	MyPos.x = Align & TXA_HRIGHT ? crb.x - 4 : loc.x + 4;
	if(!(text)) return false;
	if(!parent && disp) Out = disp;		//Dialog !
	switch(cmd) {
		case CMD_MRK_DIRTY:
			type = ET_UNKNOWN;
			if(CurrText == this) {
				Command(CMD_REDRAW, Out, data_obj);
				if(parent)((DataObj*)parent)->Command(CMD_MRK_DIRTY, Out, 0L);
				}
			else if(parent) {
				((DataObj*)parent)->Command(CMD_REDRAW, Out, 0L);
				((DataObj*)parent)->Command(CMD_MRK_DIRTY, Out, 0L);
				}
			else return Command(CMD_REDRAW, Out, data_obj);
			return true;
		case CMD_SETFONT:
			if (!text || !text[0]) return false;
			if(Out) Undo.SetDisp(Out);
			type = ET_TEXT;
			if(hasMark()) {
				Undo.TextCell(this, Out, text, &CursorPos, &m1, &m2, parent, 0L);
				switch (*((int*)data_obj)) {
				case FONT_HELVETICA:
					tag1 = (char*)"<face=helvetica>";		tag2 = (char*)"</face>";		break;
				case FONT_TIMES:
					tag1 = (char*)"<face=times>";			tag2 = (char*)"</face>";		break;
				case FONT_COURIER:
					tag1 = (char*)"<face=courier>";			tag2 = (char*)"</face>";		break;
				case FONT_GREEK:
					tag1 = (char*)"<face=greek>";			tag2 = (char*)"</face>";		break;
				default:
					return false;
					}
				if(m1 < m2) {
					j = m1;	k = m2;
					}
				else if(m1 > m2) {
					j = m2; k = m1;
					}
				else return false;			//empty mark !
				for(i = 0; i < j; i++) TmpTxt[i] = text[i];
				for(j = 0, w = i; tag1[j]; j++) TmpTxt[i++] = tag1[j];
				for( ; w < k; w++) TmpTxt[i++] = text[w];
				for(j = 0; tag2[j]; j++) TmpTxt[i++] = tag2[j];
				for( ; TmpTxt[i++] = text[w]; w++);
				m1 += (w = (int)strlen(tag1));	m2 += w;	CursorPos += w;
				CleanTags(TmpTxt, &m1, &m2, &CursorPos);
				if(text = (char*)realloc(text, strlen(TmpTxt)+2)) rlp_strcpy(text, TMP_TXT_SIZE, TmpTxt);
				Command(CMD_REDRAW, Out, 0L);
				return true;
				}
			return false;
		case CMD_SETSTYLE:
			if (!text || !text[0]) return false;
			if(Out) Undo.SetDisp(Out);
			type = ET_TEXT;
			if(hasMark()) {
				Undo.TextCell(this, Out, text, &CursorPos, &m1, &m2, parent, 0L);
				switch (*((int*)data_obj)) {
				case TXS_BOLD:
					tag1 = (char*)"<b>";		tag2 = (char*)"</b>";		break;
				case ~TXS_BOLD:
					tag1 = (char*)"</b>";		tag2 = (char*)"<b>";		break;
				case TXS_ITALIC:
					tag1 = (char*)"<i>";		tag2 = (char*)"</i>";		break;
				case ~TXS_ITALIC:
					tag1 = (char*)"</i>";		tag2 = (char*)"<i>";		break;
				case TXS_UNDERLINE:
					tag1 = (char*)"<u>";		tag2 = (char*)"</u>";		break;
				case ~TXS_UNDERLINE:
					tag1 = (char*)"</u>";		tag2 = (char*)"<u>";		break;
				case TXS_SUPER:
					tag1 = (char*)"<sup>";		tag2 = (char*)"</sup>";		break;
				case ~TXS_SUPER:
					tag1 = (char*)"</sup>";		tag2 = (char*)"<sup>";		break;
				case TXS_SUB:
					tag1 = (char*)"<sub>";		tag2 = (char*)"</sub>";		break;
				case ~TXS_SUB:
					tag1 = (char*)"</sub>";		tag2 = (char*)"<sub>";		break;
				default:
					return false;
					}
				if(m1 < m2) {
					j = m1;	k = m2;
					}
				else if(m1 > m2) {
					j = m2; k = m1;
					}
				else return false;			//empty mark !
				for(i = 0; i < j; i++) TmpTxt[i] = text[i];
				for(j = 0, w = i; tag1[j]; j++) TmpTxt[i++] = tag1[j];
				for( ; w < k; w++) TmpTxt[i++] = text[w];
				for(j = 0; tag2[j]; j++) TmpTxt[i++] = tag2[j];
				for( ; TmpTxt[i++] = text[w]; w++);
				m1 += (w = (int)strlen(tag1));	m2 += w;	CursorPos += w;
				CleanTags(TmpTxt, &m1, &m2, &CursorPos);
				if(text = (char*)realloc(text, strlen(TmpTxt)+2)) rlp_strcpy(text, TMP_TXT_SIZE, TmpTxt);
				Command(CMD_REDRAW, Out, 0L);
				return true;
				}
			return false;
		case CMD_ADDTXT:
			if((tag1 = (char*)data_obj) && *tag1 && text && 
				(type == ET_TEXT || type == ET_UNKNOWN || type == ET_FORMULA)){
				if(hasMark()) Command(CMD_DELETE, 0L, 0L);
				else Undo.TextCell(this, Out, text, &CursorPos, &m1, &m2, parent, 0L);
				if(m1 > -1 && m2 > -1) Command(CMD_DELETE, 0L, 0L);
				for(k = 0; tag1[k] && tag1[k] == text[k]; k++);
				if(tag1[k]!=';') k = 0;
				for(i = 0; i < CursorPos && text[i]; i++) TmpTxt[i] = text[i];
				j = i + rlp_strcpy(TmpTxt+i, TMP_TXT_SIZE-i, tag1+k);
				if(text[i]) j += rlp_strcpy(TmpTxt+j, TMP_TXT_SIZE-j, text+i);
				if(text = (char*)realloc(text, j+2 )) rlp_strcpy(text, j+2, TmpTxt);
				CursorPos += (int)strlen(tag1+k);
				Out->Focus();					Update(1, Out, 0L);
				set_etracc();
				}
			return true;
		case CMD_BACKSP:
			if(!text) return false;
			if(CursorPos <=0){
				Out->TextCursor(text, MyPos, (POINT *) NULL, &CursorPos, 
					scroll_et == this ? scroll_dist : scroll_dist=0);
				return false;
				}
			Undo.TextCell(this, Out, text, &CursorPos, &m1, &m2, parent, 0L);
			CursorPos--;						//continue as if delete
		case CMD_DELETE:
			if(!text) return false;
			if(cmd == CMD_DELETE) Undo.TextCell(this, Out, text, &CursorPos, &m1, &m2, parent, 0L);
			if(parent) {
				((DataObj*)parent)->Command(CMD_MRK_DIRTY, 0L, 0L);
				((DataObj*)parent)->Command(CMD_SAVEPOS, 0L, 0L);
				}
			bRet = false;
			if(!text || !text[0]) {
				type = ET_UNKNOWN;	CursorPos = 0;
				}
			if(hasMark()) {			//delete marked part of text
				if (!text || !text[0]) return false;
				if(m1 > m2) Swap(m1, m2);
				if(m2 >= (short int)strlen(text)) text[m1] = 0;
				else rlp_strcpy(text+m1, (int)strlen(text)+m1, text+m2);
				CursorPos = m1;						m1 = m2 = -1;
				if(!text[0]) {
					type = ET_UNKNOWN;	CursorPos = 0;
					}
				if(Out) Redraw(Out, (bRet = true));
				}
			else if(text[CursorPos]) {
				rlp_strcpy(text + CursorPos, (int)strlen(text + CursorPos), text + CursorPos + 1);
				if(!text || !text[0]) {
					type = ET_UNKNOWN;	CursorPos = 0;
					}
				if(Out)Redraw(Out, (bRet = true));
				}
			set_etracc();
			if(Out)Out->TextCursor(text, MyPos, (POINT *) NULL, &CursorPos,
				scroll_et == this ? scroll_dist : scroll_dist=0);
			return bRet;
		case CMD_COPY:
			if(text && text[0]) {
				rMark.left = loc.x+2;		rMark.right = rb.x-2;
				rMark.top = loc.y+1;		rMark.bottom = rb.y-2;
				if(m1 != m2 && m1 >=0 && m2 >=0) {
					if (m1 >m2) Swap(m1, m2);
					rMark.left = mx1;		rMark.right = mx2;
					if(Out) Out->UpdateRect(&rMark, false);
					CopyText(text+m1, m2-m1);
					if(Out) {
						Out->MrkMode = MRK_NONE;
						ShowCopyMark(Out, &rMark, 1);
						Out->UpdateRect(&rMark, true);
						}
					return false;
					}
				CopyText(text, (int)strlen(text));
				if(Out)Out->UpdateRect(&rMark, true);
				return false;
				}
			return false;
		case CMD_PASTE:
			if(pt = PasteText()) {
				Undo.TextCell(this, Out, text, &CursorPos, &m1, &m2, parent, 0L);
				for(i = 0; pt[i] > 0x20 && i < 81; i++) this->AddChar(pt[i], 0L, 0L);
				if(Out) Redraw(Out, true);				free(pt);
				set_etracc();
				if(i) return true;
				}
			return false;
		case CMD_SHIFTRIGHT:
			if(CursorPos == m1 && text[m1]) m1++;
			else if(CursorPos == m2 && text[m2]) m2++;
			else if(text[CursorPos]){
				m1 = CursorPos;	m2 = CursorPos+1;
				}
			set_etracc();
			if(text[CursorPos]) CursorPos++;
			else return false;
		case CMD_SHIFTLEFT:
			set_etracc();
			if (!(CursorPos)) return false;
		case CMD_REDRAW:
			if(cmd == CMD_SHIFTLEFT) {
				if(CursorPos == m1 && m1 >0) m1--;
				else if(CursorPos == m2 && m2 >0) m2--;
				else if(CursorPos > 0){
					m1 = CursorPos;	m2 = CursorPos-1;
					}
				if(CursorPos >0) CursorPos--;
				}
			if(m1 >=0 && m2 >= 0 && m1 != m2 && Out) {
				if(m1 > m2) Swap(m1, m2);
				w = h = 0;
				if(Align & TXA_HRIGHT) {	//right justified text
					Out->oGetTextExtent(text, 0, &w, &h);
					mx1 = crb.x-4 - w;
					}
				else {						//left justified text
					mx1 = loc.x +4;
					}
				Out->oGetTextExtent(text, m1, &w, &h);
				mx1 += (m1 ? w : 0);
				Out->oGetTextExtent(text+m1, m2-m1, &w, &h);
				mx2 = mx1 + w;
				}
			HideTextCursor();				Redraw(Out, true);
			Out->TextCursor(text, MyPos, (POINT *) NULL, &CursorPos,
				scroll_et == this ? scroll_dist : scroll_dist=0);
			return true;
		case CMD_CURRLEFT:
			m1 = m2 = -1;					set_etracc();
			if(CursorPos >0) {
				CursorPos--;
				if(Redraw(Out, true) && Out->TextCursor(text, MyPos, (POINT *) NULL,
					&CursorPos, scroll_et == this ? scroll_dist : scroll_dist=0)) return true;
				else return false;
				}
			else if (data_obj) {
				MyPos.x = loc.x-4;			MyPos.y = (rb.y+loc.y)/2;
				if(((DataObj*)data_obj)->Select(&MyPos))return true;
				MyPos.x = loc.x+4;
				((DataObj*)data_obj)->Select(&MyPos);
				}
			return false;
		case CMD_CURRIGHT:
			m1 = m2 = -1;					set_etracc();
			if(text[CursorPos]){
				CursorPos++;
				if(Redraw(Out, true) && Out->TextCursor(text, MyPos, (POINT *) NULL,
					&CursorPos, scroll_et == this ? scroll_dist : scroll_dist=0)) return true;
				else return false;
				}
			else if (data_obj) {
				MyPos.x = rb.x+4;		MyPos.y = (rb.y+loc.y)/2;	crb.x = rb.x;
				if(((DataObj*)data_obj)->Select(&MyPos)) return true;
				MyPos.x = rb.x-4;
				((DataObj*)data_obj)->Select(&MyPos);
				}
			return false;
		case CMD_UPDATE:
			m1 = m2 = -1;
			Redraw(Out, true);
			return true;
		case CMD_POS_FIRST:		case CMD_POS_LAST:
			CursorPos = (cmd == CMD_POS_LAST && text && text[0]) ? (int)strlen(text) : 0;
			m1 = m2 = -1;		Redraw(Out, true);
			Out->TextCursor(text, MyPos, (POINT *) NULL, &CursorPos,
				scroll_et == this ? scroll_dist : scroll_dist=0);
			set_etracc();
			return true;
		case CMD_CURRDOWN:		case CMD_CURRUP:
			if (data_obj) {
			//the following calculation of the cursor position is crude
            //it is based on a aspect of 2:1 for digits
				if(Align & TXA_HRIGHT)		//right justified text
					MyPos.x = rb.x-4-((rb.y-loc.y-4)*((long)strlen(text)-CursorPos))/2;
				else MyPos.x = loc.x+4+((rb.y-loc.y-4)*CursorPos)/2;
				MyPos.y = (cmd == CMD_CURRUP) ? loc.y-2 : rb.y+2;
				if(MyPos.x < loc.x) MyPos.x = loc.x +4;
				if(MyPos.x > rb.x) MyPos.x = rb.x -4;
				if(((DataObj*)data_obj)->Select(&MyPos))return true;
				MyPos.y = rb.y;
				((DataObj*)data_obj)->Select(&MyPos);
				}
			return false;
		case CMD_MOUSE_EVENT:					//track left mouse button
			mev = (MouseEvent*) data_obj;
			if(!text || !text[0]) return false;
			if(mev->x <loc.x || mev->x >crb.x || mev->y <loc.y || mev->y >rb.y)return false;
			if(mev->Action == MOUSE_LBDOWN) {
				m1 = m2 = -1;//					set_etracc();
				return true;
				}
			if(mev->Action == MOUSE_LBDOUBLECLICK) {
				rMark.top = loc.y+1;			rMark.bottom = rb.y-2;
				if(!Out->oGetTextExtent(text, (int)strlen(text), &w, &h)) return false;
				m1 = 0;							m2 = (int)strlen(text);
				if(Align & TXA_HRIGHT) {		//right justfied text
					rMark.right = crb.x -4;		rMark.left = crb.x - w - 4;
					}
				else {							//left justified text
					rMark.left = loc.x +4;		rMark.right = rMark.left +w;
					}
				mx1 = rMark.left;				mx2 = rMark.right;
				Redraw(Out, true);				set_etracc();
				return true;
				}
			MyPos.x = Align & TXA_HRIGHT ? mev->x + 4 : mev->x - 4;
			MyPos.y = mev->y;
			Out->TxtSet.Align = Align;
			j = Out->CalcCursorPos(text, Align & TXA_HRIGHT ? crb :loc, &MyPos);
			if(j == m1 || j == m2) return true;
			if(Align & TXA_HRIGHT) {			//right justfied text
				if((i = (int)strlen(text)-j)){
					if(!Out->oGetTextExtent(text+j, i, &w, &h)) return false;
					w = crb.x - w - 4;
					}
				else w = crb.x-1;
				}
			else {								//left justified text
				if(!j) w = 0;
				else if(!Out->oGetTextExtent(text, j, &w, &h))return false;
				w += (loc.x+4);
				}
			if(m1 == m2 && m1 == -1) {
				mx1 = (short)(rMark.left = w);
				m1 = j;
				}
			else if(j != m2){
				m2 = j;
				if(m2 >= 0)Out->UpdateRect(&rMark, false);
				mx2 = (short)(rMark.right = w);
				rMark.top = loc.y+1;				rMark.bottom = rb.y-2;
				if(rMark.right < crb.x && rMark.right > loc.x &&
					rMark.left > loc.x && rMark.left < crb.x)
					Redraw(Out, true);
				}
			if(hasMark() && parent)
				//remove range-mark of data 
				((DataObj*)parent)->Command(CMD_UNLOCK, 0L, 0L);
			return true;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// return the value (i.e. the floating point equivalent) of text
bool
EditText::GetValue(double *v)
{
	anyResult * res;

	if(((type & 0xff) == ET_UNKNOWN) && (text)) FindType();
	if(!text || !text[0]) {
		if((type & 0xff) == ET_VALUE) {
			*v = Value;		return true;
			}
		return false;
		}
	if(CurrText == this && !(type & ET_BUSY)) FindType();
	if((type & 0xff) == ET_VALUE || (type & 0xff) == ET_BOOL || (type & 0xff) == ET_DATE 
		|| (type & 0xff) == ET_TIME || (type & 0xff) == ET_DATETIME){
		*v = Value;			return true;
		}
	if((type & 0xff) == ET_FORMULA && text && text[0]){
		if(!(type & ET_BUSY)){
			type |= ET_BUSY;
			if(res = do_formula((DataObj*)parent, text+1)) {
				if(res->type == ET_VALUE || res->type == ET_DATE || res->type == ET_TIME 
					|| res->type == ET_DATETIME || res->type == ET_BOOL){
					*v = Value = res->value;	type &= ~ET_BUSY;	return true;
					}
				}
			*v = Value = 0.0;	type &= ~ET_BUSY;	return false;
			}
		else type |= ET_CIRCULAR;
		*v = Value;
		return true;
		}
	return false;
}

bool
EditText::GetText(char *tx, int size, bool bTranslate)
{
	char *t = 0L;
	static char tmp_txt[40];
	anyResult res;

	if((type & 0xff) == ET_TEXT && text && text[0]) {
		if(text[0] =='\'' && text[1]) t = text + 1;
		else t = text;
		}
	else if(bTranslate) {
		GetResult(&res, false);						TranslateResult(&res);
		switch (res.type) {
		case ET_VALUE:
			rlp_strcpy(tmp_txt, 40, res.text);		t = tmp_txt;
			Int2Nat(tmp_txt);						break;
		case ET_BOOL:		case ET_DATE:			case ET_TIME:	
		case ET_DATETIME:	case ET_TEXT:
			t = res.text;							break;
		default:
			t = 0L;									break;
			}
		}
	else if(text && text[0]) t = (text[0] =='\'' && text[1]) ? text+1 : text;
	if(t) {
		rlp_strcpy(tx, size, t);
		return true;
		}
	else if((type & 0xff) == ET_VALUE) {
#ifdef USE_WIN_SECURE
		if(text = (char*)realloc(text, 20)) sprintf_s(text, 20, "%g", Value);
#else
		if(text = (char*)realloc(text, 20)) sprintf(text, "%g", Value);
#endif
		if(text && text[0]) return(GetText(tx, size, false));
		}
	return false;
}

bool
EditText::GetResult(anyResult *r, bool use_last)
{
	anyResult * res;

	if(!text || !text[0]) {
		r->text = 0L;
		if((type & 0xff) == ET_VALUE) {
			r->value = Value;		r->type = ET_VALUE;
			}
		else {
			r->value = 0.0;			r->type = ET_UNKNOWN;
			}
		return true;
		}
    if((type & 0xff) == ET_UNKNOWN) FindType();
	if((type & 0xff) == ET_VALUE || (type & 0xff) == ET_BOOL || (type & 0xff) == ET_DATE 
		|| (type & 0xff) == ET_TIME || (type & 0xff) == ET_DATETIME){
		r->text = 0L;	r->value = Value;		r->type = (type & 0xff);
		return true;
		}
	if((type & 0xff) == ET_TEXT) {
		r->text = text;	r->value = 0.0;			r->type = ET_TEXT;
		return true;
		}
	if((type & 0xff) == ET_FORMULA){
		if(use_last) {
			if(ftext) {
				r->text = ftext;	r->value = 0.0;			r->type = ET_TEXT;
				}
			else {
				r->text = 0L;		r->value = Value;		r->type = ET_VALUE;
				}
			return true;
			}
		if(!(type & ET_BUSY)){
			type |= ET_BUSY;
			if(res = do_formula((DataObj*)parent, text+1)) {
				if(res->type == ET_VALUE) Value = res->value;
				else if(res->type == ET_ERROR) {
					res->text = "#ERROR";	res->type = ET_TEXT;
					}
				else Value = 0.0;	type &= ~ET_BUSY;
				memcpy(r, res, sizeof(anyResult));
				return true;
				}
			type &= ~ET_BUSY;
			return false;
			}
		else {
			type |= ET_CIRCULAR;
			r->text = "#CIRC.";	r->value = 0.0;			r->type = ET_TEXT;
			return true;
			}
		return false;
		}
	return false;
}

bool
EditText::SetValue(double v)
{
	if(text) text[0] = 0;
	Value = v;	type = ET_VALUE;
	return true;
}

bool
EditText::SetText(char *t)
{
	int cb;

	Value = 0.0;	type = ET_UNKNOWN;
	bgLine = &ETbgnn; bgFill = &ETfbnn; TextCol = 0x00000000L;
	if(t && t[0] && (text = (char*)realloc(text, cb = (int)(strlen(t)+2)))) rlp_strcpy(text, cb, t);
	else if (text) text[0] = 0;
	return false;
}

void
EditText::SetRec(RECT *rc)
{
	loc.x = rc->left;				loc.y = rc->top;
	crb.x = rb.x = rc->right;		crb.y = rb.y = rc->bottom;
}

	
bool
EditText::isValue()
{
	if((type & 0xff)==ET_UNKNOWN) FindType();
	return (type == ET_VALUE || type == ET_FORMULA || type == ET_BOOL
		|| type == ET_DATE || type == ET_TIME || type == ET_DATETIME);
}

bool
EditText::isFormula()
{
	if((type & 0xff)==ET_UNKNOWN) FindType();
	return (type == ET_FORMULA);
}

void
EditText::FindType()
{
	int i, c1, c2, c3, c4, c5;

	if(!text || !text[0]) {
		Align = TXA_VCENTER | TXA_HRIGHT;
		if ((type & 0xff) == ET_VALUE) type = ET_VALUE;
		else type = ET_UNKNOWN | ET_EMPTY;
		return;
		}
	if(text[0] == '=') {
		Align = TXA_VCENTER | TXA_HRIGHT;
		type = ET_FORMULA;
		}
	else if(text[0] > 31 && isdigit(text[0]) || ((text[0] == '-' ) || text[0] == '.' 
		|| text[0] == defs.DecPoint[0]) && text[1]>31 && (isdigit(text[1]))) {
		for(i = c1 = c2 = c3 = c4 = c5 = 0; text[i]; i++) {
			switch(text[i]) {
			case '.':		c1++;		break;
			case '-':		c2++;		break;
			case '/':		c3++;		break;
			case ':':		c4++;		break;
			case 'e':		break;
			case 'E':		break;
			default:		if(isalpha(text[i])) c5++;
				}
			}
		if(c5 > 0){
			Align = TXA_VCENTER | TXA_HLEFT;
			type = ET_TEXT;
			}
		else if(c1 < 2 && c2 < 2 && !c3  && !c4 && Txt2Flt(text, &Value)) {
			Align = TXA_VCENTER | TXA_HRIGHT;
			type = ET_VALUE;
			}
		else if((c1 == 2 || c2 == 2 || c3 == 2) && date_value(text, 0L, &Value)) {
			Align = TXA_VCENTER | TXA_HRIGHT;
			type = c4 == 2 ? ET_DATETIME : ET_DATE;
			}
		else if((c4 == 1 || c4 == 2) && date_value(text, "H:M:S", &Value)) {
			Align = TXA_VCENTER | TXA_HRIGHT;
			type = ET_TIME;
			}
		else {
			Align = TXA_VCENTER | TXA_HLEFT;
			type = ET_TEXT;
			}
		}
	else {
		if(0 == strcmp(text, "true")) {
			Value = 1.0;
			Align = TXA_VCENTER | TXA_HRIGHT;
			type = ET_BOOL;
			}
		else if(0 == strcmp(text, "false")) {
			Value = 0.0;
			Align = TXA_VCENTER | TXA_HRIGHT;
			type = ET_BOOL;
			}
		else if(0 == strcmp(text, "inf")) {
			Value = HUGE_VAL;
			Align = TXA_VCENTER | TXA_HRIGHT;
			type = ET_VALUE;
			}
		else if(0 == strcmp(text, "-inf")) {
			Value = -HUGE_VAL;
			Align = TXA_VCENTER | TXA_HRIGHT;
			type = ET_VALUE;
			}
		else {
			Align = TXA_VCENTER | TXA_HLEFT;
			type = ET_TEXT;
			}
		}
}

void
EditText::set_etracc()
{
	int i;
	bool accept_range;
	anyResult *res;

	if(parent) {
		if(hasMark()) i = m1 < m2 ? m1 : m2;
		else i = CursorPos;
		accept_range = (i && text && text[0] == '=' && text[i-1]!=')'
			&& text[i-1] > 31 && !(isdigit(text[i-1]) || isalpha(text[i-1])));
		if(accept_range) {
			LockData(true, true);
			res = do_formula((DataObj*)parent, text+1);
			if(res->type != ET_ERROR) accept_range = false;
			if(!accept_range) {
				if(text[i-1] == '(' || text[i-1] == ',' || text[i-1] == ';')
					accept_range = true;
				}
			((DataObj*)parent)->Command(CMD_CLEAR_ERROR, 0L, 0L);
			LockData(false, false);
			}
		((DataObj*)parent)->Command(CMD_ETRACC, accept_range ? this : 0L, 0L);
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// output formated text - style and font changes
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef struct _tag_info {
	char *tag;
	int and_style, or_style;
	int font, op;
}tag_info;

#define UC_TAG 100

static tag_info tags[] = {
	{"<i>", ~TXS_NORMAL, TXS_ITALIC, -1, 0},
	{"</i>", ~TXS_ITALIC, TXS_NORMAL, -1, 0},
	{"<b>", ~TXS_NORMAL, TXS_BOLD, -1, 0},
	{"</b>", ~TXS_BOLD, TXS_NORMAL, -1, 0},
	{"<u>", ~TXS_NORMAL, TXS_UNDERLINE, -1, 0},
	{"</u>", ~TXS_UNDERLINE, TXS_NORMAL, -1, 0},
	{"<sup>", ~TXS_NORMAL, TXS_SUPER, -1, 0},
	{"</sup>", ~TXS_SUPER, TXS_NORMAL, -1, 0},
	{"<sub>", ~TXS_NORMAL, TXS_SUB, -1, 0},
	{"</sub>", ~TXS_SUB, TXS_NORMAL, -1, 0},
	{"<face=helvetica>", 0, 0, FONT_HELVETICA, 0},
	{"<face=times>", 0, 0, FONT_TIMES, 0},
	{"<face=courier>", 0, 0, FONT_COURIER, 0},
	{"<face=greek>", 0, 0, FONT_GREEK, 0},
	{"</face>", 0, 0, -2, 0},
	{"<bullet1>", 0, 0, -1, 1},	{"<bullet2>", 0, 0, -1, 2},
	{"<bullet3>", 0, 0, -1, 3},	{"<bullet4>", 0, 0, -1, 4},
	{"<bullet5>", 0, 0, -1, 5},	{"<bullet6>", 0, 0, -1, 6},
	{"<bullet7>", 0, 0, -1, 7},	{"<bullet8>", 0, 0, -1, 8},
	{"<bullet9>", 0, 0, -1, 9},	{"<bullet10>", 0, 0, -1, 10},
	{"<white>", 0, 0, -1, 100},	{"<black>", 0, 0, -1, 101},
	{"<red>", 0, 0, -1, 102},	{"<blue>", 0, 0, -1, 103},
	{"<green>", 0, 0, -1, 104},	{"<yellow>", 0, 0, -1, 105},
	{"<cyan>", 0, 0, -1, 106},	{"<purple>", 0, 0, -1, 107},
	{"<smaller>", 0, 0, -1, 200},	{"<larger>", 0, 0, -1, 201},
	{0L, 0, 0, 0, 0}};

static int font_buff[256];
static unsigned font_idx=0;
fmtText::fmtText()
{
	src=0L;		split_text=0L;		split_text_W = 0;
	n_split = n_split_W = uc_state = 0;
	pos.x = pos.y = 0;				flags =0x0;
}

fmtText::fmtText(anyOutput *o, int x, int y, char *txt)
{
	if(txt && txt[0]) src = (char*)memdup(txt, (int)strlen(txt)+1, 0);
	else src = 0L;		split_text = 0L;	split_text_W = 0L;
	n_split = n_split_W = uc_state = 0;		flags = 0x0;
	pos.x = x;	pos.y = y;		if(src)Parse();
	if(o) DrawText(o);
}

fmtText::~fmtText()
{
	SetText(0L, 0L, 0L, 0L);
}

bool
fmtText::StyleAt(int idx, TextDEF *txt_def, int *style, int *font)
{
	TextDEF td;
	int i, j, n;

	if(!src || !split_text || (idx > (int)strlen(src))) return false;
	memcpy(&td, txt_def, sizeof(TextDEF));
	for(i = j = 0; i < n_split; i++) {
		if((n=split_text[i].tag) >= 0 && n < UC_TAG && SetTextDef(&td, n)) j += (int)strlen(tags[n].tag);
		if(j > idx) break;
		if(split_text[i].txt && split_text[i].txt[0]) j += (int)strlen(split_text[i].txt);
		if(j >= idx) break;
		}
	if(style) *style = td.Style;		if(font) *font = td.Font;
	return true;
}

int
fmtText::rightTag(char *txt, int cb)
{
	int i, j;

	for(i = 0; tags[i].tag; i++) {
		for(j=1; tags[i].tag[j] && txt[cb+j]; j++) if(tags[i].tag[j] != txt[cb+j]) break;
		if(!tags[i].tag[j]) return i;
		}
	return -1;
}

int
fmtText::leftTag(char *txt, int cb)
{
	int i, j, k;

	for(i = 0; tags[i].tag; i++) {
		for(j = 0, k=(int)strlen(tags[i].tag)-1; tags[i].tag[j] && k <=cb; j++, k--) {
			if(tags[i].tag[j] != txt[cb-k]) break;
			}
		if(!tags[i].tag[j]) return i;
		}
	return -1;
}

int 
fmtText::ucTag(char *txt, int cb, int *tl, int *ucc)
{
	int i, uc=0;

	if(txt[cb] != '&' || txt[cb+1] != '#') return -1;
	for(i = cb+2; txt[i] && txt[i] != ';'; i++) {
		if(!isdigit(txt[i])) return -1;
		}
	if(txt[i] != ';') return -1;
	i -= cb;
#ifdef USE_WIN_SECURE
	sscanf_s(txt+cb+2, "%d", &uc);
#else
	sscanf(txt+cb+2, "%d", &uc);
#endif
	if(tl) *tl = i+1;		if(ucc) *ucc = uc;
	return UC_TAG;
}

int
fmtText::ucLeft(char *txt, int cb, int *tl, int *ucc)
{
	int i, uc = 0;

	for(i = 1; i < 6 && i < cb && txt[cb-i] != '#'; i++) {
		if(!isdigit(txt[cb-i])) return -1;
		}
	if(txt[cb-i] != '#' || txt[cb-i-1] != '&') return -1;
#ifdef USE_WIN_SECURE
	sscanf_s(txt+cb-i+1, "%d", &uc);
#else
	sscanf(txt+cb-i+1, "%d", &uc);
#endif
	if(tl) *tl = i+1;			if(ucc) *ucc = uc;
	return UC_TAG;
}

void
fmtText::cur_right(int *pos)
{
	int n, tl;

	if(!src || !pos || *pos >= (int)strlen(src) || !src[*pos]) return;
	if(src[*pos] == '<' && (n=rightTag(src, *pos)) >= 0) {
		*pos += (int)strlen(tags[n].tag);
		cur_right(pos);
		}
	else if(src[*pos] == '&' && (ucTag(src, *pos, &tl, 0L)== UC_TAG)){
		*pos += tl;
		}
	else (*pos)++;
}

void 
fmtText::cur_left(int *pos)
{
	int n, tl;

	if(!src || !pos || !(*pos)) return;
	(*pos)--;
	if(*pos >= (n=(int)strlen(src))){
		*pos = n-1;		return;
		}
	if(src[*pos] == ';' && (n=ucLeft(src, *pos, &tl, 0L)) == UC_TAG) {
		*pos -= tl;		return;
		}
	while (src[*pos] == '>' && (n=leftTag(src, *pos)) >= 0) {
		*pos -= (int)strlen(tags[n].tag);
		}
}

bool
fmtText::oGetTextExtent(anyOutput *o, int *width, int *height, int cb)
{
	TextDEF td1, td2;
	int i, n, l, l1, w, w1, h, h1;

	if(!o || !width || !height) return false;
	if(!src || !src[0]) return false;
	if(!cb) cb = (int)strlen(src);
	if(!split_text) return o->oGetTextExtent(src, cb, width, height);
	memcpy(&td1, &o->TxtSet, sizeof(TextDEF));	memcpy(&td2, &o->TxtSet, sizeof(TextDEF));
	for(i = w = h = l = l1 = 0; i < n_split; i++){
		if(split_text[i].tag == UC_TAG) {
			o->oGetTextExtentW((w_char*)(&split_text[i].uc), 1, &w1, &h1);
			w += w1;		h = h1 > h ? h1 : h;
			l += split_text[i].uc_len;
			if (l >= cb) {
				*width = w;		*height = h;	o->SetTextSpec(&td1);
				return true;
				}
			}
		else {
			if((n=split_text[i].tag) >= 0 && SetTextDef(&td2, n)) {
				o->SetTextSpec(&td2);
				l += (int)strlen(tags[n].tag);
				}
			if(tags[n].font == -1 && tags[n].op > 0 && tags[n].op < 100) {
				w += o->un2ix(td2.fSize/2.0);
				}
			}
		if(split_text[i].txt && split_text[i].txt[0]){
			l1 = l;		l += (int)strlen(split_text[i].txt);
			if (l1 >= cb) break;
			o->oGetTextExtent(split_text[i].txt, l >= cb ? cb-l1 : 0, &w1, &h1);
			w += w1;	h = h1 > h ? h1 : h;
			if (l >= cb) break;
			}
		}
	*width = w;			*height = h;		o->SetTextSpec(&td1);
	return true;
}

void
fmtText::SetText(anyOutput *o, char *txt, int *px, int *py)
{
	int i;

	if(px) pos.x = *px;			if(py) pos.y = *py;
	if(src && txt && !strcmp(src, txt)) {
		if(o) DrawText(o);
		return;
		}
	if(src) free(src);			src = 0L;
	if(split_text) {
		for(i = 0; i < n_split; i++) if(split_text[i].txt) free(split_text[i].txt);
		free(split_text);		split_text = 0L;	n_split = 0;
		}
	if(split_text_W) {
		for(i = 0; i < n_split_W; i++) {
			if(split_text_W[i].uc_txt) free((split_text_W[i].uc_txt));
			}
		free(split_text_W);		split_text_W = 0L;	n_split_W = 0;
		}
	if(txt && txt[0]) src = (char*)memdup(txt, (int)strlen(txt)+1, 0);
	if(src)Parse();
	if(o) DrawText(o);
}

void
fmtText::DrawText(anyOutput *o)
{
	if(!o || !src) return;
	if(split_text)DrawFormatted(o);
	else o->oTextOut(pos.x, pos.y, src, 0);
}

bool
fmtText::SetTextDef(TextDEF *td, int idx)
{
	if(tags[idx].and_style != tags[idx].or_style) {
		td->Style &= tags[idx].and_style;			td->Style |= tags[idx].or_style;
		}
	else if(tags[idx].font >= 0) {
		font_buff[font_idx & 0xff] = td->Font;	font_idx++;
		td->Font = tags[idx].font;
		}
	else if(tags[idx].font == -2) {
		font_idx--;			td->Font=font_buff[font_idx & 0xff];
		}
	else return false;
	return true;
}

bool
fmtText::Parse()
{
	int i, li, j, n, tl, uc;
	char *tmp;

	if((flags & 0x01) || !src || !(tmp = (char*)memdup(src, (int)strlen(src)+1, 0))) return false;
	for(i = li = uc_state = 0; src[i]; i++) {
		if(i-li == 1 && split_text) {
			if(src[li] == '<' && (n=rightTag(src, li))>=0) i--;
			else if(src[li] == '&' && (n=ucTag(src, li, &tl, &uc))>0) i--;
			}
		if(src[i] == '<' && (n=rightTag(src, i))>=0) {
			if(tags[n].font == FONT_GREEK) uc_state |= 0x02;
			if(tags[n].op) uc_state |= 0x04;
			if(split_text) {				//more tags in text
				if(!(split_text = (fmt_txt_info *)realloc(split_text, (n_split+1)*sizeof(fmt_txt_info)))){
					free(tmp);					return false;
					}
				for(j = li; j < i; j++) tmp[j-li] = src[j];	tmp[j-li]=0;
				split_text[n_split-1].txt = (char*)memdup(tmp, (int)strlen((char*)tmp)+1, 0);
				i += (int)strlen(tags[n].tag);	split_text[n_split].tag = n;
				split_text[n_split++].txt = 0L;
				}
			else {							//first tag of text
				if(!(split_text = (fmt_txt_info *)calloc(2, sizeof(fmt_txt_info)))){
					free(tmp);					return false;
					}
				for(j = 0; j < i; j++) tmp[j]= src[j];	tmp[j]=0;
				split_text[0].tag = -1;		
				split_text[0].txt = (char*)memdup(tmp, (int)strlen((char*)tmp)+1, 0);
				i += (int)strlen(tags[n].tag);	split_text[1].tag = n;
				n_split = 2;
				}
			li = i;							i--;
			}
		else if(src[i] == '&' && (n=ucTag(src, i, &tl, &uc))>0) {
			uc_state |= 0x01;
			if(split_text) {				//more tags in text
				if(!(split_text = (fmt_txt_info *)realloc(split_text, (n_split+1)*sizeof(fmt_txt_info)))){
					free(tmp);					return false;
					}
				for(j = li; j < i; j++) tmp[j-li] = src[j];	tmp[j-li]=0;
				split_text[n_split-1].txt = (char*)memdup(tmp, (int)strlen((char*)tmp)+1, 0);
				i += tl;						split_text[n_split].tag = n;
				split_text[n_split].uc = uc;	split_text[n_split].uc_len = tl;
				split_text[n_split++].txt = 0L;
				}
			else {							//first tag of text
				if(!(split_text = (fmt_txt_info *)calloc(2, sizeof(fmt_txt_info)))){
					free(tmp);					return false;
					}
				for(j = 0; j < i; j++) tmp[j]= src[j];	tmp[j]=0;
				split_text[0].tag = -1;		
				split_text[0].txt = (char*)memdup(tmp, (int)strlen((char*)tmp)+1, 0);
				i += tl;					split_text[1].tag = n;
				split_text[1].uc = uc;		n_split = 2;
				split_text[1].uc_len = tl;
				}
			li = i;
			}
		}
	if(split_text && n_split && li < i && src[li]) 
		split_text[n_split-1].txt = (char*)memdup(src+li, (int)strlen((char*)src+li)+1,0);
	free(tmp);
	return true;
}

void
fmtText::DrawBullet(anyOutput *o, int x, int y, int type, double size, DWORD lc, DWORD fc)
{
	int is;
	POINT pts[5];
	static FillDEF fd = {0, 0x00ffffff, 1.0, 0L, 0x00ffffff};
	static LineDEF ld = {defs.GetSize(SIZE_SYM_LINE), 1.0, 0x00000000L, 0x00000000};

	switch (type) {
	case 3:		case 4:
		is = o->un2ix(size/4.1);
		break;
	case 5:		case 6:		case 7:		case 8:
		is = o->un2ix(size/3.7);
		break;
	default:
		is = o->un2ix(size/4.0);
		break;
		}
	fd.color = fc;				ld.color = lc;
	switch(type) {
	case 1:
		o->SetLine(&ld);				o->SetFill(&fd);
		o->oCircle(x-is, y-is, x+is, y+is);
		break;
	case 2:
		fd.color = ld.color;
		o->SetLine(&ld);				o->SetFill(&fd);
		o->oCircle(x-is, y-is, x+is, y+is);
		break;
	case 3:
		o->SetLine(&ld);				o->SetFill(&fd);
		o->oRectangle(x-is, y-is, x+is, y+is);
		break;
	case 4:
		fd.color = ld.color;
		o->SetLine(&ld);				o->SetFill(&fd);
		o->oRectangle(x-is, y-is, x+is, y+is);
		break;
	case 5:		case 6:		case 7:		case 8:		case 9:		case 10:
		if(type == 6 || type == 8 || type == 10) 		fd.color = ld.color;
		pts[0].x = pts[3].x = pts[4].x = x - is;
		pts[1].x = x;		pts[2].x = x+is;
		if(type == 5 || type == 6) {
			pts[0].y = pts[2].y = pts[3].y = y+o->un2iy(size*0.19439);
			pts[1].y = y-o->un2iy(size*0.38878);
			}
		else if(type == 9 || type ==10) {
			pts[0].y = pts[2].y = pts[4].y = y;
			pts[1].y = y - is;		pts[3].y = y + is;		pts[3].x = x;
			}
		else {
			pts[0].y = pts[2].y = pts[3].y = y-o->un2iy(size*0.19439);
			pts[1].y = y+o->un2iy(size*0.38878);
			}
		o->SetLine(&ld);				o->SetFill(&fd);
		o->oPolygon(pts, type < 9 ? 4 : 5);
		break;
		}
}

static int char2uc(char*src, w_char* dest, bool isGreek)
{
	int i;

	if(!src || !*src) return 0;
	for(i = 0; ; i++){
		if(isGreek && src[i] >= 'A' && src[i] <= 'Z') dest[i] = (src[i] - 'A' + 0x391);
		else if(isGreek && src[i] >= 'a' && src[i] <= 'z') dest[i] = (src[i] - 'a' + 0x3B1);
		else dest[i] = (src[i]);
		if(!dest[i]) return i;
		}
}

bool 
fmtText::DrawFormattedW(anyOutput *o)
{
	int i, j, n, cb, x, y, x1, y1, w, h;
	double si, csi, fx, fy;
	TextDEF td1, td2;
	bool bGreek = false;
	
	if(!o || !(split_text_W = (fmt_uc_info *)calloc(n_split, sizeof(fmt_uc_info))))return false;
	memcpy(&td1, &o->TxtSet, sizeof(TextDEF));	memcpy(&td2, &o->TxtSet, sizeof(TextDEF));
	bGreek = (td1.Font == FONT_GREEK);
	for(i = n_split_W = 0; i < n_split; i++){
		if(split_text[i].tag == UC_TAG) {
			if(i) {
				j = n_split_W ? n_split_W-1 : 0;
				cb = split_text[i].txt ? (int)strlen(split_text[i].txt) : 0;
				if(split_text_W[j].uc_txt = (w_char*)realloc(split_text_W[j].uc_txt, 
					(10 + cb + split_text_W[j].cb) * sizeof(w_char))) {
					split_text_W[j].uc_txt[split_text_W[j].cb++] = split_text[i].uc;
					if(cb) split_text_W[j].cb += char2uc(split_text[i].txt, split_text_W[j].uc_txt+split_text_W[j].cb, bGreek);
					split_text_W[j].uc_txt[split_text_W[j].cb] = 0;
					}
				}
			else {
				split_text_W[0].cb = 1;				split_text_W[0].tag = -1;
				if(split_text_W[0].uc_txt = (w_char*)malloc(2*sizeof(w_char))) {
					split_text_W[0].uc_txt[0] = split_text[0].uc;
					split_text_W[0].uc_txt[0] = 0;
					}
				}
			}
		else if(split_text[i].tag >= 0 && tags[split_text[i].tag].font == FONT_GREEK) {
			if(!i) return false;	bGreek = true;
			j = n_split_W-1;		cb = split_text[i].txt ? (int)strlen(split_text[i].txt) : 0;
			if(split_text_W[j].uc_txt = (w_char*)realloc(split_text_W[j].uc_txt, 
				(2 + cb + split_text_W[j].cb) * sizeof(w_char))) {
				if(cb) split_text_W[j].cb += char2uc(split_text[i].txt, split_text_W->uc_txt+split_text_W[j].cb, bGreek);
				}
			}
		else if(bGreek && split_text[i].tag >= 0 && tags[split_text[i].tag].font == -2){
			if(!i) return false;	bGreek = false;
			j = n_split_W-1;		cb = split_text[i].txt ? (int)strlen(split_text[i].txt) : 0;
			if(split_text_W[j].uc_txt = (w_char*)realloc(split_text_W[j].uc_txt, 
				(2 + cb + split_text_W[j].cb) * sizeof(w_char))) {
				if(cb) split_text_W[j].cb += char2uc(split_text[i].txt, split_text_W->uc_txt+split_text_W[j].cb, bGreek);
				}
			}
		else {
			if((n=split_text[i].tag) >= 0) SetTextDef(&td2, n);
			bGreek = (td2.Font == FONT_GREEK);
			split_text_W[n_split_W].tag = split_text[i].tag;
			split_text_W[n_split_W].cb = split_text[i].txt ? (int)strlen(split_text[i].txt) : 0;
			if(split_text_W[n_split_W].cb && (split_text_W[n_split_W].uc_txt = 
				(w_char*)malloc((1 + split_text_W[n_split_W].cb) * sizeof(w_char)))) {
				char2uc(split_text[i].txt, split_text_W[n_split_W].uc_txt, bGreek);
				}
			else split_text_W[n_split_W].uc_txt = 0L;
			n_split_W++;
			}
		}
	memcpy(&td2, &td1, sizeof(TextDEF));
	si = sin(td1.RotBL *0.01745329252);	csi = cos(td1.RotBL *0.01745329252);
	fx = pos.x;		fy = pos.y;	
	oGetTextExtent(o, &w, &h, 0);
	if(td2.Align & TXA_HRIGHT) {
		fx -= w*csi;		fy += w*si;
		}
	else if(td2.Align & TXA_HCENTER){
		fx -= (w>>1)*csi;	fy += (w>>1)*si;
		}
	x = iround(fx);			y = iround(fy);
	td2.Align &= ~(TXA_HRIGHT | TXA_HCENTER);			o->SetTextSpec(&td2);
	for(i = 0; i < n_split_W; i++) {
		if((n=split_text_W[i].tag) >= 0 && SetTextDef(&td2, n)) o->SetTextSpec(&td2);
		else if(n >= 0 && tags[n].op) {
			x1 = x + iround(o->un2fix(td2.fSize*0.25)*csi);
			y1 = y - iround(o->un2fiy(td2.fSize*0.25)*si);
			if((td2.Align & TXA_VTOP) == TXA_VTOP){
				y1 += iround(o->un2fiy(td2.fSize*0.5)*csi);
				x1 += iround(o->un2fix(td2.fSize*0.5)*si);
				}
			if((td2.Align & TXA_VCENTER) == TXA_VCENTER){
				y1 -= iround(o->un2fiy(td2.fSize*0.5)*csi);
				x1 -= iround(o->un2fix(td2.fSize*0.5)*si);
				}
			if((td2.Align & TXA_VBOTTOM) == TXA_VBOTTOM){
				y1 -= iround(o->un2fiy(td2.fSize)*csi);
				x1 -= iround(o->un2fix(td2.fSize)*si);
				}
			switch (tags[n].op) {
			case 1:	case 2: case 3:	case 4: case 5:	case 6:	case 7:	case 8:	case 9:	case 10:
				DrawBullet(o, x1, y1, tags[n].op, td2.fSize, td2.ColTxt, 0x00ffffff);
				w = o->un2ix(td2.fSize/2.0);
				x = iround(fx += (w*csi));		y = iround(fy -= (w*si));
				break;
			case 100:	td2.ColTxt = 0x00ffffffL;	break;
			case 101:	td2.ColTxt = 0x00000000L;	break;
			case 102:	td2.ColTxt = 0x000000ffL;	break;
			case 103:	td2.ColTxt = 0x00ff0000L;	break;
			case 104:	td2.ColTxt = 0x0000ff00L;	break;
			case 105:	td2.ColTxt = 0x0000ffffL;	break;
			case 106:	td2.ColTxt = 0x00ffff00L;	break;
			case 107:	td2.ColTxt = 0x00ff00ffL;	break;
			case 200:	td2.fSize *= 0.81;	td2.iSize = 0;	break;
			case 201:	td2.fSize /= 0.81;	td2.iSize = 0;	break;
				}
			o->SetTextSpec(&td2);
			}
		if(split_text_W[i].uc_txt && split_text_W[i].uc_txt[0]){
			o->oTextOutW(x, y, split_text_W[i].uc_txt, 0);
			o->oGetTextExtentW(split_text_W[i].uc_txt, 0, &w, &h);
			x = iround(fx += (w*csi));		y = iround(fy -= (w*si));
			}
		}
	return true;
}

void 
fmtText::DrawFormatted(anyOutput *o)
{
	int i, n, x, y, w, h;
	TextDEF td1, td2;
	double si, csi, fx, fy;

	if(!o || !split_text) return;
	if(uc_state && DrawFormattedW(o)) return;
	memcpy(&td1, &o->TxtSet, sizeof(TextDEF));	memcpy(&td2, &o->TxtSet, sizeof(TextDEF));
	si = sin(td1.RotBL *0.01745329252);	csi = cos(td1.RotBL *0.01745329252);
	fx = pos.x;		fy = pos.y;	
	oGetTextExtent(o, &w, &h, 0);
	if(td2.Align & TXA_HRIGHT) {
		fx -= w*csi;		fy += w*si;
		}
	else if(td2.Align & TXA_HCENTER){
		fx -= (w>>1)*csi;	fy += (w>>1)*si;
		}
	x = iround(fx);			y = iround(fy);
	td2.Align &= ~(TXA_HRIGHT | TXA_HCENTER);			o->SetTextSpec(&td2);
	for(i = 0; i < n_split; i++) if(split_text[i].txt || split_text[i].tag == UC_TAG) {
		if((n=split_text[i].tag) >= 0 && SetTextDef(&td2, n)) o->SetTextSpec(&td2);
		if(split_text[i].txt && split_text[i].txt[0]){
			o->oTextOut(x, y, split_text[i].txt, 0);
			o->oGetTextExtent(split_text[i].txt, 0, &w, &h);
			x = iround(fx += (w*csi));		y = iround(fy -= (w*si));
			}
		}
}

void
fmtText::EditMode(bool bEdit)
{
	if(bEdit) flags |= 0x01;
	else flags &= ~(0x01);
}
#undef UC_TAG

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// assign a value to a string
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TextValue::TextValue()
{
	items = 0L;		nitems = 0;		next = inc = 1.0;
}

TextValue::TextValue(TextValItem **tvi, int ntvi)
{
	int i;

	items = 0L;		nitems = 0;		next = inc = 1.0;
	if(ntvi && (items = (TextValItem **)malloc(ntvi*sizeof(TextValItem*)))){
		for(i = 0, nitems = ntvi; i < ntvi; i++) {
			if(items[i] = (TextValItem*)memdup(tvi[i], sizeof(TextValItem), 0)){
				if(tvi[i]->text) items[i]->text = (char*)memdup(tvi[i]->text, (int)strlen(tvi[i]->text)+1, 0);
				}
			}
		nitems = ntvi;	if(items[nitems-1]) next = items[nitems-1]->val + inc;
		}
}

TextValue::~TextValue()
{
	Reset();
}

double
TextValue::GetValue(char *txt)
{
	unsigned int hv1, hv2;
	int i;

	if(!txt || !txt[0]) return 0.0;
	hv1 = HashValue((unsigned char*)txt);
	hv2 = Hash2((unsigned char*)txt);
	if(items && nitems) for(i = 0; i < nitems; i++) {
		if(items[i] && items[i]->hv1 == hv1 && items[i]->hv2 == hv2) return items[i]->val;
		}
	else if(!items &&(items = (TextValItem **)malloc(sizeof(TextValItem*)))) {
		if(items[0] = (TextValItem*)calloc(1, sizeof(TextValItem))) {
			items[0]->hv1 = hv1;		items[0]->hv2 = hv2;
			items[0]->text = (char*)memdup(txt, (int)strlen(txt)+1, 0);
			items[0]->val = next;		next += inc;
			nitems = 1;					return (next-inc);
			}
		return 0.0;
		}
	else return 0.0;
	if(items = (TextValItem **)realloc(items, (nitems+1)*sizeof(TextValItem*))){
		if(items[nitems] = (TextValItem*)calloc(1, sizeof(TextValItem))) {
			items[nitems]->hv1 = hv1;		items[nitems]->hv2 = hv2;
			items[nitems]->text = (char*)memdup(txt, (int)strlen(txt)+1, 0);
			items[nitems]->val = next;		next += inc;	nitems++;
			return (next-inc);
			}
		}
	return 0.0;
}

bool
TextValue::GetItem(int idx, char **text, double *value)
{
	if(items && idx >=0 && idx < nitems) {
		if(text) *text = items[idx]->text;
		if(value) *value = items[idx]->val;
		return true;
		}
	return false;
}

void
TextValue::Reset()
{
	int i;

	if(items) for(i = 0; i < nitems; i++){
		if(items[i]->text) free(items[i]->text);
		free(items[i]);
		}
	if(items && nitems) free(items);
	next = inc = 1.0;	items = 0L;		nitems = 0;
}

TextValue *
TextValue::Copy()
{
	return new TextValue(items, nitems);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// manage formats and style of a spreadsheet range
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
RangeInfo::RangeInfo(int sel, int cw, int rh, int fw, RangeInfo *next)
{
	r_type = sel;		col_width = cw;		row_height = rh;	first_width = fw;
	ri_next = next;		ar = 0L;
}

RangeInfo::RangeInfo(int sel, char *range, RangeInfo *next)
{
	r_type = sel;		ri_next = next;
	if(range && range[0]) {
		ar = new AccRange(range);
		}
	else ar = 0L;
}

RangeInfo::~RangeInfo()
{
	if(ar) delete ar;
}

int
RangeInfo::SetWidth(int w)
{
	if(r_type == 0 || r_type == 1) return col_width = w;
	else if (ri_next) return ri_next->SetWidth(w);
	return w;
}

int
RangeInfo::SetDefWidth(int w)
{
	if(r_type == 0) return col_width = w;
	else if (ri_next) return ri_next->SetDefWidth(w);
	return w;
}

int
RangeInfo::SetHeight(int h)
{
	if(r_type == 0) return row_height = h;
	else if (ri_next) return ri_next->SetHeight(h);
	return h;
}

int
RangeInfo::SetFirstWidth(int w)
{
	if(r_type == 0) return first_width = w;
	else if (ri_next) return ri_next->SetFirstWidth(w);
	return w;
}

int
RangeInfo::GetWidth(int col)
{
	int r, c;

	if(r_type == 0) return col_width;
	else if (ar && r_type == 1){
		ar->GetFirst(&c, &r);
		while(ar->NextCol(&c)) {
			if(c == col) return col_width;
			}
		}
	if(ri_next) return ri_next->GetWidth(col);
	return 0;
}

int
RangeInfo::GetHeight(int row)
{
	if(r_type == 0) return row_height;
	else if (ri_next) return ri_next->GetHeight(row);
	return row_height;
}

int
RangeInfo::GetFirstWidth()
{
	if(r_type == 0) return first_width;
	else if (ri_next) return ri_next->GetFirstWidth();
	return first_width;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// the basic data object
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DataObj::DataObj()
{
	cRows = cCols = 0;
	etRows = 0L;
	ri = new RangeInfo(0, 76, 19, 32, 0L);
}

DataObj::~DataObj()
{
	FlushData();
	delete ri;
}

bool
DataObj::Init(int nR, int nC)
{
	int i, j;

	if(etRows)FlushData();
	if(!(etRows = (EditText ***)calloc (cRows = nR, sizeof(EditText **)))) return false;
	for(i = 0, cCols = nC; i < cRows; i++) {
		if(!(etRows[i] = (EditText **)calloc(cCols, sizeof(EditText *)))) {
			FlushData();	return false;
			}
		if(etRows[i]) for(j = 0; j < cCols; j++) {
			etRows[i][j] = new EditText(this, 0L, i, j);
			}
		}
	return true;
}

bool
DataObj::SetValue(int row, int col, double val)
{
	if(row < 0 || row >= cRows || col < 0 || col >= cCols) return false;
	if(etRows[row][col]) return etRows[row][col]->SetValue(val);
	return false;
}

bool
DataObj::SetText(int row, int col, char *txt)
{
	if(row < 0 || row >= cRows || col < 0 || col >= cCols) return false;
	if(etRows[row][col]) return etRows[row][col]->SetText(txt);
	return false;
}

bool
DataObj::GetValue(int row, int col, double *v)
{
	if(row < 0 || row >= cRows || col < 0 || col >= cCols) return false;
	if(etRows[row][col]) return etRows[row][col]->GetValue(v);
	return false;
}

bool
DataObj::GetText(int row, int col, char *txt, int len, bool bTranslate)
{
	if(row < 0 || row >= cRows || col < 0 || col >= cCols) return false;
	if(txt && etRows[row][col]) return etRows[row][col]->GetText(txt, len, bTranslate);
	return false;
}

char ** 
DataObj::GetTextPtr(int row, int col)
{
	if(row < 0 || row >= cRows || col < 0 || col >= cCols) return 0L;
	if(etRows[row][col]) return &etRows[row][col]->text;
	return 0L;
}

bool
DataObj::GetResult(anyResult *r, int row, int col, bool use_last)
{
	if(row < 0 || row >= cRows || col < 0 || col >= cCols) return false;
	if(etRows[row][col]) return etRows[row][col]->GetResult(r, use_last);
	return false;
}

bool
DataObj::GetSize(int *width, int *height)
{
	if(width)*width = cCols;		if(height)*height = cRows;
	return true;
}

bool
DataObj::isEmpty(int row, int col)
{
	if(row < 0 || row >= cRows || col < 0 || col >= cCols) return false;
	if(etRows[row][col]) {
		if((etRows[row][col]->type & 0xff) == ET_UNKNOWN)etRows[row][col]->Update(20, 0L, 0L);
		if((etRows[row][col]->type & ET_EMPTY) == ET_EMPTY) return true;
		}
	return false;
}

void
DataObj::FlushData()
{
	int i, j;

	if(etRows){
		for(i = 0; i < cRows; i++) if(etRows[i]) {
			for (j = 0; j< cCols; j++) if(etRows[i][j]) delete etRows[i][j];
			free(etRows[i]);
			}
		free(etRows);
		}
	etRows = 0L;
}

bool
DataObj::ValueRec(RECT *rc)
{
	int r, c;
	double val;

	if(etRows && rc){
		rc->left = cCols;	rc->right = 0;
		rc->bottom = 0;		rc->top = cRows;
		for(r = 0; r < cRows; r++) if(etRows[r]) {
			for (c = 0; c< cCols; c++) {
				if(etRows[r][c] && etRows[r][c]->GetValue(&val)) {
					if(c < rc->left) rc->left =  c;
					if(c > rc->right) rc->right =  c;
					else c = rc->right;
					if(r > rc->bottom) rc->bottom =  r;
					else c = rc->right;
					if(r < rc->top) rc->top =  r;
					}
				}
			}
		if(rc->right < rc->left) rc->right = rc->left < cCols ? rc->left : rc->left = 0;
		if(rc->bottom < rc->top) rc->bottom = rc->top < cRows ? rc->top : rc->top = 0;
		if(!rc->bottom && !rc->top && !rc->right && !rc->left) {
			rc->right = cCols-1;	rc->bottom = cRows-1;
			}
		return true;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Store Data Object as strings: less memory required than with DataObj
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
StrData::StrData(DataObj *par, RECT *rc)
{
	int r1, c1, r2, c2, w, h;
	char **tx;

	pw = ph = 0;		str_data = 0L;
	drc.left = drc.right = drc.top = drc.bottom = 0;
	if(!(src = par)) return;
	src->GetSize(&pw, & ph);
	if(rc) {
		if(0>(h = (rc->bottom - rc->top)) || 0>(w = (rc->right - rc->left))) return;
		if(!(str_data = (char***)calloc(h+1, sizeof(char**))))return;
		drc.left = rc->left;				drc.right = rc->right;
		drc.top = rc->top;					drc.bottom = rc->bottom;
		for (r1 = 0, r2 = drc.top; r1 <= h; r1++, r2++) {
			if(!(str_data[r1] = (char**)malloc((w+1) * sizeof(char*)))) break;
			for(c1 = 0, c2= drc.left; c1 <= w; c1++, c2++) {
				tx = src->GetTextPtr(r2, c2);
				if(tx && *tx && *tx[0]) {
					str_data[r1][c1] = (char*)memdup(*tx, (int)strlen(*tx)+1, 0);
					}
				else str_data[r1][c1] = 0L;
				}
			}
		}
	else {
		if(!(str_data = (char***)calloc(ph, sizeof(char**))))return;
		for (r1 = 0; r1 < ph; r1++) {
			if(!(str_data[r1] = (char**)malloc(pw * sizeof(char*)))) break;
			for(c1 = 0; c1 < pw; c1++) {
				tx = src->GetTextPtr(r1, c1);
				if(tx && *tx && *tx[0]) {
					str_data[r1][c1] = (char*)memdup(*tx, (int)strlen(*tx)+1, 0);
					}
				else str_data[r1][c1] = 0L;
				}
			}
		drc.right = pw-1;		drc.bottom = ph-1;
		}
}

StrData::~StrData()
{
	int r, c, w, h;

	w = drc.right-drc.left;			h = drc.bottom-drc.top;
	if(str_data) for (r = 0; r <= h; r++) {
		if(str_data[r]) {
			for(c = 0; c <= w; c++) if(str_data[r][c]) free(str_data[r][c]);
			free(str_data[r]);
			}
		}
	if(str_data) free(str_data);
}

bool
StrData::GetSize(int *uw, int *uh)
{
	if(uw) *uw = pw;			if(uh) *uh = ph;
	return true;
}

void
StrData::RestoreData(DataObj *dest)
{
	int r1, c1, r2, c2;

	if(!dest || !str_data) return;
	for (r1 = 0, r2 = drc.top; r2 <= drc.bottom; r1++, r2++) {
		if(str_data[r1]) {
			for(c1 = 0, c2 = drc.left; c2 <= drc.right; c1++, c2++) 
				dest->SetText(r2, c2, str_data[r1][c1]);
			}
		}
	return;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The notary class handles different types of supervision and indexing
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
notary::notary()
{
	gObs = 0L;
	goStack = 0L;
	NextPopGO = NextPushGO = NextRegGO = 0L;
}

notary::~notary()
{
	FreeStack();
}

int
notary::RegisterGO(GraphObj *go)
{
	int i, j;

	if(!go) return 0L;
	if(!gObs) {
		gObs = (GraphObj ***)calloc(0x2000L, sizeof(GraphObj **));
		gObs[0] = (GraphObj **)calloc(0x2000L, sizeof(GraphObj *));
		if(gObs && gObs[0]) {
			gObs[0][0] = go;
			return 1L;
			}
		return 0L;
		}
	i = (int)(NextRegGO >> 13);
	j = (int)(NextRegGO & 0x1fff)+1;
	if(j >=0x2000){
		i++;	j = 0;
		}
	if(gObs[i] && gObs[i][j] && gObs[i][j] == go) {
		NextRegGO = ((i << 13) | j);
		return i*0x2000+j+1;
		}
	if(gObs && gObs[0]) {
		for(i = 0; i < 0x2000; i++) {
			for(j = 0; j < 0x2000L; j++) {
				if(gObs[i][j] == go) {
					NextRegGO = ((i << 13) | j);
					return i*0x2000+j+1;
					}
				if(!gObs[i][j]) {
					gObs[i][j] = go;
					NextRegGO = ((i << 13) | j);
					return i*0x2000+j+1;
					}
				}
			if(i < 0x1fffL && !gObs[i+1])
				gObs[i+1] = (GraphObj **)calloc(0x2000L, sizeof(GraphObj *));
			if(i < 0x1fff && !gObs[i+1]) return 0;
			}
		}
	return 0;
}

void
notary::AddRegGO(GraphObj *go)
{
	int i, j;

	if(!go) return;
	if(!gObs) {
		gObs = (GraphObj ***)calloc(0x2000L, sizeof(GraphObj **));
		gObs[0] = (GraphObj **)calloc(0x2000L, sizeof(GraphObj *));
		if(gObs && gObs[0]) {
			gObs[0][0] = go;
			return;
			}
		return;
		}
	i = (int)(NextRegGO >> 13);
	j = (int)(NextRegGO & 0x1fff)+1;
	if(j >=0x2000){
		i++;
		j = 0;
		}
	if(!gObs[i]) gObs[i] = (GraphObj **)calloc(0x2000L, sizeof(GraphObj *));
	if(gObs[i] && !gObs[i][j]) {
		gObs[i][j] = go;
		NextRegGO = ((i << 13) | j);
		}
	else RegisterGO(go);
}

bool
notary::PushGO(unsigned int id, GraphObj *go)
{
	int i, j;

	NextPopGO = 0L;
	if(!go) return true;
	go->Id = id;
	if(!goStack) {
		if(!(goStack = (GraphObj ***)calloc(8192, sizeof(GraphObj **))))return false;
		goStack[0] = (GraphObj **)calloc(8192, sizeof(GraphObj *));
		if(goStack && goStack[0]) {
			goStack[0][0] = go;
			return true;
			}
		return false;
		}
	i = (int)(NextPushGO >> 13);
	j = (int)(NextPushGO & 0x1fff)+1;
	if(j >=0x2000){
		i++;
		j = 0;
		}
	if(!goStack || !goStack[0]) return false;
	if(goStack[i] && !goStack[i][j]) {
		goStack[i][j] = go;
		NextPushGO = ((i << 13) | j);
		return true;
		}
	for(i = 0; i < 0x2000; i++) {
		for(j = 0; j < 0x2000; j++) {
			if(!goStack[i][j]) {
				goStack[i][j] = go;
				NextPushGO = ((i << 13) | j);
				return true;
				}
			}
		if(i < 0x1fff && !goStack[i+1] && !(goStack[i+1] =
			(GraphObj **)calloc(0x2000, sizeof(GraphObj *)))) return false;
		}
	return false;
}

GraphObj *
notary::PopGO(unsigned int id)
{
	int i, j;
	GraphObj *go;

	NextPushGO = 0L;
	if(!id || !goStack || !goStack[0]) return 0L;
	i = (int)(NextPopGO >> 13);
	j = (int)(NextPopGO & 0x1fff)+1;
	if(j >=0x2000){
		i++;
		j = 0;
		}
	if(goStack[i] && goStack[i][j] && goStack[i][j]->Id == id) {
		go = goStack[i][j];
		goStack[i][j] = 0L;
		go->Id = 0L;
		NextPopGO = ((i << 13) | j);
		return go;
		}
	for(i = 0; i < 0x2000; i++) {
		for(j = 0; j < 0x2000; j++) {
			if(goStack[i][j] && goStack[i][j]->Id == id) {
				go = goStack[i][j];
				goStack[i][j] = 0L;
				go->Id = 0L;
				NextPopGO = ((i << 13) | j);
				return go;
				}
			}
		if(i < 0x1fff && !goStack[i+1]) return 0L;
		}
	return 0L;
}

void
notary::FreeStack()
{
	int i, j, k;

	if(gObs) {
		for(i = 0; gObs[i] && i <8192; i++) free(gObs[i]);
		free(gObs);
		gObs = 0L;
		}
	if(goStack) {
		for(i = k = 0; goStack[i] && i <8192; i++){
			for(j = 0; j < 8192; j++){
				if(goStack[i][j]) {
					goStack[i][j]->Id = 0L;
					DeleteGO(goStack[i][j]);
					k++;
					}
				}
			free(goStack[i]);
			}
		free(goStack);
		if(k){
#ifdef USE_WIN_SECURE
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "%d objects deleted\nby notary", k);
#else
			sprintf(TmpTxt,"%d objects deleted\nby notary", k);
#endif
			ErrorBox(TmpTxt);
			}
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Calculate continuous index to a range given by an ASCII string
// string examples include  "a1:a12"  or  "a1:a4;a12:a24"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AccRange::AccRange(char *asc)
{
	int i, j, l;

	if(asc && *asc && (l=(int)strlen(asc)) >1){
		txt = (char *)malloc(l+2);
		for(i = j = 0; i< (int)strlen(asc); i++)
			if(asc[i] > 32) txt[j++] = asc[i];
		txt[j] = 0;
		}
	else txt = 0L;
	x1 = y1 = x2 = y2 = 0;
}

AccRange::~AccRange()
{
	if(txt) free(txt);
}

int
AccRange::CountItems()
{
	int RetVal, l;

	RetVal = 0;
	if(txt && txt[0] && Reset()){
		l = (int)strlen(txt);
		do {
			RetVal += ((x2-x1+1)*(y2-y1+1));
			} while((curridx < l) && Parse(curridx));
		}
	return RetVal;
}

bool
AccRange::GetFirst(int *x, int *y)
{
	if(txt && Reset()) {
		if(x && y) {*x = x1; *y = y1;}
		return true;
		}
	return false;
}

bool
AccRange::GetNext(int *x, int *y)
{
	if(txt && x && y) {
		if(cx <= x2) {*x = cx; *y = cy; cx++; return true;}
		else {
			cx = x1; cy++;
			if(cy <= y2) return GetNext(x, y);
			else if(txt[curridx]){
				if(Parse(curridx)) return GetNext(x, y);
				return false;
				}
			}
		}
	return false;
}

bool
AccRange::NextRow(int *y)
{
	if(cy <= y2) {
		*y = cy;	cy++;	cx = x1;	return true;
		}
	else if(txt[curridx] && Parse(curridx)) return NextRow(y);
	return false;
}

bool
AccRange::NextCol(int *x)
{
	if(cx <= x2) {
		*x = cx;	cx++;	return true;
		}
	else if(txt[curridx] && Parse(curridx)) return NextCol(x);
	return false;
}

bool
AccRange::IsInRange(int x, int y)
{
	if(txt && Reset())	do {
		if(x >= x1 && x <= x2 && y >= y1 && y <= y2) return true;
		} while((curridx < (int)strlen(txt)) && Parse(curridx));
	return false;
}

bool
AccRange::BoundRec(RECT *rec)
{
	if(txt && Reset()){
		SetMinMaxRect(rec, x1, y1, x2, y2);
		while((curridx < (int)strlen(txt)) && Parse(curridx)) {
			UpdateMinMaxRect(rec, x1, y1);	UpdateMinMaxRect(rec, x2, y2);
			}
		Reset();
		return true;
		}
	return false;
}

bool
AccRange::Reset()
{
	curridx = 0;
	return Parse(curridx);
}

bool
AccRange::Parse(int start)
{
	int i, l, step, *v;

	i = start;
	if(!txt) return false;
	while(txt[i] == ';' || txt[i] == ',') i++;
	if(!txt[i]) return false;
	step = x1 = y1 = x2 = y2 = 0;
	v = &x1;
	for (l=(int)strlen(txt)+1 ; i < l; i++) {
		if(txt[i] == '$') i++;
		switch(step) {
		case 0:
		case 2:
			if((txt[i] >= 'a') && (txt[i] <= 'z')){
				*v *= 26;
				*v += (txt[i]-'a'+1);
				}
			else if((txt[i] >= 'A') && (txt[i] <= 'Z')){
				*v *= 26;
				*v += (txt[i]-'A'+1);
				}
			else if((txt[i] >= '0') && (txt[i] <= '9')){
				v = step == 0 ? &y1 : &y2;
				*v = txt[i]-'0';
				step++;
				}
			else return false;
			break;
		case 1:
		case 3:
			if((txt[i] >= '0') && (txt[i] <= '9')){
				*v *= 10;
				*v += (txt[i]-'0');
				}
			else if((txt[i] >= 'a') && (txt[i] <= 'z') ||
				(txt[i] >= 'A') && (txt[i] <= 'Z')){
				if(step == 1) v =  &x2;
				else return false;
				*v = txt[i] >='a' && txt[i] <= 'z' ? 
					txt[i]-'a' : txt[i]-'A';
				step++;
				}
			else if(step == 1 && (txt[i] == ':')) {
				v = &x2;
				step++;
				}
			else if((txt[i] == ';') || (txt[i] == ',') || (txt[i] == 0)) {
				if(step == 1) {		//one single cell selected
					x2 = x1;	y2 = y1;
					}
				if(x2<x1) Swap(x1,x2);		if(y2<y1) Swap(y1,y2);
				if(y1 >=0) y1--;			if(y2 >=0) y2--;
				if(x1 >=0) x1--;			if(x2 >=0) x2--;
				curridx = i;
				cx = x1; cy = y1;
				return true;
				}
			break;
			}
		}
	return false;
}

//get a description for the current range from the data object
char *
AccRange::RangeDesc(void *d, int style)
{
	anyResult res, res1;
	int cb;
	char *desc;

	if(!d || !txt || !Reset())return 0L;
	if(!((DataObj*)d)->GetResult(&res, y1, x1, false))return 0L;
	if(style == 3) {
		if(x1 == x2 && y1 > 0 && res.type == ET_TEXT) {
			if(((DataObj*)d)->GetResult(&res1, 0, x1, false) && res1.type == ET_TEXT)
				return (char*)memdup(res1.text, (int)strlen(res1.text)+1, 0);
			}
		if(y1 == y2 && x1 > 0 && res.type == ET_TEXT) {
			if(((DataObj*)d)->GetResult(&res1, y1, 0, false) && res1.type == ET_TEXT)
				return (char*)memdup(res1.text, (int)strlen(res1.text)+1, 0);
			}
		}
	switch(res.type) {
	case ET_TEXT:
		if(res.text && res.text[0])
			return (char*)memdup(res.text, (int)strlen(res.text)+1, 0);
		else return 0L;
	case ET_VALUE:
		if(style != 4) break;
	case ET_DATE:	case ET_DATETIME:	case ET_TIME:
		TranslateResult(&res);
		if(res.text && res.text[0])
			return (char*)memdup(res.text, (int)strlen(res.text)+1, 0);
		else return 0L;
		}
	if(!(desc = (char*)malloc(40*sizeof(char))))return 0L;
	if(x1 == x2) {
		if(style == 1 || style == 3) cb = rlp_strcpy(desc, 40, "Col. ");
		else if(style == 2) cb = rlp_strcpy(desc, 40, "Column ");
		else goto rdesc_err;
		rlp_strcpy(desc+cb, 40-cb, Int2ColLabel(x1, true));
		return desc;
		}
	else if(y1 == y2) {
#ifdef USE_WIN_SECURE
		sprintf_s(desc, 40, "Row %d", y1+1);
#else
		sprintf(desc, "Row %d", y1+1);
#endif
		return desc;
		}
rdesc_err:
	free(desc);
	return 0L;
}

int 
AccRange::DataTypes(void *d, int *numerical, int *strings, int *datetime)
{
	anyResult res;
	int n, r, c, c_num, c_txt, c_dattim;

	if(!d || !Reset()) return 0;
	for(n = c_num = c_txt = c_dattim = 0, GetFirst(&c, &r); GetNext(&c, &r); n++) {
		if(((DataObj*)d)->GetResult(&res, r, c, false)) {
			switch(res.type) {
			case ET_VALUE:		c_num++;		break;
			case ET_TEXT:		c_txt++;		break;
			case ET_DATE:		case ET_TIME:	case ET_DATETIME:		
				c_dattim++;		break;
				}
			}
		}
	if(numerical) *numerical = c_num;
	if(strings) *strings = c_txt;
	if(datetime) *datetime = c_dattim;
	return n;
}

//---------------------------------------------------------------------------
// Use the Delauney triangulation to create a 3D mesh of dispersed data
//---------------------------------------------------------------------------
Triangle::Triangle()
{
	bSorted = false;
}

void
Triangle::SetRect()
{
	int i, i2;
	double dy1, dy2, dx, dy;
	double m1, m2, mx1, mx2, my1, my2;

	//setup bounding rectangle
	rc.Xmin = rc.Xmax = pt[0].fx;	rc.Ymin = rc.Ymax = pt[0].fy;
	for(i = 1; i < 3; i++) {
		if(pt[i].fx < rc.Xmin) rc.Xmin = pt[i].fx;
		if(pt[i].fx > rc.Xmax) rc.Xmax = pt[i].fx;
		if(pt[i].fy < rc.Ymin) rc.Ymin = pt[i].fy;
		if(pt[i].fy > rc.Ymax) rc.Ymax = pt[i].fy;
		}
	//get three line equations in 2D
	for(i = 0; i < 3; i++) {
		i2 = (i+1)%3;
		ld[i].fx = pt[i].fy;
		if(pt[i].fx != pt[i2].fx) {
			ld[i].fy = (pt[i2].fy - pt[i].fy) / (pt[i2].fx - pt[i].fx);
			}
		else ld[i].fy = HUGE_VAL;
		}
	//close polygon
	pt[3].fx = pt[0].fx;	pt[3].fy = pt[0].fy;	pt[3].fz = pt[0].fz;
	//circumcricle
	dy1 = fabs(pt[0].fy - pt[1].fy);			dy2 = fabs(pt[1].fy - pt[2].fy);
	m1 = (pt[0].fx - pt[1].fx)/(pt[1].fy - pt[0].fy);
	m2 = (pt[1].fx - pt[2].fx)/(pt[2].fy - pt[1].fy);
	mx1 = (pt[0].fx + pt[1].fx)/2.0;			my1 = (pt[0].fy + pt[1].fy)/2.0;
	mx2 = (pt[1].fx + pt[2].fx)/2.0;			my2 = (pt[1].fy + pt[2].fy)/2.0;
	if(dy1 < 1.0e-16 && dy2 < 1.0e-16) {
		cy = (pt[0].fy + pt[1].fy + pt[2].fy)/3.0;
		cx = (pt[0].fx + pt[1].fx + pt[2].fx)/3.0;
		r2 = 0.0;			return;
		}
	else if(dy1 < 1.0e-16) {
		cx = (pt[0].fx + pt[1].fx)/2.0;			cy = m2 * (cx - mx2) + my2;
		}
	else if(dy2 < 1.0e-16) {
		cx = (pt[2].fx + pt[1].fx)/2.0;			cy = m1 * (cx - mx1) + my1;
		}
	else {
		cx = (m1*mx1-m2*mx2+my2-my1)/(m1-m2);	cy = m1*(cx - mx1) + my1;
		}
	dx = pt[1].fx - cx;	dy = pt[1].fy - cy;		r2 = dx * dx + dy * dy;
}

bool
Triangle::TestVertex(double x, double y)
{
	double dx, dy;

	dx = x-cx;		dx = dx * dx;		dy = y-cy;		dy = dy * dy;
	return (dx+dy)<r2;
}

bool
Triangle::Sort()
{
	if(pt[0].fz <= pt[1].fz) {
		if(pt[1].fz <= pt[2].fz) {
			order[0] = 0;	order[1] = 1;	order[2] = 2;
			}
		else if(pt[0].fz <= pt[2].fz) {
			order[0] = 0;	order[1] = 2;	order[2] = 1;
			}
		else {
			order[0] = 1;	order[1] = 2;	order[2] = 0;
			}
		}
	else {
		if(pt[1].fz >= pt[2].fz) {
			order[0] = 2;	order[1] = 1;	order[2] = 0;
			}
		else if(pt[0].fz >= pt[2].fz) {
			order[0] = 2;	order[1] = 0;	order[2] = 1;
			}
		else {
			order[0] = 1;	order[1] = 0;	order[2] = 2;
			}
		}
	return (bSorted = true);
}
	
void
Triangle::LinePoint(int i1, int i2, double z, lfPOINT *res)
{
	double dx, dy, dz, l, f;

	dx = pt[i2].fx - pt[i1].fx;		dy = pt[i2].fy - pt[i1].fy;
	dz = f = pt[i2].fz - pt[i1].fz;
	l = sqrt(dx * dx + dy * dy + dz * dz);
	dx /= l;	dy /= l;	dz /= l;
	f = (z-pt[i1].fz) / dz;
	res->fx = pt[i1].fx + dx * f;
	res->fy = pt[i1].fy + dy * f;
}

bool
Triangle::IsoLine(double z, void *dest)
{
	lfPOINT li[2];

	if(!bSorted && !Sort())return false;
	if(z < pt[order[0]].fz) return false;		//below lowest point
	if(z == pt[order[0]].fz) {
		if(pt[order[1]].fz > z) return true;	//crossing single lowest point
		li[0].fx = pt[order[0]].fx;		li[0].fy = pt[order[0]].fy;
		li[1].fx = pt[order[1]].fx;		li[1].fy = pt[order[1]].fy;
		return ((GraphObj*)dest)->Command(CMD_ADDTOLINE, &li, 0L);
		}
	if(z < pt[order[1]].fz) {
		LinePoint(order[0], order[1], z, &li[0]);
		LinePoint(order[0], order[2], z, &li[1]);
		return ((GraphObj*)dest)->Command(CMD_ADDTOLINE, &li, 0L);
		}
	if(z == pt[order[1]].fz && pt[order[1]].fz < pt[order[2]].fz) {
		li[0].fx = pt[order[1]].fx;		li[0].fy = pt[order[1]].fy;
		LinePoint(order[0], order[2], z, &li[1]);
		return ((GraphObj*)dest)->Command(CMD_ADDTOLINE, &li, 0L);
		}
	if(z < pt[order[2]].fz) {
		LinePoint(order[1], order[2], z, &li[0]);
		LinePoint(order[0], order[2], z, &li[1]);
		return ((GraphObj*)dest)->Command(CMD_ADDTOLINE, &li, 0L);
		}
	if(z == pt[order[1]].fz && z == pt[order[2]].fz) {
		li[0].fx = pt[order[1]].fx;		li[0].fy = pt[order[1]].fy;
		li[1].fx = pt[order[2]].fx;		li[1].fy = pt[order[2]].fy;
		return ((GraphObj*)dest)->Command(CMD_ADDTOLINE, &li, 0L);
		}
	return false;
}

Triangulate::Triangulate(Triangle *t_list)
{
	trl = t_list;		edges = 0L;
}

bool
Triangulate::AddEdge(fPOINT3D *p1, fPOINT3D *p2)
{
	edge *ce, *ne;

	//if edge exists delete both the new and the existing edge
	for(ce = edges, ne = 0L; (ce); ) {
		if((ce->p1.fx == p1->fx && ce->p1.fy == p1->fy && ce->p1.fz == p1->fz
			&& ce->p2.fx == p2->fx && ce->p2.fy == p2->fy && ce->p2.fz == p2->fz)
			|| (ce->p2.fx == p1->fx && ce->p2.fy == p1->fy && ce->p2.fz == p1->fz
			&& ce->p1.fx == p2->fx && ce->p1.fy == p2->fy && ce->p1.fz == p2->fz)) {
			if(ne) ne->next = ce->next;
			else edges = ce->next;
			delete ce;					return true;
			}
		ne = ce;	ce = ce->next;
		}
	//come here for new edge
	if(ne = new edge()) {
		ne->p1.fx = p1->fx;		ne->p1.fy = p1->fy;		ne->p1.fz = p1->fz;
		ne->p2.fx = p2->fx;		ne->p2.fy = p2->fy;		ne->p2.fz = p2->fz;
		ne->next = edges;		edges = ne;
		}
	return false;
}

bool
Triangulate::AddVertex(fPOINT3D *v)
{
	Triangle *trc, *trn, *tr1;
	edge *ce, *ae;

	for(trc = trl, trn = 0L, edges = 0L; (trc);) {
		tr1 = trc->next;
		//delete triangles whose circumcircle enclose the new vertex
		if(trc->TestVertex(v->fx, v->fy)) {
			AddEdge(&trc->pt[0], &trc->pt[1]);		AddEdge(&trc->pt[1], &trc->pt[2]);
			AddEdge(&trc->pt[0], &trc->pt[2]);
			if(trn) trn->next = trc->next;
			else trl = trc->next;
			if(trl == trc) trl = 0L;	
			delete trc;
			}
		else trn = trc;
		trc = tr1;
		}
	//create new triangles from those edges which where found only once
	for(ce = edges; (ce); ) {
		if(trn = new Triangle()) {
			trn->pt[0].fx = ce->p1.fx;	trn->pt[0].fy = ce->p1.fy;	trn->pt[0].fz = ce->p1.fz;
			trn->pt[1].fx = ce->p2.fx;	trn->pt[1].fy = ce->p2.fy;	trn->pt[1].fz = ce->p2.fz;
			trn->pt[2].fx = v->fx;		trn->pt[2].fy = v->fy;		trn->pt[2].fz = v->fz;
			trn->SetRect();				trn->next = trl;			trl = trn;
			ae = ce->next;				delete(ce);					ce = ae;
			}
		}
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Default data vault
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Default::Default()
{
	dUnits = cUnits = 0;
	rlp_strcpy(DecPoint, 2,  ".");		rlp_strcpy(ColSep, 2, ",");
	Line_0.width = .4;		Line_1.width = .04,		Line_2.width = 0.016;
	Line_0.patlength = 6.0;	Line_1.patlength = 0.6;	Line_2.patlength = 0.24;
	Line_0.color = Line_1.color = Line_2.color = 0x00000000L;	//black
	Line_0.pattern = Line_1.pattern = Line_2.pattern = 0L;		//solid line
	FillLine_0.width = FillLine_1.width = FillLine_2.width = 0.0;
	FillLine_0.patlength = 6.0;	FillLine_1.patlength = 0.6;	FillLine_2.patlength = 0.24;
	Line_0.color = Line_1.color = Line_2.color = 0x00000000L;	//black
	Line_0.pattern = Line_1.pattern = Line_2.pattern = 0L;		//solid line
	Fill_0.type = Fill_1.type = Fill_2.type = FILL_NONE;
	Fill_0.color = Fill_1.color = Fill_2.color = 0x00ffffffL;	//white background
	Fill_0.scale = Fill_1.scale = Fill_2.scale = 1.0;			//size = 100%
	Fill_0.hatch = &FillLine_0;	Fill_1.hatch = &FillLine_1;	Fill_2.hatch = &FillLine_2;
	Fill_0.color2 = Fill_1.color2 = Fill_2.color2 = 0x00ffffffL;	//white background
	OutLine_0.width = .2;	OutLine_1.width = .02;	OutLine_2.width = 0.008;
	OutLine_0.patlength = 6.0;	OutLine_1.patlength = 0.6;	OutLine_2.patlength = 0.24;
	OutLine_0.color = OutLine_1.color = OutLine_2.color = 0x00000000L;
	OutLine_0.pattern = OutLine_1.pattern = OutLine_2.pattern = 0L;
	pl = pgl = 0L;	pg = 0L;	pg_fl = 0L;	rrect_rad = 0L;
	cdisp = 0L;		min4log = 0.000001;		axis_color = 0x0L;
	ss_txt = 0.9;	out_upd = 0L;			can_upd = false;
#ifdef _WINDOWS	
	iMenuHeight = 0;
#else
	iMenuHeight = 25;
#endif
	svgAttr = svgScript = currPath = IniFile = 0L;
	File1 = File2 = File3 = File4 = File5 = File6 = 0L;
	if(fmt_date = (char*)malloc(20)) rlp_strcpy(fmt_date, 20, "Z.V.Y");
	if(fmt_time = (char*)malloc(20)) rlp_strcpy(fmt_time, 20, "H:M:S");
	if(fmt_datetime = (char*)malloc(20)) rlp_strcpy(fmt_datetime, 20, "Z.V.Y H:M:S");
}

Default::~Default()
{
	if(svgAttr) free(svgAttr);		if(svgScript) free(svgScript);
	if(currPath) free(currPath);	if(IniFile) free(IniFile);
	svgAttr = svgScript = currPath = 0L;
	if(pl) free(pl);				if(pgl) free(pgl);
	if(pg_fl) free(pg_fl);			if(pg) free(pg);
	if(rrect_rad) free(rrect_rad);
	if(File1) free(File1);			if(File2) free(File2);
	if(File3) free(File3);			if(File4) free(File4);
	if(File5) free(File5);			if(File6) free(File6);
	if(fmt_date) free(fmt_date);	if(fmt_time) free(fmt_time);
	if(fmt_datetime) free(fmt_datetime);
}

void
Default::SetDisp(anyOutput *o)
{
	if(o && o != cdisp) {
		Undo.SetDisp(o);
		cdisp = o;
		}
}

double
Default::GetSize(int select)
{
	double RetVal = 0.0;

	switch (select){
	case SIZE_SYMBOL:				RetVal = 3.0;		break;
	case SIZE_SYM_LINE:				RetVal = 0.2;		break;
	case SIZE_DATA_LINE:
		switch(cUnits) {
		case 1:		return Line_1.width;
		case 2:		return Line_2.width;
		default:	return Line_0.width;
		}
	case SIZE_TEXT:					RetVal = _TXH;		break;
	case SIZE_GRECT_TOP:			RetVal = 0.0;		break;
	case SIZE_GRECT_BOTTOM:			RetVal = 110.0;		break;
	case SIZE_GRECT_LEFT:			RetVal = 0.0;		break;
	case SIZE_GRECT_RIGHT:			RetVal = 160.0;		break;
	case SIZE_DRECT_TOP:			RetVal = 10.0;		break;
	case SIZE_DRECT_BOTTOM:			RetVal = 90.0;		break;
	case SIZE_DRECT_LEFT:			RetVal = 25.0;		break;
	case SIZE_DRECT_RIGHT:			RetVal = 140.0;		break;
	case SIZE_PATLENGTH:
	case SIZE_DATA_LINE_PAT:
		switch(cUnits) {
		case 1:		return Line_1.patlength;
		case 2:		return Line_2.patlength;
		default:	return Line_0.patlength;
		}
	case SIZE_AXIS_TICKS:			RetVal = 2.0;		break;
	case SIZE_TICK_LABELS:			RetVal = _TXH;		break;
	case SIZE_WHISKER:
	case SIZE_ERRBAR:				RetVal = 3.0;		break;
	case SIZE_WHISKER_LINE:
	case SIZE_AXIS_LINE:
	case SIZE_BAR_LINE:			
	case SIZE_ERRBAR_LINE:
		switch(cUnits) {
		case 1:		return OutLine_1.width;
		case 2:		return OutLine_2.width;
		default:	return OutLine_0.width;
		}
	case SIZE_BOX:
	case SIZE_BAR:					RetVal = 10.0;				break;
	case SIZE_BUBBLE_LINE:			RetVal = 0.4;				break;
	case SIZE_BUBBLE_HATCH_LINE:	RetVal = 0.1;				break;
	case SIZE_ARROW_LINE:			RetVal = 0.4;				break;
	case SIZE_ARROW_CAPWIDTH:		RetVal = 3.0;				break;
	case SIZE_ARROW_CAPLENGTH:		RetVal = 4.0;				break;
	case SIZE_HAIRLINE:				RetVal = 0.1;				break;
	case SIZE_SEGLINE:				RetVal = 0.4;				break;
	case SIZE_CELLWIDTH:			RetVal = 20.0;				break;
	case SIZE_CELLTEXT:
#ifdef _WINDOWS
		RetVal = 4.5*ss_txt;		break;
#else
		RetVal = 4.5*ss_txt*0.7;	break;
#endif
	case SIZE_RRECT_RAD:			
		return rrect_rad ? *rrect_rad : GetSize(SIZE_SYMBOL);
	case SIZE_SCALE:				return 1.0;
	default:	return 0.0;
		}
	switch(cUnits) {
	case 1:	RetVal /= 10.0;	break;
	case 2:	RetVal = NiceValue(RetVal/25.4);	break;
		}
	return RetVal;
}

DWORD
Default::Color(int select)
{
	switch (select){
	case COL_ERROR_LINE:
	case COL_WHISKER:
	case COL_SYM_LINE:			return 0x00000000L;
	case COL_SYM_FILL:			return 0x00ffffffL;
	case COL_ARROW:
	case COL_DATA_LINE:			return Line_0.color;
	case COL_TEXT:				return 0x00000000L;
	case COL_BG:				return 0x00ffffffL;
	case COL_AXIS:				return axis_color;
	case COL_BAR_LINE:			return OutLine_0.color;
	case COL_BAR_FILL:			return 0x00ffffffL;
	case COL_BUBBLE_LINE:
	case COL_BUBBLE_FILLLINE:	return 0x00ff0000L;
	case COL_BUBBLE_FILL:		return 0x00ffc0c0L;
	case COL_DRECT:				return 0x00ffffffL;
	case COL_GRECT:				return 0x00ffffffL;
	case COL_GRECTLINE:			return 0x00e0e0e0L;
	case COL_POLYLINE:			return pl ? pl->color : OutLine_0.color;
	case COL_POLYGON:			return pgl ? pgl->color : OutLine_0.color;
	default:					return 0x00C0C0C0L;	//Error
	}
}

LineDEF *
Default::GetLine()
{
	switch (cUnits) {
	case 1:		return &Line_1;
	case 2:		return &Line_2;
	default:	return &Line_0;
		}
}

void
Default::SetLine(int u, LineDEF *l, int which)
{
	double lw, pl;
	LineDEF *l1, *l2, *l3;

	switch (which) {
	case 0:	l1 = &Line_0;		l2 = &Line_1;		l3 = &Line_2;		break;
	case 1:	l1 = &FillLine_0;	l2 = &FillLine_1;	l3 = &FillLine_2;	break;
	case 2:	l1 = &OutLine_0;	l2 = &OutLine_1;	l3 = &OutLine_2;	break;
	default: return;
		}
	l1->color = l2->color = l3->color = l->color;
	l1->pattern = l2->pattern = l3->pattern = l->pattern;
	switch (u) {
	case 1:
		lw = l->width*10.0;	pl = l->patlength*10.0;
		l1->width = lw;			l1->patlength = pl;
		l2->width = l->width;	l2->patlength = l->patlength;
		l3->patlength = NiceValue(pl/25.4);
		l3->patlength = NiceValue(pl/25.4);
		break;
	case 2:
		lw = NiceValue(l->width*25.4);	pl = NiceValue(l->patlength*25.4);
		l1->width = lw;			l1->patlength = pl;
		l2->width = lw/10.0;	l2->patlength = pl/10.0;
		l3->width = l->width;	l3->patlength = l->patlength;
		break;
	default:
		lw = l->width;		pl = l->patlength;
		l1->width = l->width;	l1->patlength = l->patlength;
		l2->width = lw/10.0;		l2->patlength = pl/10.0;
		l3->patlength = NiceValue(pl/25.4);
		l3->patlength = NiceValue(pl/25.4);
		break;
		}
}

FillDEF *
Default::GetFill()
{
	switch (cUnits) {
	case 1:		return &Fill_1;
	case 2:		return &Fill_2;
	default:	return &Fill_0;
		}
}

void
Default::SetFill(int u, FillDEF *fd)
{
	memcpy(&Fill_0, fd, sizeof(FillDEF));
	memcpy(&Fill_1, fd, sizeof(FillDEF));
	memcpy(&Fill_2, fd, sizeof(FillDEF));
	if(fd->hatch) SetLine(u, fd->hatch, 1);
	Fill_0.hatch = &FillLine_0;
	Fill_1.hatch = &FillLine_1;
	Fill_2.hatch = &FillLine_2;
}

LineDEF *
Default::GetOutLine()
{
	switch (cUnits) {
	case 1:		return &OutLine_1;
	case 2:		return &OutLine_2;
	default:	return &OutLine_0;
		}
}

LineDEF *
Default::plLineDEF(LineDEF *ld)
{
	if(ld) {
		if(pl) free(pl);
		if(pl = (LineDEF *)malloc(sizeof(LineDEF))) memcpy(pl, ld, sizeof(LineDEF));
		}
	if(pl) return pl;
	else return GetOutLine();
}

LineDEF *
Default::pgLineDEF(LineDEF *ol)
{
	if(ol) {
		if(pgl) free(pgl);
		if(pgl = (LineDEF *)malloc(sizeof(LineDEF))) memcpy(pgl, ol, sizeof(LineDEF));
		}
	if(pgl) return pgl;
	else return GetOutLine();
}

FillDEF *
Default::pgFillDEF(FillDEF *fd)
{
	if(fd) {
		if(pg) free(pg);
		if(pg = (FillDEF *)malloc(sizeof(FillDEF))){
			memcpy(pg, fd, sizeof(FillDEF));
			if(pg->hatch) {
				if(pg_fl) free(pg_fl);
				if(pg_fl = (LineDEF *)malloc(sizeof(LineDEF)))
					memcpy(pg_fl, pg->hatch, sizeof(LineDEF));
				pg->hatch = pg_fl;
				}
			}
		}
	if(pg) return pg;
	else return GetFill();
}

double
Default::rrectRad(double rad)
{
	if(!rrect_rad)rrect_rad=(double*)malloc(sizeof(double));
	if(rrect_rad) return (*rrect_rad = rad);
	return 0.0;
}

void
Default::FileHistory(char *path)
{
	char *tmp_path = 0L, *tmp;
	char **history[] = {&File1, &File2, &File3, &File4, &File5, &File6};
	int i, j;

	if(path && (tmp_path=(char*)malloc((i=(int)strlen(path))+10))){
		rlp_strcpy(tmp_path, i+1, path);
		for(j = i ; i > 0 && tmp_path[i] != '/' && tmp_path[i] != '\\'; i--);
		tmp_path[i] = 0;
		if(currPath) free(currPath);
		if(tmp_path[0]) currPath = (char*)memdup(tmp_path, i+1, 0);
		else currPath = 0L;
		rlp_strcpy(tmp_path, j+1, path);
		if(File1 && strcmp(File1, tmp_path)) {
			for(i = 0; i < 6 && tmp_path; i++) {
				if(i && *history[i] && !strcmp(File1, *history[i])){
					free(*history[i]);
					*history[i] = tmp_path;
					tmp_path = 0L;
					break;
					}
				if(*history[i]) {
					if(strcmp(tmp_path, *history[i])){
						tmp = *history[i];
						*history[i] = tmp_path;
						tmp_path = tmp;
						}
					else { 
						free(tmp_path);
						tmp_path = 0L;
						}
					}
				else{
					tmp = *history[i];
					*history[i] = tmp_path;
					tmp_path = tmp;
					}
				}
			}
		if(!(*history[0])) {
			*history[0] = tmp_path;
			tmp_path = 0L;
			}
		}
	if(tmp_path) free(tmp_path);
}

void
Default::Idle(int cmd)
{
	if(cmd == CMD_UPDATE && can_upd && out_upd) {
		IncrementMinMaxRect(&rec_upd, 6);
		if(rec_upd.left < 0) rec_upd.left = 0;
		if(rec_upd.top < 0) rec_upd.top = 0;
		out_upd->UpdateRect(&rec_upd, false);
		}
	if(cmd == CMD_FLUSH) {
		}
	out_upd = 0L;	can_upd = false;
}

void
Default::UpdRect(anyOutput *o, int x1, int y1, int x2, int y2)
{
	if(out_upd && o != out_upd) Idle(CMD_UPDATE);
	out_upd = o;
	if(can_upd){
		UpdAdd(o, x1, y1);		UpdAdd(o, x2, y2);
		}
	else SetMinMaxRect(&rec_upd, x1, y1, x2, y2);
	can_upd = true;
}

void
Default::UpdAdd(anyOutput * o, int x, int y)
{
	if(o == out_upd && can_upd)
		UpdateMinMaxRect(&rec_upd, x, y);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Chache file input for read operations
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#define CharCacheSize 1024
ReadCache::ReadCache()
{
	Cache = 0L;		idx = max = 0;		eof = true;
}

ReadCache::~ReadCache()
{
	if(Cache) free(Cache);
	Cache = 0L;
}

bool
ReadCache::Open(char *name)
{
	idx = max = 0;
	eof = true;
	if(!name) iFile = -1;
#ifdef USE_WIN_SECURE
	else if(_sopen_s(&iFile, name, O_BINARY, 0x40, S_IWRITE) || iFile < 0) return false;
#else
	else if(-1 ==(iFile = open(name, O_BINARY))) return false;
#endif
	Cache = (unsigned char *)malloc((unsigned)(CharCacheSize + 1));
	if(Cache) return true;
	return false;
}

void
ReadCache::Close()
{
#ifdef USE_WIN_SECURE
	if(iFile >= 0) _close(iFile);
#else
	if(iFile >= 0) close(iFile);
#endif
	if(Cache) free(Cache);				Cache = 0L;
}

unsigned char
ReadCache::Getc()
{
	if(Cache){
		if(idx < max) return (last = Cache[idx++]);
		else {
			do {
#ifdef USE_WIN_SECURE
				max = _read(iFile, Cache, CharCacheSize);
#else
				max = read(iFile, Cache, CharCacheSize);
#endif
				if(max <=0) {
					eof = true;
					return 0;
					}
				else eof = false;
				}while(max == 0);
			idx = 1;
			return(last = Cache[0]);
			}
		}
	return 0;
}

unsigned char *
ReadCache::GetField()
{
	int i;
	static unsigned char *ret;

	if(Cache && max) {
		while(idx < max && Cache[idx] < 43) idx++;
		if(idx == max){
			if(max == CharCacheSize) {
#ifdef USE_WIN_SECURE
				max = _read(iFile, Cache, CharCacheSize);
#else
				max = read(iFile, Cache, CharCacheSize);
#endif
				idx = 0;
				return GetField();
				}
			else return 0L;
			}
		i = idx;
		while(i < max && Cache[i] > 32 && Cache[i] <= 'z') i++;
		if(i == max) {
			for(i = 0; (Line[i] = Getc()) >32 && Line[i] <= 'z' && i < 4096; i++);
			Line[i] = 0;
			return Line;
			}
		else {
			ret = Cache+idx;
			idx = i;
			return ret;
			}
		}
	return 0L;
}

void
ReadCache::ReadLine(char *dest, int size)
{
	int i=0;
	unsigned char c;

	dest[0] = 0;
	do {
		c =  Getc();
		if(c == 0x09) c = 0x32;			// tab to space
		if(c > 31) dest[i++] = (char)c;
		}while(c && c != 0x0a && i < size);
	dest[i] = 0;
}

bool
ReadCache::GetInt(long *in)
{
	unsigned char *field;

	field = GetField();
	if(field && field[0]) {
		*in = atol((char*)field);
		if(*in == 0 && field[0] == '}') return false;
		return true;
		}
	return false;
}

bool
ReadCache::GetFloat(double *fn)
{
	unsigned char *field;

	field = GetField();
	if(field && field[0]) {
		*fn = atof((char*)field);
		if(*fn == 0.0 && field[0] == '}') return false;
		return true;
		}
	return false;
}

unsigned char
ReadCache::Lastc()
{
	return last;
}

bool
ReadCache::IsEOF()
{
	return eof;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Process memory block as if file input
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MemCache::MemCache(unsigned char *ptr)
:ReadCache()
{
	if(ptr) {
		max = (int)strlen((char*)ptr);
		Cache = (unsigned char*) memdup(ptr, max+1, 0);
		eof = false;
		}
}

MemCache::~MemCache()
{
	if(Cache) free(Cache);
	Cache = 0L;
}

unsigned char
MemCache::Getc()
{
	if(Cache){
		if(idx < max) return (last = Cache[idx++]);
		else {
			eof = true;
			return 0;
			}
		}
	return 0;
}

unsigned char *
MemCache::GetField()
{
	int i;
	static unsigned char *ret;

	if(Cache && max) {
		while(idx < max && Cache[idx] < 43) idx++;
		i = idx;
		while(i < max && Cache[i] > 32 && Cache[i] <= 'z') i++;
		ret = Cache+idx;
		idx = i;
		return ret;
		}
	return 0L;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Process Undo 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#define UNDO_RING_SIZE 0x100
#define UNDO_IDX_MASK 0xff
UndoObj::UndoObj()
{
	buff = buff0 = (UndoInfo**)calloc(UNDO_RING_SIZE, sizeof(UndoInfo*));
	stub1 = ndisp = 0;					busy = false;
	pcb = &stub1;	cdisp = ldisp = 0L;	buffers = 0L;
}

UndoObj::~UndoObj()
{
	Flush();
	if(buff0) free(buff0);
}

//free all resources associated with undo operations
void
UndoObj::Flush()
{
	int i, j;

	if(!buffers) return;
	for(i = 0; i < ndisp; i++) if(buffers[i]) {
		if(buffers[i]->buff) {
			for(j = 0; j < UNDO_RING_SIZE; j++) {
				if(buffers[i]->buff[j]) FreeInfo(&buffers[i]->buff[j]);
				}
			free(buffers[i]->buff);
			}
		free(buffers[i]);
		}
	free(buffers);
	stub1 = ndisp = 0;
	pcb = &stub1;	cdisp = 0L;	buffers = 0L;
}

bool
UndoObj::isEmpty(anyOutput *o)
{
	int i;

	if(!buffers) return true;
	for (i = 0; i < ndisp; i++) if(buffers[i]){
		if(o && buffers[i]->disp == o ) {
			if(buffers[i]->count > 0) return false;
			else return true;
			}
		else if(!o && buffers[i]->count > 0) return false;
		}
	return true;
}

//select buffers associated with the current window/widget,
//create new buffers if called for the first time
void
UndoObj::SetDisp(anyOutput *o)
{
	int i;
	void *ptmp;

	if(o && o != cdisp) {
		ldisp = cdisp;
		if(buffers) {
			for(i = 0; i < ndisp; i++) {
				if(buffers[i] && buffers[i]->disp == o){
					cdisp = o;
					buff = buffers[i]->buff;	pcb = &buffers[i]->count;
					return;
					}
				else if(!buffers[i] && (buffers[i] = (UndoBuff*)calloc(1, sizeof(UndoBuff)))) {
					buffers[i]->buff = (UndoInfo**)calloc(UNDO_RING_SIZE, sizeof(UndoInfo*));
					buffers[i]->disp = cdisp = o;
					buff = buffers[i]->buff;	pcb = &buffers[i]->count;
					return;
					}
				}
			if(ptmp = memdup(buffers, sizeof(UndoBuff*) *(ndisp+1), 0)){
				free(buffers);		buffers = (UndoBuff**)ptmp;
				if(buffers[ndisp] = (UndoBuff*)calloc(1, sizeof(UndoBuff))) {
					buffers[ndisp]->buff = (UndoInfo**)calloc(UNDO_RING_SIZE, sizeof(UndoInfo*));
					buffers[ndisp]->disp = cdisp = o;
					buff = buffers[ndisp]->buff;	pcb = &buffers[ndisp]->count;
					ndisp++;
					}
				}
			}
		else if(buffers = (UndoBuff**)calloc(1, sizeof(UndoBuff*))){
			if(buffers[0] = (UndoBuff*)calloc(1, sizeof(UndoBuff))) {
				buffers[0]->buff = (UndoInfo**)calloc(UNDO_RING_SIZE, sizeof(UndoInfo*));
				buffers[0]->disp = cdisp = o;
				ndisp = 1;
				buff = buffers[0]->buff;	pcb = &buffers[0]->count;
				}
			}
		}
}

//free memory for a closed output
void 
UndoObj::KillDisp(anyOutput *o)
{
	int i, j, c_buf;

	InvalidateOutput(o);			//kill text cursor and animated rectangle
	if(o && buffers) {
		for(i = 0, c_buf = -1; i < ndisp; i++) {
			if(buffers[i] && buffers[i]->disp == o)	{
				c_buf = i;	break;
				}
			}
		if(c_buf < 0) return;
		if(buffers[c_buf]->buff) {
			for(j = 0; j < UNDO_RING_SIZE; j++) {
				if(buffers[c_buf]->buff[j]) FreeInfo(&buffers[c_buf]->buff[j]);
				}
			free(buffers[c_buf]->buff);
			}
		free(buffers[c_buf]);	buffers[c_buf] = 0L;
		}
	if(ldisp && o == cdisp) SetDisp(ldisp);
	else cdisp = 0L;
}

//remove references to an invalid (probabbly deleted) object
void
UndoObj::InvalidGO(GraphObj *go)
{
	int i, i1, i2;

	if(*pcb >10) {
		if(*pcb < UNDO_RING_SIZE){
			i1 = 0;		i2 = *pcb;
			}
		else {
			i1 = ((*pcb) & UNDO_IDX_MASK);
			i2 = (((*pcb)-UNDO_RING_SIZE) & UNDO_IDX_MASK);
			if(i1 > i2) Swap(i1, i2);
			}
		}
	else {
		i1 = 0;		i2 = *pcb;
		}
	for(i = i1; i < i2; i++) {
		if(buff[i] && buff[i]->owner == go) FreeInfo(&buff[i]);
		if(buff[i]) switch(buff[i]->cmd) {
//		case UNDO_OBJCONF:
		case UNDO_OBJCONF_1:
			if(buff[i]->loc == (void**)go) FreeInfo(&buff[i]);
			break;
			}
		}
}

void
UndoObj::Pop(anyOutput *o)
{
	int idx;

	if(o) {
		SetDisp(o);
		if(*pcb < 1) return;
		idx = ((*pcb-1) & UNDO_IDX_MASK);
		if(buff[idx]) FreeInfo(&buff[idx]);
		(*pcb)--;
		}
}

void
UndoObj::Restore(bool redraw, anyOutput*o)
{
	int i, j, idx;
	DWORD flags;
	UndoList *ul;
	GraphObj **gol;
	unsigned char *savbuf, *target;

	if(o) SetDisp(o);
	else if(cdisp) o = cdisp;
	CurrGO = 0L;
	if(*pcb < 1){
		InfoBox("The UNDO-cache is empty");
		return;
		}
	do {
		idx = ((*pcb-1) & UNDO_IDX_MASK);
		if(!buff[idx] && *pcb > 0) (*pcb)--;
		} while(!buff[idx] && *pcb > 0);
	if(!buff[idx]) return;
	if(buff[idx]->zd.org.fx != cdisp->VPorg.fx ||
		buff[idx]->zd.org.fy != cdisp->VPorg.fy || buff[idx]->zd.scale != cdisp->VPscale){
		cdisp->VPorg.fx = buff[idx]->zd.org.fx;			cdisp->VPorg.fy = buff[idx]->zd.org.fy;
		cdisp->VPscale = buff[idx]->zd.scale;			HideCopyMark();
		if(cdisp->VPscale > 100.0) cdisp->VPscale = 100.0;
		if(cdisp->VPscale < 0.05) cdisp->VPscale = 0.05;
		if(buff[idx]->owner)
			if(buff[idx]->owner->Id < GO_PLOT) CurrGO = buff[idx]->owner;
			if(!((buff[idx]->owner)->Command(CMD_SETSCROLL, 0L, cdisp)))
				(buff[idx]->owner)->Command(CMD_REDRAW, 0L, cdisp);
		return;
		}
	(*pcb)--;
	busy = true;
	if(buff[idx]) {
		flags = buff[idx]->flags;
		switch(buff[idx]->cmd) {
		case UNDO_DATA:
			RestoreData(buff[idx]);
			break;
		case UNDO_ET:
			if(buff[idx]->loc && buff[idx]->data) {
				if(((EtBuff*)buff[idx]->data)->DaO){
					buff[idx]->loc = (void**)((EtBuff*)buff[idx]->data)->DaO->
						etRows[((EtBuff*)buff[idx]->data)->row][((EtBuff*)buff[idx]->data)->col];
					}
				if(((EtBuff*)buff[idx]->data)->DaO) (((EtBuff*)buff[idx]->data)->DaO)->SetText(((EtBuff*)buff[idx]->data)->row,
					((EtBuff*)buff[idx]->data)->col, ((EtBuff*)buff[idx]->data)->txt);
				else ((EditText*)buff[idx]->loc)->SetText(((EtBuff*)buff[idx]->data)->txt);
				if(((EtBuff*)buff[idx]->data)->txt) free(((EtBuff*)buff[idx]->data)->txt);
				*(((EtBuff*)buff[idx]->data)->cur) = ((EtBuff*)buff[idx]->data)->vcur;
				*(((EtBuff*)buff[idx]->data)->m1) = ((EtBuff*)buff[idx]->data)->vm1;
				*(((EtBuff*)buff[idx]->data)->m2) = ((EtBuff*)buff[idx]->data)->vm2;
				((EditText*)buff[idx]->loc)->Command(CMD_MRK_DIRTY, cdisp, 0L);
				}
			break;
		case UNDO_TEXTBUF:
			if(buff[idx]->loc && buff[idx]->data) {
				target = *((TextBuff*)buff[idx]->data)->pbuff;
				target = ((TextBuff*)buff[idx]->data)->buff;
				if(*((TextBuff*)buff[idx]->data)->pbuff) free(*((TextBuff*)buff[idx]->data)->pbuff);
				*(((TextBuff*)buff[idx]->data)->pbuff) = ((TextBuff*)buff[idx]->data)->buff;
				*(((TextBuff*)buff[idx]->data)->psize) = ((TextBuff*)buff[idx]->data)->size;
				*(((TextBuff*)buff[idx]->data)->ppos) = ((TextBuff*)buff[idx]->data)->pos;
				}
			break;
		case UNDO_MUTATE:
		case UNDO_DEL_GO:
			((GraphObj*)(buff[idx]->data))->parent = buff[idx]->owner;
			if(buff[idx]->cmd == UNDO_MUTATE && *(buff[idx]->loc))
				::DeleteGO((GraphObj*)*(buff[idx]->loc));
			else CurrGO = (GraphObj*) buff[idx]->data;
			*(buff[idx]->loc) = buff[idx]->data;
			(buff[idx]->owner)->Command(CMD_MRK_DIRTY, 0L, 0L);
			break;
		case UNDO_DROPGOLIST:
			if((ul = (UndoList *)(buff[idx]->data)) && (ul->array)){
				gol = (GraphObj**)(*(ul->loc_arr));
				if(gol) for (i = 0; i < *(ul->loc_count); i++) if(gol[i]) ::DeleteGO(gol[i]);
				*(ul->loc_count) = ul->count;				if(gol) free(gol);
				*(ul->loc_arr) = ul->array;					free(ul);
				}
			break;
		case UNDO_GOLIST:
			if((ul = (UndoList *)(buff[idx]->data)) && (ul->array)){
				memcpy(*(ul->loc_arr), ul->array, ul->count * sizeof(GraphObj*));
				*(ul->loc_count) = ul->count;				free(ul->array);
				free(ul);
				}
			break;
		case UNDO_DROPMEM:
			*(buff[idx]->loc) = buff[idx]->data;				break;
		case UNDO_SAVVAR:
			if(!(savbuf = (unsigned char *)buff[idx]->data))break;
			for(i = 0; ; ) {
				memcpy(&target, savbuf+i, sizeof(unsigned char*));	i += sizeof(unsigned char*);
				memcpy(&j, savbuf+i, sizeof(int));					i += sizeof(int);
				if(!target) break;
				memcpy(target, savbuf+i, j);						i += j;
				}
			if(buff[idx]->owner)(buff[idx]->owner)->Command(CMD_MRK_DIRTY, 0L, 0L);
			free(savbuf);
			break;
		case UNDO_VALDWORD:
			*((DWORD*)(buff[idx]->loc)) = *((DWORD*)(buff[idx]->data));
			free(buff[idx]->data);								break;
		case UNDO_POINT:
			memcpy(buff[idx]->loc, buff[idx]->data, sizeof(POINT));
			free(buff[idx]->data);								break;
		case UNDO_VOIDPTR:
			*buff[idx]->loc = buff[idx]->data;					break;
		case UNDO_VALINT:
			*((int*)(buff[idx]->loc)) = *((int*)(buff[idx]->data));
			free(buff[idx]->data);								break;
		case UNDO_VALLONG:
			*((long*)(buff[idx]->loc)) = *((long*)(buff[idx]->data));
			free(buff[idx]->data);								break;
		case UNDO_OBJCONF_1:			//single object restore
			UpdGOfromMem((GraphObj *)buff[idx]->loc, (unsigned char *)buff[idx]->data);
			free(buff[idx]->data);								break;
		case UNDO_OBJCONF:				//tree of objects to restore
			RestoreConf(buff[idx]);
			if(buff[idx] && buff[idx]->data) free(buff[idx]->data);								break;
		case UNDO_LFP:
			memcpy(buff[idx]->loc, buff[idx]->data, sizeof(lfPOINT));
			free(buff[idx]->data);								break;
		case UNDO_MOVE:
			(buff[idx]->owner)->Command(CMD_UNDO_MOVE, buff[idx]->data, 0L);
			free(buff[idx]->data);								break;
		case UNDO_RECT:
			memcpy(buff[idx]->loc, buff[idx]->data, sizeof(fRECT));
			free(buff[idx]->data);								break;
		case UNDO_STRING:
			if(*(buff[idx]->loc) && buff[idx]->data)
				rlp_strcpy ((char*)(*buff[idx]->loc), 100, (char*)(buff[idx]->data));
			else if(*(buff[idx]->loc))((char*)*(buff[idx]->loc))[0] = 0;
			if(buff[idx]->data) free(buff[idx]->data);
			CurrGO = buff[idx]->owner;							break;
		case UNDO_ROTDEF:
			memcpy(*(buff[idx]->loc), buff[idx]->data, 6 * sizeof(double));
			free(buff[idx]->data);								break;
		case UNDO_SETGO:
			::DeleteGO(*((GraphObj**)(buff[idx]->loc)));
			*((GraphObj**)(buff[idx]->loc)) = 0L;				break;
		case UNDO_LINEDEF:
			memcpy(buff[idx]->loc, buff[idx]->data, sizeof(LineDEF));
			free(buff[idx]->data);								break;
		case UNDO_FILLDEF:
			memcpy(buff[idx]->loc, buff[idx]->data, sizeof(FillDEF) - (sizeof(LineDEF*) + sizeof(DWORD)));
			((FillDEF*)(buff[idx]->loc))->color2 = ((FillDEF*)(buff[idx]->data))->color2;
			free(buff[idx]->data);								break;
		case UNDO_AXISDEF:
			if(((AxisDEF*)(buff[idx]->loc))->breaks) free(((AxisDEF*)(buff[idx]->loc))->breaks);
			memcpy(buff[idx]->loc, buff[idx]->data, sizeof(AxisDEF));
			free(buff[idx]->data);								break;
		case UNDO_TEXTDEF:
			if(((TextDEF*)(buff[idx]->loc))->text) free(((TextDEF*)(buff[idx]->loc))->text);
			memcpy(buff[idx]->loc, buff[idx]->data, sizeof(TextDEF));
			free(buff[idx]->data);								break;
		case UNDO_LFP3D:
			memcpy(buff[idx]->loc, buff[idx]->data, sizeof(fPOINT3D));
			free(buff[idx]->data);								break;
		case UNDO_FLOAT:
			*((double*)(buff[idx]->loc)) = *((double*)(buff[idx]->data));
			free(buff[idx]->data);								break;
		case UNDO_MEM:
			if((ul = (UndoList *)(buff[idx]->data)) && (ul->array)){
				memcpy(*(ul->loc_arr), ul->array, ul->size);
				*(ul->loc_count) = ul->count;
				free(ul->array);
				if(buff[idx]->owner->Id < GO_PLOT) CurrGO = (GraphObj*) buff[idx]->owner;
				if(buff[idx]->owner)(buff[idx]->owner)->Command(CMD_MRK_DIRTY, 0L, 0L);
				}
			break;
			}
		if(flags & UNDO_CONTINUE){
			free(buff[idx]);	buff[idx] = 0L;
			Restore(redraw, cdisp);
			}
		else {
			if(o) o->MrkMode = MRK_NONE;
			if(redraw && buff[idx] && buff[idx]->owner){
				(buff[idx]->owner)->Command(CMD_MRK_DIRTY, 0L, 0L);
				(buff[idx]->owner)->Command(CMD_REDRAW, 0L, 0L);
				}
			if(buff[idx]) free(buff[idx]);	buff[idx] = 0L;
			}
		}
	else {
		InfoBox("The UNDO-cache is empty");
		}
	busy = false;
}

void
UndoObj::ListGOmoved(GraphObj **oldlist, GraphObj **newlist, long size)
{
	long i;
	int c;

	if(!oldlist || !newlist || oldlist == newlist) return;
	for(i = 0; i < size; i++) if(oldlist[i] == newlist[i]) {
		for(c = 0; c < UNDO_RING_SIZE; c++) {
			if(buff[c]) switch(buff[c]->cmd) {
			case UNDO_DEL_GO:
			case UNDO_SETGO:
			case UNDO_OBJCONF_1:
			case UNDO_OBJCONF:
				if(buff[c]->loc == (void**) &oldlist[i]){
					buff[c]->loc = (void**) &newlist[i];
					}
				break;
				}
			}
		}
}

void
UndoObj::DeleteGO(GraphObj **go, DWORD flags, anyOutput *o)
{
	if(!go || !(*go)) return;
	HideCopyMark();
	if(o){
		SetDisp(o);					 o->HideMark();
		}
	if(CurrGO == *go) CurrGO = 0L;
	if((*go)->Id == GO_POLYLINE || (*go)->Id == GO_POLYGON || (*go)->Id == GO_BEZIER){
		if(CurrHandle && CurrHandle->parent==*go) {
			if((*go)->Command(CMD_DELOBJ, CurrHandle, 0l)) return;
			}
		}
	NewItem(UNDO_DEL_GO, flags, (*(go))->parent, *(go), (void**)go);
	(*(go))->parent->Command(CMD_MRK_DIRTY, 0L, 0L);
	(*(go))->parent = 0L;
	*(go) = CurrGO = 0L;
}

void
UndoObj::MutateGO(GraphObj **old, GraphObj *repl, DWORD flags, anyOutput *o)
{
	if(!old || !(*old)) return;
	if(o){
		SetDisp(o);					 o->HideMark();
		}
	if(!(*old))return;	//HideMark might delete object: should never happen
	if(CurrGO == *old) CurrGO = 0L;
	NewItem(UNDO_MUTATE, flags, (*(old))->parent, *(old), (void**)old);
	repl->parent = (*(old))->parent;
	(*(old))->parent = 0L;
	*(old) = repl;
	repl->parent->Command(CMD_REDRAW, 0L, o);
}

void
UndoObj::StoreListGO(GraphObj *parent, GraphObj ***go, long *count, DWORD flags)
{
	UndoList *ul;

	if(ul = (UndoList *)malloc(sizeof(UndoList))) {
		if(ul->array = memdup(*go, *count * sizeof(GraphObj*), 0)){
			ul->loc_arr = (void **)go;
			ul->count = *count;
			ul->loc_count = count;
			if(0 > NewItem(UNDO_GOLIST, flags, parent, ul, 0L)) {
				free(ul->array);			free(ul);
				}
			}
		else free(ul);
		}
}

void
UndoObj::DropListGO(GraphObj *parent, GraphObj ***go, long *count, DWORD flags)
{
	UndoList *ul;

	if(ul = (UndoList *)malloc(sizeof(UndoList))) {
		if(ul->array = *go) {
			ul->loc_arr = (void **)go;		*go = 0L;
			ul->count = *count;			*count = 0;
			ul->loc_count = count;
			if(0 > NewItem(UNDO_DROPGOLIST, flags, parent, ul, 0L)) {
				free(ul->array);			free(ul);
				}
			}
		else free(ul);
		}
}

void
UndoObj::DropMemory(GraphObj *parent, void **mem, DWORD flags)
{
	NewItem(UNDO_DROPMEM, flags, parent, *(mem), mem);
	*mem = 0L;
}

void
UndoObj::SavVarBlock(GraphObj *parent, void **mem, DWORD flags)
{
	NewItem(UNDO_SAVVAR, flags, parent, *(mem), mem);
	*mem = 0L;
}

void
UndoObj::ValDword(GraphObj *parent, DWORD *val, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(val, sizeof(DWORD), 0))) return;
	if(0 > NewItem(UNDO_VALDWORD, flags, parent, ptr, (void**)val)) free(ptr);
}

void 
UndoObj::Point(GraphObj *parent, POINT *pt, anyOutput * o, DWORD flags)
{
	void *ptr;

	if(o) SetDisp(o);
	if(!(ptr = memdup(pt, sizeof(POINT), 0))) return;
	if(0 > NewItem(UNDO_POINT, flags, parent, ptr, (void**)pt)) free(ptr);
}

void 
UndoObj::VoidPtr(GraphObj *parent, void **pptr, void *ptr, anyOutput *o, DWORD flags)
{
	if(o) SetDisp(o);
	NewItem(UNDO_VOIDPTR, flags, parent, ptr, pptr);
}

void
UndoObj::ValInt(GraphObj *parent, int *val, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(val, sizeof(int), 0))) return;
	if(0 > NewItem(UNDO_VALINT, flags, parent, ptr, (void**)val)) free(ptr);
}

void
UndoObj::ValLong(GraphObj *parent, long *val, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(val, sizeof(long), 0))) return;
	if(0 > NewItem(UNDO_VALINT, flags, parent, ptr, (void**)val)) free(ptr);
}

void
UndoObj::ObjConf(GraphObj *go, DWORD flags)
{
	long sz;
	int idx;
	
	InvalidGO(go);
	if(0<=(idx = NewItem(UNDO_OBJCONF, flags, go->parent, GraphToMem(go, &sz), (void**)go))){
		if(cObsW == 1) buff[idx]->cmd = UNDO_OBJCONF_1;
		(buff[idx]->owner)->Command(CMD_MRK_DIRTY, 0L, 0L);
		}
}

int
UndoObj::SaveLFP(GraphObj *go, lfPOINT *lfp, DWORD flags)
{
	int idx;
	void *ptr;

	if(!(ptr = memdup(lfp, sizeof(lfPOINT), 0))) return -1;
	if(0 > (idx = NewItem(UNDO_LFP, flags, go, ptr, (void**)lfp))) free(ptr);
	return idx;
}

void
UndoObj::MoveObj(GraphObj *go, lfPOINT *lfp, DWORD flags)
{
	int idx;
	lfPOINT dsp;

	if(!lfp) return;
	dsp.fx = -1.0 * lfp->fx;		dsp.fy = -1.0 * lfp->fy;
	if((idx = SaveLFP(go, &dsp, flags)) <0) return;
	buff[idx]->cmd = UNDO_MOVE;
}

void
UndoObj::ValRect(GraphObj *go, fRECT *rec, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(rec, sizeof(fRECT), 0))) return;
	if(0 > NewItem(UNDO_RECT, flags, go, ptr, (void**)rec)) free(ptr);
}

int
UndoObj::String(GraphObj *go, char **s, DWORD flags)
{
	char *ptr;
	int iret;

	if(s && *s &&  *(*s)){
		iret = (int)strlen(*(s));
		ptr = (char*)memdup(*(s), iret+1, 0);
		}
	else {
		ptr = 0L;				iret = 0;
		}
	if(0 > NewItem(UNDO_STRING, flags, go, ptr, (void**)s)) if(ptr) free(ptr);
	return iret;
}

void
UndoObj::RotDef(GraphObj *go, double **d, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(*d, 6 * sizeof(double), 0))) return;
	if(0 > NewItem(UNDO_ROTDEF, flags, go, ptr, (void**)d)) free(ptr);
}

void
UndoObj::SetGO(GraphObj *parent, GraphObj **where, GraphObj *go, DWORD flags)
{
	*where = go;
	NewItem(UNDO_SETGO, flags, parent, 0L, (void**)where);
}

void
UndoObj::Line(GraphObj *go, LineDEF *ld, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(ld, sizeof(LineDEF), 0))) return;
	if(0 > NewItem(UNDO_LINEDEF, flags, go, ptr, (void**)ld)) free(ptr);
}

void
UndoObj::Fill(GraphObj *go, FillDEF *fd, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(fd, sizeof(FillDEF), 0))) return;
	if(0 > NewItem(UNDO_FILLDEF, flags, go, ptr, (void**)fd)) free(ptr);
}

void
UndoObj::AxisDef(GraphObj *go, AxisDEF *ad, DWORD flags)
{
	AxisDEF *ptr;

	if(!(ptr = (AxisDEF*) memdup(ad, sizeof(AxisDEF), 0))) return;
	if(ptr->nBreaks && ptr->breaks) ptr->breaks = 
		(lfPOINT*)memdup(ad->breaks, ad->nBreaks * sizeof(lfPOINT), 0);
	if(0 > NewItem(UNDO_AXISDEF, flags, go, ptr, (void**)ad)) free(ptr);
}

void
UndoObj::TextDef(GraphObj *go, TextDEF *td, DWORD flags)
{
	TextDEF *ptr;

	if(!(ptr = (TextDEF*) memdup(td, sizeof(TextDEF), 0))) return;
	if(td->text && td->text[0]) ptr->text = (char*)memdup(td->text, (int)strlen(td->text)+1, 0);
	if(0 > NewItem(UNDO_TEXTDEF, flags, go, ptr, (void**)td)){
		if(ptr->text) free(ptr->text);		free(ptr);
		}
}

void
UndoObj::ValLFP3D(GraphObj *go, fPOINT3D *lfp, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(lfp, sizeof(fPOINT3D), 0))) return;
	if(0 > NewItem(UNDO_LFP3D, flags, go, ptr, (void**)lfp)) free(ptr);
}

void
UndoObj::ValFloat(GraphObj *parent, double *val, DWORD flags)
{
	void *ptr;

	if(!(ptr = memdup(val, sizeof(double), 0))) return;
	if(0 > NewItem(UNDO_FLOAT, flags, parent, ptr, (void**)val)) free(ptr);
}

void
UndoObj::DataMem(GraphObj *go, void **mem, int size, long *count, DWORD flags)
{
	UndoList *ul;

	if(ul = (UndoList *)malloc(sizeof(UndoList))) {
		if(ul->array = memdup(*mem, size, 0)){
			ul->loc_arr = (void **)mem;
			ul->size = size;
			ul->count = *count;
			ul->loc_count = count;
			if(0 > NewItem(UNDO_MEM, flags, go, ul, 0L)) {
				free(ul->array);			free(ul);
				}
			}
		else free(ul);
		}
}

void
UndoObj::DataObject(GraphObj *go, anyOutput *o, DataObj *d, RECT *rc, DWORD flags)
{
	StrData *save;

	if(!go || !d) return;
	if(o) SetDisp(o);
	if(!(save = new StrData(d, rc))) return;
	if(0 > NewItem(UNDO_DATA, flags, go, save, (void**)d)) if(save) delete(save);
}

void 
UndoObj::TextCell(EditText *et, anyOutput *o, char *text, int *cur, int *m1, int *m2, void* DaO, DWORD flags)
{
	anyOutput *o_save;
	EtBuff *ptr;
	POINT cpos = {-1, -1};

	if(o) {
		o_save = cdisp;		SetDisp(o);
		if(ptr = (EtBuff*) calloc(1, sizeof(EtBuff))) {
			if(text && text[0]) ptr->txt = (char*)memdup(text, (int)strlen(text)+1, 0);
			ptr->cur = cur;			ptr->m1 = m1;		ptr->m2 = m2;
			ptr->vcur = *cur;		ptr->vm1 = *m1;		ptr->vm2 = *m2;
			ptr->row = et->row;		ptr->col = et->col;
			ptr->DaO = (DataObj*)et->parent;
			if(0 > NewItem(UNDO_ET, flags, 0L, ptr, (void**)et)) {
				if(ptr->txt)free (ptr->txt);			free(ptr);
				}
			}
		SetDisp(o_save);
		}
}

void
UndoObj::TextBuffer(GraphObj *parent, int *psize, int *ppos, unsigned char **pbuff, DWORD flags, anyOutput *o)
{
	TextBuff *ptr;

	if(o) SetDisp(o);
	if(!parent || !psize || !ppos || !pbuff) return;
	if(ptr = (TextBuff*)calloc(1, sizeof(TextBuff))) {
		ptr->size = *psize;			ptr->psize = psize;
		ptr->pos = *ppos;			ptr->ppos = ppos;
		ptr->pbuff = pbuff;			ptr->buff = (unsigned char*)memdup(*pbuff, ptr->size, 0);
		if(0 > NewItem(UNDO_TEXTBUF, flags, parent, ptr, (void**)pbuff)) {
			if(ptr->buff) free(ptr->buff);			free(ptr);
			}
		}
}

int
UndoObj::NewItem(int cmd, DWORD flags, GraphObj *owner, void *data, void **loc)
{
	UndoInfo *tmp;
	int idx;

	if(!buff || !cdisp) return -1;
	if(!(tmp = (UndoInfo *)malloc(sizeof(UndoInfo))))return -1;
	tmp->cmd = cmd;			tmp->flags = flags;
	tmp->owner = owner;		tmp->data = data;
	tmp->loc = loc;
	tmp->zd.org.fx = cdisp->VPorg.fx;
	tmp->zd.org.fy = cdisp->VPorg.fy;
	tmp->zd.scale = cdisp->VPscale;
	idx = (*pcb & UNDO_IDX_MASK);
	if(buff[idx]) FreeInfo(&buff[idx]);
	buff[idx] = tmp;
	(*pcb)++;
	return idx;
}

void
UndoObj::FreeInfo(UndoInfo** inf)
{
	int i;
	UndoList *ul;
	GraphObj *go, **gol;

	if(!inf || !(*inf)) return;
	switch((*inf)->cmd) {
	case UNDO_SETGO:
		break;
	case UNDO_DATA:
		delete ((StrData*)((*inf)->data));
		break;
	case UNDO_ET:
		if(((EtBuff*)((*inf)->data))->txt) free(((EtBuff*)((*inf)->data))->txt);
		free((*inf)->data);
		break;
	case UNDO_TEXTBUF:
		if(((TextBuff*)((*inf)->data))->buff) free(((TextBuff*)((*inf)->data))->buff);
		free((*inf)->data);
		break;
	case UNDO_MUTATE:
	case UNDO_DEL_GO:
		go = (GraphObj*)((*inf)->data);
		(*inf)->data = 0L;		::DeleteGO(go);
		break;
	case UNDO_DROPGOLIST:
		if((ul = (UndoList *)((*inf)->data)) && (ul->array)) {
			gol = (GraphObj**)(ul->array);
			for (i = 0; i < ul->count; i++) if(gol[i]) ::DeleteGO(gol[i]);
			free(ul->array);				free(ul);
			}
		break;
	case UNDO_GOLIST:	case UNDO_MEM:
		if((ul = (UndoList *)((*inf)->data)) && (ul->array)) {
			free(ul->array);				free(ul);
			}
		break;
	case UNDO_AXISDEF:
		if(((AxisDEF*)(*inf)->data)->breaks) free(((AxisDEF*)(*inf)->data)->breaks);
		free((*inf)->data);
		break;
	case UNDO_TEXTDEF:
		if(((TextDEF*)(*inf)->data)->text) free(((TextDEF*)(*inf)->data)->text);
		free((*inf)->data);
		break;
	case UNDO_DROPMEM:		case UNDO_VALDWORD:		case UNDO_VALINT:
	case UNDO_OBJCONF:		case UNDO_OBJCONF_1:	case UNDO_LFP:
	case UNDO_MOVE:			case UNDO_RECT:			case UNDO_STRING:
	case UNDO_ROTDEF:		case UNDO_LINEDEF:		case UNDO_FILLDEF:
	case UNDO_LFP3D:		case UNDO_FLOAT:		case UNDO_SAVVAR:
	case UNDO_POINT:		case UNDO_VALLONG:
		free((*inf)->data);
		break;
		}
	free(*inf);
	*inf = 0L;
}

class UndoUtil:public GraphObj {
public:
	GraphObj *res;

	UndoUtil(GraphObj *p, GraphObj *old):GraphObj(0L, 0L){root = p; optr = old; res = 0L;};
	bool Command(int cmd, void *tmpl, anyOutput *o);

private:
	GraphObj *root, *optr;
};

bool
UndoUtil::Command(int cmd, void *tmpl, anyOutput *o)
{
	GraphObj *xch[2];

	switch(cmd){
	case CMD_DROP_GRAPH:
		//we come here if conversion of undo-information is
		//   successfully converted into a tree of graphic objects.
		//   Now ask the parent object to replace the modified
		//   object with a previous version (i.e. undo modifications).
		xch[0] = optr;
		xch[1] = res = (GraphObj*)tmpl;
		if(root) return root->Command(CMD_REPL_GO, xch, o);
		break;
		}
	return false;
}

void
UndoObj::RestoreConf(UndoInfo *inf)
{
	UndoUtil *proc;
	int i;

	//Create a message object which will accept the translated graphic
	//   object or tree of objects, and which will forward it to the parent
	//   of the tree finalizing undo.
	if(!inf->data) return;
	if(!(proc = new UndoUtil(inf->owner, (GraphObj*)inf->loc))) return; 
	OpenGraph(proc, 0L, (unsigned char *)inf->data, false);
	if(proc->res) for(i = 0; i < UNDO_RING_SIZE; i++) {
		if(buff[i] && buff[i]->owner == (GraphObj*)inf->loc) FreeInfo(&buff[i]);
		if(buff[i] && buff[i]->cmd == UNDO_OBJCONF){
			if(buff[i]->loc == inf->loc) buff[i]->loc = (void**)proc->res;
			}
		}
	delete proc;
}

void
UndoObj::RestoreData(UndoInfo *inf)
{
	DataObj *od;
	StrData *nd;
	int w, h;

	if(!(nd = (StrData*)inf->data) || !(od = (DataObj*)inf->loc)){
		if(nd) delete(nd);		return;
		}
	nd->GetSize(&w, &h);		od->ChangeSize(w, h, false);
	nd->RestoreData(od);		delete(nd);
}

#undef UNDO_RING_SIZE
#undef UNDO_IDX_MASK
