//	Zinc Interface Library - Z_STREDT.CPP
//	COPYRIGHT (C) 1990-1999.  All Rights Reserved.
//	Zinc Software Incorporated.  Pleasant Grove, Utah  USA

// ----- Notes -------------------------------------------------------------
//
// The ZafStringEditor sets itself as the windowManager's defaultEventRoute
// and invokes itself when an F12 key event is passed to the windowManager.
// It toggles the defaultEventRoute on and off allowing non-processed events
// to be passed to their intended destination object.
//
// The windowManager's Event() was modified to check the return type from
// the defaultEventRoute's Event().  If the defaultEventRoute returns
// S_UNKNOWN, the windowManager processes the original event as if the
// defaultEventRoute did not exist.
//
// -------------------------------------------------------------------------
  
#include <z_stredt.hpp>
#include <z_border.hpp>
#define USE_RAW_KEYS
#include <z_keymap.hpp>
#include <z_scrll2.hpp>
#include <z_spin.hpp>
#include <z_status.hpp>
#include <z_string.hpp>
#include <z_sys.hpp>
#include <z_table.hpp>
#include <z_title.hpp>
#include <z_utils.hpp>

#if defined(ZAF_MOTIF)
#	include "m_text.hpp"
#	elif defined(ZAF_MSWINDOWS)
#	include <w_app.hpp>
#endif

// --------------------------------------------------------------------------
// ----- ZafStringEditor ----------------------------------------------------
// --------------------------------------------------------------------------

ZafClassID ZafStringEditor::classID = ID_ZAF_STRING_EDITOR;
ZafClassNameChar ZAF_FARDATA ZafStringEditor::className[] = ZAF_ITEXT("ZafStringEditor");

ZafEventMap ZAF_FARDATA ZafStringEditor::defaultEventMap[] =
{
	{ L_STRING_EDITOR, 	E_KEY, 	F12,		S_KEYDOWN },
//	{ L_CANCEL,		E_KEY,	ESCAPE,	S_KEYDOWN },
	{ L_NONE,			0,		0,		0 }
};

ZafStringEditor::ZafStringEditor() :
	ZafDialogWindow(0, 0, 70, 8),
	// member initialization
	page(0x00), requestor(ZAF_NULLP(ZafWindowObject)), adjusted(false)
{
	SetDestroyable(false);

	// Create the requestor's pathID buffer.
	requestID = new ZafIChar[ZAF_MAXPATHLEN];

	// Create the fields.
	CreateFields();

	// Center the window on the screen.
	windowManager->Center(this);

	// Set the string editor as the default event route.
	windowManager->SetDefaultEventRoute(this);
}

ZafStringEditor::~ZafStringEditor(void)
{
	if (requestID)
		delete []requestID;
}

void ZafStringEditor::CreateFields(void)
{
	// Support objects.
	Add(new ZafBorder);
	Add(new ZafSystemButton(ZAF_NATIVE_SYSTEM_BUTTON));
	Add(new ZafTitle(ZAF_ITEXT("ZAF String Editor")));

	// Prompt for edit field.
	ZafPrompt *prompt = new ZafPrompt(1, 0, 5, ZAF_ITEXT("Text:"));
	prompt->SetHzJustify(ZAF_HZ_RIGHT);
	prompt->SetHotKey('T', 0);
	Add(prompt);

	// Edit field.  WordWrap() is true by default.
	editField = new EditText(7, 0, 40, 2);
	editField->Add(new ZafScrollBar(0, 0, 0, 0, ZAF_NULLP(ZafScrollData), ZAF_VERTICAL_SCROLL));
	Add(editField);

	// Prompt for spin control.
	prompt = new ZafPrompt(1, 2, 5, ZAF_ITEXT("Page:"));
	prompt->SetHzJustify(ZAF_HZ_RIGHT);
	prompt->SetHotKey('P', 0);
	Add(prompt);

	// Spin control with hex integer field for controlling character page.
	ZafSpinControl *pageField = new ZafSpinControl(7, 2, 8, new ZafIntegerData((long)1));
	pageField->SetWrappedData(false);
	pageField->Add(new HexInteger(0, 0, 0));
#	if !defined(ZAF_UNICODE)
	pageField->SetDisabled(true);
#	endif
	Add(pageField);

	// Unicode mode prompt for spin control.
	prompt = new ZafPrompt(16, 2, 25, ZAF_ITEXT("(page editable only in UNICODE mode)"));
	Add(prompt);

	// Character map.
	mapField = new CharacterMap(1, 3);
	Add(mapField);

	// OK button.
	ZafButton *okay = new ZafButton(50, 0, 10, 1, ZAF_ITEXT("OK"));
	okay->SetValue(OPT_ACCEPT_EDIT);
	okay->SetSendMessageWhenSelected(true);
	okay->SetHotKey('O', 0);
	Add(okay);

	// Cancel button.
	ZafButton *cancel = new ZafButton(50, 1, 10, 1, ZAF_ITEXT("Cancel"));
	cancel->SetValue(OPT_CANCEL_EDIT);
	cancel->SetSendMessageWhenSelected(true);
	cancel->SetHotKey('C', 0);
	Add(cancel);

	// Status bar with information fields.
	ZafStatusBar *status = new ZafStatusBar(0, 0, 0, 1);
	prompt = new ZafPrompt(1, 0, 25, ZAF_ITEXT("Select character to insert into edit text"));
	status->Add(prompt);
	ZafPrompt *charPrompt = new ZafPrompt(50, 0, 10, ZAF_ITEXT("Character:"));
	charPrompt->SetHzJustify(ZAF_HZ_RIGHT);
	status->Add(charPrompt);
	charField = new ZafPrompt(60, 0, 6, ZAF_NULLP(ZafIChar));
	status->Add(charField);
	Add(status);

	// Provide geometry management for client fields within window.
	ZafGeometryManager *gmgr = new ZafGeometryManager();
	Add(gmgr);
	
	// Attach okay and cancel buttons to right side of window.
	ZafAttachment *attach = new ZafAttachment(okay, ZAF_ATCF_RIGHT);
	attach->SetOffset(1);
	gmgr->Add(attach);
	attach = new ZafAttachment(cancel, ZAF_ATCF_RIGHT);
	attach->SetOffset(1);
	gmgr->Add(attach);

	// Attach right side of edit field to left side of okay button.
	attach = new ZafAttachment(editField, ZAF_ATCF_RIGHT);
	attach->SetReference(okay);
	attach->SetOppositeSide(true);
	attach->SetOffset(1);
	attach->SetStretch(true);
	gmgr->Add(attach);

	// Provide geometry management for fields within status bar.
	gmgr = new ZafGeometryManager();
	status->Add(gmgr);

	// Attach character field to right side of status bar.
	attach = new ZafAttachment(charField, ZAF_ATCF_RIGHT);
	attach->SetOffset(1);
	gmgr->Add(attach);

	// Attach right side of character prompt to left side of character field.
	attach = new ZafAttachment(charPrompt, ZAF_ATCF_RIGHT);
	attach->SetReference(charField);
	attach->SetOppositeSide(true);
	attach->SetOffset(1);
	gmgr->Add(attach);
}

ZafEventType ZafStringEditor::Event(const ZafEventStruct &event)
{
	// Check for non editor-events.
	ZafEventType ccode = (event.InputType() == E_KEY) ? LogicalEvent(event) : event.type;
	switch (ccode)
	{
	case S_STRING_EDITOR:
	case L_STRING_EDITOR:
		if (!screenID)
		{
			// The requestor is the current focus object.
			requestor = windowManager->FocusObject();
			if (!requestor)
				break;

			// Only editable objects can invoke the string editor.
			if (!requestor->EditMode() && requestor->parent && !requestor->parent->EditMode() &&
				((requestor->IsA(ID_ZAF_STRING) && ((ZafString *)requestor)->ViewOnly() == false) ||
				(requestor->IsA(ID_ZAF_TEXT) && ((ZafText *)requestor)->ViewOnly() == false)))
			{
				ZafTableRecord *record = DynamicPtrCast(requestor->parent, ZafTableRecord);
				if (record)
				{
					fieldOffset = requestor->ListIndex();
					recordOffset = record->Offset();
				}
				windowManager->SetDefaultEventRoute(ZAF_NULLP(ZafWindowObject));
				SetRequestor(requestor);
			}
			requestor = ZAF_NULLP(ZafWindowObject);
		}
		break;

	case OPT_ACCEPT_EDIT:
	case OPT_CANCEL_EDIT:
		// Close the window.
		windowManager->Subtract(this);

		if (ccode == OPT_ACCEPT_EDIT)
		{
			// Make sure the requestor still exists before updating its text.
			requestor = windowManager->GetObject(requestID);
			if (requestor)
			{
				// If the requestor is a child of a table record,
				// call the parent table's WriteRecord() method.
				ZafTableRecord *record = DynamicPtrCast(requestor->parent, ZafTableRecord);
				if (record)
				{
					// The requestor's full path is not guaranteed to be unique
					// within the table, due to duplication of records.
					ZafTable *table = record->Table();
					table->SetCurrentOffset(recordOffset);
					record = table->Current();
					record->Get(fieldOffset)->StringData()->SetText(editField->Text());

					// Mark the record as changed, otherwise the WriteRecord()
					// method will not write out the modified data.
					record->SetChanged(true);

					// Write the modified text to the parent table's data array.
					table->WriteRecord(*record, ZAF_NULLP(ZafWindowObject));

					// The ZAF Designer uses ZafTableRecord::DeactivateObject() to
					// store the modified value of the record field (selector).
					record->DeactivateObject(requestor);
				}
				else
					// Set the requestor text data (normal edit field).
					requestor->StringData()->SetText(editField->Text());
			}

			// Reset the requestor.
			requestor = ZAF_NULLP(ZafWindowObject);
		}

		// Reset the default event route.
		windowManager->SetDefaultEventRoute(this);
		break;

	case S_CREATE:
		// Adjust the string editor region based on the map field width and height.
		ccode = ZafDialogWindow::Event(event);
		if (!adjusted)
		{
			adjusted = true;
			int hzDelta = clientRegion.Width() - mapField->zafRegion.Width() - 2 * display->cellWidth;
			int vtDelta = clientRegion.Height() - mapField->zafRegion.Height() - 3 * display->cellHeight - 5;
			zafRegion.right -= hzDelta;
			zafRegion.bottom -= vtDelta;
			OSSize();
		}
		break;

	case N_NON_CURRENT:
		// May need to update members upon losing focus.
		ccode = ZafDialogWindow::Event(event);
		break;

	case OPT_PUT_KEY:
		{
		// Update the edit field with the selected character.
		editField->Event(event);

		// Notify the user of the last character selected.
		unsigned char value = (unsigned char)event.rawCode;
		ZafIChar lastChar[16];
		sprintf(lastChar, ZAF_ITEXT("0x%02x%02x"), page, value);
		charField->SetText(lastChar);
		}
		break;

	case S_DECREMENT:
		// If the page value is not zero, decrement it and update the character map.
		if (page > 0x00)
		{
			page--;
			mapField->Event(S_REDISPLAY);
		}
		break;

	case S_INCREMENT:
		// If the page value is not 255, increment it and update the character map.
		if (page < 0xFF)
		{
			page++;
			mapField->Event(S_REDISPLAY);
		}
		break;

	default:
		if (windowManager->DefaultEventRoute() && 
			windowManager->DefaultEventRoute() == this)
			ccode = S_UNKNOWN;
		else
			ccode = ZafDialogWindow::Event(event);
		break;
	}

	return (ccode);
}

// The fullPathID buffer is allocated by the caller.
void ZafStringEditor::FullPathID(ZafIChar *fullPathID, ZafWindowObject *object)
{
	if (!fullPathID || !object)
		return;

	ZafIChar tempPathID[ZAF_MAXPATHLEN];
	strcpy(fullPathID, object->StringID());
	object = object->parent;
	while (object)
	{
		strcpy(tempPathID, fullPathID);
		sprintf(fullPathID, ZAF_ITEXT("%s~%s"), object->StringID(),
			tempPathID);
		object = object->parent;
	}
}

ZafLogicalEvent ZafStringEditor::LogicalEvent(const ZafEventStruct &event)
{
	ZafLogicalEvent ccode = MapEvent(defaultEventMap, event);
	return ((ccode == L_NONE) ? ZafWindow::LogicalEvent(event) : ccode);
}

void ZafStringEditor::SetRequestor(ZafWindowObject *setRequestor)
{
	// Update the string editor for a new requestor.
	if (setRequestor)
	{
		// Save the pathID for the requestor.
		FullPathID(requestID, setRequestor);

		// Set the edit field's stringData to match that of the requestor.
		editField->StringData()->SetText(setRequestor->Text(),
			setRequestor->StringData()->MaxLength());

		// Make sure the edit field is current.
		SetCurrent(editField);

		// Activate the string editor.
		*windowManager + this;
	}
}

void ZafStringEditor::SetPage(int setPage)
{
	// Change the page value.
	if (page != setPage)
	{
		page = setPage;
		mapField->Event(S_REDISPLAY);
	}
}

// --------------------------------------------------------------------------
// ----- HexInteger ---------------------------------------------------------
// --------------------------------------------------------------------------

HexInteger::HexInteger(int left, int top, int width) :
	ZafInteger(left, top, width, new ZafIntegerData((long)0))
{
	// Currently, the input format and output format need to be
	// the same for handling hexadecimal input and output.
	// The value '0xff' becomes '0x100' on increment, then '0x10'
	// when calling ZafInteger::Increment() for
	//    inputFormat = "%2x" and
	//    outputFormat = "0x%02x".
	// The reverse of this problem occurs on decrement.
	// i.e. the value '0x00' becomes '0xffffffff'.
	SetRange(ZAF_ITEXT("0x00..0xff"));
	SetInputFormat(ZAF_ITEXT("%x"));
	SetOutputFormat(ZAF_ITEXT("0x%x"));
}

ZafError HexInteger::Decrement(ZafData *data, bool wrapData)
{
	// Decrement the hex integer value and validate it.
	ZafError error = ZafInteger::Decrement(data, wrapData);
	if (error == ZAF_ERROR_NONE)
	{
		ZafWindowObject *root = RootObject();
		ZafEventStruct decEvent(S_DECREMENT);
		root->Event(decEvent);
	}
    return (error);
}

ZafEventType HexInteger::Event(const ZafEventStruct &event)
{
	// Check for Zinc specific messages.
	ZafEventType ccode = LogicalEvent(event);
	switch (ccode)
	{
	case L_SELECT:
		ccode = ZafInteger::Event(event);
		((ZafStringEditor *)RootObject())->SetPage(Value());
		eventManager->Put(L_PREVIOUS); // Move to the string field.
		break;

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

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

ZafError HexInteger::Increment(ZafData *data, bool wrapData)
{
	// Increment the hex integer value and validate it.
	ZafError error = ZafInteger::Increment(data, wrapData);
	if (error == ZAF_ERROR_NONE)
	{
		ZafWindowObject *root = RootObject();
		ZafEventStruct incEvent(S_INCREMENT);
		root->Event(incEvent);
	}
    return (error);
}

// --------------------------------------------------------------------------
// ----- EditText -----------------------------------------------------------
// --------------------------------------------------------------------------

EditText::EditText(int left, int top, int width, int height) :
	ZafText(left, top, width, height, ZAF_NULLP(ZafStringData))
{
	SetAutoClear(false);
}

ZafEventType EditText::Event(const ZafEventStruct &event)
{
	// Check for Zinc specific messages.
	ZafEventType ccode = event.type;
	switch (ccode)
	{
	case OPT_PUT_KEY:
		// Process characters selected from character map.
		{
			// Determine amount of selected text to replace, if any.
			bool replaceText = false;
			int startSelect, endSelect;
#if defined(ZAF_MSWINDOWS)
#	if defined(ZAF_WIN32)
			SendMessage(screenID, EM_GETSEL, (WPARAM)&startSelect, (LPARAM)&endSelect);
#	else
			startSelect = LOWORD(SendMessage(screenID, EM_GETSEL, 0, 0));
			endSelect = HIWORD(SendMessage(screenID, EM_GETSEL, 0, 0));
#	endif
#elif defined(ZAF_MOTIF)
			XmTextGetSelectionPosition(screenID, (XmTextPosition *)&startSelect,
				(XmTextPosition *)&endSelect);
#elif defined(ZAF_INDEPENDENT)
			startSelect = ZafMin(OffsetMarkAnchor(), OffsetMarkFloat());
			endSelect = ZafMax(OffsetMarkAnchor(), OffsetMarkFloat());
			startSelect = TextOffset(startSelect);
			endSelect = TextOffset(endSelect);
#endif
			if (endSelect != startSelect)
				replaceText = true;

			// Sync internal string data with OS, before inserting
			// selected character.
			OSGetText();
			int oldOffset = CursorOffset();

			// Don't insert if buffer is full.
			if (!replaceText && (int)strlen(stringData->Text()) == stringData->MaxLength())
				break;

			// Insert the selected character.
			if (replaceText)
				stringData->Replace(startSelect, endSelect - startSelect, &event.key.value, 1);
			else
				stringData->Insert(oldOffset, &event.key.value, 1);

			// Sync OS with modified string data and update cursor position.
			OSSetText();
			if (replaceText)
				SetCursorOffset(startSelect + 1);
			else
				SetCursorOffset(oldOffset + 1);

			// Mark the edit field as changed.
			SetChanged(true);
		}
		break;

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

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

// --------------------------------------------------------------------------
// ----- Character Map ------------------------------------------------------
// --------------------------------------------------------------------------

CharacterMap::CharacterMap(int left, int top) :
	ZafWindowObject(left, top, 32, 8)
{
	SetOSDraw(false);
	SetNoncurrent(true);
}

ZafEventType CharacterMap::Draw(const ZafEventStruct &, ZafEventType ccode)
{
	ZafRegionStruct drawRegion = BeginDraw();

	// Clear the background before drawing the character map.
	display->SetForeground(ZAF_CLR_BLACK);
	display->SetBackground(ZAF_CLR_WHITE);
	display->Rectangle(drawRegion, 1, true);

	// Draw the grid lines.
	int i, j, column, line;
	for (i = 0, line = drawRegion.top + cellHeight; i < 7; i++, line += cellHeight)
 		display->Line(drawRegion.left, line, drawRegion.right, line);
	for (j = 0, column = drawRegion.left + cellWidth; j < 31; j++, column += cellWidth)
		display->Line(column, drawRegion.top, column, drawRegion.bottom);

	// Draw the selectable characters.
	ZafIChar value[2];
	value[1] = 0;
	int page = ((ZafStringEditor *)RootObject())->Page();
	for (i = 0, line = drawRegion.top; i < 8; i++, line += cellHeight)
	{
		value[0] = (page << 8) + i;
		for (j = 0, column = drawRegion.left; j < 32; j++, column += cellWidth, value[0] += 0x08)
			display->Text(column, line, column + cellWidth, line + cellHeight,
				value, 1, ZAF_HZ_CENTER, ZAF_VT_CENTER);
	}

	EndDraw();

	return (ccode);
}

ZafEventType CharacterMap::Event(const ZafEventStruct &event)
{
	// Switch on the event type.
	ZafEventType ccode = LogicalEvent(event);
	switch (ccode)
	{
	case S_COMPUTE_SIZE:
		{
		// Calculate the minimum region necessary to draw the character map.
		ccode = ZafWindowObject::Event(event);

		// A display context is necessary for calculating text size.
		BeginDraw();

		// Calculate the minimum cell width and height necessary to display each font character.
		ZafRegionStruct vtRegion = display->TextSize(ZAF_ITEXT("Wy"));
		cellHeight = vtRegion.Height() + 3;
		cellWidth = display->cellWidth * 2;

		// Free the display context.
		EndDraw();

		// Adjust the zafRegion using the cellWidth and cellHeight.
		zafRegion.right = zafRegion.left + 32 * cellWidth; // 32 cells wide.
		zafRegion.bottom = zafRegion.top + 8 * cellHeight; // 8 cells high.
		}
		break;

	case L_BEGIN_SELECT:
		{
		// Process a mouse click inside the character map.
		ZafWindowObject::Event(event);
		ZafPositionStruct position = event.position;
		int column = position.column / cellWidth;
		int line = position.line / cellHeight;
		if (column > 0x1F || line > 0x07)
			break;
		int page = ((ZafStringEditor *)RootObject())->Page();
		ZafEventStruct keyEvent(OPT_PUT_KEY);
		keyEvent.rawCode = (page << 8) + (column << 3) + line;
		keyEvent.key.value = keyEvent.rawCode;
		keyEvent.key.shiftState = 0;
		eventManager->Put(keyEvent);
		}
		break;

	case L_CONTINUE_SELECT:
	case L_END_SELECT:
		// We only process the down-click of the mouse. 
		break;

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

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

