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

#define OEMRESOURCE			// Windows button messages and flags.
#include <w_app.hpp>
#include <z_htips.hpp>
#include <z_ctype.hpp>
#include <z_mouse1.hpp>
#include <z_string.hpp>
#include <z_utils.hpp>

bool processDisplay = false;

#define ZAF_HELP_TIPS_CLASSA		"ZafHelpTips"
#define ZAF_HELP_TIPS_CLASSW		L"ZafHelpTips"
#if defined(ZAF_UNICODE)
#	define ZAF_HELP_TIPS_CLASS		ZAF_HELP_TIPS_CLASSW
#else
#	define ZAF_HELP_TIPS_CLASS		ZAF_HELP_TIPS_CLASSA
#endif

LRESULT CALLBACK HelpTipsWndProc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam);

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

static ZafPaletteStruct systemPalette =
	{ ZAF_LINE_SOLID, ZAF_PTN_SOLID_FILL, ZAF_CLR_DEFAULT, ZAF_CLR_DEFAULT,
		ZAF_MONO_BLACK, ZAF_MONO_WHITE, ZAF_FNT_SMALL };
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 ZafHelpTips::DisplayHelpObjectTip(void)
{
	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(void)
{
	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)
	{
		ZafDisplay *display = this->display;
		if ((ZafMSWindowsApp::windowsPlatform == ZAF_WIN32_WINDOWS) ||
			(ZafMSWindowsApp::windowsPlatform == ZAF_WIN32_NT && ZafMSWindowsApp::windowsVersion >= 400))
		{
			quickTipPalette.osPalette.colorForeground = GetSysColor(COLOR_INFOTEXT);
			quickTipPalette.osPalette.colorBackground = GetSysColor(COLOR_INFOBK);
		}

		// Calculate quick tip region.
		quickTipRegion.coordinateType = ZAF_PIXEL;
		quickTipRegion.left = quickTipRegion.top = 0;
		quickTipRegion.right = display->columns - 1;
		quickTipRegion.bottom = display->lines - 1;
		display->BeginDraw(0, ID_ZAF_SCREEN, quickTipRegion, quickTipRegion);

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

		if (!strchr(quickTip, '\n'))
		{
			ZafRegionStruct textSize = display->TextSize(quickTip);
			height = textSize.Height(); 
			width = textSize.Width(); 
		}
		else
		{
			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;

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

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

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

		display->EndDraw();

		HWND hwndOwner = mouseObject->RootObject()->OSScreenID(ZAF_FRAMEID);
#if defined(ZAF_WIN32)
		if (ZafMSWindowsApp::convertText)
			hwnd = CreateWindowEx(WS_EX_TOPMOST, ZAF_HELP_TIPS_CLASSA, NULL, WS_POPUP,
				quickTipRegion.left, quickTipRegion.top, quickTipRegion.Width(), quickTipRegion.Height(),
				hwndOwner, 0, ZafMSWindowsApp::hInstance, NULL);
		else
			hwnd = CreateWindowEx(WS_EX_TOPMOST, ZAF_HELP_TIPS_CLASS, NULL, WS_POPUP,
				quickTipRegion.left, quickTipRegion.top, quickTipRegion.Width(), quickTipRegion.Height(),
				hwndOwner, 0, ZafMSWindowsApp::hInstance, NULL);
#else
		hwnd = CreateWindowEx(WS_EX_TOPMOST, ZAF_HELP_TIPS_CLASSA, NULL, WS_POPUP,
			quickTipRegion.left, quickTipRegion.top, quickTipRegion.Width(), quickTipRegion.Height(),
			hwndOwner, 0, ZafMSWindowsApp::hInstance, NULL);
#endif

		// Attach address of quickTipPalette so wndProc can draw properly.
#if defined(ZAF_WIN32)
		SetWindowLong(hwnd, GWL_USERDATA, (LONG)&quickTipPalette);
#else
		SetProp(hwnd, (LPCSTR)ZafMSWindowsApp::objectLowWord, (HANDLE)LOWORD(&quickTipPalette));
		SetProp(hwnd, (LPCSTR)ZafMSWindowsApp::objectHighWord, (HANDLE)HIWORD(&quickTipPalette));
#endif
		ShowWindow(hwnd, SW_SHOWNOACTIVATE);
		UpdateWindow(hwnd);
	}

	// 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_ON:
	case D_OFF:
		deviceState = ccode;
		break;

	case D_STATE:
		ccode = DeviceState();
		break;

	case D_INITIALIZE:
		{
			// Set the palette for the quickTip.
			if ((ZafMSWindowsApp::windowsPlatform == ZAF_WIN32_WINDOWS) ||
				(ZafMSWindowsApp::windowsPlatform == ZAF_WIN32_NT && ZafMSWindowsApp::windowsVersion >= 400))
				quickTipPalette = systemPalette;
			else
				quickTipPalette = zincPalette;

#if defined(ZAF_WIN32)
			if (ZafMSWindowsApp::convertText)
			{
				WNDCLASSA wndClass;
				wndClass.style = CS_SAVEBITS;
				wndClass.lpfnWndProc = HelpTipsWndProc;
				wndClass.cbClsExtra = 0;
				wndClass.cbWndExtra = 0;
				wndClass.hInstance = ZafMSWindowsApp::hInstance;
				wndClass.hIcon = 0;
				wndClass.hCursor = 0;
				wndClass.hbrBackground = 0;
				wndClass.lpszMenuName = 0;
				wndClass.lpszClassName = ZAF_HELP_TIPS_CLASSA;

				// Register the help tip window if necessary.
				if (!GetClassInfoA(ZafMSWindowsApp::hInstance, ZAF_HELP_TIPS_CLASSA, &wndClass))
					RegisterClassA(&wndClass);
			}
			else
			{
				WNDCLASS wndClass;
				wndClass.style = CS_SAVEBITS;
				wndClass.lpfnWndProc = HelpTipsWndProc;
				wndClass.cbClsExtra = 0;
				wndClass.cbWndExtra = 0;
				wndClass.hInstance = ZafMSWindowsApp::hInstance;
				wndClass.hIcon = 0;
				wndClass.hCursor = 0;
				wndClass.hbrBackground = 0;
				wndClass.lpszMenuName = 0;
				wndClass.lpszClassName = ZAF_HELP_TIPS_CLASS;

				// Register the help tip window if necessary.
				if (!GetClassInfo(ZafMSWindowsApp::hInstance, ZAF_HELP_TIPS_CLASS, &wndClass))
					RegisterClass(&wndClass);
			}
#else
			WNDCLASS wndClass;
			wndClass.style = CS_SAVEBITS;
			wndClass.lpfnWndProc = HelpTipsWndProc;
			wndClass.cbClsExtra = 0;
			wndClass.cbWndExtra = 0;
			wndClass.hInstance = ZafMSWindowsApp::hInstance;
			wndClass.hIcon = 0;
			wndClass.hCursor = 0;
			wndClass.hbrBackground = 0;
			wndClass.lpszMenuName = 0;
			wndClass.lpszClassName = ZAF_HELP_TIPS_CLASSA;

			// Register the help tip window if necessary.
			if (!GetClassInfo(ZafMSWindowsApp::hInstance, ZAF_HELP_TIPS_CLASSA, &wndClass))
				RegisterClass(&wndClass);
#endif
		}
		break;

	case D_DEINITIALIZE:
		// Kill the timer.
		Reset(displayTimerHandle, false, 0, ZAF_NULLP(ZafEventStruct));
#if defined(ZAF_WIN32) && defined(ZAF_UNICODE)
		UnregisterClassA(ZAF_HELP_TIPS_CLASSA, ZafMSWindowsApp::hInstance);
#else
		UnregisterClass(ZAF_HELP_TIPS_CLASSA, ZafMSWindowsApp::hInstance);
#endif
		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())
		{
			// Update mouse position.
			ZafEventStruct tEvent = event;
			Reset(displayTimerHandle, true, initialDelay, &tEvent);

			// Display the quick tip.  Also kills the timer.
			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;

	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))
			{
				// 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(object ? object->HelpObjectTip()
					: replaceText);
			}
		}	
		break;

	default:
		ccode = S_UNKNOWN;
		break;
	}

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

void ZafHelpTips::Poll(void)
{
	if (DeviceState() == D_OFF || !(processDisplay || allowQuickSiblingDisplay))
		return;
	
	// Don't process mouse messages if we are not the active application.
	if (!zafWindowManager->First()->Focus())
		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.
	if (event.osEvent.message == WM_LBUTTONDOWN ||
		event.osEvent.message == WM_MBUTTONDOWN ||
		event.osEvent.message == WM_RBUTTONDOWN ||
		event.osEvent.message == WM_NCLBUTTONDOWN ||
		event.osEvent.message == WM_NCMBUTTONDOWN ||
		event.osEvent.message == WM_NCRBUTTONDOWN ||
		event.InputType() == E_KEY)
	{
		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 && event.osEvent.message == WM_MOUSEMOVE)
		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.osEvent.message == WM_TIMER &&
		event.osEvent.wParam == 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.osEvent.message == WM_TIMER &&
		event.osEvent.wParam == 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)
	{
		RECT rect;
		quickTipRegion.ExportPoint(rect);
		DestroyWindow(hwnd);
	}

	// 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)
	{
		KillTimer(ZAF_NULLH(HWND), 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.
	if (resetTimer && event->osEvent.message == WM_MOUSEMOVE)
	{
		LPARAM lParam = event->osEvent.lParam;
#if defined(ZAF_WIN32)
		POINTS points = MAKEPOINTS(lParam);
		POINT point;
		point.x = points.x;
		point.y = points.y;
#else	
		POINT point = MAKEPOINT(lParam);
#endif
		ClientToScreen(event->osEvent.hwnd, &point);
		lastMousePosition.column = point.x;
		lastMousePosition.line = point.y;

		if (zafWindowManager->mouseObject)
			timerHandle = SetTimer(ZAF_NULLH(HWND), (UINT)0, (UINT)interval, NULL);
	}
}

LRESULT CALLBACK HelpTipsWndProc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
	switch (wMsg)
	{
	case WM_PAINT:
		{
			ZafDisplay *display = zafDisplay;

			// Begin the draw operation.
			PAINTSTRUCT paintStruct;
			ZafRegionStruct drawRegion;
			RECT rect;
			GetClientRect(hwnd, &rect);
			drawRegion.ImportPoint(rect);
			drawRegion.coordinateType = ZAF_PIXEL;
			display->BeginDraw(BeginPaint(hwnd, &paintStruct), 0, drawRegion, drawRegion);

			ZafPaletteStruct palette;

#if defined(ZAF_WIN32)
			palette = *((ZafPaletteStruct *)GetWindowLong(hwnd, GWL_USERDATA));
#else
			palette = *((ZafPaletteStruct *)MAKELONG(GetProp(hwnd, (LPCSTR)ZafMSWindowsApp::objectLowWord),
				GetProp(hwnd, (LPCSTR)ZafMSWindowsApp::objectHighWord)));
#endif
			display->SetPalette(palette);

			ZafWindowObject *mouseObject = zafWindowManager->mouseObject;
			ZafIChar *quickTip = strdup(mouseObject->QuickTip());

			bool multiline = false;
			if (strchr(quickTip, '\n'))
				multiline = true;

			// 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';

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

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

			// End the draw operation.
			display->EndDraw();
			EndPaint(hwnd, &paintStruct);

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

			return (0);
		}
	}

	return (DefWindowProc(hwnd, wMsg, wParam, lParam));
}

