TitleTip.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. #include "pch.h"
  2. #include "TitleTip.h"
  3. /////////////////////////////////////////////////////////////////////////////
  4. // CTitleTip
  5. CTitleTip::CTitleTip()
  6. {
  7. // Register the window class if it has not already been registered.
  8. WNDCLASS wndcls;
  9. HINSTANCE hInst = AfxGetInstanceHandle();
  10. if (!(::GetClassInfo(hInst, TITLETIP_CLASSNAME, &wndcls)))
  11. {
  12. // otherwise we need to register a new class
  13. wndcls.style = CS_SAVEBITS;
  14. wndcls.lpfnWndProc = ::DefWindowProc;
  15. wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
  16. wndcls.hInstance = hInst;
  17. wndcls.hIcon = NULL;
  18. wndcls.hCursor = LoadCursor(hInst, IDC_ARROW);
  19. wndcls.hbrBackground = (HBRUSH)(COLOR_INFOBK + 1);
  20. wndcls.lpszMenuName = NULL;
  21. wndcls.lpszClassName = TITLETIP_CLASSNAME;
  22. if (!AfxRegisterClass(&wndcls))
  23. AfxThrowResourceException();
  24. }
  25. m_dwLastLButtonDown = ULONG_MAX;
  26. m_dwDblClickMsecs = GetDoubleClickTime();
  27. m_bCreated = FALSE;
  28. m_pParentWnd = NULL;
  29. }
  30. CTitleTip::~CTitleTip()
  31. {
  32. }
  33. BEGIN_MESSAGE_MAP(CTitleTip, CWnd)
  34. //{{AFX_MSG_MAP(CTitleTip)
  35. ON_WM_MOUSEMOVE()
  36. //}}AFX_MSG_MAP
  37. END_MESSAGE_MAP()
  38. /////////////////////////////////////////////////////////////////////////////
  39. // CTitleTip message handlers
  40. BOOL CTitleTip::Create(CWnd * pParentWnd)
  41. {
  42. ASSERT_VALID(pParentWnd);
  43. // Already created?
  44. if (m_bCreated)
  45. return TRUE;
  46. DWORD dwStyle = WS_BORDER | WS_POPUP;
  47. DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
  48. m_pParentWnd = pParentWnd;
  49. m_bCreated = CreateEx(dwExStyle, TITLETIP_CLASSNAME, NULL, dwStyle,
  50. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  51. NULL, NULL, NULL);
  52. return m_bCreated;
  53. }
  54. BOOL CTitleTip::DestroyWindow()
  55. {
  56. m_bCreated = FALSE;
  57. return CWnd::DestroyWindow();
  58. }
  59. // Show - Show the titletip if needed
  60. // rectTitle - The rectangle within which the original
  61. // title is constrained - in client coordinates
  62. // lpszTitleText - The text to be displayed
  63. // xoffset - Number of pixel that the text is offset from
  64. // left border of the cell
  65. void CTitleTip::Show(CRect rectTitle, LPCTSTR lpszTitleText, int xoffset /*=0*/,
  66. LPRECT lpHoverRect /*=NULL*/,
  67. const LOGFONT* lpLogFont /*=NULL*/,
  68. COLORREF crTextClr /* CLR_DEFAULT */,
  69. COLORREF crBackClr /* CLR_DEFAULT */)
  70. {
  71. if (!IsWindow(m_hWnd))
  72. Create(m_pParentWnd);
  73. ASSERT(::IsWindow(GetSafeHwnd()));
  74. if (rectTitle.IsRectEmpty())
  75. return;
  76. // If titletip is already displayed, don't do anything.
  77. if (IsWindowVisible())
  78. return;
  79. m_rectHover = (lpHoverRect != NULL) ? lpHoverRect : rectTitle;
  80. m_rectHover.right++; m_rectHover.bottom++;
  81. m_pParentWnd->ClientToScreen(m_rectHover);
  82. ScreenToClient(m_rectHover);
  83. // Do not display the titletip is app does not have focus
  84. if (GetFocus() == NULL)
  85. return;
  86. // Define the rectangle outside which the titletip will be hidden.
  87. // We add a buffer of one pixel around the rectangle
  88. m_rectTitle.top = -1;
  89. m_rectTitle.left = -xoffset - 1;
  90. m_rectTitle.right = rectTitle.Width() - xoffset;
  91. m_rectTitle.bottom = rectTitle.Height() + 1;
  92. // Determine the width of the text
  93. m_pParentWnd->ClientToScreen(rectTitle);
  94. CClientDC dc(this);
  95. CString strTitle = _T("");
  96. strTitle += _T(" ");
  97. strTitle += lpszTitleText;
  98. strTitle += _T(" ");
  99. CFont font, *pOldFont = NULL;
  100. if (lpLogFont)
  101. {
  102. font.CreateFontIndirect(lpLogFont);
  103. pOldFont = dc.SelectObject(&font);
  104. }
  105. else
  106. {
  107. // use same font as ctrl
  108. pOldFont = dc.SelectObject(m_pParentWnd->GetFont());
  109. }
  110. CSize size = dc.GetTextExtent(strTitle);
  111. TEXTMETRIC tm;
  112. dc.GetTextMetrics(&tm);
  113. size.cx += tm.tmOverhang;
  114. CRect rectDisplay = rectTitle;
  115. rectDisplay.left += xoffset;
  116. rectDisplay.right = rectDisplay.left + size.cx + xoffset;
  117. // Do not display if the text fits within available space
  118. if (rectDisplay.right > rectTitle.right - xoffset)
  119. {
  120. // Show the titletip
  121. SetWindowPos(&wndTop, rectDisplay.left, rectDisplay.top,
  122. rectDisplay.Width(), rectDisplay.Height(),
  123. SWP_SHOWWINDOW | SWP_NOACTIVATE);
  124. // FNA - handle colors correctly
  125. if (crBackClr != CLR_DEFAULT)
  126. {
  127. CBrush backBrush(crBackClr);
  128. CBrush* pOldBrush = dc.SelectObject(&backBrush);
  129. CRect rect;
  130. dc.GetClipBox(&rect); // Erase the area needed
  131. dc.PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
  132. dc.SelectObject(pOldBrush);
  133. }
  134. // Set color
  135. if (crTextClr != CLR_DEFAULT)//FNA
  136. dc.SetTextColor(crTextClr);//FA
  137. dc.SetBkMode(TRANSPARENT);
  138. dc.TextOut(0, 0, strTitle);
  139. SetCapture();
  140. }
  141. dc.SelectObject(pOldFont);
  142. }
  143. void CTitleTip::Hide()
  144. {
  145. if (!::IsWindow(GetSafeHwnd()))
  146. return;
  147. if (GetCapture()->GetSafeHwnd() == GetSafeHwnd())
  148. ReleaseCapture();
  149. ShowWindow(SW_HIDE);
  150. }
  151. void CTitleTip::OnMouseMove(UINT nFlags, CPoint point)
  152. {
  153. if (!m_rectHover.PtInRect(point))
  154. {
  155. Hide();
  156. // Forward the message
  157. ClientToScreen(&point);
  158. CWnd *pWnd = WindowFromPoint(point);
  159. if (pWnd == this)
  160. pWnd = m_pParentWnd;
  161. int hittest = (int)pWnd->SendMessage(WM_NCHITTEST, 0, MAKELONG(point.x, point.y));
  162. if (hittest == HTCLIENT) {
  163. pWnd->ScreenToClient(&point);
  164. pWnd->PostMessage(WM_MOUSEMOVE, nFlags, MAKELONG(point.x, point.y));
  165. }
  166. else {
  167. pWnd->PostMessage(WM_NCMOUSEMOVE, hittest, MAKELONG(point.x, point.y));
  168. }
  169. }
  170. }
  171. BOOL CTitleTip::PreTranslateMessage(MSG* pMsg)
  172. {
  173. // Used to qualify WM_LBUTTONDOWN messages as double-clicks
  174. DWORD dwTick = 0;
  175. BOOL bDoubleClick = FALSE;
  176. CWnd *pWnd;
  177. int hittest;
  178. switch (pMsg->message)
  179. {
  180. case WM_LBUTTONDOWN:
  181. // Get tick count since last LButtonDown
  182. dwTick = GetTickCount();
  183. bDoubleClick = ((dwTick - m_dwLastLButtonDown) <= m_dwDblClickMsecs);
  184. m_dwLastLButtonDown = dwTick;
  185. // NOTE: DO NOT ADD break; STATEMENT HERE! Let code fall through
  186. case WM_RBUTTONDOWN:
  187. case WM_MBUTTONDOWN:
  188. {
  189. POINTS pts = MAKEPOINTS(pMsg->lParam);
  190. POINT point;
  191. point.x = pts.x;
  192. point.y = pts.y;
  193. ClientToScreen(&point);
  194. Hide();
  195. pWnd = WindowFromPoint(point);
  196. if (!pWnd)
  197. return CWnd::PreTranslateMessage(pMsg);
  198. if (pWnd->GetSafeHwnd() == GetSafeHwnd())
  199. pWnd = m_pParentWnd;
  200. hittest = (int)pWnd->SendMessage(WM_NCHITTEST, 0, MAKELONG(point.x, point.y));
  201. if (hittest == HTCLIENT)
  202. {
  203. pWnd->ScreenToClient(&point);
  204. pMsg->lParam = MAKELONG(point.x, point.y);
  205. }
  206. else
  207. {
  208. switch (pMsg->message) {
  209. case WM_LBUTTONDOWN:
  210. pMsg->message = WM_NCLBUTTONDOWN;
  211. break;
  212. case WM_RBUTTONDOWN:
  213. pMsg->message = WM_NCRBUTTONDOWN;
  214. break;
  215. case WM_MBUTTONDOWN:
  216. pMsg->message = WM_NCMBUTTONDOWN;
  217. break;
  218. }
  219. pMsg->wParam = hittest;
  220. pMsg->lParam = MAKELONG(point.x, point.y);
  221. }
  222. // If this is the 2nd WM_LBUTTONDOWN in x milliseconds,
  223. // post a WM_LBUTTONDBLCLK message instead of a single click.
  224. pWnd->PostMessage(bDoubleClick ? WM_LBUTTONDBLCLK : pMsg->message,
  225. pMsg->wParam,
  226. pMsg->lParam);
  227. return TRUE;
  228. }
  229. case WM_KEYDOWN:
  230. case WM_SYSKEYDOWN:
  231. Hide();
  232. m_pParentWnd->PostMessage(pMsg->message, pMsg->wParam, pMsg->lParam);
  233. return TRUE;
  234. }
  235. if (GetFocus() == NULL)
  236. {
  237. Hide();
  238. return TRUE;
  239. }
  240. return CWnd::PreTranslateMessage(pMsg);
  241. }