Source code of Windows XP (NT5)
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.

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