// ==========================================================================
// Class Implementation : COXToolTipCtrl
// ==========================================================================
// Source file : OXToolTipCtrl.cpp
// 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.
// //////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "OXToolTipCtrl.h"
#include "UTB64Bit.h"
#if defined _AFXDLL
#define COMPILE_MULTIMON_STUBS
#endif
#pragma warning(disable : 4706)
#include <multimon.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// TODO: Add message handlers (eg TTM_SETTIPBKCOLOR) and point them
// to the correct functions.
LPCTSTR COXToolTipCtrl::m_szArrowSpace = _T(" ");
/////////////////////////////////////////////////////////////////////////////
// COXToolTipCtrl construction
COXToolTipCtrl::COXToolTipCtrl() :
m_pParentWnd(NULL),
m_rectMargin(2,2,3,3),
m_nMaxWidth(0),
m_ptOffset(0,20),
m_pCurrentToolTip(NULL),
m_nCheckInterval(500), // Time between checks of the tooltip - allows
// user to move the mouse to the tooltip before
// it disappears.
m_nDisplayDelay(500), // Delay in milliseconds until the tooltip is
// displayed.
m_nDisplayTime(5000), // Length of time the tooltip is displayed (mS).
m_nElapsedTime(0),
m_bActivated(TRUE),
m_bTipCancelled(FALSE),
m_bHasExtendedText(FALSE),
m_hOldFocusWnd(NULL),
m_crBackColor(CLR_DEFAULT),
m_crTextColor(CLR_DEFAULT),
m_bUsingSystemFont(TRUE),
m_dwTextStyle(DT_EXPANDTABS|DT_EXTERNALLEADING|DT_NOPREFIX|DT_WORDBREAK)
{
m_arrTools.RemoveAll();
}
COXToolTipCtrl::~COXToolTipCtrl()
{
m_Font.DeleteObject();
COXToolTipInfo *pInfo = NULL;
int nSize = PtrToInt(m_arrTools.GetSize());
for (int nIndex = 0; nIndex < nSize; nIndex++)
{
pInfo = (COXToolTipInfo* )m_arrTools.GetAt(nIndex);
delete pInfo;
}
m_arrTools.RemoveAll();
if (IsWindow(m_hWnd))
DestroyWindow();
}
/////////////////////////////////////////////////////////////////////////////
// COXToolTipCtrl message handlers
BEGIN_MESSAGE_MAP(COXToolTipCtrl, CWnd)
//{{AFX_MSG_MAP(COXToolTipCtrl)
ON_WM_PAINT()
ON_WM_TIMER()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_SETFOCUS()
ON_WM_DESTROY()
ON_WM_SETTINGCHANGE()
ON_WM_MOUSEACTIVATE()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_SETFONT, OnSetFont)
ON_MESSAGE(WM_GETFONT, OnGetFont)
END_MESSAGE_MAP()
// --- In� :
// --- Out :
// --- Returns :
// --- Effect : Draws the tooltip - called in response to a WM_PAINT message
void COXToolTipCtrl::OnPaint()
{
if (!m_pCurrentToolTip)
return;
CString str = GetTooltipText(m_pCurrentToolTip);
if (str.IsEmpty())
return;
CPaintDC dc(this); // Device context for painting
CRect rect;
GetClientRect(rect);
CBrush Brush, *pOldBrush;
if (m_pCurrentToolTip->clrBackColor == CLR_DEFAULT)
Brush.CreateSolidBrush(GetSysColor(COLOR_INFOBK));
else
Brush.CreateSolidBrush(m_pCurrentToolTip->clrBackColor);
pOldBrush = dc.SelectObject(&Brush);
if (m_pCurrentToolTip->clrTextColor == CLR_DEFAULT)
dc.SetTextColor(GetSysColor(COLOR_INFOTEXT));
else
dc.SetTextColor(m_pCurrentToolTip->clrTextColor);
CFont *pOldFont = dc.SelectObject(&m_Font);
// Draw Border
dc.FillRect(&rect, &Brush);
dc.SelectStockObject(NULL_BRUSH);
dc.SelectStockObject(BLACK_PEN);
dc.Rectangle(rect);
// Draw Text
dc.SetBkMode(TRANSPARENT);
rect.DeflateRect(m_rectMargin);
dc.DrawText(str, rect, m_dwTextStyle);
if (m_bHasExtendedText)
{
TEXTMETRIC tm;
dc.GetTextMetrics(&tm);
int nYMargin = max(tm.tmExternalLeading + tm.tmInternalLeading, tm.tmDescent);
int nXMargin = tm.tmAveCharWidth / 2;
CSize size = dc.GetTextExtent(m_szArrowSpace);
if ((size.cy & 1))
size.cy--;
CPoint pt[4];
if (m_bExtended)
{
pt[0] = CPoint(rect.left + size.cx - nXMargin, rect.top + nYMargin);
pt[1] = CPoint(rect.left, rect.top + size.cy/2);
pt[2] = CPoint(rect.left + size.cx - nXMargin, rect.top + size.cy - nYMargin);
pt[3] = pt[0];
}
else
{
pt[0] = CPoint(rect.right - size.cx + nXMargin, rect.top + nYMargin);
pt[1] = CPoint(rect.right, rect.top + size.cy/2);
pt[2] = CPoint(rect.right - size.cx + nXMargin, rect.top + size.cy - nYMargin);
pt[3] = pt[0];
}
dc.SelectStockObject(BLACK_BRUSH);
dc.Polygon(pt, 4);
}
// Cleanup
dc.SelectObject(pOldBrush);
dc.SelectObject(pOldFont);
}
// --- In� : nIDEvent - The timer event
// --- Out :
// --- Returns :
// --- Effect : Timer events are used to either Activate the tooltip after
// the mouse has been stationary for the specified time, remove
// the tip after a specified display time, or periodically check
// that the mouse is still within the bounds of the tool.
// v9.3 - update 03 - 64-bit - using OXTPARAM here - see UTB64Bit.h
void COXToolTipCtrl::OnTimer(OXTPARAM nIDEvent)
{
CPoint pt;
COXToolTipInfo *pToolTip = NULL;
if (!m_pCurrentToolTip || m_bTipCancelled)
{
KillTimer(nIDEvent);
Pop();
return;
}
switch (nIDEvent)
{
// The mouse has been still for sufficient time. If it's still in the
// initial tool, then show the tooltip for that tool.
case eIDDisplayToolEvent:
KillTimer(eIDDisplayToolEvent);
if (IsCursorInTool(m_pCurrentToolTip))
{
CPoint pt;
GetCursorPos(&pt);
pt += m_ptOffset;
DisplayToolTip(pt);
m_nElapsedTime = 0;
SetTimer(eIDCheckToolEvent, m_nCheckInterval, NULL);
}
else
{
m_pCurrentToolTip = NULL;
}
break;
// The tooltip is visible, check to see if: (a) it has been visible too long, and
// (b) is still in the bounding rect (or tooltip window). If all is well then
// reset the egg timer and check again later.
case eIDCheckToolEvent:
m_nElapsedTime += m_nCheckInterval;
GetCursorPos(&pt);
m_pParentWnd->ScreenToClient(&pt);
pToolTip = FindToolFromPoint(pt);
// Check that the cursor isn't over a new tool
if (pToolTip && pToolTip != m_pCurrentToolTip)
{
KillTimer(eIDCheckToolEvent);
Pop();
return;
}
if (!IsCursorInTool(m_pCurrentToolTip) || (m_nElapsedTime >= m_nDisplayTime))
{
if (IsCursorInToolTip())
{
//TRACE0("Cursor in tooltip - will check later\n");
//SetTimer(eIDCheckToolEvent, m_nCheckInterval, NULL);
}
else
{
//TRACE0("Not in tool or tooltip anymore, or expired. Destroying\n");
KillTimer(eIDCheckToolEvent);
Pop();
//m_pCurrentToolTip = NULL;
}
}
else
{
//TRACE0("Everything's OK - will check later\n");
//SetTimer(eIDCheckToolEvent, m_nCheckInterval, NULL);
}
break;
default:
ASSERT(FALSE);
}
}
// --- In� : nFlags - Unused
// point - Unused
// --- Out :
// --- Returns :
// --- Effect : Causes a switch between standard and extended info viewing
void COXToolTipCtrl::OnLButtonDown(UINT /*nFlags*/, CPoint /*point*/)
{
// Sometimes a click comes through the pipeline after the tool
// has already been removed
if (!m_pCurrentToolTip || m_bTipCancelled)
return;
if (m_bHasExtendedText)
{
CRect rect;
GetWindowRect(rect);
DisplayToolTip(rect.TopLeft(), !m_bExtended);
}
if (m_hOldFocusWnd)
::SetFocus(m_hOldFocusWnd);
//CWnd::OnLButtonDown(nFlags, point);
}
// --- In� : pOldWnd - Contains the CWnd object that loses the input focus
// (may be NULL).
// --- Out :
// --- Returns :
// --- Effect : Restores the focus back to the previous window (the tooltip does
// not need the focus at all)
void COXToolTipCtrl::OnSetFocus(CWnd* pOldWnd)
{
CWnd::OnSetFocus(pOldWnd);
m_hOldFocusWnd = pOldWnd->GetSafeHwnd();
}
int COXToolTipCtrl::OnMouseActivate(CWnd* /*pDesktopWnd*/, UINT /*nHitTest*/,
UINT /*message*/)
{
return MA_NOACTIVATE;
}
// --- In� :
// --- Out :
// --- Returns :
// --- Effect : Kills off any remaining timers
void COXToolTipCtrl::OnDestroy()
{
KillTimer(eIDDisplayToolEvent);
KillTimer(eIDCheckToolEvent);
CWnd::OnDestroy();
}
// --- In� : wFlag - System-wide parameter flag (see WM_SETTINGCHANGE).
// lpszSection - Name of changed section or registry changes.
// --- Out :
// --- Returns :
// --- Effect : If the tooltip font has not been overriden, this updates the
// font with the new system tooltip font
void COXToolTipCtrl::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
CWnd::OnSettingChange(uFlags, lpszSection);
if (m_bUsingSystemFont)
SetLogFont(GetSystemToolTipFont(), TRUE);
}
// --- In� : hFont - Specifies a handle to the new font.
// --- Out :
// --- Returns : No return value
// --- Effect : Sets the new tooltip font
LRESULT COXToolTipCtrl::OnSetFont(WPARAM hFont, LPARAM /*lParam */)
{
LRESULT result = Default();
// Get the logical font from the supplied handle
LOGFONT lf;
if (!GetObject((HFONT) hFont, sizeof(LOGFONT), &lf))
{
SetLogFont(GetSystemToolTipFont());
return result;
}
SetLogFont(&lf, TRUE);
return result;
}
// --- In� : The parameters are not used
// --- Out :
// --- Returns : The return value is a handle to the font used by the control
// --- Effect :
LRESULT COXToolTipCtrl::OnGetFont(WPARAM /*wParam*/, LPARAM /*lParam*/)
{
return (LRESULT) (HFONT) m_Font;
}
/////////////////////////////////////////////////////////////////////////////
// COXToolTipCtrl operations
BOOL COXToolTipCtrl::Create(CWnd* pParentWnd)
{
m_pParentWnd = pParentWnd;
// Get the class name and create the window
CString szClassName = AfxRegisterWndClass(CS_CLASSDC|CS_SAVEBITS,
LoadCursor(NULL, IDC_ARROW));
// Create the window - just don't show it yet.
if (!CWnd::CreateEx(WS_EX_TOPMOST, szClassName, _T(""),
WS_POPUP,
0, 0, 10, 10, // Size & position updated when needed
pParentWnd->GetSafeHwnd(), 0, NULL))
{
return FALSE;
}
SetLogFont(GetSystemToolTipFont(), FALSE);
return TRUE;
}
void COXToolTipCtrl::RelayEvent(MSG* pMsg)
{
ASSERT(m_pParentWnd);
if (pMsg->message == WM_MOUSEMOVE)
{
CPoint pt = pMsg->pt;
m_pParentWnd->ScreenToClient(&pt);
// Get the tool under the cursor
COXToolTipInfo *pToolTip = FindToolFromPoint(pt);
// If the tip has been cancelled and the mouse has moved into a "no tool"
// area, then we can reset the current tool in order for a new tool to be
// set as soon as it goes back into tool territory.
if (!pToolTip && ( !m_bHasExtendedText || m_bTipCancelled ))
{
m_pCurrentToolTip = NULL;
}
// If no tooltip (we are in a no tooltip fly zone), but we have a potential
// tooltip, then just relax and let the timer do it's thing.
if (!pToolTip || (m_pCurrentToolTip && (pToolTip == m_pCurrentToolTip)))
return;
// First see if we have a current tooltip, then see that it is
// still in the current tool or tooltip (if not, dismiss it)
if (m_pCurrentToolTip != NULL)
{
// Cursor over tooltip?
if (IsCursorInToolTip())
return;
if (pToolTip && (pToolTip != m_pCurrentToolTip))
{
StartNewTool(pToolTip);
return;
}
// Cursor over tool?
//if (!IsCursorInTool(m_pCurrentToolTip))
// return; // Allow the timer to deal with this instead of killing now
}
else // m_pCurrentToolTip == NULL
{
// If we have a tool under the cursor then prepare to display
if (pToolTip)
{
StartNewTool(pToolTip);
return;
}
}
}
else if (pMsg->message == WM_LBUTTONDOWN)
{
if (!IsCursorInToolTip())
Pop();
}
else if ((pMsg->message >= WM_KEYFIRST && pMsg->message <= WM_KEYLAST) ||
//(pMsg->message >= WM_SYSKEYFIRST && pMsg->message <= WM_SYSKEYLAST) ||
(/*pMsg->message == WM_LBUTTONDOWN ||*/ pMsg->message == WM_LBUTTONDBLCLK) ||
(pMsg->message == WM_RBUTTONDOWN || pMsg->message == WM_RBUTTONDBLCLK) ||
(pMsg->message == WM_MBUTTONDOWN || pMsg->message == WM_MBUTTONDBLCLK) ||
(pMsg->message == WM_NCLBUTTONDOWN || pMsg->message == WM_NCLBUTTONDBLCLK) ||
(pMsg->message == WM_NCRBUTTONDOWN || pMsg->message == WM_NCRBUTTONDBLCLK) ||
(pMsg->message == WM_NCMBUTTONDOWN || pMsg->message == WM_NCMBUTTONDBLCLK))
{
// The user has interrupted the current tool - dismiss it
Pop();
}
}
BOOL COXToolTipCtrl::AddTool(CWnd* pWnd, UINT nIDText, LPCRECT lpRectTool /*=NULL*/,
UINT nIDTool /*=0*/)
{
CString str;
str.LoadString(nIDText);
return AddTool(pWnd, (LPCTSTR) str, lpRectTool, nIDTool);
}
BOOL COXToolTipCtrl::AddTool(CWnd* pWnd, LPCTSTR lpszText,
LPCRECT lpRectTool /*=NULL*/,
UINT nIDTool /*=0*/)
{
ASSERT(pWnd);
if (!pWnd)
return FALSE;
COXToolTipInfo *pToolTip = new COXToolTipInfo;
ASSERT( pToolTip != NULL );
if (pToolTip == NULL)
return FALSE;
// Fill the tooltip structure
pToolTip->hWnd = pWnd->GetSafeHwnd();
pToolTip->nIDTool = nIDTool;
if (lpszText == LPSTR_TEXTCALLBACK)
{
pToolTip->szText = lpszText;
pToolTip->strText.Empty();
}
else
{
pToolTip->szText = NULL;
pToolTip->strText = lpszText;
}
pToolTip->clrBackColor = m_crBackColor;
pToolTip->clrTextColor = m_crTextColor;
pToolTip->nWidth = m_nMaxWidth;
pToolTip->lParam = 0;
// Get bounding region for tooltip info
CRect rect;
if (lpRectTool)
{
rect = lpRectTool;
pWnd->ClientToScreen(rect);
}
else
{
pWnd->GetWindowRect(rect);
}
m_pParentWnd->ScreenToClient(rect);
pToolTip->rectBounds = rect;
// Add to the list
m_arrTools.Add(pToolTip);
return TRUE;
}
void COXToolTipCtrl::DelTool(CWnd* pWnd, UINT nIDTool /*=0*/)
{
for (int i = 0; i < m_arrTools.GetSize(); i++)
{
COXToolTipInfo* pToolInfo = (COXToolTipInfo*) m_arrTools.GetAt(i);
if (!pToolInfo)
continue;
if (pWnd->GetSafeHwnd() == pToolInfo->hWnd &&
nIDTool == pToolInfo->nIDTool)
{
if (pToolInfo == m_pCurrentToolTip)
{
Pop();
m_pCurrentToolTip = NULL;
}
delete pToolInfo;
m_arrTools.RemoveAt(i, 1);
}
}
}
BOOL COXToolTipCtrl::UpdateToolRect(CWnd* pWnd, LPCRECT lpRectTool /*=NULL*/, UINT nIDTool /*=0*/)
{
for (int i = 0; i < m_arrTools.GetSize(); i++)
{
COXToolTipInfo* pToolInfo = (COXToolTipInfo*) m_arrTools.GetAt(i);
if (!pToolInfo)
continue;
if (pWnd->GetSafeHwnd() == pToolInfo->hWnd &&
nIDTool == pToolInfo->nIDTool)
{
if (pToolInfo == m_pCurrentToolTip)
{
Pop();
m_pCurrentToolTip = NULL;
}
// Get bounding region for tooltip info
CRect rect;
if (lpRectTool)
{
rect = lpRectTool;
pWnd->ClientToScreen(rect);
}
else
{
pWnd->GetWindowRect(rect);
}
m_pParentWnd->ScreenToClient(rect);
pToolInfo->rectBounds = rect;
return TRUE;
}
}
return FALSE;
}
void COXToolTipCtrl::GetText(CString& str, CWnd* pWnd, UINT nIDTool /*=0*/)
{
COXToolTipInfo* pToolInfo = GetToolInfoPtr(pWnd, nIDTool);
if (pToolInfo)
str = GetTooltipText(pToolInfo);
}
void COXToolTipCtrl::GetMargin( LPRECT lprc) const
{
ASSERT(lprc);
lprc->top = m_rectMargin.top;
lprc->left = m_rectMargin.left;
lprc->bottom = m_rectMargin.bottom;
lprc->right = m_rectMargin.right;
}
void COXToolTipCtrl::SetDelayTime(DWORD dwDuration, int nTime)
{
switch (dwDuration)
{
case TTDT_AUTOPOP:
m_nDisplayTime = nTime;
break;
case TTDT_INITIAL:
m_nDisplayDelay = nTime;
break;
case TTDT_RESHOW:
default:
ASSERT(FALSE);
}
}
int COXToolTipCtrl::GetDelayTime(DWORD dwDuration) const
{
switch (dwDuration)
{
case TTDT_AUTOPOP:
return m_nDisplayTime;
case TTDT_INITIAL:
return m_nDisplayDelay;
case TTDT_RESHOW:
default:
ASSERT(FALSE);
return 0;
}
}
int COXToolTipCtrl::GetMaxTipWidth() const
{
return m_nMaxWidth;
}
int COXToolTipCtrl::SetMaxTipWidth(int nWidth)
{
int nOldWidth = m_nMaxWidth;
m_nMaxWidth = nWidth;
// Update the width for all tooltips whose width is standard.
// Non-standard widths have obviously been modified outside
// of this function, so leave them be.
int nSize = PtrToInt(m_arrTools.GetSize());
for (int i = 0; i < nSize; i++)
{
COXToolTipInfo* pTool = (COXToolTipInfo*) m_arrTools.GetAt(i);
if (!pTool)
continue;
if (pTool->nWidth == nOldWidth)
pTool->nWidth = nWidth;
}
return nOldWidth;
}
COLORREF COXToolTipCtrl::GetTipBkColor() const
{
return m_crBackColor;
}
void COXToolTipCtrl::SetTipBkColor(COLORREF clr)
{
// Update the width for all tooltips whose width is standard.
// Non-standard widths have obviously been modified outside
// of this function, so leave them be.
int nSize = PtrToInt(m_arrTools.GetSize());
for (int i = 0; i < nSize; i++)
{
COXToolTipInfo* pTool = (COXToolTipInfo*) m_arrTools.GetAt(i);
if (!pTool)
continue;
if (pTool->clrBackColor == m_crBackColor)
pTool->clrBackColor = clr;
}
m_crBackColor = clr;
}
COLORREF COXToolTipCtrl::GetTipTextColor() const
{
return m_crTextColor;
}
void COXToolTipCtrl::SetTipTextColor(COLORREF clr)
{
// Update the width for all tooltips whose width is standard.
// Non-standard widths have obviously been modified outside
// of this function, so leave them be.
int nSize = PtrToInt(m_arrTools.GetSize());
for (int i = 0; i < nSize; i++)
{
COXToolTipInfo* pTool = (COXToolTipInfo*) m_arrTools.GetAt(i);
if (!pTool)
continue;
if (pTool->clrTextColor == m_crTextColor)
pTool->clrTextColor = clr;
}
m_crTextColor = clr;
}
BOOL COXToolTipCtrl::HitTest(HWND pWnd, POINT pt, OXTOOLINFO* pToolInfo) const
{
if (!pWnd)
return FALSE;
COXToolTipInfo* pTool = NULL;
// Loop through all registered tool tips to check if we should display one.
// In order to support multiple tool tips per window we will check
// all registered tool tips to make sure if there is a need for a tool tip.
int nSize = PtrToInt(m_arrTools.GetSize());
for (int tipIndex = 0; tipIndex < nSize; tipIndex++)
{
pTool = (COXToolTipInfo*) m_arrTools.GetAt( tipIndex );
if (!pTool)
// the tool tip found is not valid, continue to the next
continue;
if( pWnd == pTool->hWnd )
{ // The window has a tool - but is the cursor currently over it?
if ( pTool->rectBounds.PtInRect(pt) != FALSE )
{
if (pToolInfo)
*pToolInfo = *pTool;
return TRUE;
}
}
else if ( ::IsChild(pWnd, pTool->hWnd ) && pTool->rectBounds.PtInRect(pt) != FALSE )
{ // The user's mouse is over a child window, make a recursive call to HitTest
// function in order to find the proper window that the user has the mouse over.
return HitTest( pTool->hWnd, pt, pToolInfo );
}
}
// No tool in window - return
return FALSE;
}
void COXToolTipCtrl::Pop()
{
ShowWindow(SW_HIDE);
m_bTipCancelled = TRUE;
m_pCurrentToolTip = NULL;
}
BOOL COXToolTipCtrl::GetToolInfo(OXTOOLINFO& ToolInfo, CWnd* pWnd,
UINT nIDTool /*=0*/)
{
COXToolTipInfo *pToolInfo = GetToolInfoPtr(pWnd, nIDTool);
if (pToolInfo == NULL)
return FALSE;
ToolInfo = *pToolInfo;
return TRUE;
}
void COXToolTipCtrl::SetToolInfo(OXTOOLINFO* pToolInfo)
{
COXToolTipInfo *pInfo = GetToolInfoPtr(CWnd::FromHandle(pToolInfo->hwnd),
pToolInfo->uId);
if (pToolInfo != NULL)
*pInfo = *pToolInfo;
}
/////////////////////////////////////////////////////////////////////////////
// COXToolTipCtrl implementation
CString COXToolTipCtrl::GetTooltipText(COXToolTipInfo *pToolTip)
{
CString strTooltipText(_T(""));
if (!pToolTip)
return strTooltipText;
if (pToolTip->szText == LPSTR_TEXTCALLBACK)
{
NMTTDISPINFO nmtt;
nmtt.hdr.hwndFrom = m_hWnd;
nmtt.hdr.idFrom = ::GetDlgCtrlID(pToolTip->hWnd);
nmtt.hdr.code = TTN_NEEDTEXT;
nmtt.lpszText = NULL;
nmtt.hinst = NULL;
nmtt.uFlags = 0;
m_pParentWnd->SendMessage(WM_NOTIFY, (WPARAM) pToolTip->nIDTool, (LPARAM) &nmtt);
if (nmtt.hinst != NULL)
{
TCHAR sz[512];
UINT nLen = PtrToUint(::LoadString(nmtt.hinst,
PtrToUint((UINT_PTR)nmtt.lpszText),
sz,
512));
ASSERT(nLen < 511);
if (nLen > 0)
strTooltipText = sz;
else
strTooltipText.Empty();
}
else if (nmtt.lpszText)
strTooltipText = nmtt.lpszText;
else
strTooltipText = nmtt.szText;
}
else
strTooltipText = pToolTip->strText;
m_bHasExtendedText = (strTooltipText.Find(_T('\r')) != -1);
if (m_bHasExtendedText)
{
if (m_bExtended)
strTooltipText = m_szArrowSpace + GetFieldFromString(strTooltipText, 1, _T('\r'));
else
strTooltipText = GetFieldFromString(strTooltipText, 0, _T('\r')) + m_szArrowSpace;
}
return strTooltipText;
}
LPLOGFONT COXToolTipCtrl::GetSystemToolTipFont() const
{
static LOGFONT LogFont;
//////////////////////////////////////////////////////////////////
// v9.3 update 02 - the NONCLIENTMETRICS struct is an int larger
// on Vista vs XP (iPaddedBorderWidth added). Code compiled for
// WINVER 0x0600 will fail the call to SystemParametersInfo on XP.
// Code compiled for XP should still run on Vista.
int ncmSize = sizeof( NONCLIENTMETRICS );
# if WINVER >= 0x0600
// compiled for Vista - check for OS version
OSVERSIONINFO vi={ sizeof(OSVERSIONINFO) };
ASSERT(GetVersionEx(&vi));
if(vi.dwMajorVersion < 6) {
// running on lesser version - adjust size of NONCLIENTMETRICS struct
ncmSize -= sizeof( int );
}
# endif
NONCLIENTMETRICS ncm={ ncmSize };
if (!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncmSize, &ncm, 0))
return FALSE;
// end NONCLIENTMETRICS mods v9.3 update 02
/////////////////////////////////////////////////
memcpy(&LogFont, &(ncm.lfStatusFont), sizeof(LOGFONT));
return &LogFont;
}
BOOL COXToolTipCtrl::SetLogFont(LPLOGFONT lpLogFont, BOOL bRedraw /*=TRUE*/)
{
ASSERT(lpLogFont);
if (!lpLogFont)
return FALSE;
// Store font as the global default
memcpy(&m_LogFont, lpLogFont, sizeof(LOGFONT));
// If the new font is not the same as the system font, then we will no
// longer update the font in the event of a System Settings Change.
LPLOGFONT lpSysFont = GetSystemToolTipFont();
// Not reliable
//m_bUsingSystemFont = (memcmp(&m_LogFont, lpSysFont, sizeof(LOGFONT)) == 0);
m_bUsingSystemFont = (m_LogFont.lfWeight == lpSysFont->lfWeight &&
m_LogFont.lfItalic == lpSysFont->lfItalic &&
m_LogFont.lfHeight == lpSysFont->lfHeight &&
m_LogFont.lfCharSet == lpSysFont->lfCharSet &&
!_tcscmp(m_LogFont.lfFaceName, lpSysFont->lfFaceName));
// Create the actual font object
m_Font.DeleteObject();
m_Font.CreateFontIndirect(&m_LogFont);
if (bRedraw && ::IsWindow(GetSafeHwnd()))
Invalidate();
return TRUE;
}
CRect COXToolTipCtrl::GetBoundsRect(CString strText, int nWidth) const
{
CWindowDC dc(NULL);
CFont *pOldFont = (CFont*) dc.SelectObject((CFont*)&m_Font);
int nLineWidth = nWidth;
if (nLineWidth == 0)
{
// Count the number of lines of text
int nStart = 0, nNumLines = 0;
CString strTextCopy=strText;
do {
nStart = strTextCopy.Find(_T("\n"));
// Skip found character
if (nStart >= 0)
strTextCopy=strTextCopy.Mid(nStart+1);
nNumLines++;
} while (nStart > 0);
// Find the widest line
for (int i = 0; i < nNumLines; i++)
{
CString strLine = GetFieldFromString(strText, i, _T('\n')) + _T(" ");
nLineWidth = max(nLineWidth, dc.GetTextExtent(strLine).cx);
}
}
CRect rect(0,0, max(0,nLineWidth), 0);
dc.DrawText(strText, rect, DT_CALCRECT | m_dwTextStyle);
rect.bottom += m_rectMargin.top + m_rectMargin.bottom;
rect.right += m_rectMargin.left + m_rectMargin.right;
dc.SelectObject(pOldFont);
return rect;
}
CRect COXToolTipCtrl::CalculateInfoBoxRect(CPoint& pt, COXToolTipInfo* pToolTip,
CRect& rectTextBounds) const
{
CRect InfoRect = rectTextBounds;
InfoRect.OffsetRect(-InfoRect.TopLeft());
InfoRect.OffsetRect(pt);
// Need to check if it will fit on the monitor
// Get the rectangle of the closest monitor
HMONITOR hMonitor = ::MonitorFromRect(InfoRect,MONITOR_DEFAULTTONEAREST);
MONITORINFO mi;
mi.cbSize = sizeof(MONITORINFO);
::GetMonitorInfo(hMonitor, &mi);
// calculate the monitor's range
const int iMixScreenX = mi.rcMonitor.left;
const int iMaxScreenX = mi.rcMonitor.right;
const int iMaxScreenY = mi.rcMonitor.bottom;
// Too far right?
if (InfoRect.right > iMaxScreenX)
InfoRect.OffsetRect(-(InfoRect.right - iMaxScreenX), 0);
// Too far left?
if (InfoRect.left < iMixScreenX)
InfoRect.OffsetRect( -InfoRect.left, iMixScreenX);
// Bottom falling out of screen?
if (InfoRect.bottom > iMaxScreenY)
{
CRect ParentRect;
::GetWindowRect(pToolTip->hWnd, ParentRect);
int nHeight = InfoRect.Height();
InfoRect.top = ParentRect.top - nHeight;
InfoRect.bottom = InfoRect.top + nHeight;
}
return InfoRect;
}
void COXToolTipCtrl::StartNewTool(COXToolTipInfo* pToolInfo)
{
KillTimer(eIDDisplayToolEvent);
KillTimer(eIDCheckToolEvent);
Pop();
m_bTipCancelled = FALSE;
m_pCurrentToolTip = pToolInfo;
SetTimer(eIDDisplayToolEvent, m_nDisplayDelay, NULL);
}
BOOL COXToolTipCtrl::IsCursorInTool(COXToolTipInfo* pToolInfo) const
{
ASSERT(m_pParentWnd);
CPoint pt;
GetCursorPos(&pt);
m_pParentWnd->ScreenToClient(&pt);
return HitTest( FromHandle(pToolInfo->hWnd)->GetSafeHwnd(), pt, NULL );
}
BOOL COXToolTipCtrl::IsCursorInToolTip() const
{
ASSERT(m_pParentWnd);
// Is tooltip visible?
if (m_bTipCancelled || !IsVisible() || !IsWindow(m_hWnd))
return FALSE;
CPoint pt;
GetCursorPos(&pt);
CRect rect;
GetWindowRect(rect);
return rect.PtInRect(pt);
}
COXToolTipInfo* COXToolTipCtrl::FindToolFromPoint(POINT& pt)
{
ASSERT(m_pParentWnd);
CWnd* pWnd = GetChildWindowFromPoint(pt);
if (!pWnd /*||
pWnd->GetSafeHwnd() == m_pParentWnd->GetSafeHwnd()*/)
return NULL;
OXTOOLINFO ti;
BOOL bHitTest = HitTest(pWnd->GetSafeHwnd(), pt, &ti);
if (bHitTest)
return GetToolInfoPtr(CWnd::FromHandle(ti.hwnd), ti.uId);
else
return NULL;
}
COXToolTipInfo* COXToolTipCtrl::GetToolInfoPtr(CWnd* pWnd, UINT_PTR nIDTool /*=0*/)
{
int nSize = PtrToInt(m_arrTools.GetSize());
for (int i = 0; i < nSize; i++)
{
COXToolTipInfo* pToolInfo = (COXToolTipInfo*) m_arrTools.GetAt(i);
if (!pToolInfo)
continue;
if (pWnd->GetSafeHwnd() == pToolInfo->hWnd &&
nIDTool == pToolInfo->nIDTool)
{
return pToolInfo;
}
}
return NULL;
}
CWnd* COXToolTipCtrl::GetChildWindowFromPoint(POINT& point) const
{
ASSERT(m_pParentWnd);
CPoint pt = point;
// Find the window under the cursor
m_pParentWnd->ClientToScreen(&pt);
HWND hWnd = ::WindowFromPoint(pt);
// WindowFromPoint misses disabled windows and such - go for a more
// comprehensive search in this case.
if (hWnd == m_pParentWnd->GetSafeHwnd())
hWnd = m_pParentWnd->ChildWindowFromPoint(point, CWP_ALL)->GetSafeHwnd();
// Check that we aren't over the parent or out of client area
if (!hWnd /*|| hWnd == m_pParentWnd->GetSafeHwnd()*/)
return NULL;
// If it's not part of the main parent window hierarchy, then we are
// not interested
if (!::IsChild(m_pParentWnd->GetSafeHwnd(), hWnd)
&& m_pParentWnd->GetSafeHwnd()!=hWnd)
return NULL;
return CWnd::FromHandle(hWnd);
}
void COXToolTipCtrl::DisplayToolTip(CPoint& pt, BOOL bExtended /*= FALSE*/)
{
ASSERT(::IsWindow(m_hWnd));
if (!m_bActivated || !m_pCurrentToolTip)
return;
m_bExtended = bExtended;
CString str = GetTooltipText(m_pCurrentToolTip);
if (str.IsEmpty())
return;
//Calculate the width and height of the box dynamically
CRect rect = GetBoundsRect(str, m_pCurrentToolTip->nWidth);
rect = CalculateInfoBoxRect(pt, m_pCurrentToolTip, rect);
ShowWindow(SW_HIDE);
SetWindowPos(NULL,
rect.left, rect.top,
rect.Width(), rect.Height(),
SWP_SHOWWINDOW|SWP_NOCOPYBITS|SWP_NOACTIVATE|SWP_NOZORDER);
// ModifyStyle(0, WS_VISIBLE);
// ShowWindow(SW_SHOWNA);
}
CString COXToolTipCtrl::GetFieldFromString(CString ref, int nIndex, TCHAR ch) const
{
CString strReturn;
LPCTSTR pstrStart = ref.LockBuffer();
LPCTSTR pstrBuffer = pstrStart;
int nCurrent = 0;
int nStart = 0;
int nEnd = 0;
int nOldStart = 0;
while (nCurrent <= nIndex && *pstrBuffer != _T('\0'))
{
if (*pstrBuffer == ch)
{
nOldStart = nStart;
nStart = nEnd+1;
nCurrent++;
}
nEnd++;
pstrBuffer++;
}
// May have reached the end of the string
if (*pstrBuffer == _T('\0'))
{
nOldStart = nStart;
nEnd++;
}
ref.UnlockBuffer();
if (nCurrent < nIndex)
{
//TRACE1("Warning: GetStringField - Couldn't find field %d.\n", nIndex);
return strReturn;
}
return ref.Mid(nOldStart, nEnd-nOldStart-1);
}