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

#include <z_ctype.hpp>
#include <z_cursor.hpp>
#define USE_RAW_KEYS
#include <z_keymap.hpp>
#include <z_mouse2.hpp>
#include <z_str1.hpp>
#include <z_string.hpp>
#include <z_utils.hpp>

const char STRING_PASSWORD_CHAR = '*';

static void StrCharSet(ZafIChar *string, int value, size_t num);

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

int ZafString::CursorOffset(void) const
{
	return (cursor);
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafString::Draw(const ZafEventStruct &event, ZafEventType ccode)
{
	// Compute the actual draw region.
	ZafPaletteState state = PaletteState();
	ZafRegionStruct drawRegion = BeginDraw();

	display->SetClipRegion(event.region);

	// Don't draw if everything will be clipped.
	if (!drawRegion.Overlap(display->ClipRegion()))
	{
		EndDraw();
		return (ccode);
	}

	if (Bordered())
		DrawBorder(drawRegion, ccode);

	ZafRegionStruct clip = display->ClipRegion();
	clip.right = ZafMin(clip.right, drawRegion.right);
	clip.bottom = ZafMin(clip.bottom, drawRegion.bottom);
	display->SetClipRegion(clip);

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

	if (!Bordered())
		drawRegion.left += ZAF_BORDER_SIZE;

	// Set the palette for drawing the text.
	ZafPaletteStruct palette = LogicalPalette(ZAF_PM_TEXT, state);
	display->SetPalette(palette);

	// If the field is a password, create and display a password string.
	ZafIChar *buffer = new ZafIChar[length + 1];
	buffer[length] = '\0';

	if (password)
		StrCharSet(buffer, STRING_PASSWORD_CHAR, length);
	else
		strcpy(buffer, stringData->Text());

	int leftDraw = ZAF_TEXT_MARGIN_SIZE;
	int leftJust;
	if (HzJustify() == ZAF_HZ_LEFT || display->TextSize(&buffer[firstDisplayedChar]).Width() > drawRegion.Width())
		leftJust = 0;
	else if (HzJustify() == ZAF_HZ_RIGHT)
	{
		if (display->TextSize(buffer).Width() < drawRegion.Width())
		{
			leftDraw = 0;
			leftJust = drawRegion.Width() - display->TextSize(&buffer[firstDisplayedChar]).Width() -
				(Bordered() ? ZAF_BORDER_SIZE : 0);
		}
		else
			leftJust = 0;
	}
	else
		leftJust = drawRegion.Width() / 2 - display->TextSize(&buffer[firstDisplayedChar]).Width() / 2 -
			(Bordered() ? ZAF_BORDER_SIZE : 0);
	int width = zafRegion.Width();
	int beginOffset;
	int endOffset = 0;
	for (int i = 0; endOffset < length; i++)
	{
		beginOffset = (endOffset > firstDisplayedChar) ? endOffset : firstDisplayedChar;
		if (i == 0)			// Begin mark region.
			endOffset = (beginMark != -1) ? beginMark : length;
		else if (i == 1)	// End mark region.
			endOffset = endMark;
		else				// End of string.
			endOffset = length;
		if (beginOffset != endOffset && endOffset > firstDisplayedChar && leftDraw < width)
		{
			ZafIChar saveChar = buffer[endOffset];
			buffer[endOffset] = '\0';
			int drawRightPos = drawRegion.left + leftDraw + leftJust + display->TextSize(&buffer[firstDisplayedChar]).Width();
			if (i == 1)
			{
				// Marking.
				display->SetForeground(((ZafScreenDisplay *)display)->markPalette->colorForeground);
				display->SetBackground(((ZafScreenDisplay *)display)->markPalette->colorBackground);
				display->Text(drawRegion.left + leftDraw + leftJust, drawRegion.top + ZAF_TEXT_MARGIN_SIZE, drawRightPos, drawRegion.bottom, &buffer[beginOffset],
					-1, ZAF_HZ_LEFT, ZAF_VT_TOP, -1, true);
			}
			else
			{
				// Not marking.
				display->SetForeground(palette.colorForeground);
				display->SetBackground(palette.colorBackground);
	 			display->Text(drawRegion.left + leftDraw + leftJust, drawRegion.top + ZAF_TEXT_MARGIN_SIZE, drawRightPos, drawRegion.bottom, &buffer[beginOffset],
					-1, ZAF_HZ_LEFT, ZAF_VT_TOP);
			}

			leftDraw += display->TextSize(&buffer[beginOffset]).Width();
			buffer[endOffset] = saveChar;
		}
	}

	delete []buffer;

	if (Focus())
		DrawFocus(drawRegion, ccode);
 
	EndDraw();
	return (ccode);
}
#endif

ZafEventType ZafString::Event(const ZafEventStruct &event)
{
	int tCursor = cursor;   // Checks to see if cursor moves.
	int tBeginMark = beginMark; // Checks to see if begin mark changes.
	int tEndMark = endMark; // Checks to see if end mark changes.
	int insertCount = 0;		// Counts how many characters have been inserted.
	int deleteCount = 0;		// Counts how many characters have been deleted.

	// Used to check for double-clicks.
	static ZafWindowObject *lastMarked;

	const ZafIChar *text = stringData->Text();

	ZafEventType ccode = LogicalEvent(event);

	// Check for zinc events.
	switch (ccode)
	{
	// ----- Action messages -----
	case L_BEGIN_SELECT:
	case L_CONTINUE_SELECT:
	case L_END_SELECT:
//	case L_CONTINUE_COPY_DRAG:
//	case L_END_COPY_DRAG:
		{
			if (editMode || windowManager->dragObject)
			{
				ZafWindowObject::Event(event);
				break;
			}

			if (Draggable())
				ZafWindowObject::Event(event);

			ZafPositionStruct mousePosition = event.position;
//			if (Bordered())
#if !defined(ZAF_TEXT_MODE)
			{
				mousePosition.column -= ZAF_BORDER_SIZE;
				mousePosition.line -= ZAF_BORDER_SIZE;
			}
#endif

			if (Noncurrent() || (ccode == L_CONTINUE_SELECT && lastMarked != this))
				return (ccode);

			ZafIChar *buffer = new ZafIChar[length + 1];
			buffer[length] = '\0';

			if (password)
				StrCharSet(buffer, STRING_PASSWORD_CHAR, length);
			else
				strcpy(buffer, stringData->Text());

			int bufferWidth = display->TextSize(buffer).Width();
			if (HzJustify() == ZAF_HZ_LEFT || bufferWidth > zafRegion.Width())
				; // Don't adjust the mouse position
			else if (HzJustify() == ZAF_HZ_RIGHT)
				mousePosition.column -= zafRegion.Width() - bufferWidth - (Bordered() ? 2 * ZAF_BORDER_SIZE : 0) - 1;
			else
				mousePosition.column -= (zafRegion.Width() - bufferWidth) / 2  - (Bordered() ? 2 * ZAF_BORDER_SIZE : 0) - 1;

			if (ccode == L_END_SELECT)
			{
				windowManager->CaptureMouse(ZAF_NULLP(ZafWindowObject));
				delete []buffer;
				lastMarked = ZAF_NULLP(ZafWindowObject);
				if (beginMark == endMark)
					beginMark = endMark = -1;
				return (ccode);
			}
			if (ccode == L_BEGIN_SELECT)
			{
				windowManager->CaptureMouse(this);
				lastMarked = this;
			}

			// If the field is a password, display a password string.
			ZafIChar saveChar = buffer[tCursor];
			buffer[tCursor] = '\0';
			int currentLength = display->TextSize(&buffer[firstDisplayedChar]).Width();
 			buffer[tCursor] = saveChar;

			if (currentLength > mousePosition.column)
				while (tCursor > 0)
				{
					saveChar = buffer[tCursor];
  					buffer[tCursor] = '\0';
// This fixes a cursor placement problem in text mode.
#if defined(ZAF_TEXT_MODE)
					if (!buffer[firstDisplayedChar] || !tCursor ||
						tCursor == mousePosition.column)
#else
					if (!buffer[firstDisplayedChar] || !tCursor ||
						display->TextSize(&buffer[firstDisplayedChar]).Width() -
						display->TextSize(&buffer[tCursor - 1]).Width() / 2  < mousePosition.column)
#endif
					{
						buffer[tCursor] = saveChar;
						if (mousePosition.column > 0)
							tCursor = ZafMin(length, tCursor);
						break;
					}
					buffer[tCursor] = saveChar;
					--tCursor;// = ZafMax(0, tCursor-1);
				}
			else
			{
				while (tCursor < length)
				{
					int charWidth;
					ZafIChar buf[2];
					buf[1] = '\0';
					buf[0] = buffer[tCursor];
					charWidth = display->TextSize(buf).Width();

					saveChar = buffer[tCursor];
					buffer[tCursor] = '\0';
// This fixes a cursor placement problem in text mode.
#if defined(ZAF_TEXT_MODE)
					if (tCursor == mousePosition.column)
#else
					if (display->TextSize(&buffer[firstDisplayedChar]).Width() +
						charWidth / 2 + 1 > mousePosition.column)
#endif 
					{
						buffer[tCursor] = saveChar;
						break;
					}
					buffer[tCursor] = saveChar;
					tCursor++;
				}
			}

			delete []buffer;

			if (ccode == L_BEGIN_SELECT)
				beginMark = endMark = tCursor;
		}
		break;

	case L_DOUBLE_CLICK:
		{
		beginMark = endMark = -1;
		Event(L_WORD_LEFT);
		Event(L_MARK_WORD_RIGHT);
		tCursor = cursor;

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

	case S_COPY:
	case L_COPY:
		if (beginMark != -1)
		{
			pasteLength = endMark - beginMark;
			if (pasteBuffer)
				delete []pasteBuffer;
			if (pasteLength)
				pasteBuffer = new ZafIChar[pasteLength];
			else
				pasteBuffer = ZAF_NULLP(ZafIChar);
			if (password)
				StrCharSet(pasteBuffer, STRING_PASSWORD_CHAR, pasteLength);
			else
				memcpy(pasteBuffer, &stringData->Text()[beginMark], pasteLength * sizeof(ZafIChar));
			beginMark = endMark = -1;
		}
		break;

	case S_PASTE:
	case L_PASTE:
		if (!ViewOnly() && beginMark != endMark)
		{
			Event(L_DELETE);
			// Reset the temporaries.
			tCursor = cursor;
			tBeginMark = beginMark;
			tEndMark = endMark;
			insertCount = 0;
			deleteCount = 0;
		}
		if (!ViewOnly() && (stringData->MaxLength() == -1 || pasteLength <= stringData->MaxLength() - length))
		{
			length += pasteLength;
			ZafUpdateType generalUpdateType = stringData->Update();
			stringData->SetUpdate(ZAF_UPDATE_NONE);
			ZafUpdateType thisUpdateType = stringData->Update(this);
			stringData->SetUpdate(this, ZAF_UPDATE_NONE);
			stringData->SetChar(length, '\0');
			for (int i = 0; i < pasteLength; i++)
			{
				ZafIChar key = pasteBuffer[i];
				if (lowerCase && IsUpper(pasteBuffer[i]))
					key = ToLower(pasteBuffer[i]);
				if (upperCase && IsLower(pasteBuffer[i]))
					key = ToUpper(pasteBuffer[i]);
				if (VariableName() && pasteBuffer[i] == ' ')
					key = '_';
				stringData->Insert(tCursor++, &key, 1);
			}
			insertCount = pasteLength;
			stringData->SetUpdate(generalUpdateType);
			stringData->UpdateData();
			stringData->SetUpdate(this, thisUpdateType);
			SetChanged(true);
		}
		break;

	case L_SELECT:
	case N_NON_CURRENT:
		// Call the associated user function.
		OSGetText();
		ccode = (this->*memberUserFunction)(event, (ccode == L_SELECT) ? L_SELECT : N_NON_CURRENT);

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

	case S_REDISPLAY:
		ZafWindowObject::Event(event);
		if (Focus())
			SetCursorOffset(cursor);
		break;

	case S_CURRENT:
		{
			if (autoClear && !Focus() && SystemObject())
			{
				beginMark = 0;
				endMark = length;

				// Make sure an auto-clear cursor stays at the beginning of the
				// string when the string becomes current if the text is longer
				// than will display all at once.
				if (display->TextSize(stringData->Text()).Width() >= zafRegion.Width() &&
					HzJustify() == ZAF_HZ_LEFT)
					tCursor = 0;
				else
					tCursor = length;
				ZafWindowObject::Event(event);
			}
			else
			{
				ZafWindowObject::Event(event);
				Event(S_REDISPLAY);
			}

			// Activate the cursor device.
			ZafEventStruct cEvent(D_ACTIVATE);
			cEvent.windowObject = this;
			eventManager->Event(cEvent, E_CURSOR);
			SetCursorOffset(tCursor);
			windowManager->cursorObject = this;
		}
		break;

	case S_NON_CURRENT:
		{
//			OSGetText();
			ccode = ZafWindowObject::Event(event);
			beginMark = endMark = -1;

			// Deactivate the cursor device.
			ZafEventStruct cEvent(D_DEACTIVATE);
			eventManager->Event(cEvent, E_CURSOR);
			eventManager->SetDeviceState(E_CURSOR, D_OFF);
			windowManager->cursorObject = ZAF_NULLP(ZafWindowObject);

			if (!SystemObject())
				Draw(ZafEventStruct(S_REDISPLAY_REGION, 0, ConvertToDrawRegion(this, &zafRegion)),
					S_REDISPLAY_REGION);
		}
		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 S_REDISPLAY_CURSOR:
		{
			// Activate the cursor device.
			eventManager->SetDeviceState(E_CURSOR, D_ON);
			ZafEventStruct cEvent(D_ACTIVATE);
			cEvent.windowObject = this;
			eventManager->Event(cEvent, E_CURSOR);
			SetCursorOffset(cursor);
		}
		break;

	case N_MOUSE_ENTER:
		ZafWindowObject::Event(event);
		if (SystemObject() && !Noncurrent())
			eventManager->SetDeviceState(E_MOUSE, DM_EDIT);
		else
			ccode = S_UNKNOWN;
		break;

	case L_LEFT:
	case L_MARK_LEFT:
		if (ccode == L_LEFT)
			beginMark = endMark = -1;
		else if (beginMark == -1)
		{
			beginMark = endMark = tCursor;
			tBeginMark = tEndMark = -1;
		}
		if (tCursor > 0)
			tCursor--;
			else
				ccode = S_UNKNOWN;
		break;

	case L_RIGHT:
	case L_MARK_RIGHT:
		if (ccode == L_RIGHT)
			beginMark = endMark = -1;
		else if (beginMark == -1)
		{
			beginMark = endMark = tCursor;
			tBeginMark = tEndMark = -1;
		}
		if (tCursor < length)
			tCursor++;
			else
				ccode = S_UNKNOWN;
		break;

	case L_WORD_RIGHT:
	case L_MARK_WORD_RIGHT:
		if (ccode == L_WORD_RIGHT)
			beginMark = endMark = -1;
		else if (beginMark == -1)
		{
			beginMark = endMark = tCursor;
			tBeginMark = tEndMark = -1;
		}
		if (password)
			tCursor = length;
		while (tCursor < length && text[tCursor] != ' ')
			tCursor++;
		while (tCursor < length && text[tCursor] == ' ')
			tCursor++;
		break;

	case L_WORD_LEFT:
	case L_MARK_WORD_LEFT:
		if (ccode == L_WORD_LEFT)
			beginMark = endMark = -1;
		else if (beginMark == -1)
		{
			beginMark = endMark = tCursor;
			tBeginMark = tEndMark = -1;
		}
		if (password)
			tCursor = 0;
		while (tCursor > 0 && text[tCursor - 1] == ' ')
			tCursor--;
		while (tCursor > 0 && text[tCursor - 1] != ' ')
			tCursor--;
		break;

	case L_BOL:
	case L_MARK_BOL:
		if (ccode == L_MARK_BOL && beginMark == -1)
		{
			beginMark = endMark = tCursor;
			tBeginMark = tEndMark = -1;
		}
		else if (beginMark != -1)
			beginMark = endMark = -1;
		tCursor = 0;
		break;

	case L_EOL:
	case L_MARK_EOL:
		if (ccode == L_MARK_EOL && beginMark == -1)
		{
			beginMark = endMark = tCursor;
			tBeginMark = tEndMark = -1;
		}
		else if (beginMark != -1)
			beginMark = endMark = -1;
		tCursor = length;
		break;

	case L_CUT:
		ccode = S_CUT;
		// Continue to S_CUT.

	case S_CUT:
	case L_DELETE:
	case L_DELETE_EOL:
	case L_DELETE_WORD:
		{
			if (ViewOnly())
				break;
			else if (beginMark != -1)
			{
				deleteCount = endMark - beginMark;
				if (ccode == S_CUT)
				{
					if (pasteBuffer)
						delete []pasteBuffer;
					pasteLength = deleteCount;
					if (pasteLength)
					{
						pasteBuffer = new ZafIChar[pasteLength];
						if (password)
							StrCharSet(pasteBuffer, STRING_PASSWORD_CHAR, pasteLength);
						else
							memcpy(pasteBuffer, &stringData->Text()[beginMark], pasteLength * sizeof(ZafIChar));
					}
					else
						pasteBuffer = ZAF_NULLP(ZafIChar);
				}
				tCursor = beginMark;
				beginMark = endMark = -1;
				length -= deleteCount;
				ZafUpdateType updateType = stringData->Update(this);
				stringData->SetUpdate(this, ZAF_UPDATE_NONE);
				stringData->Remove(tCursor, deleteCount);
				stringData->SetUpdate(this, updateType);
				SetChanged(true);
				break;
			}

			// Short cut to get repeated Delete's off the queue.
			int emptyQueue;
//			const ZafIChar *text = Text();
			ZafEventStruct tEvent = event;
			do
			{
				if (ccode == L_DELETE)
				{
					if (tCursor < length)
						++deleteCount;
				}
				else if (ccode == L_DELETE_EOL)
					deleteCount = length - tCursor;
				else if (ccode == L_DELETE_WORD)
				{
					while (tCursor > 0 && text[tCursor-1] != ' ')
						tCursor--;
					deleteCount = tCursor;
					while (deleteCount < length && text[deleteCount] != ' ')
						deleteCount++;
					while (deleteCount < length && text[deleteCount] == ' ')
						deleteCount++;
					deleteCount -= tCursor;
				}
				emptyQueue = eventManager->Get(tEvent, Q_NO_BLOCK | Q_NO_DESTROY);
				if (!emptyQueue && tEvent.type == event.type && tEvent.rawCode == event.rawCode)
					eventManager->Get(tEvent);
				else
					emptyQueue = true;
			} while (!emptyQueue);

			if (deleteCount > 0)
			{
				length -= deleteCount;
				ZafUpdateType updateType = stringData->Update(this);
				stringData->SetUpdate(this, ZAF_UPDATE_NONE);
				stringData->Remove(tCursor, deleteCount);
				stringData->SetUpdate(this, updateType);
				SetChanged(true);
			}
			SetCursorOffset(cursor);
		}
		break;

	case L_BACKSPACE:
		// If at the left edge of the text and no text is marked, do nothing.
		// L_LEFT event updates the cursor, if required.
		if (beginMark == -1 && Event(L_LEFT) == S_UNKNOWN)
			break;

		// Delete the marked text.
		Event(L_DELETE);

		// Don't need to update the cursor, since it was taken care of
		// during processing of the L_DELETE.
		return (ccode);

	case E_KEY:
		{
			if (ViewOnly() || Disabled() || Noncurrent() || !OSDraw() ||
				!IsPrint(event.key.value))
				break;

			if (tCursor > length)
				tCursor = cursor = length;

			if (beginMark != -1)
			{
				ZafString::Event(L_DELETE);
				tCursor = cursor;
				tBeginMark = beginMark, tEndMark = endMark;
			}

			insertCount = 0;
			ZafIChar key = event.key.value;
			if (lowerCase && IsUpper(key))
				key = ToLower(key);
			if (upperCase && IsLower(key))
				key = ToUpper(key);
			if (VariableName() && key == ' ')
				key = '_';

//			const ZafIChar *text = Text();
			if ((stringData->MaxLength() == -1 || length < stringData->MaxLength()))// &&
//				(/*Insert() || tCursor == length ||*/
//				text[tCursor] == '\r' || text[tCursor] == '\n'))
			{
				length++;
				ZafUpdateType updateType = stringData->Update(this);
				stringData->SetUpdate(this, ZAF_UPDATE_NONE);
				stringData->Insert(tCursor++, &key, 1);
				stringData->SetUpdate(this, updateType);
				insertCount++;
			}
/* START BLOCK COMMENT
**				else if (!Insert() && tCursor < length)
**				{
**					ZafUpdateType updateType = stringData->Update(this);
**					stringData->SetUpdate(this, ZAF_UPDATE_NONE);
**					stringData->Replace(tCursor++, 1, &key, 1);
**					stringData->SetUpdate(this, updateType);
**					insertCount++;
**				}
END BLOCK COMMENT */

			SetChanged(true);
		}
		break;

	case S_COPY_DATA:
		{
			ZafWindowObject *object = event.windowObject;
			ZafString *string = DynamicPtrCast(object, ZafString);
			if (!string)
				break;

			stringData->SetText(*string->StringData());
		}
		break;

	case S_SET_DATA:
		{
			ZafWindowObject *object = event.windowObject;
			if (object)
			{
				ZafString *string = DynamicPtrCast(object, ZafString);
				if (!string)
					break;

				SetStringData(string->StringData());
			}
			else
				SetStringData(new ZafStringData(*stringData));
		}
		break;

	case S_REDISPLAY_DATA:
		{
			ZafRegionStruct tRegion = zafRegion;
			tRegion.VtShift(-tRegion.top);
			tRegion.HzShift(-tRegion.left);
			if (Bordered())
				tRegion -= ZAF_BORDER_SIZE;
			OSSetText();
			OSInvalidateRegion(&tRegion, true, false);
		}
		break;

	case S_DEINITIALIZE:
		if (Focus())
		{
			// Deactivate the cursor device.
			ZafEventStruct cEvent(D_DEACTIVATE);
			eventManager->Event(cEvent, E_CURSOR);
			eventManager->SetDeviceState(E_CURSOR, D_OFF);
		}
		return (ZafWindowObject::Event(event));

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

	if (Disabled() || Noncurrent() || !OSDraw() || !SystemObject() || !Visible())
		return (ccode);

	// Check the mark information.
	if (beginMark != -1)
	{
		if (cursor == beginMark)
			beginMark = tCursor;
		else
			endMark = tCursor;
		if (beginMark > endMark)	// Make sure the mark order is correct.
		{
			int temp = beginMark;
			beginMark = endMark;
			endMark = temp;
		}
	}
/* START BLOCK COMMENT
**		if (cursor != tCursor && beginMark != -1 && ccode != S_CURRENT)
**		{
**			// Reset the mark region if the mark status is set.
**			if (FlagSet(woStatus, WOS_AUTO_SELECT))
**			{
**				beginMark = endMark = -1;
**				woStatus &= ~WOS_AUTO_SELECT;
**			}
**			else
**				tBeginMark = tEndMark = -1;
**		}
END BLOCK COMMENT */

	// Recompute the cursor position and redraw the string.
	if (!screenID)							       
	{
		if (insertCount || deleteCount)
			SetChanged(true);
		cursor = tCursor;
		return (ccode);
	}

	bool scrolled = false;
	if (cursor != tCursor)
	{
		int oldFirstChar = firstDisplayedChar;
		SetCursorOffset(tCursor);
		if (oldFirstChar != firstDisplayedChar)
			scrolled = true;
	}

	// Set a clipping region based on what actually changed and redraw the
	// damaged portion of the string.
	if ((insertCount || deleteCount || scrolled ||
		beginMark != tBeginMark || endMark != tEndMark) && AutomaticUpdate())
	{
		ZafRegionStruct drawRegion;
		drawRegion.left = drawRegion.top = 0;
		drawRegion.right = zafRegion.Width() - 1;
		drawRegion.bottom = zafRegion.Height() - 1;
		drawRegion.coordinateType = zafRegion.coordinateType;

		// If the field is a password, create and display a password string.
		ZafIChar *buffer = new ZafIChar[length + 1];
		buffer[length] = '\0';

		if (password)
			StrCharSet(buffer, STRING_PASSWORD_CHAR, length);
		else
			strcpy(buffer, stringData->Text());

		// If scrolled, the entire text is or was marked, redraw entire string.
/* START BLOCK COMMENT
**			if (scrolled || 
**				(beginMark == 0 && endMark == length) ||
**				(tBeginMark == 0 && tEndMark == length))
**				; // Don't modify the drawRegion.  Need to redraw whole string.
END BLOCK COMMENT */
		if (scrolled || 
			(beginMark == 0 && endMark == length) ||
			(tBeginMark == 0 && tEndMark == length) ||
			HzJustify() != ZAF_HZ_LEFT)
			; // Don't modify the drawRegion.  Need to redraw whole string.
		else
		{
			int beginDraw = 0, endDraw = length;

			// At least the beginMark changed.
			if (beginMark != tBeginMark)
			{
				// The cursor may have just moved.  If so, don't need to redraw 
				// anything, so set beginDraw and endDraw to length.
				if (beginMark == endMark && tBeginMark == tEndMark)
					beginDraw = endDraw = length;
				else
				{
					beginDraw = ZafMin(beginMark, tBeginMark);
					if (endMark != tEndMark)
						endDraw = ZafMax(endMark, tEndMark);
					else
						endDraw = ZafMax(beginMark, tBeginMark);
				}
			}
			// The endMark changed.
			else if (endMark != tEndMark)
			{
				// The cursor may have just moved.  If so, don't need to redraw 
				// anything, so set beginDraw and endDraw to length.
				if (beginMark == endMark && tBeginMark == tEndMark)
					beginDraw = endDraw = length;
				else
				{
					beginDraw = ZafMin(endMark, tEndMark);
					endDraw = ZafMax(endMark, tEndMark);
				}
			}
			// Text was inserted.
			else if (insertCount)
			{
				beginDraw = tCursor - insertCount;
				endDraw = length;
			}
			// Text was deleted.
			else if (deleteCount)
				beginDraw = tCursor;

			if (beginDraw < firstDisplayedChar)
				beginDraw = firstDisplayedChar;

			ZafIChar saveBeginChar = buffer[beginDraw];
			ZafIChar saveEndChar = buffer[endDraw];

			buffer[endDraw] = '\0';

			// Just redraw from the cursor location to the end of the field on
			// a deletion.
			if (!deleteCount)
#if defined(ZAF_TEXT_MODE)
				drawRegion.right = display->TextSize(&buffer[firstDisplayedChar]).Width() - 1;
#else
				drawRegion.right = display->TextSize(&buffer[firstDisplayedChar]).Width() + 1;
#endif
			buffer[endDraw] = saveEndChar;

			buffer[beginDraw] = '\0';

#if defined(ZAF_TEXT_MODE)
			drawRegion.left = display->TextSize(&buffer[firstDisplayedChar]).Width() - 1;			
#else
			drawRegion.left = display->TextSize(&buffer[firstDisplayedChar]).Width() + 1;
#endif			
			buffer[beginDraw] = saveBeginChar;

#if !defined(ZAF_TEXT_MODE)
//			if (Bordered())
			{
				drawRegion.left += ZAF_BORDER_SIZE;
				drawRegion.right += ZAF_BORDER_SIZE;
			}
#endif
		}

		OSInvalidateRegion(&drawRegion, true);
//		Draw(ZafEventStruct(S_REDISPLAY_REGION, 0, drawRegion), S_REDISPLAY_REGION);

		delete []buffer;
	}

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

ZafError ZafString::SetCursorOffset(int newOffset)
{
	// Reset the string position.

	if (!SystemObject() || !screenID)
		return (ZAF_ERROR_INVALID_ID);

	if (newOffset < 0 || newOffset > length)
		return (ZAF_ERROR_INVALID);

	// Scroll by one-fourth the region.
	int scrollWidth = zafRegion.Width() / (4 * display->cellWidth);
	if (scrollWidth < 1)
		scrollWidth = 1;

	while (newOffset <= firstDisplayedChar && firstDisplayedChar != 0)
		firstDisplayedChar = ZafMax(0, firstDisplayedChar - scrollWidth);

	// If the field is a password, create and display a password string.
	ZafIChar *buffer = new ZafIChar[length + 1];
	buffer[length] = '\0';

	if (password)
		StrCharSet(buffer, STRING_PASSWORD_CHAR, length);
	else
		strcpy(buffer, stringData->Text());

	// Determine offset of cursor from left of field.
	ZafIChar saveChar = buffer[newOffset];
	buffer[newOffset] = '\0';

	display->SetFont(LogicalPalette(ZAF_PM_TEXT, ZAF_PM_ANY_STATE).font);

	// Don't let the cursor get closer than one cell width to the right edge
	// of the field.
	bool doneScrolling = false;

	if (zafRegion.Width() <= 0)
		doneScrolling = true;

	while (!doneScrolling)
	{
		if ((display->TextSize(&buffer[firstDisplayedChar]).Width() + ZAF_TEXT_MARGIN_SIZE > zafRegion.Width() - display->cellWidth &&
			newOffset != length) ||
			(display->TextSize(&buffer[firstDisplayedChar]).Width() + ZAF_TEXT_MARGIN_SIZE > zafRegion.Width() - 2 * (Bordered() ? ZAF_BORDER_SIZE : 0) &&
			newOffset == length))
		{
			if (firstDisplayedChar == length)
				doneScrolling = true;
			firstDisplayedChar = ZafMin(length, firstDisplayedChar + scrollWidth);
		}
		else
			doneScrolling = true;
	}

	// Get width of text from first displayed char to the cursor location.
	int leftOffset;

//	if (HzJustify() == ZAF_HZ_LEFT || firstDisplayedChar != 0)
	if (HzJustify() == ZAF_HZ_LEFT)
	{
		leftOffset = display->TextSize(&buffer[firstDisplayedChar]).Width();
		buffer[newOffset] = saveChar;
	}
	else if (HzJustify() == ZAF_HZ_CENTER)
	{
		buffer[newOffset] = saveChar;
		int showingWidth = display->TextSize(&buffer[firstDisplayedChar]).Width();
		buffer[newOffset] = 0;
		leftOffset = (zafRegion.Width() - showingWidth ) / 2 + display->TextSize(&buffer[firstDisplayedChar]).Width()
			- (Bordered() ? ZAF_BORDER_SIZE : 0);
	}
	else
	{
		buffer[newOffset] = saveChar;
		if (display->TextSize(buffer).Width() > zafRegion.Width())
		{
			buffer[newOffset] = 0;
			leftOffset = display->TextSize(&buffer[firstDisplayedChar]).Width();
//			buffer[newOffset] = saveChar;
		}
		else
		{
//			buffer[newOffset] = saveChar;
			int showingWidth = display->TextSize(&buffer[firstDisplayedChar]).Width();
			buffer[newOffset] = 0;
			int tempWidth = display->TextSize(&buffer[firstDisplayedChar]).Width();
			leftOffset = zafRegion.Width() - showingWidth + tempWidth -
				(Bordered() ? ZAF_BORDER_SIZE * 2 : 0) - ZAF_TEXT_MARGIN_SIZE;
		}
	}

	// Get OS position of field.
	ZafPositionStruct cursorPosition;
	cursorPosition.Assign(zafRegion.left, zafRegion.top);
	cursorPosition.coordinateType = zafRegion.coordinateType;
	cursorPosition = parent->ConvertToOSPosition(this, &cursorPosition);

	// Place the cursor device at the new screen location.
	if (Focus())
	{
		if (!AutomaticUpdate())
			eventManager->SetDeviceState(E_CURSOR, D_OFF);
		else
		{
			eventManager->SetDeviceState(E_CURSOR, /*Insert() ?*/ DC_INSERT /*: DC_OVERSTRIKE*/);
#if defined(ZAF_TEXT_MODE)
 			eventManager->SetDevicePosition(E_CURSOR, cursorPosition.column + leftOffset - 1,
 				cursorPosition.line);
#else
			eventManager->SetDevicePosition(E_CURSOR, cursorPosition.column + leftOffset +
				ZAF_BORDER_SIZE, cursorPosition.line + ZAF_TEXT_MARGIN_SIZE +
				(Bordered() ? ZAF_BORDER_SIZE : 0));
#endif
		}
	}

	// Update cursor counter to new location.
	cursor = newOffset;

	delete []buffer;

	return (ZAF_ERROR_NONE);
}

// ----- Attributes ---------------------------------------------------------

bool ZafString::SetSelected(bool tSelected)
{
	if (selected != tSelected)
	{
		ZafWindowObject::SetSelected(tSelected);

		if (screenID)
			Redisplay();
	}

	return (selected);
}

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

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

ZafError ZafString::OSGetText(void)
{
	return (ZAF_ERROR_NONE);
}

void ZafString::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType type, ZafPaletteState state)
{
	ZafWindowObject::OSMapPalette(palette, type, state);
}

void ZafString::OSRegisterObject(void)
{
	OSSetText();
	ZafWindowObject::OSRegisterObject();
}

ZafError ZafString::OSSetText(void)
{
	const ZafIChar *text = stringData->Text();
	cursor = length = strlen(text);
  	beginMark = endMark = -1;
	if (upperCase || lowerCase || variableName)
	{
 		// Convert the data.
		for (int i = 0; i < length; ++i)
		{
			ZafIChar newChar = 0;
			if (lowerCase && IsUpper(text[i]))
				newChar = ToLower(text[i]);
			if (upperCase && IsLower(text[i])) 
				newChar = ToUpper(text[i]);
			if (variableName && text[i] == ' ')
				newChar = '_';

			if (newChar)
				stringData->SetChar(i, newChar);
		}
	}

	if (screenID)
	{
		// Update cursor in case the text shifted or the new text is shorter.
		firstDisplayedChar = 0;
//		if (Focus())
	  		SetCursorOffset(cursor);
		ZafRegionStruct drawRegion;
		drawRegion.left = drawRegion.top = 0;
		drawRegion.right = zafRegion.Width() - 1;
		drawRegion.bottom = zafRegion.Height() - 1;
		drawRegion.coordinateType = zafRegion.coordinateType;
		if (bordered)
			drawRegion -= ZAF_BORDER_SIZE;
		Event(ZafEventStruct(S_REDISPLAY_REGION, 0, drawRegion));
	}
	return (ZAF_ERROR_NONE);
}

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

static void StrCharSet(ZafIChar *string, int value, size_t num)
{
#if defined(ZAF_UNICODE)
		int i;
		for (i = 0; i < num; i++)
			string[i] = value;
#else
		memset(string, value, num);
#endif
}

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

