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

#include <w_app.hpp>
#include <z_ctype.hpp>
#include <z_keymap.hpp>
#include <z_str1.hpp>
#include <z_string.hpp>

//??? This is where I list my string bugs/issues so they don't get lost.
//??? Should vtJustify be supported?
void ZafUpdateOSMaxLength(OSWindowID screenID, int maxLength, bool isPaste);

// ----- ZafString ---------------------------------------------------------

int ZafString::CursorOffset(void) const
{
	// Get cursor offset from windows.
	if (screenID && OSDraw())
		return (HIWORD(SendMessage(screenID, EM_GETSEL, 0, 0L)));
	else
		return 0;
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafString::Draw(const ZafEventStruct &, ZafEventType ccode)
{
	// Begin drawing operation.
	ZafRegionStruct drawRegion = BeginDraw();

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

	// Set the clipping region.
	display->SetClipRegion(drawRegion);

	// Fill the background.
	DrawBackground(drawRegion, (ccode == S_REDISPLAY_DATA || ccode == L_SELECT) ?
		S_REDISPLAY : ccode);

	// Draw the text.
	if (ccode == S_REDISPLAY || ccode == S_REDISPLAY_REGION || ccode == S_REDISPLAY_DATA ||
		ccode == L_SELECT)
	{
		// Set the text palette.
		display->SetPalette(LogicalPalette(ZAF_PM_TEXT, PaletteState()));

		// Draw the text.
		ZafRegionStruct textRegion = drawRegion;
		if (bordered)
		{
			textRegion.top++;
			if (hzJustify == ZAF_HZ_LEFT)
				textRegion.left++;
			else if (hzJustify == ZAF_HZ_RIGHT)
				textRegion.right--;
		}
		display->Text(textRegion, stringData->Text(), -1, hzJustify, ZAF_VT_TOP);
	}

	// Focus rect is drawn only on strings in lists.
	if (ParentDrawFocus())
		DrawFocus(drawRegion, ccode);

	// End the drawing operation.
	EndDraw();

	return (ccode);
}
#endif

ZafEventType ZafString::Event(const ZafEventStruct &event)
{
	ZafEventType ccode = event.type;

	if (ccode == E_OSEVENT)
		return ZafString::OSEvent(event);

	// Process Zinc events.
	switch (ccode)
	{
	case S_CURRENT:
		ccode = ZafWindowObject::Event(event);
		if (autoClear && SystemObject() && OSDraw())
		{
#if defined(ZAF_WIN32)
			SendMessage(screenID, EM_SETSEL, (WPARAM)0, (LPARAM)0xFFFFFFFF);
	 		SendMessage(screenID, EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
#else
	  		SendMessage(screenID, EM_SETSEL, (WPARAM)0, (LPARAM)0x7FFF0000L);
#endif
		}
		break;

	case S_NON_CURRENT:
		OSGetText();
		ccode = ZafWindowObject::Event(event);
		break;

	case L_SELECT:
	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 = ZafWindowObject::Event(event);

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

	case S_CUT:
		SendMessage(screenID, WM_CUT, 0, 0);
		break;

	case S_COPY:
		SendMessage(screenID, WM_COPY, 0, 0);
		break;

	case S_PASTE:
		SendMessage(screenID, WM_PASTE, 0, 0);
		break;

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

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

	case S_UPDATE_OBJECT:
		OSSetText();
		break;

	case S_UPDATE_DATA:
		OSGetText();
		break;

	case S_VALID_CHECK:
		if (!Invalid())
			ccode = S_VALID;
		else if (AllowInvalid() && !newData)
			ccode = S_INVALID;
		else
			ccode = S_ERROR;
		break;

	case N_SIZE:
		if (!systemObject || !osDraw)
		{
			ZafEventStruct updateEvent(S_REDISPLAY_REGION);
			updateEvent.region.coordinateType = ZAF_PIXEL;
			updateEvent.region.right = oldRegion.Width() - 1;
			updateEvent.region.left = updateEvent.region.right - ZAF_BORDER_SIZE;
			updateEvent.region.top = 0;
			updateEvent.region.bottom = oldRegion.Height() - 1;
			ZafWindowObject::Event(updateEvent);
			updateEvent.region.left = 0;
			updateEvent.region.top = updateEvent.region.bottom - ZAF_BORDER_SIZE;
			ZafWindowObject::Event(updateEvent);

			updateEvent.region.right = zafRegion.Width() - 1;
			updateEvent.region.left = updateEvent.region.right - ZAF_BORDER_SIZE;
			updateEvent.region.top = 0;
			updateEvent.region.bottom = zafRegion.Height() - 1;
			ZafWindowObject::Event(updateEvent);
			updateEvent.region.left = 0;
			updateEvent.region.top = updateEvent.region.bottom - ZAF_BORDER_SIZE;
			ZafWindowObject::Event(updateEvent);
		}
		break;

	default:
		ccode = ZafWindowObject::Event(event);
		break;
	}

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

ZafEventType ZafString::OSEvent(const ZafEventStruct &event)
{
	if (editMode && event.InputType() == E_MOUSE)
		return (ZafWindowObject::Event(event));

	ZafEventType ccode = event.type;

	switch (event.osEvent.message)
	{
	case WM_CHAR:
		ZafUpdateOSMaxLength(screenID, stringData->MaxLength(), false);
		if (VariableName() && event.osEvent.wParam == VK_SPACE)
		{
			// Convert space character to an underbar.
			ZafEventStruct underbarEvent = event;
			underbarEvent.osEvent.wParam = (WPARAM)'_';
			ccode = ZafWindowObject::OSEvent(underbarEvent);
		}
		else
			// Default processing.
			ccode = ZafWindowObject::OSEvent(event);
		break;

#if defined(ZAF_WIN32)
	case WM_CTLCOLOREDIT:
#else
	case WM_CTLCOLOR:
#endif
		{
		// Get the palettes.
		ZafPaletteState state = PaletteState();
		ZafPaletteStruct textPalette = LogicalPalette(ZAF_PM_TEXT, state);
		ZafPaletteStruct backgroundPalette = LogicalPalette(ZAF_PM_BACKGROUND, state);

		// Get the OS colors from the text palette.
		COLORREF colorForeground = (textPalette.colorForeground == ZAF_CLR_DEFAULT) ?
			textPalette.osPalette.colorForeground :
			((ZafScreenDisplay *)display)->colorTable[textPalette.colorForeground];
		COLORREF colorBackground = (backgroundPalette.colorBackground == ZAF_CLR_DEFAULT) ?
			backgroundPalette.osPalette.colorBackground :
			((ZafScreenDisplay *)display)->colorTable[backgroundPalette.colorBackground];

		// Set the OS colors.
		SelectPalette((HDC)event.osEvent.wParam, ((ZafScreenDisplay *)display)->logicalPalette, FALSE);
		::SetTextColor((HDC)event.osEvent.wParam, colorForeground);
		SetBkColor((HDC)event.osEvent.wParam, colorBackground);

		// Get a background brush.
		HBRUSH backgroundBrush = ((ZafScreenDisplay *)display)->GetGdiCacheBrush(backgroundPalette);
		((ZafScreenDisplay *)display)->ReleaseGdiCacheObject(backgroundBrush);
		ccode = (ZafEventType)backgroundBrush;
		}
		break;

	case WM_KEYDOWN:
	case WM_KEYUP:
		{
		int keyValue = (int)event.osEvent.wParam;

		if (keyValue == VK_RETURN)
		{
			if (event.osEvent.message == WM_KEYDOWN)
				ccode = ZafString::Event(ZafEventStruct(L_SELECT));
			else
				ccode = 0;
		}
		else if (keyValue == VK_TAB)
			ccode = S_UNKNOWN;
		else
		{
			ccode = ZafWindowObject::OSEvent(event);
			if (ccode == S_UNKNOWN)
			{
				// Translate the virtual key code to an ASCII value.
				UINT charValue = MapVirtualKey(keyValue, 2);
				if ((charValue >= '0' && charValue <= '9') ||
				 (charValue >= 'A' && charValue <= 'Z') ||
				 (keyValue >= VK_NUMPAD0 && keyValue <= VK_DIVIDE) ||
				 (keyValue >= VK_SPACE && keyValue <= VK_DOWN) ||
				 keyValue == VK_BACK)
					ccode = 0;
			}
		}
		}
		break;

	case WM_LBUTTONDOWN:
		if (!OSDraw())
			SetFocus(true);
		ccode = ZafWindowObject::OSEvent(event);
		break;

	case WM_PAINT:
	case WM_ERASEBKGND:
		if (OSDraw())
			ccode = DefaultCallback(event);
		else
			ccode = ZafWindowObject::Event(event);
		break;

	case WM_PASTE:
		ZafUpdateOSMaxLength(screenID, stringData->MaxLength(), true);
		if (VariableName() && IsClipboardFormatAvailable(CF_TEXT))
		{
			// Find out how much space is left in the strings buffer.
			OSGetText();
			int	charsRemaining = stringData->MaxLength();
			if (charsRemaining >= 0)
				charsRemaining -= strlen(stringData->Text());

			// Paste text character by character to enforce variable name attribute.
			OpenClipboard(screenID);
			HANDLE hText = GetClipboardData(CF_TEXT);
			if (hText)
			{
				char *textPtr = (char *)GlobalLock(hText);
				while (*textPtr && (charsRemaining == -1 || charsRemaining-- > 0))
					SendMessage(screenID, WM_CHAR, (WPARAM)*textPtr++, (LPARAM)1);
				GlobalUnlock(hText);
			}
			CloseClipboard();	
		}
		else
			ccode = ZafWindowObject::OSEvent(event);
		break;

	default:
		ccode = ZafWindowObject::OSEvent(event);
	}

	return (ccode);
}

ZafError ZafString::SetCursorOffset(int offset)
{
	if (screenID && OSDraw())
	{
		// Set the cursor position.
#if defined(ZAF_WIN32)
			SendMessage(screenID, EM_SETSEL, (WPARAM)offset, (LPARAM)offset);
	 		SendMessage(screenID, EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
#else
	  		SendMessage(screenID, EM_SETSEL, (WPARAM)0, MAKELPARAM(offset, offset));
#endif
		return (ZAF_ERROR_NONE);
	}

	return (ZAF_ERROR_INVALID_ID);
}

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

ZafError ZafString::OSGetText(void)
{
	// Update the internal text.
	if (screenID && SystemObject() && OSDraw())
	{
		stringData->SetUpdate(this, ZAF_UPDATE_NONE);
		int textLength = (WORD)SendMessage(screenID, WM_GETTEXTLENGTH, (WPARAM)0, (LPARAM)0);
#if defined(ZAF_UNICODE)
		if (ZafMSWindowsApp::convertText)
		{
			char *osText = new char[textLength + 1];
#	if defined(ZAF_WIN32)
			SendMessageA(screenID, WM_GETTEXT, textLength + 1, (LONG)osText);
#	else
			SendMessage(screenID, WM_GETTEXT, textLength + 1, (LONG)osText);
#	endif
			stringData->SetOSText(osText);
			delete []osText;
		}
		else
		{
			ZafIChar *text = new ZafIChar[textLength + 1];
			SendMessage(screenID, WM_GETTEXT, textLength + 1, (LONG)text);
			stringData->SetText(text);
			delete []text;
		}
#else
		char *text = new char[textLength + 1];
		SendMessage(screenID, WM_GETTEXT, textLength + 1, (LONG)text);
		if (ZafMSWindowsApp::convertText)
			stringData->SetOSText(text);
		else
			stringData->SetText(text);
		delete []text;
#endif
		stringData->SetUpdate(this, ZAF_UPDATE_ALL);
	}
	return (ZAF_ERROR_NONE);
}

void ZafString::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType type, ZafPaletteState state)
{
	// Get default colors from the operating system.
	if (type == ZAF_PM_BACKGROUND)
	{
		if (palette.colorForeground == ZAF_CLR_DEFAULT && palette.osPalette.colorForeground == 0xFFFFFFFFL)
			palette.osPalette.colorForeground = GetSysColor(COLOR_WINDOW);
		if (palette.colorBackground == ZAF_CLR_DEFAULT && palette.osPalette.colorBackground == 0xFFFFFFFFL)
			palette.osPalette.colorBackground = GetSysColor(COLOR_WINDOW);
	}
	else
		ZafWindowObject::OSMapPalette(palette, type, state);
}

void ZafString::OSRegisterObject(void)
{
	// Set the Windows style flags.
	DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS | ES_AUTOHSCROLL;
	DWORD dwExStyle = 0;

	if (Disabled() && !editMode)
		dwStyle |= WS_DISABLED;
	if (visible)
		dwStyle |= WS_VISIBLE;

	if (Bordered() && OSDraw())
	{
#if defined(ZAF_MSWINDOWS_3D)
		if (ZafMSWindowsApp::native3D)
			dwExStyle |= WS_EX_CLIENTEDGE;
		else
			dwStyle |= WS_BORDER;
#else
		dwStyle |= WS_BORDER;
#endif
	}

	if (OSDraw())
	{
		if (Password())
			dwStyle |= ES_PASSWORD;
		if (UpperCase())
			dwStyle |= ES_UPPERCASE;
		else if (LowerCase())
			dwStyle |= ES_LOWERCASE;
		if (HzJustify() == ZAF_HZ_RIGHT)
			dwStyle |= ES_RIGHT | ES_MULTILINE;
		if (HzJustify() == ZAF_HZ_CENTER)
			dwStyle |= ES_CENTER | ES_MULTILINE;
		if (viewOnly)
			dwStyle |= ES_READONLY;

		// Create the window.
		screenID = ZafMSWindowsApp::CreateSubclassedWindow(this, ZAF_ITEXT("EDIT"),
			dwStyle, NULL, 0, dwExStyle);

		// Limit the size of the edit buffer;
		SendMessage(screenID, EM_LIMITTEXT,
			(WPARAM)(stringData->MaxLength() > -1) ? stringData->MaxLength() : 0,
			(LPARAM)FALSE);

		// Set the font.
		ZafPaletteStruct fontPalette = LogicalPalette(ZAF_PM_TEXT, ZAF_PM_ACTIVE | ZAF_PM_ENABLED);
		SendMessage(screenID, WM_SETFONT, (WPARAM)((ZafScreenDisplay *)display)->fontTable[fontPalette.font], (LPARAM)FALSE);

		// Set the text.
		bool saveChanged = changed;
		OSSetText();
		changed = saveChanged;
	}
	else
		screenID = ZafMSWindowsApp::CreateSubclassedWindow(this, NULL,
			dwStyle, NULL, 0, dwExStyle);
}

ZafError ZafString::OSSetText(void)
{
	if (screenID && parent->IsA(ID_ZAF_LIST) && parent->OSDraw())
		return (ZafWindowObject::OSSetText());

//??? This code should be in a "z_" file?
	if (VariableName())
	{
		// Convert appropriate characters in the text.
		int length = stringData->Length();
		ZafStringData *zncText = StringData();
		for (int i = 0; i < length; i++)
			if (isspace((*zncText)[i]))
				zncText->SetChar(i, '_');
	}

	if (screenID && SystemObject() && OSDraw())
	{
		if (ZafMSWindowsApp::convertText)
		{
			char *osText = stringData->DynamicOSText();
#if defined(ZAF_WIN32) && defined(ZAF_UNICODE)
			SendMessageA(screenID, WM_SETTEXT, (WPARAM)0, (LPARAM)osText);
#else
			SendMessage(screenID, WM_SETTEXT, (WPARAM)0, (LPARAM)osText);
#endif
			delete []osText;
		}
		else
			SendMessage(screenID, WM_SETTEXT, (WPARAM)0, (LPARAM)stringData->Text());
	}
	else
		RedisplayData();
	return (ZAF_ERROR_NONE);
}

// ----- Stubs --------------------------------------------------------------

void ZafString::OSSize(void)
{
	ZafWindowObject::OSSize();
}

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

bool ZafString::SetSelected(bool setSelected)
{
	return (ZafWindowObject::SetSelected(setSelected));
}

#if defined(ZAF_UNICODE)
void ZafUpdateOSMaxLength(OSWindowID screenID, int maxLength, bool isPaste)
{
	if (!screenID || maxLength <= 0 || !ZafMSWindowsApp::convertText)
	return;

	// get text in the edit control
	ZafStringData inputData(ZAF_NULLP(ZafIChar), maxLength);
	int textLength = (WORD)SendMessage(screenID, WM_GETTEXTLENGTH, (WPARAM)0, (LPARAM)0);
	char* osText = new char[textLength + 1];
#	if defined(ZAF_WIN32)
	SendMessageA(screenID, WM_GETTEXT, textLength + 1, (LONG)osText);
#	else
	SendMessage(screenID, WM_GETTEXT, textLength + 1, (LONG)osText);
#	endif
	inputData.SetOSText(osText);
	delete []osText;

	// append text in a clipboard
	if (isPaste && OpenClipboard(screenID))
	{
		HANDLE hText = GetClipboardData(CF_TEXT);
		if (hText)
		{
			ZafStringData clipboardData(ZAF_NULLP(ZafIChar), maxLength);
			clipboardData.SetOSText((char *)GlobalLock(hText));
			inputData += clipboardData;
			GlobalUnlock(hText);
		}
		CloseClipboard();
	}

	// now, inputData has a text after clipboard paste.
	// get a char-string from it, and calc length.
	osText = inputData.DynamicOSText();
	textLength = strlen(osText);
	delete []osText;
	int textLengthW = inputData.Length(); // may be less or equal maxLength

	// calc limit by char-length
	textLength += (maxLength - textLengthW) * sizeof(ZafIChar);

	// set new limit value
	SendMessage(screenID, EM_LIMITTEXT, textLength, 0);
}
#else
void ZafUpdateOSMaxLength(OSWindowID, int, bool)
{
}
#endif
