1060 lines
29 KiB
C++
1060 lines
29 KiB
C++
// ==========================================================================
|
||
// Class Implementation : COXToolTipCtrl
|
||
// ==========================================================================
|
||
|
||
// Source file : OXToolTipCtrl.cpp
|
||
|
||
// Copyright © Dundas Software Ltd. 1999, All Rights Reserved
|
||
|
||
// //////////////////////////////////////////////////////////////////////////
|
||
|
||
#include "OXToolTipCtrl.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 till the tooltip is
|
||
// displayed
|
||
m_nDisplayTime(5000), // Length of time the tooltip is displayed
|
||
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 = 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 peridodically check
|
||
// that the mouse is still within the bounds of the tool
|
||
void COXToolTipCtrl::OnTimer(UINT 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))
|
||
{
|
||
GetCursorPos(&pt);
|
||
pt += m_ptOffset;
|
||
DisplayToolTip(pt);
|
||
m_nElapsedTime = 0;
|
||
SetTimer(eIDCheckToolEvent, m_nCheckInterval, NULL);
|
||
}
|
||
break;
|
||
|
||
// The tooltip is visible, check it's (a) not been around too long, and
|
||
// (b) 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 has has 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_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 check the if we have a current tooltip, that it's
|
||
// 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 interupted 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*/)
|
||
{
|
||
int nSize = 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)
|
||
{
|
||
if (pToolInfo == m_pCurrentToolTip)
|
||
{
|
||
Pop();
|
||
m_pCurrentToolTip = NULL;
|
||
}
|
||
delete pToolInfo;
|
||
m_arrTools.RemoveAt(i, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
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 = 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 = 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 = 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(CWnd *pWnd, POINT pt, OXTOOLINFO* pToolInfo) const
|
||
{
|
||
if (!pWnd)
|
||
return FALSE;
|
||
|
||
COXToolTipInfo* pTool = NULL;
|
||
|
||
// Check that the window has been registered as containing a tool
|
||
BOOL bFoundToolWnd = FALSE;
|
||
int nSize = m_arrTools.GetSize();
|
||
for (int i = 0; i < nSize; i++)
|
||
{
|
||
pTool = (COXToolTipInfo*) m_arrTools.GetAt(i);
|
||
if (!pTool)
|
||
continue;
|
||
|
||
if (pWnd->GetSafeHwnd() == pTool->hWnd ||
|
||
::IsChild(pTool->hWnd, pWnd->GetSafeHwnd()))
|
||
{
|
||
if (pToolInfo)
|
||
*pToolInfo = *pTool;
|
||
bFoundToolWnd = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// No tool in window - return
|
||
if (!bFoundToolWnd || pTool == NULL)
|
||
return FALSE;
|
||
|
||
// The window is a tool - but is the cursor currently over it?
|
||
CWnd* pScreenWnd = GetChildWindowFromPoint(pt);
|
||
|
||
if (pTool->hWnd != pScreenWnd->GetSafeHwnd() &&
|
||
!::IsChild(pTool->hWnd, pScreenWnd->GetSafeHwnd()))
|
||
{
|
||
return FALSE;
|
||
}
|
||
else
|
||
return pTool->rectBounds.PtInRect(pt);
|
||
}
|
||
|
||
void COXToolTipCtrl::Pop()
|
||
{
|
||
ShowWindow(SW_HIDE);
|
||
/* if (::IsWindow(m_hWnd))
|
||
ModifyStyle(WS_VISIBLE, 0);
|
||
*/
|
||
m_bTipCancelled = TRUE;
|
||
}
|
||
|
||
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 = ::LoadString(nmtt.hinst, (UINT) 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;
|
||
|
||
NONCLIENTMETRICS ncm;
|
||
ncm.cbSize = sizeof(NONCLIENTMETRICS);
|
||
if (!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0))
|
||
return FALSE;
|
||
|
||
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 it'll fit on screen
|
||
CSize ScreenSize(::GetSystemMetrics(SM_CXSCREEN),
|
||
::GetSystemMetrics(SM_CYSCREEN));
|
||
|
||
// Too far right?
|
||
if (InfoRect.right > ScreenSize.cx)
|
||
InfoRect.OffsetRect(-(InfoRect.right - ScreenSize.cx), 0);
|
||
|
||
// Too far left?
|
||
if (InfoRect.left < 0)
|
||
InfoRect.OffsetRect( -InfoRect.left, 0);
|
||
|
||
// Bottom falling out of screen?
|
||
if (InfoRect.bottom > ScreenSize.cy)
|
||
{
|
||
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(CWnd::FromHandle(pToolInfo->hWnd), 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, pt, &ti);
|
||
|
||
if (bHitTest)
|
||
return GetToolInfoPtr(CWnd::FromHandle(ti.hwnd), ti.uId);
|
||
else
|
||
return NULL;
|
||
}
|
||
|
||
COXToolTipInfo* COXToolTipCtrl::GetToolInfoPtr(CWnd* pWnd, UINT nIDTool /*=0*/)
|
||
{
|
||
int nSize = 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 heirachy, then we are
|
||
// not interested
|
||
if (!::IsChild(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);
|
||
}
|