// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"

#ifdef AFX_CMNCTL_SEG
#pragma code_seg(AFX_CMNCTL_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// CToolTipCtrl

BEGIN_MESSAGE_MAP(CToolTipCtrl, CWnd)
	//{{AFX_MSG_MAP(CToolTipCtrl)
	ON_MESSAGE(WM_DISABLEMODAL, OnDisableModal)
	ON_MESSAGE(TTM_WINDOWFROMPOINT, OnWindowFromPoint)
	ON_MESSAGE(TTM_ADDTOOL, OnAddTool)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CToolTipCtrl::CToolTipCtrl()
{
}

BOOL CToolTipCtrl::Create(CWnd* pParentWnd, DWORD dwStyle)
{
	BOOL bResult = CWnd::CreateEx(NULL, TOOLTIPS_CLASS, NULL,
		WS_POPUP | dwStyle, // force WS_POPUP
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		pParentWnd->GetSafeHwnd(), NULL, NULL);

	if (bResult)
		SetOwner(pParentWnd);
	return bResult;
}

CToolTipCtrl::~CToolTipCtrl()
{
	DestroyWindow();
}

BOOL CToolTipCtrl::DestroyToolTipCtrl()
{
#ifdef _AFXDLL
	BOOL bDestroy = (AfxGetModuleState() == m_pModuleState);
#else
	BOOL bDestroy = TRUE;
#endif

	if (bDestroy)
	{
		DestroyWindow();
		delete this;
	}
	return bDestroy;
}

LRESULT CToolTipCtrl::OnAddTool(WPARAM wParam, LPARAM lParam)
{
	TOOLINFO ti = *(LPTOOLINFO)lParam;
	if ((ti.hinst == NULL) && (ti.lpszText != LPSTR_TEXTCALLBACK)
		&& (ti.lpszText != NULL))
	{
		void* pv;
		if (!m_mapString.Lookup(ti.lpszText, pv))
			m_mapString.SetAt(ti.lpszText, NULL);
		// set lpszText to point to the permanent memory associated
		// with the CString
		VERIFY(m_mapString.LookupKey(ti.lpszText,
#ifdef __WATCOMC__
		(LPCTSTR&)ti.lpszText));
#else
		ti.lpszText));
#endif
	}
	return DefWindowProc(TTM_ADDTOOL, wParam, (LPARAM)&ti);
}

LRESULT CToolTipCtrl::OnDisableModal(WPARAM, LPARAM)
{
	SendMessage(TTM_ACTIVATE, 0);
	return FALSE;
}

LRESULT CToolTipCtrl::OnWindowFromPoint(WPARAM, LPARAM lParam)
{
	ASSERT(lParam != NULL);

	// the default implementation of tooltips just calls WindowFromPoint
	// which does not work for certain kinds of combo boxes
	CPoint pt = *(POINT*)lParam;
	HWND hWnd = ::WindowFromPoint(pt);
	if (hWnd == NULL)
		return 0;

	// try to hit combobox instead of edit control for CBS_DROPDOWN styles
	HWND hWndTemp = ::GetParent(hWnd);
	if (hWndTemp != NULL && _AfxIsComboBoxControl(hWndTemp, CBS_DROPDOWN))
		return (LRESULT)hWndTemp;

	// handle special case of disabled child windows
	::ScreenToClient(hWnd, &pt);
	hWndTemp = _AfxChildWindowFromPoint(hWnd, pt);
	if (hWndTemp != NULL && !::IsWindowEnabled(hWndTemp))
		return (LRESULT)hWndTemp;

	return (LRESULT)hWnd;
}

BOOL CToolTipCtrl::AddTool(CWnd* pWnd, LPCTSTR lpszText, LPCRECT lpRectTool,
	UINT nIDTool)
{
	ASSERT(::IsWindow(m_hWnd));
	ASSERT(pWnd != NULL);
	ASSERT(lpszText != NULL);
	// the toolrect and toolid must both be zero or both valid
	ASSERT((lpRectTool != NULL && nIDTool != 0) ||
		   (lpRectTool == NULL) && (nIDTool == 0));

	TOOLINFO ti;
	FillInToolInfo(ti, pWnd, nIDTool);
	if (lpRectTool != NULL)
		memcpy(&ti.rect, lpRectTool, sizeof(RECT));
	ti.lpszText = (LPTSTR)lpszText;
	return (BOOL) ::SendMessage(m_hWnd, TTM_ADDTOOL, 0, (LPARAM)&ti);
}

BOOL CToolTipCtrl::AddTool(CWnd* pWnd, UINT nIDText, LPCRECT lpRectTool,
	UINT nIDTool)
{
	ASSERT(::IsWindow(m_hWnd));
	ASSERT(nIDText != 0);
	ASSERT(pWnd != NULL);
	// the toolrect and toolid must both be zero or both valid
	ASSERT((lpRectTool != NULL && nIDTool != 0) ||
		   (lpRectTool == NULL) && (nIDTool == 0));

	TOOLINFO ti;
	FillInToolInfo(ti, pWnd, nIDTool);
	if (lpRectTool != NULL)
		memcpy(&ti.rect, lpRectTool, sizeof(RECT));
	ti.hinst = AfxFindResourceHandle(MAKEINTRESOURCE((nIDText>>4)+1),
		RT_STRING);
	ASSERT(ti.hinst != NULL);
	ti.lpszText = (LPTSTR)MAKEINTRESOURCE(nIDText);
	return (BOOL) ::SendMessage(m_hWnd, TTM_ADDTOOL, 0, (LPARAM)&ti);
}

void CToolTipCtrl::DelTool(CWnd* pWnd, UINT nIDTool)
{
	ASSERT(::IsWindow(m_hWnd));
	ASSERT(pWnd != NULL);

	TOOLINFO ti;
	FillInToolInfo(ti, pWnd, nIDTool);
	::SendMessage(m_hWnd, TTM_DELTOOL, 0, (LPARAM)&ti);
}

void CToolTipCtrl::GetText(CString& str, CWnd* pWnd, UINT nIDTool) const
{
	ASSERT(::IsWindow(m_hWnd));
	ASSERT(pWnd != NULL);

	TOOLINFO ti;
	FillInToolInfo(ti, pWnd, nIDTool);
	ti.lpszText = str.GetBuffer(256);
	::SendMessage(m_hWnd, TTM_GETTEXT, 0, (LPARAM)&ti);
	str.ReleaseBuffer();
}

BOOL CToolTipCtrl::GetToolInfo(CToolInfo& ToolInfo, CWnd* pWnd,
	UINT nIDTool) const
{
	ASSERT(::IsWindow(m_hWnd));
	ASSERT(pWnd != NULL);

	FillInToolInfo(ToolInfo, pWnd, nIDTool);
	ToolInfo.lpszText = ToolInfo.szText;
	return (BOOL)::SendMessage(m_hWnd, TTM_GETTOOLINFO, 0, (LPARAM)&ToolInfo);
}

BOOL CToolTipCtrl::HitTest(CWnd* pWnd, CPoint pt, LPTOOLINFO lpToolInfo) const
{
	ASSERT(::IsWindow(m_hWnd));
	ASSERT(pWnd != NULL);
	ASSERT(lpToolInfo != NULL);

	TTHITTESTINFO hti;
	memset(&hti, 0, sizeof(hti));
	hti.hwnd = pWnd->GetSafeHwnd();
	hti.pt.x = pt.x;
	hti.pt.y = pt.y;
	if ((BOOL)::SendMessage(m_hWnd, TTM_HITTEST, 0, (LPARAM)&hti))
	{
		memcpy(lpToolInfo, &hti.ti, sizeof(TOOLINFO));
		return TRUE;
	}
	return FALSE;
}

void CToolTipCtrl::SetToolRect(CWnd* pWnd, UINT nIDTool, LPCRECT lpRect)
{
	ASSERT(::IsWindow(m_hWnd));
	ASSERT(pWnd != NULL);
	ASSERT(nIDTool != 0);

	TOOLINFO ti;
	FillInToolInfo(ti, pWnd, nIDTool);
	memcpy(&ti.rect, lpRect, sizeof(RECT));
	::SendMessage(m_hWnd, TTM_NEWTOOLRECT, 0, (LPARAM)&ti);
}

void CToolTipCtrl::UpdateTipText(LPCTSTR lpszText, CWnd* pWnd, UINT nIDTool)
{
	ASSERT(::IsWindow(m_hWnd));
	ASSERT(pWnd != NULL);

	TOOLINFO ti;
	FillInToolInfo(ti, pWnd, nIDTool);
	ti.lpszText = (LPTSTR)lpszText;
	::SendMessage(m_hWnd, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
}

void CToolTipCtrl::UpdateTipText(UINT nIDText, CWnd* pWnd, UINT nIDTool)
{
	ASSERT(nIDText != 0);

	CString str;
	VERIFY(str.LoadString(nIDText));
	UpdateTipText(str, pWnd, nIDTool);
}

/////////////////////////////////////////////////////////////////////////////
// CToolTipCtrl Implementation

void CToolTipCtrl::FillInToolInfo(TOOLINFO& ti, CWnd* pWnd, UINT nIDTool) const
{
	memset(&ti, 0, sizeof(ti));
	ti.cbSize = sizeof(ti);
	HWND hwnd = pWnd->GetSafeHwnd();
	if (nIDTool == 0)
	{
		ti.hwnd = ::GetParent(hwnd);
		ti.uFlags = TTF_IDISHWND;
		ti.uId = (UINT)hwnd;
	}
	else
	{
		ti.hwnd = hwnd;
		ti.uFlags = 0;
		ti.uId = nIDTool;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CWnd tooltip support

BOOL CWnd::EnableToolTips(BOOL bEnable)
{
	_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
	CToolTipCtrl* pToolTip = pThreadState->m_pToolTip;

	if (!bEnable)
	{
		// nothing to do if tooltips not enabled
		if (!(m_nFlags & WF_TOOLTIPS))
			return TRUE;

		// cancel tooltip if this window is active
		if (pThreadState->m_pLastHit == this)
			CancelToolTips(TRUE);

		// remove "dead-area" toolbar
		if (pToolTip->GetSafeHwnd() != NULL)
		{
			TOOLINFO ti; memset(&ti, 0, sizeof(TOOLINFO));
			ti.cbSize = sizeof(TOOLINFO);
			ti.uFlags = TTF_IDISHWND;
			ti.hwnd = m_hWnd;
			ti.uId = (UINT)m_hWnd;
			pToolTip->SendMessage(TTM_DELTOOL, 0, (LPARAM)&ti);
		}

		// success
		m_nFlags &= ~WF_TOOLTIPS;
		return TRUE;
	}

	// if already enabled for tooltips, nothing to do
	if (!(m_nFlags & WF_TOOLTIPS))
	{
		// success
		AFX_MODULE_PROCESS_STATE* pState = AfxGetModuleProcessState();
		pState->m_pfnFilterToolTipMessage = &CWnd::_FilterToolTipMessage;
		m_nFlags |= WF_TOOLTIPS;
	}
	return TRUE;
}

static void AFXAPI RelayToolTipMessage(CToolTipCtrl* pToolTip, MSG* pMsg)
{
	// transate the message based on TTM_WINDOWFROMPOINT
	MSG msg = *pMsg;
	msg.hwnd = (HWND)pToolTip->SendMessage(TTM_WINDOWFROMPOINT, 0, (LPARAM)&msg.pt);
	CPoint pt = pMsg->pt;
	if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
		::ScreenToClient(msg.hwnd, &pt);
	msg.lParam = MAKELONG(pt.x, pt.y);

	// relay mouse event before deleting old tool
	pToolTip->SendMessage(TTM_RELAYEVENT, 0, (LPARAM)&msg);
}

void PASCAL CWnd::_FilterToolTipMessage(MSG* pMsg, CWnd* pWnd)
{
	pWnd->FilterToolTipMessage(pMsg);
}

void CWnd::FilterToolTipMessage(MSG* pMsg)
{
	UINT message = pMsg->message;
	if ((m_nFlags & WF_TOOLTIPS) &&
		(message == WM_MOUSEMOVE || message == WM_NCMOUSEMOVE ||
		 message == WM_LBUTTONUP || message == WM_RBUTTONUP ||
		 message == WM_MBUTTONUP) &&
		(GetKeyState(VK_LBUTTON) >= 0 && GetKeyState(VK_RBUTTON) >= 0 &&
		 GetKeyState(VK_MBUTTON) >= 0))
	{
		// make sure that tooltips are not already being handled
		CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
		while (pWnd != NULL && pWnd != this && !(pWnd->m_nFlags & WF_TOOLTIPS))
			pWnd = pWnd->GetParent();
		if (pWnd != this)
			return;

		_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
		CToolTipCtrl* pToolTip = pThreadState->m_pToolTip;
		CWnd* pOwner = GetParentOwner();
		if (pToolTip != NULL && pToolTip->GetOwner() != pOwner)
		{
			pToolTip->DestroyWindow();
			delete pToolTip;
			pThreadState->m_pToolTip = NULL;
			pToolTip = NULL;
		}
		if (pToolTip == NULL)
		{
			pToolTip = new CToolTipCtrl;
			if (!pToolTip->Create(pOwner, TTS_ALWAYSTIP))
			{
				delete pToolTip;
				return;
			}
			pToolTip->SendMessage(TTM_ACTIVATE, FALSE);
			pThreadState->m_pToolTip = pToolTip;
		}

		ASSERT_VALID(pToolTip);
		ASSERT(::IsWindow(pToolTip->m_hWnd));

		// add a "dead-area" tool for areas between toolbar buttons
		TOOLINFO ti; memset(&ti, 0, sizeof(TOOLINFO));
		ti.cbSize = sizeof(TOOLINFO);
		ti.uFlags = TTF_IDISHWND;
		ti.hwnd = m_hWnd;
		ti.uId = (UINT)m_hWnd;
		if (!pToolTip->SendMessage(TTM_GETTOOLINFO, 0, (LPARAM)&ti))
		{
			ASSERT(ti.cbSize == sizeof(TOOLINFO));
			ASSERT(ti.uFlags == TTF_IDISHWND);
			ASSERT(ti.hwnd == m_hWnd);
			ASSERT(ti.uId == (UINT)m_hWnd);
			VERIFY(pToolTip->SendMessage(TTM_ADDTOOL, 0, (LPARAM)&ti));
		}

		// determine which tool was hit
		CPoint point = pMsg->pt;
		::ScreenToClient(m_hWnd, &point);
		TOOLINFO tiHit; memset(&tiHit, 0, sizeof(TOOLINFO));
		tiHit.cbSize = sizeof(TOOLINFO);
		int nHit = OnToolHitTest(point, &tiHit);

		// build new toolinfo and if different than current, register it
		CWnd* pHitWnd = nHit == -1 ? NULL : this;
		if (pThreadState->m_nLastHit != nHit || pThreadState->m_pLastHit != pHitWnd)
		{
			if (nHit != -1)
			{
				// add new tool and activate the tip
				ti = tiHit;
				ti.uFlags &= ~(TTF_NOTBUTTON|TTF_ALWAYSTIP);
				VERIFY(pToolTip->SendMessage(TTM_ADDTOOL, 0, (LPARAM)&ti));
				if ((tiHit.uFlags & TTF_ALWAYSTIP) || IsTopParentActive())
				{
					// allow the tooltip to popup when it should
					pToolTip->SendMessage(TTM_ACTIVATE, TRUE);

					// bring the tooltip window above other popup windows
					::SetWindowPos(pToolTip->m_hWnd, HWND_TOP, 0, 0, 0, 0,
						SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE);
				}
			}

			// relay mouse event before deleting old tool
			RelayToolTipMessage(pToolTip, pMsg);

			// now safe to delete the old tool
			if (pThreadState->m_lastInfo.cbSize == sizeof(TOOLINFO))
				pToolTip->SendMessage(TTM_DELTOOL, 0, (LPARAM)&pThreadState->m_lastInfo);
			pThreadState->m_pLastHit = pHitWnd;
			pThreadState->m_nLastHit = nHit;
			pThreadState->m_lastInfo = tiHit;
		}
		else
		{
			// relay mouse events through the tooltip
			if (nHit != -1)
				RelayToolTipMessage(pToolTip, pMsg);
		}

		if (tiHit.lpszText != LPSTR_TEXTCALLBACK)
			free(tiHit.lpszText);
	}
}

/////////////////////////////////////////////////////////////////////////////

#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif

IMPLEMENT_DYNAMIC(CToolTipCtrl, CWnd)

/////////////////////////////////////////////////////////////////////////////
