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

#include <time.h>
#include <z_htips.hpp>
#include <z_mouse1.hpp>
#include <z_string.hpp>
#include <z_scrdsp.hpp>
#include <z_timer.hpp>
#include <z_utils.hpp>
#include <z_win.hpp>

#define processDisplay zafHelpTips_processDisplay
extern bool processDisplay;

static ZafPaletteStruct zincPalette =
	{ ZAF_LINE_SOLID, ZAF_PTN_SOLID_FILL, ZAF_CLR_BLACK, ZAF_CLR_YELLOW,
		ZAF_MONO_BLACK, ZAF_MONO_WHITE, ZAF_FNT_SMALL };

// ----- ZafHelpTips -------------------------------------------------------

void ZafHelpTips::DisplayHelpObjectTip()
{
	ZafWindowObject *mouseObject = zafWindowManager->mouseObject;
	if (!mouseObject)
		return;

	// Update the help object's text.
	const ZafIChar *helpObjectTip = mouseObject->HelpObjectTip();
	ZafWindowObject *helpObject = zafWindowManager->helpObject;
	if (helpObject && helpObjectTip && (helpTipsType == ZAF_HELPTIPS_HELPOBJECT ||
		helpTipsType == ZAF_HELPTIPS_BOTH))
	{
		// If replaceText, it means we got 2 N_MOUSE_ENTERS in a row, without
		// an N_MOUSE_LEAVE in between.  So don't replace replaceText, keep 
		// what it had since that's really what's supposed to go back.
		if (replaceText)
			;
		else
			replaceText = helpObject->Text() ? strdup(helpObject->Text()) : ZAF_NULLP(ZafIChar);
		helpObject->SetText(helpObjectTip);
	}
}

void ZafHelpTips::DisplayQuickTip()
{
	ZafWindowObject *mouseObject = zafWindowManager->mouseObject;
	if (!mouseObject)
		return;

	// Display the quick tip.
	ZafIChar *quickTip;
	if (mouseObject->QuickTip())
 		quickTip = strdup(mouseObject->QuickTip());
	else
		return;

	if (helpTipsType == ZAF_HELPTIPS_QUICKTIP || helpTipsType == ZAF_HELPTIPS_BOTH)
	{
		if (quickTipVisible)
			return;

		// Repair the damage so the quicktip doesn't get draw on.
		zafWindowManager->RepairDamage();

		// Begin drawing operation.
		ZafDisplay *display = this->display;
		ZafRegionStruct drawRegion;
		drawRegion.coordinateType = ZAF_PIXEL;
		drawRegion.left = drawRegion.top = 0;
		drawRegion.right = display->columns - 1;
		drawRegion.bottom = display->lines - 1;
		display->BeginDraw(0, ID_ZAF_DIRECT, drawRegion, drawRegion);

		// Calculate position and size of quick tip using last mouse position
		// obtained with the last mouse movement.
		display->SetPalette(quickTipPalette);

		int height = 0, width = 0;
		bool multiline = false;

		if (!strchr(quickTip, '\n'))
		{
			ZafRegionStruct textSize = display->TextSize(quickTip);
			height = textSize.Height(); 
			width = textSize.Width(); 
		}
		else
		{
			multiline = true;
			ZafIChar *startLine = quickTip, *endLine;

			bool finished = false;
			do
			{
				endLine = strchr(startLine, '\n');
				if (!endLine)
					finished = true;
				else
				{
					// Remove the new line so can parse the string.
					*endLine = '\0';
				}

				// Get the string size.
				ZafRegionStruct textSize = display->TextSize(startLine);
				width = ZafMax(textSize.Width(), width);
				height += textSize.Height();

				// Replace the new line.
				if (!finished)
				{
					*endLine = '\n';
					startLine = ++endLine;
				}
			}while (!finished);
		}

		height += 2 * display->preSpace;
		width += 2 * display->cellWidth;

		drawRegion.left = lastMousePosition.column;
		drawRegion.top = lastMousePosition.line + ZAF_HELPTIPS_VT_OFFSET;
		ZafClassID objectID = mouseObject->ClassID();
		if (objectID == ID_ZAF_STRING || objectID == ID_ZAF_TEXT)
			drawRegion.top -= ZAF_HELPTIPS_VT_OFFSET / 2;
		drawRegion.right = drawRegion.left + width;
		drawRegion.bottom = drawRegion.top + height;

		// Adjust the drawRegion to not go beyond the right side of the display.
		if (drawRegion.right > (display->columns - 1))
		{
			drawRegion.right = display->columns - 1;
			drawRegion.left = drawRegion.right - width;
		}

		// Adjust the drawRegion to not go beyond the bottom of the display.
		if (drawRegion.bottom > (display->lines - 1))
		{
			drawRegion.bottom = display->lines - 1;
			drawRegion.top = drawRegion.bottom - height;
		}

		// Save the region under the quick tip.
		quickTipRegion = drawRegion;

		// Draw the quick tip.
		display->Rectangle(drawRegion, 1, true);

		if (!multiline)
			display->Text(drawRegion.left, drawRegion.top, drawRegion.right, drawRegion.bottom,
				quickTip, strlen(quickTip), ZAF_HZ_CENTER, ZAF_VT_CENTER);
		else
		{
			ZafIChar *startLine = quickTip, *endLine;
			drawRegion.top += display->preSpace;
			drawRegion.left += display->cellWidth;

			bool finished = false;
			do
			{
				endLine = strchr(startLine, '\n');
				if (!endLine)
					finished = true;
				else
				{
					// Remove the new line so can parse the string.
					*endLine = '\0';
				}

				// Get the string size.
				ZafRegionStruct textSize = display->TextSize(startLine);
				height = textSize.Height();

				display->Text(drawRegion.left, drawRegion.top, drawRegion.right, drawRegion.bottom,
					startLine, -1, ZAF_HZ_LEFT, ZAF_VT_TOP);

				drawRegion.top += height;
		
				// Replace the new line.
				if (!finished)
				{
					*endLine = '\n';
					startLine = ++endLine;
				}
			}while (!finished);
		}
	
		// End drawing operation.
		display->EndDraw();
	}

	// Indicate that the quick tip is visible.
	processDisplay = true;
	quickTipVisible = true;
	allowQuickSiblingDisplay = true;

	// Turn off the timer.
	Reset(displayTimerHandle, false, 0, ZAF_NULLP(ZafEventStruct));

	// Delete the allocated string.
	delete []quickTip;
}

ZafEventType ZafHelpTips::Event(const ZafEventStruct &event)
{
	ZafEventType ccode = event.type;
	switch (ccode)
	{
	case D_INITIALIZE:
		// Create the timer.
		displayTimerHandle = new ZafTimer(D_OFF, initialDelay);
		displayTimerHandle->SetQueueEvents(true);
		displayTimerHandle->SetNotifyMessage(N_TIMER_PRIVATE);
		eventManager->Add(displayTimerHandle);

		// Initialize the palette.
		quickTipPalette = zincPalette;
		break;

	case D_DEINITIALIZE:
		// Kill the timer.
		Reset(displayTimerHandle, false, 0, ZAF_NULLP(ZafEventStruct));
		eventManager->Subtract(displayTimerHandle);
		delete displayTimerHandle;
		displayTimerHandle = ZAF_NULLP(ZafTimer);
		break;

	case D_ON:
	case D_OFF:
		deviceState = ccode;
		if (displayTimerHandle)
			displayTimerHandle->SetDeviceState(ccode);
		break;

	case D_STATE:
		ccode = DeviceState();
		break;

	case DH_SET_HELP_OBJECT:
		SetHelpObject(event.windowObject);
		break;

	case DH_UPDATE_HELP_OBJECT:
		{
		ZafWindowObject *object = event.windowObject;
		ZafWindowObject *helpObject = zafWindowManager->helpObject;
		if (deviceState == D_ON && helpObject && (helpTipsType == ZAF_HELPTIPS_HELPOBJECT ||
			helpTipsType == ZAF_HELPTIPS_BOTH))
			helpObject->SetText(object ? object->HelpObjectTip() : replaceText);
		}	
		break;

	case N_MOUSE_ENTER:
		{
		if (deviceState != D_ON)
			break;

		// Update the help object's text.
		DisplayHelpObjectTip();

		// Display the quick tip if necessary.
		ZafWindowObject *mouseObject = zafWindowManager->mouseObject;
		if (allowQuickSiblingDisplay && mouseObject && mouseObject->QuickTip())
		{
			// Set the mouse position.
			ZafEventStruct tEvent = event;
			Reset(displayTimerHandle, true, initialDelay, &tEvent);

			// Display the quick tip.
			DisplayQuickTip();
		}
		else
		{
			// Indicate that we're trying to display the quick tip.
			processDisplay = true;
			quickTipVisible = false;
			allowQuickSiblingDisplay = false;

			// Set the timer and mouse position.
			ZafEventStruct tEvent = event;
			Reset(displayTimerHandle, true, initialDelay, &tEvent);
		}
		}
		break;

	case N_MOUSE_LEAVE:
		{
		// Remove quick tip and update help object's text.
		RemoveHelpTip();

		// Indicate that no longer trying to display quick tip.
		processDisplay = false;

		// Set the timer and mouse position.
		ZafEventStruct tEvent = event;
		Reset(displayTimerHandle, true, newHelpTipDelay, &tEvent);
		}
		break;

	default:
		ccode = S_UNKNOWN;
		break;
	}

	return (ccode);
}

void ZafHelpTips::Poll(void)
{
	if (DeviceState() == D_OFF || !(processDisplay || allowQuickSiblingDisplay))
		return;

	// Get the next event (without destroying it) to see if we need
	// to do anything.
	ZafEventStruct event;
	eventManager->Get(event, Q_NO_BLOCK | Q_BEGIN | Q_NO_DESTROY | Q_NO_POLL);

	// If the quick tip is currently displayed and a mouse click or keystroke 
	// occurred, remove the quick tip.
	bool mouseMoved = (event.InputType() == E_MOUSE && !event.rawCode);
	if (event.rawCode == (M_LEFT | M_LEFT_CHANGE) ||
		event.rawCode == (M_MIDDLE | M_MIDDLE_CHANGE) ||
		event.rawCode == (M_RIGHT | M_RIGHT_CHANGE) ||
		event.InputType() == E_KEY)
	{
		// If the quick tip is currently displayed and a mouse click or
		// keystroke occurred, remove the quick tip.
		if (quickTipVisible)
			RemoveHelpTip();
		else
			Reset(displayTimerHandle, false, 0, ZAF_NULLP(ZafEventStruct));
		processDisplay = false;
		allowQuickSiblingDisplay = false;
	}

	// If the quick tip is not displayed, but the mouse is moving over the
	// object and we're trying to display the quick tip, reset the timer and 
	// mouse location.
	else if (!quickTipVisible && processDisplay && mouseMoved)
		Reset(displayTimerHandle, true, initialDelay, &event);

	// If the timer expired, and waiting to display a quick tip 
	// (processDisplay == true) display the quick tip.
	else if (!quickTipVisible && processDisplay && event.type == N_TIMER_PRIVATE &&
		event.device == displayTimerHandle)
	{
		DisplayQuickTip();
		eventManager->Get(event, Q_NO_BLOCK | Q_BEGIN | Q_NO_POLL);
	}

	// If the timer expired, and waiting for N_MOUSE_ENTER 
	// (allowQuickSiblingDisplay == true) turn off allowQuickSiblingDisplay
	// and kill the timer.
	else if (!quickTipVisible && allowQuickSiblingDisplay && event.type == N_TIMER_PRIVATE &&
		event.device == displayTimerHandle)
	{
		allowQuickSiblingDisplay = false;
		Reset(displayTimerHandle, false, initialDelay, ZAF_NULLP(ZafEventStruct));
		eventManager->Get(event, Q_NO_BLOCK | Q_BEGIN | Q_NO_POLL);
	}
}

void ZafHelpTips::RemoveHelpTip(void)
{
	// Restore the help object's text.
	ZafWindowObject *helpObject = zafWindowManager->helpObject;
	if (helpObject && (helpTipsType == ZAF_HELPTIPS_HELPOBJECT || helpTipsType == ZAF_HELPTIPS_BOTH))
	{
	   	helpObject->SetText(replaceText);
		if (replaceText)
		{
			delete []replaceText;
			replaceText = ZAF_NULLP(ZafIChar);
		}
	}

	// Restore the screen image under the quick tip.
	if ((helpTipsType == ZAF_HELPTIPS_QUICKTIP || helpTipsType == ZAF_HELPTIPS_BOTH) &&
		quickTipVisible)
	{
		ZafEventStruct updateEvent(S_REDISPLAY_REGION);
		updateEvent.region = quickTipRegion;
		zafWindowManager->Event(updateEvent);
	}

	// Indicate that the quick tip is not displayed.
	quickTipVisible = false;

	// Turn off the timer.
	Reset(displayTimerHandle, false, 0, ZAF_NULLP(ZafEventStruct));
}

void ZafHelpTips::Reset(OSTimerHandle &timerHandle, bool resetTimer, unsigned long interval, ZafEventStruct *event)
{
	// Kill the timer.
	if (timerHandle)
		timerHandle->Event(D_OFF);

	// Restart the timer and get the current mouse position. We need to keep
	// track of the mouse position now, because the quick tip won't be
	// displayed if the mouse moves again.
	if (resetTimer)
	{
		if (event)
			lastMousePosition.Assign(event->osEvent.position.column, event->osEvent.position.line, zafWindowManager->zafRegion.coordinateType);

		if (timerHandle)
		{
			timerHandle->Event(D_ON);
			if (zafWindowManager->mouseObject)
				timerHandle->SetInterval(interval);
		}
	}
}
