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

#include <z_string.hpp>
#include <z_cursor.hpp>
#include <z_scrll2.hpp>
#include "m_win.hpp"
#include "m_text.hpp"

#include <limits.h>

// ----- MtfText widget definition ------------------------------------------

XmOffsetPtr MtfText::offsets;

XtActionsRec MtfText::action[] =
{
	{ "ZafCallDefault", (XtActionProc)call_default },
	{ "ZafDragDrop",	(XtActionProc)drag_drop },
	{ "ZafEnter",		(XtActionProc)enter },
	{ "ZafFocusIn",		(XtActionProc)focus_in },
	{ "ZafHelp", 		(XtActionProc)help },
	{ "ZafLeave",		(XtActionProc)leave }
};

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

MtfTextClassRec MtfText::classRecord =
{
	// --- CoreClassPart ---
	{
		(WidgetClass)&xmTextClassRec,	// superclass
		"ZafText", 						// class_name
		sizeof(MtfTextPart), 			// widget_size (data part)
		MtfText::classInitialize,		// class_initialize
		(XtWidgetClassProc)0,			// class_part_initialize
		false,							// class inited
		(XtInitProc)0,					// initialize
		(XtArgsProc)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
		XtInheritResize,				// 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
	},
	// --- XmPrimitiveClassPart ---
	{
		(XtWidgetProc)highlight,		// border_highlight
		(XtWidgetProc)unhighlight,		// border_unhighlight
		XtInheritTranslations,			// translations
		0,								// arm_and_activate
		0,								// syn_resources
		0,								// num_syn_resources
		0								// extension
	},
	// --- XmScrolledTextClassPart ---
	{
		0								// extension
	},
	// --- MtfTextClassPart ---
	{
		0									// extension
	}
};

void MtfText::changed(MtfText *, ZafText *object, XmTextVerifyCallbackStruct *)
{
	// Update the status flags.
	object->changed = true;
	object->unanswered = false;
}

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

void MtfText::drag_drop(MtfText *mtfObject, XEvent *xEvent, String *param, Cardinal *nparams)
{
	ZafText *text = GetZincObject(mtfObject);
	if (!text->CopyDraggable() && !text->MoveDraggable() && !text->LinkDraggable())
	{
		XtCallActionProc((Widget)mtfObject, "copy-primary", xEvent, param, *nparams);
		ZafIChar *currentValue;
		// XmNvaluesWcs (Unicode) does not work on Sun OS 4 so use XmNvalue.
#if defined(ZAF_UNICODE) && !defined(_SUNOS4)
		XtVaSetValues((Widget)mtfObject, XmNvalueWcs, &currentValue, NULL);
#else
		XtVaGetValues((Widget)mtfObject, XmNvalue, &currentValue, NULL);
#endif
		text->StringData()->SetText(currentValue);
		XtFree((char *)currentValue);
	}
	else
	{
		// Begin the drag/drop operation.
		ZafEventStruct event(S_BEGIN_DRAG, xEvent);
		GetZincObject(mtfObject)->Event(event);
	}
}

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

void MtfText::expose(MtfText *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);
}

void MtfText::focus_in(MtfText *mtfObject, XEvent *xEvent, String *params, Cardinal *nparams)
{
	// Chain to the base class.
	XtCallActionProc((Widget)mtfObject, "focusIn", xEvent, params, *nparams);
	if (!mtfObject->primitive.have_traversal)
		return; // Object is not really getting the keyboard focus.

	// Reflect changes to Zinc.
	ZafText *zafObject = GetZincObject(mtfObject);
	zafObject->NotifyFocus(zafObject, true);
}

void MtfText::highlight(MtfText *mtfObject)
{
	// Draw the button highlight.
	ZafText *zafObject = GetZincObject(mtfObject);
	ZafRegionStruct drawRegion = zafObject->BeginDraw();
	zafObject->DrawFocus(drawRegion, S_CURRENT);
	zafObject->EndDraw();
}

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

void MtfText::unhighlight(MtfText *mtfObject)
{
	// Draw the button highlight.
	ZafText *zafObject = GetZincObject(mtfObject);
	ZafRegionStruct drawRegion = zafObject->BeginDraw();
	zafObject->DrawFocus(drawRegion, S_NON_CURRENT);
	zafObject->EndDraw();
}

// ----- ZafText ------------------------------------------------------------

ZafWindowObject *ZafText::Add(ZafWindowObject *object, ZafWindowObject *position)
{
	// Addition of any object's requires the total re-creation of the widget.
	if (screenID)
	{
		Event(S_DESTROY);
		ZafWindow::Add(object, position);
		Event(S_CREATE);
		return (object);
	}

	// Defer to the base class.
	return (ZafWindow::Add(object, position));
}

int ZafText::CursorOffset(void) const
{
	// Reset the string position.
	XmTextPosition textPosition = 0;
	if (screenID)
		XtVaGetValues(screenID, XmNcursorPosition, &textPosition, 0);
	return ((int)textPosition);
}

ZafPositionStruct ZafText::CursorPosition(void) const
{
	// Get the cursor offset.
	ZafPositionStruct position;
	position.column = 0;
	position.line = 0;
	if (screenID)
	{
		Position x = 0;
		Position y = 0;
		XmTextPosition textOffset = XmTextGetCursorPosition(screenID);
		XmTextPosToXY(screenID, textOffset, &x, &y);
		position.column = x;
		position.line = y;
	}

	// Return the cursor position.
	return (position);
}

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

	// Draw the text.
	if (SystemObject())
	{
		// Defer drawing to the motif widget.
		(*xmTextClassRec.core_class.expose)
			(screenID, event.osEvent.type ? (XEvent *)&event.osEvent : ZAF_NULLP(XEvent), (_XRegion *)event.data);
	}
	else
	{
		// Compute the actual draw region.
		ZafPaletteState state = PaletteState();
		ZafRegionStruct drawRegion = BeginDraw();

		// Draw the focus.
		DrawFocus(drawRegion, Focus() ? S_CURRENT : S_NON_CURRENT);

		// Draw the border.
		if (Bordered())
			DrawBorder(drawRegion, ccode);

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

		// Draw the text.
		if (Bordered())
			drawRegion -= ZAF_MARGIN_SIZE;
		display->SetClipRegion(drawRegion);
		display->SetPalette(LogicalPalette(ZAF_PM_TEXT, state));
		display->Text(drawRegion, stringData->Text(), -1, hzJustify, ZAF_VT_TOP);

		// Restore the draw operation.
		EndDraw();
	}

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

ZafEventType ZafText::Event(const ZafEventStruct &event)
{
	// Check for zinc events.
	ZafEventType ccode = event.type;
	switch (ccode)
	{
	// ----- Create messages -----
	case S_INITIALIZE:
		// Check for an unanswered field.
		if (Unanswered())
			*stringData = ZafLanguageManager::blankString;
		ccode = ZafWindow::Event(event);
		break;

	// ----- Action messages -----
	case S_CURRENT:
		// Check for auto focus.
		ccode = ZafWindow::Event(event);
		if (screenID && SystemObject())
		{
			ZafIChar *currentValue;
#if defined(ZAF_UNICODE)
			XtVaGetValues((Widget)screenID, XmNvalueWcs, &currentValue, NULL);
#else
			XtVaGetValues((Widget)screenID, XmNvalue, &currentValue, NULL);
#endif
			StringData()->SetText(currentValue);
			XtFree((char *)currentValue);
			if (AutoClear())
				XmTextSetSelection(screenID, 0, XmTextGetLastPosition(screenID), 0);
		}
		break;

	case S_NON_CURRENT:
		ccode = ZafWindow::Event(event);
		break;

	case N_NON_CURRENT:
		{
		// Syncronize text buffers.
		ZafIChar *oldText = strdup(stringData->Text());
		OSGetText();

		// Set changed only if the text is really different.
		if (strcmp(oldText, stringData->Text()) != 0)
			SetChanged(true);
		delete []oldText;

		// Validate the field.
		ccode = ZafWindow::Event(event);

		// Update the field status.
		unanswered = (stringData->Length() == 0) ? true : false;
		invalid = (ccode == ZAF_ERROR_LEAVE_INVALID) ? true : false;
		}
		break;

	case S_CUT:
		XmTextCut(screenID, CurrentTime);
		break;

	case S_COPY:
		XmTextCopy(screenID, CurrentTime);
		break;

	case S_PASTE:
		XmTextPaste(screenID);
		break;

	case S_COPY_DATA:
		{
		ZafWindowObject *object = event.windowObject;
		ZafText *text = DynamicPtrCast(object, ZafText);
		if (text)
			stringData->SetText(*text->StringData());
		}
		break;

	case S_SET_DATA:
		{
		ZafWindowObject *object = event.windowObject;
		if (object)
		{
			ZafText *text = DynamicPtrCast(object, ZafText);
			if (text)
				SetStringData(text->StringData());
		}
		else
			SetStringData(new ZafStringData(*stringData));
		}
		break;

	case S_UPDATE_OBJECT:
		OSSetText();
		break;

	case S_UPDATE_DATA:
		OSGetText();
		break;

	case S_HOT_KEY:
		ccode = S_ERROR;
		break;

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

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

bool ZafText::SetAutoClear(bool setAutoClear)
{
	// Make sure the attribute has changed.
	if (autoClear != setAutoClear)
		autoClear = setAutoClear;

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

ZafError ZafText::SetCursorOffset(int offset)
{
	// Make sure the request is valid.
	if (!screenID)
		return (ZAF_ERROR_INVALID);

	// Reset the string position.
	XmTextPosition textPosition = (XmTextPosition)offset;
	XtVaSetValues(screenID, XmNcursorPosition, textPosition, 0);
	return (ZAF_ERROR_NONE);
}

ZafError ZafText::SetCursorPosition(ZafPositionStruct position)
{
	// Make sure the request is valid.
	if (!screenID)
		return (ZAF_ERROR_INVALID);

	// Reset the text cursor.
	XmTextPosition textOffset = XmTextXYToPos(screenID, position.column, position.line);
	XmTextShowPosition(screenID, textOffset);
	XmTextSetCursorPosition(screenID, textOffset);
	return (ZAF_ERROR_NONE);
}

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

bool ZafText::SetWordWrap(bool setWordWrap)
{
	// Make sure the attribute has changed.
	if (wordWrap != setWordWrap)
	{
		wordWrap = setWordWrap;
		if (screenID && SystemObject())
			XtVaSetValues(screenID, XmNwordWrap, wordWrap, 0);
	}

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

ZafWindowObject *ZafText::Subtract(ZafWindowObject *object)
{
	// Run-time subtraction of sub-object's requires widget re-creation.
	if (screenID)
	{
		Event(S_DESTROY);
		ZafWindowObject *nextObject = ZafWindow::Subtract(object);
		Event(S_CREATE);
		return (nextObject);
	}

	// Defer to the base class.
	return (ZafWindow::Subtract(object));
}

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

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

ZafError ZafText::OSGetText(void)
{
	// Update the internal text.
	if (screenID && SystemObject() && Changed())
	{
		stringData->SetUpdate(this, ZAF_UPDATE_NONE);
#if defined(ZAF_UNICODE)
		wchar_t *osText = XmTextGetStringWcs(screenID);
		stringData->SetOSWText(osText);
		XtFree((char *)osText);
#else
		char *osText = XmTextGetString(screenID);
		stringData->SetOSText(osText);
		XtFree(osText);
#endif
		stringData->SetUpdate(this, ZAF_UPDATE_ALL);
	}
	return (ZAF_ERROR_NONE);
}

void ZafText::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType type, ZafPaletteState state)
{
	// Check for a system object.
	if (!screenID)
		return;
	else if (!SystemObject())
	{
		parent->OSMapPalette(palette, type, state);
		return;
	}

	// Defer to the base class.
	XGCValues values;
	XtGCMask mask = GCForeground | GCBackground | GCFillStyle;
	MtfText *mtfObject = MtfText::GetMotifWidget(screenID);
	if (mtfObject->text.output)
	{
		if (type == ZAF_PM_TEXT || type == ZAF_PM_HOT_KEY)
		{
		   values.foreground = mtfObject->primitive.foreground;
	    	values.background = mtfObject->core.background_pixel;
			if (state & ZAF_PM_DISABLED)
			{
				mask |= GCTile;
				values.fill_style = FillTiled;
				values.tile = XmGetPixmapByDepth(XtScreen(screenID), "50_foreground",
					mtfObject->primitive.foreground, mtfObject->core.background_pixel, mtfObject->core.depth);
			}
			else
				values.fill_style = FillSolid;
			palette.osPalette = mtfObject->text.output->data->gc;
			XChangeGC(XtDisplay(screenID), palette.osPalette, mask, &values);
		}
		else if (type == ZAF_PM_BACKGROUND)
		{
		   values.foreground = mtfObject->core.background_pixel;
	    	values.background = mtfObject->primitive.foreground;
			values.fill_style = FillSolid;
			palette.osPalette = mtfObject->text.output->data->gc;
			XChangeGC(XtDisplay(screenID), palette.osPalette, mask, &values);
		}
		else
			ZafWindowObject::OSMapPalette(palette, type, state);
	}
	else
		ZafWindowObject::OSMapPalette(palette, type, state);
}

void ZafText::OSRegisterObject(void)
{
	// Level 1 - Create the frame area.
	char *name = zafCodeSet->ConvertToOSString(stringID, 0, false);
	screenID = parent->OSScreenID(ZAF_CLIENTID);
	screenID = XtVaCreateWidget(name,
		xmScrolledWindowWidgetClass, screenID,
		// user information
		XmNuserData, this,
		// miscellaneous
		XmNscrollingPolicy, XmAPPLICATION_DEFINED,
		XmNvisualPolicy, XmVARIABLE,
		XmNscrollBarDisplayPolicy, XmSTATIC,
		XmNshadowThickness, 0,	
		0);

	// Level 2 - Create the widget.
	if (!MtfText::classRecord.primitiveClassPart.extension)
		MtfText::classRecord.primitiveClassPart.extension = xmTextClassRec.primitive_class.extension;
	ZafWindowObject *vScroll = VerticalScrollBar();
	ZafWindowObject *hScroll = HorizontalScrollBar();
	screenID = XtVaCreateManagedWidget(name,
		(WidgetClass)&MtfText::classRecord, screenID,
		// user information
		XmNuserData, this,
		// miscellaneous
		XmNsensitive, Disabled(false) ? false : true,
		XmNtraversalOn, Noncurrent() ? false : true,
		XmNshadowThickness, (vScroll || hScroll || Bordered()) ? ZAF_SHADOW_SIZE : 0,
		XmNeditable, (ViewOnly() || Disabled(false) || 
		 (Noncurrent() && !Previous() && !Next())) ? false : true,
		XmNcursorPositionVisible, (ViewOnly() || Disabled(false)) ? false : true,
		XmNblinkRate, ZafCursor::BlinkRate(),
		XmNeditMode, XmMULTI_LINE_EDIT,
		XmNwordWrap, wordWrap,
		XmNscrollVertical, vScroll ? true : false,
		XmNscrollHorizontal, hScroll ? true : false,
		XmNhighlightThickness, 0,
		XmNborderWidth, 0,
		XmNmarginHeight, ZAF_TEXT_MARGIN_SIZE,
		0);
	OSSetText();

	// Hook in scroll-bar information.
	XmScrollBarWidget vWidget = ((XmScrolledWindowRec *)XtParent(screenID))->swindow.vScrollBar;
	if (vScroll && vWidget)
	{
		vScroll->screenID = (Widget)vWidget;
		XtVaSetValues(vScroll->screenID, XmNuserData, vScroll->screenID, 
			XmNshadowThickness, ZAF_SHADOW_SIZE, 0);
	}
	XmScrollBarWidget hWidget = ((XmScrolledWindowRec *)XtParent(screenID))->swindow.hScrollBar;
	if (hScroll && hWidget)
	{
		hScroll->screenID = (Widget)hWidget;
		XtVaSetValues(hScroll->screenID, XmNuserData, hScroll->screenID, 
			XmNshadowThickness, ZAF_SHADOW_SIZE, 0);
	}

	// If there is a scrollbar and no Border the border must be turned off 
	// after the scroll bars have their borders set.
	if ((vScroll || hScroll) && !Bordered())
		XtVaSetValues(screenID, 
			XmNshadowThickness, Bordered() ? ZAF_SHADOW_SIZE : 0, 0);
	
	// Override translations.
	XtTranslations translationTable = XtParseTranslationTable(MtfText::translations);
	XtOverrideTranslations(screenID, translationTable);

	// special callbacks
	XtAddCallback(screenID, XmNactivateCallback, 
		(XtCallbackProc)MtfText::activate, (XtPointer)this);
	XtAddCallback(screenID, XmNvalueChangedCallback, 
		(XtCallbackProc)MtfText::changed, (XtPointer)this);

	// Seed the os colors.
//	LogicalPalette(ZAF_PM_BACKGROUND, ZAF_PM_ANY_STATE);
//	LogicalPalette(ZAF_PM_TEXT, ZAF_PM_ANY_STATE);

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

	// Add drag/drop capabilities.
	ZafDropSiteRegister(this);
}

OSWindowID ZafText::OSScreenID(ZafScreenIDType type) const
{
	// Return the client widget.
	if (type == ZAF_FRAMEID && screenID)
		return (XtParent(screenID));
	return (ZafWindowObject::OSScreenID(type));
}

ZafError ZafText::OSSetText(void)
{
	// Disable notification.
	ZafUpdateType update = stringData->Update(this);
	stringData->SetUpdate(this, ZAF_UPDATE_NONE);

	// Restore notification.
	stringData->SetUpdate(this, update);

	// Set the os text.
	if (screenID && SystemObject())
	{
		// Set the values.
		int maxLength = stringData->MaxLength();
		// If maxLength is -1 set it to the largest int. (Default value on Motif)
		if (maxLength < 0)
			maxLength = INT_MAX;
#		if defined(ZAF_UNICODE)
		wchar_t *osText = stringData->DynamicOSWText();
		XtVaSetValues(screenID,
			XmNmaxLength, maxLength,
			XmNvalueWcs, osText,
			0);
		delete []osText;
#		else
		char *osText = stringData->DynamicOSText();
		XtVaSetValues(screenID,
			XmNmaxLength, maxLength,
			XmNvalue, osText,
			0);
		delete []osText;
#		endif
	}
	else if (screenID && AutomaticUpdate())
		RedisplayData();
	return (ZAF_ERROR_NONE);
}

void ZafText::OSSize(void)
{
	// Default to the base class.
	ZafWindowObject::OSSize();
}

ZafError ZafText::OSUpdatePalettes(ZafPaletteType zafTypes, ZafPaletteType osTypes)
{
	// Default to the base class.
	return (ZafWindow::OSUpdatePalettes(zafTypes, osTypes));
}

