/****************************************************************************/
/*   xmCi2.C                                                                */
/****************************************************************************/
/*                                                                          */
/*   Copyright (c) 1992, 1993 Bernhard Strassl                              */
/*       Vienna User Interface Group                                        */
/*       Institute for Applied Computer Science and Information Systems     */
/*       University of Vienna, Austria                                      */
/*                                                                          */
/*   See file COPYRIGHT in this directory for details. If this file is      */
/*   missing please mail to xmpp@ani.univie.ac.at                           */
/****************************************************************************/

#include "xmCi2.h"
#include <stream.h>

CiObject::CiObject(DrawPrim* p, char* n, ciAttrib a) : DrawPrim(p->data)
{
	relCoord = p->origin();
	p->data = NULL;
	delete p;
	initObject(n, a);
}

CiObject::CiObject(XmControl*, char*, ciAttrib) : DrawPrim((gprData* )NULL)
{
	cerr << "ciObject: sorry, controls not supported in this release.\n";
}

CiObject::~CiObject()
{
// cerr << "ciObj " << instName << " destr...";
//	long rc;
	if(parent)
		/*rc = (long )*/ parent->removeChild(this);
	else if(world)
		/*rc = (long )*/ world->removeChild(this);
// cerr << " returned: " << rc << " children: " << numChildren << "\n";

	for(int i = 0; i < numChildren; i++)
		delete children[i];
	delete instName;
}

void CiObject::initObject(char* aName, ciAttrib a)
{
	basePtr = (XmRootClass* )((void* )this);	// needed only by IBM's xlC...
	world = NULL;
	parent = NULL;
	numChildren = 0;
	objData = NULL;
	instName = strcpy(new char[strlen(aName) + 1], aName);
	attributes = a;
	reAssigned = NULL;
	moved = FALSE;
	methodCount = 0;
	for(int i = 0; i < CI_NUM_INTERACTION_PRIMITIVES; i++)
		preOpCallbacks[i] = postOpCallbacks[i] = NULL;
}

bool CiObject::callCallback(ciPrim p, bool pop, int nx, int ny, CiObject* anObj, int op)
{
	bool reply = TRUE;
#ifdef AIX
	ciCallbackInfo* cbk;

	if(pop)
		cbk = preOpCallbacks[p];
	else
		cbk = postOpCallbacks[p];
#else
	ciCallbackInfo* cbk = (pop ? preOpCallbacks : postOpCallbacks)[p];
#endif
	void* clData, * answer = NULL;
// cerr << "(" << pop << "-" << getName() << " calling " << cbk << ")";
	if(cbk)
	{	if(!cbk->receiver || !validProc(cbk->callback))		// also for AIX...
		{	cerr << "ciObject: error - invalid callback info.\n";
			return(FALSE);
		}
		if(cbk->clientData == CB_OBJ_NAME)
			answer = instName;
		else if(cbk->clientData == CB_CLASS_NAME)
			answer = className();
		else if(cbk->clientData == CB_OBJ_PTR)
			answer = this;
		else if(cbk->clientData > CB_CLASS_NAME)
			answer = cbk->clientData;
		clData = answer;
		if(pop || cbk->clientData == CB_CI_DATA)
			clData = new ciClientData(reply, answer ? answer : this, nx, ny, anObj, op);
// cerr << "calling...\n";
		((cbk->receiver)->*(cbk->callback))(clData);
	}
	if(cbk && (pop || cbk->clientData == CB_CI_DATA))
		delete (ciClientData* )clData;
	return(reply);
}

void CiObject::changeAssignInfo(bool sr, int kind, char* name)
{
	char** names = assigninf.names[kind];
	int curNum = assigninf.nums[kind];

	for(int i = 0; i < curNum; i++)
	{	if(!strcmp(names[i], name))
		{	curNum = i;
			break;
		}
	}

	if(sr)	//add
	{	if(curNum == assigninf.nums[kind])
		{	if(assigninf.nums[kind] < CI_MAX_ASSIGNINFO_NAMES)
				names[assigninf.nums[kind]++] = strcpy(new char[strlen(name) + 1], name);
		}
	}
	else	// remove
	{	for(int j = curNum; j < assigninf.nums[kind] - 1; j++)
				names[j] = names[j + 1];
	}
}

bool parseWildcards(char* cmp, char* aName)
{
	while(*cmp && *aName)
	{	if(*cmp == *aName || *cmp == '?')
		{	cmp++;
			aName++;
			continue;
		}
		else if(*cmp == '*')
		{	if(!cmp[1])
				return(TRUE);
			if(parseWildcards(cmp + 1, aName))
				return(TRUE);
			else
				aName++;
			continue;
		}
		return(FALSE);
	}
	return(!*cmp && !*aName ? TRUE : FALSE);
}

bool CiObject::matchAssignInfo(int kind, char* aName)
{
	char** names = assigninf.names[kind];
// cerr << "matching: " << kind << " - " << className() << " / " << instName << " for: " << aName;
	for(int i = 0; i < assigninf.nums[kind]; i++)
	{	// cerr << " (" << assigninf.names[kind][i] << "==" << aName << ") ";
		if(parseWildcards(assigninf.names[kind][i], aName))
		{	// cerr << " -> TRUE\n";
			return(TRUE);
		}
	}
	// cerr << " -> FALSE\n";
	return(FALSE);
}

CiObject* CiObject::addChild(CiObject* anObj, bool showIt)
{
	int x = anObj->relCoord.x;
	int y = anObj->relCoord.y;

	if(numChildren < CI_MAX_CHILDREN)
	{	if(showIt)
			showIt = anObj->getWorld() ? FALSE : TRUE;
		children[numChildren++] = anObj;
		anObj->parent = this;
		makeAbsolute(x, y);
		anObj->setOrigin(x, y);
		if(showIt && getWorld())
			anObj->addTreeTo(getWorld());
		return(anObj);
	}
	cerr << "ciObject - error: Maximum number of children exceeded.\n";
	return(NULL);
}

CiObject* CiObject::removeChild(CiObject* anObj, bool hideIt)
{
// cerr << instName << " removing " << anObj->getName() << " ";
	for(int i = 0; i < numChildren; i++)
	{	if(children[i] == anObj)
		{	numChildren--;
			for(int j = i; j < numChildren; j++)
				children[j] = children[j + 1];
			anObj->parent = NULL;
			if(hideIt)
				anObj->removeTreeFrom(getWorld());
// cerr << "ok\n";
			return(anObj);
		}
	}
// cerr << "failed!\n";
	return(NULL);
}

CiObject** CiObject::getChildren(int code, ciState st, CiObject** all, int* count)
{
	int localNdx = 0, & ndx = count ? *count : localNdx;

	if(!all)
		all = new CiObject*[CI_MAX_TREE_SIZE];
	for(int i = 0; i < numChildren; i++)
	{	CiObject* aChild = child(i);
		if(st == -1 || aChild->getState(st))
			all[ndx++] = aChild;
		if(code == CH_ALL || (code == CH_SHALLOW && !aChild->getState(st)))
			aChild->getChildren(code, st, all, &ndx);
	}
	all[ndx] = NULL;
	return(all);
}

CiObject* CiObject::linkTo(CiObject* anObj)				{ return(anObj->addChild(this)); }
CiObject* CiObject::linkTo(XmCiWorld* aWorld)			{ return(aWorld->addChild(this)); }
CiObject* CiObject::unlink()
{
// cerr << "ciObj " << instName << " unlink...";
//	long rc;
	if(parent)
		/* rc = (long ) */ parent->removeChild(this, TRUE);
	else if(world)
		/* rc = (long ) */ world->removeChild(this);
// cerr << " returned: " << rc << " children: " << numChildren << "\n";
	return(this);
}

// { return(parent ? parent->removeChild(this) : (world ? world->removeChild(this) : NULL)); }

bool CiObject::setAttributes(ciAttrib a, bool sr)
{
	if(sr)
		attributes |= a;
	else
		attributes ^= a;

	if(disp)
		changeFlags(a, sr);
	return(TRUE);
}

bool CiObject::setCallback(ciPrim i, CiObject* o, cbProc p, bool sr, bool pop, void* d)
{
	if(i < CI_NUM_INTERACTION_PRIMITIVES)
	{	ciCallbackInfo** cbk = (pop ? preOpCallbacks : postOpCallbacks) + i;
		delete *cbk;
		*cbk = sr ? new ciCallbackInfo(o->basePtr, p, d) : NULL;
		return(TRUE);
	}
	cerr << "ciObject - error: Invalid interaction primitive for callback.\n";
	return(FALSE);
}

bool CiObject::setCallback(ciPrim i, XmObject* o, cbProc p, bool sr, bool pop, void* d)
{
	if(i < CI_NUM_INTERACTION_PRIMITIVES)
	{	ciCallbackInfo** cbk = (pop ? preOpCallbacks : postOpCallbacks) + i;
		delete *cbk;
		*cbk = sr ? new ciCallbackInfo(o->getBase(), p, d) : NULL;
		return(TRUE);
	}
	cerr << "ciObject - error: Invalid interaction primitive for callback.\n";
	return(FALSE);
}

bool CiObject::disable()	{ return(FALSE); }
bool CiObject::enable()		{ return(FALSE); }

bool CiObject::objMark(bool on, bool notify, bool byUser, bool pop, bool repaint)
{
	if(byUser)
	{	// if(on ? callCallback(Mark, pop) : TRUE)
		if(callCallback(Mark, pop))
		{	if(pop)
				markChildren(on);
			return(TRUE);
		}
		return(FALSE);
	}
	if(on)
	{	if(disp)
		{	// cerr << "disp mark...\n";
			disp->select(this, TRUE, FALSE);
		}
		else
			state |= MARKED;
	}
	else
	{	if(disp)
			disp->deSelect(this, FALSE);
		else
			state ^= MARKED;
	}
	markChildren(on);
	if(disp && repaint)
		disp->refresh();
	if(notify)
		callCallback(Mark, FALSE);
	return(TRUE);
}

//			if(rec == this ? callCallback(Move, pop, x, y, rec) : callCallback(Assign, pop, x, y, rec))

bool CiObject::objAssign(CiObject* rec, int x, int y, bool notify, bool byUser, bool pop, bool repaint)
{
	if(byUser)
	{	gprPoint p = origin();
		if(x != p.x || y != p.y)
		{	CiObject* aParent = pop ? parent : (reAssigned > (CiObject* )1 ? reAssigned : NULL);

// cerr << pop << " - this: " << getName() << ", old parent: " << (aParent ? aParent->getName() : "WORLD")
//	<< ", new parent: " << (rec ? rec->getName() : "WORLD") << "\n";

			if(	(rec == aParent ? callCallback(Move, pop, x, y, rec) :
				(callCallback(Assign, pop, x, y, rec) && 
				(aParent ? aParent->callCallback(ChangeContents, pop, x, y, this, OP_REMOVE) : TRUE) &&
				(rec ? rec->callCallback(ChangeContents, pop, x, y, this, OP_ADD) : TRUE))))
			{	if(pop && rec != parent)
				{	reAssigned = parent ? parent : (CiObject* )TRUE;
					if(parent)
						parent->removeChild(this);
					if(rec)
						rec->addChild(this, FALSE);
				}
				else if(pop)
					moved = TRUE;				
				return(TRUE);
			}
		}
		return(FALSE);
	}
	gprPoint delta = origin();
	if(rec)
		rec->makeAbsolute(x, y);
	setOrigin(x, y);
	update();
	if(rec != parent)
	{	if(parent)
			parent->removeChild(this);
		if(rec)
			rec->addChild(this, FALSE);
	}
	delta.x = x - delta.x;
	delta.y = y - delta.y;
	relocateChildren(delta.x, delta.y);
	if(disp && repaint)
		disp->refresh();
	if(notify)
		callCallback(rec == this ? Move : Assign, FALSE, x, y, rec);
	return(TRUE);
}

bool CiObject::objResize(int w, int h, bool notify, bool byUser, bool pop, bool repaint)
{
	if(byUser)
		return(callCallback(Resize, pop, w, h));
	setExtent(w, h);
	update();
	if(disp && repaint)
		disp->refresh();
	if(notify)
		callCallback(Resize, FALSE, w, h);
	return(TRUE);
}

void CiObject::objSelect(int x, int y)
{
	callCallback(Select, FALSE, x, y);
}

void CiObject::objActivate()
{
	callCallback(Activate, FALSE);
}

bool CiObject::objChangeData(void*, bool, bool, bool)
{
	return(FALSE);
}

void CiObject::addTreeTo(XmCiWorld* w)
{
	world = w;
	w->setDefaultFlags(attributes);
	w->add(this);
	for(int i = 0; i < numChildren; i++)
		children[i]->addTreeTo(w);
}

void CiObject::removeTreeFrom(XmCiWorld* w)
{
	world = NULL;
	w->remove(this);
	for(int i = 0; i < numChildren; i++)
		children[i]->removeTreeFrom(w);
}

void CiObject::markChildren(bool onOrOff)
{
// cerr << "marking " << numChildren << "\n";
	for(int i = 0; i < numChildren; i++)
	{	children[i]->objMark(onOrOff, FALSE, FALSE, FALSE, FALSE);
		disp->makeSelDepend(children[i], this);
	}
}

void CiObject::relocateChildren(int dx, int dy)
{
	for(int i = 0; i < numChildren; i++)
	{	CiObject* aChild = children[i];
		gprPoint p = aChild->origin();
		aChild->setOrigin(p.x + dx, p.y + dy);
		aChild->relocateChildren(dx, dy);
	}
}

void CiObject::restackChild(CiObject* obj, bool dir)
{
	for(int i = 0; i < numChildren; i++)
	{	CiObject* aChild;
		if((aChild = children[i]) == obj)
		{	if(dir)
			{	for(int j = i; j > 0; j--)
					children[j] = children[j - 1];
				children[0] = aChild;
			}
			else
			{	for(int j = i; j < numChildren - 1; j++)
					children[j] = children[j + 1];
				children[numChildren - 1] = aChild;
			}
			break;
		}
	}
}

CiObject* CiObject::getMarkedRoot()
{
	if(!parent)
	{	if(!getState(MARKED))
			cerr << "ciObject: Internal Error - (" << this << "/" << instName << ") getMarkedRoot\n";
		return(this);
	}
	if(!getState(MARKED))
	{	cerr << "ciObject: Warning - cannot answer marked root (this not marked).\n";
		return(NULL);
	}
	if(parent->getState(MARKED))
		return(parent->getMarkedRoot());
	return(this);
}

bool CiObject::registerMethod(char* msg, cbProc cbk, char* pt)
{
	int newndx = findMethod(msg, pt);

	if(newndx >= 0)
	{	delete protocol[newndx];
		if(!validProc(cbk))
		{	methodCount--;
			for(int i = newndx; i < methodCount; i++)
				protocol[i] = protocol[i + 1];
			return(TRUE);
		}
	}
	else
	{	if(methodCount >= CI_MAX_METHODS)
		{	cerr << "ciObject: maximum number of methods exceeded, ignoring \"" << msg << "\"\n";
			return(FALSE);
		}
		newndx = methodCount++;
	}
	protocol[newndx] = new ciMethod(msg, pt, cbk);
// cerr << "registered " << msg << " as " << newndx << "\n";
	return(TRUE);
}

int CiObject::findMethod(char* n, char* t)
{
	for(int i = 0; i < methodCount; i++)
	{	if(protocol[i]->match(n, t))
			return(i);
	}
	return(-1);
}

bool CiObject::msg(char* msg, char* pt, void* param)
{
	int callndx = findMethod(msg, pt);

	if(callndx >= 0)
	{	
// cerr << "calling " << msg << " as " << callndx << "\n";
		(this->*(protocol[callndx]->callback))(param);
		return(TRUE);
	}
	return(FALSE);
}

gprPoint CiObject::calcRel(gprPoint p)
{
	if(parent)
	{	int x = p.x;
		int y = p.y;
// cerr << "calcrel - " << x << "@" << y << "/";
		parent->makeRelative(x, y);
		p.x = x;
		p.y = y;
// cerr << p.x << "@" << p.y << "\n";
	}
	return(p);
}

XmCiWorld* CiObject::getWorld()
{
	if(world)
		return(world);
	return(parent ? parent->getWorld() : NULL);
}

bool CiObject::getState(ciState s)
{
	if(disp)
		return(disp->getStatus(this, s));
	return(state & s ? TRUE : FALSE);
}

void CiObject::makeRelative(int& x, int& y)
{
	gprPoint loc = origin();

	x -= loc.x;
	y -= loc.y;
}

void CiObject::makeAbsolute(int& x, int& y)
{
	gprPoint loc = origin();

	x += loc.x;
	y += loc.y;
}

void CiObject::toForeground(bool restack)
{
	if(disp)
	{	disp->toForeground(this);
		if(restack)
		{	if(parent)
				parent->restackChild(this, TRUE);
			else if(world)
				world->restackChild(this, TRUE);
		}
		for(int i = numChildren; i > 0; i--)
			children[i - 1]->toForeground(FALSE);
	}
}

void CiObject::toBackground(bool restack)
{
	if(disp)
	{	for(int i = 0; i < numChildren; i++)
			children[i]->toBackground(FALSE);
		disp->toBackground(this);
		if(restack)
		{	if(parent)
				parent->restackChild(this, FALSE);
			else if(world)
				world->restackChild(this, FALSE);
		}
	}
}

#ifndef NO_VARARGS

#include <stdarg.h>

#define do_varargs(sr, objkind, classkind) \
	char* aName = first; \
	va_list ap; \
	va_start(ap, first); \
	do \
		changeAssignInfo(sr, strstr(aName, "Class") ? classkind : objkind, aName); \
	while((aName = va_arg(ap, char *)) != (char *)0); \
	va_end(ap);

#else

#define do_varargs(sr, objkind, classkind) \
	changeAssignInfo(sr, strstr(first, "Class") ? classkind : objkind, first);

#endif /* NO_VARARGS */

void CiObject::allowAssignmentOf(char* first, ...)
{
	do_varargs(TRUE, CHILD_OBJECTS, CHILD_CLASSES)
}

void CiObject::denyAssignmentOf(char* first, ...)
{
	do_varargs(FALSE, CHILD_OBJECTS, CHILD_CLASSES)
}

void CiObject::allowAssigningTo(char* first, ...)
{
	do_varargs(TRUE, PARENT_OBJECTS, PARENT_CLASSES)
}

void CiObject::denyAssigningTo(char* first, ...)
{
	do_varargs(FALSE, PARENT_OBJECTS, PARENT_CLASSES)
}

XmCiWorld::XmCiWorld(char* n, int x, int y, int w, int h) : XmDrawing(n, x, y, w, h)
{
	numChildren = 0;
}

XmCiWorld::~XmCiWorld()
{
}

CiObject* XmCiWorld::addChild(CiObject* anObj)
{
	if(numChildren < CI_MAX_CHILDREN)
	{	children[numChildren++] = anObj;
		anObj->addTreeTo(this);
		return(anObj);
	}
	cerr << "ciWorld - error: Maximum number of children exceeded.\n";
	return(NULL);

}

CiObject* XmCiWorld::removeChild(CiObject* anObj)
{
	for(int i = 0; i < numChildren; i++)
	{	if(children[i] == anObj)
		{	numChildren--;
			for(int j = i; j < numChildren; j++)
				children[j] = children[j + 1];
			anObj->removeTreeFrom(this);
			return(anObj);
		}
	}
	return(NULL);
}

CiObject** XmCiWorld::getChildren(int cd, ciState st, CiObject** all, int* count)
{
	int localNdx = 0, & ndx = count ? *count : localNdx;

	if(!all)
		all = new CiObject*[CI_MAX_TREE_SIZE];
	for(int i = 0; i < numChildren; i++)
	{	CiObject* aChild = child(i);
		if(st == -1 || aChild->getState(st))
			all[ndx++] = aChild;
		if(cd == CH_ALL || (cd == CH_SHALLOW && !aChild->getState(st)))
			aChild->getChildren(cd, st, all, &ndx);
	}
	all[ndx] = NULL;
	return(all);
}

void XmCiWorld::restackChild(CiObject* obj, bool dir)
{
	for(int i = 0; i < numChildren; i++)
	{	CiObject* aChild;
		if((aChild = children[i]) == obj)
		{	if(dir)
			{	for(int j = i; j > 0; j--)
					children[j] = children[j - 1];
				children[0] = aChild;
			}
			else
			{	for(int j = i; j < numChildren - 1; j++)
					children[j] = children[j + 1];
				children[numChildren - 1] = aChild;
			}
			break;
		}
	}
}

bool XmCiWorld::userClick(int, int)
{
	return(TRUE);
}

bool XmCiWorld::userSelect(DrawPrim* p, int, int)
{
	if(!p)
		return(TRUE);
	if(((CiObject* )p)->objMark(TRUE, TRUE, TRUE, TRUE))
	{	// refresh(FALSE);	// automatically done by the drawing class after return...
		return(TRUE);
	}
	return(FALSE);
}

#define validate_move ((bool )((aTarget == anObj->parent ? FALSE : anObj->validateParent(aTarget)) && (aTarget ? aTarget->validateChild(anObj) : TRUE)))
/*
bool XmCiWorld::userMove(DrawPrim*, int, int, int, int)
{
	return(FALSE);
}
*/

bool XmCiWorld::userMove(DrawPrim* p, int x, int y, int, int)
{
	CiObject* anObj = (CiObject* )p, * aRoot, * aTarget = (CiObject* )curDragTarget();

	if(!anObj)
	{	CiObject** tmpPrims = new CiObject*[primCount];
		int count, i;
		count = primCount;			// primCount may change during operation...
		for(i = 0; i < count; i++)
			tmpPrims[i] = (CiObject* )prims[i];
		for(i = 0; i < count; i++)
		{	if((anObj = tmpPrims[i])->reAssigned)
			{	anObj->toForeground(FALSE);
				if((aRoot = anObj->getMarkedRoot()) && anObj == aRoot)
					anObj->objAssign(aTarget, x, y, TRUE, TRUE, FALSE);
				anObj->reAssigned = NULL;
			}
			anObj->moved = FALSE;
		}
		return(TRUE);
	}
	if(!(aRoot = anObj->getMarkedRoot()))
		return(FALSE);
	bool yesno = TRUE;
	if(aTarget != aRoot->parent)
	{	if(aRoot->validateParent(aTarget))
		{	if(aTarget)
				yesno = aTarget->validateChild(aRoot);
		}
		else
			yesno = FALSE;
	}
	if(yesno && anObj == aRoot)
		anObj->objAssign(aTarget, x, y, TRUE, TRUE, TRUE);
	return(yesno);
}

bool XmCiWorld::userResize(DrawPrim* p, int w, int h)
{
	return(((CiObject* )p)->objResize(w, h, TRUE, TRUE, TRUE));
}

void XmCiWorld::userActivate(DrawPrim* p, int, int)
{
	((CiObject* )p)->objActivate();
}

#define validate_drag ((bool )((aTarget == anObj->parent ? TRUE : anObj->validateParent(aTarget)) && (aTarget ? aTarget->validateChild(anObj) : TRUE)))
/*
bool XmCiWorld::userDragEnter(DrawPrim*, DrawPrim**, bool*)
{
	return(FALSE);
}
*/

bool XmCiWorld::userDragEnter(DrawPrim* target, DrawPrim** dragged, bool* vals)
{
	CiObject* anObj, * aTarget = (CiObject* )target;
	int i = 0;
// cerr << "Target: " << (aTarget ? aTarget->instName : "WORLD") << "\n";
	while(anObj = (CiObject* )dragged[i])
	{
// cerr << anObj->instName << " - " << anObj << "/" << anObj->getMarkedRoot() << "\n";
		anObj = anObj->getMarkedRoot();
		// vals[i++] = validate_drag;
		bool yesno = FALSE;
		if(aTarget == anObj->parent)
			yesno = TRUE;
		else
		{	if(anObj->validateParent(aTarget))
			{	if(aTarget)
					yesno = aTarget->validateChild(anObj);
				else
					yesno = TRUE;
			}
		}
		vals[i++] = yesno;
// cerr << i << "(" << anObj->instName << " " << vals[i-1] << ") ";
	}
// cerr << ".\n";
	return(TRUE);
}


