//	Zinc Application Framework - M_SPIN.CPP
//	COPYRIGHT (C) 1990-1999.  All Rights Reserved.
//	Zinc Software Incorporated.  Pleasant Grove, Utah  USA

#include <z_str1.hpp>
#include "m_spin.hpp"

// The default size of a scrollbar is hard-coded in Motif 1.2 to be 11.  For
// more information on the default size see the static Initialize() function
// in ScrollBar.c.  In addition, the initial and repeat delay times were
// taken from ScrollBar.c as the default values.
const int ARROW_SIZE = 11;

// ----- ZafSpinControl widget definition -----------------------------------

XmOffsetPtr MtfSpinControl::offsets;

XtActionsRec MtfSpinControl::action[] =
{
	{ "ManagerGadgetArm",			(XtActionProc)arm },
	{ "ManagerGadgetActivate",		(XtActionProc)activate },
	{ "ManagerGadgetMultiArm",		(XtActionProc)multi_arm },
	{ "ManagerGadgetMultiActivate",	(XtActionProc)multi_activate },
	{ "ZafDragDrop",				(XtActionProc)drag_drop },
	{ "ZafEnter",					(XtActionProc)enter },
	{ "ZafHelp", 					(XtActionProc)help },
	{ "ZafLeave",					(XtActionProc)leave }
};

char MtfSpinControl::translations[] =
	"<Btn2Down>:	ZafDragDrop()\n\
	 <Enter>:		ZafEnter()\n\
	 <Key>F1:		ZafHelp()\n\
	 <Key>osfHelp:	ZafHelp()\n\
	 <Leave>:		ZafLeave()";

MtfSpinControlClassRec MtfSpinControl::classRecord =
{
	// --- CoreClassPart ---
	{
		(WidgetClass)&xmBulletinBoardClassRec,	// superclass
		"ZafSpinControl",						// class_name
		sizeof(MtfSpinControlPart), 			// widget_size (data part)
		MtfSpinControl::classInitialize,		// class_initialize
		0,										// class_part_initialize
		false,									// class inited
		0,										// initialize
		0,										// initialize_hook
		XtInheritRealize,						// realize
		action,									// actions
		XtNumber(action),						// num_actions
		0, 										// resources
		0, 										// num_resources
		NULLQUARK,								// xrm
		true,									// compress_motion
		XtExposeCompressMultiple,				// compress_exposure
		true,									// compress_enterleave
		false,									// visible_interest
		0, 										// destroy
		(XtWidgetProc)resize,					// resize
		(XtExposeProc)expose,					// expose
		0,										// set_values
		0, 										// set_values_hook
		XtInheritSetValuesAlmost,				// set_values_almost
		0, 										// get_values_hook
		XtInheritAcceptFocus,					// accept_focus
		XtVersionDontCheck,					// version
		0, 										// callback_private
		XtInheritTranslations,					// tm_table
		XtInheritQueryGeometry, 				// query_geometry
		XtInheritDisplayAccelerator,			// display_accelerator
		0 										// extension
	},
	// --- CompositeClassPart ---
	{
		gmgr,									// geometry_manager
		XtInheritChangeManaged,					// change_managed
		XtInheritInsertChild,					// insert_child
		XtInheritDeleteChild,					// delete_child
		0										// extension
	},
	// --- ConstraintClassPart ---
	{
		0,										// resources
		0,										// num_resources
		0,										// constraint_size
		0,										// initialize
		0,										// destroy
		0,										// set_values
		0										// extension
	},
	// --- XmManagerClassPart ---
	{
		XtInheritTranslations,					// translations
		0,										// syn_resources
		0,										// num_syn_resources
		0,										// syn_constraint_resources
		0,										// num_syn_constraint_resources
		XmInheritParentProcess,					// parent_process
		0										// extension
	},
	// --- XmBulletinBoardClassPart ---
	{
		false,									// always_install_accelerators
		0,										// geo_matrix_create
		XmInheritFocusMovedProc,				// focus_moved_proc
		0										// extension
	},
	// --- MtfSpinControlClassPart ---
	{
		0									// extension
	}
};

void MtfSpinControl::activate(MtfSpinControl *mtfObject, XEvent *xEvent, String *, Cardinal *)
{
	// This action occurs on the first "up-click" portion of a mouse click.
	ZafSpinControl *zafObject = GetZincObject(mtfObject);
	ZafEventStruct event(E_MOTIF, xEvent);
	zafObject->ConvertToZafEvent(event); // match up the coordinates.
	event.type = L_END_SELECT;
	zafObject->Event(event);
}

void MtfSpinControl::arm(MtfSpinControl *mtfObject, XEvent *xEvent, String *, Cardinal *)
{
	// Perform the motif arm.
	XmProcessTraversal((Widget)mtfObject, XmTRAVERSE_CURRENT);

	// This action occurs on the first "down-click" portion of a mouse click.
	ZafSpinControl *zafObject = GetZincObject(mtfObject);
	ZafEventStruct event(E_MOTIF, xEvent);
	zafObject->ConvertToZafEvent(event); // match up the coordinates.
	event.type = L_BEGIN_SELECT;
	zafObject->Event(event);
}

void MtfSpinControl::classInitialize()
{
	XmResolvePartOffsets((WidgetClass) &(MtfSpinControl::classRecord),
	 &(MtfSpinControl::offsets));
}

void MtfSpinControl::drag_drop(MtfSpinControl *mtfObject, XEvent *xEvent, String *, Cardinal *)
{
	// Begin the drag/drop operation.
	ZafEventStruct event(S_BEGIN_DRAG, xEvent);
	GetZincObject(mtfObject)->Event(event);
}

void MtfSpinControl::enter(MtfSpinControl *mtfObject, XEvent *xEvent, String *param, Cardinal *nparams)
{
	// Perform the motif enter and match with zinc.
	XtCallActionProc((Widget)mtfObject, "ManagerEnter", xEvent, param, *nparams);
	ZafEventStruct event(N_MOUSE_ENTER, xEvent);
	GetZincObject(mtfObject)->Event(event);
}

void MtfSpinControl::expose(MtfSpinControl *mtfObject, XEvent *xEvent, Region xRegion)
{
	// Draw the object.
	ZafEventStruct event(E_MOTIF, xEvent);
	XRectangle xRect;
	XClipBox(xRegion, &xRect);
  	event.region.Assign(xRect.x, xRect.y, xRect.width, xRect.height, ZAF_PIXEL);
	GetZincObject(mtfObject)->Draw(event, E_MOTIF);
}

XtGeometryResult MtfSpinControl::gmgr(Widget , XtWidgetGeometry * , XtWidgetGeometry * )
{
	// ZAF automatically handles geometry of children.
    return (XtGeometryNo);
}

void MtfSpinControl::leave(MtfSpinControl *mtfObject, XEvent *xEvent, String *param, Cardinal *nparams)
{
	// Perform the motif enter and match with zinc.
	XtCallActionProc((Widget)mtfObject, "ManagerLeave", xEvent, param, *nparams);
	ZafEventStruct event(N_MOUSE_LEAVE, xEvent);
	GetZincObject(mtfObject)->Event(event);
}

void MtfSpinControl::multi_activate(MtfSpinControl *mtfObject, XEvent *xEvent, String *, Cardinal *)
{
	// This action occurs on the "up-click" portion of a mouse double-click.
	ZafSpinControl *zafObject = GetZincObject(mtfObject);
	ZafEventStruct event(E_MOTIF, xEvent);
	zafObject->ConvertToZafEvent(event); // match up the coordinates.
	event.type = L_END_SELECT;
	zafObject->Event(event);
}

void MtfSpinControl::multi_arm(MtfSpinControl *mtfObject, XEvent *xEvent, String *, Cardinal *)
{
	// This action occurs on the "down-click" portion of a mouse double-click.
	ZafSpinControl *zafObject = GetZincObject(mtfObject);
	ZafEventStruct event(E_MOTIF, xEvent);
	zafObject->ConvertToZafEvent(event); // match up the coordinates.
	event.type = L_BEGIN_SELECT;
	zafObject->Event(event);
}

void MtfSpinControl::repeat(MtfSpinControl *mtfObject, XtIntervalId *)
{
	ZafSpinControl *zafObject = GetZincObject(mtfObject);
	IntervalID((Widget)mtfObject) = 0;
//	mtfObject->intervalID = 0;
	if (zafObject->Focus())
	{
//		ZafEventStruct event(L_CONTINUE_SELECT, 0, mtfObject->lastMousePosition);
		ZafEventStruct event(L_CONTINUE_SELECT, 0, LastMousePosition((Widget)mtfObject));
		zafObject->Event(event);
	}
}

void MtfSpinControl::resize(MtfSpinControl *mtfObject)
{
	GetZincObject(mtfObject)->Event(N_SIZE);
}

// ----- ZafSpinControl -----------------------------------------------------

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafSpinControl::Draw(const ZafEventStruct &event, ZafEventType ccode)
{
	// Check for draw errors.
	if (!screenID)
		return (S_ERROR);

	// Compute the actual draw region.
	ZafPaletteState state = PaletteState();
	ZafRegionStruct drawRegion = BeginDraw();

	// Clear the background.
	ZafPaletteStruct background = LogicalPalette(ZAF_PM_BACKGROUND, state);
	display->SetPalette(background);
	display->Rectangle(drawRegion, 0, true);

	// Draw the combo-box shadow and scroll-arrows.
	ZafRegionStruct arrowRegion;
	arrowRegion.left = drawRegion.left + upArrow.left;
	arrowRegion.top = drawRegion.top + upArrow.top;
	arrowRegion.right = drawRegion.left + downArrow.right;
	arrowRegion.bottom = drawRegion.top + downArrow.bottom;
	arrowRegion += ZAF_SHADOW_SIZE;
	DrawShadow(arrowRegion, -ZAF_SHADOW_SIZE, ccode);
	display->SetPalette(background);
	display->Rectangle(arrowRegion, 0, true);
	DrawArrows(false, false);

	// Draw the non-system children.
	for (ZafWindowObject *object = First(); object; object = object->Next())
		if (!object->SystemObject())
			object->Draw(event, ccode);

	// Return the control code.
	EndDraw();
	return (ccode);
}

void ZafSpinControl::DrawArrows(bool upDepressed, bool downDepressed)
{
	// Draw the scroll arrows.
	ZafPaletteState state = PaletteState();
	ZafRegionStruct drawRegion = BeginDraw();

	ZafPaletteStruct topShadow = LogicalPalette(ZAF_PM_LIGHT_SHADOW, state);
	ZafPaletteStruct bottomShadow = LogicalPalette(ZAF_PM_DARK_SHADOW, state);
	ZafPaletteStruct background = LogicalPalette(ZAF_PM_BACKGROUND, state);

	ZafRegionStruct arrowRegion = upArrow;
	arrowRegion.HzShift(drawRegion.left);
	arrowRegion.VtShift(drawRegion.top);
	_XmDrawArrow(XtDisplay(screenID), XtWindow(screenID),
		upDepressed ? bottomShadow.osPalette : topShadow.osPalette,
		upDepressed ? topShadow.osPalette : bottomShadow.osPalette,
		background.osPalette, arrowRegion.left - 1, arrowRegion.top - 1,
		upArrow.Width() + 2, upArrow.Height() + 2, ZAF_SHADOW_SIZE,
		XmARROW_UP);

	arrowRegion = downArrow;
	arrowRegion.HzShift(drawRegion.left);
	arrowRegion.VtShift(drawRegion.top);
	_XmDrawArrow(XtDisplay(screenID), XtWindow(screenID),
		downDepressed ? bottomShadow.osPalette : topShadow.osPalette,
		downDepressed ? topShadow.osPalette : bottomShadow.osPalette,
		background.osPalette, arrowRegion.left - 1, arrowRegion.top - 1,
		downArrow.Width() + 2, downArrow.Height() + 2, ZAF_SHADOW_SIZE,
		XmARROW_DOWN);

	EndDraw();
}
#endif

ZafEventType ZafSpinControl::Event(const ZafEventStruct &event)
{
	// Check for Motif key events.
	ZafEventType ccode = event.type;
	if (ccode == E_MOTIF && event.InputType() == E_KEY)
		ccode = LogicalEvent(event);

	// Check for zinc events.
	ZafWindowObject *object;
	switch (ccode)
	{
	// ----- Create messages -----
	case S_REGISTER_OBJECT:
	{
		// Reset the system-object settings.
		ccode = ZafWindowObject::Event(event);

		// Get the font used by the spin control and set it on the spin object.
		XmFontList font = 0;
 		static XtResource resource = { "fontList", "FontList", "FontList", sizeof(XmFontList), 0, "Immediate", (XtPointer)0 };
		XtGetApplicationResources(screenID, &font, &resource, 1, 0, 0);

		// Get any other resources that may have been set through an Xdefaults file.
		Pixel background;
		Pixel foreground;
		Pixel lightShadow;
		Pixel darkShadow;
		XtVaGetValues(screenID, XmNbackground, &background, 
		 XmNforeground, &foreground, XmNtopShadowColor, &lightShadow,
		 XmNbottomShadowColor, &darkShadow, 0);

		// Reflect attributes to the spin items.
		for (object = First(); object; object = object->Next())
		{
			object->screenID = 0;
			object->SetAcceptDrop(acceptDrop);
			object->SetBordered(bordered);
			object->SetChanged(changed);
			object->SetCopyDraggable(copyDraggable);
			object->SetDisabled(disabled);
			object->SetHelpContext(helpContext);
			object->SetHelpObjectTip(helpObjectTip);
			object->SetLinkDraggable(linkDraggable);
			object->SetMoveDraggable(moveDraggable);
			object->SetNoncurrent(noncurrent);
			object->SetQuickTip(quickTip);
			object->SetSystemObject((EditMode() || !SystemObject()) ? false : true);
			object->Event(event);

			// Set the resources on the object.
			XtVaSetValues(object->screenID, XmNbackground, background,
		 	 XmNforeground, foreground, XmNtopShadowColor, lightShadow,
		 	 XmNbottomShadowColor, darkShadow, XmNfontList, font, 0);
		}
	}
		break;

	case S_DEINITIALIZE:
		// Restore the system-object settings.
		ccode = ZafWindow::Event(event);
		for (object = First(); object; object = object->Next())
			object->SetSystemObject(true);
		break;

	// ----- Position & Size messages -----
	case N_SIZE:
		// Determine the size of the arrows.
		// up
		upArrow.coordinateType = ZAF_PIXEL;
		upArrow.right = zafRegion.right - zafRegion.left - 2 * ZAF_SHADOW_SIZE;
		upArrow.top = 2 * ZAF_SHADOW_SIZE;
		upArrow.left = upArrow.right - ARROW_SIZE + 1;
		upArrow.bottom = upArrow.top + ARROW_SIZE - 1;
		// down
		downArrow.coordinateType = ZAF_PIXEL;
		downArrow.left = upArrow.left;
		downArrow.right = upArrow.right;
		downArrow.bottom = zafRegion.bottom - zafRegion.top - 2 * ZAF_SHADOW_SIZE;
		downArrow.top = downArrow.bottom - ARROW_SIZE + 1;

		// Modify the size for the field objects.
		for (object = First(); object; object = object->Next())
		{
			object->zafRegion.left = object->zafRegion.top = 0;
			object->zafRegion.right = upArrow.left - ZAF_SHADOW_SIZE;
			object->zafRegion.bottom = zafRegion.bottom - zafRegion.top;
			object->Event(N_SIZE);
			object->OSSize();
		}
		break;

	// ----- Movement messages -----
	case L_BEGIN_SELECT:
	case L_CONTINUE_SELECT:
		if (editMode)
			return (ZafWindowObject::Event(event));
		else
		{
			// Check for an overlap on the up or down arrows.
			MtfSpinControl *mtfObject = MtfSpinControl::GetMotifWidget(screenID);
			if (ViewOnly())
				break;
			else if (upArrow.Overlap(event.position))
			{
				Event(S_INCREMENT);
				DrawArrows(true, false);
			}
			else if (downArrow.Overlap(event.position))
			{
				Event(S_DECREMENT);
				DrawArrows(false, true);
			}
			else
			{
				// Temporarily stop the repeat process.
				if (IntervalID((Widget)mtfObject))
				 	XtRemoveTimeOut(IntervalID((Widget)mtfObject));
				IntervalID((Widget)mtfObject) = 0;
				break;
			}

			// Start the repeat process.
			LastMousePosition((Widget)mtfObject) = event.position;
			IntervalID((Widget)mtfObject) = XtAppAddTimeOut(((ZafScreenDisplay *)display)->xAppContext,
				(ccode == L_BEGIN_SELECT) ? InitialDelay() : RepeatDelay(),
				(XtTimerCallbackProc)mtfObject->repeat, (XtPointer)mtfObject);
		}
		break;

	case L_END_SELECT:
		// End the repeat process.
		if (editMode)
			return (ZafWindowObject::Event(event));
		else
		{
			MtfSpinControl *mtfObject = MtfSpinControl::GetMotifWidget(screenID);
			if (ViewOnly())
				break;
			else if (IntervalID((Widget)mtfObject))
			 	XtRemoveTimeOut(IntervalID((Widget)mtfObject));
			LastMousePosition((Widget)mtfObject).Assign(-1, -1, ZAF_PIXEL);
			IntervalID((Widget)mtfObject) = 0;
			DrawArrows(false, false);
		}
		break;

	case L_DOWN:
		if (ViewOnly())
			break;
		// Continue to S_DECREMENT.
	case S_DECREMENT:
		for (object = First(); object; object = object->Next())
		{
			ZafString *string = DynamicPtrCast(object, ZafString);
			if (string)
				string->Decrement(delta, WrappedData());

			// Call the user function.
			(this->*memberUserFunction)(event, L_SELECT);
		}
		break;

	case L_UP:
		if (ViewOnly())
			break;
		// Continue to S_INCREMENT.
	case S_INCREMENT:
		for (object = First(); object; object = object->Next())
		{
			ZafString *string = DynamicPtrCast(object, ZafString);
			if (string)
				string->Increment(delta, WrappedData());

			// Call the user function.
			(this->*memberUserFunction)(event, L_SELECT);
		}
		break;

	// ----- Default messages -----
	default:
		ccode = ZafWindow::Event(event);
		break;
	}

	// Return the control code.
	return (ccode);
}

bool ZafSpinControl::SetAcceptDrop(bool setAcceptDrop)
{
	// Make sure the attribute has changed.
	if (acceptDrop != setAcceptDrop)
	{
		// Activate/Deactivate the Motif drop capabilities.
		ZafWindowObject *object = First();
		if (object && object->screenID)
			acceptDrop = object->SetAcceptDrop(setAcceptDrop);
		else
			acceptDrop = setAcceptDrop;
	}

	// Return the current attribute.
	return (acceptDrop);
}

bool ZafSpinControl::SetDisabled(bool setDisabled)
{
	// Defer to the base class.
	return (ZafWindow::SetDisabled(setDisabled));
}

bool ZafSpinControl::SetVisible(bool setVisible)
{
	// Defer to the base class.
	return (ZafWindowObject::SetVisible(setVisible));
}

// ----- OS Specific Functions ----------------------------------------------

ZafEventType ZafSpinControl::OSEvent(const ZafEventStruct &)
{
	return (S_ERROR);
}

void ZafSpinControl::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType type, ZafPaletteState state)
{
	// Defer to the base class.
	ZafWindow::OSMapPalette(palette, type, state);
}

void ZafSpinControl::OSRegisterObject(void)
{
	// Create the widget.
	char *name = zafCodeSet->ConvertToOSString(stringID, 0, false);
	screenID = parent->OSScreenID(ZAF_CLIENTID);
	screenID = XtVaCreateWidget(name,
		(WidgetClass)&MtfSpinControl::classRecord, screenID,
		// user information
		XmNuserData, this,
		// miscellaneous
		XmNsensitive, Disabled(false) ? false : true,
		XmNtraversalOn, Noncurrent() ? false : true,
		XmNresizePolicy, XmRESIZE_NONE,
		XmNmarginWidth, 0,
		XmNmarginHeight, 0,
		0);

	// Override translations.
	XtTranslations translationTable = XtParseTranslationTable(MtfSpinControl::translations);
	XtOverrideTranslations(screenID, translationTable);

	// Set additional default values.
	MtfSpinControl *mtfObject = MtfSpinControl::GetMotifWidget(screenID);
	IntervalID((Widget)mtfObject) = 0;
//	mtfObject->intervalID = 0;
	LastMousePosition((Widget)mtfObject).Assign(-1, -1, ZAF_PIXEL);
//	mtfObject->lastMousePosition.Assign(-1, -1, ZAF_PIXEL);

	// Add edit mode overrides.
	ZafEditModeRegister(this);
}

OSWindowID ZafSpinControl::OSScreenID(ZafScreenIDType type) const
{
	// Defer to the base class.
	return (ZafWindowObject::OSScreenID(type));
}

void ZafSpinControl::OSSize(void)
{
	// Defer to the base class.
	ZafWindowObject::OSSize();
}

ZafError ZafSpinControl::OSUpdatePalettes(ZafPaletteType zafTypes, ZafPaletteType osTypes)
{
	// Make sure the widget can be updated.
	if (!screenID || !AutomaticUpdate() || !SystemObject())
		return (ZAF_ERROR_INVALID);

	// This class bypasses setting its own values so the background won't
	// clear to some funny color.  Also, support children are automatically
	// redrawn with the parent color, so only normal children need the
	// color updates.
	int width = screenID->core.width;
	int height = screenID->core.height;
	for (ZafWindowObject *object = First(); object; object = object->Next())
		object->OSUpdatePalettes(zafTypes, osTypes);
	if (width != screenID->core.width || height != screenID->core.height)
		XtConfigureWidget(screenID, screenID->core.x, screenID->core.y, width, height, 0);
	return (ZAF_ERROR_NONE);
}

