mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
12 KiB
443 lines
12 KiB
// 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)
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|