//use_gui.cpp, Copyright 2000-2008 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
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This modules contains code for builds using a graphical user interface.
// Builds running without GUI use no_gui.cpp instead.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#include "rlplot.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern char TmpTxt[];
extern Default defs;
extern GraphObj *CurrGO, *TrackGO;			//Selected Graphic Objects
extern Graph *CurrGraph;
extern dragHandle *CurrHandle;
extern UndoObj Undo;
extern fmtText DrawFmtText;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Spread sheet buttons used for rows and columns
ssButton::ssButton(GraphObj *par, int x, int y, int w, int h):GraphObj(par, 0L)
{
	bLBdown = bSelected = bMarked = false;
	SetMinMaxRect(&rDims, x, y, x+w, y+h);
	Line.width = 0.0f;				Line.patlength = 1.0f;
	Line.color = 0x00000000L;		Line.pattern = 0x00000000L;
	Fill.type = FILL_NONE;			Fill.color = 0x00e8e8e8L;
	Fill.scale = 1.0;				Fill.hatch = NULL;
	TextDef.ColTxt = 0x00000000L;	TextDef.ColBg = 0x00ffffffL;
	TextDef.fSize = 4.0;			TextDef.RotBL = TextDef.RotCHAR = 0.0;
	TextDef.iSize = 0;				TextDef.Align = TXA_HLEFT | TXA_VTOP;
	TextDef.Mode = TXM_TRANSPARENT;	TextDef.Style = TXS_NORMAL;
	TextDef.Font = FONT_HELVETICA;	TextDef.text = 0L;
}

ssButton::~ssButton()
{
	if(TextDef.text) free(TextDef.text);
}

void
ssButton::DoPlot(anyOutput *o)
{
	POINT pts[3];

	Line.color = 0x00000000L;
	if(bSelected) {
		Fill.color = bMarked ? 0x00ffffe0L : 0x00ffffffL;
		}
	else {
		Fill.color = bMarked ? 0x00ffe0e0L : 0x00e8e8e8L;
		}
	o->SetLine(&Line);				o->SetFill(&Fill);
	if(bLBdown){
		o->oRectangle(rDims.left, rDims.top, rDims.right-1, rDims.bottom-1);
		}
	else {
		o->oRectangle(rDims.left, rDims.top, rDims.right-2, rDims.bottom-2);
		Line.color = 0x00000000L;
		o->SetLine(&Line);
		pts[0].x = rDims.left;					pts[0].y = pts[1].y = rDims.bottom-1;
		pts[1].x = pts[2].x = rDims.right-1;	pts[2].y = rDims.top-1;
		o->oPolyline(pts, 3);
		Line.color = 0x00ffffffL;
		o->SetLine(&Line);
		pts[0].x = pts[1].x = rDims.left;		pts[0].y = rDims.bottom -3;
		pts[1].y = pts[2].y = rDims.top;		pts[2].x = rDims.right -2;
		o->oPolyline(pts, 3);
		}
	if(TextDef.text) {
		o->SetTextSpec(&TextDef);
#ifdef _WINDOWS
		o->oTextOut((rDims.left + rDims.right)/2-2, (rDims.top + rDims.bottom)/2-1, TextDef.text, 0);
#else
		o->oTextOut((rDims.left + rDims.right)/2-2, (rDims.top + rDims.bottom)/2+1, TextDef.text, 0);
#endif
		}

}
void
ssButton::DoMark(anyOutput *o, bool mark)
{
	bLBdown = mark;
	DoPlot(o);
	o->UpdateRect(&rDims, false);
}

bool
ssButton::Command(int cmd, void *tmpl, anyOutput *o)
{
	char *tmptxt;
	MouseEvent *mev;

	switch (cmd) {
	case CMD_GETTEXT:
		if(TextDef.text && tmpl) {
			rlp_strcpy((char*)tmpl, 10, TextDef.text);
			return true;
			}
		return false;
	case CMD_SELECT:
		if(tmpl && *((int*)tmpl)) bSelected = true;
		else bSelected = false;
		if(o) {
			DoPlot(o);
			o->UpdateRect(&rDims, false);
			}
		return true;
	case CMD_SETSTYLE:
		if(tmpl && *((int*)tmpl)) bMarked = true;
		else bMarked = false;
		if(o) {
			DoPlot(o);		o->UpdateRect(&rDims, false);
			}
		return true;
	case CMD_SETTEXT:
		if(tmpl) {
			if(! TextDef.text) TextDef.text = (char*)malloc(10*sizeof(char));
			rlp_strcpy(TextDef.text, 10, (char*)tmpl);
			}
		return true;
	case CMD_GETTEXTDEF:
		if(!tmpl) return false;
		memcpy(tmpl, &TextDef, sizeof(TextDEF));
		return true;
	case CMD_SETTEXTDEF:
		if(!tmpl)return false;
		tmptxt = TextDef.text;
		memcpy(&TextDef, tmpl, sizeof(TextDEF));
		TextDef.text = tmptxt;
		return true;
	case CMD_MOUSE_EVENT:
		if(!o || !(mev = (MouseEvent *) tmpl)) break;
		if(IsInRect(&rDims, mev->x, mev->y)) {
			if(bLBdown) {
				if(!(mev->StateFlags & 0x01)) {
					o->HideMark();
					if(bLBdown) {
						bLBdown = false; DoPlot(o);
						}
					}
				}
			else if(mev->StateFlags & 0x01) {
				o->ShowMark(this, MRK_SSB_DRAW);
				}
			return true;
			}
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// drag handles are graphic objects which allow the user to interactively
//   change the size of an object
dragHandle::dragHandle(GraphObj *par, int which):GraphObj(par, 0L)
{
	type = which;
	Id = GO_DRAGHANDLE;
	LineDef.width = LineDef.patlength = 0.0;
	LineDef.color = LineDef.pattern = 0L;
	FillDef.type = FILL_NONE;
	FillDef.color = 0x00ffffffL;
	FillDef.scale = 1.0;
	FillDef.hatch = 0L;
	minRC = maxRC = 0L;
}

dragHandle::~dragHandle()
{
	if(minRC) free(minRC);
	if(maxRC) free(maxRC);
	if(CurrHandle == this) CurrHandle = 0L;
}

void
dragHandle::DoPlot(anyOutput *o)
{
	double fx, fy, dx, dy;
	int ix, iy, idx;
	fPOINT3D fp3d, ifp3d;
	bool is3D = false;

	if(!o || !parent) return;
	dx = parent->GetSize(SIZE_GRECT_LEFT);	dy = parent->GetSize(SIZE_GRECT_TOP);
	SetMinMaxRect(&drec, o->co2ix(parent->GetSize(SIZE_XPOS)+dx), 
		o->co2iy(parent->GetSize(SIZE_YPOS)+dy), o->co2ix(parent->GetSize(SIZE_XPOS+1)+dx),
		o->co2iy(parent->GetSize(SIZE_YPOS+1)+dy));
	switch(type) {
	case DH_19:		case DH_12:
		fx = parent->GetSize(SIZE_XPOS);	fy = parent->GetSize(SIZE_YPOS);
		break;
	case DH_99:		case DH_22:
		fx = parent->GetSize(SIZE_XPOS+1);	fy = parent->GetSize(SIZE_YPOS+1);
		break;
	case DH_29:
		fx = (parent->GetSize(SIZE_XPOS) + parent->GetSize(SIZE_XPOS+1))/2.0;
		fy = parent->GetSize(SIZE_YPOS);
		break;
	case DH_39:
		fx = parent->GetSize(SIZE_XPOS+1);	fy = parent->GetSize(SIZE_YPOS);
		break;
	case DH_49:
		fx = parent->GetSize(SIZE_XPOS);
		fy = (parent->GetSize(SIZE_YPOS) + parent->GetSize(SIZE_YPOS+1))/2.0;
		break;
	case DH_59:
		fx = (parent->GetSize(SIZE_XPOS) + parent->GetSize(SIZE_XPOS+1))/2.0;
		fy = (parent->GetSize(SIZE_YPOS) + parent->GetSize(SIZE_YPOS+1))/2.0;
		break;
	case DH_69:
		fx = parent->GetSize(SIZE_XPOS+1);
		fy = (parent->GetSize(SIZE_YPOS) + parent->GetSize(SIZE_YPOS+1))/2.0;
		break;
	case DH_79:
		fx = parent->GetSize(SIZE_XPOS);
		fy = parent->GetSize(SIZE_YPOS+1);
		break;
	case DH_89:
		fx = (parent->GetSize(SIZE_XPOS) + parent->GetSize(SIZE_XPOS+1))/2.0;
		fy = parent->GetSize(SIZE_YPOS+1);
		break;
	case DH_18:	case DH_28:	case DH_38:	case DH_48:
	case DH_58:	case DH_68:	case DH_78:	case DH_88:
		fp3d.fx = parent->GetSize(SIZE_XPOS + type - DH_18);
		fp3d.fy = parent->GetSize(SIZE_YPOS + type - DH_18);
		fp3d.fz = parent->GetSize(SIZE_ZPOS + type - DH_18);
		is3D = true;
		break;
	default:
		if(type >= DH_DATA) {
			fx = parent->GetSize(SIZE_XPOS + type - DH_DATA);
			fy = parent->GetSize(SIZE_YPOS + type - DH_DATA);
			FillDef.color = (this == CurrHandle) ? 0x0L : 0x00ffffffL;
			}
		else return;
		}
	if(is3D) {
		o->cvec2ivec(&fp3d, &ifp3d);
		ix = iround(ifp3d.fx);		iy = iround(ifp3d.fy);
		}
	else {
		ix = o->co2ix(fx+dx);	iy = o->co2iy(fy+dy);
		}
	SetMinMaxRect(&rDims, ix-4, iy-4, ix+4, iy+4);
	memcpy(&upd, &rDims, sizeof(RECT));
	switch(type) {
	case DH_12:	case DH_22:	case DH_19:	case DH_29:	case DH_39:
	case DH_49:	case DH_69:	case DH_79:	case DH_89:	case DH_99:
		o->SetLine(&LineDef);		o->SetFill(&FillDef);
		o->oRectangle(rDims.left, rDims.top, rDims.right, rDims.bottom);
		o->UpdateRect(&rDims, false);
		break;
	case DH_59:
		o->SetLine(&LineDef);		o->SetFill(&FillDef);
		IncrementMinMaxRect(&rDims, 2);
		o->oCircle(rDims.left, rDims.top, rDims.right, rDims.bottom);
		IncrementMinMaxRect(&rDims, 1);
		o->UpdateRect(&rDims, false);
		break;
	default:
		if(type >= DH_DATA) {
			idx = (type - DH_DATA);
			o->SetLine(&LineDef);		o->SetFill(&FillDef);
			if(parent->Id == GO_BEZIER && (idx %3)) {
				IncrementMinMaxRect(&rDims, -1);
				o->oRectangle(rDims.left, rDims.top, rDims.right, rDims.bottom);
				}
			else if(parent->Id == GO_POLYLINE || parent->Id == GO_BEZIER) {
				o->oCircle(rDims.left, rDims.top, rDims.right, rDims.bottom);
				}
			else {
				o->oRectangle(rDims.left, rDims.top, rDims.right, rDims.bottom);
				}
			o->UpdateRect(&rDims, false);
			}
		else {
			IncrementMinMaxRect(&rDims, -1);
#ifdef _WINDOWS
			o->UpdateRect(&rDims, true);
#else
			o->ShowInvert(&rDims);
#endif
			}
		break;
		}
}

bool
dragHandle::Command(int cmd, void *tmpl, anyOutput *o)
{
	lfPOINT pos;
	int idx;

	if(!parent) return false;
	switch (cmd) {
	case CMD_MOUSECURSOR:
		if(o) switch(type) {
		case DH_19:	case DH_99:
			o->MouseCursor(MC_SE, false);							break;
		case DH_29:	case DH_89:
			o->MouseCursor(MC_NORTH, false);						break;
		case DH_39:	case DH_79:
			o->MouseCursor(MC_NE, false);							break;
		case DH_49:	case DH_69:
			o->MouseCursor(MC_EAST, false);							break;
		default:
			if(type >= DH_DATA) o->MouseCursor(MC_SALL, false);
			else o->MouseCursor(MC_MOVE, false);					break;
			}
		return true;
	case CMD_MINRC:
		if(!(minRC)) minRC = (RECT*)calloc(1, sizeof(RECT));
		if(tmpl && minRC) SetMinMaxRect(minRC, ((RECT*)tmpl)->left, ((RECT*)tmpl)->top, 
			((RECT*)tmpl)->right, ((RECT*)tmpl)->bottom);
		return true;
	case CMD_MAXRC:
		if(!(maxRC)) maxRC = (RECT*)calloc(1, sizeof(RECT));
		if(tmpl && maxRC) SetMinMaxRect(maxRC, ((RECT*)tmpl)->left, ((RECT*)tmpl)->top, 
			((RECT*)tmpl)->right, ((RECT*)tmpl)->bottom);
		return true;
	case CMD_MOVE:
		idx = type >= DH_DATA ? type - DH_DATA : 0;
		pos.fx = NiceValue(((lfPOINT*)tmpl)[0].fx);
		pos.fy = NiceValue(((lfPOINT*)tmpl)[0].fy);
		if(pos.fx == 0.0 && pos.fy == 0.0) return false;
		parent->Command(CMD_SAVEPOS, &idx, o);
		switch(type) {
		case DH_12:
			parent->SetSize(SIZE_XPOS, pos.fx + parent->GetSize(SIZE_XPOS));
			parent->SetSize(SIZE_YPOS, pos.fy + parent->GetSize(SIZE_YPOS));
			break;
		case DH_22:
			parent->SetSize(SIZE_XPOS+1, pos.fx + parent->GetSize(SIZE_XPOS+1));
			parent->SetSize(SIZE_YPOS+1, pos.fy + parent->GetSize(SIZE_YPOS+1));
			break;
		case DH_19: parent->parent->SetSize(SIZE_XPOS, 
						pos.fx + parent->GetSize(SIZE_XPOS));
		case DH_29: parent->parent->SetSize(SIZE_YPOS,
						pos.fy + parent->GetSize(SIZE_YPOS));
			break;
		case DH_39: parent->parent->SetSize(SIZE_YPOS,
						pos.fy + parent->GetSize(SIZE_YPOS));
		case DH_69: parent->parent->SetSize(SIZE_XPOS+1,
						pos.fx + parent->GetSize(SIZE_XPOS+1));
			break;
		case DH_99: parent->parent->SetSize(SIZE_XPOS+1, 
						pos.fx + parent->GetSize(SIZE_XPOS+1));
		case DH_89: parent->parent->SetSize(SIZE_YPOS+1,
						pos.fy + parent->GetSize(SIZE_YPOS+1));
			break;
		case DH_79: parent->parent->SetSize(SIZE_YPOS+1,
						pos.fy + parent->GetSize(SIZE_YPOS+1));
		case DH_49:
			parent->parent->SetSize(SIZE_XPOS, 
						pos.fx + parent->GetSize(SIZE_XPOS));
			break;
		case DH_18:	case DH_28:	case DH_38:	case DH_48:
		case DH_58:	case DH_68:	case DH_78:	case DH_88:
		case DH_59:
			return parent->parent->Command(cmd, tmpl, o);
		default:
			if (type >= DH_DATA) {
				parent->SetSize(SIZE_XPOS + idx, pos.fx + parent->GetSize(SIZE_XPOS + idx));
				parent->SetSize(SIZE_YPOS + idx, pos.fy + parent->GetSize(SIZE_YPOS + idx));
				CurrGO = parent;
				}
			break;
			}
		return parent->Command(CMD_REDRAW, 0L, o);
		break;
		}
	return false;
}

void *
dragHandle::ObjThere(int x, int y)
{
	if(IsInRect(&rDims, x, y)) return (CurrHandle = this);
	else return 0L;
}


void
dragHandle::Track(POINT *p, anyOutput *o)
{
	POINT p1, p2, pts[5], bez[256];
	double dx, dy;
	int i, first=0, last = 0, idx;
	long nbez;
	DWORD color;

	if(!parent || !o) return;
	Command(CMD_MOUSECURSOR, 0L, o);
	if(upd.right < upd.left) Swap(upd.right, upd.left);
	if(upd.bottom < upd.top) Swap(upd.bottom, upd.top);
	dx = parent->GetSize(SIZE_GRECT_LEFT);	dy = parent->GetSize(SIZE_GRECT_TOP);
	IncrementMinMaxRect(&upd, 2);
	o->UpdateRect(&upd, false);
	if(type >= DH_19 && type <= DH_99) memcpy(&upd, &drec, sizeof(RECT));
	color = parent->GetColor(COL_DATA_LINE);
	switch(type) {
	case DH_12:
	case DH_22:
		pts[0].x = o->co2ix(parent->GetSize(SIZE_XPOS)+dx);
		pts[0].y = o->co2iy(parent->GetSize(SIZE_YPOS)+dy);
		pts[1].x = o->co2ix(parent->GetSize(SIZE_XPOS+1)+dx);
		pts[1].y = o->co2iy(parent->GetSize(SIZE_YPOS+1)+dy);
		if(type == DH_12) {
			pts[0].x += p->x;			pts[0].y += p->y;
			}
		else if(type == DH_22) {
			pts[1].x += p->x;			pts[1].y += p->y;
			}
		UpdateMinMaxRect(&upd, pts[0].x, pts[0].y);
		UpdateMinMaxRect(&upd, pts[1].x, pts[1].y);
		last = 2;
		break;
	case DH_19:
		if(minRC && IsInRect(minRC, upd.left+p->x, (upd.top+upd.bottom)>>1))
			upd.left = minRC->left;
		else if(maxRC && !IsInRect(maxRC, upd.left+p->x, (upd.top+upd.bottom)>>1))
			upd.left = upd.left+p->x >= maxRC->right ? maxRC->right : maxRC->left;
		else upd.left += p->x;
	case DH_29:
		if(minRC && IsInRect(minRC, (upd.left + upd.right)>>1, upd.top+p->y))
			upd.top = minRC->top;
		else if(maxRC && !IsInRect(maxRC, (upd.left + upd.right)>>1, upd.top+p->y))
			upd.top = upd.top +p->y >= maxRC->bottom? maxRC->bottom : maxRC->top;
		else upd.top += p->y;
		break;
	case DH_39:
		if(minRC && IsInRect(minRC, (upd.left + upd.right)>>1, upd.top+p->y))
			upd.top = minRC->top;
		else if(maxRC && !IsInRect(maxRC, (upd.left + upd.right)>>1, upd.top+p->y))
			upd.top = upd.top+p->y >= maxRC->bottom ? maxRC->bottom : maxRC->top;
		else upd.top += p->y;
	case DH_69:
		if(minRC && IsInRect(minRC, upd.right+p->x, (upd.top+upd.bottom)>>1))
			upd.right = minRC->right;
		else if(maxRC && !IsInRect(maxRC, upd.right+p->x, (upd.top+upd.bottom)>>1))
			upd.right = upd.right+p->x <= maxRC->left ? maxRC->left : maxRC->right;
		else upd.right += p->x;
		break;
	case DH_99:
		if(minRC && IsInRect(minRC, upd.right+p->x, (upd.top+upd.bottom)>>1))
			upd.right = minRC->right;
		else if(maxRC && !IsInRect(maxRC, upd.right+p->x, (upd.top+upd.bottom)>>1))
			upd.right = upd.right+p->x <= maxRC->left ? maxRC->left : maxRC->right;
		else upd.right += p->x;
	case DH_89:
		if(minRC && IsInRect(minRC, (upd.left + upd.right)>>1, upd.bottom+p->y))
			upd.bottom = minRC->bottom;
		else if(maxRC && !IsInRect(maxRC, (upd.left + upd.right)>>1, upd.bottom+p->y))
			upd.bottom = upd.bottom+p->y <= maxRC->top? maxRC->top : maxRC->bottom;
		else upd.bottom += p->y;
		break;
	case DH_79:
		if(minRC && IsInRect(minRC, (upd.left + upd.right)>>1, upd.bottom+p->y))
			upd.bottom = minRC->bottom;
		else if(maxRC && !IsInRect(maxRC, (upd.left + upd.right)>>1, upd.bottom+p->y))
			upd.bottom = upd.bottom+p->y <= maxRC->top? maxRC->top : maxRC->bottom;
		else upd.bottom += p->y;
	case DH_49:
		if(minRC && IsInRect(minRC, upd.left+p->x, (upd.top+upd.bottom)>>1))
			upd.left = minRC->left;
		else if(maxRC && !IsInRect(maxRC, upd.left+p->x, (upd.top+upd.bottom)>>1))
			upd.left = upd.left+p->x >= maxRC->right ? maxRC->right : maxRC->left;
		else upd.left += p->x;
		break;
	case DH_18:	case DH_28:	case DH_38:	case DH_48:
	case DH_58:	case DH_68:	case DH_78:	case DH_88:
		CurrGO = this;
	case DH_59:
		parent->parent->Track(p, o);
		return;
	default:
		if(type >= DH_DATA) {
			idx = type - DH_DATA;
			pts[1].x = o->co2ix(parent->GetSize(SIZE_XPOS + idx)+dx);
			pts[1].y = o->co2iy(parent->GetSize(SIZE_YPOS + idx)+dy);
			pts[1].x += p->x;				pts[1].y += p->y;
			if(type > DH_DATA) {
				pts[0].x = o->co2ix(parent->GetSize(SIZE_XPOS + idx -1)+dx);
				pts[0].y = o->co2iy(parent->GetSize(SIZE_YPOS + idx -1)+dy);
				}
			else {
				pts[0].x = pts[1].x;		pts[0].y = pts[1].y;
				}
			pts[3].x = pts[2].x = o->co2ix(parent->GetSize(SIZE_XPOS + idx +1)+dx);
			pts[3].y = pts[2].y = o->co2iy(parent->GetSize(SIZE_YPOS + idx +1)+dy);
			UpdateMinMaxRect(&upd, pts[0].x, pts[0].y);
			UpdateMinMaxRect(&upd, pts[1].x, pts[1].y);
			UpdateMinMaxRect(&upd, pts[2].x, pts[2].y);
			if(parent ->Id == GO_BEZIER) {
				switch(idx % 3) {
				case 0:
					o->ShowLine(pts, 3, 0x00c0c0c0L);
					pts[0].x = pts[1].x;		pts[0].y = pts[1].y;
					for(nbez = 0, i = 1; i < 4; i++) {
						pts[i].x = o->co2ix(parent->GetSize(SIZE_XPOS + idx +i)+dx);
						pts[i].y = o->co2iy(parent->GetSize(SIZE_YPOS + idx +i)+dy);
						}
					DrawBezier(&nbez, bez, pts[0], pts[1], pts[2], pts[3], 0);
					o->ShowLine(bez, nbez, color);
					if(idx < 3) return;
					pts[3].x = pts[0].x;		pts[3].y = pts[0].y;
					for(i = nbez = 0, idx -= 3; i < 3; i++) {
						pts[i].x = o->co2ix(parent->GetSize(SIZE_XPOS + idx +i)+dx);
						pts[i].y = o->co2iy(parent->GetSize(SIZE_YPOS + idx +i)+dy);
						}
					DrawBezier(&nbez, bez, pts[0], pts[1], pts[2], pts[3], 0);
					o->ShowLine(bez, nbez, color);
					return;
				case 1:		
					pts[3].x = o->co2ix(parent->GetSize(SIZE_XPOS + idx +2)+dx);
					pts[3].y = o->co2iy(parent->GetSize(SIZE_YPOS + idx +2)+dy);
					last = 2;				break;
				case 2:	
					pts[2].x = pts[1].x;	pts[2].y = pts[1].y;
					pts[1].x = pts[0].x;	pts[1].y = pts[0].y;
					pts[0].x = o->co2ix(parent->GetSize(SIZE_XPOS + idx -2)+dx);
					pts[0].y = o->co2iy(parent->GetSize(SIZE_YPOS + idx -2)+dy);
					first = 2;	last = 4;	break;
					}
				if(idx %3) {
					nbez = 0;
					DrawBezier(&nbez, bez, pts[0], pts[1], pts[2], pts[3], 0);
					o->ShowLine(bez, nbez, color);
					}
				color = 0x00c0c0c0L;
				}
			else last = 3;
			if(color == 0x0L || color == 0x00ffffffL) color = 0x00c0c0c0L;
			}
		else return;
		}
	if(type >= DH_19 && type <= DH_99) {
		pts[0].x = pts[4].x = pts[3].x = p1.x = upd.left;	
		pts[0].y = pts[1].y = pts[4].y = p1.y = upd.top;
		pts[1].x = pts[2].x = p2.x = upd.right;	
		pts[2].y = pts[3].y = p2.y = upd.bottom;
		last = 5;
		if(parent->parent->Id == GO_ELLIPSE) o->ShowEllipse(p1, p2, color);
		}
	o->ShowLine(pts+first, last-first, color);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// the dragRect object uses nine dragHandles to create a user interface
//   for modification of rectangular shapes
dragRect::dragRect(GraphObj *par, int which):GraphObj(par, 0L)
{
	int i;

	type = which;
	Id = GO_DRAGRECT;
	if(handles = (dragHandle**)calloc(9, sizeof(dragHandle*)))
		for(i = 0; i < 9; i++){
			if(i == 4 && type == 0) handles[i] = new dragHandle(this, DH_19 + i);
			else if(i != 4) handles[i] = new dragHandle(this, DH_19 + i);
			}
}

dragRect::~dragRect()
{
	int i;

	if(handles) for(i = 0; i < 9; i++) if(handles[i]) DeleteGO(handles[i]);
}

void
dragRect::DoPlot(anyOutput *o)
{
	int i;

	if(handles) for(i = 0; i < 9; i++) if(handles[i]) handles[i]->DoPlot(o);
}

bool
dragRect::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;

	if(!parent) return false;
	switch (cmd) {
	case CMD_MINRC:
	case CMD_MAXRC:
		if(handles) for(i = 0; i < 9; i++) {
			if(handles[i]) handles[i]->Command(cmd, tmpl, o);
			}
		break;
	case CMD_SAVEPOS:
	case CMD_REDRAW:
		return parent->Command(cmd, tmpl, o);
		}
	return false;
}

void *
dragRect::ObjThere(int x, int y)
{
	int i;
	void *go;

	if(handles)	for(i = 0; i < 9; i++) 
		if(handles[i] && (go = (handles[i])->ObjThere(x, y))) return go;
	return 0L;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// implement some kind of virtual trackball for 3D plots
Drag3D::Drag3D(GraphObj *par):GraphObj(par, 0L)
{
	int i;

	Id = GO_DRAG3D;
	if(handles = (dragHandle**)calloc(8, sizeof(dragHandle*)))
		for(i = 0; i < 8; i++){
			handles[i] = new dragHandle(this, DH_18 + i);
			}
}

Drag3D::~Drag3D()
{
	int i;

	if(handles) for(i = 0; i < 8; i++) if(handles[i]) DeleteGO(handles[i]);
}

void
Drag3D::DoPlot(anyOutput *o)
{
	int i;

	if(handles) for(i = 0; i < 8; i++) if(handles[i]) handles[i]->DoPlot(o);
}

void *
Drag3D::ObjThere(int x, int y)
{
	int i;
	void *go;

	if(handles)	for(i = 0; i < 8; i++) 
		if(handles[i] && (go = (handles[i])->ObjThere(x, y))) return go;
	return 0L;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// frame rectangle
FrmRect::FrmRect(GraphObj *par, fRECT *lim, fRECT *c, fRECT *chld):GraphObj(par, 0L)
{
	parent = par;
	limRC = lim;	cRC = c;	chldRC = chld;
	drag = 0L;		mo = 0L;
	Id = GO_FRAMERECT;
	moveable = true;
	Fill.type = FILL_NONE;
	Line.color = FillLine.color = Fill.color = 0x00ffffffL;
	Line.width = FillLine.width = 0.0;
	Line.patlength = FillLine.patlength = Fill.scale = 1.0;
	Line.pattern = FillLine.pattern = 0x0L;
	Fill.hatch = &FillLine;
	minRC = maxRC = 0L;
}

FrmRect::~FrmRect()
{
	if(drag) DeleteGO(drag);		drag = 0L;
	if(minRC) free(minRC);			minRC = 0L;	
	if(maxRC) free(maxRC);			maxRC = 0L;
	if(mo) DelBitmapClass(mo);		mo = 0L;
}

double
FrmRect::GetSize(int select)
{
	switch(select) {
	case SIZE_XPOS:		return CurrRect.Xmin;
	case SIZE_XPOS+1:	return CurrRect.Xmax;
	case SIZE_YPOS:		return CurrRect.Ymin;
	case SIZE_YPOS+1:	return CurrRect.Ymax;
		}
	return 0.0;
}

bool
FrmRect::SetSize(int select, double value)
{
	double tmp, o_left, o_top;

	o_left = cRC->Xmin;		o_top = cRC->Ymin;
	switch (select & 0xfff) {
	case SIZE_XPOS:
		if(limRC) value -= limRC->Xmin;
		if(swapX) cRC->Xmax = value;
		else cRC->Xmin = value;
		break;
	case SIZE_XPOS+1:
		if(limRC) value -= limRC->Xmin;
		if(swapX) cRC->Xmin = value;
		else cRC->Xmax = value;
		break;
	case SIZE_YPOS:
		if(limRC) value -= limRC->Ymin;
		if(swapY) cRC->Ymin = value;
		else cRC->Ymax = value;
		break;
	case SIZE_YPOS+1:
		if(limRC) value -= limRC->Ymin;
		if(swapY) cRC->Ymax = value;
		else cRC->Ymin = value;
		break;
	default: return false;
		}
	if((swapX && cRC->Xmin < cRC->Xmax) || (!swapX && cRC->Xmin > cRC->Xmax)) {
		tmp = cRC->Xmin;	cRC->Xmin = cRC->Xmax;	cRC->Xmax = tmp;
		}
	if((swapY && cRC->Ymin > cRC->Ymax) || (!swapY && cRC->Ymin < cRC->Ymax)) {
		tmp = cRC->Ymin;	cRC->Ymin = cRC->Ymax;	cRC->Ymax = tmp;
		}
	if(chldRC) {		//check if new rectangle is not inside child rectangle
		if(cRC->Xmin > o_left+ chldRC->Xmin) cRC->Xmin = o_left + chldRC->Xmin;
		if(cRC->Xmax < o_left+ chldRC->Xmax) cRC->Xmax = o_left + chldRC->Xmax;
		if(cRC->Ymin > o_top+ chldRC->Ymin) cRC->Ymin = o_top + chldRC->Ymin;
		if(cRC->Ymax < o_top+ chldRC->Ymax) cRC->Ymax = o_top + chldRC->Ymax;
		}
	if(chldRC && (o_left != cRC->Xmin || o_top != cRC->Ymin)) {
		chldRC->Xmin -= (tmp = cRC->Xmin - o_left);		chldRC->Xmax -= tmp;
		chldRC->Ymin -= (tmp = cRC->Ymin - o_top);		chldRC->Ymax -= tmp;
		}
	return true;
}

bool
FrmRect::SetColor(int select, DWORD col)
{
	switch(select & 0xfff){
	case COL_DRECT:
		Line.color = col;
	case COL_BG:
		Fill.color = col;		return true;
	case COL_GRECT:
		Fill.color = col;		return true;
	case COL_GRECTLINE:
		Line.color = col;		return true;
		}
	return false;
}

void 
FrmRect::DoMark(anyOutput *o, bool mark)
{
	if(!parent || !o) return;
	if(!drag && (drag = new dragRect(this, (!limRC && parent->moveable) ? 0 : 1))){
		if(minRC) drag->Command(CMD_MINRC, minRC, o);
		if(maxRC) drag->Command(CMD_MAXRC, maxRC, o);
		}
	if(mark && drag){
		if(mo) DelBitmapClass(mo);					mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		IncrementMinMaxRect(&mrc, 6);
		mo = GetRectBitmap(&mrc, o);
		drag->DoPlot(o);
		o->UpdateRect(&mrc, false);
		}
	else RestoreRectBitmap(&mo, &mrc, o);
}

void
FrmRect::DoPlot(anyOutput *o)
{
	int x1, y1, x2, y2;
	double tmp;

	if(!(cRC) || !o) return;
	o->dFillCol = Fill.color ^ 0x00ffffff;	//force new brush
	o->dLineCol = Line.color ^ 0x00ffffff;	//force new pen
	o->SetLine(&Line);						o->SetFill(&Fill);
	CurrRect.Xmin = cRC->Xmin;				CurrRect.Xmax = cRC->Xmax;
	CurrRect.Ymin = cRC->Ymax;				CurrRect.Ymax = cRC->Ymin;
	if(limRC) {
		CurrRect.Xmin += limRC->Xmin;		CurrRect.Xmax += limRC->Xmin;
		CurrRect.Ymin += limRC->Ymin;		CurrRect.Ymax += limRC->Ymin;
		}
	if(swapX = (CurrRect.Xmin > CurrRect.Xmax)) {
		tmp = CurrRect.Xmin;	CurrRect.Xmin = CurrRect.Xmax;	CurrRect.Xmax = tmp;
		}
	if(swapY = (CurrRect.Ymin > CurrRect.Ymax)) {
		tmp = CurrRect.Ymin;	CurrRect.Ymin = CurrRect.Ymax;	CurrRect.Ymax = tmp;
		}
	o->oRectangle(x1 = o->co2ix(CurrRect.Xmin), y1 = o->co2iy(CurrRect.Ymin), 
		x2 = o->co2ix(CurrRect.Xmax), y2 = o->co2iy(CurrRect.Ymax));
	SetMinMaxRect(&rDims, x1, y1, x2, y2);
}

bool
FrmRect::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;

	if(!parent) return false;
	switch (cmd) {
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !(CurrGO) && (o)){
				o->ShowMark(this, MRK_GODRAW);
				if(!CurrGraph && parent && parent->Id == GO_GRAPH) CurrGraph = (Graph*)parent;
				return true;
				}
			}
		return false;
	case CMD_MINRC:
		if(!(minRC)) minRC = (RECT*)calloc(1, sizeof(RECT));
		if(minRC && tmpl) SetMinMaxRect(minRC, ((RECT*)tmpl)->left, ((RECT*)tmpl)->top, 
			((RECT*)tmpl)->right, ((RECT*)tmpl)->bottom);
		if(drag) drag->Command(cmd, tmpl, o);
		return true;
	case CMD_MAXRC:
		if(!(maxRC)) maxRC = (RECT*)calloc(1, sizeof(RECT));
		if(maxRC && tmpl) SetMinMaxRect(maxRC, ((RECT*)tmpl)->left, ((RECT*)tmpl)->top, 
			((RECT*)tmpl)->right, ((RECT*)tmpl)->bottom);
		if(drag) drag->Command(cmd, tmpl, o);
		return true;
	case CMD_MOVE:
		cRC->Xmin += NiceValue(((lfPOINT*)tmpl)[0].fx);
		cRC->Ymin += NiceValue(((lfPOINT*)tmpl)[0].fy);
		cRC->Xmax += NiceValue(((lfPOINT*)tmpl)[0].fx);
		cRC->Ymax += NiceValue(((lfPOINT*)tmpl)[0].fy);
	case CMD_REDRAW:
		return parent->Command(CMD_REDRAW, 0L, o);
	case CMD_SAVEPOS:
		return parent->Command(cmd, tmpl, o);
	case CMD_SETCHILD:
		chldRC = (fRECT*)tmpl;
		return true;
		}
	return false;
}

void *
FrmRect::ObjThere(int x, int y)
{
	if(drag) return drag->ObjThere(x, y);
	return 0L;
}

void
FrmRect::Track(POINT *p, anyOutput *o)
{
	POINT tpts[5];
	RECT old_rc;

	if(o){
		memcpy(&old_rc, &rDims, sizeof(rDims));
		o->UpdateRect(&rDims, false);
		tpts[0].x = tpts[1].x = tpts[4].x = o->co2ix(cRC->Xmin)+p->x;		
		tpts[0].y = tpts[3].y = tpts[4].y = o->co2iy(cRC->Ymin)+p->y;
		tpts[1].y = tpts[2].y = o->co2iy(cRC->Ymax)+p->y;
		tpts[2].x = tpts[3].x = o->co2ix(cRC->Xmax)+p->x;
		UpdateMinMaxRect(&rDims, tpts[0].x, tpts[0].y);
		UpdateMinMaxRect(&rDims, tpts[2].x, tpts[2].y);	
		if(old_rc.left != rDims.left || old_rc.right != rDims.right || old_rc.top !=
			rDims.top || old_rc.bottom != rDims.bottom)IncrementMinMaxRect(&rDims, 3);
		o->ShowLine(tpts, 5, Fill.color ^ 0x00ffffffL);
		}

}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void * 
Arrow::ObjThere(int x, int y)
{
	if(!IsInRect(&rDims, x, y)) return 0L;
	if(IsCloseToLine(&pts[0], &pts[1], x, y) ||
		IsCloseToLine(&pts[2], &pts[3], x, y) ||
		IsCloseToLine(&pts[3], &pts[4], x, y)){
		if(dh1 && dh1->ObjThere(x, y)) return dh1;
		if(dh2 && dh2->ObjThere(x, y)) return dh2;
		return this;
		}
	return 0L;
}

void
Arrow::Track(POINT *p, anyOutput *o)
{
	POINT *tpts;
	RECT old_rc;
	int i;

	if(o && (tpts = (POINT*)malloc(6*sizeof(POINT)))){
		memcpy(&old_rc, &rDims, sizeof(rDims));
		o->UpdateRect(&rDims, false);
		for(i = 0; i < 5; i++) {
			tpts[i].x = pts[i].x+p->x;
			tpts[i].y = pts[i].y+p->y;
			UpdateMinMaxRect(&rDims, tpts[i].x, tpts[i].y);
			}
		switch(type & 0xff) {
		case ARROW_LINE:
			o->ShowLine(tpts+2, 3, LineDef.color);
		case ARROW_NOCAP:
			o->ShowLine(tpts, 2, LineDef.color);
			break;
		case ARROW_TRIANGLE:
			tpts[5].x = tpts[2].x;		tpts[5].y = tpts[2].y;
			o->ShowLine(tpts+2, 4, LineDef.color);
			tpts[1].x = (tpts[2].x + tpts[4].x)>>1;
			tpts[1].y = (tpts[2].y + tpts[4].y)>>1;
			o->ShowLine(tpts, 2, LineDef.color);
			break;
			}
		if(old_rc.left != rDims.left || old_rc.right != rDims.right || old_rc.top !=
			rDims.top || old_rc.bottom != rDims.bottom)IncrementMinMaxRect(&rDims, 3);
		free(tpts);
		}
}

void
Label::ShowCursor(anyOutput *o)
{
	if(o) {
		CalcRect(o);
		if((o->OC_type & 0xff) == OC_BITMAP) ShowTextCursor(o, &Cursor, TextDef.ColTxt);
		}
}

bool
Label::AddChar(int ci, anyOutput *o)
{
	char c, *txt1 = 0L;
	int i, j, k;
	GraphObj *golist[2];
	DWORD flags;

	if(!o || (ci != 13 && ci < 32)) return false;
	if(CheckMark()) {
		Undo.String(this, &TextDef.text, 0L);
		Undo.ValInt(this, &m1, UNDO_CONTINUE);
		Undo.ValInt(this, &m2, UNDO_CONTINUE);
		rlp_strcpy(TextDef.text+m1, (int)strlen(TextDef.text+m1)+1, TextDef.text+m2);
		CursorPos = m1;		flags = UNDO_CONTINUE;
		}
	else flags = 0L;
	m1 = m2 = -1;
	if(ci == 13 && parent){		//CR
		if(parent->Id == GO_MLABEL){
			parent->Command(CMD_SETFOCUS, this, o);
			return parent->Command(CMD_ADDCHAR, &ci, o);
			}
		if(golist[1] = new mLabel(parent, data, fPos.fx, fPos.fy, &TextDef, 
			TextDef.text, CursorPos, flags)){
			golist[1]->moveable = moveable;
			golist[1]->SetSize(SIZE_LB_XDIST, fDist.fx);
			golist[1]->SetSize(SIZE_LB_YDIST, fDist.fy);
			}
		golist[0] = this;
		if(!parent->Command(CMD_MUTATE, golist, o)) DeleteGO(golist[1]);
		return false;
		}
	if(TextDef.text && TextDef.text[0]) i = (int)strlen(TextDef.text);
	else i = 0;		if(CursorPos > i)CursorPos = i;
	if(ci > 254) {
		Undo.String(this, &TextDef.text, flags);
		Undo.ValInt(this, &CursorPos, flags = UNDO_CONTINUE);
		txt1 = (char*)malloc((i+12)*sizeof(char));
		if(txt1) {
			if(j=CursorPos) k = rlp_strcpy(txt1, CursorPos+1, TextDef.text);
			else k = 0;
#ifdef USE_WIN_SECURE
			k += sprintf_s(txt1+k, i+12-k, "&#%d;",ci);
#else
			k += sprintf(txt1+k, "&#%d;",ci);
#endif
			CursorPos = k;
			k += rlp_strcpy(txt1+k, i+12-k, TextDef.text+j);
			if(TextDef.text) free(TextDef.text);
			TextDef.text = txt1;			RedrawEdit(o);
			}
		return true;
		}
	else if( ci > 31) c = (char)ci;
	else return false;
	if(!TextDef.text || CursorPos < 10 || c == ' ') {
		Undo.String(this, &TextDef.text, flags);
		Undo.ValInt(this, &CursorPos, flags = UNDO_CONTINUE);
		}
	txt1 = (char*)malloc((i+4)* sizeof(char));
	if(txt1) {
		if(j=CursorPos) k = rlp_strcpy(txt1, CursorPos+1, TextDef.text);
		else k = 0;			txt1[k++] = c;
		k += rlp_strcpy(txt1+k, i+12-k, TextDef.text+j);
		if(TextDef.text) free(TextDef.text);
		TextDef.text = txt1;	txt1[k] = 0;
		CursorPos++;			RedrawEdit(o);
		}
	return true;
}

void
Label::CalcCursorPos(int x, int y, anyOutput *o)
{
	int i, j, ix, x1, y1, x2, h;

	if(!o || !TextDef.text) return;
	TextDef.iSize = o->un2iy(TextDef.fSize);
	if(parent && parent->Id != GO_MLABEL) o->SetTextSpec(&TextDef);
	x1 = ((pts[3].x + pts[4].x)>>1);	y1 = ((pts[3].y + pts[4].y)>>1);
	ix = ((j=(x1-x))*j);	ix += ((j=(y1-y))*j);	ix = isqr(ix);
	for(i = 1,  x1 = 0; TextDef.text[i-1]; i++) {
		DrawFmtText.SetText(0L, TextDef.text, 0L, 0L);
		DrawFmtText.oGetTextExtent(o, &x2, &h, i);
		if(x1 <= ix && x2 >= ix) {
			if((ix-x1) < (x2-ix)) CursorPos = i-1;
			else CursorPos = i;
			return;
			}
		else if(ix < x2){
			CursorPos = i-1;
			return;
			}
		x1 = x2;
		}
	if(pts[3].x < pts[2].x && x < pts[3].x) CursorPos = 0;
	else CursorPos = (int)strlen(TextDef.text);
}

void 
TextFrame::ShowCursor(anyOutput *o)
{
	int cx, cy, w, h;

	if(!o || (o->OC_type & 0xff) != OC_BITMAP) return;
	TextDef.iSize = o->un2iy(TextDef.fSize);
#ifdef _WINDOWS
	linc = o->un2iy(TextDef.fSize*lspc);
#else
	linc = o->un2iy(TextDef.fSize*lspc*1.2);
#endif
	o->SetTextSpec(&TextDef);
	cy = rDims.top + linc * cur_pos.y;			cx = rDims.left;
	cx += ipad.left;							cy += ipad.top;
	if(cur_pos.y < nlines) {
		if(lines[cur_pos.y] && lines[cur_pos.y][0] && cur_pos.x) {
			fmt_txt.SetText(0L, (char*)lines[cur_pos.y], &cx, &cy);
			if(!fmt_txt.oGetTextExtent(o, &w, &h, cur_pos.x))return;
			}
		else {
			if(!o->oGetTextExtent("A", 1, &w, &h))return;	w = 0;
			}
		Cursor.left = cx+w;		Cursor.right = cx+w;
		Cursor.top = cy;		Cursor.bottom = cy+linc;
		ShowTextCursor(o, &Cursor, TextDef.ColTxt);
		}
}

void
TextFrame::CalcCursorPos(int x, int y, anyOutput *o)
{
	int i, iy, ix, x1, x2, h;

	if(!o) return;
	TextDef.iSize = o->un2iy(TextDef.fSize);
	o->SetTextSpec(&TextDef);
	iy = (y-rDims.top-ipad.top)/linc;		x1 = x2 = 0;
	if(iy >= nlines) iy = nlines-1;			cur_pos.y = iy;
	x2 = rDims.right - rDims.left -ipad.left - ipad.right;
	x = x - rDims.left -ipad.left;
	fmt_txt.SetText(0L, (char*)lines[iy], &x1, &x2);
	for(i = 0,  ix = x1 = 0; lines[iy][i]; i++) {
		if(i) fmt_txt.oGetTextExtent(o, &x1, &h, i);
		x1 -= (TextDef.iSize >> 2);
		if(i && x1 <= x2 && x1 >= x) {
			if(i >1) fmt_txt.oGetTextExtent(o, &x1, &h, i-1);
			else x1 = 0;
			fmt_txt.oGetTextExtent(o, &x2, &h, i);
			ix = (abs(x1-x)<abs(x2-x)) ? i = (i-1) : i;
			break;
			}
		}
	if(ix) cur_pos.x = ix;
	else cur_pos.x = i;
}

void
Axis::DoMark(anyOutput *o, bool mark)
{
	LineDEF mrkLine;
	double minw = DefSize(SIZE_DATA_LINE);
	POINT *arc;
	long narc = 0;

	if(axis->flags & AXIS_ANGULAR) {
		if(mark) {
			if(mo) DelBitmapClass(mo);					mo = 0L;
			o->GetSize(&mrc);						mo = GetRectBitmap(&mrc, o);
			memcpy(&mrkLine, &axline, sizeof(LineDEF));
			mrkLine.width = axline.width < minw ? minw * 3.0 : axline.width *3.0;
			o->SetLine(&mrkLine);
			if(arc = MakeArc(pts[0].x, pts[0].y, pts[1].x, 0x0f, &narc)) {
				o->oPolyline(arc, (int)narc, 0L);
				mrkLine.color ^= 0x00ffffffL;		mrkLine.width = axline.width;
				o->SetLine(&mrkLine);				o->oPolyline(arc, (int)narc, 0L);
				free(arc);
				}
			o->UpdateRect(&mrc, false);
			}
		else RestoreRectBitmap(&mo, &mrc, o);
		return;
		}
	if(mark){
		if(mo) DelBitmapClass(mo);					mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		IncrementMinMaxRect(&mrc, 6 + o->un2ix(axline.width));
		mo = GetRectBitmap(&mrc, o);
		if(type & 0x04) InvertLine(gradient_box, 5, &axline, &rDims, o, mark);
		else InvertLine(pts, 2, &axline, &rDims, o, mark);
		o->UpdateRect(&mrc, false);
		}
	else RestoreRectBitmap(&mo, &mrc, o);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Graphs are graphic objects containing plots, axes, and drawn objects
bool
Graph::ExecTool(MouseEvent *mev)
{
	static POINT pl = {0, 0}, pc = {0, 0};
	static DWORD color = 0L;
	scaleINFO scale;
	POINT line[5];
	GraphObj **tmpPlots, *new_go;
	TextDEF *td;
	lfPOINT *lfp, lf;
	int i, j;
	double x, y;

	if(!mev || !CurrDisp) return false;
	td = 0L;	if(mev->Action == MOUSE_LBUP) SuspendAnimation(CurrDisp, false);
	if(ToolMode & TM_MOVE) switch (mev->Action) {
		case MOUSE_LBDOWN:
			pl.x = pc.x = mev->x;				pl.y = pc.y = mev->y;
			if(TrackGO && TrackGO->Command(CMD_MOUSECURSOR, 0L, CurrDisp)) return true;
			CurrDisp->HideMark();			CurrDisp->MouseCursor(MC_MOVE, false);
			return true;
		case MOUSE_MOVE:
			defs.Idle(CMD_UPDATE);
			if(TrackGO) {
				if(TrackGO->Id == GO_DRAGHANDLE && TrackGO->type >= DH_18 &&
					TrackGO->type <= DH_88) {
					lf.fx = CurrDisp->fix2un((double)(mev->x-pl.x));	
					lf.fy = CurrDisp->fiy2un((double)(mev->y-pl.y));
					TrackGO->Track((POINT*)&lf, CurrDisp);
					}
				else {
					pc.x = mev->x-pl.x;				pc.y = mev->y-pl.y;
					TrackGO->Track(&pc, CurrDisp);
					}
				}
			return true;
		case MOUSE_LBUP:
			if(TrackGO) {
				ToolMode &= ~TM_MOVE;
				pc.x = mev->x-pl.x;				pc.y = mev->y-pl.y;
				if(!pc.x && !pc.y){
					Command(CMD_TOOLMODE, (void*)(& ToolMode), CurrDisp);
					return false;
					}
				TrackGO->Track(&pc, CurrDisp);
				lf.fx = CurrDisp->fix2un((double)(mev->x-pl.x));	
				lf.fy = CurrDisp->fiy2un((double)(mev->y-pl.y));
				TrackGO->Command(CMD_MOVE, &lf, CurrDisp);
				bModified = true;
				}
			Command(CMD_TOOLMODE, (void*)(& ToolMode), CurrDisp);
			return true;
		default:
			return true;
		}
	if(ToolMode == TM_MARK || ToolMode == TM_ZOOMIN) {
		switch (mev->Action) {
		case MOUSE_LBDOWN:			//we should not receive that !
			rc_mrk.left = mev->x;	rc_mrk.top = mev->y;
			return false;
		case MOUSE_LBUP:
			j = (i = rc_mrk.left - mev->x)*i;
			j += ((i = rc_mrk.top - mev->y)*i);
			if(j < 20) {						//glitch
				rc_mrk.left = rc_mrk.right = rc_mrk.top = rc_mrk.bottom = 0;
				ToolMode = TM_STANDARD;		CurrDisp->MouseCursor(MC_ARROW, false);
				return false;
				}
			if(ToolMode == TM_ZOOMIN) {
				rc_mrk.right = mev->x;	rc_mrk.bottom = mev->y;
				ToolMode = TM_STANDARD;
				return Command(CMD_ZOOM, (void*) &"+", 0L);
				}
		default:
			ToolMode = TM_STANDARD;
		case MOUSE_MOVE:
			if((mev->StateFlags &1) && rc_mrk.left >= 0 && rc_mrk.top >= 0) {
				if(rcUpd.left != rcUpd.right && rcUpd.top != rcUpd.bottom)
					CurrDisp->UpdateRect(&rcUpd, false);
				line[0].x = line[3].x = line[4].x = rc_mrk.left;
				line[0].y = line[1].y = line[4].y = rc_mrk.top;
				line[1].x = line[2].x = mev->x;
				line[2].y = line[3].y = mev->y;
				CurrDisp->ShowLine(line, 5, 0x00c0c0c0L);
				rcUpd.left = rcUpd.right = rc_mrk.left;
				rcUpd.top = rcUpd.bottom = rc_mrk.top;
				UpdateMinMaxRect(&rcUpd, rc_mrk.right = mev->x, rc_mrk.bottom = mev->y);
				IncrementMinMaxRect(&rcUpd,2);
				}
			return true;
			}
		}
	if(ToolMode == TM_PASTE) {
		switch (mev->Action) {
		case MOUSE_LBDOWN:
			CurrGO = 0L;
			CurrDisp->MrkMode = MRK_NONE;
			pl.x = pc.x = mev->x;		pl.y = pc.y = mev->y;
			rcDim.left = rcDim.right = rcUpd.left = rcUpd.right = mev->x;
			rcDim.top = rcDim.bottom = rcUpd.top = rcUpd.bottom = mev->y;
			return true;
		case MOUSE_MOVE:
			if(mev->StateFlags &1) {
				if(rcUpd.left != rcUpd.right && rcUpd.top != rcUpd.bottom)
					CurrDisp->UpdateRect(&rcUpd, false);
				line[0].x = line[4].x = pl.x;	line[0].y = line[4].y = pl.y;
				line[1].x = pc.x = mev->x;		line[1].y = pl.y;
				line[2].x = mev->x;				line[2].y = pc.y = mev->y;
				line[3].x = pl.x;				line[3].y = mev->y;
				CurrDisp->ShowLine(line, 5, 0x00c0c0c0L);
				memcpy(&rcUpd, &rcDim, sizeof(RECT));
				UpdateMinMaxRect(&rcUpd, mev->x, mev->y);
				IncrementMinMaxRect(&rcUpd, 2);
				return true;
				}
			break;
		case MOUSE_LBUP:
			if(!PasteObj) return false;
			pc.x = mev->x;			pc.y = mev->y;
			if((lfp = (lfPOINT*)malloc(2 * sizeof(lfPOINT)))){
				lfp[0].fx = CurrDisp->fix2un(pl.x - CurrDisp->VPorg.fx);
				lfp[0].fy = CurrDisp->fiy2un(pl.y - CurrDisp->VPorg.fy);
				lfp[1].fx = CurrDisp->fix2un(pc.x - CurrDisp->VPorg.fx);
				lfp[1].fy = CurrDisp->fiy2un(pc.y - CurrDisp->VPorg.fy);
				if(lfp[0].fx > lfp[1].fx) {
					x = lfp[0].fx;		lfp[0].fx = lfp[1].fx;		lfp[1].fx = x;
					}
				if(lfp[0].fy > lfp[1].fy) {
					y = lfp[0].fy;		lfp[0].fy = lfp[1].fy;		lfp[1].fy = y;
					}
				}
			else {
				DeleteGO(PasteObj);			PasteObj = 0L;
				ToolMode = TM_STANDARD;		CurrDisp->MouseCursor(MC_ARROW, false);
				return true;
				}
			scale.sx.fx = lfp[0].fx;	scale.sy.fx = lfp[0].fy;	scale.sz.fx = 0.0;
			scale.sx.fy = scale.sy.fy = scale.sz.fy = 1.0;
			if(PasteObj->Id == GO_GRAPH) {
				if(abs(pc.x -pl.x) > 20 && abs(pc.y -pl.y) >20) {
					x = ((Graph*)PasteObj)->GRect.Xmax - ((Graph*)PasteObj)->GRect.Xmin;
					y = ((Graph*)PasteObj)->GRect.Ymax - ((Graph*)PasteObj)->GRect.Ymin;
					scale.sx.fy = (lfp[1].fx - lfp[0].fx)/x;	scale.sy.fy = (lfp[1].fy - lfp[0].fy)/y;
					scale.sx.fy = (scale.sx.fy + scale.sy.fy) /2.0;		//preserve aspect ratio
					scale.sy.fy = scale.sx.fy;
					}
				((Graph*)PasteObj)->GRect.Xmax -= ((Graph*)PasteObj)->GRect.Xmin;
				((Graph*)PasteObj)->GRect.Ymax -= ((Graph*)PasteObj)->GRect.Ymin;
				((Graph*)PasteObj)->GRect.Ymin = ((Graph*)PasteObj)->GRect.Xmin = 0.0;
				PasteObj->Command(CMD_SCALE, &scale, 0L);
				PasteObj->moveable = 1;
				Command(CMD_DROP_GRAPH, (void*)PasteObj, 0L);
				}
			else {
				anyOutput *ScaleOut;
				
				if(ScaleOut = NewBitmapClass(10, 10, CurrDisp->hres, CurrDisp->vres)) {
					PasteObj->parent = this;	PasteObj->DoPlot(ScaleOut);
					switch(PasteObj->Id){
					case GO_POLYLINE:		case GO_POLYGON:
						IncrementMinMaxRect(&PasteObj->rDims, -(3*CurrDisp->un2ix(((polyline*)PasteObj)->pgLine.width)+3));
						break;
					case GO_BEZIER:
						IncrementMinMaxRect(&PasteObj->rDims, 3);
						break;
						}
					scale.sx.fx = CurrDisp->fix2un(-PasteObj->rDims.left);
					scale.sy.fx = CurrDisp->fiy2un(-PasteObj->rDims.top);
					PasteObj->Command(CMD_SCALE, &scale, 0L);
					scale.sx.fx = lfp[0].fx;	scale.sy.fx = lfp[0].fy;	scale.sz.fx = 0.0;
					if(abs(pc.x -pl.x) > 8 && abs(pc.y -pl.y) > 8) {
						x = CurrDisp->fix2un(PasteObj->rDims.right - PasteObj->rDims.left);
						y = CurrDisp->fiy2un(PasteObj->rDims.bottom - PasteObj->rDims.top);
						scale.sx.fy = (lfp[1].fx - lfp[0].fx)/x;	
						scale.sy.fy = (lfp[1].fy - lfp[0].fy)/y;
						}
					PasteObj->Command(CMD_SCALE, &scale, 0L);
					delete ScaleOut;
					}
				Command(CMD_DROP_GRAPH, (void*)PasteObj, 0L);
				}
			PasteObj = 0L;
			ToolMode = TM_STANDARD;			CurrDisp->MouseCursor(MC_ARROW, false);
			break;
			}
		return true;
		}
	if(NumPlots && !Plots[NumPlots-1]) {
		Undo.StoreListGO(this, &Plots, &NumPlots, UNDO_CONTINUE);
		for(i = j = 0; i < NumPlots; i++) if(Plots[i]) Plots[j++] = Plots[i];
		NumPlots = j;
		}
	else if(!Plots && !(Plots = (GraphObj**)calloc(2, sizeof(GraphObj*))))return false;
	else if(Plots[NumPlots]){
		if(tmpPlots = (GraphObj**)memdup(Plots, (NumPlots+2) * sizeof(GraphObj*), 0L)){
			Undo.ListGOmoved(Plots, tmpPlots, NumPlots);
			free(Plots);	Plots = tmpPlots;
			Plots[NumPlots] = Plots[NumPlots+1] = 0L;
			}
		else return false;
		}
	switch(ToolMode & 0x0f) {
	case TM_DRAW:
		switch (mev->Action) {
		case MOUSE_LBDOWN:
			CurrGO = 0L;
			CurrDisp->MrkMode = MRK_NONE;
			Command(CMD_REDRAW, 0L, CurrDisp);
			color = defs.Color(COL_POLYLINE);
			pl.x = pc.x = mev->x;		pl.y = pc.y = mev->y;
			if(tl_pts) free(tl_pts);
			tl_nPts = 0;
			if(tl_pts = (POINT*)malloc(4096 * sizeof(POINT)))
				AddToPolygon(&tl_nPts, tl_pts, &pc);
			return true;
		case MOUSE_LBUP:
			pl.x = mev->x;				pl.y = mev->y;
			if(tl_pts) AddToPolygon(&tl_nPts, tl_pts, &pc);
			// create line object
			if(tl_pts && tl_nPts >1) {			//DEBUG: check for plausibility
				if(lfp = (lfPOINT*)malloc(tl_nPts * sizeof(lfPOINT))){
					for(i = 0; i < tl_nPts; i++) {
						lfp[i].fx = CurrDisp->fix2un(tl_pts[i].x - CurrDisp->VPorg.fx);	
						lfp[i].fy = CurrDisp->fiy2un(tl_pts[i].y - CurrDisp->VPorg.fy);
						}
					if(Plots[NumPlots]) i = NumPlots+1;
					else i = NumPlots;
//					Undo.SetGO(this, &Plots[i], new polyline(this, data, lfp, (int)tl_nPts), 0L);
					Undo.SetGO(this, &Plots[i], new Bezier(this, data, lfp, (int)tl_nPts, 0,
							(CurrDisp->hres/CurrDisp->VPscale+CurrDisp->vres/CurrDisp->VPscale)/2.0), 0L);
					if(Plots[i]){
						NumPlots = i+1;
						Plots[i]->moveable = 1;
						Plots[i]->DoPlot(CurrDisp);							//init
						CurrDisp->ShowMark(CurrGO = Plots[i], MRK_GODRAW);	//edit
						bModified = true;
						}
					free(lfp);
					}
				if(tl_pts) free(tl_pts);	tl_pts = 0L;	tl_nPts = 0;
				return true;
				}
			if(tl_pts) free(tl_pts);	tl_pts = 0L;	tl_nPts = 0;
			return false;
		case MOUSE_MOVE:
			if((mev->StateFlags &1) && tl_pts && tl_nPts < 4095) {
				memcpy(&pl, &pc, sizeof(POINT));
				line[1].x = pc.x = mev->x;		line[1].y = pc.y = mev->y;
				line[0].x = pl.x;				line[0].y = pl.y;
				AddToPolygon(&tl_nPts, tl_pts, &pc);
#ifdef _WINDOWS
				CurrDisp->ShowLine(line, 2, color);
#else
				CurrDisp->ShowLine(tl_pts, tl_nPts, color);
#endif
				return true;
				}
			break;
			}
		break;
	case TM_POLYLINE:	case TM_POLYGON:
		switch (mev->Action) {
		case MOUSE_LBDOWN:
			if(!tl_pts) {
				CurrGO = 0L;
				CurrDisp->MrkMode = MRK_NONE;
				Command(CMD_REDRAW, 0L, CurrDisp);
				color = defs.Color((ToolMode & 0x0f) == TM_POLYLINE ? COL_POLYLINE : COL_POLYGON);
				pl.x = pc.x = mev->x;		pl.y = pc.y = mev->y;
				tl_nPts = 0;
				if(tl_pts = (POINT*)malloc(4096 * sizeof(POINT)))
				AddToPolygon(&tl_nPts, tl_pts, &pc);
				rcDim.left = rcDim.right = rcUpd.left = rcUpd.right = mev->x;
				rcDim.top = rcDim.bottom = rcUpd.top = rcUpd.bottom = mev->y;
				}
			return true;
		case MOUSE_MOVE:
			if(tl_pts && tl_nPts) {
				if(rcUpd.left != rcUpd.right && rcUpd.top != rcUpd.bottom)
					CurrDisp->UpdateRect(&rcUpd, false);
				CurrDisp->ShowLine(tl_pts, tl_nPts, color);
				line[1].x = mev->x;				line[1].y = mev->y;
				line[0].x = pc.x;				line[0].y = pc.y;
				CurrDisp->ShowLine(line, 2, color);
				UpdateMinMaxRect(&rcUpd, mev->x, mev->y);
				memcpy(&rcUpd, &rcDim, sizeof(RECT));
				UpdateMinMaxRect(&rcUpd, mev->x, mev->y);
				IncrementMinMaxRect(&rcUpd, 2);
				return true;
				}
			break;
		case MOUSE_LBUP:
			if(tl_pts && tl_nPts) {
				memcpy(&pl, &pc, sizeof(POINT));
				pc.x = mev->x;				pc.y = mev->y;
				UpdateMinMaxRect(&rcDim, mev->x, mev->y);
				AddToPolygon(&tl_nPts, tl_pts, &pc);
				}
			return true;
		default:			// create line or polygon object
			if(tl_pts && tl_nPts >0) {			//DEBUG: check for plausibility
				pc.x = mev->x;				pc.y = mev->y;
				AddToPolygon(&tl_nPts, tl_pts, &pc);
				if(lfp = (lfPOINT*)malloc(tl_nPts * sizeof(lfPOINT))){
					for(i = 0; i < tl_nPts; i++) {
						lfp[i].fx = CurrDisp->fix2un(tl_pts[i].x-CurrDisp->VPorg.fx);	
						lfp[i].fy = CurrDisp->fiy2un(tl_pts[i].y-CurrDisp->VPorg.fy);
						}
					if(Plots[NumPlots]) i = NumPlots+1;
					else i = NumPlots;
					Undo.SetGO(this, &Plots[i], ToolMode == TM_POLYLINE ?
						new polyline(this, data, lfp, (int)tl_nPts) :
						(GraphObj*) new polygon(this, data, lfp, (int)tl_nPts), 0L);
					if(Plots[i]){
						NumPlots = i+1;
						Plots[i]->moveable = 1;
						Plots[i]->DoPlot(CurrDisp);							//init
						CurrDisp->ShowMark(CurrGO = Plots[i], MRK_GODRAW);	//edit
						bModified = true;
						}
					free(lfp);
					}
				free(tl_pts);			tl_pts = 0L;		tl_nPts = 0;
				return true;
				}
			}
		if(mev->x == pc.x && mev->y == pc.y) return true;	//rebounce
		break;
	case TM_RECTANGLE:	case TM_ELLIPSE:	case TM_ROUNDREC:	case TM_ARROW:
		switch (mev->Action) {
		case MOUSE_LBDOWN:
			CurrGO = 0L;
			CurrDisp->MrkMode = MRK_NONE;
			Command(CMD_REDRAW, 0L, CurrDisp);
			color = defs.Color((ToolMode & 0x0f) != TM_ARROW ? COL_POLYGON : COL_DATA_LINE);
			pl.x = pc.x = mev->x;		pl.y = pc.y = mev->y;
			rcDim.left = rcDim.right = rcUpd.left = rcUpd.right = mev->x;
			rcDim.top = rcDim.bottom = rcUpd.top = rcUpd.bottom = mev->y;
			return true;
		case MOUSE_MOVE:
			if(mev->StateFlags &1) {
				if(rcUpd.left != rcUpd.right && rcUpd.top != rcUpd.bottom)
					CurrDisp->UpdateRect(&rcUpd, false);
				line[0].x = line[4].x = pl.x;	line[0].y = line[4].y = pl.y;
				if((ToolMode & 0x0f)==TM_ARROW) {
					line[1].x = pc.x = mev->x;		line[1].y = pc.y = mev->y;
					CurrDisp->ShowLine(line, 2, color);
					}
				else {
					line[1].x = pc.x = mev->x;		line[1].y = pl.y;
					line[2].x = mev->x;				line[2].y = pc.y = mev->y;
					line[3].x = pl.x;				line[3].y = mev->y;
					CurrDisp->ShowLine(line, 5, color);
					if((ToolMode & 0x0f) == TM_ELLIPSE) 
						CurrDisp->ShowEllipse(pc, pl, color);
					}
				memcpy(&rcUpd, &rcDim, sizeof(RECT));
				UpdateMinMaxRect(&rcUpd, mev->x, mev->y);
				IncrementMinMaxRect(&rcUpd, 2);
				return true;
				}
			break;
		case MOUSE_LBUP:
			pc.x = mev->x;			pc.y = mev->y;
			if(((ToolMode & 0x0f)==TM_ARROW || (abs(pc.x-pl.x) >5 && abs(pc.y-pl.y) >5)) && 
				(lfp = (lfPOINT*)malloc(2 * sizeof(lfPOINT)))){
				lfp[0].fx = CurrDisp->fix2un(pl.x - CurrDisp->VPorg.fx);
				lfp[0].fy = CurrDisp->fiy2un(pl.y - CurrDisp->VPorg.fy);
				lfp[1].fx = CurrDisp->fix2un(pc.x - CurrDisp->VPorg.fx);
				lfp[1].fy = CurrDisp->fiy2un(pc.y - CurrDisp->VPorg.fy);
				if(Plots[NumPlots]) i = NumPlots+1;
				else i = NumPlots;
				if(ToolMode == TM_RECTANGLE) new_go = new rectangle(this, data,
					&lfp[0], &lfp[1]);
				else if(ToolMode == TM_ELLIPSE) new_go = new ellipse(this, data,
					&lfp[0], &lfp[1]);
				else if(ToolMode == TM_ROUNDREC) new_go = new roundrec(this, data,
					&lfp[0], &lfp[1]);
				else if(ToolMode == TM_ARROW) new_go = new Arrow(this, data,
					lfp[0], lfp[1], ARROW_UNITS | ARROW_LINE);
				else new_go = 0L;
				if(new_go) Undo.SetGO(this, &Plots[i], new_go, 0L);
				if(Plots[i]){
					NumPlots = i+1;				CurrDisp->HideMark();
					Plots[i]->DoPlot(CurrDisp);								//init
					CurrDisp->ShowMark(CurrGO = Plots[i], MRK_GODRAW);		//edit
					Plots[i]->moveable = 1;
					bModified = true;
					}
				free(lfp);
				}
			else if(rcUpd.left != rcUpd.right && rcUpd.top != rcUpd.bottom) {
				CurrDisp->UpdateRect(&rcUpd, false);
				}
			}
		break;
	case TM_TEXT:
		if(Plots[NumPlots]) i = NumPlots+1;
		else i = NumPlots;
		switch(mev->Action) {
		case MOUSE_LBDOWN:
			CurrGO = 0L;
			CurrDisp->MrkMode = MRK_NONE;
			Command(CMD_REDRAW, 0L, CurrDisp);
			color = 0x00cbcbcb;
			pl.x = pc.x = mev->x;		pl.y = pc.y = mev->y;
			rcDim.left = rcDim.right = rcUpd.left = rcUpd.right = mev->x;
			rcDim.top = rcDim.bottom = rcUpd.top = rcUpd.bottom = mev->y;
			return true;
		case MOUSE_MOVE:
			if(mev->StateFlags &1) {
				CurrDisp->MouseCursor(MC_TXTFRM, false);
				if(rcUpd.left != rcUpd.right && rcUpd.top != rcUpd.bottom)
					CurrDisp->UpdateRect(&rcUpd, false);
				line[0].x = line[4].x = pl.x;	line[0].y = line[4].y = pl.y;
				if((ToolMode & 0x0f)==TM_ARROW) {
					line[1].x = pc.x = mev->x;		line[1].y = pc.y = mev->y;
					CurrDisp->ShowLine(line, 2, color);
					}
				else {
					line[1].x = pc.x = mev->x;		line[1].y = pl.y;
					line[2].x = mev->x;				line[2].y = pc.y = mev->y;
					line[3].x = pl.x;				line[3].y = mev->y;
					CurrDisp->ShowLine(line, 5, color);
					}
				memcpy(&rcUpd, &rcDim, sizeof(RECT));
				UpdateMinMaxRect(&rcUpd, mev->x, mev->y);
				IncrementMinMaxRect(&rcUpd, 2);
				return true;
				}
			break;
		case MOUSE_LBUP:
			pc.x = mev->x;			pc.y = mev->y;
			if(((abs(pc.x-pl.x) >20 && abs(pc.y-pl.y) >20)) && 
				(lfp = (lfPOINT*)malloc(2 * sizeof(lfPOINT)))){
				lfp[0].fx = CurrDisp->fix2un(pl.x - CurrDisp->VPorg.fx);
				lfp[0].fy = CurrDisp->fiy2un(pl.y - CurrDisp->VPorg.fy);
				lfp[1].fx = CurrDisp->fix2un(pc.x - CurrDisp->VPorg.fx);
				lfp[1].fy = CurrDisp->fiy2un(pc.y - CurrDisp->VPorg.fy);
				if(Plots[NumPlots]) i = NumPlots+1;
				else i = NumPlots;
				new_go = new TextFrame(this, data, &lfp[0], &lfp[1], 0L);
				if(new_go) Undo.SetGO(this, &Plots[i], new_go, 0L);
				if(Plots[i]){
					NumPlots = i+1;
					Plots[i]->DoPlot(CurrDisp);							//init
					CurrDisp->ShowMark(CurrGO = Plots[i], MRK_GODRAW);				//edit
					Plots[i]->moveable = 1;
					bModified = true;
					}
				free(lfp);
				CurrDisp->CheckMenu(ToolMode & 0x0f, false);
				CurrDisp->CheckMenu(ToolMode = TM_STANDARD, true);
				CurrDisp->MouseCursor(MC_ARROW, false);
				}
			else {
				CurrDisp->MouseCursor(MC_TEXT, false);
				if(!(td = (TextDEF *)calloc(1, sizeof(TextDEF))))return false;
				x = CurrDisp->fix2un(mev->x-CurrDisp->VPorg.fx);
				y = CurrDisp->fiy2un(mev->y-CurrDisp->VPorg.fy);
				td->ColTxt = defs.Color(COL_TEXT);		td->ColBg = defs.Color(COL_BG);
				td->RotBL = td->RotCHAR = 0.0f;			td->fSize = DefSize(SIZE_TEXT);
				td->Align = TXA_VTOP | TXA_HLEFT;		td->Style = TXS_NORMAL;
				td->Mode = TXM_TRANSPARENT;				td->Font = FONT_HELVETICA;
				td->text = 0L;
				CurrGO = 0L;
				CurrDisp->MrkMode = MRK_NONE;
				Command(CMD_REDRAW, 0L, CurrDisp);
				y -= td->fSize/2.0;
				Undo.SetGO(this, &Plots[i], new Label(this, data, x, y, td, 0L), 0L);
				if(Plots[i]){
					NumPlots = i+1;
					Plots[i]->DoPlot(CurrDisp);							//init
					CurrDisp->ShowMark(CurrGO = Plots[i], MRK_GODRAW);	//edit
					Plots[i]->moveable = 1;
					}
				free(td);
				}
			return true;
			}
		break;
		}
	return false;
}

bool
Graph::MoveObj(int cmd, GraphObj *g)
{
	int i, j;
	GraphObj *g1 = 0;

	if(!g || NumPlots <2 || g->parent != this) return false;
	switch(cmd) {
	case CMD_MOVE_TOP:
		for(i = j = 0; i <NumPlots; i++){
			if(g == Plots[i]) g1 = Plots[i];
			else Plots[j++] = Plots[i];
			}
		if(g1) {
			Plots[j++] = g1;
			return true;
			}
		break;
	case CMD_MOVE_UP:
		for(i = 0; i<NumPlots-1; i++){
			if(g == Plots[i]){
				g1 = Plots[i];	Plots[i] = Plots[i+1];	Plots[i+1] = g1;
				return true;
				}
			}
		break;
	case CMD_MOVE_DOWN:
		for(i = 1; i<NumPlots; i++){
			if(g == Plots[i]){
				g1 = Plots[i];	Plots[i] = Plots[i-1];	Plots[i-1] = g1;
				return true;
				}
			}
		break;
	case CMD_MOVE_BOTTOM:
		if(Plots[0] == g) return false;
		for(i =  j = NumPlots-1; i >= 0; i--) {
			if(g == Plots[i]) g1 = Plots[i];
			else Plots[j--] = Plots[i];
			}
		if(g1) {
			Plots[j--] = g1;
			return true;
			}
		break;
		}
	return false;
}

bool
Graph::DoZoom(char *z)
{
	RECT cw;
	double fac, f1, f2;
	ZoomDEF *tz;
	Graph *cg;

	if(!z) return false;
	HideCopyMark();
	if(0==strcmp("fit", z)) {
		if(CurrGraph) cg = CurrGraph;
		else cg = this;
		rc_mrk.left = CurrDisp->un2ix(cg->GetSize(SIZE_GRECT_LEFT))-4;
		rc_mrk.right = CurrDisp->un2ix(cg->GetSize(SIZE_GRECT_RIGHT))+4
			+ iround(CurrDisp->MenuHeight);
		rc_mrk.top = CurrDisp->un2ix(cg->GetSize(SIZE_GRECT_TOP))-4 
			- iround(CurrDisp->MenuHeight);
		rc_mrk.bottom = CurrDisp->un2ix(cg->GetSize(SIZE_GRECT_BOTTOM))+4;
		if(!(CurrDisp->ActualSize(&cw)))return false;
		f1 = (double)(cw.bottom - cw.top)/(double)(rc_mrk.bottom - rc_mrk.top);
		f2 = ((double)(cw.right - cw.left)/(double)(rc_mrk.right - rc_mrk.left));
		fac = f1 < f2 ? f1 : f2;
		if((CurrDisp->VPscale * fac) > 100.0) fac = 100.0/CurrDisp->VPscale;
		if((CurrDisp->VPscale * fac) < 0.05) fac = 0.05/CurrDisp->VPscale;
		if(fac == 1.0) return false;
		if(tz = (ZoomDEF*)memdup(zoom_def, sizeof(ZoomDEF)*(zoom_level+1), 0)){
			if(zoom_def) free(zoom_def);
			zoom_def = tz;
			zoom_def[zoom_level].org.fx = CurrDisp->VPorg.fx;
			zoom_def[zoom_level].org.fy = CurrDisp->VPorg.fy;
			zoom_def[zoom_level].scale = CurrDisp->VPscale;
			zoom_level++;
			}
		CurrDisp->VPscale *= fac;
		if(CurrDisp->VPscale < 0.05) CurrDisp->VPscale = 0.05; 
		if(CurrDisp->VPscale > 100.0) CurrDisp->VPscale = 100.0; 
		CurrDisp->VPorg.fx = -rc_mrk.left * fac;
		CurrDisp->VPorg.fy = -rc_mrk.top * fac;
		HideTextCursor();
		Command(CMD_SETSCROLL, 0L, CurrDisp);
		return true;
		}
	else if(0==strcmp("+", z)) {
		if(rc_mrk.left >= 0 && rc_mrk.right >= 0 && rc_mrk.top >= 0 && rc_mrk.bottom >= 0) {
			if(rc_mrk.left > rc_mrk.right) Swap(rc_mrk.left, rc_mrk.right);
			if(rc_mrk.top > rc_mrk.bottom) Swap(rc_mrk.top, rc_mrk.bottom);
			if(5 > (rc_mrk.right - rc_mrk.left) || 5 > (rc_mrk.bottom - rc_mrk.top)) {
				rc_mrk.left = rc_mrk.left = rc_mrk.left = rc_mrk.left = -1;
				ToolMode = TM_STANDARD;		Command(CMD_TOOLMODE, &ToolMode, CurrDisp);
				return false;
				}
			if(!(CurrDisp->ActualSize(&cw)))return false;
			fac = (double)(cw.bottom - cw.top)/(double)(rc_mrk.bottom - rc_mrk.top);
			fac += ((double)(cw.right - cw.left)/(double)(rc_mrk.right - rc_mrk.left));
			fac /= 2.0;
			if((CurrDisp->VPscale * fac) > 100.0) fac = 100.0/CurrDisp->VPscale;
			if((CurrDisp->VPscale * fac) < 0.05) fac = 0.05/CurrDisp->VPscale;
			if(fac == 1.0) return false;
			if(tz = (ZoomDEF*)memdup(zoom_def, sizeof(ZoomDEF)*(zoom_level+1), 0)){
				if(zoom_def) free(zoom_def);
				zoom_def = tz;
				zoom_def[zoom_level].org.fx = CurrDisp->VPorg.fx;
				zoom_def[zoom_level].org.fy = CurrDisp->VPorg.fy;
				zoom_def[zoom_level].scale = CurrDisp->VPscale;
				zoom_level++;
				}
			CurrDisp->VPscale *= fac;
			if(CurrDisp->VPscale < 0.05) CurrDisp->VPscale = 0.05; 
			if(CurrDisp->VPscale > 100.0) CurrDisp->VPscale = 100.0; 
			CurrDisp->VPorg.fx = CurrDisp->VPorg.fx * fac - rc_mrk.left * fac;
			CurrDisp->VPorg.fy = CurrDisp->VPorg.fy * fac - rc_mrk.top * fac;
			HideTextCursor();
			Command(CMD_SETSCROLL, 0L, CurrDisp);
			CurrDisp->MouseCursor(MC_ARROW, false);
			rc_mrk.left = rc_mrk.left = rc_mrk.left = rc_mrk.left = -1;
			ToolMode = TM_STANDARD;		Command(CMD_TOOLMODE, &ToolMode, CurrDisp);
			return true;
			}
		else {
			ToolMode = TM_ZOOMIN;			CurrDisp->MouseCursor(MC_ZOOM, true);
			}
		}
	else if(0==strcmp("-", z)) {
		HideTextCursor();
		if(zoom_def && zoom_level > 0) {
			zoom_level--;
			if(CurrDisp->VPscale == zoom_def[zoom_level].scale &&
				CurrDisp->VPorg.fx == zoom_def[zoom_level].org.fx &&
				CurrDisp->VPorg.fy == zoom_def[zoom_level].org.fy) {
				DoZoom(z);
				}
			else {
				CurrDisp->VPscale = zoom_def[zoom_level].scale;
				CurrDisp->VPorg.fx = zoom_def[zoom_level].org.fx;
				CurrDisp->VPorg.fy = zoom_def[zoom_level].org.fy;
				}
			}
		else {
			CurrDisp->VPorg.fx = CurrDisp->VPorg.fy = 0.0;
			CurrDisp->VPscale = Id == GO_PAGE ? 0.5 : 1.0;
			}
		Command(CMD_SETSCROLL, 0L, CurrDisp);
		return true;
		}
	else if(0==strcmp("25", z)){
		CurrDisp->VPscale = 0.25;		return DoZoom("org");
		}
	else if(0==strcmp("50", z)){
		CurrDisp->VPscale = 0.50;		return DoZoom("org");
		}
	else if(0==strcmp("100", z)){
		CurrDisp->VPscale = 1.00;		return DoZoom("org");
		}
	else if(0==strcmp("200", z)){
		CurrDisp->VPscale = 2.00;		return DoZoom("org");
		}
	else if(0==strcmp("400", z)){
		CurrDisp->VPscale = 4.00;		return DoZoom("org");
		}
	else if(0==strcmp("org", z)){
		CurrDisp->VPorg.fx = 0.0;		CurrDisp->VPorg.fy = iround(CurrDisp->MenuHeight);
		HideTextCursor();
		Command(CMD_SETSCROLL, 0L, CurrDisp);
		return DoZoom("reset");
		}
	else if(0==strcmp("reset", z)){
		if(zoom_def && zoom_level > 0) free(zoom_def);
		zoom_def = 0L;			zoom_level = 0;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create a tree structure of all interesting objects
//   This object is used e.g. for layer control
ObjTree::ObjTree(GraphObj *par, DataObj *d, GraphObj *root):GraphObj(par, d)
{
	Id = GO_OBJTREE;					base = root;		list=0L;
	TextDef.ColTxt = 0x00000000L;		TextDef.ColBg = 0x00ffffffL;
	TextDef.fSize = 4.0;				TextDef.RotBL = TextDef.RotCHAR = 0.0;
	TextDef.iSize = 0;					TextDef.Align = TXA_HLEFT | TXA_VTOP;
	TextDef.Mode = TXM_TRANSPARENT;		TextDef.Style = TXS_NORMAL;
	TextDef.Font = FONT_HELVETICA;		TextDef.text = 0L;
	Command(CMD_LAYERS, 0L, 0L);
}

ObjTree::~ObjTree()
{
	if(list) free(list);		list = 0L;
}

void
ObjTree::DoPlot(anyOutput *o)
{
	int i, n, ix, iy;
	GraphObj *curr_obj;

	if(!o || !list) return;
	o->Erase(0x00ffffffL);
	ix = 10;	iy = 0;
	for(i = 0; i < count; i++, iy += (TextDef.iSize+_SBINC)) {
		if(list[i]) {
			curr_obj = list[i];			n = 0;
			if(i) while(curr_obj && curr_obj != list[0]) {
				if(curr_obj != list[0]) n += rlp_strcpy(TmpTxt+n, TMP_TXT_SIZE -n, " -");
				if(curr_obj) curr_obj = curr_obj->parent; 
				}
			if(n) TmpTxt[n++] = ' ';
			n += rlp_strcpy(TmpTxt+n, TMP_TXT_SIZE -n, get_name(i));
			if(list[i]->Id >= GO_PLOT && list[i]->Id < GO_GRAPH) {
				TextDef.ColTxt = ((Plot*)list[i])->hidden ? 0x00000080L : 0x00008000L;
				}
			else TextDef.ColTxt = 0x00000000L;
			o->SetTextSpec(&TextDef);
			o->oTextOut(ix, iy, TmpTxt, 0);
			}
		}
}

bool
ObjTree::Command(int cmd, void *tmpl, anyOutput *o)
{
	switch(cmd){
	case CMD_LAYERS:
		if(list) free(list);
		count = 0;		maxcount = 100;
		if(base) {
			if(list = (GraphObj**) malloc(100 * sizeof(GraphObj*))) {
				list[count++] = base;
				}
			base->Command(CMD_OBJTREE, this, 0L);
			}
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_OBJTREE;
		return true;
	case CMD_UPDATE:
		if(tmpl && (((GraphObj*)tmpl)->Id >= GO_PLOT && ((GraphObj*)tmpl)->Id < GO_SPREADDATA)
			|| ((GraphObj*)tmpl)->Id == GO_LEGEND) {
			if(count >= maxcount) {
				maxcount += 100;
				list = (GraphObj**) realloc(list, maxcount);
				}
			if(list) list[count++] = (GraphObj*)tmpl;
			}
		return true;
	case CMD_TEXTDEF:
		if(tmpl) memcpy(&TextDef, tmpl, sizeof(TextDEF));
		TextDef.text = 0L;
		return true;
		}
	return false;
}

anyOutput *
ObjTree::CreateBitmap(int *bw, int *bh, anyOutput *tmpl)
{
	anyOutput *bmp = 0L;
	int h;
	
	h = (tmpl->un2iy(TextDef.fSize)+_SBINC) * (count+1);
	if(h > *bh) *bh = h;
	if(bmp = NewBitmapClass(*bw, *bh, tmpl->hres, tmpl->vres)) DoPlot(bmp);
	return bmp;
}

char *
ObjTree::get_name(int li)
{
	if(li < count && list[li] && list[li]->name) return list[li]->name;
	else return "(unknown)";
}

int
ObjTree::get_vis(int li)
{
	if(li < count && list[li] && list[li]->Id >= GO_PLOT && list[li]->Id < GO_GRAPH)
		return ((Plot*)list[li])->hidden ? 0 : 1;
	return 2;
}

bool
ObjTree::set_vis(int li, bool vis)
{
	if(li < count && list[li] && list[li]->Id >= GO_PLOT && list[li]->Id < GO_GRAPH) {
		((Plot*)list[li])->hidden = vis ? 0 : 1;
		list[li]->Command(CMD_MRK_DIRTY, 0L, 0L);
		list[li]->Command(CMD_REDRAW, 0L, 0L);
		}
	return false;
}

GraphObj*
ObjTree::get_obj(int li)
{
	if(li < count && list[li]) return list[li];
	return 0L;
}

