Click here to Skip to main content
15,881,877 members
Articles / Desktop Programming / MFC

The Ultimate Toolbox - Updates and User Contributions

Rate me:
Please Sign up or sign in to vote.
4.79/5 (26 votes)
12 Feb 2013CPOL8 min read 254.5K   23.6K   170  
Updates and User Contributions for the Ultimate Toolbox Libraries
// ==========================================================================
//                     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);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Canada Canada
In January 2005, David Cunningham and Chris Maunder created TheUltimateToolbox.com, a new group dedicated to the continued development, support and growth of Dundas Software’s award winning line of MFC, C++ and ActiveX control products.

Ultimate Grid for MFC, Ultimate Toolbox for MFC, and Ultimate TCP/IP have been stalwarts of C++/MFC development for a decade. Thousands of developers have used these products to speed their time to market, improve the quality of their finished products, and enhance the reliability and flexibility of their software.
This is a Organisation

476 members

Comments and Discussions