// This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1995 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) //}}AFX_MSG_MAP END_MESSAGE_MAP() CToolTipCtrl::CToolTipCtrl() { } BOOL CToolTipCtrl::Create(CWnd* pParentWnd, DWORD dwStyle) { 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, 0); return FALSE; } 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.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(TOOLINFO)); 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(ti)); ti.cbSize = sizeof(ti); 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) { _AFX_THREAD_STATE* pThreadState = AfxGetThreadState(); CToolTipCtrl* pToolTip = pThreadState->m_pToolTip; if (!bEnable) { // nothing to do if tooltips not enabled if (!(m_nFlags & WF_TOOLTIPS)) 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(TOOLINFO); ti.uFlags = TTF_IDISHWND; ti.hwnd = m_hWnd; ti.uId = (UINT)m_hWnd; pToolTip->SendMessage(TTM_DELTOOL, 0, (LPARAM)&ti); } // success m_nFlags &= ~WF_TOOLTIPS; return TRUE; } // if already enabled for tooltips, nothing to do if (!(m_nFlags & WF_TOOLTIPS)) { // success AFX_MODULE_PROCESS_STATE* pState = AfxGetModuleProcessState(); pState->m_pfnFilterToolTipMessage = &CWnd::_FilterToolTipMessage; m_nFlags |= WF_TOOLTIPS; } return TRUE; } static void AFXAPI RelayToolTipMessage(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) { UINT message = pMsg->message; if ((m_nFlags & WF_TOOLTIPS) && (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 != this && !(pWnd->m_nFlags & WF_TOOLTIPS)) pWnd = pWnd->GetParent(); if (pWnd != this) return; _AFX_THREAD_STATE* pThreadState = AfxGetThreadState(); 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(TOOLINFO); ti.uFlags = TTF_IDISHWND; ti.hwnd = m_hWnd; ti.uId = (UINT)m_hWnd; if (!pToolTip->SendMessage(TTM_GETTOOLINFO, 0, (LPARAM)&ti)) { ASSERT(ti.cbSize == sizeof(TOOLINFO)); 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(TOOLINFO); 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); 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); // bring the tooltip window above other popup windows ::SetWindowPos(pToolTip->m_hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE); } } // relay mouse event before deleting old tool RelayToolTipMessage(pToolTip, pMsg); // now safe to delete the old tool if (pThreadState->m_lastInfo.cbSize == sizeof(TOOLINFO)) pToolTip->SendMessage(TTM_DELTOOL, 0, (LPARAM)&pThreadState->m_lastInfo); pThreadState->m_pLastHit = pHitWnd; pThreadState->m_nLastHit = nHit; pThreadState->m_lastInfo = tiHit; } else { // relay mouse events through the tooltip if (nHit != -1) RelayToolTipMessage(pToolTip, pMsg); } if (tiHit.lpszText != LPSTR_TEXTCALLBACK) free(tiHit.lpszText); } } ///////////////////////////////////////////////////////////////////////////// #ifdef AFX_INIT_SEG #pragma code_seg(AFX_INIT_SEG) #endif IMPLEMENT_DYNAMIC(CToolTipCtrl, CWnd) /////////////////////////////////////////////////////////////////////////////