// ==========================================================================
//						   Class Specification : 
//								COXItemTip
// ==========================================================================

// Version: 9.3

// This software along with its related components, documentation and files ("The Libraries")
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement").  Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office.  For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
                          
// //////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// Generic class that provides tips for items from List Control that are not 
// entirely visible. Derived from COXHookWnd - generic class that is 
// used to subclass any CWnd and provides capability to handle any message 
// routed to the hooked window before it. Handles WM_MOUSEMOVE messages to handle 
// drawing tips. When mouse is moved over any of items that only partially seen
// then item tip will be displayed (the same functionality as CTreeCtrl provides)
//
// To use it:
//
//	1)	call Attach to hook any CListCtrl based control
//
//	2)	for ownerdraw CListCtrl you have to provide additional information
//		about current item. Such information can be requested in two ways.
//		There is WM_DOYOUNEEDITEMTIP message which COXItemTip will send to
//		corresponding CListCtrl each time it needs to request specific 
//		information by means of NEEDITEMTIPINFO structure. Another way to 
//		provide item information is to use callback function that can be
//		set while calling COXItemInfo::Attach fuction and should be defined as:
//
//			void CallbackNeedItemTip(LPARAM lpSource, LPARAM lpInfo)
//
//		where lpSource is a pointer to COXItemTip object that is requesting 
//		item information and lpInfo is pointer to NEEDITEMTIPINFO structure.
//
//		As I mentioned before you have to provide item information for
//		COXItemTip if CListCtrl is ownerdrawfixed. But you can use the same 
//		technique to amend any item info that was collected by COXItemTip for
//		standard CListCtrl too. E.g. you can change the color of text or 
//		background or even font used to draw item tip window.
//
//		It is up to a programmer to choose the best way of providing item info.
//		As a possible solution I would recommend the next:
//		
//			a)	you want to use item tip functionality in your CListCtrl derived
//				class. Then it's good idea to provide handler for 
//				WM_DOYOUNEEDITEMTIP message.
//			b)	you want to use item tip functionality for any CListCtrl derived
//				class and you don't want to make any changes in the source code
//				and don't want to write any specific derivation. Then you can easily
//				register callback function.
//
//		You cannot use both way simultaneously because if you register callback 
//		function then WM_DOYOUNEEDITEMTIP message won't be sent.
//
//		Here is the description of NEEDITEMTIPINFO structure:
//
//			typedef struct _tagNEEDITEMTIPINFO
//			{
//				POINT point;
//				int row;
//				int col;
//				RECT rectItem;
//				TCHAR itemText[MAX_ITEMTIP];
//				int sizeText;
//				int offset;
//				LPARAM pFont;
//				COLORREF clrText;
//				COLORREF clrBackground;
//				int alignment;
//				int result;
//			} NEEDITEMTIPINFO, *LPNEEDITEMTIPINFO;
//
//		where:
//			point		-	current coordinates of mouse cursor, using this parameter 
//							you can	identify the item cell
//			row			-	item row
//			col			-	item column
//			rectItem	-	rectangle where item tip should be drawn, in particular 
//							the left-top point is the most important here
//			itemText	-	text to draw in item tip
//			sizeText	-	size of text
//			offset		-	offset from the right and left edge of item tip window 
//							to draw text in
//			pFont		-	pointer to a CFont object to be used to draw text in
//							item tip window
//			clrText		-	color to be used to draw text in item tip window
//			clrBackground	color to be used to fill background in item tip window
//			alignment	-	alignment used to draw text in item tip window, can be:
//							DT_LEFT
//							DT_CENTER
//							DT_RIGHT
//			result		-	result of collecting item information, can be:
//							ID_NIT_SUCCESS				-	info was successfully 
//															collected
//							ID_NIT_WRONGFORMAT			-	wrong format of CListCtrl 
//															(e.g. it is not in Report 
//															view)
//							ID_NIT_OUTOFCONTROLBORDER	-	point is out of valid 
//															area of CListCtrl items
//															
//							ID_NIT_OUTOFITEMBORDER		-	point is within CListCtrl
//															but doesn't lay in any 
//															item
//							ID_NIT_NOTHIDDEN			-	text of (row,col) is 
//															entirely seen 
//							ID_NIT_CUSTOMREJECT			-	user requests not to 
//															display an item tip
//
//	
//		Before WM_DOYOUNEEDITEMTIP message or callback function is fired above 
//		described structure is filled by COXItemTip::NeedItemTip function. But
//		if a hooked CListCtrl is ownerdrawfixed then next elements could be defined 
//		incorrectly: row, col, rectItem, itemText, offset.  
//
//
//	3)	In the case when you don't have and don't want to provide item info using
//		above described ways but still don't like the settings that COXItemTip
//		uses by default you can use next functions to change them:
//
//			// sets font to draw text
//			void SetTextFont(CFont* pFont);
//			// gets font to draw text
//			CFont* GetTextFont();
//
//			// sets color of text
//			void SetTextColor(COLORREF clrText);
//			// gets color of text
//			COLORREF GetTextColor();
//
//			// sets color of text
//			void SetBkColor(COLORREF clrBackground);
//			// gets color of text
//			COLORREF GetBkColor();
//
//			// sets offset from the right and left edge of item tip window
//			// to draw text in
//			void SetOffset(int nOffset)
//			// gets offset from the right and left edge of item tip window
//			// to draw text in
//			int GetOffset();
//

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


#ifndef _ITEMTIP_H
#define _ITEMTIP_H

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include "OXDllExt.h"
#include "OXMainRes.h"

#include "OXHookWnd.h"
#include "OXItemTipWnd.h"
#include "OXBaseSubclassedComboBox.h"

//#include <windowsx.h>
#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lp)                        ((int)(short)LOWORD(lp))
#endif
#ifndef GET_Y_LPARAM
#define GET_Y_LPARAM(lp)                        ((int)(short)HIWORD(lp))
#endif

#ifndef _WIN32_IE
#if _MFC_VER>=0x0420
#define _WIN32_IE 0x0300
#else
#define _WIN32_IE 0x0200
#endif
#endif


// offset from left and right side of ItemTipWindow
#define ID_ITEMTIP_XOFFSET					6
#define ID_ITEMTIP_XOFFSET_LISTBOX			2
#define ID_ITEMTIP_XOFFSET_COMBOBOX			4

//result codes returned by NeedItemTip function
#define ID_NIT_SUCCESS						0x00000001
#define ID_NIT_WRONGFORMAT					0x00000002
#define ID_NIT_OUTOFCONTROLBORDER			0x00000003
#define ID_NIT_OUTOFITEMBORDER				0x00000004
#define ID_NIT_NOTHIDDEN					0x00000005
#define ID_NIT_CUSTOMREJECT					0x00000006

// max length of itemtip text
#define MAX_ITEMTIP							300


typedef struct _tagNEEDITEMTIPINFO
{
	POINT point;
	int row;
	int col;
	RECT rectItem;
	TCHAR itemText[MAX_ITEMTIP];
	int sizeText;
	int offset;
	LPARAM pFont;
	COLORREF clrText;
	COLORREF clrBackground;
	int alignment;
	int result;

	_tagNEEDITEMTIPINFO operator=(const _tagNEEDITEMTIPINFO& nitInfo)
	{
		if(this==&nitInfo)
			return *this;

		point.x=nitInfo.point.x;
		point.y=nitInfo.point.y;
		row=nitInfo.row;
		col=nitInfo.col;
		rectItem.left=nitInfo.rectItem.left;
		rectItem.top=nitInfo.rectItem.top;
		rectItem.right=nitInfo.rectItem.right;
		rectItem.bottom=nitInfo.rectItem.bottom;
		lstrcpyn(itemText,nitInfo.itemText,MAX_ITEMTIP);
		sizeText=nitInfo.sizeText;
		offset=nitInfo.offset;
		pFont=nitInfo.pFont;
		clrText=nitInfo.clrText;
		clrBackground=nitInfo.clrBackground;
		alignment=nitInfo.alignment;
		result=nitInfo.result;

		return *this;
	}

} NEEDITEMTIPINFO, *LPNEEDITEMTIPINFO;

// special callback function that can be registered in Attach function
typedef void (CALLBACK* NEEDITEMTIP_HELPER) (LPARAM lpSource, LPARAM lpInfo);

typedef struct _tagNMNEEDITEMTIPINFO
{
	NMHDR hdr;
	NEEDITEMTIPINFO nitInfo;
} NMNEEDITEMTIPINFO, *LPNMNEEDITEMTIPINFO;


template<class ATTACHED_CONTROL>
class COXBaseItemTip : public COXHookWnd 
{
protected:
	// pointer to hooked control
	ATTACHED_CONTROL* m_pAttachedCtrl;
	// item tip window used to display item tips
	COXItemTipWnd m_ItemTipWnd;
	
	// callback function that can be registered to provide or amen item info 
	// before it is displayed
	NEEDITEMTIP_HELPER m_pfnCallback;

	// font to draw text
	CFont* m_pFont;
	// color to draw text
	COLORREF m_clrText;
	// color of background
	COLORREF m_clrBackground;
	// offset to draw text
	int m_nOffset;

	// The heart of the class. Handle all needed messages. While deriving
	// your own class make sure to call this function. I wouldn't recommend you 
	// to change anything in it
	// --- In  :	msg	-	message ID
	//				wp	-	WPARAM
	//				lp	-	LPARAM
	// --- Out : 
	// --- Returns:	result of message handling. Different for different messages.
	// --- Effect :	Sets the number of color shades used in gradient filling. 
	virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);	

	// --- In  :	pnitInfo	-	pointer to NEEDITEMTIPINFO structure that 
	//								should be filled as a result of this function
	// --- Out : 
	// --- Returns:
	// --- Effect : fills pnitInfo with item information
	virtual void NeedItemTip(LPNEEDITEMTIPINFO pnitInfo);

	// --- In  :	
	// --- Out : 
	// --- Returns:
	// --- Effect : Initializes the process of checking if item tip 
	//				should be displayed
	virtual void CheckItemTip();

public:
	// --- In  :
	// --- Out : 
	// --- Returns:
	// --- Effect : Constructs the object
	COXBaseItemTip();

	// --- In  :
	// --- Out : 
	// --- Returns:
	// --- Effect : Destroys the object
	virtual ~COXBaseItemTip();


	// --- In  :	pWnd		-	pointer to created ATTACHED_CONTROL-derived control
	//								to be hooked
	//				pfnCallback	-	callback function to be called to request item 
	//								info (must  be implemented if ATTACHED_CONTROL is 
	//								ownerdrawfixed). If pfnCallback is NULL then
	//								WM_DOYOUNEEDITEMTIP message will be sent (this 
	//								is an alternative way of providing item info).
	// --- Out : 
	// --- Returns:	TRUE if pWnd was successfully hooked, otherwise FALSE
	// --- Effect : Hooks window
	virtual BOOL Attach(CWnd* pWnd, NEEDITEMTIP_HELPER pfnCallback=NULL);

	// --- In  :
	// --- Out : 
	// --- Returns:
	// --- Effect : Unhooks attached window if any was hooked
	virtual void Detach();


	// --- In  :
	// --- Out : 
	// --- Returns:
	// --- Effect : set font, text color, background color and offset to their
	//				default values.
	void Reset();


	// --- In  :	pFont	-	pointer to CFont object to be used to draw text
	//							in item tip window
	// --- Out : 
	// --- Returns:
	// --- Effect : sets the font to draw text. By default we use the font 
	//				associated with hooked CListCtrl.
	inline void SetTextFont(CFont* pFont) { m_pFont=pFont; }
	
	// --- In  :	
	// --- Out : 
	// --- Returns:	pointer to CFont object to be used to draw text	in item tip 
	//				window
	// --- Effect : gets the font to draw text
	inline CFont* GetTextFont() const { return m_pFont; }


	// --- In  :	clrText	-	color to be used to draw text in item tip window
	// --- Out : 
	// --- Returns:
	// --- Effect : sets color of text. By default we use the color returned by 
	//				CListCtrl::GetTextColor function applied to the hooked CListCtrl.
	inline void SetTextColor(COLORREF clrText) { m_clrText=clrText; }

	// --- In  :	
	// --- Out : 
	// --- Returns:	color to be used to draw text in item tip window
	// --- Effect : gets the color to draw text
	inline COLORREF GetTextColor() const { return m_clrText; }


	// --- In  :	clrBackground	-	color to be used to fill background 
	//									in item tip window
	// --- Out : 
	// --- Returns:
	// --- Effect : sets color of background. By default we use the background color 
	//				used in ToolTip controls.
	inline void SetBkColor(COLORREF clrBackground) { m_clrBackground=clrBackground; }

	// --- In  :	
	// --- Out : 
	// --- Returns:	color to be used to fill background in item tip window
	// --- Effect : gets the color of background
	inline COLORREF GetBkColor() const { return m_clrBackground; }


	// --- In  :	nOffset	-	offset from the right and left edge of item tip 
	//							window to draw text in
	// --- Out : 
	// --- Returns:
	// --- Effect : sets the size of an offset. By default we use the 
	//				ID_ITEMTIP_XOFFSET constant
	inline void SetOffset(int nOffset) { m_nOffset=nOffset; }

	// --- In  :	
	// --- Out : 
	// --- Returns:	offset from the right and left edge of item tip window to draw 
	//				text in
	// --- Effect : gets the size of an offset.
	inline int GetOffset() const { return m_nOffset; }
};

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

// Constructor
template<class ATTACHED_CONTROL> 
COXBaseItemTip<ATTACHED_CONTROL>::COXBaseItemTip()
{
	m_pAttachedCtrl=NULL;
	m_pfnCallback=NULL;

	Reset();
}

// Destructor
template<class ATTACHED_CONTROL>
COXBaseItemTip<ATTACHED_CONTROL>::~COXBaseItemTip()
{
	if(::IsWindow(m_ItemTipWnd.m_hWnd))
	{
		m_ItemTipWnd.DestroyWindow();
	}
}

template<class ATTACHED_CONTROL> 
void COXBaseItemTip<ATTACHED_CONTROL>::Reset()
{
	m_pFont=NULL;
	m_clrText=ID_OX_COLOR_NONE;
	m_clrBackground=ID_OX_COLOR_NONE;
	m_nOffset=ID_ITEMTIP_XOFFSET;
}

// Attach ATTACHED_CONTROL handler.
template<class ATTACHED_CONTROL> 
BOOL COXBaseItemTip<ATTACHED_CONTROL>::Attach(CWnd* pWnd, 
											  NEEDITEMTIP_HELPER pfnCallback)
{
	ASSERT(pWnd!=NULL);
	ASSERT(::IsWindow(pWnd->m_hWnd));

	HookWindow(pWnd);

	m_pAttachedCtrl=(ATTACHED_CONTROL*)pWnd;
	ASSERT(m_pAttachedCtrl!=NULL);

	m_pfnCallback=pfnCallback;

	if(!::IsWindow(m_ItemTipWnd.GetSafeHwnd()))
	{
		m_ItemTipWnd.Create(pWnd);
	}

	return TRUE;
}

// Detach ATTACHED_CONTROL handler. Called by default when hooked window is about
// to be destroyed.
template<class ATTACHED_CONTROL>
void COXBaseItemTip<ATTACHED_CONTROL>::Detach()
{
	UnhookWindow();

	m_pAttachedCtrl=NULL;
	m_pfnCallback=NULL;
}

////////////////////////////////////////////////////////////////////////////
// Message handler handles ItemTip-related messages
//
template<class ATTACHED_CONTROL>
LRESULT COXBaseItemTip<ATTACHED_CONTROL>::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
#if defined (_WINDLL)
#if defined (_AFXDLL)
	AFX_MANAGE_STATE(AfxGetAppModuleState());
#else
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
#endif
#endif

	ASSERT(::IsWindow(m_hWndHooked));
	ASSERT(m_hWndHooked==m_pAttachedCtrl->m_hWnd);

	switch (msg) 
	{
	case WM_MOUSEMOVE: 
		{
			if(wp==0)
			{
				CheckItemTip();
			}
			break;
		}

	case WM_KEYDOWN: 
	case WM_SYSKEYDOWN: 
		{
			if(m_ItemTipWnd.GetStyle()&WS_VISIBLE)
			{
				m_ItemTipWnd.Hide();
			}
			break;
		}

	case WM_SHOWWINDOW:
		{
			if(!(BOOL)wp && m_ItemTipWnd.GetStyle()&WS_VISIBLE)
			{
				m_ItemTipWnd.Hide();
			}
			break;
		}
	
	case WM_WINDOWPOSCHANGED: 
		{
			if(m_ItemTipWnd.GetStyle()&WS_VISIBLE)
			{
				m_ItemTipWnd.Hide();
				CheckItemTip();
			}
			break;
		}
	}

	// I don't handle it: pass along
	return COXHookWnd::WindowProc(msg, wp, lp);
}

template<class ATTACHED_CONTROL>
void COXBaseItemTip<ATTACHED_CONTROL>::NeedItemTip(LPNEEDITEMTIPINFO pnitInfo)
{
	ASSERT(::IsWindow(m_hWndHooked));
	ASSERT(m_hWndHooked==m_pAttachedCtrl->m_hWnd);

	pnitInfo->result=ID_NIT_WRONGFORMAT;

	return;
}


template<class ATTACHED_CONTROL>
void COXBaseItemTip<ATTACHED_CONTROL>::CheckItemTip()
{
	DWORD dwMessagePos=::GetMessagePos();
	CPoint point(GET_X_LPARAM(dwMessagePos),GET_Y_LPARAM(dwMessagePos));
	::ScreenToClient(m_hWndHooked,&point);

	if(m_ItemTipWnd.IsWindowVisible() && ::IsWindowVisible(m_hWndHooked))
	{
		return;
	}

	NEEDITEMTIPINFO nitInfo;
	nitInfo.point.x=point.x;
	nitInfo.point.y=point.y;
	nitInfo.row=-1;
	nitInfo.col=-1;
	nitInfo.rectItem.left=0;
	nitInfo.rectItem.right=0;
	nitInfo.rectItem.top=0;
	nitInfo.rectItem.bottom=0;
	nitInfo.sizeText=MAX_ITEMTIP;
	nitInfo.offset=m_nOffset;
	nitInfo.pFont=(m_pFont==NULL ? 
		(LPARAM)GetHookedWnd()->GetFont() : (LPARAM)m_pFont);
	nitInfo.clrText=m_clrText;
	nitInfo.clrBackground=m_clrBackground;
	nitInfo.alignment=DT_LEFT;
	nitInfo.result=ID_NIT_SUCCESS;

	NeedItemTip(&nitInfo);

	if(m_pfnCallback==NULL)
	{
		::SendMessage(m_hWndHooked,WM_DOYOUNEEDITEMTIP,(WPARAM)0,(LPARAM)&nitInfo);
	}
	else
	{
		m_pfnCallback((LPARAM)this,(LPARAM)&nitInfo);
	}

	// notify parent window
	if(GetHookedWnd()->GetParent()!=NULL)
	{
		NMNEEDITEMTIPINFO nmnitInfo;
		nmnitInfo.hdr.hwndFrom=m_hWndHooked;
		nmnitInfo.hdr.idFrom=::GetDlgCtrlID(m_hWndHooked);
		nmnitInfo.hdr.code=WN_DOYOUNEEDITEMTIP;
		nmnitInfo.nitInfo=nitInfo;
		GetHookedWnd()->GetParent()->SendMessage(
			WM_NOTIFY,(WPARAM)nmnitInfo.hdr.idFrom,(LPARAM)&nmnitInfo);
		nitInfo=nmnitInfo.nitInfo;
	}

	if(nitInfo.result==ID_NIT_SUCCESS)
	{
		CString sText=(LPCTSTR)nitInfo.itemText;
		CRect rect=nitInfo.rectItem;
		m_ItemTipWnd.Display(rect,sText,nitInfo.offset,
			nitInfo.alignment,(CFont*)nitInfo.pFont,
			nitInfo.clrText,nitInfo.clrBackground);
	}
}

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


class OX_CLASS_DECL COXItemTip : public COXBaseItemTip<CListCtrl> 
{
protected:
	DECLARE_DYNAMIC(COXItemTip);

	// --- In  :	pnitInfo	-	pointer to NEEDITEMTIPINFO structure that 
	//								should be filled as a result of this function
	// --- Out : 
	// --- Returns:
	// --- Effect : fills pnitInfo with item information
	virtual void NeedItemTip(LPNEEDITEMTIPINFO pnitInfo);

private:
	// --- In  :	nCol	-	number of column
	// --- Out : 
	// --- Returns:	real number of column taking into account order of column
	//				that could be changed starting from comctl32.dll version 4.70
	// --- Effect : 
	int GetRealColumn(int nCol);
};


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


class OX_CLASS_DECL COXEditTip : public COXBaseItemTip<CEdit> 
{
protected:
	DECLARE_DYNAMIC(COXEditTip);

	// --- In  :	pnitInfo	-	pointer to NEEDITEMTIPINFO structure that 
	//								should be filled as a result of this function
	// --- Out : 
	// --- Returns:
	// --- Effect : fills pnitInfo with item information
	virtual void NeedItemTip(LPNEEDITEMTIPINFO pnitInfo);

public:
	// --- In  :	pWnd		-	pointer to created ATTACHED_CONTROL-derived control
	//								to be hooked
	//				pfnCallback	-	callback function to be called to request item 
	//								info (must  be implemented if ATTACHED_CONTROL is 
	//								ownerdrawfixed). If pfnCallback is NULL then
	//								WM_DOYOUNEEDITEMTIP message will be sent (this 
	//								is an alternative way of providing item info).
	// --- Out : 
	// --- Returns:	TRUE if pWnd was successfully hooked, otherwise FALSE
	// --- Effect : Hooks window
	virtual BOOL Attach(CWnd* pWnd, NEEDITEMTIP_HELPER pfnCallback=NULL);
};


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


class OX_CLASS_DECL COXListBoxTip : public COXBaseItemTip<CListBox> 
{
protected:
	DECLARE_DYNAMIC(COXListBoxTip);

	// --- In  :	pnitInfo	-	pointer to NEEDITEMTIPINFO structure that 
	//								should be filled as a result of this function
	// --- Out : 
	// --- Returns:
	// --- Effect : fills pnitInfo with item information
	virtual void NeedItemTip(LPNEEDITEMTIPINFO pnitInfo);
};


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

class OX_CLASS_DECL COXComboBoxTipHelper : 
public COXBaseSubclassedComboBox<CComboBox, CListBox, CEdit> 
{
protected:
	DECLARE_DYNAMIC(COXComboBoxTipHelper);

	COXEditTip m_editTip;
	COXListBoxTip m_listBoxTip;

protected:
	virtual void SubclassListBoxCtrl(CWnd* pWnd);
	virtual void SubclassEditCtrl(CWnd* pWnd);
};


class OX_CLASS_DECL COXComboBoxTip : public COXBaseItemTip<COXComboBoxTipHelper> 
{
protected:
	DECLARE_DYNAMIC(COXComboBoxTip);

protected:
	// --- In  :	pnitInfo	-	pointer to NEEDITEMTIPINFO structure that 
	//								should be filled as a result of this function
	// --- Out : 
	// --- Returns:
	// --- Effect : fills pnitInfo with item information
	virtual void NeedItemTip(LPNEEDITEMTIPINFO pnitInfo);
};


#endif // _ITEMTIP_H

