tooltip.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. // This is a part of the Microsoft Foundation Classes C++ library.
  2. // Copyright (C) 1992-1998 Microsoft Corporation
  3. // All rights reserved.
  4. //
  5. // This source code is only intended as a supplement to the
  6. // Microsoft Foundation Classes Reference and related
  7. // electronic documentation provided with the library.
  8. // See these sources for detailed information regarding the
  9. // Microsoft Foundation Classes product.
  10. #include "stdafx.h"
  11. #ifdef AFX_CMNCTL_SEG
  12. #pragma code_seg(AFX_CMNCTL_SEG)
  13. #endif
  14. #ifdef _DEBUG
  15. #undef THIS_FILE
  16. static char THIS_FILE[] = __FILE__;
  17. #endif
  18. #define new DEBUG_NEW
  19. /////////////////////////////////////////////////////////////////////////////
  20. // CToolTipCtrl
  21. BEGIN_MESSAGE_MAP(CToolTipCtrl, CWnd)
  22. //{{AFX_MSG_MAP(CToolTipCtrl)
  23. ON_MESSAGE(WM_DISABLEMODAL, OnDisableModal)
  24. ON_MESSAGE(TTM_WINDOWFROMPOINT, OnWindowFromPoint)
  25. ON_MESSAGE(TTM_ADDTOOL, OnAddTool)
  26. ON_WM_ENABLE()
  27. //}}AFX_MSG_MAP
  28. END_MESSAGE_MAP()
  29. CToolTipCtrl::CToolTipCtrl()
  30. {
  31. }
  32. BOOL CToolTipCtrl::Create(CWnd* pParentWnd, DWORD dwStyle)
  33. {
  34. // initialize common controls
  35. VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTL_BAR_REG));
  36. BOOL bResult = CWnd::CreateEx(NULL, TOOLTIPS_CLASS, NULL,
  37. WS_POPUP | dwStyle, // force WS_POPUP
  38. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  39. pParentWnd->GetSafeHwnd(), NULL, NULL);
  40. if (bResult)
  41. SetOwner(pParentWnd);
  42. return bResult;
  43. }
  44. CToolTipCtrl::~CToolTipCtrl()
  45. {
  46. DestroyWindow();
  47. }
  48. BOOL CToolTipCtrl::DestroyToolTipCtrl()
  49. {
  50. #ifdef _AFXDLL
  51. BOOL bDestroy = (AfxGetModuleState() == m_pModuleState);
  52. #else
  53. BOOL bDestroy = TRUE;
  54. #endif
  55. if (bDestroy)
  56. {
  57. DestroyWindow();
  58. delete this;
  59. }
  60. return bDestroy;
  61. }
  62. LRESULT CToolTipCtrl::OnAddTool(WPARAM wParam, LPARAM lParam)
  63. {
  64. TOOLINFO ti = *(LPTOOLINFO)lParam;
  65. if ((ti.hinst == NULL) && (ti.lpszText != LPSTR_TEXTCALLBACK)
  66. && (ti.lpszText != NULL))
  67. {
  68. void* pv;
  69. if (!m_mapString.Lookup(ti.lpszText, pv))
  70. m_mapString.SetAt(ti.lpszText, NULL);
  71. // set lpszText to point to the permanent memory associated
  72. // with the CString
  73. VERIFY(m_mapString.LookupKey(ti.lpszText, ti.lpszText));
  74. }
  75. return DefWindowProc(TTM_ADDTOOL, wParam, (LPARAM)&ti);
  76. }
  77. LRESULT CToolTipCtrl::OnDisableModal(WPARAM, LPARAM)
  78. {
  79. SendMessage(TTM_ACTIVATE, FALSE);
  80. return FALSE;
  81. }
  82. void CToolTipCtrl::OnEnable(BOOL bEnable)
  83. {
  84. SendMessage(TTM_ACTIVATE, bEnable);
  85. }
  86. LRESULT CToolTipCtrl::OnWindowFromPoint(WPARAM, LPARAM lParam)
  87. {
  88. ASSERT(lParam != NULL);
  89. // the default implementation of tooltips just calls WindowFromPoint
  90. // which does not work for certain kinds of combo boxes
  91. CPoint pt = *(POINT*)lParam;
  92. HWND hWnd = ::WindowFromPoint(pt);
  93. if (hWnd == NULL)
  94. return 0;
  95. // try to hit combobox instead of edit control for CBS_DROPDOWN styles
  96. HWND hWndTemp = ::GetParent(hWnd);
  97. if (hWndTemp != NULL && _AfxIsComboBoxControl(hWndTemp, CBS_DROPDOWN))
  98. return (LRESULT)hWndTemp;
  99. // handle special case of disabled child windows
  100. ::ScreenToClient(hWnd, &pt);
  101. hWndTemp = _AfxChildWindowFromPoint(hWnd, pt);
  102. if (hWndTemp != NULL && !::IsWindowEnabled(hWndTemp))
  103. return (LRESULT)hWndTemp;
  104. return (LRESULT)hWnd;
  105. }
  106. BOOL CToolTipCtrl::AddTool(CWnd* pWnd, LPCTSTR lpszText, LPCRECT lpRectTool,
  107. UINT nIDTool)
  108. {
  109. ASSERT(::IsWindow(m_hWnd));
  110. ASSERT(pWnd != NULL);
  111. ASSERT(lpszText != NULL);
  112. // the toolrect and toolid must both be zero or both valid
  113. ASSERT((lpRectTool != NULL && nIDTool != 0) ||
  114. (lpRectTool == NULL) && (nIDTool == 0));
  115. TOOLINFO ti;
  116. FillInToolInfo(ti, pWnd, nIDTool);
  117. if (lpRectTool != NULL)
  118. memcpy(&ti.rect, lpRectTool, sizeof(RECT));
  119. ti.lpszText = (LPTSTR)lpszText;
  120. return (BOOL) ::SendMessage(m_hWnd, TTM_ADDTOOL, 0, (LPARAM)&ti);
  121. }
  122. BOOL CToolTipCtrl::AddTool(CWnd* pWnd, UINT nIDText, LPCRECT lpRectTool,
  123. UINT nIDTool)
  124. {
  125. ASSERT(::IsWindow(m_hWnd));
  126. ASSERT(nIDText != 0);
  127. ASSERT(pWnd != NULL);
  128. // the toolrect and toolid must both be zero or both valid
  129. ASSERT((lpRectTool != NULL && nIDTool != 0) ||
  130. (lpRectTool == NULL) && (nIDTool == 0));
  131. TOOLINFO ti;
  132. FillInToolInfo(ti, pWnd, nIDTool);
  133. if (lpRectTool != NULL)
  134. memcpy(&ti.rect, lpRectTool, sizeof(RECT));
  135. ti.hinst = AfxFindResourceHandle(MAKEINTRESOURCE((nIDText>>4)+1),
  136. RT_STRING);
  137. ASSERT(ti.hinst != NULL);
  138. ti.lpszText = (LPTSTR)MAKEINTRESOURCE(nIDText);
  139. return (BOOL) ::SendMessage(m_hWnd, TTM_ADDTOOL, 0, (LPARAM)&ti);
  140. }
  141. void CToolTipCtrl::DelTool(CWnd* pWnd, UINT nIDTool)
  142. {
  143. ASSERT(::IsWindow(m_hWnd));
  144. ASSERT(pWnd != NULL);
  145. TOOLINFO ti;
  146. FillInToolInfo(ti, pWnd, nIDTool);
  147. ::SendMessage(m_hWnd, TTM_DELTOOL, 0, (LPARAM)&ti);
  148. }
  149. void CToolTipCtrl::GetText(CString& str, CWnd* pWnd, UINT nIDTool) const
  150. {
  151. ASSERT(::IsWindow(m_hWnd));
  152. ASSERT(pWnd != NULL);
  153. TOOLINFO ti;
  154. FillInToolInfo(ti, pWnd, nIDTool);
  155. ti.lpszText = str.GetBuffer(256);
  156. ::SendMessage(m_hWnd, TTM_GETTEXT, 0, (LPARAM)&ti);
  157. str.ReleaseBuffer();
  158. }
  159. BOOL CToolTipCtrl::GetToolInfo(CToolInfo& ToolInfo, CWnd* pWnd,
  160. UINT nIDTool) const
  161. {
  162. ASSERT(::IsWindow(m_hWnd));
  163. ASSERT(pWnd != NULL);
  164. FillInToolInfo(ToolInfo, pWnd, nIDTool);
  165. ToolInfo.lpszText = ToolInfo.szText;
  166. return (BOOL)::SendMessage(m_hWnd, TTM_GETTOOLINFO, 0, (LPARAM)&ToolInfo);
  167. }
  168. BOOL CToolTipCtrl::HitTest(CWnd* pWnd, CPoint pt, LPTOOLINFO lpToolInfo) const
  169. {
  170. ASSERT(::IsWindow(m_hWnd));
  171. ASSERT(pWnd != NULL);
  172. ASSERT(lpToolInfo != NULL);
  173. TTHITTESTINFO hti;
  174. memset(&hti, 0, sizeof(hti));
  175. hti.ti.cbSize = sizeof(AFX_OLDTOOLINFO);
  176. hti.hwnd = pWnd->GetSafeHwnd();
  177. hti.pt.x = pt.x;
  178. hti.pt.y = pt.y;
  179. if ((BOOL)::SendMessage(m_hWnd, TTM_HITTEST, 0, (LPARAM)&hti))
  180. {
  181. memcpy(lpToolInfo, &hti.ti, sizeof(AFX_OLDTOOLINFO));
  182. return TRUE;
  183. }
  184. return FALSE;
  185. }
  186. void CToolTipCtrl::SetToolRect(CWnd* pWnd, UINT nIDTool, LPCRECT lpRect)
  187. {
  188. ASSERT(::IsWindow(m_hWnd));
  189. ASSERT(pWnd != NULL);
  190. ASSERT(nIDTool != 0);
  191. TOOLINFO ti;
  192. FillInToolInfo(ti, pWnd, nIDTool);
  193. memcpy(&ti.rect, lpRect, sizeof(RECT));
  194. ::SendMessage(m_hWnd, TTM_NEWTOOLRECT, 0, (LPARAM)&ti);
  195. }
  196. void CToolTipCtrl::UpdateTipText(LPCTSTR lpszText, CWnd* pWnd, UINT nIDTool)
  197. {
  198. ASSERT(::IsWindow(m_hWnd));
  199. ASSERT(pWnd != NULL);
  200. TOOLINFO ti;
  201. FillInToolInfo(ti, pWnd, nIDTool);
  202. ti.lpszText = (LPTSTR)lpszText;
  203. ::SendMessage(m_hWnd, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
  204. }
  205. void CToolTipCtrl::UpdateTipText(UINT nIDText, CWnd* pWnd, UINT nIDTool)
  206. {
  207. ASSERT(nIDText != 0);
  208. CString str;
  209. VERIFY(str.LoadString(nIDText));
  210. UpdateTipText(str, pWnd, nIDTool);
  211. }
  212. /////////////////////////////////////////////////////////////////////////////
  213. // CToolTipCtrl Implementation
  214. void CToolTipCtrl::FillInToolInfo(TOOLINFO& ti, CWnd* pWnd, UINT nIDTool) const
  215. {
  216. memset(&ti, 0, sizeof(AFX_OLDTOOLINFO));
  217. ti.cbSize = sizeof(AFX_OLDTOOLINFO);
  218. HWND hwnd = pWnd->GetSafeHwnd();
  219. if (nIDTool == 0)
  220. {
  221. ti.hwnd = ::GetParent(hwnd);
  222. ti.uFlags = TTF_IDISHWND;
  223. ti.uId = (UINT)hwnd;
  224. }
  225. else
  226. {
  227. ti.hwnd = hwnd;
  228. ti.uFlags = 0;
  229. ti.uId = nIDTool;
  230. }
  231. }
  232. /////////////////////////////////////////////////////////////////////////////
  233. // CWnd tooltip support
  234. BOOL CWnd::_EnableToolTips(BOOL bEnable, UINT nFlag)
  235. {
  236. ASSERT(nFlag == WF_TOOLTIPS || nFlag == WF_TRACKINGTOOLTIPS);
  237. _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
  238. CToolTipCtrl* pToolTip = pThreadState->m_pToolTip;
  239. if (!bEnable)
  240. {
  241. // nothing to do if tooltips not enabled
  242. if (!(m_nFlags & nFlag))
  243. return TRUE;
  244. // cancel tooltip if this window is active
  245. if (pThreadState->m_pLastHit == this)
  246. CancelToolTips(TRUE);
  247. // remove "dead-area" toolbar
  248. if (pToolTip->GetSafeHwnd() != NULL)
  249. {
  250. TOOLINFO ti; memset(&ti, 0, sizeof(TOOLINFO));
  251. ti.cbSize = sizeof(AFX_OLDTOOLINFO);
  252. ti.uFlags = TTF_IDISHWND;
  253. ti.hwnd = m_hWnd;
  254. ti.uId = (UINT)m_hWnd;
  255. pToolTip->SendMessage(TTM_DELTOOL, 0, (LPARAM)&ti);
  256. }
  257. // success
  258. m_nFlags &= ~nFlag;
  259. return TRUE;
  260. }
  261. // if already enabled for tooltips, nothing to do
  262. if (!(m_nFlags & nFlag))
  263. {
  264. // success
  265. AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
  266. pModuleState->m_pfnFilterToolTipMessage = &CWnd::_FilterToolTipMessage;
  267. m_nFlags |= nFlag;
  268. }
  269. return TRUE;
  270. }
  271. BOOL CWnd::EnableToolTips(BOOL bEnable)
  272. {
  273. return _EnableToolTips(bEnable, WF_TOOLTIPS);
  274. }
  275. BOOL CWnd::EnableTrackingToolTips(BOOL bEnable)
  276. {
  277. return _EnableToolTips(bEnable, WF_TRACKINGTOOLTIPS);
  278. }
  279. AFX_STATIC void AFXAPI _AfxRelayToolTipMessage(CToolTipCtrl* pToolTip, MSG* pMsg)
  280. {
  281. // transate the message based on TTM_WINDOWFROMPOINT
  282. MSG msg = *pMsg;
  283. msg.hwnd = (HWND)pToolTip->SendMessage(TTM_WINDOWFROMPOINT, 0, (LPARAM)&msg.pt);
  284. CPoint pt = pMsg->pt;
  285. if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
  286. ::ScreenToClient(msg.hwnd, &pt);
  287. msg.lParam = MAKELONG(pt.x, pt.y);
  288. // relay mouse event before deleting old tool
  289. pToolTip->SendMessage(TTM_RELAYEVENT, 0, (LPARAM)&msg);
  290. }
  291. void PASCAL CWnd::_FilterToolTipMessage(MSG* pMsg, CWnd* pWnd)
  292. {
  293. pWnd->FilterToolTipMessage(pMsg);
  294. }
  295. void CWnd::FilterToolTipMessage(MSG* pMsg)
  296. {
  297. // this CWnd has tooltips enabled
  298. UINT message = pMsg->message;
  299. if ((message == WM_MOUSEMOVE || message == WM_NCMOUSEMOVE ||
  300. message == WM_LBUTTONUP || message == WM_RBUTTONUP ||
  301. message == WM_MBUTTONUP) &&
  302. (GetKeyState(VK_LBUTTON) >= 0 && GetKeyState(VK_RBUTTON) >= 0 &&
  303. GetKeyState(VK_MBUTTON) >= 0))
  304. {
  305. // make sure that tooltips are not already being handled
  306. CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
  307. while (pWnd != NULL && !(pWnd->m_nFlags & (WF_TOOLTIPS|WF_TRACKINGTOOLTIPS)))
  308. {
  309. pWnd = pWnd->GetParent();
  310. }
  311. if (pWnd != this)
  312. {
  313. if (pWnd == NULL)
  314. {
  315. // tooltips not enabled on this CWnd, clear last state data
  316. _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
  317. pThreadState->m_pLastHit = NULL;
  318. pThreadState->m_nLastHit = -1;
  319. }
  320. return;
  321. }
  322. _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
  323. CToolTipCtrl* pToolTip = pThreadState->m_pToolTip;
  324. CWnd* pOwner = GetParentOwner();
  325. if (pToolTip != NULL && pToolTip->GetOwner() != pOwner)
  326. {
  327. pToolTip->DestroyWindow();
  328. delete pToolTip;
  329. pThreadState->m_pToolTip = NULL;
  330. pToolTip = NULL;
  331. }
  332. if (pToolTip == NULL)
  333. {
  334. pToolTip = new CToolTipCtrl;
  335. if (!pToolTip->Create(pOwner, TTS_ALWAYSTIP))
  336. {
  337. delete pToolTip;
  338. return;
  339. }
  340. pToolTip->SendMessage(TTM_ACTIVATE, FALSE);
  341. pThreadState->m_pToolTip = pToolTip;
  342. }
  343. ASSERT_VALID(pToolTip);
  344. ASSERT(::IsWindow(pToolTip->m_hWnd));
  345. // add a "dead-area" tool for areas between toolbar buttons
  346. TOOLINFO ti; memset(&ti, 0, sizeof(TOOLINFO));
  347. ti.cbSize = sizeof(AFX_OLDTOOLINFO);
  348. ti.uFlags = TTF_IDISHWND;
  349. ti.hwnd = m_hWnd;
  350. ti.uId = (UINT)m_hWnd;
  351. if (!pToolTip->SendMessage(TTM_GETTOOLINFO, 0, (LPARAM)&ti))
  352. {
  353. ASSERT(ti.uFlags == TTF_IDISHWND);
  354. ASSERT(ti.hwnd == m_hWnd);
  355. ASSERT(ti.uId == (UINT)m_hWnd);
  356. VERIFY(pToolTip->SendMessage(TTM_ADDTOOL, 0, (LPARAM)&ti));
  357. }
  358. // determine which tool was hit
  359. CPoint point = pMsg->pt;
  360. ::ScreenToClient(m_hWnd, &point);
  361. TOOLINFO tiHit; memset(&tiHit, 0, sizeof(TOOLINFO));
  362. tiHit.cbSize = sizeof(AFX_OLDTOOLINFO);
  363. int nHit = OnToolHitTest(point, &tiHit);
  364. // build new toolinfo and if different than current, register it
  365. CWnd* pHitWnd = nHit == -1 ? NULL : this;
  366. if (pThreadState->m_nLastHit != nHit || pThreadState->m_pLastHit != pHitWnd)
  367. {
  368. if (nHit != -1)
  369. {
  370. // add new tool and activate the tip
  371. ti = tiHit;
  372. ti.uFlags &= ~(TTF_NOTBUTTON|TTF_ALWAYSTIP);
  373. if (m_nFlags & WF_TRACKINGTOOLTIPS)
  374. ti.uFlags |= TTF_TRACK;
  375. VERIFY(pToolTip->SendMessage(TTM_ADDTOOL, 0, (LPARAM)&ti));
  376. if ((tiHit.uFlags & TTF_ALWAYSTIP) || IsTopParentActive())
  377. {
  378. // allow the tooltip to popup when it should
  379. pToolTip->SendMessage(TTM_ACTIVATE, TRUE);
  380. if (m_nFlags & WF_TRACKINGTOOLTIPS)
  381. pToolTip->SendMessage(TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
  382. // bring the tooltip window above other popup windows
  383. ::SetWindowPos(pToolTip->m_hWnd, HWND_TOP, 0, 0, 0, 0,
  384. SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
  385. }
  386. }
  387. else
  388. {
  389. pToolTip->SendMessage(TTM_ACTIVATE, FALSE);
  390. }
  391. // relay mouse event before deleting old tool
  392. _AfxRelayToolTipMessage(pToolTip, pMsg);
  393. // now safe to delete the old tool
  394. if (pThreadState->m_lastInfo.cbSize >= sizeof(AFX_OLDTOOLINFO))
  395. pToolTip->SendMessage(TTM_DELTOOL, 0, (LPARAM)&pThreadState->m_lastInfo);
  396. pThreadState->m_pLastHit = pHitWnd;
  397. pThreadState->m_nLastHit = nHit;
  398. pThreadState->m_lastInfo = tiHit;
  399. }
  400. else
  401. {
  402. if (m_nFlags & WF_TRACKINGTOOLTIPS)
  403. {
  404. POINT pt;
  405. ::GetCursorPos( &pt );
  406. pToolTip->SendMessage(TTM_TRACKPOSITION, 0, MAKELPARAM(pt.x, pt.y));
  407. }
  408. else
  409. {
  410. // relay mouse events through the tooltip
  411. if (nHit != -1)
  412. _AfxRelayToolTipMessage(pToolTip, pMsg);
  413. }
  414. }
  415. if ((tiHit.lpszText != LPSTR_TEXTCALLBACK) && (tiHit.hinst == 0))
  416. free(tiHit.lpszText);
  417. }
  418. else if (m_nFlags & (WF_TOOLTIPS|WF_TRACKINGTOOLTIPS))
  419. {
  420. // make sure that tooltips are not already being handled
  421. CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
  422. while (pWnd != NULL && pWnd != this && !(pWnd->m_nFlags & (WF_TOOLTIPS|WF_TRACKINGTOOLTIPS)))
  423. pWnd = pWnd->GetParent();
  424. if (pWnd != this)
  425. return;
  426. BOOL bKeys = (message >= WM_KEYFIRST && message <= WM_KEYLAST) ||
  427. (message >= WM_SYSKEYFIRST && message <= WM_SYSKEYLAST);
  428. if ((m_nFlags & WF_TRACKINGTOOLTIPS) == 0 &&
  429. (bKeys ||
  430. (message == WM_LBUTTONDOWN || message == WM_LBUTTONDBLCLK) ||
  431. (message == WM_RBUTTONDOWN || message == WM_RBUTTONDBLCLK) ||
  432. (message == WM_MBUTTONDOWN || message == WM_MBUTTONDBLCLK) ||
  433. (message == WM_NCLBUTTONDOWN || message == WM_NCLBUTTONDBLCLK) ||
  434. (message == WM_NCRBUTTONDOWN || message == WM_NCRBUTTONDBLCLK) ||
  435. (message == WM_NCMBUTTONDOWN || message == WM_NCMBUTTONDBLCLK)))
  436. {
  437. CWnd::CancelToolTips(bKeys);
  438. }
  439. }
  440. }
  441. /////////////////////////////////////////////////////////////////////////////
  442. #ifdef AFX_INIT_SEG
  443. #pragma code_seg(AFX_INIT_SEG)
  444. #endif
  445. IMPLEMENT_DYNAMIC(CToolTipCtrl, CWnd)
  446. /////////////////////////////////////////////////////////////////////////////