// This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1998 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and related // electronic documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include "stdafx.h" #ifdef AFX_CMNCTL_SEG #pragma code_seg(AFX_CMNCTL_SEG) #endif #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define new DEBUG_NEW ///////////////////////////////////////////////////////////////////////////// // CToolTipCtrl BEGIN_MESSAGE_MAP(CToolTipCtrl, CWnd) //{{AFX_MSG_MAP(CToolTipCtrl) ON_MESSAGE(WM_DISABLEMODAL, OnDisableModal) ON_MESSAGE(TTM_WINDOWFROMPOINT, OnWindowFromPoint) ON_MESSAGE(TTM_ADDTOOL, OnAddTool) ON_WM_ENABLE() //}}AFX_MSG_MAP END_MESSAGE_MAP() CToolTipCtrl::CToolTipCtrl() { } BOOL CToolTipCtrl::Create(CWnd* pParentWnd, DWORD dwStyle) { // initialize common controls VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTL_BAR_REG)); BOOL bResult = CWnd::CreateEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | dwStyle, // force WS_POPUP CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, pParentWnd->GetSafeHwnd(), NULL, NULL); if (bResult) SetOwner(pParentWnd); return bResult; } CToolTipCtrl::~CToolTipCtrl() { DestroyWindow(); } BOOL CToolTipCtrl::DestroyToolTipCtrl() { #ifdef _AFXDLL BOOL bDestroy = (AfxGetModuleState() == m_pModuleState); #else BOOL bDestroy = TRUE; #endif if (bDestroy) { DestroyWindow(); delete this; } return bDestroy; } LRESULT CToolTipCtrl::OnAddTool(WPARAM wParam, LPARAM lParam) { TOOLINFO ti = *(LPTOOLINFO)lParam; if ((ti.hinst == NULL) && (ti.lpszText != LPSTR_TEXTCALLBACK) && (ti.lpszText != NULL)) { void* pv; if (!m_mapString.Lookup(ti.lpszText, pv)) m_mapString.SetAt(ti.lpszText, NULL); // set lpszText to point to the permanent memory associated // with the CString VERIFY(m_mapString.LookupKey(ti.lpszText, ti.lpszText)); } return DefWindowProc(TTM_ADDTOOL, wParam, (LPARAM)&ti); } LRESULT CToolTipCtrl::OnDisableModal(WPARAM, LPARAM) { SendMessage(TTM_ACTIVATE, FALSE); return FALSE; } void CToolTipCtrl::OnEnable(BOOL bEnable) { SendMessage(TTM_ACTIVATE, bEnable); } LRESULT CToolTipCtrl::OnWindowFromPoint(WPARAM, LPARAM lParam) { ASSERT(lParam != NULL); // the default implementation of tooltips just calls WindowFromPoint // which does not work for certain kinds of combo boxes CPoint pt = *(POINT*)lParam; HWND hWnd = ::WindowFromPoint(pt); if (hWnd == NULL) return 0; // try to hit combobox instead of edit control for CBS_DROPDOWN styles HWND hWndTemp = ::GetParent(hWnd); if (hWndTemp != NULL && _AfxIsComboBoxControl(hWndTemp, CBS_DROPDOWN)) return (LRESULT)hWndTemp; // handle special case of disabled child windows ::ScreenToClient(hWnd, &pt); hWndTemp = _AfxChildWindowFromPoint(hWnd, pt); if (hWndTemp != NULL && !::IsWindowEnabled(hWndTemp)) return (LRESULT)hWndTemp; return (LRESULT)hWnd; } BOOL CToolTipCtrl::AddTool(CWnd* pWnd, LPCTSTR lpszText, LPCRECT lpRectTool, UINT nIDTool) { ASSERT(::IsWindow(m_hWnd)); ASSERT(pWnd != NULL); ASSERT(lpszText != NULL); // the toolrect and toolid must both be zero or both valid ASSERT((lpRectTool != NULL && nIDTool != 0) || (lpRectTool == NULL) && (nIDTool == 0)); TOOLINFO ti; FillInToolInfo(ti, pWnd, nIDTool); if (lpRectTool != NULL) memcpy(&ti.rect, lpRectTool, sizeof(RECT)); ti.lpszText = (LPTSTR)lpszText; return (BOOL) ::SendMessage(m_hWnd, TTM_ADDTOOL, 0, (LPARAM)&ti); } BOOL CToolTipCtrl::AddTool(CWnd* pWnd, UINT nIDText, LPCRECT lpRectTool, UINT nIDTool) { ASSERT(::IsWindow(m_hWnd)); ASSERT(nIDText != 0); ASSERT(pWnd != NULL); // the toolrect and toolid must both be zero or both valid ASSERT((lpRectTool != NULL && nIDTool != 0) || (lpRectTool == NULL) && (nIDTool == 0)); TOOLINFO ti; FillInToolInfo(ti, pWnd, nIDTool); if (lpRectTool != NULL) memcpy(&ti.rect, lpRectTool, sizeof(RECT)); ti.hinst = AfxFindResourceHandle(MAKEINTRESOURCE((nIDText>>4)+1), RT_STRING); ASSERT(ti.hinst != NULL); ti.lpszText = (LPTSTR)MAKEINTRESOURCE(nIDText); return (BOOL) ::SendMessage(m_hWnd, TTM_ADDTOOL, 0, (LPARAM)&ti); } void CToolTipCtrl::DelTool(CWnd* pWnd, UINT nIDTool) { ASSERT(::IsWindow(m_hWnd)); ASSERT(pWnd != NULL); TOOLINFO ti; FillInToolInfo(ti, pWnd, nIDTool); ::SendMessage(m_hWnd, TTM_DELTOOL, 0, (LPARAM)&ti); } void CToolTipCtrl::GetText(CString& str, CWnd* pWnd, UINT nIDTool) const { ASSERT(::IsWindow(m_hWnd)); ASSERT(pWnd != NULL); TOOLINFO ti; FillInToolInfo(ti, pWnd, nIDTool); ti.lpszText = str.GetBuffer(256); ::SendMessage(m_hWnd, TTM_GETTEXT, 0, (LPARAM)&ti); str.ReleaseBuffer(); } BOOL CToolTipCtrl::GetToolInfo(CToolInfo& ToolInfo, CWnd* pWnd, UINT nIDTool) const { ASSERT(::IsWindow(m_hWnd)); ASSERT(pWnd != NULL); FillInToolInfo(ToolInfo, pWnd, nIDTool); ToolInfo.lpszText = ToolInfo.szText; return (BOOL)::SendMessage(m_hWnd, TTM_GETTOOLINFO, 0, (LPARAM)&ToolInfo); } BOOL CToolTipCtrl::HitTest(CWnd* pWnd, CPoint pt, LPTOOLINFO lpToolInfo) const { ASSERT(::IsWindow(m_hWnd)); ASSERT(pWnd != NULL); ASSERT(lpToolInfo != NULL); TTHITTESTINFO hti; memset(&hti, 0, sizeof(hti)); hti.ti.cbSize = sizeof(AFX_OLDTOOLINFO); hti.hwnd = pWnd->GetSafeHwnd(); hti.pt.x = pt.x; hti.pt.y = pt.y; if ((BOOL)::SendMessage(m_hWnd, TTM_HITTEST, 0, (LPARAM)&hti)) { memcpy(lpToolInfo, &hti.ti, sizeof(AFX_OLDTOOLINFO)); return TRUE; } return FALSE; } void CToolTipCtrl::SetToolRect(CWnd* pWnd, UINT nIDTool, LPCRECT lpRect) { ASSERT(::IsWindow(m_hWnd)); ASSERT(pWnd != NULL); ASSERT(nIDTool != 0); TOOLINFO ti; FillInToolInfo(ti, pWnd, nIDTool); memcpy(&ti.rect, lpRect, sizeof(RECT)); ::SendMessage(m_hWnd, TTM_NEWTOOLRECT, 0, (LPARAM)&ti); } void CToolTipCtrl::UpdateTipText(LPCTSTR lpszText, CWnd* pWnd, UINT nIDTool) { ASSERT(::IsWindow(m_hWnd)); ASSERT(pWnd != NULL); TOOLINFO ti; FillInToolInfo(ti, pWnd, nIDTool); ti.lpszText = (LPTSTR)lpszText; ::SendMessage(m_hWnd, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); } void CToolTipCtrl::UpdateTipText(UINT nIDText, CWnd* pWnd, UINT nIDTool) { ASSERT(nIDText != 0); CString str; VERIFY(str.LoadString(nIDText)); UpdateTipText(str, pWnd, nIDTool); } ///////////////////////////////////////////////////////////////////////////// // CToolTipCtrl Implementation void CToolTipCtrl::FillInToolInfo(TOOLINFO& ti, CWnd* pWnd, UINT nIDTool) const { memset(&ti, 0, sizeof(AFX_OLDTOOLINFO)); ti.cbSize = sizeof(AFX_OLDTOOLINFO); HWND hwnd = pWnd->GetSafeHwnd(); if (nIDTool == 0) { ti.hwnd = ::GetParent(hwnd); ti.uFlags = TTF_IDISHWND; ti.uId = (UINT)hwnd; } else { ti.hwnd = hwnd; ti.uFlags = 0; ti.uId = nIDTool; } } ///////////////////////////////////////////////////////////////////////////// // CWnd tooltip support BOOL CWnd::_EnableToolTips(BOOL bEnable, UINT nFlag) { ASSERT(nFlag == WF_TOOLTIPS || nFlag == WF_TRACKINGTOOLTIPS); _AFX_THREAD_STATE* pThreadState = AfxGetThreadState(); CToolTipCtrl* pToolTip = pThreadState->m_pToolTip; if (!bEnable) { // nothing to do if tooltips not enabled if (!(m_nFlags & nFlag)) return TRUE; // cancel tooltip if this window is active if (pThreadState->m_pLastHit == this) CancelToolTips(TRUE); // remove "dead-area" toolbar if (pToolTip->GetSafeHwnd() != NULL) { TOOLINFO ti; memset(&ti, 0, sizeof(TOOLINFO)); ti.cbSize = sizeof(AFX_OLDTOOLINFO); ti.uFlags = TTF_IDISHWND; ti.hwnd = m_hWnd; ti.uId = (UINT)m_hWnd; pToolTip->SendMessage(TTM_DELTOOL, 0, (LPARAM)&ti); } // success m_nFlags &= ~nFlag; return TRUE; } // if already enabled for tooltips, nothing to do if (!(m_nFlags & nFlag)) { // success AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE(); pModuleState->m_pfnFilterToolTipMessage = &CWnd::_FilterToolTipMessage; m_nFlags |= nFlag; } return TRUE; } BOOL CWnd::EnableToolTips(BOOL bEnable) { return _EnableToolTips(bEnable, WF_TOOLTIPS); } BOOL CWnd::EnableTrackingToolTips(BOOL bEnable) { return _EnableToolTips(bEnable, WF_TRACKINGTOOLTIPS); } AFX_STATIC void AFXAPI _AfxRelayToolTipMessage(CToolTipCtrl* pToolTip, MSG* pMsg) { // transate the message based on TTM_WINDOWFROMPOINT MSG msg = *pMsg; msg.hwnd = (HWND)pToolTip->SendMessage(TTM_WINDOWFROMPOINT, 0, (LPARAM)&msg.pt); CPoint pt = pMsg->pt; if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) ::ScreenToClient(msg.hwnd, &pt); msg.lParam = MAKELONG(pt.x, pt.y); // relay mouse event before deleting old tool pToolTip->SendMessage(TTM_RELAYEVENT, 0, (LPARAM)&msg); } void PASCAL CWnd::_FilterToolTipMessage(MSG* pMsg, CWnd* pWnd) { pWnd->FilterToolTipMessage(pMsg); } void CWnd::FilterToolTipMessage(MSG* pMsg) { // this CWnd has tooltips enabled UINT message = pMsg->message; if ((message == WM_MOUSEMOVE || message == WM_NCMOUSEMOVE || message == WM_LBUTTONUP || message == WM_RBUTTONUP || message == WM_MBUTTONUP) && (GetKeyState(VK_LBUTTON) >= 0 && GetKeyState(VK_RBUTTON) >= 0 && GetKeyState(VK_MBUTTON) >= 0)) { // make sure that tooltips are not already being handled CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd); while (pWnd != NULL && !(pWnd->m_nFlags & (WF_TOOLTIPS|WF_TRACKINGTOOLTIPS))) { pWnd = pWnd->GetParent(); } if (pWnd != this) { if (pWnd == NULL) { // tooltips not enabled on this CWnd, clear last state data _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); pThreadState->m_pLastHit = NULL; pThreadState->m_nLastHit = -1; } return; } _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); CToolTipCtrl* pToolTip = pThreadState->m_pToolTip; CWnd* pOwner = GetParentOwner(); if (pToolTip != NULL && pToolTip->GetOwner() != pOwner) { pToolTip->DestroyWindow(); delete pToolTip; pThreadState->m_pToolTip = NULL; pToolTip = NULL; } if (pToolTip == NULL) { pToolTip = new CToolTipCtrl; if (!pToolTip->Create(pOwner, TTS_ALWAYSTIP)) { delete pToolTip; return; } pToolTip->SendMessage(TTM_ACTIVATE, FALSE); pThreadState->m_pToolTip = pToolTip; } ASSERT_VALID(pToolTip); ASSERT(::IsWindow(pToolTip->m_hWnd)); // add a "dead-area" tool for areas between toolbar buttons TOOLINFO ti; memset(&ti, 0, sizeof(TOOLINFO)); ti.cbSize = sizeof(AFX_OLDTOOLINFO); ti.uFlags = TTF_IDISHWND; ti.hwnd = m_hWnd; ti.uId = (UINT)m_hWnd; if (!pToolTip->SendMessage(TTM_GETTOOLINFO, 0, (LPARAM)&ti)) { ASSERT(ti.uFlags == TTF_IDISHWND); ASSERT(ti.hwnd == m_hWnd); ASSERT(ti.uId == (UINT)m_hWnd); VERIFY(pToolTip->SendMessage(TTM_ADDTOOL, 0, (LPARAM)&ti)); } // determine which tool was hit CPoint point = pMsg->pt; ::ScreenToClient(m_hWnd, &point); TOOLINFO tiHit; memset(&tiHit, 0, sizeof(TOOLINFO)); tiHit.cbSize = sizeof(AFX_OLDTOOLINFO); int nHit = OnToolHitTest(point, &tiHit); // build new toolinfo and if different than current, register it CWnd* pHitWnd = nHit == -1 ? NULL : this; if (pThreadState->m_nLastHit != nHit || pThreadState->m_pLastHit != pHitWnd) { if (nHit != -1) { // add new tool and activate the tip ti = tiHit; ti.uFlags &= ~(TTF_NOTBUTTON|TTF_ALWAYSTIP); if (m_nFlags & WF_TRACKINGTOOLTIPS) ti.uFlags |= TTF_TRACK; VERIFY(pToolTip->SendMessage(TTM_ADDTOOL, 0, (LPARAM)&ti)); if ((tiHit.uFlags & TTF_ALWAYSTIP) || IsTopParentActive()) { // allow the tooltip to popup when it should pToolTip->SendMessage(TTM_ACTIVATE, TRUE); if (m_nFlags & WF_TRACKINGTOOLTIPS) pToolTip->SendMessage(TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti); // bring the tooltip window above other popup windows ::SetWindowPos(pToolTip->m_hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER); } } else { pToolTip->SendMessage(TTM_ACTIVATE, FALSE); } // relay mouse event before deleting old tool _AfxRelayToolTipMessage(pToolTip, pMsg); // now safe to delete the old tool if (pThreadState->m_lastInfo.cbSize >= sizeof(AFX_OLDTOOLINFO)) pToolTip->SendMessage(TTM_DELTOOL, 0, (LPARAM)&pThreadState->m_lastInfo); pThreadState->m_pLastHit = pHitWnd; pThreadState->m_nLastHit = nHit; pThreadState->m_lastInfo = tiHit; } else { if (m_nFlags & WF_TRACKINGTOOLTIPS) { POINT pt; ::GetCursorPos( &pt ); pToolTip->SendMessage(TTM_TRACKPOSITION, 0, MAKELPARAM(pt.x, pt.y)); } else { // relay mouse events through the tooltip if (nHit != -1) _AfxRelayToolTipMessage(pToolTip, pMsg); } } if ((tiHit.lpszText != LPSTR_TEXTCALLBACK) && (tiHit.hinst == 0)) free(tiHit.lpszText); } else if (m_nFlags & (WF_TOOLTIPS|WF_TRACKINGTOOLTIPS)) { // make sure that tooltips are not already being handled CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd); while (pWnd != NULL && pWnd != this && !(pWnd->m_nFlags & (WF_TOOLTIPS|WF_TRACKINGTOOLTIPS))) pWnd = pWnd->GetParent(); if (pWnd != this) return; BOOL bKeys = (message >= WM_KEYFIRST && message <= WM_KEYLAST) || (message >= WM_SYSKEYFIRST && message <= WM_SYSKEYLAST); if ((m_nFlags & WF_TRACKINGTOOLTIPS) == 0 && (bKeys || (message == WM_LBUTTONDOWN || message == WM_LBUTTONDBLCLK) || (message == WM_RBUTTONDOWN || message == WM_RBUTTONDBLCLK) || (message == WM_MBUTTONDOWN || message == WM_MBUTTONDBLCLK) || (message == WM_NCLBUTTONDOWN || message == WM_NCLBUTTONDBLCLK) || (message == WM_NCRBUTTONDOWN || message == WM_NCRBUTTONDBLCLK) || (message == WM_NCMBUTTONDOWN || message == WM_NCMBUTTONDBLCLK))) { CWnd::CancelToolTips(bKeys); } } } ///////////////////////////////////////////////////////////////////////////// #ifdef AFX_INIT_SEG #pragma code_seg(AFX_INIT_SEG) #endif IMPLEMENT_DYNAMIC(CToolTipCtrl, CWnd) /////////////////////////////////////////////////////////////////////////////