// ========================================================================== // 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 #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. 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)) { 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; 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 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); }