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

#include <z_htips.hpp>
#include <z_mouse1.hpp>
#include <z_win.hpp>
#include <z_string.hpp>
#include <z_stdio.hpp>

extern "C"
{
#include _FrameP_
#include _LabelP_
}

bool processDisplay = false;

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

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 };

void DisplayTimerCallback(ZafHelpTips *helpTips, XtIntervalId *)
{
	helpTips->Event(DH_HELP_TIPS_TIMER);
}

void ZafHelpTips::DisplayHelpObjectTip(void)
{
	// Check for a valid mouse object.
	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)
		{
			const ZafIChar *newReplaceText = helpObject->Text();
			replaceText = newReplaceText ? strdup(newReplaceText) : ZAF_NULLP(ZafIChar);
		}
		helpObject->SetText(helpObjectTip);
	}
}

void ZafHelpTips::DisplayQuickTip(void)
{
	// Make sure there is quick-tip text.
	ZafWindowObject *mouseObject = zafWindowManager->mouseObject;
	if (!mouseObject || !mouseObject->QuickTip() || !mouseObject->screenID)
		return;

	if (helpTipsType == ZAF_HELPTIPS_QUICKTIP || helpTipsType == ZAF_HELPTIPS_BOTH)
	{
		// Create the new quick-tip window.
		if (quickTipWindow)
			XDestroyWindow(((ZafScreenDisplay *)display)->xDisplay, quickTipWindow);
		Widget rootWidget = mouseObject->RootObject()->OSScreenID(ZAF_FRAMEID);

		const ZafIChar *quickTip = mouseObject->QuickTip();
		quickTipPalette = userPalette ? *userPalette : zincPalette;
		if (display->DisplayMode() == ZAF_DISPLAY_MONO)
			quickTipWindow = XCreateSimpleWindow(((ZafScreenDisplay *)display)->xDisplay, XtWindow(rootWidget),
				0, 0, rootWidget->core.width, rootWidget->core.height, 1,
				((ZafScreenDisplay *)display)->monoTable[quickTipPalette.monoForeground].pixel,
				((ZafScreenDisplay *)display)->monoTable[quickTipPalette.monoBackground].pixel);
		else
			quickTipWindow = XCreateSimpleWindow(((ZafScreenDisplay *)display)->xDisplay, XtWindow(rootWidget),
				0, 0, rootWidget->core.width, rootWidget->core.height, 1,
				((ZafScreenDisplay *)display)->colorTable[quickTipPalette.colorForeground].pixel,
				((ZafScreenDisplay *)display)->colorTable[quickTipPalette.colorBackground].pixel);

		// Use the widget as the basis for text calculations.
		display->SetPalette(quickTipPalette);
		ZafRegionStruct drawRegion = display->TextSize(quickTip);

		// Now, size the window.
		Widget widget = mouseObject->OSScreenID(ZAF_FRAMEID);
		XWindowChanges changes;
		changes.x = widget->core.width / 2;
		changes.y = widget->core.height;
		changes.width = drawRegion.Width() + 2 * ZAF_BORDER_SIZE;
		changes.height = drawRegion.Height() + 2 * ZAF_BORDER_SIZE;
		for (Widget temp = widget; temp != rootWidget; temp = XtParent(temp))
		{
			changes.x += temp->core.x;
			changes.y += temp->core.y;
		}
		// check for out-of-bounds.
		if (changes.x + changes.width > rootWidget->core.width - ZAF_BORDER_SIZE)
			changes.x = rootWidget->core.width - changes.width - ZAF_BORDER_SIZE;
		if (changes.x < ZAF_BORDER_SIZE)
			changes.x = ZAF_BORDER_SIZE;
		if (changes.y + changes.height > rootWidget->core.height - ZAF_BORDER_SIZE)
			changes.y -= widget->core.height + changes.height + ZAF_BORDER_SIZE;
		if (changes.y < ZAF_BORDER_SIZE)
			changes.y = ZAF_BORDER_SIZE;
		XConfigureWindow(((ZafScreenDisplay *)display)->xDisplay, quickTipWindow, CWX |CWY | CWWidth | CWHeight, &changes);

		// Render the quick-tip window to the display.
		XMapWindow(((ZafScreenDisplay *)display)->xDisplay, quickTipWindow);

		// Draw the quick-tip information within the window.
		display->BeginDraw(((ZafScreenDisplay *)display)->xDisplay, quickTipWindow, drawRegion, drawRegion);
		drawRegion -= ZAF_BORDER_SIZE;
		display->SetPalette(quickTipPalette);
		display->Text(drawRegion, quickTip, -1, ZAF_HZ_LEFT, ZAF_VT_TOP);
		display->EndDraw();
	}

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

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

ZafEventType ZafHelpTips::Event(const ZafEventStruct &event)
{
	ZafEventType ccode = event.type;
	switch (ccode)
	{
	case D_ON:
	case D_OFF:
		deviceState = ccode;
		break;

	case D_STATE:
		ccode = DeviceState();
		break;

	case D_INITIALIZE:
		quickTipPalette = zincPalette;
		break;

	case D_DEINITIALIZE:
		Reset(displayTimerHandle, false, 0, ZAF_NULLP(ZafEventStruct));
		break;

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

		// Update lastTime.
		ZafWindowObject *mouseObject = zafWindowManager->mouseObject;
		if (mouseObject && mouseObject->QuickTip())
			lastTime = event.osEvent.xcrossing.time;

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

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

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

		// Elapsed time in milliseconds.
		int elapsedTime = (int)event.osEvent.xcrossing.time - (int)lastTime;

		// Display the quick tip if necessary.
		ZafWindowObject *mouseObject = zafWindowManager->mouseObject;
		if (allowQuickSiblingDisplay && mouseObject &&
			mouseObject->QuickTip() && elapsedTime < newHelpTipDelay)
		{
			// 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 DH_HELP_TIPS_TIMER:
		if (!quickTipVisible && processDisplay)
			DisplayQuickTip();
		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;

	default:
		ccode = S_UNKNOWN;
		break;
	}

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

void ZafHelpTips::Poll(void)
{
	if (DeviceState() == D_OFF)
		return;
	
	// Don't process mouse messages if we are not the active application.
	if (!zafWindowManager->First() || !zafWindowManager->First()->Focus())
		return;

	// If the quick tip is currently displayed and a mouse click or 
	// keystroke occurred, remove the quick tip.  Disable timer on all 
	// other mouse clicks and keystrokes.
	ZafEventStruct event;
	if (eventManager->Get(event, Q_NO_BLOCK | Q_BEGIN | Q_NO_POLL | Q_NO_DESTROY) == 0 && event.type == E_MOTIF &&
		(event.osEvent.type == KeyPress || event.osEvent.type == ButtonPress || event.osEvent.type == FocusIn))
	{
		if (quickTipVisible)
		{
			RemoveHelpTip();
			processDisplay = false;
		}
		else
			Reset(displayTimerHandle, false, 0, ZAF_NULLP(ZafEventStruct));
	}

	// 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 && event.osEvent.type == MotionNotify)
		Reset(displayTimerHandle, true, initialDelay, &event);

	// If the quick tip is displayed and the mouse moves, update the time of the 
	// movement so can display on a sibling if the mouse moves to it quickly
	// enough.
	else if (quickTipVisible && event.osEvent.type == MotionNotify)
		lastTime = event.osEvent.xmotion.time;
}

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 (quickTipWindow && 
		(helpTipsType == ZAF_HELPTIPS_QUICKTIP || helpTipsType == ZAF_HELPTIPS_BOTH))
	{
		XDestroyWindow(((ZafScreenDisplay *)display)->xDisplay, quickTipWindow);
		quickTipWindow = 0;
	}

	// 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)
	{
		XtRemoveTimeOut(timerHandle);
		timerHandle = 0;
	}

	// Restart the timer and get the current mouse position.  We need to keep
	// track of the mouse position now, because the mouse won't be moved again
	// if the quick tip is to be displayed.

	// Update lastMousePosition and lastTime.
	if (event && zafWindowManager->mouseObject && zafWindowManager->mouseObject->QuickTip())
	{
		if (event->osEvent.type == MotionNotify)
		{
			lastMousePosition.column = event->osEvent.xmotion.x_root;
			lastMousePosition.line = event->osEvent.xmotion.y_root;

			// Remember when this mouse movement occurred so can display a 
			// sibling's quick tip without delay if move quickly enough from object
			// to object.
			lastTime = event->osEvent.xmotion.time;
		}
		else if (event->osEvent.type == EnterNotify)
		{
			lastMousePosition.column = event->osEvent.xcrossing.x_root;
			lastMousePosition.line = event->osEvent.xcrossing.y_root;

			// Remember when this mouse movement occurred so can display a 
			// sibling's quick tip without delay if move quickly enough from object
			// to object.
			lastTime = event->osEvent.xcrossing.time;
		}
	}

	// Reset the timer.
	if (resetTimer)
		timerHandle = XtAppAddTimeOut(((ZafScreenDisplay *)display)->xAppContext, interval,
			(XtTimerCallbackProc)DisplayTimerCallback, (XtPointer)this);
}


