/* ScianComplexControls.c: John R Murray August 23, 1990
	Repository for all those weird controls with lots of little
	thingamabobs that you can move around */

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianColors.h"
#include "ScianIDs.h"
#include "ScianErrors.h"
#include "ScianEvents.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianComplexControls.h"
#include "ScianMethods.h"
#include "ScianArrays.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianScripts.h"
#include "ScianDialogs.h"
#include "ScianStyle.h"
#include "ScianDraw.h"
#include "ScianTextBoxes.h"
#include "ScianSnap.h"

/* globals */
ObjPtr	boundsControlClass;	/* Class of bounds control */
ObjPtr	XYControlClass;		/* Class of XY controls */

#define LINELENGTH	15.0	/* length of border lines */
#define BOUNDSBORDER	8.0	/* blank border around control stuff */
#define LABELFONTFUDGE	1	/* fudge strings up (or <0 = down) to center */

ObjPtr	NewBoundsControl(left, right, bottom, top, label)
int	left, right, bottom, top;
char *label;
/*Makes a new panel with bounds left, right, bottom, top, and label label*/
{
    real        values[6], hilites[6];
    ObjPtr	valuesArray, hilitesArray;
    ObjPtr      retVal;
    ObjPtr      labelPtr;
    ObjPtr      hilightPtr;

    values[0] = 0.0;
    values[1] = 1.0;
    values[2] = 0.0;
    values[3] = 1.0;
    values[4] = 0.0;
    values[5] = 1.0;

    hilites[0] = false;
    hilites[1] = false;
    hilites[2] = false;
    hilites[3] = false;
    hilites[4] = false;
    hilites[5] = false;

    valuesArray = NewRealArray(1, (long) 6);
    hilitesArray = NewRealArray(1, (long) 6);
    if (valuesArray && hilitesArray)
    {
	CArray2Array(valuesArray, values);
	CArray2Array(hilitesArray, hilites);

	retVal = NewObject(boundsControlClass, 0);
	Set2DIntBounds(retVal, left, right, bottom, top);
	SetVar(retVal, VALUE, (ThingPtr) valuesArray);
	SetVar(retVal, HIGHLIGHTED, (ThingPtr) hilitesArray);
	labelPtr = NewString(label);
	SetVar(retVal, NAME, labelPtr);
	return retVal;
    }
    else
    {
        return NULLOBJ;
    }
}

void	SetBoundsControl(obj)
ObjPtr obj;
{
    if (logging) LogControl(obj);
}

/* Methods functions for Bounds Controls */

myMin(a,b)
int a,b;
{
    return a < b ? a : b;
}

int	DrawBoundsControl(theControl)
ObjPtr	theControl;
{
#ifdef GRAPHICS
    int left, right, bottom, top;
    ObjPtr	theLabel;
    char        *label;
    ObjPtr	valuesArray, hilitesArray;
    real        values[6], hilites[4];
    real        xmin, xmax, ymin, ymax, zmin, zmax;
    Coord       v[4][2];
    real        xPos, yPos;
    real        xRel, yRel;
    real        fudgeScale;
    real	recsize, recwidth, recdepth;

    /* get bounds, and draw control background */
    if (!Get2DIntBounds(theControl, &left, &right, &bottom, &top))
    {
	return false;
    }

    if (IsDrawingRestricted(left, right, bottom, top))
    {
	return false;
    }

    valuesArray = GetFixedArrayVar("DrawBoundsControl",theControl,VALUE,1,6L);
    hilitesArray = GetFixedArrayVar("DrawBoundsControl", theControl,
					HIGHLIGHTED, 1, 4L);
    if (valuesArray && hilitesArray)
    {
        Array2CArray(values, valuesArray);
        xmin = values[0];
        xmax = values[1];
        ymin = values[2];
        ymax = values[3];
        zmin = values[2];
        zmax = values[3];

        Array2CArray(hilites, hilitesArray);
    }
    else
    {
        /* figure out a recovery? */
        ReportError("DrawBoundsControl", "control missing bounds or values!\n");
        return;
    }
    DrawRaisedRect(left, right, bottom, top, UIBACKGROUND);

    /* attempt to draw the control label */
    theLabel = GetStringVar("DrawBoundsControl", theControl, NAME);
    if (theLabel)
    {

        label = GetString(theLabel);
        SetUIColor(UITEXT);
	SetupFont(BUTTONFONT, BUTTONFONTSIZE);
	DrawAString(CENTERALIGN, (left + right) / 2,
		   (top+bottom)/2 + LABELFONTFUDGE - (int)BUTTONFONTSIZE/2,
		   label);
    }

    SetUIColor(UITEXT);

    /* Draw the rest of the parts of the control */
    recsize = myMin(right - left, top - bottom) - 2*(BOUNDSBORDER + LINELENGTH + 3);
    recwidth = (int) ((2*recsize) / 3);
    recdepth = (int) (recsize - recwidth);
    /* Draw icon-like central figure, in center of control */
    move2((int) ((right - left) / 2),(int) ((top - bottom) / 2));
    rmv2((int) (recdepth / 2.0), (int) (recdepth / 2.0));
    rdr2(-recwidth, 0.0);
    rdr2(0.0, -recwidth);
    rdr2(recwidth, 0.0);
    rdr2(0.0, recwidth);
    rdr2(recdepth, recdepth);
    rdr2(-recwidth, 0.0);
    rdr2(0.0, -recwidth);
    rdr2(recwidth, 0.0);
    rdr2(0.0, recwidth);
    rmv2(-recwidth, 0.0);
    rdr2(-recdepth, -recdepth);
    rmv2(0.0, -recwidth);
    rdr2(recdepth, recdepth);
    rmv2(recwidth, 0.0);
    rdr2(-recdepth, -recdepth);

    /* Draw bounds range lines. */

#endif /* GRAPHICS */
}

int	PressBoundsControl(theControl, mouseX, mouseY, whichButton)
ObjPtr		theControl;
int	mouseX, mouseY;
int		whichButton;
{
#ifdef INTERACTIVE
    if (logging) LogControl(theControl);
#endif /* INTERACTIVE */
}

#define INITMINX	0.0
#define INITMAXX	1.0
#define INITX		0.5
#define INITMINY	0.0
#define INITMAXY	1.0
#define INITY		0.5

ObjPtr	NewXYControl(left, right, bottom, top, name)
int	left, right, bottom, top;
char	*name;
{
    real        values[2], range[4];
    ObjPtr	valuesArray, rangeArray;
    ObjPtr      retVal;

    values[0] = INITX;
    values[1] = INITY;

    range[0] = INITMINX;
    range[1] = INITMAXX;
    range[2] = INITMINY;
    range[3] = INITMAXY;

    valuesArray = NewRealArray(1, 2L);
    rangeArray = NewRealArray(1, 4L);
    if (valuesArray && rangeArray)
    {
        CArray2Array(valuesArray, values);
        CArray2Array(rangeArray, range);

        retVal = NewObject(XYControlClass, 0);
	Set2DIntBounds(retVal, left, right, bottom, top);
        SetVar(retVal, VALUE, valuesArray);
	SetVar(retVal, RANGE, rangeArray);
        SetVar(retVal, HIGHLIGHTED, NewInt(false));
	SetVar(retVal, BACKGROUND, NewInt(UIPGREEN));
        SetVar(retVal, NAME, NewString(name));
        return retVal;
    }
    else
    {
	OMErr();
        return NULLOBJ;
    }
}

#define XYCONTROLMARGIN (EDGE + 2)
#define XYCONTROLKNOBSIZE 7
#define XYCONTROLSLOP (SLOP + XYCONTROLMARGIN + XYCONTROLKNOBSIZE)


ObjPtr	DrawXYControl(theControl)
ObjPtr	theControl;
{
#ifdef GRAPHICS
    int		left, right, bottom, top;
    ObjPtr      theLabel;
    char        *label;
    ObjPtr      valuesArray, rangeArray, hilitesArray;
    real        value[2], range[4];
    real        xPos, yPos;
    real        xRel, yRel;
    real        fudgeScale;
    real	recsize, recwidth, recdepth;

    /* get bounds, and draw control background */
    if (!Get2DIntBounds(theControl, &left, &right, &bottom, &top))
    {
        return NULLOBJ;
    }

    if (IsDrawingRestricted(left, right, bottom, top))
    {
	return ObjFalse;
    }

    valuesArray = GetFixedArrayVar("DrawXYControl", theControl, VALUE, 1, 2L);
    rangeArray = GetFixedArrayVar("DrawXYControl", theControl, RANGE, 1, 4L);
    if (valuesArray && rangeArray)
    {
        Array2CArray(value, valuesArray);
	Array2CArray(range, rangeArray);
    }
    else
    {
        /* figure out a recovery? */
        ReportError("DrawXYControl", "control missing values");
        return NULLOBJ;
    }
    /* draw the field */
    DrawField(theControl);
    DrawSunkenRect(left, right, bottom, top, UIPGREEN);

    left = left + XYCONTROLMARGIN + XYCONTROLKNOBSIZE;
    right = right - XYCONTROLMARGIN - XYCONTROLKNOBSIZE;
    bottom = bottom + XYCONTROLMARGIN + XYCONTROLKNOBSIZE;
    top = top - XYCONTROLMARGIN - XYCONTROLKNOBSIZE;

    xPos = left + (right - left) * (value[0]-range[0])/(range[1]-range[0]);
    yPos = bottom + (top - bottom) * (value[1]-range[2])/(range[3]-range[2]);

    /* draw the thingamabob in the middle, for now */
    DrawRaisedRect(xPos - XYCONTROLKNOBSIZE, xPos + XYCONTROLKNOBSIZE,
	yPos - XYCONTROLKNOBSIZE, yPos + XYCONTROLKNOBSIZE,
	GetPredicate(theControl, HIGHLIGHTED) ? UIHIBACKGROUND : UIBACKGROUND);
#endif /* GRAPHICS */
}

ObjPtr PressXYControl(object, mouseX, mouseY, flags)
ObjPtr object;
int mouseX, mouseY;
long flags;
/*Track a click in an XY control.  Double-click snaps to corner, edge, or center*/
{
#ifdef INTERACTIVE
    real left, right, bottom, top;
    int li, ri, bi, ti;
    ObjPtr valueArray, rangeArray;
    real value[2], range[4];
    real oldv0, oldv1;			/* old values of value[0] and [1] */
    int lastX, lastY;			/*Last X and Y mouse position*/
    Bool dontTrack, alwaysChange;	/*True iff don't track, always change*/

    if ( ! Get2DIntBounds(object, &li, &ri, &bi, &ti))
    {
#if 0
	I think Eric changed this in Austin
	GetWindowBounds(&li, &ri, &bi, &ti);
#else
	return ObjFalse;
#endif

    }
    left = li;
    right = ri;
    bottom = bi;
    top = ti;

    if (mouseX < left || mouseX > right || mouseY < bottom || mouseY > top)
    {
	return ObjFalse;
    }

    if (TOOL(flags) == T_HELP)
    {
	ContextHelp(object);
	return ObjTrue;
    }

    /* it's really a press, and not a helpclick, so.. */
    SaveForUndo(object);

    left = left + XYCONTROLMARGIN + XYCONTROLKNOBSIZE;
    right = right - XYCONTROLMARGIN - XYCONTROLKNOBSIZE;
    bottom = bottom + XYCONTROLMARGIN + XYCONTROLKNOBSIZE;
    top = top - XYCONTROLMARGIN - XYCONTROLKNOBSIZE;

    /*Get the current value of the control*/
    valueArray = GetFixedArrayVar("PressXYControl", object, VALUE, 1, 2L);
    rangeArray = GetFixedArrayVar("PressXYControl", object, RANGE, 1, 4L);
    if (!valueArray || !rangeArray)
    { 
	return ObjFalse;
    }
    Array2CArray(value, valueArray);
    Array2CArray(range, rangeArray);

    oldv0 = value[0];
    oldv1 = value[1];
    lastX = value[0];
    lastY = value[1];

    if (flags & F_DOUBLECLICK)
    {
	FuncTyp method;
	/*Snap current value to closest corner, side, or center*/
	if ((mouseX - left) / (right - left)  < 0.25)
	{
	    value[0] = range[0];
	}
	else if ((mouseX - left) / (right - left) > 0.75)
	{
	    value[0] = range[1];
	}
	else
	{
	    value[0] = (range[1] + range[0]) / 2;
	}

	if ((mouseY - bottom) / (top - bottom) < 0.25)
	{
	    value[1] = range[2];
	}
	else if ((mouseY - bottom) / (top - bottom) > 0.75)
	{
	    value[1] = range[3];
	}
	else
	{
	    value[1] = (range[3] + range[2]) / 2;
	}

	valueArray = NewRealArray(1, 2L);
	CArray2Array(valueArray, value);
	SetVar(object, VALUE, valueArray);
	DrawMe(object);
	ChangedValue(object);
	return ObjTrue;
    }
    dontTrack = GetPredicate(object, TRACKNOT);
    alwaysChange = GetPredicate(object, ALWAYSCHANGE);

    SetVar(object, HIGHLIGHTED, NewInt(true));

    if (flags & F_SHIFTDOWN)
    {
	lastX = mouseX;
	lastY = mouseY;

	SetVar(object, HIGHLIGHTED, NewInt(true));
	DrawMe(object);
	while (abs(lastX - mouseX) < 3 && abs(lastY - mouseY) < 3)
	{
	    if (!Mouse(&mouseX, &mouseY))
		return ObjTrue;
	    if (alwaysChange && !dontTrack)
	    {
		ChangedValue(object);
	    }
	}
	if (abs(lastX - mouseX) > abs(lastY - mouseY)) /* X axis only */
	{
	    while (Mouse(&mouseX, &mouseY))
	    {
		if (mouseX != lastX)
		{
		    lastX = mouseX;

		    if (mouseX < left - XYCONTROLSLOP
			|| mouseX > right + XYCONTROLSLOP
			|| mouseY < bottom - XYCONTROLSLOP
			|| mouseY > top + XYCONTROLSLOP)
		    {
			/* outside slop rectangle, restore old values */
			value[0] = oldv0;
			value[1] = oldv1;
			SetVar(object, HIGHLIGHTED, NewInt(false));
		    }
		    else /* calculate new values based on mouse position */
		    {
			value[0] = range[0] + ((mouseX - left) / (right - left)) * (range[1] - range[0]);
			if (value[0] < range[0]) value[0] = range[0];
			if (value[0] > range[1]) value[0] = range[1];
			SetVar(object, HIGHLIGHTED, NewInt(true));
		    }
		    valueArray = NewRealArray(1, 2L);
		    CArray2Array(valueArray, value);
		    SetVar(object, VALUE, valueArray);
		    DrawMe(object);
		    if (!dontTrack)
		    {
			ChangedValue(object);
		    }		    
		}
		else if (alwaysChange && !dontTrack)
		{
		    ChangedValue(object);
		}
	    }
	}
	else /* Y axis only */
	{
	    while (Mouse(&mouseX, &mouseY))
	    {
		if (mouseY != lastY)
		{
		    lastY = mouseY;

		    if (mouseX < left - XYCONTROLSLOP
			|| mouseX > right + XYCONTROLSLOP
			|| mouseY < bottom - XYCONTROLSLOP
			|| mouseY > top + XYCONTROLSLOP)
		    {
			/* outside slop rectangle, restore old values */
			value[0] = oldv0;
			value[1] = oldv1;
			SetVar(object, HIGHLIGHTED, NewInt(false));
		    }
		    else /* calculate new values based on mouse position */
		    {
			value[1] = range[2] + ((mouseY - bottom) / (top - bottom)) * (range[3] - range[2]);
			if (value[1] < range[2]) value[1] = range[2];
			if (value[1] > range[3]) value[1] = range[3];
			SetVar(object, HIGHLIGHTED, NewInt(true));
		    }
		    valueArray = NewRealArray(1, 2L);
		    CArray2Array(valueArray, value);
		    SetVar(object, VALUE, valueArray);
		    DrawMe(object);
		    if (!dontTrack)
		    {
			ChangedValue(object);
		    }		    
		}
		else if (alwaysChange && !dontTrack)
		{
		    ChangedValue(object);
		}
	    }
	}
    }
    else /* free tracking on X and Y axes */
    {
	while (Mouse(&mouseX, &mouseY))
	{
	    if (mouseX != lastX || mouseY != lastY)
	    {
		lastX = mouseX;
		lastY = mouseY;

		if (mouseX < left - XYCONTROLSLOP
		    || mouseX > right + XYCONTROLSLOP
		    || mouseY < bottom - XYCONTROLSLOP
		    || mouseY > top + XYCONTROLSLOP)
		{
		    /* outside slop rectangle, restore old values */
		    value[0] = oldv0;
		    value[1] = oldv1;
		    SetVar(object, HIGHLIGHTED, NewInt(false));
		}
		else /* calculate new values based on mouse position */
		{
		    value[0] = range[0] + ((mouseX - left) / (right - left)) * (range[1] - range[0]);
		    if (value[0] < range[0]) value[0] = range[0];
		    if (value[0] > range[1]) value[0] = range[1];
		    value[1] = range[2] + ((mouseY - bottom) / (top - bottom)) * (range[3] - range[2]);
		    if (value[1] < range[2]) value[1] = range[2];
		    if (value[1] > range[3]) value[1] = range[3];
		    SetVar(object, HIGHLIGHTED, NewInt(true));
		}

		valueArray = NewRealArray(1, 2L);
		CArray2Array(valueArray, value);
		SetVar(object, VALUE, valueArray);
		DrawMe(object);
		if (!dontTrack)
		{
		    ChangedValue(object);
		}
	    }
	    else if (alwaysChange && !dontTrack)
	    {
		DrawMe(object);
		ChangedValue(object);
	    }
	}
    }
    if (dontTrack)
    {
	ChangedValue(object);
    }
    SetVar(object, HIGHLIGHTED, NewInt(false));
    ImInvalid(object);
    if (logging) LogControl(object);
    return ObjTrue;
#endif /* INTERACTIVE */
}

#ifdef PROTO
Bool SetXYControlLimits(ObjPtr theControl, real minX, real maxX,
					   real minY, real maxY)
#else
Bool SetXYControlLimits(theControl, minX, maxX, minY, maxY)
ObjPtr theControl;
real minX, maxX, minY, maxY;
#endif
{
    ObjPtr	valuesArray, rangeArray;
    real	values[2], range[4];
    Bool	changed = false;

    if (minX >= maxX || minY >= maxY)
    {
	/* give up! */
	ReportError("SetXYControlLimits", "error in XY control range\n");
	return false;
    }

    valuesArray = GetFixedArrayVar("SetXYControlLimits", theControl, VALUE, 1, 2L);
    rangeArray = GetFixedArrayVar("SetXYControlLimits", theControl, RANGE, 1, 4L);
    if (!valuesArray || !rangeArray)
    {
	return false;
    }
    Array2CArray(values, valuesArray);
    Array2CArray(range, rangeArray);
    range[0] = minX;
    range[1] = maxX;
    range[2] = minY;
    range[3] = maxY;
    /* adjust slider values if they're now out of range */
    if (values[0]<minX || values[0]>maxX || values[1]<minY || values[1]>maxY)
    {
	changed = true;
	values[0] = values[0]<minX ? minX : (values[0]>maxX ? maxX : values[0]);
	values[1] = values[1]<minY ? minY : (values[1]>maxY ? maxY : values[1]);
	CArray2Array(valuesArray, values);
    }
    CArray2Array(rangeArray, range);

    ImInvalid(theControl);
    if (changed)
    {
	ChangedValue(theControl);
    }

    return true;
}

Bool GetXYControlLimits(obj, minX, maxX, minY, maxY)
ObjPtr obj;
real *minX, *minY, *maxX, *maxY;
{
    ObjPtr rangeArray;
    real range[4];

    rangeArray = GetFixedArrayVar("GetXYControlLimits", obj, RANGE, 1, 4L);
    if (!rangeArray)
    {
	return false;
    }
    Array2CArray(range, rangeArray);

    *minX = range[0];
    *maxX = range[1];
    *minY = range[2];
    *maxY = range[3];
    return true;
}

#ifdef PROTO
Bool SetXYControlValue(ObjPtr obj, real xvalue, real yvalue)
#else
Bool SetXYControlValue(obj, xvalue, yvalue)
ObjPtr obj;
real xvalue, yvalue;
#endif
/* sets values of 2D control. Non-method version */
{
    ObjPtr valuesArray, rangeArray;
    real value[2], range[4];
    real oldv0, oldv1;

    valuesArray = GetFixedArrayVar("SetXYControlValue", obj, VALUE, 1, 2L);
    rangeArray = GetFixedArrayVar("SetXYControlValue", obj, RANGE, 1, 4L);
    if (!valuesArray || !rangeArray)
    {
	ReportError("SetXYControlValue", "error in values or range array");
	return false;
    }
    Array2CArray(value, valuesArray);
    Array2CArray(range, rangeArray);

    oldv0 = value[0];
    oldv1 = value[1];
    if (xvalue < range[0])
	value[0] = range[0];
    else if (xvalue > range[1])
	value [0] = range[1];
    else
	value[0] = xvalue;

    if (yvalue < range[2])
	value[1] = range[2];
    else if (yvalue > range[3])
	value[1] = range[3];
    else
	value[1] = yvalue;

    valuesArray = NewRealArray(1, 2L);
    CArray2Array(valuesArray, value);
    SetVar(obj, VALUE, valuesArray);
    if (oldv0 != value[0] || oldv1 != value[1])
    {
	ImInvalid(obj);
	ChangedValue(obj);
    }
    if (logging) LogControl(obj);
    return true;
}

Bool GetXYControlValue(obj, xvalue, yvalue)
ObjPtr obj;
real *xvalue, *yvalue;
{
    ObjPtr valuesArray;
    real value[2];

    valuesArray = GetFixedArrayVar("GetXYControlValue", obj, VALUE, 1, 2L);
    if (!valuesArray)
    {
	return false;
    }
    Array2CArray(value, valuesArray);

    *xvalue = value[0];
    *yvalue = value[1];
    return true;
}

ObjPtr GetXYControlObjValue(obj)
ObjPtr obj;
{
    return GetVar(obj, VALUE);
}

void	InitXYControls()
{
    XYControlClass = NewObject(controlClass, 0);
    AddToReferenceList((ThingPtr) XYControlClass);
    SetMethod(XYControlClass, DRAW, DrawXYControl);
    SetMethod(XYControlClass, PRESS, PressXYControl);
/* These are incorrect until FuncTyp gets redefined
    SetMethod(XYControlClass, SETVAL, SetXYControlObjValue);
*/
    SetMethod(XYControlClass, GETVAL, GetXYControlObjValue);
    SetVar(XYControlClass, TYPESTRING, NewString("control"));
    SetVar(XYControlClass, HELPSTRING, NewString(
"This is a two-valued slider. By clicking and dragging the \"thumb\" in \
the control, you can change one or both of the values. You can change only \
one of the values by holding the Shift key down while you click and drag \
the \"thumb\". If you double-click it, the values will snap to the closest \
one of maximum, minimum or middle value of the range for each value."));
}

void KillXYControls()
{
    RemoveFromReferenceList(XYControlClass);
}
