Leaked source code of windows server 2003
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.

3393 lines
104 KiB

  1. #include "ctlspriv.h"
  2. #define TF_TT 0x10
  3. //#define TTDEBUG
  4. #define ACTIVE 0x10
  5. #define BUTTONISDOWN 0x20
  6. #define BUBBLEUP 0x40
  7. #define VIRTUALBUBBLEUP 0x80 // this is for dead areas so we won't
  8. //wait after moving through dead areas
  9. #define TRACKMODE 0x01
  10. #define MAXTIPSIZE 128
  11. #define INITIALTIPSIZE 80
  12. #define XTEXTOFFSET 2
  13. #define YTEXTOFFSET 1
  14. #define XBALLOONOFFSET 10
  15. #define YBALLOONOFFSET 8
  16. #define BALLOON_X_CORNER 13
  17. #define BALLOON_Y_CORNER 13
  18. #define STEMOFFSET 16
  19. #define STEMHEIGHT 20
  20. #define STEMWIDTH 14
  21. #define MINBALLOONWIDTH 30 // min width for stem to show up
  22. #define TTT_INITIAL 1
  23. #define TTT_RESHOW 2
  24. #define TTT_POP 3
  25. #define TTT_AUTOPOP 4
  26. #define TIMEBETWEENANIMATE 2000 // 2 Seconds between animates
  27. #define MAX_TIP_CHARACTERS 100
  28. #define TITLEICON_WIDTH 16
  29. #define TITLEICON_HEIGHT 16
  30. #define TITLEICON_DIST 8 // Distance from Icon to Title
  31. #define TITLE_INFO_DIST 6 // Distance from the Title to the Tip Text
  32. #define MAX_TIP_WIDTH 300 // Seems kind of arbitrary. Width of the tip.
  33. typedef struct tagWIN95TOOLINFO {
  34. UINT cbSize;
  35. UINT uFlags;
  36. HWND hwnd;
  37. UINT uId;
  38. RECT rect;
  39. HINSTANCE hinst;
  40. LPSTR lpszText;
  41. } WIN95TTTOOLINFO;
  42. /* tooltips.c */
  43. typedef struct {
  44. CONTROLINFO ci;
  45. //HWND hwnd; // in ci
  46. int iNumTools;
  47. int iDelayTime;
  48. int iReshowTime;
  49. int iAutoPopTime;
  50. PTOOLINFO tools;
  51. PTOOLINFO pCurTool;
  52. BOOL fMyFont;
  53. HFONT hFont;
  54. //UINT uiCodePage; // in ci
  55. DWORD dwFlags;
  56. //DWORD dwStyle; // in ci
  57. // Timer info;
  58. UINT_PTR idTimer;
  59. POINT pt;
  60. UINT_PTR idtAutoPop;
  61. // Tip buffer
  62. LPTSTR lpTipText;
  63. UINT cchTipText;
  64. LPTSTR lpTipTitle;
  65. UINT cchTipTitle;
  66. UINT uTitleBitmap;
  67. int iTitleHeight;
  68. HIMAGELIST himlTitleBitmaps;
  69. POINT ptTrack; // the saved track point from TTM_TRACKPOSITION
  70. BOOL fBkColorSet :1;
  71. BOOL fTextColorSet :1;
  72. BOOL fUnderStem : 1; // true if stem is under the balloon
  73. BOOL fInWindowFromPoint:1; // handling a TTM_WINDOWFROMPOINT message
  74. BOOL fEverShown:1; // Have we ever been shown before?
  75. COLORREF clrTipBk; // This is joeb's idea...he wants it
  76. COLORREF clrTipText; // to be able to _blend_ more, so...
  77. int iMaxTipWidth; // the maximum tip width
  78. RECT rcMargin; // margin offset b/t border and text
  79. int iStemHeight; // balloon mode stem/wedge height
  80. DWORD dwLastDisplayTime; // The tick count taken at the last display. Used for animate puroposes.
  81. } CToolTipsMgr, NEAR *PToolTipsMgr;
  82. #define TTToolHwnd(pTool) ((pTool->uFlags & TTF_IDISHWND) ? (HWND)pTool->uId : pTool->hwnd)
  83. #define IsTextPtr(lpszText) (((lpszText) != LPSTR_TEXTCALLBACK) && (!IS_INTRESOURCE(lpszText)))
  84. //
  85. // Function prototypes
  86. //
  87. LRESULT WINAPI ToolTipsWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  88. void NEAR PASCAL TTSetDelayTime(PToolTipsMgr pTtm, WPARAM wParam, LPARAM lParam);
  89. int NEAR PASCAL TTGetDelayTime(PToolTipsMgr pTtm, WPARAM wParam);
  90. BOOL ThunkToolInfoAtoW (LPTOOLINFOA lpTiA, LPTOOLINFOW lpTiW, BOOL bThunkText, UINT uiCodePage);
  91. BOOL ThunkToolInfoWtoA (LPTOOLINFOW lpTiW, LPTOOLINFOA lpTiA, UINT uiCodePage);
  92. BOOL ThunkToolTipTextAtoW (LPTOOLTIPTEXTA lpTttA, LPTOOLTIPTEXTW lpTttW, UINT uiCodePage);
  93. #pragma code_seg(CODESEG_INIT)
  94. BOOL FAR PASCAL InitToolTipsClass(HINSTANCE hInstance)
  95. {
  96. WNDCLASS wc;
  97. // See if we must register a window class
  98. wc.lpfnWndProc = ToolTipsWndProc;
  99. wc.lpszClassName = c_szSToolTipsClass;
  100. wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  101. wc.hIcon = NULL;
  102. wc.lpszMenuName = NULL;
  103. wc.hbrBackground = (HBRUSH)(NULL);
  104. wc.hInstance = hInstance;
  105. wc.style = CS_DBLCLKS | CS_GLOBALCLASS | CS_SAVEBITS;
  106. wc.cbClsExtra = 0;
  107. wc.cbWndExtra = sizeof(PToolTipsMgr);
  108. RegisterClass(&wc);
  109. return TRUE;
  110. }
  111. #pragma code_seg()
  112. /* _ G E T H C U R S O R P D Y 3 */
  113. /*-------------------------------------------------------------------------
  114. %%Function: _GetHcursorPdy3
  115. %%Contact: migueldc
  116. With the new mouse drivers that allow you to customize the mouse
  117. pointer size, GetSystemMetrics returns useless values regarding
  118. that pointer size.
  119. Assumptions:
  120. 1. The pointer's width is equal to its height. We compute
  121. its height and infer its width.
  122. 2. The pointer's leftmost pixel is located in the 0th column
  123. of the bitmap describing it.
  124. 3. The pointer's topmost pixel is located in the 0th row
  125. of the bitmap describing it.
  126. This function looks at the mouse pointer bitmap,
  127. to find out the height of the mouse pointer (not returned),
  128. the vertical distance between the cursor's hot spot and
  129. the cursor's lowest visible pixel (pdyBottom),
  130. the horizontal distance between the hot spot and the pointer's
  131. left edge (pdxLeft) annd the horizontal distance between the
  132. hot spot and the pointer's right edge (pdxRight).
  133. -------------------------------------------------------------------------*/
  134. typedef WORD CURMASK;
  135. #define _BitSizeOf(x) (sizeof(x)*8)
  136. void NEAR PASCAL _GetHcursorPdy3(int *pdxRight, int *pdyBottom)
  137. {
  138. int i;
  139. int iXOR = 0;
  140. int dy, dx;
  141. CURMASK CurMask[16*8];
  142. ICONINFO iconinfo;
  143. BITMAP bm;
  144. HCURSOR hCursor = GetCursor();
  145. *pdyBottom = 16; //best guess
  146. *pdxRight = 16; //best guess
  147. if (!GetIconInfo(hCursor, &iconinfo))
  148. return;
  149. if (!GetObject(iconinfo.hbmMask, sizeof(bm), (LPSTR)&bm))
  150. return;
  151. if (!GetBitmapBits(iconinfo.hbmMask, sizeof(CurMask), CurMask))
  152. return;
  153. i = (int)(bm.bmWidth * bm.bmHeight / _BitSizeOf(CURMASK) );
  154. if (!iconinfo.hbmColor)
  155. {
  156. // if no color bitmap, then the hbmMask is a double height bitmap
  157. // with the cursor and the mask stacked.
  158. iXOR = i - 1;
  159. i /= 2;
  160. }
  161. if ( i >= sizeof(CurMask)) i = sizeof(CurMask) -1;
  162. if (iXOR >= sizeof(CurMask)) iXOR = 0;
  163. for (i--; i >= 0; i--)
  164. {
  165. if (CurMask[i] != 0xFFFF || (iXOR && (CurMask[iXOR--] != 0)))
  166. break;
  167. }
  168. if (iconinfo.hbmColor) DeleteObject(iconinfo.hbmColor);
  169. if (iconinfo.hbmMask) DeleteObject(iconinfo.hbmMask);
  170. // Compute the pointer height
  171. dy = (i + 1) * _BitSizeOf(CURMASK) / (int)bm.bmWidth;
  172. dx = (i + 1) * _BitSizeOf(CURMASK) / (int)bm.bmHeight;
  173. // Compute the distance between the pointer's lowest, left, rightmost
  174. // pixel and the HotSpotspot
  175. *pdyBottom = dy - (int)iconinfo.yHotspot;
  176. *pdxRight = dx - (int)iconinfo.xHotspot;
  177. }
  178. // this returns the values in work area coordinates because
  179. // that's what set window placement uses
  180. void NEAR PASCAL _GetCursorLowerLeft(int *piLeft, int *piTop, int *piWidth, int *piHeight)
  181. {
  182. DWORD dwPos;
  183. dwPos = GetMessagePos();
  184. _GetHcursorPdy3(piWidth, piHeight);
  185. *piLeft = GET_X_LPARAM(dwPos);
  186. *piTop = GET_Y_LPARAM(dwPos) + *piHeight;
  187. }
  188. void NEAR PASCAL ToolTips_NewFont(PToolTipsMgr pTtm, HFONT hFont)
  189. {
  190. if (pTtm->fMyFont && pTtm->hFont)
  191. {
  192. DeleteObject(pTtm->hFont);
  193. pTtm->fMyFont = FALSE;
  194. }
  195. if ( !hFont )
  196. {
  197. hFont = CCCreateStatusFont();
  198. pTtm->fMyFont = TRUE;
  199. if (!hFont) {
  200. hFont = g_hfontSystem;
  201. pTtm->fMyFont = FALSE;
  202. }
  203. }
  204. pTtm->hFont = hFont;
  205. pTtm->ci.uiCodePage = GetCodePageForFont(hFont);
  206. }
  207. BOOL NEAR PASCAL ChildOfActiveWindow(HWND hwndChild)
  208. {
  209. HWND hwnd = hwndChild;
  210. HWND hwndActive = GetForegroundWindow();
  211. while (hwnd) {
  212. if (hwnd == hwndActive)
  213. return TRUE;
  214. else
  215. hwnd = GetParent(hwnd);
  216. }
  217. return FALSE;
  218. }
  219. void NEAR PASCAL PopBubble(PToolTipsMgr pTtm)
  220. {
  221. // we're at least waiting to show;
  222. DebugMsg(TF_TT, TEXT("PopBubble (killing timer)"));
  223. if(pTtm->idTimer) {
  224. KillTimer(pTtm->ci.hwnd, pTtm->idTimer);
  225. pTtm->idTimer = 0;
  226. }
  227. if (pTtm->idtAutoPop) {
  228. KillTimer(pTtm->ci.hwnd, pTtm->idtAutoPop);
  229. pTtm->idtAutoPop = 0;
  230. }
  231. if (IsWindowVisible(pTtm->ci.hwnd) && pTtm->pCurTool) {
  232. NMHDR nmhdr;
  233. nmhdr.hwndFrom = pTtm->ci.hwnd;
  234. nmhdr.idFrom = pTtm->pCurTool->uId;
  235. nmhdr.code = TTN_POP;
  236. SendNotifyEx(pTtm->pCurTool->hwnd, (HWND)-1,
  237. TTN_POP, &nmhdr,
  238. (pTtm->pCurTool->uFlags & TTF_UNICODE) ? 1 : 0);
  239. }
  240. KillTimer(pTtm->ci.hwnd, TTT_POP);
  241. ShowWindow(pTtm->ci.hwnd, SW_HIDE);
  242. pTtm->dwFlags &= ~(BUBBLEUP|VIRTUALBUBBLEUP);
  243. pTtm->pCurTool = NULL;
  244. }
  245. PToolTipsMgr NEAR PASCAL ToolTipsMgrCreate(HWND hwnd, CREATESTRUCT FAR* lpCreateStruct)
  246. {
  247. PToolTipsMgr pTtm = (PToolTipsMgr)LocalAlloc(LPTR, sizeof(CToolTipsMgr));
  248. if (pTtm) {
  249. CIInitialize(&pTtm->ci, hwnd, lpCreateStruct);
  250. // LPTR zeros the rest of the struct for us
  251. TTSetDelayTime(pTtm, TTDT_AUTOMATIC, (LPARAM)-1);
  252. pTtm->dwFlags = ACTIVE;
  253. pTtm->iMaxTipWidth = -1;
  254. // These are the defaults (straight from cutils.c),
  255. // but you can always change them...
  256. pTtm->clrTipBk = g_clrInfoBk;
  257. pTtm->clrTipText = g_clrInfoText;
  258. // Setup the default tooltip text buffer
  259. pTtm->lpTipText = LocalAlloc (LPTR, INITIALTIPSIZE * sizeof(TCHAR));
  260. if (pTtm->lpTipText) {
  261. pTtm->cchTipText = INITIALTIPSIZE;
  262. } else {
  263. LocalFree (pTtm);
  264. pTtm = NULL;
  265. }
  266. }
  267. return pTtm;
  268. }
  269. void NEAR PASCAL TTSetTimer(PToolTipsMgr pTtm, int id)
  270. {
  271. int iDelayTime = 0;
  272. if(pTtm->idTimer) {
  273. KillTimer(pTtm->ci.hwnd, pTtm->idTimer);
  274. }
  275. switch (id) {
  276. case TTT_POP:
  277. case TTT_RESHOW:
  278. iDelayTime = pTtm->iReshowTime;
  279. if (iDelayTime < 0)
  280. iDelayTime = GetDoubleClickTime() / 5;
  281. break;
  282. case TTT_INITIAL:
  283. iDelayTime = pTtm->iDelayTime;
  284. if (iDelayTime < 0)
  285. iDelayTime = GetDoubleClickTime();
  286. break;
  287. case TTT_AUTOPOP:
  288. iDelayTime = pTtm->iAutoPopTime;
  289. if (iDelayTime < 0)
  290. iDelayTime = GetDoubleClickTime() * 10;
  291. pTtm->idtAutoPop = SetTimer(pTtm->ci.hwnd, id, iDelayTime, NULL);
  292. return;
  293. }
  294. DebugMsg(TF_TT, TEXT("TTSetTimer %d for %d ms"), id, iDelayTime);
  295. if (SetTimer(pTtm->ci.hwnd, id, iDelayTime, NULL) &&
  296. (id != TTT_POP)) {
  297. pTtm->idTimer = id;
  298. GetCursorPos(&pTtm->pt);
  299. }
  300. }
  301. //
  302. // Double-hack to solve blinky-tooltips problems.
  303. //
  304. // fInWindowFromPoint makes us temporarily transparent.
  305. //
  306. // Clear the WS_DISABLED flag to trick USER into hit-testing against us.
  307. // USER by default skips disabled windows. Restore the flag afterwards.
  308. // VB in particular likes to run around disabling all top-level windows
  309. // owned by his process.
  310. //
  311. // We must use SetWindowBits() instead of EnableWindow() because
  312. // EnableWindow() will mess with the capture and focus.
  313. //
  314. HWND TTWindowFromPoint(PToolTipsMgr pTtm, LPPOINT ppt)
  315. {
  316. HWND hwnd;
  317. DWORD dwStyle;
  318. dwStyle = SetWindowBits(pTtm->ci.hwnd, GWL_STYLE, WS_DISABLED, 0);
  319. pTtm->fInWindowFromPoint = TRUE;
  320. hwnd = (HWND)SendMessage(pTtm->ci.hwnd, TTM_WINDOWFROMPOINT, 0, (LPARAM)ppt);
  321. pTtm->fInWindowFromPoint = FALSE;
  322. SetWindowBits(pTtm->ci.hwnd, GWL_STYLE, WS_DISABLED, dwStyle);
  323. return hwnd;
  324. }
  325. BOOL NEAR PASCAL ToolHasMoved(PToolTipsMgr pTtm)
  326. {
  327. // this is in case Raymond pulls something sneaky like moving
  328. // the tool out from underneath the cursor.
  329. HWND hwnd;
  330. RECT rc;
  331. PTOOLINFO pTool = pTtm->pCurTool;
  332. if (!pTool)
  333. return TRUE;
  334. hwnd = TTToolHwnd(pTool);
  335. // if the window is no longer visible, or is no long a child
  336. // of the active (without the always tip flag)
  337. // also check window at point to ensure that the window isn't covered
  338. if (IsWindowVisible(hwnd) &&
  339. ((pTtm->ci.style & TTS_ALWAYSTIP) || ChildOfActiveWindow(hwnd)) &&
  340. (hwnd == TTWindowFromPoint(pTtm, &pTtm->pt))) {
  341. GetWindowRect(hwnd, &rc);
  342. if(PtInRect(&rc, pTtm->pt) )
  343. return FALSE;
  344. }
  345. return TRUE;
  346. }
  347. PTOOLINFO NEAR PASCAL FindTool(PToolTipsMgr pTtm, LPTOOLINFO lpToolInfo)
  348. {
  349. int i;
  350. PTOOLINFO pTool;
  351. if (!(pTtm && lpToolInfo))
  352. {
  353. DebugMsg(TF_ALWAYS, TEXT("FindTool passed invalid argumnet. Exiting..."));
  354. return NULL;
  355. }
  356. // BUGBUG: in win95, this was NOT validated... by doing so now, we may
  357. // cause some compat problems... if so, we need to assume for 4.0 marked
  358. // guys that cbSize == &(0->lParam)
  359. if (lpToolInfo->cbSize > sizeof(TOOLINFO))
  360. return NULL;
  361. // you can pass in an index or a toolinfo descriptor
  362. if (IS_INTRESOURCE(lpToolInfo)) {
  363. i = PtrToUlong(lpToolInfo);
  364. if (i < pTtm->iNumTools) {
  365. return &pTtm->tools[i];
  366. } else
  367. return NULL;
  368. }
  369. for(i = 0 ; i < pTtm->iNumTools; i++) {
  370. pTool = &pTtm->tools[i];
  371. if((pTool->hwnd == lpToolInfo->hwnd) &&
  372. (pTool->uId == lpToolInfo->uId))
  373. return pTool;
  374. }
  375. return NULL;
  376. }
  377. LRESULT WINAPI TTSubclassProc(HWND hwnd, UINT message, WPARAM wParam,
  378. LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData);
  379. void NEAR PASCAL TTUnsubclassHwnd(HWND hwnd, HWND hwndTT, BOOL fForce)
  380. {
  381. ULONG_PTR dwRefs;
  382. if (IsWindow(hwnd) &&
  383. GetWindowSubclass(hwnd, TTSubclassProc, (UINT_PTR)hwndTT, (PULONG_PTR) &dwRefs))
  384. {
  385. if (!fForce && (dwRefs > 1))
  386. SetWindowSubclass(hwnd, TTSubclassProc, (UINT_PTR)hwndTT, dwRefs - 1);
  387. else
  388. RemoveWindowSubclass(hwnd, TTSubclassProc, (UINT_PTR)hwndTT);
  389. }
  390. }
  391. LRESULT WINAPI TTSubclassProc(HWND hwnd, UINT message, WPARAM wParam,
  392. LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData)
  393. {
  394. if (((message >= WM_MOUSEFIRST) && (message <= WM_MOUSELAST)) ||
  395. (message == WM_NCMOUSEMOVE))
  396. {
  397. RelayToToolTips((HWND)uIdSubclass, hwnd, message, wParam, lParam);
  398. }
  399. else if (message == WM_NCDESTROY)
  400. {
  401. TTUnsubclassHwnd(hwnd, (HWND)uIdSubclass, TRUE);
  402. }
  403. return DefSubclassProc(hwnd, message, wParam, lParam);
  404. }
  405. void NEAR PASCAL TTSubclassHwnd(PTOOLINFO pTool, HWND hwndTT)
  406. {
  407. HWND hwnd;
  408. if (IsWindow(hwnd = TTToolHwnd(pTool)))
  409. {
  410. ULONG_PTR dwRefs;
  411. GetWindowSubclass(hwnd, TTSubclassProc, (UINT_PTR)hwndTT, &dwRefs);
  412. SetWindowSubclass(hwnd, TTSubclassProc, (UINT_PTR)hwndTT, dwRefs + 1);
  413. }
  414. }
  415. void NEAR PASCAL TTSetTipText(LPTOOLINFO pTool, LPTSTR lpszText)
  416. {
  417. // if it wasn't alloc'ed before, set it to NULL now so we'll alloc it
  418. // otherwise, don't touch it and it will be realloced
  419. if (!IsTextPtr(pTool->lpszText)) {
  420. pTool->lpszText = NULL;
  421. }
  422. if (IsTextPtr(lpszText)) {
  423. DebugMsg(TF_TT, TEXT("TTSetTipText %s"), lpszText);
  424. Str_Set(&pTool->lpszText, lpszText);
  425. } else {
  426. // if it was alloc'ed before free it now.
  427. Str_Set(&pTool->lpszText, NULL);
  428. pTool->lpszText = lpszText;
  429. }
  430. }
  431. LRESULT NEAR PASCAL AddTool(PToolTipsMgr pTtm, LPTOOLINFO lpToolInfo)
  432. {
  433. PTOOLINFO pTool;
  434. PTOOLINFO ptoolsNew;
  435. LRESULT lResult;
  436. // bail for right now;
  437. if (lpToolInfo->cbSize > sizeof(TOOLINFO)) {
  438. ASSERT(0);
  439. return 0L;
  440. }
  441. // on failure to alloc do nothing.
  442. ptoolsNew = CCLocalReAlloc(pTtm->tools,
  443. sizeof(TOOLINFO)*(pTtm->iNumTools+1));
  444. if ( !ptoolsNew )
  445. return 0L;
  446. if(pTtm->tools) {
  447. // realloc could have moved stuff around. repoint pCurTool
  448. if (pTtm->pCurTool) {
  449. pTtm->pCurTool = ((PTOOLINFO)ptoolsNew) + (pTtm->pCurTool - pTtm->tools);
  450. }
  451. }
  452. pTtm->tools = ptoolsNew;
  453. pTool = &pTtm->tools[pTtm->iNumTools];
  454. pTtm->iNumTools++;
  455. hmemcpy(pTool, lpToolInfo, lpToolInfo->cbSize);
  456. pTool->lpszText = NULL;
  457. //
  458. // If the tooltip will be displayed within a RTL mirrored window, then
  459. // simulate mirroring the tooltip. [samera]
  460. //
  461. //
  462. if (IS_WINDOW_RTL_MIRRORED(lpToolInfo->hwnd) &&
  463. (!(pTtm->ci.dwExStyle & RTL_MIRRORED_WINDOW)))
  464. {
  465. // toggle (mirror) the flags
  466. pTool->uFlags ^= (TTF_RTLREADING | TTF_RIGHT);
  467. }
  468. TTSetTipText(pTool, lpToolInfo->lpszText);
  469. if (pTool->uFlags & TTF_SUBCLASS) {
  470. TTSubclassHwnd(pTool, pTtm->ci.hwnd);
  471. }
  472. if (!lpToolInfo->hwnd || !IsWindow(lpToolInfo->hwnd)) {
  473. lResult = NFR_UNICODE;
  474. } else if (pTool->uFlags & TTF_UNICODE) {
  475. lResult = NFR_UNICODE;
  476. } else {
  477. lResult = SendMessage (pTool->hwnd, WM_NOTIFYFORMAT,
  478. (WPARAM)pTtm->ci.hwnd, NF_QUERY);
  479. }
  480. if (lResult == NFR_UNICODE) {
  481. pTool->uFlags |= TTF_UNICODE;
  482. }
  483. #ifdef TTDEBUG
  484. DebugMsg(TF_TT, TEXT("Tool Added: ptr=%d, uFlags=%d, wid=%d, hwnd=%d"),
  485. pTool, pTool->uFlags, pTool->uId, pTool->hwnd);
  486. #endif
  487. return 1L;
  488. }
  489. void NEAR PASCAL TTBeforeFreeTool(PToolTipsMgr pTtm, LPTOOLINFO pTool)
  490. {
  491. if (pTool->uFlags & TTF_SUBCLASS)
  492. TTUnsubclassHwnd(TTToolHwnd(pTool), pTtm->ci.hwnd, FALSE);
  493. // clean up
  494. TTSetTipText(pTool, NULL);
  495. }
  496. void NEAR PASCAL DeleteTool(PToolTipsMgr pTtm, LPTOOLINFO lpToolInfo)
  497. {
  498. PTOOLINFO pTool;
  499. // bail for right now;
  500. if (lpToolInfo->cbSize > sizeof(TOOLINFO)) {
  501. ASSERT(0);
  502. return;
  503. }
  504. pTool = FindTool(pTtm, lpToolInfo);
  505. if(pTool) {
  506. if (pTtm->pCurTool == pTool)
  507. PopBubble(pTtm);
  508. TTBeforeFreeTool(pTtm, pTool);
  509. // replace it with the last one.. no need to waste cycles in realloc
  510. pTtm->iNumTools--;
  511. *pTool = pTtm->tools[pTtm->iNumTools]; // struct copy
  512. //cleanup if we moved the current tool
  513. if(pTtm->pCurTool == &pTtm->tools[pTtm->iNumTools])
  514. pTtm->pCurTool = pTool;
  515. }
  516. }
  517. // this strips out & markers so that people can use menu text strings
  518. void NEAR PASCAL StripAccels(PToolTipsMgr pTtm)
  519. {
  520. if (!(pTtm->ci.style & TTS_NOPREFIX)) {
  521. StripAccelerators(pTtm->lpTipText, pTtm->lpTipText, FALSE);
  522. }
  523. }
  524. //
  525. // The way we detect if a window is a toolbar or not is by asking it
  526. // for its MSAA class ID. We cannot use GetClassWord(GCL_ATOM) because
  527. // Microsoft LiquidMotion **superclasses** the toolbar, so the classname
  528. // won't match.
  529. //
  530. #define IsToolbarWindow(hwnd) \
  531. (SendMessage(hwnd, WM_GETOBJECT, 0, OBJID_QUERYCLASSNAMEIDX) == MSAA_CLASSNAMEIDX_TOOLBAR)
  532. LPTSTR NEAR PASCAL GetToolText(PToolTipsMgr pTtm, PTOOLINFO pTool)
  533. {
  534. int id;
  535. HINSTANCE hinst;
  536. DWORD dwStrLen;
  537. TOOLTIPTEXT ttt;
  538. if (!pTool)
  539. return NULL;
  540. #ifdef TTDEBUG
  541. DebugMsg(TF_TT, TEXT(" **Enter GetToolText: ptr=%d, wFlags=%d, wid=%d, hwnd=%d"),
  542. pTool, pTool->uFlags, pTool->uId, pTool->hwnd);
  543. #endif
  544. if (pTtm->lpTipText) {
  545. *pTtm->lpTipText = TEXT('\0');
  546. } else {
  547. pTtm->lpTipText = LocalAlloc (LPTR, INITIALTIPSIZE * sizeof(TCHAR));
  548. pTtm->cchTipText = INITIALTIPSIZE;
  549. }
  550. if (pTool->lpszText == LPSTR_TEXTCALLBACK) {
  551. ttt.hdr.idFrom = pTool->uId;
  552. ttt.hdr.code = TTN_NEEDTEXT;
  553. ttt.hdr.hwndFrom = pTtm->ci.hwnd;
  554. ttt.szText[0] = TEXT('\0');
  555. ttt.lpszText = ttt.szText;
  556. ttt.uFlags = pTool->uFlags;
  557. ttt.lParam = pTool->lParam;
  558. ttt.hinst = NULL;
  559. SendNotifyEx(pTool->hwnd, (HWND) -1,
  560. 0, (NMHDR FAR *)&ttt,
  561. (pTool->uFlags & TTF_UNICODE) ? 1 : 0);
  562. // APPHACK for Elcom Advanced Disk Catalog and for Microsoft
  563. // LiquidMotion:
  564. // they subclass toolbar & expect this notification to
  565. // be ANSI. So if the UNICODE notification failed,
  566. // and our parent is a toolbar, then try again in ANSI.
  567. if (ttt.lpszText == ttt.szText && ttt.szText[0] == TEXT('\0') &&
  568. (pTool->uFlags & TTF_UNICODE) && pTtm->ci.iVersion < 5 &&
  569. IsToolbarWindow(pTool->hwnd)) {
  570. SendNotifyEx(pTool->hwnd, (HWND) -1,
  571. 0, (NMHDR FAR *)&ttt,
  572. FALSE);
  573. }
  574. if (ttt.uFlags & TTF_DI_SETITEM) {
  575. if (IS_INTRESOURCE(ttt.lpszText)) {
  576. pTool->lpszText = ttt.lpszText;
  577. pTool->hinst = ttt.hinst;
  578. } else if (ttt.lpszText != LPSTR_TEXTCALLBACK) {
  579. TTSetTipText(pTool, ttt.lpszText);
  580. }
  581. }
  582. if (IsFlagPtr(ttt.lpszText))
  583. return NULL;
  584. //
  585. // we allow the RtlReading flag ONLY to be changed here.
  586. //
  587. if (ttt.uFlags & TTF_RTLREADING)
  588. {
  589. pTool->uFlags |= TTF_RTLREADING;
  590. }
  591. else
  592. {
  593. pTool->uFlags &= ~TTF_RTLREADING;
  594. }
  595. if (IS_INTRESOURCE(ttt.lpszText)) {
  596. id = PtrToUlong(ttt.lpszText);
  597. hinst = ttt.hinst;
  598. ttt.lpszText = ttt.szText;
  599. goto LoadFromResource;
  600. }
  601. if (*ttt.lpszText == TEXT('\0'))
  602. return NULL;
  603. dwStrLen = lstrlen(ttt.lpszText) + 1;
  604. if (pTtm->cchTipText < dwStrLen)
  605. {
  606. LPTSTR psz = LocalReAlloc (pTtm->lpTipText,
  607. dwStrLen * sizeof(TCHAR),
  608. LMEM_MOVEABLE);
  609. if (psz)
  610. {
  611. pTtm->lpTipText = psz;
  612. pTtm->cchTipText = dwStrLen;
  613. }
  614. }
  615. if (pTtm->lpTipText)
  616. {
  617. StringCchCopy(pTtm->lpTipText, pTtm->cchTipText, ttt.lpszText);
  618. }
  619. //
  620. // if ttt.lpszText != ttt.szText and the ttt.uFlags has TTF_MEMALLOCED, then
  621. // the ANSI thunk allocated the buffer for us, so free it.
  622. //
  623. if ((ttt.lpszText != ttt.szText) && (ttt.uFlags & TTF_MEMALLOCED)) {
  624. LocalFree (ttt.lpszText);
  625. }
  626. StripAccels(pTtm);
  627. } else if (pTool->lpszText && IS_INTRESOURCE(pTool->lpszText)) {
  628. id = PtrToLong(pTool->lpszText);
  629. hinst = pTool->hinst;
  630. LoadFromResource:
  631. if (pTtm->lpTipText) {
  632. if (!LoadString(hinst, id, pTtm->lpTipText, pTtm->cchTipText))
  633. return NULL;
  634. StripAccels(pTtm);
  635. }
  636. } else {
  637. // supplied at creation time.
  638. #ifdef TTDEBUG
  639. DebugMsg(TF_TT, TEXT("GetToolText returns %s"), pTool->lpszText);
  640. #endif
  641. if (pTool->lpszText && *pTool->lpszText) {
  642. dwStrLen = lstrlen(pTool->lpszText) + 1;
  643. if (pTtm->cchTipText < dwStrLen)
  644. {
  645. LPTSTR psz = LocalReAlloc (pTtm->lpTipText,
  646. dwStrLen * sizeof(TCHAR),
  647. LMEM_MOVEABLE);
  648. if (psz)
  649. {
  650. pTtm->lpTipText = psz;
  651. pTtm->cchTipText = dwStrLen;
  652. }
  653. }
  654. if (pTtm->lpTipText)
  655. {
  656. StringCchCopy(pTtm->lpTipText, pTtm->cchTipText, pTool->lpszText);
  657. StripAccels(pTtm);
  658. }
  659. }
  660. }
  661. #ifdef TTDEBUG
  662. DebugMsg(TF_TT, TEXT(" **GetToolText returns %s"), pTtm->lpTipText ? pTtm->lpTipText : TEXT("NULL"));
  663. #endif
  664. return pTtm->lpTipText;
  665. }
  666. LPTSTR NEAR PASCAL GetCurToolText(PToolTipsMgr pTtm)
  667. {
  668. LPTSTR psz = NULL;
  669. if (pTtm->pCurTool)
  670. psz = GetToolText(pTtm, pTtm->pCurTool);
  671. // this could have changed during the WM_NOTIFY back
  672. if (!pTtm->pCurTool)
  673. psz = NULL;
  674. return psz;
  675. }
  676. void NEAR PASCAL GetToolRect(PTOOLINFO pTool, LPRECT lprc)
  677. {
  678. if (pTool->uFlags & TTF_IDISHWND) {
  679. GetWindowRect((HWND)pTool->uId, lprc);
  680. } else {
  681. *lprc = pTool->rect;
  682. MapWindowPoints(pTool->hwnd, HWND_DESKTOP, (LPPOINT)lprc, 2);
  683. }
  684. }
  685. BOOL NEAR PASCAL PointInTool(PTOOLINFO pTool, HWND hwnd, int x, int y)
  686. {
  687. // We never care if the point is in a track tool or we're using
  688. // a hit-test.
  689. if (pTool->uFlags & (TTF_TRACK | TTF_USEHITTEST))
  690. return FALSE;
  691. if (pTool->uFlags & TTF_IDISHWND) {
  692. if (hwnd == (HWND)pTool->uId) {
  693. return TRUE;
  694. }
  695. } else if(hwnd == pTool->hwnd) {
  696. POINT pt;
  697. pt.x = x;
  698. pt.y = y;
  699. if (PtInRect(&pTool->rect, pt)) {
  700. return TRUE;
  701. }
  702. }
  703. return FALSE;
  704. }
  705. #ifdef TTDEBUG
  706. void NEAR PASCAL DebugDumpTool(PTOOLINFO pTool)
  707. {
  708. if (pTool) {
  709. DebugMsg(TF_TT, TEXT(" DumpTool: (%d) hwnd = %d %d, %d %d %d %d"),pTool,
  710. pTool->hwnd,
  711. (UINT)pTool->uFlags,
  712. pTool->rect.left, pTool->rect.top,
  713. pTool->rect.right, pTool->rect.bottom);
  714. } else {
  715. DebugMsg(TF_TT, TEXT(" DumpTool: (NULL)"));
  716. }
  717. }
  718. #else
  719. #define DebugDumpTool(p)
  720. #endif
  721. #define HittestInTool(pTool, hwnd, ht) \
  722. ((pTool->uFlags & TTF_USEHITTEST) && pTool->hwnd == hwnd && ht == pTool->rect.left)
  723. PTOOLINFO NEAR PASCAL GetToolAtPoint(PToolTipsMgr pTtm, HWND hwnd, int x, int y,
  724. int ht, BOOL fCheckText)
  725. {
  726. PTOOLINFO pToolReturn = NULL;
  727. PTOOLINFO pTool;
  728. // short cut.. if we're in the same too, and the bubble is up (not just virtual)
  729. // return it. this prevents us from having to poll all the time and
  730. // prevents us from switching to another tool when this one is good
  731. if ((pTtm->dwFlags & BUBBLEUP) && pTtm->pCurTool != NULL &&
  732. (HittestInTool(pTtm->pCurTool, hwnd, ht) ||
  733. PointInTool(pTtm->pCurTool, hwnd, x, y)))
  734. {
  735. return pTtm->pCurTool;
  736. }
  737. #ifdef TTDEBUG
  738. DebugMsg(TF_TT, TEXT("******Entering GetToolAtPoint"));
  739. #endif
  740. if(pTtm->iNumTools) {
  741. for(pTool = &pTtm->tools[pTtm->iNumTools-1];
  742. pTool >= pTtm->tools;
  743. pTool--) {
  744. #ifdef TTDEBUG
  745. //DebugMsg(TF_TT, TEXT(" Point in Tool Check"));
  746. //DebugDumpTool(pTool);
  747. #endif
  748. if(HittestInTool(pTool, hwnd, ht) || PointInTool(pTool, hwnd, x, y)) {
  749. #ifdef TTDEBUG
  750. //DebugMsg(TF_TT, TEXT(" yes"));
  751. #endif
  752. // if this tool has text, return it.
  753. // otherwise, save it away as a dead area tool,
  754. // and keep looking
  755. if (fCheckText) {
  756. if (GetToolText(pTtm, pTool)) {
  757. #ifdef TTDEBUG
  758. //DebugMsg(TF_TT, TEXT(" Return! case it Has text"));
  759. //DebugDumpTool(pTool);
  760. #endif
  761. return pTool;
  762. } else if (pTtm->dwFlags & (BUBBLEUP|VIRTUALBUBBLEUP)) {
  763. // only return this (only allow a virutal tool
  764. // if there was previously a tool up.
  765. // IE, we can't start things off with a virutal tool
  766. pToolReturn = pTool;
  767. }
  768. } else {
  769. #ifdef TTDEBUG
  770. //DebugMsg(TF_TT, TEXT(" Return! No text check"));
  771. //DebugDumpTool(pTool);
  772. #endif
  773. return pTool;
  774. }
  775. }
  776. }
  777. }
  778. #ifdef TTDEBUG
  779. DebugMsg(TF_TT, TEXT(" Return! no text but returning anyways"));
  780. DebugDumpTool(pToolReturn);
  781. #endif
  782. return pToolReturn;
  783. }
  784. void NEAR PASCAL ShowVirtualBubble(PToolTipsMgr pTtm)
  785. {
  786. PTOOLINFO pTool = pTtm->pCurTool;
  787. DebugMsg(TF_TT, TEXT("Entering ShowVirtualBubble so popping bubble"));
  788. PopBubble(pTtm);
  789. // Set this back in so that while we're in this tool's area,
  790. // we won't keep querying for info
  791. pTtm->pCurTool = pTool;
  792. pTtm->dwFlags |= VIRTUALBUBBLEUP;
  793. }
  794. #define TRACK_TOP 0
  795. #define TRACK_LEFT 1
  796. #define TRACK_BOTTOM 2
  797. #define TRACK_RIGHT 3
  798. void NEAR PASCAL TTGetTipPosition(PToolTipsMgr pTtm, LPRECT lprc, int cxText, int cyText, int *pxStem, int *pyStem)
  799. {
  800. RECT rcWorkArea;
  801. // ADJUSTRECT! Keep TTAdjustRect and TTM_GETBUBBLESIZE in sync.
  802. int cxMargin = pTtm->rcMargin.left + pTtm->rcMargin.right;
  803. int cyMargin = pTtm->rcMargin.top + pTtm->rcMargin.bottom;
  804. int iBubbleWidth = 2*XTEXTOFFSET * g_cxBorder + cxText + cxMargin;
  805. int iBubbleHeight = 2*YTEXTOFFSET * g_cyBorder + cyText + cyMargin;
  806. UINT uSide = (UINT)-1;
  807. RECT rcTool;
  808. MONITORINFO mi;
  809. HMONITOR hMonitor;
  810. POINT pt;
  811. BOOL bBalloon = pTtm->ci.style & TTS_BALLOON;
  812. int xStem, yStem;
  813. int iCursorHeight=0;
  814. int iCursorWidth=0;
  815. if (bBalloon || pTtm->cchTipTitle)
  816. {
  817. // ADJUSTRECT! Keep TTAdjustRect and TTM_GETBUBBLESIZE in sync.
  818. iBubbleWidth += 2*XBALLOONOFFSET;
  819. iBubbleHeight += 2*YBALLOONOFFSET;
  820. if (bBalloon)
  821. {
  822. if (iBubbleWidth < MINBALLOONWIDTH)
  823. pTtm->iStemHeight = 0;
  824. else
  825. {
  826. pTtm->iStemHeight = STEMHEIGHT;
  827. if (pTtm->iStemHeight > iBubbleHeight/3)
  828. pTtm->iStemHeight = iBubbleHeight/3; // don't let the stem be longer than the bubble -- looks ugly
  829. }
  830. }
  831. }
  832. GetToolRect(pTtm->pCurTool, &rcTool);
  833. if (pTtm->pCurTool->uFlags & TTF_TRACK) {
  834. lprc->left = pTtm->ptTrack.x;
  835. lprc->top = pTtm->ptTrack.y;
  836. if (bBalloon)
  837. {
  838. // adjust the desired left hand side
  839. xStem = pTtm->ptTrack.x;
  840. yStem = pTtm->ptTrack.y;
  841. }
  842. // BUGBUG: should we not do this in case of TTS_BALLOON?
  843. if (pTtm->pCurTool->uFlags & TTF_CENTERTIP) {
  844. // center the bubble around the ptTrack
  845. lprc->left -= (iBubbleWidth / 2);
  846. if (!bBalloon)
  847. lprc->top -= (iBubbleHeight / 2);
  848. }
  849. if (pTtm->pCurTool->uFlags & TTF_ABSOLUTE)
  850. {
  851. // with goto bellow we'll skip adjusting
  852. // bubble height -- so do it here
  853. if (bBalloon)
  854. iBubbleHeight += pTtm->iStemHeight;
  855. goto CompleteRect;
  856. }
  857. // in balloon style the positioning depends on the position
  858. // of the stem and we don't try to position the tooltip
  859. // next to the tool rect
  860. if (!bBalloon)
  861. {
  862. // now align it so that the tip sits beside the rect.
  863. if (pTtm->ptTrack.y > rcTool.bottom)
  864. {
  865. uSide = TRACK_BOTTOM;
  866. if (lprc->top < rcTool.bottom)
  867. lprc->top = rcTool.bottom;
  868. }
  869. else if (pTtm->ptTrack.x < rcTool.left)
  870. {
  871. uSide = TRACK_LEFT;
  872. if (lprc->left + iBubbleWidth > rcTool.left)
  873. lprc->left = rcTool.left - iBubbleWidth;
  874. }
  875. else if (pTtm->ptTrack.y < rcTool.top)
  876. {
  877. uSide = TRACK_TOP;
  878. if (lprc->top + iBubbleHeight > rcTool.top)
  879. lprc->top = rcTool.top - iBubbleHeight;
  880. }
  881. else
  882. {
  883. uSide = TRACK_RIGHT;
  884. if (lprc->left < rcTool.right)
  885. lprc->left = rcTool.right;
  886. }
  887. }
  888. }
  889. else if (pTtm->pCurTool->uFlags & TTF_CENTERTIP)
  890. {
  891. lprc->left = (rcTool.right + rcTool.left - iBubbleWidth)/2;
  892. lprc->top = rcTool.bottom;
  893. if (bBalloon)
  894. {
  895. xStem = (rcTool.left + rcTool.right)/2;
  896. yStem = rcTool.bottom;
  897. }
  898. }
  899. else
  900. {
  901. // now set it
  902. _GetCursorLowerLeft((LPINT)&lprc->left, (LPINT)&lprc->top, &iCursorWidth, &iCursorHeight);
  903. if (bBalloon)
  904. {
  905. HMONITOR hMon1, hMon2;
  906. POINT pt;
  907. BOOL bOnSameMonitor = FALSE;
  908. int iTop = lprc->top - (iCursorHeight + iBubbleHeight + pTtm->iStemHeight);
  909. xStem = lprc->left;
  910. yStem = lprc->top;
  911. pt.x = xStem;
  912. pt.y = lprc->top;
  913. hMon1 = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
  914. pt.y = iTop;
  915. hMon2 = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
  916. if (hMon1 == hMon2)
  917. {
  918. // the hmons are the same but maybe iTop is off any monitor and we just defaulted
  919. // to the nearest one -- check if it's really on the monitor
  920. mi.cbSize = sizeof(mi);
  921. GetMonitorInfo(hMon1, &mi);
  922. if (PtInRect(&mi.rcMonitor, pt))
  923. {
  924. // we'd like to show balloon above the cursor so that wedge/stem points
  925. // to tip of the cursor not its bottom left corner
  926. yStem -= iCursorHeight;
  927. lprc->top = iTop;
  928. bOnSameMonitor = TRUE;
  929. }
  930. }
  931. if (!bOnSameMonitor)
  932. {
  933. xStem += iCursorWidth/2;
  934. iCursorHeight = iCursorWidth = 0;
  935. }
  936. }
  937. }
  938. //
  939. // At this point, (lprc->left, lprc->top) is the position
  940. // at which we would prefer that the tooltip appear.
  941. //
  942. if (bBalloon)
  943. {
  944. // adjust the left point now that all calculations are done
  945. // but only if we're not in the center tip mode
  946. // note we use height as width so we can have 45 degree angle that looks nice
  947. if (!(pTtm->pCurTool->uFlags & TTF_CENTERTIP) && iBubbleWidth > STEMOFFSET + pTtm->iStemHeight)
  948. lprc->left -= STEMOFFSET;
  949. // adjust the height to include stem
  950. iBubbleHeight += pTtm->iStemHeight;
  951. }
  952. pt.x = lprc->left;
  953. pt.y = lprc->top;
  954. hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
  955. mi.cbSize = sizeof(mi);
  956. GetMonitorInfo(hMonitor, &mi);
  957. if (GetWindowLong(pTtm->ci.hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST)
  958. {
  959. CopyRect(&rcWorkArea, &mi.rcMonitor);
  960. } else {
  961. CopyRect(&rcWorkArea, &mi.rcWork);
  962. }
  963. //
  964. // At this point, rcWorkArea is the rectangle within which
  965. // the tooltip should finally appear.
  966. //
  967. // Now fiddle with the coordinates to try to find a sane location
  968. // for the tip.
  969. //
  970. // move it up if it's at the bottom of the screen
  971. if ((lprc->top + iBubbleHeight) >= (rcWorkArea.bottom)) {
  972. if (uSide == TRACK_BOTTOM)
  973. lprc->top = rcTool.top - iBubbleHeight; // flip to top
  974. else
  975. {
  976. //
  977. // We can't "stick to bottom" because that would cause
  978. // our tooltip to lie under the mouse cursor, causing it
  979. // to pop immediately! So go just above the mouse cursor.
  980. //
  981. // cannot do that in the track mode -- tooltip randomly on the
  982. // screen, not even near the button
  983. //
  984. // BUGBUG raymondc v6: This messes up Lotus SmartCenter.
  985. // Need to be smarter about when it is safe to flip up.
  986. // Perhaps by checking if the upflip would put the tip too
  987. // far away from the mouse.
  988. if (pTtm->pCurTool->uFlags & TTF_TRACK)
  989. lprc->top = pTtm->ptTrack.y - iBubbleHeight;
  990. else
  991. {
  992. int y = GET_Y_LPARAM(GetMessagePos());
  993. lprc->top = y - iBubbleHeight;
  994. if (bBalloon)
  995. yStem = y;
  996. }
  997. }
  998. }
  999. // If above the top of the screen...
  1000. if (lprc->top < rcWorkArea.top)
  1001. {
  1002. if (uSide == TRACK_TOP)
  1003. lprc->top = rcTool.bottom; // flip to bottom
  1004. else
  1005. lprc->top = rcWorkArea.top; // stick to top
  1006. }
  1007. // move it over if it extends past the right.
  1008. if ((lprc->left + iBubbleWidth) >= (rcWorkArea.right))
  1009. {
  1010. // flipping is not the right thing to do with balloon style
  1011. // because the wedge/stem can stick out of the window and
  1012. // would therefore be clipped so
  1013. if (bBalloon)
  1014. {
  1015. // move it to the left so that stem appears on the right side of the balloon
  1016. // again we use height as width so we can have 45 degree angle
  1017. if (iBubbleWidth >= MINBALLOONWIDTH)
  1018. lprc->left = xStem + min(STEMOFFSET, (iBubbleWidth-pTtm->iStemHeight)/2) - iBubbleWidth;
  1019. // are we still out?
  1020. if (lprc->left + iBubbleWidth >= rcWorkArea.right)
  1021. lprc->left = rcWorkArea.right - iBubbleWidth - 1;
  1022. }
  1023. else if (uSide == TRACK_RIGHT)
  1024. lprc->left = rcTool.left - iBubbleWidth; // flip to left
  1025. else
  1026. // not in right tracking mode, just scoot it over
  1027. lprc->left = rcWorkArea.right - iBubbleWidth - 1; // stick to right
  1028. }
  1029. // if too far left...
  1030. if (lprc->left < rcWorkArea.left)
  1031. {
  1032. if (uSide == TRACK_LEFT)
  1033. {
  1034. // flipping is not the right thing to do with balloon style
  1035. // because the wedge/stem can stick out of the window and
  1036. // would therefore be clipped so
  1037. if (bBalloon)
  1038. lprc->left = rcWorkArea.left; //pTtm->ptTrack.x;
  1039. else
  1040. lprc->left = rcTool.right; // flip to right
  1041. }
  1042. else
  1043. lprc->left = rcWorkArea.left; // stick to left
  1044. }
  1045. CompleteRect:
  1046. lprc->right = lprc->left + iBubbleWidth;
  1047. lprc->bottom = lprc->top + iBubbleHeight;
  1048. if (bBalloon && pxStem && pyStem)
  1049. {
  1050. *pxStem = xStem;
  1051. *pyStem = yStem;
  1052. }
  1053. }
  1054. BOOL TTCreateTitleBitmaps(PToolTipsMgr pTtm)
  1055. {
  1056. if (pTtm->himlTitleBitmaps)
  1057. return TRUE;
  1058. pTtm->himlTitleBitmaps = ImageList_Create(TITLEICON_WIDTH, TITLEICON_HEIGHT, ILC_COLOR24 | ILC_MASK, 3, 1);
  1059. if (pTtm->himlTitleBitmaps)
  1060. {
  1061. HICON hicon;
  1062. hicon = (HICON)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_TITLE_INFO), IMAGE_ICON,
  1063. TITLEICON_WIDTH, TITLEICON_HEIGHT, LR_DEFAULTCOLOR);
  1064. ImageList_AddIcon(pTtm->himlTitleBitmaps, hicon);
  1065. DestroyIcon(hicon);
  1066. hicon = (HICON)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_TITLE_WARNING), IMAGE_ICON,
  1067. TITLEICON_WIDTH, TITLEICON_HEIGHT, LR_DEFAULTCOLOR);
  1068. ImageList_AddIcon(pTtm->himlTitleBitmaps, hicon);
  1069. DestroyIcon(hicon);
  1070. hicon = (HICON)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_TITLE_ERROR), IMAGE_ICON,
  1071. TITLEICON_WIDTH, TITLEICON_HEIGHT, LR_DEFAULTCOLOR);
  1072. ImageList_AddIcon(pTtm->himlTitleBitmaps, hicon);
  1073. DestroyIcon(hicon);
  1074. return TRUE;
  1075. }
  1076. return FALSE;
  1077. }
  1078. // Called when caclulating the size of a "titled tool tip" or actually drawing
  1079. // based on the boolean value bCalcRect.
  1080. BOOL TTRenderTitledTip(PToolTipsMgr pTtm, HDC hdc, BOOL bCalcRect, RECT* prc, UINT uDrawFlags)
  1081. {
  1082. RECT rc;
  1083. int lWidth=0, lHeight=0;
  1084. HFONT hfont;
  1085. COLORREF crOldTextColor;
  1086. int iOldBKMode;
  1087. // If we don't have a title, we don't need to be here.
  1088. if (pTtm->cchTipTitle == 0)
  1089. return FALSE;
  1090. CopyRect(&rc, prc);
  1091. if (pTtm->uTitleBitmap != TTI_NONE)
  1092. {
  1093. lWidth = TITLEICON_WIDTH + TITLEICON_DIST;
  1094. lHeight += TITLEICON_HEIGHT;
  1095. if (!bCalcRect && pTtm->himlTitleBitmaps)
  1096. {
  1097. ImageList_Draw(pTtm->himlTitleBitmaps, pTtm->uTitleBitmap - 1, hdc, rc.left, rc.top, ILD_TRANSPARENT);
  1098. }
  1099. rc.left += lWidth;
  1100. }
  1101. if (!bCalcRect)
  1102. {
  1103. crOldTextColor = SetTextColor(hdc, pTtm->clrTipText);
  1104. iOldBKMode = SetBkMode(hdc, TRANSPARENT);
  1105. }
  1106. if (pTtm->lpTipTitle[0] != TEXT('\0'))
  1107. {
  1108. LOGFONT lf;
  1109. HFONT hfTitle;
  1110. UINT uFlags = uDrawFlags | DT_SINGLELINE; // title should be on one line only
  1111. hfont = GetCurrentObject(hdc, OBJ_FONT);
  1112. GetObject(hfont, sizeof(lf), &lf);
  1113. lf.lfWeight = FW_BOLD;
  1114. hfTitle = CreateFontIndirect(&lf);
  1115. // hfont should already be set to this
  1116. hfont = SelectObject(hdc, hfTitle);
  1117. // drawtext does not calculate the height if these are specified
  1118. if (!bCalcRect)
  1119. uFlags |= DT_BOTTOM;
  1120. // we need to calc title height -- either we did it before or we'll do it now
  1121. ASSERT(pTtm->iTitleHeight != 0 || uFlags & DT_CALCRECT);
  1122. // adjust the rect so we can stick the title to the bottom of it
  1123. rc.bottom = rc.top + max(pTtm->iTitleHeight, TITLEICON_HEIGHT);
  1124. // problems in DrawText if margins make rc.right < rc.left
  1125. // even though we are asking for calculation of the rect nothing happens, so ...
  1126. if (bCalcRect)
  1127. rc.right = rc.left + MAX_TIP_WIDTH;
  1128. DrawText(hdc, pTtm->lpTipTitle, lstrlen(pTtm->lpTipTitle), &rc, uFlags);
  1129. if (pTtm->iTitleHeight == 0)
  1130. pTtm->iTitleHeight = RECTHEIGHT(rc); // Use rc instead of lfHeight, because it can be Negative.
  1131. lHeight = max(lHeight, pTtm->iTitleHeight) + TITLE_INFO_DIST;
  1132. lWidth += RECTWIDTH(rc);
  1133. SelectObject(hdc, hfont);
  1134. DeleteObject(hfTitle);
  1135. }
  1136. // adjust the rect for the info text
  1137. CopyRect(&rc, prc);
  1138. rc.top += lHeight;
  1139. // we want multi line text -- tooltip will give us single line if we did not set MAXWIDTH
  1140. uDrawFlags &= ~DT_SINGLELINE;
  1141. DrawText(hdc, pTtm->lpTipText, lstrlen(pTtm->lpTipText), &rc, uDrawFlags);
  1142. lHeight += RECTHEIGHT(rc);
  1143. lWidth = max(lWidth, RECTWIDTH(rc));
  1144. if (bCalcRect)
  1145. {
  1146. prc->right = prc->left + lWidth;
  1147. prc->bottom = prc->top + lHeight;
  1148. }
  1149. else
  1150. {
  1151. SetTextColor(hdc, crOldTextColor);
  1152. SetBkMode(hdc, iOldBKMode);
  1153. }
  1154. return TRUE;
  1155. }
  1156. void NEAR PASCAL TTGetTipSize(PToolTipsMgr pTtm, PTOOLINFO pTool,LPTSTR lpstr, LPINT pcxText, LPINT pcyText)
  1157. {
  1158. // get the size it will be
  1159. HDC hdc = GetDC(pTtm->ci.hwnd);
  1160. HFONT hOldFont;
  1161. if(pTtm->hFont) hOldFont = SelectObject(hdc, pTtm->hFont);
  1162. /* If need to fire off the pre-DrawText notify then do so, otherwise use the
  1163. original implementation that just called MGetTextExtent */
  1164. {
  1165. NMTTCUSTOMDRAW nm;
  1166. DWORD dwCustom;
  1167. UINT uDefDrawFlags = 0;
  1168. nm.nmcd.hdr.hwndFrom = pTtm->ci.hwnd;
  1169. nm.nmcd.hdr.idFrom = pTool->uId;
  1170. nm.nmcd.hdr.code = NM_CUSTOMDRAW;
  1171. nm.nmcd.hdc = hdc;
  1172. // TTGetTipSize must use CDDS_PREPAINT so the client can tell
  1173. // whether we are measuring or painting
  1174. nm.nmcd.dwDrawStage = CDDS_PREPAINT;
  1175. nm.nmcd.rc.left = nm.nmcd.rc.top = 0;
  1176. if (pTtm->ci.style & TTS_NOPREFIX)
  1177. uDefDrawFlags = DT_NOPREFIX;
  1178. if (pTtm->iMaxTipWidth == -1)
  1179. {
  1180. uDefDrawFlags |= DT_CALCRECT|DT_SINGLELINE |DT_LEFT;
  1181. MGetTextExtent(hdc, lpstr, -1, pcxText, pcyText);
  1182. nm.nmcd.rc.right = *pcxText;
  1183. nm.nmcd.rc.bottom = *pcyText;
  1184. }
  1185. else
  1186. {
  1187. uDefDrawFlags |= DT_CALCRECT | DT_LEFT | DT_WORDBREAK | DT_EXPANDTABS | DT_EXTERNALLEADING;
  1188. nm.nmcd.rc.right = pTtm->iMaxTipWidth;
  1189. nm.nmcd.rc.bottom = 0;
  1190. DrawText( hdc, lpstr, lstrlen(lpstr), &nm.nmcd.rc, uDefDrawFlags );
  1191. *pcxText = nm.nmcd.rc.right;
  1192. *pcyText = nm.nmcd.rc.bottom;
  1193. }
  1194. if ( (pTtm->pCurTool->uFlags & TTF_RTLREADING) || (pTtm->ci.dwExStyle & WS_EX_RTLREADING) )
  1195. {
  1196. uDefDrawFlags |= DT_RTLREADING;
  1197. }
  1198. //
  1199. // Make it right aligned, if requested. [samera]
  1200. //
  1201. if (pTool->uFlags & TTF_RIGHT)
  1202. uDefDrawFlags |= DT_RIGHT;
  1203. nm.uDrawFlags = uDefDrawFlags;
  1204. dwCustom = (DWORD)SendNotifyEx(pTool->hwnd, (HWND) -1,
  1205. 0, (NMHDR*) &nm,
  1206. (pTool->uFlags & TTF_UNICODE) ? 1 : 0);
  1207. if (TTRenderTitledTip(pTtm, hdc, TRUE, &nm.nmcd.rc, uDefDrawFlags))
  1208. {
  1209. *pcxText = nm.nmcd.rc.right - nm.nmcd.rc.left;
  1210. *pcyText = nm.nmcd.rc.bottom - nm.nmcd.rc.top;
  1211. }
  1212. else if ((dwCustom & CDRF_NEWFONT) || nm.uDrawFlags != uDefDrawFlags)
  1213. {
  1214. DrawText( hdc, lpstr, lstrlen(lpstr), &nm.nmcd.rc, nm.uDrawFlags );
  1215. *pcxText = nm.nmcd.rc.right - nm.nmcd.rc.left;
  1216. *pcyText = nm.nmcd.rc.bottom - nm.nmcd.rc.top;
  1217. }
  1218. // did the owner specify the size?
  1219. else if (pTtm->ci.iVersion >= 5 && (nm.nmcd.rc.right - nm.nmcd.rc.left != *pcxText ||
  1220. nm.nmcd.rc.bottom - nm.nmcd.rc.top != *pcyText))
  1221. {
  1222. *pcxText = nm.nmcd.rc.right - nm.nmcd.rc.left;
  1223. *pcyText = nm.nmcd.rc.bottom - nm.nmcd.rc.top;
  1224. }
  1225. // notify parent afterwards if they want us to
  1226. if (!(dwCustom & CDRF_SKIPDEFAULT) &&
  1227. dwCustom & CDRF_NOTIFYPOSTPAINT) {
  1228. nm.nmcd.dwDrawStage = CDDS_POSTPAINT;
  1229. SendNotifyEx(pTool->hwnd, (HWND) -1,
  1230. 0, (NMHDR*) &nm,
  1231. (pTool->uFlags & TTF_UNICODE) ? 1 : 0);
  1232. }
  1233. }
  1234. if(pTtm->hFont) SelectObject(hdc, hOldFont);
  1235. ReleaseDC(pTtm->ci.hwnd, hdc);
  1236. // after the calc rect, add a little space on the right
  1237. *pcxText += g_cxEdge;
  1238. *pcyText += g_cyEdge;
  1239. }
  1240. //
  1241. // Given an inner rectangle, return the coordinates of the outer,
  1242. // or vice versa.
  1243. //
  1244. // "outer rectangle" = window rectangle.
  1245. // "inner rectangle" = the area where we draw the text.
  1246. //
  1247. // This allows people like listview and treeview to position
  1248. // the tooltip so the inner rectangle exactly coincides with
  1249. // their existing text.
  1250. //
  1251. // All the places we do rectangle adjusting are marked with
  1252. // the comment
  1253. //
  1254. // // ADJUSTRECT! Keep TTAdjustRect in sync.
  1255. //
  1256. LRESULT TTAdjustRect(PToolTipsMgr pTtm, BOOL fLarger, LPRECT prc)
  1257. {
  1258. RECT rc;
  1259. if (!prc)
  1260. return 0;
  1261. //
  1262. // Do all the work on our private little rectangle on the
  1263. // assumption that everything is getting bigger. At the end,
  1264. // we'll flip all the numbers around if in fact we're getting
  1265. // smaller.
  1266. //
  1267. rc.top = rc.left = rc.bottom = rc.right = 0;
  1268. // TTRender adjustments -
  1269. rc.left -= XTEXTOFFSET*g_cxBorder + pTtm->rcMargin.left;
  1270. rc.right += XTEXTOFFSET*g_cxBorder + pTtm->rcMargin.right;
  1271. rc.top -= YTEXTOFFSET*g_cyBorder + pTtm->rcMargin.top;
  1272. rc.bottom += YTEXTOFFSET*g_cyBorder + pTtm->rcMargin.bottom;
  1273. // Compensate for the hack in TTRender that futzes all the rectangles
  1274. // by one pixel. Look for "Account for off-by-one."
  1275. rc.bottom--;
  1276. rc.right--;
  1277. if (pTtm->ci.style & TTS_BALLOON || pTtm->cchTipTitle)
  1278. {
  1279. InflateRect(&rc, XBALLOONOFFSET, YBALLOONOFFSET);
  1280. }
  1281. //
  1282. // Ask Windows how much adjusting he will do to us.
  1283. //
  1284. // Since we don't track WM_STYLECHANGED/GWL_EXSTYLE, we have to ask USER
  1285. // for our style information, since the app may have changed it.
  1286. //
  1287. AdjustWindowRectEx(&rc,
  1288. pTtm->ci.style,
  1289. BOOLFROMPTR(GetMenu(pTtm->ci.hwnd)),
  1290. GetWindowLong(pTtm->ci.hwnd, GWL_EXSTYLE));
  1291. //
  1292. // Now adjust our caller's rectangle.
  1293. //
  1294. if (fLarger)
  1295. {
  1296. prc->left += rc.left;
  1297. prc->right += rc.right;
  1298. prc->top += rc.top;
  1299. prc->bottom += rc.bottom;
  1300. }
  1301. else
  1302. {
  1303. prc->left -= rc.left;
  1304. prc->right -= rc.right;
  1305. prc->top -= rc.top;
  1306. prc->bottom -= rc.bottom;
  1307. }
  1308. return TRUE;
  1309. }
  1310. #define CSTEMPOINTS 3
  1311. // bMirrored does not mean a mirrored tooltip.
  1312. // It means simulating the behavior or a mirrored tooltip for a tooltip created with a mirrored parent.
  1313. HRGN CreateBalloonRgn(int xStem, int yStem, int iWidth, int iHeight, int iStemHeight, BOOL bUnderStem, BOOL bMirrored)
  1314. {
  1315. int y = 0, yHeight = iHeight;
  1316. HRGN rgn;
  1317. if (bUnderStem)
  1318. yHeight -= iStemHeight;
  1319. else
  1320. y = iStemHeight;
  1321. rgn = CreateRoundRectRgn(0, y, iWidth, yHeight, BALLOON_X_CORNER, BALLOON_Y_CORNER);
  1322. if (rgn)
  1323. {
  1324. // create wedge/stem rgn
  1325. if (iWidth >= MINBALLOONWIDTH)
  1326. {
  1327. HRGN rgnStem;
  1328. POINT aptStemRgn[CSTEMPOINTS];
  1329. POINT *ppt = aptStemRgn;
  1330. POINT pt;
  1331. BOOL bCentered;
  1332. int iStemWidth = iStemHeight+1; // for a 45 degree angle
  1333. // we center the stem if we have TTF_CENTERTIP or the width
  1334. // of the balloon is not big enough to offset the stem by
  1335. // STEMOFFSET
  1336. // can't quite center the tip on TTF_CENTERTIP because it may be
  1337. // moved left or right it did not fit on the screen: just check
  1338. // if xStem is in the middle
  1339. bCentered = (xStem == iWidth/2) || (iWidth < 2*STEMOFFSET + iStemWidth);
  1340. if (bCentered)
  1341. pt.x = (iWidth - iStemWidth)/2;
  1342. else if (xStem > iWidth/2)
  1343. {
  1344. if(bMirrored)
  1345. {
  1346. pt.x = STEMOFFSET + iStemWidth;
  1347. }
  1348. else
  1349. {
  1350. pt.x = iWidth - STEMOFFSET - iStemWidth;
  1351. }
  1352. }
  1353. else
  1354. {
  1355. if(bMirrored)
  1356. {
  1357. pt.x = iWidth - STEMOFFSET;
  1358. }
  1359. else
  1360. {
  1361. pt.x = STEMOFFSET;
  1362. }
  1363. }
  1364. if (bMirrored && (ABS(pt.x - (iWidth - xStem)) <= 2))
  1365. {
  1366. pt.x = iWidth - xStem; // avoid rough edges, have a straight line
  1367. }
  1368. else if (!bMirrored && (ABS(pt.x - xStem) <= 2))
  1369. {
  1370. pt.x = xStem; // avoid rough edges, have a straight line
  1371. }
  1372. if (bUnderStem)
  1373. pt.y = iHeight - iStemHeight - 2;
  1374. else
  1375. pt.y = iStemHeight + 2;
  1376. *ppt++ = pt;
  1377. if(bMirrored)
  1378. {
  1379. pt.x -= iStemWidth;
  1380. }
  1381. else
  1382. {
  1383. pt.x += iStemWidth;
  1384. }
  1385. if (bMirrored && (ABS(pt.x - (iWidth - xStem)) <= 2))
  1386. {
  1387. pt.x = iWidth - xStem; // avoid rough edges, have a straight line
  1388. }
  1389. else if (!bMirrored && (ABS(pt.x - xStem) <= 2))
  1390. {
  1391. pt.x = xStem; // avoid rough edges, have a straight line
  1392. }
  1393. *ppt++ = pt;
  1394. if(bMirrored)
  1395. {
  1396. pt.x = iWidth - xStem;
  1397. }
  1398. else
  1399. {
  1400. pt.x = xStem;
  1401. }
  1402. pt.y = yStem;
  1403. *ppt = pt;
  1404. rgnStem = CreatePolygonRgn(aptStemRgn, CSTEMPOINTS, ALTERNATE);
  1405. if (rgnStem)
  1406. {
  1407. CombineRgn(rgn, rgn, rgnStem, RGN_OR);
  1408. DeleteObject(rgnStem);
  1409. }
  1410. }
  1411. }
  1412. return rgn;
  1413. }
  1414. void NEAR PASCAL DoShowBubble(PToolTipsMgr pTtm)
  1415. {
  1416. HFONT hFontPrev;
  1417. RECT rc;
  1418. int cxText, cyText;
  1419. int xStem, yStem;
  1420. LPTSTR lpstr;
  1421. NMTTSHOWINFO si;
  1422. DebugMsg(TF_TT, TEXT("Entering DoShowBubble"));
  1423. lpstr = GetCurToolText(pTtm);
  1424. if (pTtm->dwFlags & TRACKMODE) {
  1425. if (!lpstr || !*lpstr) {
  1426. PopBubble(pTtm);
  1427. pTtm->dwFlags &= ~TRACKMODE;
  1428. return;
  1429. }
  1430. } else {
  1431. TTSetTimer(pTtm, TTT_POP);
  1432. if( !lpstr || !*lpstr ) {
  1433. ShowVirtualBubble(pTtm);
  1434. return;
  1435. }
  1436. TTSetTimer(pTtm, TTT_AUTOPOP);
  1437. }
  1438. do {
  1439. // get the size it will be
  1440. TTGetTipSize(pTtm, pTtm->pCurTool, lpstr, &cxText, &cyText);
  1441. TTGetTipPosition(pTtm, &rc, cxText, cyText, &xStem, &yStem);
  1442. {
  1443. UINT uFlags = SWP_NOACTIVATE | SWP_NOZORDER;
  1444. if (pTtm->ci.style & TTS_BALLOON)
  1445. uFlags |= SWP_HIDEWINDOW;
  1446. SetWindowPos(pTtm->ci.hwnd, NULL, rc.left, rc.top,
  1447. rc.right-rc.left, rc.bottom-rc.top, uFlags);
  1448. }
  1449. // BUGBUG: chicago id was busted. I *hope* no one relied on it...
  1450. // bzzzz... folks did. we're stuck with it
  1451. si.hdr.hwndFrom = pTtm->ci.hwnd;
  1452. si.hdr.idFrom = pTtm->pCurTool->uId;
  1453. si.hdr.code = TTN_SHOW;
  1454. si.dwStyle = pTtm->ci.style;
  1455. hFontPrev = pTtm->hFont;
  1456. if (!SendNotifyEx(pTtm->pCurTool->hwnd, (HWND)-1,
  1457. TTN_SHOW, &si.hdr,
  1458. (pTtm->pCurTool->uFlags & TTF_UNICODE) ? 1 : 0)) {
  1459. // Bring to top only if we are an unowned tooltip, since we
  1460. // may have sunken below our tool in the Z-order. Do this
  1461. // only if unowned; if we are owned, then USER will make sure
  1462. // we are above our owner.
  1463. //
  1464. // We must scrupulously avoid messing with our Z-order in the
  1465. // owned case, because Office curiously creates a tooltip
  1466. // owned by toplevel window 1, but attached to a tool on
  1467. // toplevel window 2. When you hover over window 2, the
  1468. // tooltip from window 1 wants to appear. If we brought
  1469. // ourselves to the top, this would also bring window 1
  1470. // to the top (because USER raises and lowers owned/owner
  1471. // windows as a group). Result: Window 1 covers window 2.
  1472. UINT uFlags = SWP_NOACTIVATE | SWP_NOSIZE;
  1473. if (GetWindow(pTtm->ci.hwnd, GW_OWNER))
  1474. uFlags |= SWP_NOZORDER;
  1475. SetWindowPos(pTtm->ci.hwnd, HWND_TOP, rc.left, rc.top,
  1476. 0, 0, uFlags);
  1477. }
  1478. } while (hFontPrev != pTtm->hFont);
  1479. // create the balloon region if necessary
  1480. // Note: Don't use si.dwStyle here, since other parts of comctl32
  1481. // look at pTtm->ci.style to decide what to do
  1482. if (pTtm->ci.style & TTS_BALLOON)
  1483. {
  1484. HRGN rgn;
  1485. BOOL bMirrored = FALSE;
  1486. if(pTtm->pCurTool)
  1487. {
  1488. bMirrored = (IS_WINDOW_RTL_MIRRORED(pTtm->pCurTool->hwnd) && (!(pTtm->ci.dwExStyle & RTL_MIRRORED_WINDOW)));
  1489. }
  1490. pTtm->fUnderStem = yStem >= rc.bottom-1;
  1491. rgn = CreateBalloonRgn(bMirrored ? (rc.right - xStem) : (xStem - rc.left), yStem-rc.top, rc.right-rc.left, rc.bottom-rc.top,
  1492. pTtm->iStemHeight, pTtm->fUnderStem, bMirrored);
  1493. if (rgn && !SetWindowRgn(pTtm->ci.hwnd, rgn, FALSE))
  1494. DeleteObject(rgn);
  1495. // AnimateWindow does not support regions so we must do SetWindowPos
  1496. SetWindowPos(pTtm->ci.hwnd,HWND_TOP,0,0,0,0,SWP_NOACTIVATE|SWP_SHOWWINDOW|SWP_NOSIZE|SWP_NOMOVE);
  1497. }
  1498. else
  1499. {
  1500. BOOL fAllowFade = !(si.dwStyle & TTS_NOFADE);
  1501. BOOL fAllowAnimate = !(si.dwStyle & TTS_NOANIMATE);
  1502. DWORD dwCurrentTime = (pTtm->dwLastDisplayTime == 0)? TIMEBETWEENANIMATE : GetTickCount();
  1503. DWORD dwDelta = dwCurrentTime - pTtm->dwLastDisplayTime;
  1504. // If we're under the minimum time between animates, then we don't animate
  1505. if (dwDelta < TIMEBETWEENANIMATE)
  1506. fAllowFade = fAllowAnimate = FALSE;
  1507. CoolTooltipBubble(pTtm->ci.hwnd, &rc, fAllowFade, fAllowAnimate);
  1508. pTtm->dwLastDisplayTime = GetTickCount();
  1509. //
  1510. // HACK! for MetaStock 6.5. They superclass the tooltips class and install
  1511. // their own class which takes over WM_PAINT completely. Animation causes
  1512. // them to get confused because that causes us to receive a WM_PRINTCLIENT,
  1513. // which causes TTRender to send a TTN_NEEDTEXT, and they never expected
  1514. // to receive that notification at that time.
  1515. //
  1516. // We used to show ourselves with an empty window region, then see if the
  1517. // WM_PAINT ever reached us. Unfortunately, that roached Outlook. So we
  1518. // just look at the flag afterwards. This means that MetaStock's first
  1519. // tooltip will look bad, but the rest will be okay.
  1520. //
  1521. if (pTtm->ci.iVersion < 4 && !pTtm->fEverShown &&
  1522. (si.dwStyle & (TTS_NOFADE | TTS_NOANIMATE)) == 0) {
  1523. // Force a WM_PAINT message so we can check if we got it
  1524. InvalidateRect(pTtm->ci.hwnd, NULL, TRUE);
  1525. UpdateWindow(pTtm->ci.hwnd);
  1526. if (!pTtm->fEverShown) {
  1527. // Detected a hacky app. Turn off animation.
  1528. SetWindowBits(pTtm->ci.hwnd, GWL_STYLE, TTS_NOFADE | TTS_NOANIMATE,
  1529. TTS_NOFADE | TTS_NOANIMATE);
  1530. pTtm->fEverShown = TRUE; // don't make this check again
  1531. }
  1532. }
  1533. }
  1534. pTtm->dwFlags |= BUBBLEUP;
  1535. RedrawWindow(pTtm->ci.hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
  1536. }
  1537. void NEAR PASCAL ShowBubbleForTool(PToolTipsMgr pTtm, PTOOLINFO pTool)
  1538. {
  1539. DebugMsg(TF_TT, TEXT("ShowBubbleForTool"));
  1540. // if there's a bubble up for a different tool, pop it.
  1541. if ((pTool != pTtm->pCurTool) && (pTtm->dwFlags & BUBBLEUP)) {
  1542. PopBubble(pTtm);
  1543. }
  1544. // if the bubble was for a different tool, or no bubble, show it
  1545. if ((pTool != pTtm->pCurTool) || !(pTtm->dwFlags & (VIRTUALBUBBLEUP|BUBBLEUP))) {
  1546. pTtm->pCurTool = pTool;
  1547. DoShowBubble(pTtm);
  1548. } else {
  1549. DebugMsg(TF_TT, TEXT("ShowBubbleForTool not showinb bubble"));
  1550. }
  1551. }
  1552. void NEAR PASCAL HandleRelayedMessage(PToolTipsMgr pTtm, HWND hwnd,
  1553. UINT message, WPARAM wParam, LPARAM lParam)
  1554. {
  1555. int ht = HTERROR;
  1556. if (pTtm->dwFlags & TRACKMODE) {
  1557. // punt all messages if we're in track mode
  1558. return;
  1559. }
  1560. if (pTtm->dwFlags & BUTTONISDOWN) {
  1561. // verify that the button is down
  1562. // this can happen if the tool didn't set capture so it didn't get the up message
  1563. if (GetKeyState(VK_LBUTTON) >= 0 &&
  1564. GetKeyState(VK_RBUTTON) >= 0 &&
  1565. GetKeyState(VK_MBUTTON) >= 0)
  1566. pTtm->dwFlags &= ~BUTTONISDOWN;
  1567. }
  1568. switch(message) {
  1569. case WM_NCLBUTTONUP:
  1570. case WM_NCRBUTTONUP:
  1571. case WM_NCMBUTTONUP:
  1572. case WM_MBUTTONUP:
  1573. case WM_RBUTTONUP:
  1574. case WM_LBUTTONUP:
  1575. pTtm->dwFlags &= ~BUTTONISDOWN;
  1576. break;
  1577. case WM_NCLBUTTONDOWN:
  1578. case WM_NCRBUTTONDOWN:
  1579. case WM_NCMBUTTONDOWN:
  1580. case WM_MBUTTONDOWN:
  1581. case WM_RBUTTONDOWN:
  1582. case WM_LBUTTONDOWN:
  1583. pTtm->dwFlags |= BUTTONISDOWN;
  1584. ShowVirtualBubble(pTtm);
  1585. break;
  1586. case WM_NCMOUSEMOVE:
  1587. {
  1588. // convert to client coords
  1589. POINT pt;
  1590. pt.x = GET_X_LPARAM(lParam);
  1591. pt.y = GET_Y_LPARAM(lParam);
  1592. ScreenToClient(hwnd, &pt);
  1593. lParam = MAKELONG(pt.x, pt.y);
  1594. ht = (int) wParam;
  1595. // Fall thru...
  1596. }
  1597. case WM_MOUSEMOVE: {
  1598. PTOOLINFO pTool;
  1599. // to prevent us from popping up when some
  1600. // other app is active
  1601. if(((!(pTtm->ci.style & TTS_ALWAYSTIP)) && !(ChildOfActiveWindow(hwnd))) ||
  1602. !(pTtm->dwFlags & ACTIVE) ||
  1603. (pTtm->dwFlags & BUTTONISDOWN))
  1604. {
  1605. break;
  1606. }
  1607. pTool = GetToolAtPoint(pTtm, hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), ht, FALSE);
  1608. if(pTool) {
  1609. int id;
  1610. // show only if another is showing
  1611. if (pTtm->dwFlags & (VIRTUALBUBBLEUP | BUBBLEUP)) {
  1612. // call show if bubble is up to make sure we're showing
  1613. // for the right tool
  1614. if (pTool != pTtm->pCurTool) {
  1615. DebugMsg(TF_TT, TEXT("showing virtual bubble"));
  1616. PopBubble(pTtm);
  1617. pTtm->pCurTool = pTool;
  1618. ShowVirtualBubble(pTtm);
  1619. id = TTT_RESHOW;
  1620. } else {
  1621. if (pTtm->idTimer == TTT_RESHOW) {
  1622. // if the timer is currently waiting to reshow,
  1623. // don't reset the timer on mouse moves
  1624. id = 0;
  1625. } else {
  1626. // if we're looking to pop the bubble,
  1627. // any mouse move within the same window
  1628. // should reset our timer.
  1629. id = TTT_POP;
  1630. }
  1631. }
  1632. if (pTtm->idtAutoPop)
  1633. TTSetTimer(pTtm, TTT_AUTOPOP);
  1634. } else {
  1635. pTtm->pCurTool = pTool;
  1636. id = TTT_INITIAL;
  1637. }
  1638. DebugMsg(TF_TT, TEXT("MouseMove over pTool id = %d"), id);
  1639. if (id)
  1640. TTSetTimer(pTtm, id);
  1641. } else {
  1642. DebugMsg(TF_TT, TEXT("MouseMove over non-tool"));
  1643. PopBubble(pTtm);
  1644. }
  1645. break;
  1646. }
  1647. }
  1648. }
  1649. void NEAR PASCAL TTUpdateTipText(PToolTipsMgr pTtm, LPTOOLINFO lpti)
  1650. {
  1651. LPTOOLINFO lpTool;
  1652. lpTool = FindTool(pTtm, lpti);
  1653. if (lpTool) {
  1654. lpTool->hinst = lpti->hinst;
  1655. TTSetTipText(lpTool, lpti->lpszText);
  1656. if (pTtm->dwFlags & TRACKMODE) {
  1657. // if track mode is in effect and active, then
  1658. // redisplay the bubble.
  1659. if (pTtm->pCurTool)
  1660. DoShowBubble(pTtm);
  1661. } else
  1662. if (lpTool == pTtm->pCurTool) {
  1663. // set the current position to our saved position.
  1664. // ToolHasMoved will return false for us if those this point
  1665. // is no longer within pCurTool's area
  1666. GetCursorPos(&pTtm->pt);
  1667. if (!ToolHasMoved(pTtm)) {
  1668. if (pTtm->dwFlags & ( VIRTUALBUBBLEUP | BUBBLEUP))
  1669. DoShowBubble(pTtm);
  1670. } else {
  1671. DebugMsg(TF_TT, TEXT("TTUpdateTipText popping bubble"));
  1672. PopBubble(pTtm);
  1673. }
  1674. }
  1675. }
  1676. }
  1677. void NEAR PASCAL TTSetFont(PToolTipsMgr pTtm, HFONT hFont, BOOL fInval)
  1678. {
  1679. ToolTips_NewFont(pTtm, hFont);
  1680. if (fInval)
  1681. {
  1682. // is a balloon up and is it in the track mode?
  1683. if ((pTtm->dwFlags & ACTIVE) && pTtm->pCurTool && (pTtm->pCurTool->uFlags & TTF_TRACK))
  1684. {
  1685. PTOOLINFO pCurTool = pTtm->pCurTool;
  1686. PopBubble(pTtm); // sets pTtm->pCurTool to NULL
  1687. ShowBubbleForTool(pTtm, pCurTool);
  1688. }
  1689. else
  1690. InvalidateRect(pTtm->ci.hwnd, NULL, FALSE);
  1691. }
  1692. }
  1693. void NEAR PASCAL TTSetDelayTime(PToolTipsMgr pTtm, WPARAM wParam, LPARAM lParam)
  1694. {
  1695. int iDelayTime = GET_X_LPARAM(lParam);
  1696. switch (wParam)
  1697. {
  1698. case TTDT_INITIAL:
  1699. pTtm->iDelayTime = iDelayTime;
  1700. break;
  1701. case TTDT_AUTOPOP:
  1702. pTtm->iAutoPopTime = iDelayTime;
  1703. break;
  1704. case TTDT_RESHOW:
  1705. pTtm->iReshowTime = iDelayTime;
  1706. break;
  1707. case TTDT_AUTOMATIC:
  1708. if (iDelayTime > 0)
  1709. {
  1710. pTtm->iDelayTime = iDelayTime;
  1711. pTtm->iReshowTime = pTtm->iDelayTime / 5;
  1712. pTtm->iAutoPopTime = pTtm->iDelayTime * 10;
  1713. }
  1714. else
  1715. {
  1716. pTtm->iDelayTime = -1;
  1717. pTtm->iReshowTime = -1;
  1718. pTtm->iAutoPopTime = -1;
  1719. }
  1720. break;
  1721. }
  1722. }
  1723. int NEAR PASCAL TTGetDelayTime(PToolTipsMgr pTtm, WPARAM wParam)
  1724. {
  1725. switch (wParam) {
  1726. case TTDT_AUTOMATIC:
  1727. case TTDT_INITIAL:
  1728. return (pTtm->iDelayTime < 0 ? GetDoubleClickTime() : pTtm->iDelayTime);
  1729. case TTDT_AUTOPOP:
  1730. return (pTtm->iAutoPopTime < 0 ? GetDoubleClickTime()*10 : pTtm->iAutoPopTime);
  1731. case TTDT_RESHOW:
  1732. return (pTtm->iReshowTime < 0 ? GetDoubleClickTime()/5 : pTtm->iReshowTime);
  1733. default:
  1734. return -1;
  1735. }
  1736. }
  1737. BOOL NEAR PASCAL CopyToolInfoA(PTOOLINFO pToolSrc, PTOOLINFOA lpTool, UINT uiCodePage)
  1738. {
  1739. if (pToolSrc && lpTool)
  1740. {
  1741. if (lpTool->cbSize >= sizeof(TOOLINFOA) - sizeof(LPARAM))
  1742. {
  1743. lpTool->uFlags = pToolSrc->uFlags;
  1744. lpTool->hwnd = pToolSrc->hwnd;
  1745. lpTool->uId = pToolSrc->uId;
  1746. lpTool->rect = pToolSrc->rect;
  1747. lpTool->hinst = pToolSrc->hinst;
  1748. if ((pToolSrc->lpszText != LPSTR_TEXTCALLBACK) &&
  1749. !IS_INTRESOURCE(pToolSrc->lpszText))
  1750. {
  1751. if (lpTool->lpszText)
  1752. {
  1753. WideCharToMultiByte(uiCodePage, 0,
  1754. pToolSrc->lpszText,
  1755. -1,
  1756. lpTool->lpszText,
  1757. 80, NULL, NULL);
  1758. }
  1759. }
  1760. else
  1761. {
  1762. lpTool->lpszText = (LPSTR)pToolSrc->lpszText;
  1763. }
  1764. }
  1765. if (lpTool->cbSize > FIELD_OFFSET(TOOLINFOA, lParam))
  1766. lpTool->lParam = pToolSrc->lParam;
  1767. if (lpTool->cbSize > sizeof(TOOLINFOA))
  1768. return FALSE;
  1769. return TRUE;
  1770. }
  1771. else
  1772. return FALSE;
  1773. }
  1774. BOOL NEAR PASCAL CopyToolInfo(PTOOLINFO pToolSrc, PTOOLINFO lpTool)
  1775. {
  1776. if (pToolSrc && lpTool && lpTool->cbSize <= sizeof(TOOLINFO))
  1777. {
  1778. if (lpTool->cbSize >= sizeof(TOOLINFO) - sizeof(LPARAM))
  1779. {
  1780. lpTool->uFlags = pToolSrc->uFlags;
  1781. lpTool->hwnd = pToolSrc->hwnd;
  1782. lpTool->uId = pToolSrc->uId;
  1783. lpTool->rect = pToolSrc->rect;
  1784. lpTool->hinst = pToolSrc->hinst;
  1785. if ((pToolSrc->lpszText != LPSTR_TEXTCALLBACK) && !IS_INTRESOURCE(pToolSrc->lpszText))
  1786. {
  1787. if (lpTool->lpszText)
  1788. {
  1789. // REVIEW: message parameters do not indicate the size of the
  1790. // destination buffer.
  1791. StringCchCopy(lpTool->lpszText, lstrlen(pToolSrc->lpszText)+1, pToolSrc->lpszText);
  1792. }
  1793. }
  1794. else
  1795. lpTool->lpszText = pToolSrc->lpszText;
  1796. }
  1797. if (lpTool->cbSize > FIELD_OFFSET(TOOLINFO, lParam))
  1798. lpTool->lParam = pToolSrc->lParam;
  1799. if (lpTool->cbSize > sizeof(TOOLINFO))
  1800. return FALSE;
  1801. return TRUE;
  1802. }
  1803. else
  1804. return FALSE;
  1805. }
  1806. PTOOLINFO TTToolAtMessagePos(PToolTipsMgr pTtm)
  1807. {
  1808. PTOOLINFO pTool;
  1809. HWND hwndPt;
  1810. POINT pt;
  1811. DWORD dwPos = GetMessagePos();
  1812. //int ht;
  1813. pt.x = GET_X_LPARAM(dwPos);
  1814. pt.y = GET_Y_LPARAM(dwPos);
  1815. hwndPt = TTWindowFromPoint(pTtm, &pt);
  1816. //ht = SendMessage(hwndPt, WM_NCHITTEST, 0, MAKELONG(pt.x, pt.y));
  1817. ScreenToClient(hwndPt, &pt);
  1818. pTool = GetToolAtPoint(pTtm, hwndPt, pt.x, pt.y, HTERROR, TRUE);
  1819. return pTool;
  1820. }
  1821. void TTCheckCursorPos(PToolTipsMgr pTtm)
  1822. {
  1823. PTOOLINFO pTool;
  1824. pTool = TTToolAtMessagePos(pTtm);
  1825. if ((pTtm->pCurTool != pTool) ||
  1826. ToolHasMoved(pTtm)) {
  1827. PopBubble(pTtm);
  1828. DebugMsg(TF_TT, TEXT("TTCheckCursorPos popping bubble"));
  1829. }
  1830. }
  1831. void NEAR PASCAL TTHandleTimer(PToolTipsMgr pTtm, UINT_PTR id)
  1832. {
  1833. PTOOLINFO pTool;
  1834. // punt all timers in track mode
  1835. if (pTtm->dwFlags & TRACKMODE)
  1836. return;
  1837. switch (id) {
  1838. case TTT_AUTOPOP:
  1839. TTCheckCursorPos(pTtm);
  1840. if (pTtm->pCurTool) {
  1841. DebugMsg(TF_TT, TEXT("ToolTips: Auto popping"));
  1842. ShowVirtualBubble(pTtm);
  1843. }
  1844. break;
  1845. case TTT_POP:
  1846. // this could be started up again by a slight mouse touch
  1847. if (pTtm->dwFlags & VIRTUALBUBBLEUP) {
  1848. KillTimer(pTtm->ci.hwnd, TTT_POP);
  1849. }
  1850. TTCheckCursorPos(pTtm);
  1851. break;
  1852. case TTT_INITIAL:
  1853. if(ToolHasMoved(pTtm)) {
  1854. // this means the timer went off
  1855. // without us getting a mouse move
  1856. // which means they left our tools.
  1857. PopBubble(pTtm);
  1858. break;
  1859. }
  1860. // else fall through
  1861. case TTT_RESHOW:
  1862. pTool = TTToolAtMessagePos(pTtm);
  1863. if (!pTool) {
  1864. if (pTtm->pCurTool)
  1865. PopBubble(pTtm);
  1866. } else if (pTtm->dwFlags & ACTIVE) {
  1867. if (id == TTT_RESHOW) {
  1868. // this will force a re-show
  1869. pTtm->dwFlags &= ~(BUBBLEUP|VIRTUALBUBBLEUP);
  1870. }
  1871. ShowBubbleForTool(pTtm, pTool);
  1872. }
  1873. break;
  1874. }
  1875. }
  1876. BOOL TTRender(PToolTipsMgr pTtm, HDC hdc)
  1877. {
  1878. BOOL bRet = FALSE;
  1879. RECT rc;
  1880. LPTSTR lpszStr;
  1881. if (pTtm->pCurTool &&
  1882. (lpszStr = GetCurToolText(pTtm)) &&
  1883. *lpszStr) {
  1884. UINT uFlags;
  1885. NMTTCUSTOMDRAW nm;
  1886. UINT uDefDrawFlags = 0;
  1887. BOOL bUseDrawText;
  1888. LPRECT prcMargin = &pTtm->rcMargin;
  1889. HBRUSH hbr;
  1890. DWORD dwCustomDraw;
  1891. uFlags = 0;
  1892. if ( (pTtm->pCurTool->uFlags & TTF_RTLREADING) || (pTtm->ci.dwExStyle & WS_EX_RTLREADING) )
  1893. {
  1894. uFlags |= ETO_RTLREADING;
  1895. }
  1896. SelectObject(hdc, pTtm->hFont);
  1897. GetClientRect(pTtm->ci.hwnd, &rc);
  1898. SetTextColor(hdc, pTtm->clrTipText);
  1899. /* If we support pre-Draw text then call the client allowing them to modify
  1900. / the item, and then render. Otherwise just use ExTextOut */
  1901. nm.nmcd.hdr.hwndFrom = pTtm->ci.hwnd;
  1902. nm.nmcd.hdr.idFrom = pTtm->pCurTool->uId;
  1903. nm.nmcd.hdr.code = NM_CUSTOMDRAW;
  1904. nm.nmcd.hdc = hdc;
  1905. nm.nmcd.dwDrawStage = CDDS_PREPAINT;
  1906. // ADJUSTRECT! Keep TTAdjustRect and TTGetTipPosition in sync.
  1907. nm.nmcd.rc.left = rc.left + XTEXTOFFSET*g_cxBorder + prcMargin->left;
  1908. nm.nmcd.rc.right = rc.right - XTEXTOFFSET*g_cxBorder - prcMargin->right;
  1909. nm.nmcd.rc.top = rc.top + YTEXTOFFSET*g_cyBorder + prcMargin->top;
  1910. nm.nmcd.rc.bottom = rc.bottom - YTEXTOFFSET*g_cyBorder - prcMargin->bottom;
  1911. if (pTtm->ci.style & TTS_BALLOON)
  1912. {
  1913. InflateRect(&(nm.nmcd.rc), -XBALLOONOFFSET, -YBALLOONOFFSET);
  1914. if (!pTtm->fUnderStem)
  1915. OffsetRect(&(nm.nmcd.rc), 0, pTtm->iStemHeight);
  1916. }
  1917. if (pTtm->iMaxTipWidth == -1)
  1918. uDefDrawFlags = DT_SINGLELINE |DT_LEFT;
  1919. else
  1920. uDefDrawFlags = DT_LEFT | DT_WORDBREAK | DT_EXPANDTABS | DT_EXTERNALLEADING;
  1921. if (pTtm->ci.style & TTS_NOPREFIX)
  1922. uDefDrawFlags |= DT_NOPREFIX;
  1923. if ( (pTtm->pCurTool->uFlags & TTF_RTLREADING) || (pTtm->ci.dwExStyle & WS_EX_RTLREADING) )
  1924. {
  1925. uDefDrawFlags |= DT_RTLREADING;
  1926. }
  1927. //
  1928. // Make it right aligned, if requested. [samera]
  1929. //
  1930. if (pTtm->pCurTool->uFlags & TTF_RIGHT)
  1931. uDefDrawFlags |= DT_RIGHT;
  1932. nm.uDrawFlags = uDefDrawFlags;
  1933. dwCustomDraw = (DWORD)SendNotifyEx(pTtm->pCurTool->hwnd, (HWND) -1,
  1934. 0, (NMHDR*) &nm,
  1935. (pTtm->pCurTool->uFlags & TTF_UNICODE) ? 1 : 0);
  1936. // did the owner do custom draw? yes, we're done
  1937. if (pTtm->ci.iVersion >= 5 && dwCustomDraw == CDRF_SKIPDEFAULT)
  1938. return TRUE;
  1939. bUseDrawText = (nm.uDrawFlags != uDefDrawFlags ||
  1940. !(uDefDrawFlags & DT_SINGLELINE) ||
  1941. (uDefDrawFlags & (DT_RTLREADING|DT_RIGHT)) ||
  1942. (pTtm->cchTipTitle != 0));
  1943. if (pTtm->clrTipBk != GetNearestColor(hdc, pTtm->clrTipBk) ||
  1944. bUseDrawText)
  1945. {
  1946. // if this fails, it may be the a dither...
  1947. // in which case, we can't set the bk color
  1948. hbr = CreateSolidBrush(pTtm->clrTipBk);
  1949. FillRect(hdc, &rc, hbr);
  1950. DeleteObject(hbr);
  1951. SetBkMode(hdc, TRANSPARENT);
  1952. uFlags |= ETO_CLIPPED;
  1953. }
  1954. else
  1955. {
  1956. uFlags |= ETO_OPAQUE;
  1957. SetBkColor(hdc, pTtm->clrTipBk);
  1958. }
  1959. if (bUseDrawText)
  1960. {
  1961. // Account for off-by-one. Something wierd about DrawText
  1962. // clips the bottom-most pixelrow, so increase one more
  1963. // into the margin space.
  1964. // ADJUSTRECT! Keep TTAdjustRect in sync.
  1965. nm.nmcd.rc.bottom++;
  1966. nm.nmcd.rc.right++;
  1967. // if in balloon style the text is already indented so no need for inflate..
  1968. if (pTtm->cchTipTitle > 0 && !(pTtm->ci.style & TTS_BALLOON))
  1969. InflateRect(&nm.nmcd.rc, -XBALLOONOFFSET, -YBALLOONOFFSET);
  1970. if (!TTRenderTitledTip(pTtm, hdc, FALSE, &nm.nmcd.rc, uDefDrawFlags))
  1971. DrawText(hdc, lpszStr, lstrlen(lpszStr), &nm.nmcd.rc, nm.uDrawFlags);
  1972. }
  1973. else
  1974. {
  1975. // ADJUSTRECT! Keep TTAdjustRect and TTGetTipPosition in sync.
  1976. int x = XTEXTOFFSET*g_cxBorder + prcMargin->left;
  1977. int y = YTEXTOFFSET*g_cyBorder + prcMargin->top;
  1978. if (pTtm->ci.style & TTS_BALLOON)
  1979. {
  1980. HRGN rgn;
  1981. x += XBALLOONOFFSET;
  1982. y += YBALLOONOFFSET;
  1983. InflateRect(&rc, -XBALLOONOFFSET, -YBALLOONOFFSET);
  1984. if (!pTtm->fUnderStem)
  1985. {
  1986. y += pTtm->iStemHeight;
  1987. OffsetRect(&rc, 0, pTtm->iStemHeight);
  1988. }
  1989. rgn = CreateRectRgn(1,1,2,2);
  1990. if (rgn)
  1991. {
  1992. int iRet = GetWindowRgn(pTtm->ci.hwnd, rgn);
  1993. if (iRet != ERROR)
  1994. {
  1995. // ExtTextOut only fills the rect specified and that
  1996. // only if uFlags & ETO_OPAQUE
  1997. HBRUSH hbr = CreateSolidBrush(pTtm->clrTipBk);
  1998. FillRgn(hdc, rgn, hbr);
  1999. DeleteObject(hbr);
  2000. }
  2001. DeleteObject(rgn);
  2002. }
  2003. }
  2004. else if (pTtm->cchTipTitle > 0)
  2005. {
  2006. InflateRect(&rc, -XBALLOONOFFSET, -YBALLOONOFFSET);
  2007. }
  2008. if (!TTRenderTitledTip(pTtm, hdc, FALSE, &rc, uDefDrawFlags))
  2009. ExtTextOut(hdc, x, y, uFlags, &rc, lpszStr, lstrlen(lpszStr), NULL);
  2010. }
  2011. if (pTtm->ci.style & TTS_BALLOON)
  2012. {
  2013. HRGN rgn = CreateRectRgn(1,1,2,2);
  2014. if (rgn)
  2015. {
  2016. int iRet = GetWindowRgn(pTtm->ci.hwnd, rgn);
  2017. if (iRet != ERROR)
  2018. {
  2019. HBRUSH hbr = CreateSolidBrush(pTtm->clrTipText);
  2020. FrameRgn(hdc, rgn, hbr, 1, 1);
  2021. DeleteObject(hbr);
  2022. }
  2023. DeleteObject(rgn);
  2024. }
  2025. }
  2026. // notify parent afterwards if they want us to
  2027. if (!(dwCustomDraw & CDRF_SKIPDEFAULT) &&
  2028. dwCustomDraw & CDRF_NOTIFYPOSTPAINT) {
  2029. // Convert PREPAINT to POSTPAINT and ITEMPREPAINT to ITEMPOSTPAINT
  2030. COMPILETIME_ASSERT(CDDS_POSTPAINT - CDDS_PREPAINT ==
  2031. CDDS_ITEMPOSTPAINT - CDDS_ITEMPREPAINT);
  2032. nm.nmcd.dwDrawStage += CDDS_POSTPAINT - CDDS_PREPAINT;
  2033. SendNotifyEx(pTtm->pCurTool->hwnd, (HWND) -1,
  2034. 0, (NMHDR*) &nm,
  2035. (pTtm->pCurTool->uFlags & TTF_UNICODE) ? 1 : 0);
  2036. }
  2037. bRet = TRUE;
  2038. }
  2039. return bRet;
  2040. }
  2041. void TTOnPaint(PToolTipsMgr pTtm)
  2042. {
  2043. PAINTSTRUCT ps;
  2044. HDC hdc = BeginPaint(pTtm->ci.hwnd, &ps);
  2045. if (!TTRender(pTtm, hdc)) {
  2046. DebugMsg(TF_TT, TEXT("TTOnPaint render failed popping bubble"));
  2047. PopBubble(pTtm);
  2048. }
  2049. EndPaint(pTtm->ci.hwnd, &ps);
  2050. pTtm->fEverShown = TRUE; // See TTOnFirstShow
  2051. }
  2052. LRESULT WINAPI ToolTipsWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  2053. {
  2054. PTOOLINFO pTool;
  2055. PTOOLINFO pToolSrc;
  2056. PToolTipsMgr pTtm = GetWindowPtr(hwnd, 0);
  2057. if (!pTtm && uMsg != WM_CREATE)
  2058. goto DoDefault;
  2059. switch(uMsg)
  2060. {
  2061. case TTM_ACTIVATE:
  2062. if (wParam) {
  2063. pTtm->dwFlags |= ACTIVE;
  2064. } else {
  2065. PopBubble(pTtm);
  2066. pTtm->dwFlags &= ~(ACTIVE | TRACKMODE);
  2067. }
  2068. break;
  2069. case TTM_SETDELAYTIME:
  2070. TTSetDelayTime(pTtm, wParam, lParam);
  2071. break;
  2072. case TTM_GETDELAYTIME:
  2073. return (LRESULT)(UINT)TTGetDelayTime(pTtm, wParam);
  2074. case TTM_ADDTOOLA:
  2075. {
  2076. LRESULT res;
  2077. TOOLINFOW ti;
  2078. if (!lParam) {
  2079. return FALSE;
  2080. }
  2081. if (!ThunkToolInfoAtoW ((LPTOOLINFOA)lParam, &ti, TRUE, pTtm->ci.uiCodePage)) {
  2082. return FALSE;
  2083. }
  2084. res = AddTool(pTtm, &ti);
  2085. if ((ti.uFlags & TTF_MEMALLOCED) && (ti.lpszText != LPSTR_TEXTCALLBACK)) {
  2086. LocalFree (ti.lpszText);
  2087. }
  2088. return res;
  2089. }
  2090. case TTM_ADDTOOL:
  2091. if (!lParam)
  2092. return FALSE;
  2093. return AddTool(pTtm, (LPTOOLINFO)lParam);
  2094. case TTM_DELTOOLA:
  2095. {
  2096. TOOLINFOW ti;
  2097. if (!lParam) {
  2098. return FALSE;
  2099. }
  2100. if (!ThunkToolInfoAtoW ((LPTOOLINFOA)lParam, &ti, FALSE, pTtm->ci.uiCodePage)) {
  2101. break;
  2102. }
  2103. DeleteTool(pTtm, &ti);
  2104. break;
  2105. }
  2106. case TTM_DELTOOL:
  2107. if (!lParam)
  2108. return FALSE;
  2109. DeleteTool(pTtm, (LPTOOLINFO)lParam);
  2110. break;
  2111. case TTM_NEWTOOLRECTA:
  2112. {
  2113. TOOLINFOW ti;
  2114. if (!lParam) {
  2115. return FALSE;
  2116. }
  2117. if (!ThunkToolInfoAtoW ((LPTOOLINFOA)lParam, &ti, FALSE, pTtm->ci.uiCodePage)) {
  2118. break;
  2119. }
  2120. pTool = FindTool(pTtm, &ti);
  2121. if(pTool) {
  2122. pTool->rect = ((LPTOOLINFOA)lParam)->rect;
  2123. }
  2124. break;
  2125. }
  2126. case TTM_NEWTOOLRECT:
  2127. if (!lParam)
  2128. return FALSE;
  2129. pTool = FindTool(pTtm, (LPTOOLINFO)lParam);
  2130. if(pTool) {
  2131. pTool->rect = ((LPTOOLINFO)lParam)->rect;
  2132. }
  2133. break;
  2134. case TTM_GETTOOLCOUNT:
  2135. return pTtm->iNumTools;
  2136. case TTM_GETTOOLINFOA:
  2137. {
  2138. TOOLINFOW ti;
  2139. if (!lParam) {
  2140. return FALSE;
  2141. }
  2142. if (!ThunkToolInfoAtoW ((LPTOOLINFOA)lParam, &ti, FALSE, pTtm->ci.uiCodePage)) {
  2143. return FALSE;
  2144. }
  2145. pToolSrc = FindTool(pTtm, &ti);
  2146. return (LRESULT)(UINT)CopyToolInfoA(pToolSrc, (LPTOOLINFOA)lParam, pTtm->ci.uiCodePage);
  2147. }
  2148. case TTM_GETCURRENTTOOLA:
  2149. if (lParam)
  2150. return (LRESULT)(UINT)CopyToolInfoA(pTtm->pCurTool, (LPTOOLINFOA)lParam, pTtm->ci.uiCodePage);
  2151. else
  2152. return BOOLFROMPTR(pTtm->pCurTool);
  2153. case TTM_ENUMTOOLSA:
  2154. {
  2155. if (wParam < (UINT)pTtm->iNumTools) {
  2156. pToolSrc = &pTtm->tools[wParam];
  2157. return (LRESULT)(UINT)CopyToolInfoA(pToolSrc, (LPTOOLINFOA)lParam, pTtm->ci.uiCodePage);
  2158. }
  2159. return FALSE;
  2160. }
  2161. case TTM_GETTOOLINFO:
  2162. if (!lParam)
  2163. return FALSE;
  2164. pToolSrc = FindTool(pTtm, (LPTOOLINFO)lParam);
  2165. return (LRESULT)(UINT)CopyToolInfo(pToolSrc, (LPTOOLINFO)lParam);
  2166. case TTM_GETCURRENTTOOL:
  2167. if (lParam)
  2168. return (LRESULT)(UINT)CopyToolInfo(pTtm->pCurTool, (LPTOOLINFO)lParam);
  2169. else
  2170. return BOOLFROMPTR(pTtm->pCurTool);
  2171. case TTM_ENUMTOOLS:
  2172. {
  2173. if (wParam < (UINT)pTtm->iNumTools) {
  2174. pToolSrc = &pTtm->tools[wParam];
  2175. return (LRESULT)(UINT)CopyToolInfo(pToolSrc, (LPTOOLINFO)lParam);
  2176. }
  2177. return FALSE;
  2178. }
  2179. case TTM_SETTOOLINFOA:
  2180. {
  2181. TOOLINFOW ti;
  2182. if (!lParam) {
  2183. return FALSE;
  2184. }
  2185. if (!ThunkToolInfoAtoW ((LPTOOLINFOA)lParam, &ti, TRUE, pTtm->ci.uiCodePage)) {
  2186. return FALSE;
  2187. }
  2188. pTool = FindTool(pTtm, &ti);
  2189. if (pTool)
  2190. {
  2191. TTSetTipText(pTool, NULL);
  2192. hmemcpy(pTool, &ti, ti.cbSize);
  2193. pTool->lpszText = NULL;
  2194. TTSetTipText(pTool, ti.lpszText);
  2195. if (pTool == pTtm->pCurTool)
  2196. {
  2197. DoShowBubble(pTtm);
  2198. }
  2199. }
  2200. if ((ti.uFlags & TTF_MEMALLOCED) && (ti.lpszText != LPSTR_TEXTCALLBACK)) {
  2201. LocalFree (ti.lpszText);
  2202. }
  2203. break;
  2204. }
  2205. case TTM_SETTOOLINFO:
  2206. if (!lParam)
  2207. return FALSE;
  2208. pTool = FindTool(pTtm, (LPTOOLINFO)lParam);
  2209. if (pTool)
  2210. {
  2211. TTSetTipText(pTool, NULL);
  2212. hmemcpy(pTool,(LPTOOLINFO)lParam, ((LPTOOLINFO)lParam)->cbSize);
  2213. pTool->lpszText = NULL;
  2214. TTSetTipText(pTool, ((LPTOOLINFO)lParam)->lpszText);
  2215. if (pTool == pTtm->pCurTool)
  2216. {
  2217. DoShowBubble(pTtm);
  2218. }
  2219. }
  2220. break;
  2221. case TTM_HITTESTA:
  2222. #define lphitinfoA ((LPHITTESTINFOA)lParam)
  2223. if (!lParam)
  2224. return FALSE;
  2225. pTool = GetToolAtPoint(pTtm, lphitinfoA->hwnd, lphitinfoA->pt.x, lphitinfoA->pt.y, HTERROR, TRUE);
  2226. if (pTool) {
  2227. ThunkToolInfoWtoA(pTool, (LPTOOLINFOA)(&(lphitinfoA->ti)), pTtm->ci.uiCodePage);
  2228. return TRUE;
  2229. }
  2230. return FALSE;
  2231. case TTM_HITTEST:
  2232. #define lphitinfo ((LPHITTESTINFO)lParam)
  2233. if (!lParam)
  2234. return FALSE;
  2235. pTool = GetToolAtPoint(pTtm, lphitinfo->hwnd, lphitinfo->pt.x, lphitinfo->pt.y, HTERROR, TRUE);
  2236. if (pTool) {
  2237. // for back compat... if thesize isn't set right, we only give
  2238. // them the win95 amount.
  2239. if (lphitinfo->ti.cbSize != sizeof(TTTOOLINFO)) {
  2240. *((WIN95TTTOOLINFO*)&lphitinfo->ti) = *(WIN95TTTOOLINFO*)pTool;
  2241. } else {
  2242. lphitinfo->ti = *pTool;
  2243. }
  2244. return TRUE;
  2245. }
  2246. return FALSE;
  2247. case TTM_GETTEXTA:
  2248. {
  2249. LPWSTR lpszTemp;
  2250. TOOLINFOW ti;
  2251. if (!lParam || !((LPTOOLINFOA)lParam)->lpszText)
  2252. return FALSE;
  2253. if (!ThunkToolInfoAtoW((LPTOOLINFOA)lParam, &ti, FALSE, pTtm->ci.uiCodePage))
  2254. break;
  2255. ((LPTOOLINFOA)lParam)->lpszText[0] = 0;
  2256. pTool = FindTool(pTtm, &ti);
  2257. lpszTemp = GetToolText(pTtm, pTool);
  2258. if (lpszTemp)
  2259. {
  2260. WideCharToMultiByte (pTtm->ci.uiCodePage,
  2261. 0,
  2262. lpszTemp,
  2263. -1,
  2264. (((LPTOOLINFOA)lParam)->lpszText),
  2265. 80, NULL, NULL);
  2266. }
  2267. break;
  2268. }
  2269. case TTM_GETTEXT:
  2270. {
  2271. LPTSTR lpszTemp;
  2272. if (!lParam || !pTtm || !((LPTOOLINFO)lParam)->lpszText)
  2273. return FALSE;
  2274. ((LPTOOLINFO)lParam)->lpszText[0] = 0;
  2275. pTool = FindTool(pTtm, (LPTOOLINFO)lParam);
  2276. lpszTemp = GetToolText(pTtm, pTool);
  2277. if (lpszTemp)
  2278. {
  2279. // REVIEW: message parameters do not indicate the size of the
  2280. // destination buffer.
  2281. StringCchCopy((((LPTOOLINFO)lParam)->lpszText), lstrlen(lpszTemp)+1, lpszTemp);
  2282. }
  2283. }
  2284. break;
  2285. case WM_GETTEXTLENGTH:
  2286. case WM_GETTEXT:
  2287. {
  2288. LPTSTR lpszStr;
  2289. TCHAR *pszDest = uMsg == WM_GETTEXT ? (TCHAR *)lParam : NULL;
  2290. LRESULT lres;
  2291. // Pre-terminate the string just in case
  2292. if (pszDest && wParam)
  2293. {
  2294. pszDest[0] = 0;
  2295. }
  2296. if (pTtm && (lpszStr = GetCurToolText(pTtm)))
  2297. {
  2298. if (pszDest && wParam)
  2299. {
  2300. StringCchCopy(pszDest, wParam, lpszStr);
  2301. lres = lstrlen(pszDest);
  2302. }
  2303. else
  2304. {
  2305. lres = lstrlen(lpszStr);
  2306. }
  2307. }
  2308. else
  2309. {
  2310. // No current tool
  2311. lres = 0;
  2312. }
  2313. return lres;
  2314. }
  2315. case TTM_RELAYEVENT:
  2316. #define lpmsg ((LPMSG)lParam)
  2317. if (!lParam)
  2318. return FALSE;
  2319. HandleRelayedMessage(pTtm, lpmsg->hwnd, lpmsg->message, lpmsg->wParam,
  2320. lpmsg->lParam);
  2321. #undef lpmsg
  2322. break;
  2323. // this is here for people to subclass and fake out what we
  2324. // think the window from point is. this facilitates "transparent" windows
  2325. case TTM_WINDOWFROMPOINT: {
  2326. HWND hwndPt = WindowFromPoint(*((POINT FAR *)lParam));
  2327. DebugMsg(TF_TT, TEXT("TTM_WINDOWFROMPOINT %x"), hwndPt);
  2328. return (LRESULT)hwndPt;
  2329. }
  2330. case TTM_UPDATETIPTEXTA:
  2331. {
  2332. TOOLINFOW ti;
  2333. if (lParam) {
  2334. if (!ThunkToolInfoAtoW ((LPTOOLINFOA)lParam, &ti, TRUE, pTtm->ci.uiCodePage)) {
  2335. break;
  2336. }
  2337. TTUpdateTipText(pTtm, &ti);
  2338. if ((ti.uFlags & TTF_MEMALLOCED) && (ti.lpszText != LPSTR_TEXTCALLBACK)) {
  2339. LocalFree (ti.lpszText);
  2340. }
  2341. }
  2342. break;
  2343. }
  2344. case TTM_UPDATETIPTEXT:
  2345. if (lParam)
  2346. TTUpdateTipText(pTtm, (LPTOOLINFO)lParam);
  2347. break;
  2348. /* Pop the current tooltip if there is one displayed, ensuring that the virtual
  2349. / bubble is also discarded. */
  2350. case TTM_POP:
  2351. {
  2352. if ( pTtm ->dwFlags & BUBBLEUP )
  2353. PopBubble( pTtm );
  2354. pTtm ->dwFlags &= ~VIRTUALBUBBLEUP;
  2355. break;
  2356. }
  2357. case TTM_TRACKPOSITION:
  2358. if ((GET_X_LPARAM(lParam) != pTtm->ptTrack.x) ||
  2359. (GET_Y_LPARAM(lParam) != pTtm->ptTrack.y))
  2360. {
  2361. pTtm->ptTrack.x = GET_X_LPARAM(lParam);
  2362. pTtm->ptTrack.y = GET_Y_LPARAM(lParam);
  2363. // if track mode is in effect, update the position
  2364. if ((pTtm->dwFlags & TRACKMODE) &&
  2365. pTtm->pCurTool) {
  2366. DoShowBubble(pTtm);
  2367. }
  2368. }
  2369. break;
  2370. case TTM_UPDATE:
  2371. if (!lParam ||
  2372. lParam == (LPARAM)pTtm->pCurTool) {
  2373. DoShowBubble(pTtm);
  2374. }
  2375. break;
  2376. case TTM_TRACKACTIVATE:
  2377. if (pTtm->dwFlags & ACTIVE) {
  2378. if (wParam && lParam)
  2379. wParam = TRACKMODE;
  2380. else
  2381. wParam = 0;
  2382. if ((wParam ^ pTtm->dwFlags) & TRACKMODE) {
  2383. // if the trackmode changes by this..
  2384. PopBubble(pTtm);
  2385. pTtm->dwFlags ^= TRACKMODE;
  2386. if (wParam) {
  2387. // turning on track mode
  2388. pTool = FindTool(pTtm, (LPTOOLINFO)lParam);
  2389. if (pTool) {
  2390. // only if the tool is found
  2391. ShowBubbleForTool(pTtm, pTool);
  2392. }
  2393. }
  2394. }
  2395. }
  2396. return TRUE;
  2397. case TTM_SETTIPBKCOLOR:
  2398. if (pTtm->clrTipBk != (COLORREF)wParam) {
  2399. pTtm->clrTipBk = (COLORREF)wParam;
  2400. InvalidateRgn(pTtm->ci.hwnd,NULL,TRUE);
  2401. }
  2402. pTtm->fBkColorSet = TRUE;
  2403. break;
  2404. case TTM_GETTIPBKCOLOR:
  2405. return (LRESULT)(UINT)pTtm->clrTipBk;
  2406. case TTM_SETTIPTEXTCOLOR:
  2407. if (pTtm->clrTipText != (COLORREF)wParam) {
  2408. InvalidateRgn(pTtm->ci.hwnd,NULL,TRUE);
  2409. pTtm->clrTipText = (COLORREF)wParam;
  2410. }
  2411. pTtm->fTextColorSet = TRUE;
  2412. break;
  2413. case TTM_GETTIPTEXTCOLOR:
  2414. return (LRESULT)(UINT)pTtm->clrTipText;
  2415. case TTM_SETMAXTIPWIDTH:
  2416. {
  2417. int iOld = pTtm->iMaxTipWidth;
  2418. pTtm->iMaxTipWidth = (int)lParam;
  2419. return iOld;
  2420. }
  2421. case TTM_GETMAXTIPWIDTH:
  2422. return pTtm->iMaxTipWidth;
  2423. case TTM_SETMARGIN:
  2424. if (lParam)
  2425. pTtm->rcMargin = *(LPRECT)lParam;
  2426. break;
  2427. case TTM_GETMARGIN:
  2428. if (lParam)
  2429. *(LPRECT)lParam = pTtm->rcMargin;
  2430. break;
  2431. case TTM_GETBUBBLESIZE:
  2432. if (lParam)
  2433. {
  2434. pTool = FindTool(pTtm, (LPTOOLINFO)lParam);
  2435. if (pTool)
  2436. {
  2437. LPTSTR lpstr = GetToolText(pTtm, pTool);
  2438. int cxText, cyText, cxMargin, cyMargin, iBubbleWidth, iBubbleHeight;
  2439. TTGetTipSize(pTtm, pTool, lpstr, &cxText, &cyText);
  2440. cxMargin = pTtm->rcMargin.left + pTtm->rcMargin.right;
  2441. cyMargin = pTtm->rcMargin.top + pTtm->rcMargin.bottom;
  2442. iBubbleWidth = 2*XTEXTOFFSET * g_cxBorder + cxText + cxMargin;
  2443. iBubbleHeight = 2*YTEXTOFFSET * g_cyBorder + cyText + cyMargin;
  2444. if (pTtm->ci.style & TTS_BALLOON)
  2445. {
  2446. iBubbleWidth += 2*XBALLOONOFFSET;
  2447. iBubbleHeight += 2*YBALLOONOFFSET;
  2448. }
  2449. return MAKELONG(iBubbleWidth, iBubbleHeight);
  2450. }
  2451. }
  2452. break;
  2453. case TTM_ADJUSTRECT:
  2454. return TTAdjustRect(pTtm, BOOLFROMPTR(wParam), (LPRECT)lParam);
  2455. case TTM_SETTITLEA:
  2456. {
  2457. TCHAR szTitle[MAX_TIP_CHARACTERS];
  2458. pTtm->uTitleBitmap = (UINT)wParam;
  2459. Str_Set(&pTtm->lpTipTitle, NULL);
  2460. pTtm->iTitleHeight = 0;
  2461. TTCreateTitleBitmaps(pTtm);
  2462. if (lParam)
  2463. {
  2464. pTtm->cchTipTitle = lstrlenA((LPCSTR)lParam);
  2465. if (pTtm->cchTipTitle < ARRAYSIZE(szTitle))
  2466. {
  2467. ConvertAToWN(pTtm->ci.uiCodePage, szTitle, ARRAYSIZE(szTitle),
  2468. (LPCSTR)lParam, -1);
  2469. Str_Set(&pTtm->lpTipTitle, szTitle);
  2470. return TRUE;
  2471. }
  2472. }
  2473. pTtm->cchTipTitle = 0;
  2474. return FALSE;
  2475. }
  2476. break;
  2477. case TTM_SETTITLE:
  2478. {
  2479. pTtm->uTitleBitmap = (UINT)wParam;
  2480. Str_Set(&pTtm->lpTipTitle, NULL);
  2481. pTtm->iTitleHeight = 0;
  2482. TTCreateTitleBitmaps(pTtm);
  2483. if (lParam)
  2484. {
  2485. pTtm->cchTipTitle = lstrlen((LPCTSTR)lParam);
  2486. if (pTtm->cchTipTitle < MAX_TIP_CHARACTERS)
  2487. {
  2488. Str_Set(&pTtm->lpTipTitle, (LPCTSTR)lParam);
  2489. return TRUE;
  2490. }
  2491. }
  2492. pTtm->cchTipTitle = 0;
  2493. return FALSE;
  2494. }
  2495. break;
  2496. /* uMsgs that REALLY came for me. */
  2497. case WM_CREATE:
  2498. {
  2499. DWORD dwBits, dwValue;
  2500. CCCreateWindow();
  2501. pTtm = ToolTipsMgrCreate(hwnd, (LPCREATESTRUCT)lParam);
  2502. if (!pTtm)
  2503. return -1;
  2504. SetWindowPtr(hwnd, 0, pTtm);
  2505. SetWindowBits(hwnd, GWL_EXSTYLE, WS_EX_TOOLWINDOW, WS_EX_TOOLWINDOW);
  2506. dwBits = WS_CHILD | WS_POPUP | WS_BORDER | WS_DLGFRAME;
  2507. dwValue = WS_POPUP | WS_BORDER;
  2508. // we don't want border for balloon style
  2509. if (pTtm->ci.style & TTS_BALLOON)
  2510. dwValue &= ~WS_BORDER;
  2511. SetWindowBits(hwnd, GWL_STYLE, dwBits, dwValue);
  2512. TTSetFont(pTtm, 0, FALSE);
  2513. break;
  2514. }
  2515. case WM_TIMER:
  2516. TTHandleTimer(pTtm, wParam);
  2517. break;
  2518. case WM_NCHITTEST:
  2519. // we should not return HTTRANSPARENT here because then we don't receive the mouse events
  2520. // and we cannot forward them down to our parent. but because of the backcompat we keep doing
  2521. // it unless we are using comctl32 v5 or greater
  2522. //
  2523. // If we are inside TTWindowFromPoint, then respect transparency
  2524. // even on v5 clients.
  2525. //
  2526. // Otherwise, your tooltips flicker because the tip appears,
  2527. // then WM_NCHITTEST says "not over the tool any more" (because
  2528. // it's over the tooltip), so the bubble pops, and then the tip
  2529. // reappears, etc.
  2530. if (pTtm && (pTtm->ci.iVersion < 5 || pTtm->fInWindowFromPoint) &&
  2531. pTtm->pCurTool && (pTtm->pCurTool->uFlags & TTF_TRANSPARENT))
  2532. {
  2533. return HTTRANSPARENT;
  2534. }
  2535. goto DoDefault;
  2536. case WM_MOUSEMOVE:
  2537. // the cursor moved onto the tips window.
  2538. if (!(pTtm->dwFlags & TRACKMODE) && pTtm->pCurTool && !(pTtm->pCurTool->uFlags & TTF_TRANSPARENT))
  2539. PopBubble(pTtm);
  2540. // fall through
  2541. case WM_LBUTTONDOWN:
  2542. case WM_RBUTTONDOWN:
  2543. case WM_MBUTTONDOWN:
  2544. if (pTtm->pCurTool && (pTtm->pCurTool->uFlags & TTF_TRANSPARENT))
  2545. {
  2546. POINT pt;
  2547. pt.x = GET_X_LPARAM(lParam);
  2548. pt.y = GET_Y_LPARAM(lParam);
  2549. MapWindowPoints(pTtm->ci.hwnd, pTtm->pCurTool->hwnd, &pt, 1);
  2550. SendMessage(pTtm->pCurTool->hwnd, uMsg, wParam, MAKELPARAM(pt.x, pt.y));
  2551. }
  2552. break;
  2553. case WM_SYSCOLORCHANGE:
  2554. InitGlobalColors();
  2555. if (pTtm) {
  2556. if (!pTtm->fBkColorSet)
  2557. pTtm->clrTipBk = g_clrInfoBk;
  2558. if (!pTtm->fTextColorSet)
  2559. pTtm->clrTipText = g_clrInfoText;
  2560. }
  2561. break;
  2562. case WM_WININICHANGE:
  2563. InitGlobalMetrics(wParam);
  2564. if (pTtm->fMyFont)
  2565. TTSetFont(pTtm, 0, FALSE);
  2566. break;
  2567. case WM_PAINT:
  2568. TTOnPaint(pTtm);
  2569. break;
  2570. case WM_SETFONT:
  2571. TTSetFont(pTtm, (HFONT)wParam, (BOOL)lParam);
  2572. return(TRUE);
  2573. case WM_GETFONT:
  2574. if (pTtm) {
  2575. return((LRESULT)pTtm->hFont);
  2576. }
  2577. break;
  2578. case WM_NOTIFYFORMAT:
  2579. if (lParam == NF_QUERY) {
  2580. return NFR_UNICODE;
  2581. } else if (lParam == NF_REQUERY) {
  2582. int i;
  2583. for(i = 0 ; i < pTtm->iNumTools; i++) {
  2584. pTool = &pTtm->tools[i];
  2585. if (SendMessage (pTool->hwnd, WM_NOTIFYFORMAT,
  2586. (WPARAM)hwnd, NF_QUERY) == NFR_UNICODE) {
  2587. pTool->uFlags |= TTF_UNICODE;
  2588. } else {
  2589. pTool->uFlags &= ~TTF_UNICODE;
  2590. }
  2591. }
  2592. return CIHandleNotifyFormat(&pTtm->ci, lParam);
  2593. }
  2594. return 0;
  2595. case WM_ERASEBKGND:
  2596. break;
  2597. case WM_STYLECHANGED:
  2598. if ((wParam == GWL_STYLE) && pTtm)
  2599. {
  2600. DWORD dwNewStyle = ((LPSTYLESTRUCT)lParam)->styleNew;
  2601. if ( pTtm->ci.style & TTS_BALLOON && // If the old style was a balloon,
  2602. !(dwNewStyle & TTS_BALLOON)) // And the new style is not a balloon,
  2603. {
  2604. // Then we need to unset the region.
  2605. SetWindowRgn(pTtm->ci.hwnd, NULL, FALSE);
  2606. }
  2607. pTtm->ci.style = ((LPSTYLESTRUCT)lParam)->styleNew;
  2608. }
  2609. break;
  2610. case WM_DESTROY:
  2611. {
  2612. CCDestroyWindow();
  2613. if (pTtm->tools)
  2614. {
  2615. int i;
  2616. // free the tools
  2617. for(i = 0 ; i < pTtm->iNumTools; i++)
  2618. {
  2619. TTBeforeFreeTool(pTtm, &pTtm->tools[i]);
  2620. }
  2621. LocalFree((HANDLE)pTtm->tools);
  2622. }
  2623. TTSetFont(pTtm, (HFONT)1, FALSE); // delete font if we made one.
  2624. Str_Set(&pTtm->lpTipText, NULL);
  2625. Str_Set(&pTtm->lpTipTitle, NULL);
  2626. if (pTtm->himlTitleBitmaps)
  2627. ImageList_Destroy(pTtm->himlTitleBitmaps);
  2628. LocalFree((HANDLE)pTtm);
  2629. SetWindowPtr(hwnd, 0, 0);
  2630. }
  2631. break;
  2632. case WM_PRINTCLIENT:
  2633. TTRender(pTtm, (HDC)wParam);
  2634. break;
  2635. case WM_GETOBJECT:
  2636. if( lParam == OBJID_QUERYCLASSNAMEIDX )
  2637. return MSAA_CLASSNAMEIDX_TOOLTIPS;
  2638. goto DoDefault;
  2639. default:
  2640. {
  2641. LRESULT lres;
  2642. if (CCWndProc(&pTtm->ci, uMsg, wParam, lParam, &lres))
  2643. return lres;
  2644. }
  2645. DoDefault:
  2646. return DefWindowProc(hwnd, uMsg, wParam, lParam);
  2647. }
  2648. return 0;
  2649. }
  2650. //========================================================================
  2651. //
  2652. // Ansi <=> Unicode Thunk Routines
  2653. //
  2654. //========================================================================
  2655. //*************************************************************
  2656. //
  2657. // ThunkToolInfoAtoW()
  2658. //
  2659. // Purpose: Thunks a TOOLINFOA structure to a TOOLINFOW
  2660. // structure.
  2661. //
  2662. // Return: (BOOL) TRUE if successful
  2663. // FALSE if an error occurs
  2664. //
  2665. //*************************************************************
  2666. BOOL ThunkToolInfoAtoW (LPTOOLINFOA lpTiA, LPTOOLINFOW lpTiW, BOOL bThunkText, UINT uiCodePage)
  2667. {
  2668. //
  2669. // Copy the constants to the new structure.
  2670. //
  2671. lpTiW->uFlags = lpTiA->uFlags;
  2672. lpTiW->hwnd = lpTiA->hwnd;
  2673. lpTiW->uId = lpTiA->uId;
  2674. lpTiW->rect.left = lpTiA->rect.left;
  2675. lpTiW->rect.top = lpTiA->rect.top;
  2676. lpTiW->rect.right = lpTiA->rect.right;
  2677. lpTiW->rect.bottom = lpTiA->rect.bottom;
  2678. lpTiW->hinst = lpTiA->hinst;
  2679. //
  2680. // Set the size properly and optionally copy the new fields if the
  2681. // structure is large enough.
  2682. //
  2683. if (lpTiA->cbSize <= TTTOOLINFOA_V1_SIZE) {
  2684. lpTiW->cbSize = TTTOOLINFOW_V1_SIZE;
  2685. } else {
  2686. lpTiW->cbSize = sizeof(TOOLINFOW);
  2687. lpTiW->lParam = lpTiA->lParam;
  2688. }
  2689. if (bThunkText) {
  2690. //
  2691. // Thunk the string to the new structure.
  2692. // Special case LPSTR_TEXTCALLBACK.
  2693. //
  2694. if (lpTiA->lpszText == LPSTR_TEXTCALLBACKA) {
  2695. lpTiW->lpszText = LPSTR_TEXTCALLBACKW;
  2696. } else if (!IS_INTRESOURCE(lpTiA->lpszText)) {
  2697. DWORD dwBufSize;
  2698. int iResult;
  2699. dwBufSize = lstrlenA(lpTiA->lpszText) + 1;
  2700. lpTiW->lpszText = LocalAlloc (LPTR, dwBufSize * sizeof(WCHAR));
  2701. if (!lpTiW->lpszText) {
  2702. return FALSE;
  2703. }
  2704. iResult = MultiByteToWideChar (uiCodePage, 0, lpTiA->lpszText, -1,
  2705. lpTiW->lpszText, dwBufSize);
  2706. //
  2707. // If iResult is 0, and GetLastError returns an error code,
  2708. // then MultiByteToWideChar failed.
  2709. //
  2710. if (!iResult) {
  2711. if (GetLastError()) {
  2712. return FALSE;
  2713. }
  2714. }
  2715. lpTiW->uFlags |= TTF_MEMALLOCED;
  2716. } else {
  2717. lpTiW->lpszText = (LPWSTR)lpTiA->lpszText;
  2718. }
  2719. }
  2720. return TRUE;
  2721. }
  2722. //*************************************************************
  2723. //
  2724. // ThunkToolInfoWtoA()
  2725. //
  2726. // Purpose: Thunks a TOOLINFOW structure to a TOOLINFOA
  2727. // structure.
  2728. //
  2729. // Return: (BOOL) TRUE if successful
  2730. // FALSE if an error occurs
  2731. //
  2732. //*************************************************************
  2733. BOOL ThunkToolInfoWtoA (LPTOOLINFOW lpTiW, LPTOOLINFOA lpTiA, UINT uiCodePage)
  2734. {
  2735. int iResult = 1;
  2736. //
  2737. // Copy the constants to the new structure.
  2738. //
  2739. lpTiA->uFlags = lpTiW->uFlags;
  2740. lpTiA->hwnd = lpTiW->hwnd;
  2741. lpTiA->uId = lpTiW->uId;
  2742. lpTiA->rect.left = lpTiW->rect.left;
  2743. lpTiA->rect.top = lpTiW->rect.top;
  2744. lpTiA->rect.right = lpTiW->rect.right;
  2745. lpTiA->rect.bottom = lpTiW->rect.bottom;
  2746. lpTiA->hinst = lpTiW->hinst;
  2747. //
  2748. // Set the size properly and optionally copy the new fields if the
  2749. // structure is large enough.
  2750. //
  2751. if (lpTiW->cbSize <= TTTOOLINFOW_V1_SIZE) {
  2752. lpTiA->cbSize = TTTOOLINFOA_V1_SIZE;
  2753. } else {
  2754. lpTiA->cbSize = sizeof(TOOLINFOA);
  2755. lpTiA->lParam = lpTiA->lParam;
  2756. }
  2757. //
  2758. // Thunk the string to the new structure.
  2759. // Special case LPSTR_TEXTCALLBACK.
  2760. //
  2761. if (lpTiW->lpszText == LPSTR_TEXTCALLBACKW) {
  2762. lpTiA->lpszText = LPSTR_TEXTCALLBACKA;
  2763. } else if (!IS_INTRESOURCE(lpTiW->lpszText)) {
  2764. //
  2765. // It is assumed that lpTiA->lpszText is already setup to
  2766. // a valid buffer, and that buffer is 80 characters.
  2767. // 80 characters is defined in the TOOLTIPTEXT structure.
  2768. //
  2769. iResult = WideCharToMultiByte (uiCodePage, 0, lpTiW->lpszText, -1,
  2770. lpTiA->lpszText, 80, NULL, NULL);
  2771. } else {
  2772. lpTiA->lpszText = (LPSTR)lpTiW->lpszText;
  2773. }
  2774. //
  2775. // If iResult is 0, and GetLastError returns an error code,
  2776. // then WideCharToMultiByte failed.
  2777. //
  2778. if (!iResult) {
  2779. if (GetLastError()) {
  2780. return FALSE;
  2781. }
  2782. }
  2783. return TRUE;
  2784. }
  2785. //*************************************************************
  2786. //
  2787. // ThunkToolTipTextAtoW()
  2788. //
  2789. // Purpose: Thunks a TOOLTIPTEXTA structure to a TOOLTIPTEXTW
  2790. // structure.
  2791. //
  2792. // Return: (BOOL) TRUE if successful
  2793. // FALSE if an error occurs
  2794. //
  2795. //*************************************************************
  2796. BOOL ThunkToolTipTextAtoW (LPTOOLTIPTEXTA lpTttA, LPTOOLTIPTEXTW lpTttW, UINT uiCodePage)
  2797. {
  2798. int iResult;
  2799. if (!lpTttA || !lpTttW)
  2800. return FALSE;
  2801. //
  2802. // Thunk the NMHDR structure.
  2803. //
  2804. lpTttW->hdr.hwndFrom = lpTttA->hdr.hwndFrom;
  2805. lpTttW->hdr.idFrom = lpTttA->hdr.idFrom;
  2806. lpTttW->hdr.code = TTN_NEEDTEXTW;
  2807. lpTttW->hinst = lpTttA->hinst;
  2808. lpTttW->uFlags = lpTttA->uFlags;
  2809. lpTttW->lParam = lpTttA->lParam;
  2810. //
  2811. // Thunk the string to the new structure.
  2812. // Special case LPSTR_TEXTCALLBACK.
  2813. //
  2814. if (lpTttA->lpszText == LPSTR_TEXTCALLBACKA) {
  2815. lpTttW->lpszText = LPSTR_TEXTCALLBACKW;
  2816. } else if (!IS_INTRESOURCE(lpTttA->lpszText)) {
  2817. //
  2818. // Transfer the lpszText into the lpTttW...
  2819. //
  2820. // First see if it fits into the buffer, and optimistically assume
  2821. // it will.
  2822. //
  2823. lpTttW->lpszText = lpTttW->szText;
  2824. iResult = MultiByteToWideChar (uiCodePage, 0, lpTttA->lpszText, -1,
  2825. lpTttW->szText, ARRAYSIZE(lpTttW->szText));
  2826. if (!iResult) {
  2827. //
  2828. // Didn't fit into the small buffer; must alloc our own.
  2829. //
  2830. lpTttW->lpszText = ProduceWFromA(uiCodePage, lpTttA->lpszText);
  2831. lpTttW->uFlags |= TTF_MEMALLOCED;
  2832. }
  2833. } else {
  2834. lpTttW->lpszText = (LPWSTR)lpTttA->lpszText;
  2835. }
  2836. return TRUE;
  2837. }