BalloonHelp.cpp
上传用户:lswyart
上传日期:2008-06-12
资源大小:3441k
文件大小:51k
源码类别:

杀毒

开发平台:

Visual C++

  1. /*-----------------------------------------------------------------------------
  2. # Name:        BalloonHelp.cpp
  3. # Product:     ClamWin Antivirus
  4. #
  5. # Licence:     
  6. #   This program is free software; you can redistribute it and/or modify
  7. #   it under the terms of the GNU General Public License as published by
  8. #   the Free Software Foundation; either version 2 of the License, or
  9. #   (at your option) any later version.
  10. #   This program is distributed in the hope that it will be useful,
  11. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #   GNU General Public License for more details.
  14. #   You should have received a copy of the GNU General Public License
  15. #   along with this program; if not, write to the Free Software
  16. #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  17. #-----------------------------------------------------------------------------
  18. # 11 May 2004 Alch - Converted the class to pure win32 api - no MFC
  19. */
  20. // ******************************************************************************
  21. // BalloonHelp.cpp : implementation file
  22. // Copyright 2001-2002, Joshua Heyer
  23. //  You are free to use this code for whatever you want, provided you
  24. // give credit where credit is due.  (I seem to get a lot of questions
  25. // about that statement...  All i mean is, don't copy huge bits of code
  26. // and then claim you wrote it.  You don't have to put my name in an about
  27. // box or anything.  Though i'm not going to stop you if that's really what
  28. // you want :~) )
  29. //  I'm providing this code in the hope that it is useful to someone, as i have
  30. // gotten much use out of other peoples code over the years.
  31. //  If you see value in it, make some improvements, etc., i would appreciate it 
  32. // if you sent me some feedback.
  33. //
  34. // ******************************************************************************
  35. #include "stdafx.h"
  36. #include "BalloonHelp.h"
  37. // allow multimonitor-aware code on Win95 systems
  38. // comment out the first line if you have already define it in another file
  39. // comment out both lines if you don't care about Win95
  40. //#define COMPILE_MULTIMON_STUBS
  41. //#include "multimon.h"
  42. //
  43. // constants that may not be defined if you don't have the latest SDK
  44. // (but i like to use them anyway)
  45. //
  46. #ifndef DFCS_HOT
  47. #define DFCS_HOT 0x1000
  48. #endif
  49. #ifndef AW_HIDE
  50. #define AW_HIDE 0x00010000
  51. #define AW_BLEND 0x00080000
  52. #endif
  53. #ifndef CS_DROPSHADOW
  54. #define CS_DROPSHADOW   0x00020000
  55. #endif
  56. #ifndef SPI_GETDROPSHADOW
  57. #define SPI_GETDROPSHADOW  0x1024
  58. #endif
  59. #ifndef SPI_GETTOOLTIPANIMATION
  60. #define SPI_GETTOOLTIPANIMATION 0x1016
  61. #endif
  62. #ifndef SPI_GETTOOLTIPFADE
  63. #define SPI_GETTOOLTIPFADE 0x1018
  64. #endif
  65. /////////////////////////////////////////////////////////////////////////////
  66. // CBalloonHelp
  67. // option constants (bits)
  68. const unsigned int   CBalloonHelp::unCLOSE_ON_LBUTTON_UP    = 0x0001;
  69. const unsigned int   CBalloonHelp::unCLOSE_ON_MBUTTON_UP    = 0x0002;
  70. const unsigned int   CBalloonHelp::unCLOSE_ON_RBUTTON_UP    = 0x0004;
  71. const unsigned int   CBalloonHelp::unCLOSE_ON_LBUTTON_DOWN  = 0x0008;
  72. const unsigned int   CBalloonHelp::unCLOSE_ON_MBUTTON_DOWN  = 0x0010;
  73. const unsigned int   CBalloonHelp::unCLOSE_ON_RBUTTON_DOWN  = 0x0020;
  74. const unsigned int   CBalloonHelp::unCLOSE_ON_MOUSE_MOVE    = 0x0040;
  75. const unsigned int   CBalloonHelp::unCLOSE_ON_KEYPRESS      = 0x0080;
  76. const unsigned int   CBalloonHelp::unCLOSE_ON_ANYTHING      = CBalloonHelp::unCLOSE_ON_MOUSE_MOVE|CBalloonHelp::unCLOSE_ON_RBUTTON_DOWN|CBalloonHelp::unCLOSE_ON_RBUTTON_DOWN|CBalloonHelp::unCLOSE_ON_MBUTTON_DOWN|CBalloonHelp::unCLOSE_ON_LBUTTON_DOWN|CBalloonHelp::unCLOSE_ON_RBUTTON_UP|CBalloonHelp::unCLOSE_ON_MBUTTON_UP|CBalloonHelp::unCLOSE_ON_LBUTTON_UP;
  77. const unsigned int   CBalloonHelp::unDELAY_CLOSE            = 0x0100;
  78. const unsigned int   CBalloonHelp::unDELETE_THIS_ON_CLOSE   = 0x0200;
  79. const unsigned int   CBalloonHelp::unSHOW_CLOSE_BUTTON      = 0x0400;
  80. const unsigned int   CBalloonHelp::unSHOW_INNER_SHADOW      = 0x0800;
  81. const unsigned int   CBalloonHelp::unSHOW_TOPMOST           = 0x1000;
  82. const unsigned int   CBalloonHelp::unDISABLE_XP_SHADOW      = 0x2000;
  83. const unsigned int   CBalloonHelp::unDISABLE_FADEIN         = 0x4000;
  84. const unsigned int   CBalloonHelp::unDISABLE_FADEOUT        = 0x8000;
  85. const unsigned int   CBalloonHelp::unDISABLE_FADE           = CBalloonHelp::unDISABLE_FADEIN|CBalloonHelp::unDISABLE_FADEOUT;
  86. // layout constants (should prolly be configurable, but who's really gonna care?)
  87. const int            CBalloonHelp::nTIP_TAIL             = 20;
  88. const int            CBalloonHelp::nTIP_MARGIN           = 8;
  89. // class atom (why don't i do this the MFC way?  Drop shadows!)
  90. ATOM                 CBalloonHelp::s_ClassAtom           = 0;
  91. ATOM                 CBalloonHelp::s_ClassAtomShadowed   = 0;
  92. // Kill timer
  93. #define ID_TIMER_CLOSE  1
  94. extern HINSTANCE g_hInstance;
  95. //
  96. // The launchers
  97. //
  98. //
  99. // Show a help balloon on screen
  100. // Parameters:
  101. //    strTitle    |  Title of balloon
  102. //    unTitle     |  Title of balloon (id of string resource)
  103. //    strContent  |  Content of balloon
  104. //    unContent   |  Content of balloon (id of string resource)
  105. //    ptAnchor    |  point tail of balloon will be "anchor"ed to
  106. //    szIcon      |  One of:
  107. //                   IDI_APPLICATION
  108. //                   IDI_INFORMATION IDI_ASTERISK (same)
  109. //                   IDI_ERROR IDI_HAND (same)
  110. //                   IDI_EXCLAMATION IDI_WARNING (same)
  111. //                   IDI_QUESTION
  112. //                   IDI_WINLOGO
  113. //                   NULL (no icon)
  114. //    unIconID    |  ID of icon to display (loaded from resources)
  115. //    unOptions   |  One or more of:
  116. //                :     unCLOSE_ON_LBUTTON_UP   |  closes window on WM_LBUTTON_UP
  117. //                :     unCLOSE_ON_MBUTTON_UP   |  closes window on WM_MBUTTON_UP
  118. //                :     unCLOSE_ON_RBUTTON_UP   |  closes window on WM_RBUTTON_UP
  119. //                :     unCLOSE_ON_LBUTTON_DOWN |  closes window on WM_LBUTTON_DOWN
  120. //                :     unCLOSE_ON_MBUTTON_DOWN |  closes window on WM_MBUTTON_DOWN
  121. //                :     unCLOSE_ON_RBUTTON_DOWN |  closes window on WM_RBUTTON_DOWN
  122. //                :     unCLOSE_ON_MOUSE_MOVE   |  closes window when user moves mouse past threshhold
  123. //                :     unCLOSE_ON_KEYPRESS     |  closes window on the next keypress message sent to this thread.
  124. //                :     unCLOSE_ON_ANYTHING     |  all of the above.
  125. //                :     unDELAY_CLOSE           |  when a user action triggers the close, begins timer.  closes when timer expires.
  126. //                :     unSHOW_CLOSE_BUTTON     |  shows close button in upper right
  127. //                :     unSHOW_INNER_SHADOW     |  draw inner shadow in balloon
  128. //                :     unSHOW_TOPMOST          |  place balloon above all other windows
  129. //                :     unDISABLE_XP_SHADOW     |  disable Windows XP's drop-shadow effect (overrides system and user settings)
  130. //                :     unDISABLE_FADE          |  disable the fade-in/fade-out effects (overrides system and user settings)
  131. //                :     unDISABLE_FADEIN        |  disable the fade-in effect
  132. //                :     unDISABLE_FADEOUT       |  disable the fade-out effect
  133. //    pParentWnd  |  Parent window.  If NULL will be set to AfxGetMainWnd(), and anchor to screen
  134. //    strURL      |  If not empty, when the balloon is clicked ShellExecute() will
  135. //                |  be called, with strURL passed in.
  136. //    unTimeout   |  If not 0, balloon will automatically close after unTimeout milliseconds.
  137. //
  138. void CBalloonHelp::LaunchBalloon(const CStdString& strTitle, const CStdString& strContent, 
  139.                POINT& ptAnchor, 
  140.                LPCTSTR szIcon /*= IDI_EXCLAMATION*/,
  141.                unsigned int unOptions /*= unSHOW_CLOSE_BUTTON*/,
  142.                HWND hParentWnd /*= NULL*/,
  143.                const CStdString strURL /*= ""*/,
  144.                unsigned int unTimeout /*= 10000*/)
  145. {
  146.    CBalloonHelp* pbh = new CBalloonHelp;
  147.    if ( NULL != szIcon )
  148.    {
  149.       // Note: Since i'm scaling the icon anyway, i'll allow it to become larger
  150.       // than the standard small icon if the close button is.
  151.       SIZE sizeIcon = {max(::GetSystemMetrics(SM_CXSIZE), ::GetSystemMetrics(SM_CXSMICON)), max(::GetSystemMetrics(SM_CYSIZE), ::GetSystemMetrics(SM_CYSMICON))};
  152.       HICON hIcon = (HICON)::LoadImage(NULL, szIcon, IMAGE_ICON, sizeIcon.cx, sizeIcon.cy, LR_SHARED);
  153.       if (NULL != hIcon)
  154.          pbh->SetIconScaled(hIcon, sizeIcon.cx, sizeIcon.cy);
  155.    }
  156.    pbh->Create(strTitle, strContent, ptAnchor, unOptions|unDELETE_THIS_ON_CLOSE, 
  157.                hParentWnd, strURL, unTimeout, NULL);
  158. }
  159. //
  160. //  The class
  161. //
  162. CBalloonHelp::CBalloonHelp()
  163. :  CWnd(g_hInstance),
  164. m_fnAnimateWindow(NULL),
  165.    m_unOptions(0),
  166.    m_unTimeout(0),
  167.    m_unTimerClose(0),
  168.    m_strURL(""),   
  169. m_hilIcon(NULL),
  170.    m_hwndAnchor(NULL),
  171. m_hrgnComplete(NULL),   
  172.    m_strContent(""),
  173.    m_nMouseMoveTolerance(3),     // later retrieved from system   
  174.    m_uCloseState(0),
  175.    m_hTitleFont(NULL),
  176.    m_hContentFont(NULL),
  177.    m_crForeground(::GetSysColor(COLOR_INFOTEXT)),
  178.    m_crBackground(::GetSysColor(COLOR_INFOBK)),
  179.    m_hKeyboardHook(NULL),
  180.    m_hMouseHook(NULL),
  181.    m_hCallWndRetHook(NULL)
  182. {
  183. m_ptAnchor.x = m_ptAnchor.y = 0;
  184. m_ptMouseOrig.x = m_ptMouseOrig.y = 0;
  185. m_screenRect.top = m_screenRect.left = m_screenRect.bottom = m_screenRect.right = 0;
  186.    // retrieve window animation API if available
  187.    HMODULE hUser32 = GetModuleHandle(_T("USER32.DLL"));
  188.    // can't imagine why that would fail, but might as well *look* safe...  ;~)
  189.    if ( NULL != hUser32 )
  190.       m_fnAnimateWindow = (FN_ANIMATE_WINDOW)GetProcAddress(hUser32, _T("AnimateWindow"));
  191.    else
  192.       m_fnAnimateWindow = NULL;
  193.    // get system tolerance values
  194.    int nTol = 0;
  195.    if ( ::SystemParametersInfo(SPI_GETMOUSEHOVERWIDTH, 0, &nTol, 0) && nTol > 0 )
  196.       m_nMouseMoveTolerance = nTol;
  197.    // setup hook procedures
  198.    BHKeybHookThunk<CBalloonHelp>::InitThunk((TMFP)KeyboardHookProc, this);
  199.    BHMouseHookThunk<CBalloonHelp>::InitThunk((TMFP)MouseHookProc, this);
  200.    BHCallWndRetHookThunk<CBalloonHelp>::InitThunk((TMFP)CallWndRetProc, this);
  201. }
  202. CBalloonHelp::~CBalloonHelp()
  203. {
  204.    if ( NULL != m_hTitleFont )
  205.       ::DeleteObject(m_hTitleFont);
  206.    m_hTitleFont = NULL;
  207.    if ( NULL != m_hContentFont )
  208.       ::DeleteObject(m_hContentFont);
  209.    m_hContentFont = NULL;
  210. if ( NULL != m_hrgnComplete )
  211.       ::DeleteObject(m_hrgnComplete);
  212.    m_hrgnComplete = NULL;
  213. if ( NULL != m_hilIcon )
  214. ::ImageList_Destroy(m_hilIcon);
  215. m_hilIcon = NULL;
  216. }
  217. // Sets the font used for drawing the balloon title.  Deleted by balloon, do not use CFont* after passing to this function.
  218. void CBalloonHelp::SetTitleFont(HFONT hFont)
  219. {
  220.    if ( NULL != m_hTitleFont )
  221.       ::DeleteObject(m_hTitleFont);
  222.    m_hTitleFont = hFont;
  223.    // if already visible, resize & move
  224.    if ( NULL != m_hWnd )
  225.       PositionWindow();
  226. }
  227. // Sets the font used for drawing the balloon content.  Deleted by balloon, do not use CFont* after passing to this function.
  228. void CBalloonHelp::SetContentFont(HFONT hFont)
  229. {
  230.    if ( NULL != m_hContentFont )
  231.       ::DeleteObject(m_hContentFont);
  232.    m_hContentFont = hFont;
  233.    // if already visible, resize & move
  234.    if ( NULL != m_hWnd )
  235.       PositionWindow();
  236. }
  237. // Sets the icon displayed in the top left of the balloon (pass NULL to hide icon)
  238. void CBalloonHelp::SetIcon(HICON hIcon)
  239. {
  240.    if ( NULL != m_hilIcon )
  241. ::ImageList_Destroy(m_hilIcon);
  242.    ICONINFO iconinfo;
  243.    if ( NULL != hIcon && ::GetIconInfo(hIcon, &iconinfo) )
  244.    {
  245.       SetIcon(iconinfo.hbmColor, iconinfo.hbmMask);
  246.       ::DeleteObject(iconinfo.hbmColor);
  247.       ::DeleteObject(iconinfo.hbmMask);
  248.    }
  249.    // if already visible, resize & move (icon size may have changed)
  250.    if ( NULL != m_hWnd )
  251.       PositionWindow();
  252. }
  253. // Sets the icon displayed in the top left of the balloon (pass NULL to hide icon)
  254. void CBalloonHelp::SetIconScaled(HICON hIcon, int cx, int cy)
  255. {
  256.    // i now have two device contexts and two bitmaps.
  257.    // i will select a bitmap in each device context,
  258.    // draw the icon into the first one,
  259.    // scale it into the second one,
  260.    // and set the second one as the balloon icon.
  261.    // This is a rather long process to get a scaled icon,
  262.    // but ensures maximum compatibility between different
  263.    // versions of Windows, while producing the best possible
  264.    // results on each version (quite good in WinNT and better, sorta ok in Win9x).
  265.    ICONINFO iconinfo;
  266.    if ( NULL != hIcon && ::GetIconInfo(hIcon, &iconinfo) )
  267.    {
  268.       BITMAP bm;
  269.       if (::GetObject(iconinfo.hbmColor, sizeof(bm),(LPVOID)&bm))
  270.       {
  271.          HDC dc;
  272.          HDC dcTmp1;
  273.          HDC dcTmp2;
  274.          HBITMAP bmpIcon;
  275.          HBITMAP bmpIconScaled;
  276.          dc = ::GetDC(NULL);
  277.          dcTmp1 = ::CreateCompatibleDC(dc);
  278.          dcTmp2 = ::CreateCompatibleDC(dc);
  279.          bmpIcon = ::CreateCompatibleBitmap(dc, bm.bmWidth, bm.bmHeight);
  280.          bmpIconScaled = CreateCompatibleBitmap(dc, cx, cy);
  281.          ::ReleaseDC(NULL, dc);
  282.          HBITMAP pbmpOld1 = (HBITMAP)SelectObject(dcTmp1, bmpIcon);
  283.          HBITMAP pbmpOld2 = (HBITMAP)SelectObject(dcTmp2, bmpIconScaled);
  284. HBRUSH hBrush = ::CreateSolidBrush(m_crBackground);
  285. RECT rc = {0,0,bm.bmWidth,bm.bmHeight};
  286. ::FillRect(dcTmp1,&rc,hBrush);
  287. ::DeleteObject(hBrush);
  288.          ::DrawIconEx(dcTmp1, 0,0,hIcon,bm.bmWidth,bm.bmHeight,0,NULL,DI_NORMAL);
  289.          ::SetStretchBltMode(dcTmp2, HALFTONE);
  290.          ::StretchBlt(dcTmp2, 0,0,cx,cy,dcTmp1, 0,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
  291.          ::SelectObject(dcTmp1, pbmpOld1);
  292.          ::SelectObject(dcTmp2, pbmpOld2);
  293.          SetIcon(bmpIconScaled, m_crBackground);
  294.          ::ReleaseDC(m_hWnd, dcTmp1);
  295.          ::ReleaseDC(m_hWnd, dcTmp2);
  296. ::DeleteObject(dcTmp1);
  297. ::DeleteObject(dcTmp2);
  298.          ::DeleteObject(bmpIcon);
  299.          ::DeleteObject(bmpIconScaled);
  300.       }
  301.       ::DeleteObject(iconinfo.hbmColor);
  302.       ::DeleteObject(iconinfo.hbmMask);
  303.    }
  304. }
  305. // Sets the icon displayed in the top left of the balloon (pass NULL hBitmap to hide icon)
  306. void CBalloonHelp::SetIcon(HBITMAP hBitmap, COLORREF crMask)
  307. {
  308.    if ( NULL != m_hilIcon )
  309. ::ImageList_Destroy(m_hilIcon);
  310.    if ( NULL != hBitmap )
  311.    {
  312.       BITMAP bm;
  313.       if (::GetObject(hBitmap, sizeof(bm),(LPVOID)&bm))
  314.       {
  315.          m_hilIcon = ::ImageList_Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24|ILC_MASK,1,0);
  316.          ::ImageList_AddMasked(m_hilIcon, hBitmap, crMask);
  317.       }
  318.    }
  319.    // if already visible, resize & move (icon size may have changed)
  320.    if ( NULL != m_hWnd )
  321.       PositionWindow();
  322. }
  323. // Sets the icon displayed in the top left of the balloon
  324. void CBalloonHelp::SetIcon(HBITMAP hBitmap, HBITMAP hMask)
  325. {
  326.     if ( NULL != m_hilIcon )
  327. ::ImageList_Destroy(m_hilIcon);
  328.    ASSERT(NULL != hBitmap);
  329.    ASSERT(NULL != hMask);
  330.    BITMAP bm;
  331.    if (::GetObject(hBitmap, sizeof(bm),(LPVOID)&bm))
  332.    {
  333.       m_hilIcon = ::ImageList_Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24|ILC_MASK,1,0);
  334.       ::ImageList_Add(m_hilIcon, hBitmap, hMask);
  335.    }
  336.    // if already visible, resize & move (icon size may have changed)
  337.    if ( NULL != m_hWnd )
  338.       PositionWindow();
  339. }
  340. // Set icon displayed in the top left of the balloon to image # nIconIndex from pImageList
  341. void CBalloonHelp::SetIcon(HIMAGELIST hImageList, int nIconIndex)
  342. {
  343.    // sanity checks
  344.    ASSERT(hImageList);
  345.    ASSERT(nIconIndex >= 0 && nIconIndex < ::ImageList_GetImageCount(hImageList));
  346.    HICON hIcon = NULL;
  347.    if ( NULL != hImageList && nIconIndex >= 0 && nIconIndex < ::ImageList_GetImageCount(hImageList) )
  348.        ::ImageList_ExtractIcon(&hIcon, hImageList, nIconIndex);
  349.    SetIcon(hIcon);
  350.    if ( NULL != hIcon )
  351.       ::DestroyIcon(hIcon);
  352.    // if already visible, resize & move (icon size may have changed)
  353.    if ( NULL != m_hWnd )
  354.       PositionWindow();
  355. }
  356. // Sets the URL to be opened when balloon is clicked.  Pass "" to disable.
  357. void CBalloonHelp::SetURL(const CStdString& strURL)
  358. {
  359.    m_strURL = strURL;
  360. }
  361. // Sets the number of milliseconds the balloon can remain open.  Set to 0 to disable timeout.
  362. void CBalloonHelp::SetTimeout(unsigned int unTimeout)
  363. {
  364.    m_unTimeout = unTimeout;
  365.    // if timer is already set, reset.
  366.    if ( NULL != m_hWnd )
  367.    {
  368.       if ( m_unTimeout > 0 )
  369.       {
  370.          m_unTimerClose = ::SetTimer(m_hWnd, ID_TIMER_CLOSE, m_unTimeout, NULL);
  371.       }
  372.       else
  373.       {
  374.          ::KillTimer(m_hWnd, m_unTimerClose);
  375.       }
  376.    }
  377. }
  378. // Sets the point to which the balloon is "anchored"
  379. void CBalloonHelp::SetAnchorPoint(POINT& ptAnchor, HWND hWndAnchor /*= NULL*/)
  380. {
  381.    m_ptAnchor = ptAnchor;
  382.    m_hwndAnchor = hWndAnchor;
  383.    // if we're anchored to a window, set hook
  384.    if ( NULL != m_hwndAnchor )
  385.       SetCallWndRetHook();
  386.    else
  387.       RemoveCallWndRetHook();
  388.    // if already visible, move
  389.    if ( NULL != m_hWnd )
  390.    {
  391.       // reposition
  392.       PositionWindow();
  393.    }
  394. }
  395. // Sets the title of the balloon
  396. void CBalloonHelp::SetTitle(const CStdString& strTitle)
  397. {
  398.    ::SetWindowText(m_hWnd, strTitle);
  399.    // if already visible, resize & move
  400.    if ( NULL != m_hWnd )
  401.       PositionWindow();
  402. }
  403. // Sets the content of the balloon (plain text only)
  404. void CBalloonHelp::SetContent(const CStdString& strContent)
  405. {
  406.    m_strContent = strContent;
  407.    // if already visible, resize & move
  408.    if ( NULL != m_hWnd )
  409.       PositionWindow();
  410. }
  411. // Sets the forground (text and border) color of the balloon
  412. void CBalloonHelp::SetForegroundColor(COLORREF crForeground)
  413. {
  414.    m_crForeground = crForeground;
  415.    // repaint if visible
  416.    if ( NULL != m_hWnd )
  417.       ::InvalidateRect(m_hWnd, NULL, FALSE);
  418. }
  419. // Sets the background color of the balloon
  420. void CBalloonHelp::SetBackgroundColor(COLORREF crBackground)
  421. {
  422.    m_crBackground = crBackground;
  423.    // repaint if visible
  424.    if ( NULL != m_hWnd )
  425.       ::InvalidateRect(m_hWnd, NULL, FALSE);
  426. }
  427. // Sets the distance the mouse must move before the balloon closes when the unCLOSE_ON_MOUSE_MOVE option is set.
  428. void CBalloonHelp::SetMouseMoveTolerance(int nTolerance)
  429. {
  430.    m_nMouseMoveTolerance = nTolerance;
  431. }
  432. //
  433. // creates a new balloon window
  434. // Parameters:
  435. //    strTitle    |  Title of balloon
  436. //    strContent  |  Content of balloon
  437. //    ptAnchor    |  point tail of balloon will be "anchor"ed to
  438. //    unOptions   |  One or more of:
  439. //                :     unCLOSE_ON_LBUTTON_UP   |  closes window on WM_LBUTTON_UP
  440. //                :     unCLOSE_ON_MBUTTON_UP   |  closes window on WM_MBUTTON_UP
  441. //                :     unCLOSE_ON_RBUTTON_UP   |  closes window on WM_RBUTTON_UP
  442. //                :     unCLOSE_ON_LBUTTON_DOWN |  closes window on WM_LBUTTON_DOWN
  443. //                :     unCLOSE_ON_MBUTTON_DOWN |  closes window on WM_MBUTTON_DOWN
  444. //                :     unCLOSE_ON_RBUTTON_DOWN |  closes window on WM_RBUTTON_DOWN
  445. //                :     unCLOSE_ON_MOUSE_MOVE   |  closes window when user moves mouse past threshhold
  446. //                :     unCLOSE_ON_KEYPRESS     |  closes window on the next keypress message sent to this thread.
  447. //                :     unCLOSE_ON_ANYTHING     |  all of the above.
  448. //                :     unDELAY_CLOSE           |  when a user action triggers the close, begins timer.  closes when timer expires.
  449. //                :     unDELETE_THIS_ON_CLOSE  |  deletes object when window is closed.  Used by LaunchBalloon(), use with care
  450. //                :     unSHOW_CLOSE_BUTTON     |  shows close button in upper right
  451. //                :     unSHOW_INNER_SHADOW     |  draw inner shadow in balloon
  452. //                :     unSHOW_TOPMOST          |  place balloon above all other windows
  453. //                :     unDISABLE_XP_SHADOW     |  disable Windows XP's drop-shadow effect (overrides system and user settings)
  454. //                :     unDISABLE_FADE          |  disable the fade-in/fade-out effects (overrides system and user settings)
  455. //                :     unDISABLE_FADEIN        |  disable the fade-in effect
  456. //                :     unDISABLE_FADEOUT       |  disable the fade-out effect
  457. //    pParentWnd  |  Parent window.  If NULL will be set to AfxGetMainWnd() and anchor to screen
  458. //    strURL      |  If not empty, when the balloon is clicked ShellExecute() will
  459. //                |  be called, with strURL passed in.
  460. //    unTimeout   |  If not 0, balloon will automatically close after unTimeout milliseconds.
  461. //    hIcon       |  If not NULL, the icon indicated by hIcon will be displayed at top-left of the balloon.
  462. //
  463. // Returns:
  464. //    TRUE if successful, else FALSE
  465. //
  466. BOOL CBalloonHelp::Create(const CStdString& strTitle, const CStdString& strContent, 
  467.                POINT& ptAnchor, unsigned int unOptions,
  468.                HWND hParentWnd /*=NULL*/,
  469.                const CStdString strURL /*= ""*/,
  470.                unsigned int unTimeout /*= 0*/,
  471.                HICON hIcon /*= NULL*/)
  472. {
  473.    m_strContent   = strContent;
  474.    SetAnchorPoint(ptAnchor, hParentWnd);
  475.    m_unOptions    = unOptions;
  476.    m_strURL       = strURL;
  477.    m_unTimeout    = unTimeout;
  478.    if ( NULL != hIcon )
  479.       SetIcon(hIcon);
  480.    
  481.    
  482.    // if no fonts set, use defaults
  483.    if ( NULL == m_hContentFont )
  484.    {
  485.       m_hContentFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
  486.       if ( NULL == m_hContentFont )
  487.          return FALSE;
  488.    }
  489.    // title font defaults to bold version of content font
  490.    if ( NULL == m_hTitleFont )
  491.    {      
  492.       LOGFONT LogFont;
  493.       m_hTitleFont = (HFONT)::GetObject(m_hContentFont, sizeof(LOGFONT), &LogFont);
  494.       LogFont.lfWeight = FW_BOLD;
  495. m_hTitleFont = ::CreateFontIndirect(&LogFont);
  496.       if ( NULL == m_hTitleFont )
  497.          return FALSE;
  498.    }
  499.    ATOM wndClass = GetClassAtom(!(m_unOptions&unDISABLE_XP_SHADOW));
  500.    if ( 0 == wndClass )  // couldn't register class
  501.       return FALSE;
  502.    // check system settings: if fade effects are disabled or unavailable, disable here too
  503.    BOOL bFade = FALSE;
  504.    ::SystemParametersInfo(SPI_GETTOOLTIPANIMATION, 0, &bFade, 0);
  505.    if (bFade)
  506.       ::SystemParametersInfo(SPI_GETTOOLTIPFADE, 0, &bFade, 0);
  507.    if (!bFade || NULL == m_fnAnimateWindow)
  508.       m_unOptions |= unDISABLE_FADE;
  509.    // create invisible at arbitrary position; then position, set region, and finally show
  510.    // the idea with WS_EX_TOOLWINDOW is, you can't switch to this using alt+tab
  511.    DWORD dwExStyle = WS_EX_TOOLWINDOW;
  512.    if ( m_unOptions&unSHOW_TOPMOST )      // make topmost, if requested
  513.       dwExStyle |= WS_EX_TOPMOST;
  514. RECT rc = {0,0,10,10};
  515.    if ( !CreateEx(dwExStyle, (LPCTSTR)wndClass, strTitle, WS_POPUP, rc, hParentWnd, 0, NULL) )
  516.       return FALSE;
  517.    PositionWindow();
  518.    if ( (m_unOptions&unCLOSE_ON_MOUSE_MOVE)
  519.       ||(m_unOptions&unCLOSE_ON_LBUTTON_UP)
  520.       ||(m_unOptions&unCLOSE_ON_LBUTTON_DOWN)
  521.       ||(m_unOptions&unCLOSE_ON_MBUTTON_UP)
  522.       ||(m_unOptions&unCLOSE_ON_MBUTTON_DOWN)
  523.       ||(m_unOptions&unCLOSE_ON_RBUTTON_UP)
  524.       ||(m_unOptions&unCLOSE_ON_RBUTTON_DOWN) )
  525.    {
  526.       ::GetCursorPos(&m_ptMouseOrig);
  527.       SetMouseHook();
  528.    }
  529.    // these need to take effect even if the window receiving them
  530.    // is not owned by this process.  So, if this process does not
  531.    // already have the mouse captured, capture it!
  532.    if ( (m_unOptions&unCLOSE_ON_LBUTTON_UP)
  533.       ||(m_unOptions&unCLOSE_ON_MBUTTON_UP)
  534.       ||(m_unOptions&unCLOSE_ON_RBUTTON_UP) )
  535.    {
  536.       // no, i don't particularly need or want to deal with a situation
  537.       // where a balloon is being created and another program has captured
  538.       // the mouse.  If you need it, it shouldn't be too hard, just do it here.
  539.       if ( NULL == ::GetCapture() )
  540.          ::SetCapture(m_hWnd);
  541.    }
  542.    if ( m_unOptions&unCLOSE_ON_KEYPRESS )
  543.       SetKeyboardHook();
  544.    ShowBalloon();
  545.    return TRUE;
  546. }
  547. // calculate anchor position (adjust for client coordinates if used)
  548. POINT CBalloonHelp::GetAnchorPoint()
  549. {
  550.    POINT ptAnchor = m_ptAnchor;
  551.    // assume if window was given, point is in client coords
  552.    if ( NULL != m_hwndAnchor )
  553.       ::ClientToScreen(m_hwndAnchor, &ptAnchor);
  554.    return ptAnchor;
  555. }
  556. // determine bounds of screen anchor is on (Multi-Monitor compatibility)
  557. void CBalloonHelp::GetAnchorScreenBounds(RECT& rect)
  558. {
  559.    if ( ::IsRectEmpty(&rect) )
  560.    {     
  561.       // get the nearest monitor to the anchor
  562.       HMONITOR hMonitor = MonitorFromPoint(GetAnchorPoint(), MONITOR_DEFAULTTONEAREST);
  563.       // get the monitor bounds
  564.       MONITORINFO mi;
  565.       mi.cbSize = sizeof(mi);
  566.       GetMonitorInfo(hMonitor, &mi);
  567.       // work area (area not obscured by task bar, etc.)
  568.       m_screenRect = mi.rcWork;
  569.    }
  570.    rect = m_screenRect;
  571. }
  572. // calculates the area of the screen the balloon falls into
  573. // this determins which direction the tail points
  574. CBalloonHelp::BALLOON_QUADRANT CBalloonHelp::GetBalloonQuadrant()
  575. {
  576.    RECT rectDesktop;
  577.    GetAnchorScreenBounds(rectDesktop);
  578.    POINT ptAnchor = GetAnchorPoint();
  579.    
  580.    if ( ptAnchor.y < rectDesktop.top + (rectDesktop.bottom - rectDesktop.top)/2 )
  581.    {
  582.       if ( ptAnchor.x < rectDesktop.left + (rectDesktop.right - rectDesktop.left)/2 )
  583.       {
  584.          return BQ_TOPLEFT;
  585.       }
  586.       else
  587.       {
  588.          return BQ_TOPRIGHT;
  589.       }
  590.    }
  591.    else
  592.    {
  593.       if ( ptAnchor.x < rectDesktop.left + (rectDesktop.right - rectDesktop.left)/2 )
  594.       {
  595.          return BQ_BOTTOMLEFT;
  596.       }
  597.       else
  598.       {
  599.          return BQ_BOTTOMRIGHT;
  600.       }
  601.    }
  602.    // unreachable
  603. }
  604. // Draw the non-client area
  605. void CBalloonHelp::DrawNonClientArea(HDC hDC)
  606. {
  607.    RECT rect;
  608.    ::GetWindowRect(m_hWnd, &rect);
  609. POINT pt = {rect.left, rect.top};
  610.    ::ScreenToClient(m_hWnd, &pt);
  611. rect.left = pt.x; rect.top = pt.y;
  612. pt.x = rect.right; pt.y=rect.bottom;
  613.    ::ScreenToClient(m_hWnd, &pt);
  614. rect.right = pt.x; rect.bottom = pt.y;
  615.    RECT rectClient;
  616.    ::GetClientRect(m_hWnd, &rectClient);
  617.    ::OffsetRect(&rectClient, -rect.left, -rect.top);
  618.    ::OffsetRect(&rect, -rect.left, -rect.top);
  619.    ::ExcludeClipRect(hDC, rectClient.left, rectClient.top, rectClient.right, rectClient.bottom);
  620. HBRUSH hBrush = ::CreateSolidBrush(m_crBackground);
  621.    ::FillRect(hDC, &rect, hBrush);
  622. ::DeleteObject(hBrush);
  623. ::SelectClipRgn(hDC, NULL);
  624.    ASSERT(NULL != m_hrgnComplete);
  625.    HBRUSH  brushFg = ::CreateSolidBrush(m_crForeground);
  626.    if ( m_unOptions & unSHOW_INNER_SHADOW )
  627.    {
  628.       HBRUSH    brushHL;
  629.       // slightly lighter color
  630.       int red = 170 + GetRValue(m_crBackground)/3;
  631.       int green = 170 + GetGValue(m_crBackground)/3;
  632.       int blue = 170 + GetBValue(m_crBackground)/3;
  633.       brushHL = ::CreateSolidBrush(RGB(red,green,blue));
  634.       ::OffsetRgn(m_hrgnComplete, 1,1);
  635.       ::FrameRgn(hDC, m_hrgnComplete, brushHL, 2, 2);
  636.       // slightly darker color
  637.       red = GetRValue(m_crForeground)/3 + GetRValue(m_crBackground)/3*2;
  638.       green = GetGValue(m_crForeground)/3 + GetGValue(m_crBackground)/3*2;
  639.       blue = GetBValue(m_crForeground)/3 + GetBValue(m_crBackground)/3*2;
  640.       ::DeleteObject(brushHL);
  641.       ::OffsetRgn(m_hrgnComplete, -2,-2);
  642.       brushHL = ::CreateSolidBrush(RGB(red,green,blue));
  643.       ::FrameRgn(hDC, m_hrgnComplete, brushHL, 2, 2);
  644.       ::OffsetRgn(m_hrgnComplete, 1,1);
  645. ::DeleteObject(brushHL);
  646.    }
  647.    // outline
  648.    FrameRgn(hDC, m_hrgnComplete, brushFg, 1, 1);
  649. ::DeleteObject(brushFg);
  650. }
  651. // Draw the client area
  652. void CBalloonHelp::DrawClientArea(HDC hDC)
  653. {
  654.    SIZE sizeHeader = DrawHeader(hDC);
  655.    DrawContent(hDC, sizeHeader.cy+nTIP_MARGIN);
  656. }
  657. // Calculate the dimensions and draw the balloon header
  658. SIZE CBalloonHelp::DrawHeader(HDC hDC, bool bDraw)
  659. {
  660.    SIZE sizeHdr = {0,0};
  661.    RECT rectClient;
  662.    ::GetClientRect(m_hWnd, &rectClient);   // use this for positioning when drawing
  663.                                  // else if content is wider than title, centering wouldn't work
  664.    // calc & draw icon
  665.    if ( NULL != m_hilIcon)
  666.    {
  667.       int x = 0;
  668.       int y = 0;
  669.       ImageList_GetIconSize(m_hilIcon, &x, &y);
  670.       sizeHdr.cx += x;
  671.       sizeHdr.cy = max(sizeHdr.cy, y);
  672.       ImageList_SetBkColor(m_hilIcon, m_crBackground);
  673.       if (bDraw)
  674.          ImageList_Draw(m_hilIcon, 0, hDC, 0, 0, ILD_NORMAL);//ILD_TRANSPARENT);
  675.       rectClient.left += x;
  676.    }
  677.    // calc & draw close button
  678.    if ( m_unOptions & unSHOW_CLOSE_BUTTON )
  679.    {
  680.       int nBtnWidth = ::GetSystemMetrics(SM_CXSIZE);
  681.       // if something is already in the header (icon) leave space
  682.       if ( sizeHdr.cx > 0 )
  683.          sizeHdr.cx += nTIP_MARGIN;
  684.       sizeHdr.cx += nBtnWidth;
  685.       sizeHdr.cy = max(sizeHdr.cy, ::GetSystemMetrics(SM_CYSIZE));
  686.       if (bDraw)
  687. {
  688. RECT rc = {rectClient.right-nBtnWidth,0,rectClient.right,::GetSystemMetrics(SM_CYSIZE)};
  689.          ::DrawFrameControl(hDC, &rc, DFC_CAPTION, DFCS_CAPTIONCLOSE|DFCS_FLAT);
  690. }
  691.       rectClient.right -= nBtnWidth;
  692.    }
  693.    // calc title size
  694. CStdString strTitle;
  695.    ::GetWindowText(m_hWnd, strTitle.GetBuffer(MAX_PATH), MAX_PATH);
  696. strTitle.ReleaseBuffer();
  697.    if ( !strTitle.IsEmpty() )
  698.    {
  699.       HFONT hOldFont = (HFONT) SelectObject(hDC, m_hTitleFont);
  700.       // if something is already in the header (icon or close button) leave space
  701.       if ( sizeHdr.cx > 0 ) 
  702.          sizeHdr.cx += nTIP_MARGIN;
  703.       RECT rectTitle = {0,0,0,0};
  704.       ::DrawText(hDC, strTitle, strTitle.GetLength(), &rectTitle, DT_CALCRECT | DT_NOPREFIX | DT_EXPANDTABS | DT_SINGLELINE);
  705.       sizeHdr.cx += rectTitle.right - rectTitle.left ;
  706.       sizeHdr.cy = max(sizeHdr.cy, rectTitle.bottom - rectTitle.top);
  707.       // draw title
  708.       if ( bDraw )
  709.       {
  710.          ::SetBkMode(hDC, TRANSPARENT);
  711.          ::SetTextColor(hDC, m_crForeground);
  712.          ::DrawText(hDC, strTitle, strTitle.GetLength(), &rectClient, DT_CENTER | DT_NOPREFIX  | DT_EXPANDTABS | DT_SINGLELINE);
  713.       }
  714.       // cleanup
  715.       SelectObject(hDC, hOldFont);
  716.    }
  717.    return sizeHdr;
  718. }
  719. // Calculate the dimensions and draw the balloon contents
  720. SIZE CBalloonHelp::DrawContent(HDC hDC, int nTop, bool bDraw)
  721. {
  722. RECT rectContent;
  723.    GetAnchorScreenBounds(rectContent);
  724.    ::OffsetRect(&rectContent, -rectContent.left, -rectContent.top);
  725.    rectContent.top = nTop;
  726.    // limit to half screen width
  727.    rectContent.right -= (rectContent.right - rectContent.left)/2;
  728.    // calc size
  729.    HFONT hOldFont = (HFONT)SelectObject(hDC,m_hContentFont);
  730.    if ( !m_strContent.IsEmpty() )
  731.       ::DrawText(hDC, m_strContent, m_strContent.GetLength(), &rectContent, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EXPANDTABS | DT_WORDBREAK);
  732.    else
  733.       ::SetRectEmpty(&rectContent);   // don't want to leave half the screen for empty strings ;)
  734.    
  735.    // draw
  736.    if (bDraw)
  737.    {
  738.       ::SetBkMode(hDC, TRANSPARENT);
  739.       ::SetTextColor(hDC, m_crForeground);
  740.       ::DrawText(hDC, m_strContent, m_strContent.GetLength(), &rectContent, DT_LEFT | DT_NOPREFIX | DT_WORDBREAK | DT_EXPANDTABS);
  741.    }
  742.    // cleanup
  743.    ::SelectObject(hDC, hOldFont);
  744. SIZE ret = {rectContent.right - rectContent.left, rectContent.bottom - rectContent.top}; 
  745.    return ret;
  746. }
  747. // calculates the client size necessary based on title and content
  748. SIZE CBalloonHelp::CalcClientSize()
  749. {
  750.    ASSERT(NULL != m_hWnd);
  751.    HDC dc = ::GetWindowDC(m_hWnd);
  752.    SIZE sizeHeader = CalcHeaderSize(dc);
  753.    SIZE sizeContent = CalcContentSize(dc);
  754. ::ReleaseDC(m_hWnd, dc);
  755. SIZE ret = {max(sizeHeader.cx,sizeContent.cx), sizeHeader.cy + nTIP_MARGIN + sizeContent.cy};
  756.    return ret;
  757. }
  758. // calculates the size for the entire window based on content size
  759. SIZE CBalloonHelp::CalcWindowSize()
  760. {
  761.    SIZE size = CalcClientSize();
  762.    size.cx += nTIP_MARGIN*2;
  763.    size.cy += nTIP_TAIL+nTIP_MARGIN*2;
  764.    //size.cx = max(size.cx, nTIP_MARGIN*2+nTIP_TAIL*4);
  765.    return size;
  766. }
  767. // this routine calculates the size and position of the window relative
  768. // to it's anchor point, and moves the window accordingly.  The region is also
  769. // created and set here.
  770. void CBalloonHelp::PositionWindow()
  771. {
  772.    SIZE sizeWnd = CalcWindowSize();
  773.    POINT ptTail[3];
  774.    POINT ptTopLeft = {0,0};
  775.    POINT ptBottomRight = {sizeWnd.cx, sizeWnd.cy};
  776.    // force recalculation of desktop
  777.    ::SetRectEmpty(&m_screenRect);
  778.    switch (GetBalloonQuadrant())
  779.    {
  780.    case BQ_TOPLEFT:
  781.       ptTopLeft.y = nTIP_TAIL;
  782.       ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4 + nTIP_TAIL;
  783.       ptTail[0].y = nTIP_TAIL+1;
  784.       ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4;
  785.       ptTail[2].y = ptTail[0].y;
  786.       ptTail[1].x = ptTail[2].x;
  787.       ptTail[1].y = 1;
  788.       break;
  789.    case BQ_TOPRIGHT:
  790.       ptTopLeft.y = nTIP_TAIL;
  791.       ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4*3;
  792.       ptTail[0].y = nTIP_TAIL+1;
  793.       ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4*3 + nTIP_TAIL;
  794.       ptTail[2].y = ptTail[0].y;
  795.       ptTail[1].x = ptTail[2].x;
  796.       ptTail[1].y = 1;
  797.       break;
  798.    case BQ_BOTTOMLEFT:
  799.       ptBottomRight.y = sizeWnd.cy-nTIP_TAIL;
  800.       ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4 + nTIP_TAIL;
  801.       ptTail[0].y = sizeWnd.cy-nTIP_TAIL-2;
  802.       ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4;
  803.       ptTail[2].y = ptTail[0].y;
  804.       ptTail[1].x = ptTail[2].x;
  805.       ptTail[1].y = sizeWnd.cy-2;
  806.       break;
  807.    case BQ_BOTTOMRIGHT:
  808.       ptBottomRight.y = sizeWnd.cy-nTIP_TAIL;
  809.       ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4*3;
  810.       ptTail[0].y = sizeWnd.cy-nTIP_TAIL-2;
  811.       ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4*3 + nTIP_TAIL;
  812.       ptTail[2].y = ptTail[0].y;
  813.       ptTail[1].x = ptTail[2].x;
  814.       ptTail[1].y = sizeWnd.cy-2;
  815.       break;
  816.    }
  817.    // adjust for very narrow balloons
  818.    if ( ptTail[0].x < nTIP_MARGIN )
  819.       ptTail[0].x = nTIP_MARGIN;
  820.    if ( ptTail[0].x > sizeWnd.cx - nTIP_MARGIN )
  821.       ptTail[0].x = sizeWnd.cx - nTIP_MARGIN;
  822.    if ( ptTail[1].x < nTIP_MARGIN )
  823.       ptTail[1].x = nTIP_MARGIN;
  824.    if ( ptTail[1].x > sizeWnd.cx - nTIP_MARGIN )
  825.       ptTail[1].x = sizeWnd.cx - nTIP_MARGIN;
  826.    if ( ptTail[2].x < nTIP_MARGIN )
  827.       ptTail[2].x = nTIP_MARGIN;
  828.    if ( ptTail[2].x > sizeWnd.cx - nTIP_MARGIN )
  829.       ptTail[2].x = sizeWnd.cx - nTIP_MARGIN;
  830.    // get window position
  831.    POINT ptAnchor = GetAnchorPoint();
  832.    POINT ptOffs = {ptAnchor.x - ptTail[1].x, ptAnchor.y - ptTail[1].y};
  833.    // adjust position so all is visible
  834.    RECT rectScreen;
  835.    GetAnchorScreenBounds(rectScreen);
  836.    int nAdjustX = 0;
  837.    int nAdjustY = 0;
  838.    if ( ptOffs.x < rectScreen.left )
  839.       nAdjustX = rectScreen.left-ptOffs.x;
  840.    else if ( ptOffs.x + sizeWnd.cx >= rectScreen.right )
  841.       nAdjustX = rectScreen.right - (ptOffs.x + sizeWnd.cx);
  842.    if ( ptOffs.y + nTIP_TAIL < rectScreen.top )
  843.       nAdjustY = rectScreen.top - (ptOffs.y + nTIP_TAIL);
  844.    else if ( ptOffs.y + sizeWnd.cy - nTIP_TAIL >= rectScreen.bottom )
  845.       nAdjustY = rectScreen.bottom - (ptOffs.y + sizeWnd.cy - nTIP_TAIL);
  846.    // reposition tail
  847.    // uncomment two commented lines below to move entire tail 
  848.    // instead of just anchor point
  849.    //ptTail[0].x -= nAdjustX;
  850.    ptTail[1].x -= nAdjustX;
  851.    //ptTail[2].x -= nAdjustX;
  852.    ptOffs.x    += nAdjustX;
  853.    ptOffs.y    += nAdjustY;
  854.    // place window
  855.    ::MoveWindow(m_hWnd, ptOffs.x, ptOffs.y, sizeWnd.cx, sizeWnd.cy, TRUE);
  856.    // apply region
  857.    HRGN region = ::CreatePolygonRgn(&ptTail[0], 3, ALTERNATE);
  858.    HRGN regionRound = ::CreateRoundRectRgn(ptTopLeft.x,ptTopLeft.y,ptBottomRight.x,ptBottomRight.y,nTIP_MARGIN*3,nTIP_MARGIN*3);
  859.    HRGN regionComplete = ::CreateRectRgn(0,0,1,1);
  860.       
  861.    ::CombineRgn(regionComplete, region, regionRound, RGN_OR);
  862.    if ( NULL == m_hrgnComplete)
  863.       m_hrgnComplete = ::CreateRectRgn(0,0,1,1);
  864.    if ( !::EqualRgn(m_hrgnComplete, regionComplete) )
  865.    {
  866.       ::CopyRgn(m_hrgnComplete, regionComplete);
  867.       ::SetWindowRgn(m_hWnd, regionComplete, TRUE);
  868.       // There is a bug with layered windows and NC changes in Win2k
  869.       // As a workaround, redraw the entire window if the NC area changed.
  870.       // Changing the anchor point is the ONLY thing that will change the
  871.       // position of the client area relative to the window during normal
  872.       // operation.
  873.       ::RedrawWindow(m_hWnd, NULL, NULL, RDW_UPDATENOW| RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
  874.    }
  875. else
  876. ::DeleteObject(regionComplete);
  877. ::DeleteObject(region);
  878. ::DeleteObject(regionRound);
  879. }
  880. // Returns the class ATOM for a BalloonHelp control.  Registers the class first, if necessary.
  881. ATOM CBalloonHelp::GetClassAtom(BOOL bShadowed)
  882. {
  883.    if ( 0 == s_ClassAtom )
  884.    {
  885.       WNDCLASSEX wcx; 
  886.       // Fill in the window class structure with parameters 
  887.       // that describe the main window. 
  888.       wcx.cbSize = sizeof(wcx);                 // size of structure 
  889.       wcx.style = CS_DBLCLKS|CS_SAVEBITS
  890.          |CS_DROPSHADOW;                        // notify of double clicks, save screen under, show dropshadow
  891.       wcx.lpfnWndProc = CWnd::stWinMsgHandler;             // points to window procedure 
  892.       wcx.cbClsExtra = 0;                       // no extra class memory 
  893.       wcx.cbWndExtra = 0;                       // no extra window memory 
  894.       wcx.hInstance = g_hInstance;   // handle to instance 
  895.       wcx.hIcon = NULL;                         // no app. icon 
  896.       wcx.hCursor = LoadCursor(NULL,IDC_ARROW); // predefined arrow 
  897.       wcx.hbrBackground = ::GetSysColorBrush(COLOR_WINDOW);                 // no background brush 
  898.       wcx.lpszMenuName =  NULL;                 // no menu resource 
  899.       wcx.lpszClassName = "BalloonHelpClassDS"; // name of window class 
  900.       wcx.hIconSm = NULL;                       // no small class icon
  901.       // Register the window class (this may not work if dropshadows are not supported)
  902.       s_ClassAtomShadowed = ::RegisterClassEx(&wcx);
  903.       // Register shadow-less class
  904.       wcx.style &= ~CS_DROPSHADOW;
  905.       wcx.lpszClassName = "BalloonHelpClass";
  906.       s_ClassAtom = ::RegisterClassEx(&wcx);
  907.    }
  908.    if ( bShadowed && 0 != s_ClassAtomShadowed )
  909.       return s_ClassAtomShadowed;
  910.    return s_ClassAtom;
  911. }
  912. // Displays the balloon on the screen, performing fade-in if enabled.
  913. void CBalloonHelp::ShowBalloon(void)
  914. {
  915.    ::ShowWindow(m_hWnd, SW_SHOWNOACTIVATE);
  916.    if ( !(m_unOptions&unDELAY_CLOSE) )
  917.       SetTimeout(m_unTimeout);     // start close timer
  918. }
  919. // Removes the balloon from the screen, performing the fade-out if enabled
  920. void CBalloonHelp::HideBalloon(void)
  921. {
  922.    if ( m_unOptions&unDELAY_CLOSE )
  923.    {
  924.       m_unOptions &= ~(unDELAY_CLOSE|unCLOSE_ON_ANYTHING);  // close only via timer or button
  925.       SetTimeout(m_unTimeout);     // start close timer
  926.       return;
  927.    }
  928.    ::ShowWindow(m_hWnd, SW_HIDE);
  929.    if ( GetCapture() == m_hWnd ) 
  930.       ReleaseCapture();
  931.    ::DestroyWindow(m_hWnd);
  932. }
  933. //
  934. // Keyboard hook
  935. //
  936. void CBalloonHelp::SetKeyboardHook()
  937. {
  938.    if ( NULL==m_hKeyboardHook )
  939.    {
  940.       m_hKeyboardHook = ::SetWindowsHookEx(WH_KEYBOARD,
  941.          (HOOKPROC)BHKeybHookThunk<CBalloonHelp>::GetThunk(),
  942.          NULL, ::GetCurrentThreadId());
  943.    }
  944. }
  945. void CBalloonHelp::RemoveKeyboardHook()
  946. {
  947.    if ( NULL!=m_hKeyboardHook )
  948.    {
  949.       ::UnhookWindowsHookEx(m_hKeyboardHook);
  950.       m_hKeyboardHook=NULL;
  951.    }
  952. }
  953. //
  954. // Mouse hook
  955. //
  956. void CBalloonHelp::SetMouseHook()
  957. {
  958.    if ( NULL==m_hMouseHook )
  959.    {
  960.       m_hMouseHook = ::SetWindowsHookEx(WH_MOUSE,
  961.          (HOOKPROC)BHMouseHookThunk<CBalloonHelp>::GetThunk(),
  962.          NULL, ::GetCurrentThreadId());
  963.    }
  964. }
  965. void CBalloonHelp::RemoveMouseHook()
  966. {
  967.    if ( NULL!=m_hMouseHook )
  968.    {
  969.       ::UnhookWindowsHookEx(m_hMouseHook);
  970.       m_hMouseHook=NULL;
  971.    }
  972. }
  973. //
  974. // Call Window Return hook
  975. //
  976. void CBalloonHelp::SetCallWndRetHook()
  977. {
  978.    if ( NULL==m_hCallWndRetHook )
  979.    {
  980.       m_hCallWndRetHook = ::SetWindowsHookEx(WH_CALLWNDPROCRET,
  981.          (HOOKPROC)BHCallWndRetHookThunk<CBalloonHelp>::GetThunk(),
  982.          NULL, ::GetCurrentThreadId());
  983.    }
  984. }
  985. void CBalloonHelp::RemoveCallWndRetHook()
  986. {
  987.    if ( NULL!=m_hCallWndRetHook )
  988.    {
  989.       ::UnhookWindowsHookEx(m_hCallWndRetHook);
  990.       m_hCallWndRetHook=NULL;
  991.    }
  992. }
  993. LRESULT CALLBACK CBalloonHelp::WinMsgHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  994. {
  995. switch(uMsg)
  996. {
  997. HANDLE_MSG (hwnd, WM_CLOSE, CBalloonHelp::OnClose);
  998. HANDLE_MSG (hwnd, WM_DESTROY, CBalloonHelp::OnDestroy);
  999. HANDLE_MSG (hwnd, WM_ERASEBKGND, CBalloonHelp::OnEraseBkgnd);
  1000. HANDLE_MSG (hwnd, WM_LBUTTONDOWN, CBalloonHelp::OnLButtonDown);
  1001. HANDLE_MSG (hwnd, WM_LBUTTONUP, CBalloonHelp::OnLButtonUp);
  1002. HANDLE_MSG (hwnd, WM_MOUSEMOVE, CBalloonHelp::OnMouseMove);
  1003. HANDLE_MSG (hwnd, WM_NCCALCSIZE, CBalloonHelp::OnNCCalcSize);
  1004. HANDLE_MSG (hwnd, WM_NCDESTROY, CBalloonHelp::OnNCDestroy);
  1005. HANDLE_MSG (hwnd, WM_NCHITTEST, CBalloonHelp::OnNCHitTest);
  1006. HANDLE_MSG (hwnd, WM_NCPAINT, CBalloonHelp::OnNCPaint);
  1007. HANDLE_MSG (hwnd, WM_PAINT, CBalloonHelp::OnPaint);
  1008. HANDLE_MSG (hwnd, WM_SHOWWINDOW, CBalloonHelp::OnShowWindow);
  1009. HANDLE_MSG (hwnd, WM_TIMER, CBalloonHelp::OnTimer);
  1010. case WM_PRINT: return CBalloonHelp::OnPrint(hwnd, wParam, lParam);
  1011. case WM_PRINTCLIENT: return CBalloonHelp::OnPrintClient(hwnd, wParam, lParam);
  1012. default: return DefWindowProc (hwnd, uMsg, wParam, lParam);
  1013. }
  1014. }
  1015. #define MSGHANDLER_PROLOG_BOOL
  1016. CBalloonHelp *thisPtr = (CBalloonHelp*)GetObjectFromWindow(hwnd);
  1017. if(!thisPtr) 
  1018. return FALSE; 
  1019. #define MSGHANDLER_PROLOG
  1020. CBalloonHelp *thisPtr = (CBalloonHelp*)GetObjectFromWindow(hwnd);
  1021. if(!thisPtr) 
  1022. return; 
  1023. #define MSGHANDLER_EPILOG 
  1024. void CBalloonHelp::OnShowWindow(HWND hwnd, BOOL fShow, UINT status)
  1025. {
  1026. MSGHANDLER_PROLOG
  1027.    if ( NULL != thisPtr->m_fnAnimateWindow )
  1028.    {
  1029.       if ( fShow && !(thisPtr->m_unOptions&unDISABLE_FADEIN) )
  1030.          thisPtr->m_fnAnimateWindow(thisPtr->m_hWnd, 200, AW_BLEND);
  1031.       else if ( !fShow && !(thisPtr->m_unOptions&unDISABLE_FADEOUT) )
  1032.          thisPtr->m_fnAnimateWindow(thisPtr->m_hWnd, 200, AW_HIDE | AW_BLEND );
  1033.    }
  1034. MSGHANDLER_EPILOG 
  1035. }
  1036. // Erase client area of balloon
  1037. BOOL CBalloonHelp::OnEraseBkgnd(HWND hwnd, HDC hdc)
  1038. {
  1039. MSGHANDLER_PROLOG_BOOL
  1040.    RECT rect;
  1041.    GetClientRect(hwnd, &rect);
  1042. HBRUSH hBrush = ::CreateSolidBrush(thisPtr->m_crBackground);
  1043.    ::FillRect(hdc, &rect, hBrush);
  1044. ::DeleteObject(hBrush);
  1045. MSGHANDLER_EPILOG
  1046.    return TRUE;
  1047. }
  1048. // draw balloon client area (title & contents)
  1049. void CBalloonHelp::OnPaint(HWND hwnd) 
  1050. {
  1051. MSGHANDLER_PROLOG
  1052. PAINTSTRUCT ps;
  1053. HDC dc = ::BeginPaint(hwnd, &ps);   
  1054.    thisPtr->DrawClientArea(dc);
  1055. ::EndPaint(hwnd, &ps);
  1056. MSGHANDLER_EPILOG
  1057. }
  1058. // draw balloon shape & border
  1059. void CBalloonHelp::OnNCPaint(HWND hwnd, HRGN hrgn) 
  1060. {
  1061. MSGHANDLER_PROLOG
  1062. HDC dc = ::GetWindowDC(hwnd);
  1063.    thisPtr->DrawNonClientArea(dc);
  1064. ::ReleaseDC(hwnd, dc);
  1065. MSGHANDLER_EPILOG
  1066. }
  1067. // draw the window into the specified device context
  1068. LRESULT CBalloonHelp::OnPrint(HWND hwnd, WPARAM wParam, LPARAM lParam)
  1069. {
  1070. MSGHANDLER_PROLOG_BOOL
  1071.    if ( lParam & PRF_NONCLIENT  ) 
  1072.       thisPtr->DrawNonClientArea((HDC)wParam);
  1073. MSGHANDLER_EPILOG
  1074. return ::DefWindowProc(hwnd, WM_PRINT, wParam, lParam);
  1075. }
  1076. // draw the client area into the specified device context
  1077. LRESULT CBalloonHelp::OnPrintClient(HWND hwnd, WPARAM wParam, LPARAM lParam)
  1078. {  
  1079. MSGHANDLER_PROLOG_BOOL
  1080.    if ( lParam & PRF_ERASEBKGND ) 
  1081.       ::SendMessage(hwnd, WM_ERASEBKGND, wParam, lParam );
  1082.    if ( lParam & PRF_CLIENT ) 
  1083.       thisPtr->DrawClientArea((HDC)wParam);
  1084. MSGHANDLER_EPILOG
  1085.    return 0;
  1086. }
  1087. // Close button handler
  1088. void CBalloonHelp::OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
  1089. {
  1090. MSGHANDLER_PROLOG
  1091.    if (thisPtr->m_unOptions & unSHOW_CLOSE_BUTTON)
  1092.    {
  1093.       RECT rect;
  1094.       ::GetClientRect(hwnd, &rect);
  1095.       rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE);
  1096.       rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE);
  1097. POINT point = {x, y};
  1098.       if ( ::PtInRect(&rect, point) )
  1099.       {
  1100.          thisPtr->m_uCloseState |= DFCS_PUSHED;
  1101.          ::SetCapture(hwnd);
  1102.          CBalloonHelp::OnMouseMove(hwnd, x, y, keyFlags);
  1103.       }
  1104.    }
  1105. MSGHANDLER_EPILOG
  1106. }
  1107. // Close button handler,
  1108. // URL handler
  1109. void CBalloonHelp::OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags) 
  1110. {
  1111. MSGHANDLER_PROLOG
  1112.    if ( (thisPtr->m_unOptions & unSHOW_CLOSE_BUTTON) && (thisPtr->m_uCloseState & DFCS_PUSHED))
  1113.    {
  1114.       ReleaseCapture();
  1115.       thisPtr->m_uCloseState &= ~DFCS_PUSHED;
  1116.       RECT rect;
  1117.       ::GetClientRect(hwnd, &rect);
  1118.       rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE);
  1119.       rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE);
  1120. POINT point = {x, y};
  1121.       if ( ::PtInRect(&rect, point) )
  1122.          thisPtr->HideBalloon();
  1123.    }
  1124.    else if ( !thisPtr->m_strURL.IsEmpty() )
  1125.    {
  1126.       RECT rect;
  1127.       ::GetClientRect(hwnd, &rect);
  1128. POINT point = {x, y};
  1129.       if ( ::PtInRect(&rect, point) )
  1130.       {
  1131.          ::ShellExecute(NULL, NULL, thisPtr->m_strURL, NULL, NULL, SW_SHOWNORMAL);
  1132.          thisPtr->HideBalloon();
  1133.       }
  1134.    }
  1135. MSGHANDLER_EPILOG
  1136. }
  1137. //
  1138. // Ensure WM_MOUSEMOVE messages are sent for the entire window
  1139. //
  1140. UINT CBalloonHelp::OnNCHitTest(HWND hwnd, int x, int y)
  1141. {
  1142.    return HTCLIENT;
  1143. }
  1144. //
  1145. // do mouse tracking:
  1146. //   Tracking for close button;
  1147. //
  1148. void CBalloonHelp::OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
  1149. {
  1150. MSGHANDLER_PROLOG
  1151.    if (thisPtr->m_unOptions & unSHOW_CLOSE_BUTTON)
  1152.    {
  1153.       RECT rect;
  1154.       GetClientRect(hwnd, &rect);
  1155.       rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE);
  1156.       rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE);
  1157.       HDC dc = ::GetDC(hwnd);
  1158.       UINT uState = DFCS_CAPTIONCLOSE;
  1159.       BOOL bPushed = thisPtr->m_uCloseState&DFCS_PUSHED;
  1160.       thisPtr->m_uCloseState &= ~DFCS_PUSHED;
  1161. POINT point = {x, y};
  1162.       if ( ::PtInRect(&rect, point) )
  1163.       {
  1164.          uState |= DFCS_HOT;
  1165.          if ( bPushed )
  1166.             uState |= DFCS_PUSHED;
  1167.       }
  1168.       else
  1169.       {
  1170.          uState |= DFCS_FLAT;
  1171.       }
  1172.       if ( uState != thisPtr->m_uCloseState )
  1173.       {
  1174.          ::DrawFrameControl(dc, &rect, DFC_CAPTION, uState);
  1175.          thisPtr->m_uCloseState = uState;
  1176.       }
  1177.       if ( bPushed )
  1178.          thisPtr->m_uCloseState |= DFCS_PUSHED;
  1179. ::ReleaseDC(hwnd, dc);
  1180.    }
  1181. MSGHANDLER_EPILOG
  1182. }
  1183. // Ensures client area is the correct size relative to window size,
  1184. // presearves client contents if possible when moving.
  1185. UINT CBalloonHelp::OnNCCalcSize(HWND hwnd, BOOL fCalcValidRects, NCCALCSIZE_PARAMS * lpcsp)
  1186. {
  1187. MSGHANDLER_PROLOG_BOOL
  1188.    // nTIP_MARGIN pixel margin on all sides
  1189.    ::InflateRect(&lpcsp->rgrc[0], -nTIP_MARGIN,-nTIP_MARGIN);
  1190.    // nTIP_TAIL pixel "tail" on side closest to anchor
  1191.    switch ( thisPtr->GetBalloonQuadrant() )
  1192.    {
  1193.    case BQ_TOPRIGHT:
  1194.    case BQ_TOPLEFT:
  1195.       lpcsp->rgrc[0].top += nTIP_TAIL;
  1196.       break;
  1197.    case BQ_BOTTOMRIGHT:
  1198.    case BQ_BOTTOMLEFT:
  1199.       lpcsp->rgrc[0].bottom -= nTIP_TAIL;
  1200.       break;
  1201.    }
  1202.    // sanity: ensure rect does not have negative size
  1203.    if ( lpcsp->rgrc[0].right < lpcsp->rgrc[0].left )
  1204.       lpcsp->rgrc[0].right = lpcsp->rgrc[0].left;
  1205.    if ( lpcsp->rgrc[0].bottom < lpcsp->rgrc[0].top )
  1206.       lpcsp->rgrc[0].bottom = lpcsp->rgrc[0].top;
  1207.    if ( fCalcValidRects )
  1208.    {
  1209.       // determine if client position has changed relative to the window position
  1210.       // if so, don't bother presearving anything.
  1211.       if ( !::EqualRect(&lpcsp->rgrc[0], &lpcsp->rgrc[2]) )
  1212.       {
  1213.          ::SetRectEmpty(&lpcsp->rgrc[2]);
  1214.       }
  1215.    }
  1216. MSGHANDLER_EPILOG
  1217. return 0;
  1218. }
  1219. // handle kill timer
  1220. void CBalloonHelp::OnTimer(HWND hwnd, UINT id)
  1221. {
  1222. MSGHANDLER_PROLOG
  1223.    // really shouldn't be any other timers firing, but might as well make sure
  1224.    if ( id == ID_TIMER_CLOSE )
  1225.    {
  1226.       ::KillTimer(hwnd, thisPtr->m_unTimerClose);
  1227.       thisPtr->HideBalloon();
  1228.    }
  1229. MSGHANDLER_EPILOG
  1230. }
  1231. // Called as the window is being destroyed.  Completes destruction after removing keyboard hook.
  1232. void CBalloonHelp::OnDestroy(HWND hwnd)
  1233. {
  1234. MSGHANDLER_PROLOG
  1235.    // remove hooks
  1236.    thisPtr->RemoveMouseHook();
  1237.    thisPtr->RemoveKeyboardHook();
  1238.    thisPtr->RemoveCallWndRetHook();   
  1239. MSGHANDLER_EPILOG
  1240. }
  1241. // close the balloon, performing any set transition effect.
  1242. void CBalloonHelp::OnClose(HWND hwnd)
  1243. {
  1244. MSGHANDLER_PROLOG
  1245.    thisPtr->HideBalloon();
  1246. MSGHANDLER_EPILOG
  1247. }
  1248. // Called after window has been destroyed.  Destroys the object if option is set.
  1249. void CBalloonHelp::OnNCDestroy(HWND hwnd)
  1250. {   
  1251. MSGHANDLER_PROLOG
  1252.    // free object if requested
  1253.    // be careful with this one :D
  1254.    if ( thisPtr->m_unOptions & unDELETE_THIS_ON_CLOSE )
  1255.       delete thisPtr;
  1256. MSGHANDLER_EPILOG
  1257. }
  1258. // Keyboard hook: used to implement the unCLOSE_ON_KEYPRESS option
  1259. LRESULT CBalloonHelp::KeyboardHookProc( int code, WPARAM wParam, LPARAM lParam)
  1260. {
  1261.    // Skip if the key was released or if it's a repeat
  1262.    // Bit 31:  Specifies the transition state. The value is 0 if the key  
  1263.    //       is being pressed and 1 if it is being released (see MSDN).
  1264.    if ( code>=0 && !(lParam&0x80000000) && NULL != m_hWnd )
  1265.    {
  1266.       ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
  1267.    }
  1268.    return ::CallNextHookEx(m_hKeyboardHook, code, wParam, lParam);
  1269. }
  1270. // Mouse hook: used to implement un-obtrusive mouse tracking
  1271. LRESULT CBalloonHelp::MouseHookProc(int code, WPARAM wParam, LPARAM lParam)
  1272. {
  1273.    if (code>=0 && NULL != m_hWnd )
  1274.    {
  1275.       switch ( (UINT)wParam )
  1276.       {
  1277.       case WM_NCMOUSEMOVE:
  1278.       case WM_MOUSEMOVE:
  1279.          if ((m_unOptions & unCLOSE_ON_MOUSE_MOVE))
  1280.          {
  1281.             POINT pt;
  1282.             ::GetCursorPos(&pt);
  1283.             if ((abs(pt.x-m_ptMouseOrig.x) > m_nMouseMoveTolerance || abs(pt.y-m_ptMouseOrig.y) > m_nMouseMoveTolerance) )
  1284.                ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
  1285.          }
  1286.          break;
  1287.       case WM_NCLBUTTONDOWN:
  1288.       case WM_LBUTTONDOWN:
  1289.          if ((m_unOptions & unCLOSE_ON_LBUTTON_DOWN))
  1290.             ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
  1291.          break;
  1292.       case WM_NCMBUTTONDOWN:
  1293.       case WM_MBUTTONDOWN:
  1294.          if ((m_unOptions & unCLOSE_ON_MBUTTON_DOWN))
  1295.             ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
  1296.          break;
  1297.       case WM_NCRBUTTONDOWN:
  1298.       case WM_RBUTTONDOWN:
  1299.          if ((m_unOptions& unCLOSE_ON_RBUTTON_DOWN))
  1300.             ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
  1301.          break;
  1302.       case WM_NCLBUTTONUP:
  1303.       case WM_LBUTTONUP:
  1304.          if ((m_unOptions & unCLOSE_ON_LBUTTON_UP))
  1305.             ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
  1306.          break;
  1307.       case WM_NCMBUTTONUP:
  1308.       case WM_MBUTTONUP:
  1309.          if ((m_unOptions & unCLOSE_ON_MBUTTON_UP))
  1310.             ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
  1311.          break;
  1312.       case WM_NCRBUTTONUP:
  1313.       case WM_RBUTTONUP:
  1314.          if ((m_unOptions & unCLOSE_ON_RBUTTON_UP))
  1315.             ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
  1316.          break;
  1317.       }
  1318.    }
  1319.    return ::CallNextHookEx(m_hMouseHook, code, wParam, lParam);
  1320. }
  1321. // Window Return hook: used to implement window following
  1322. LRESULT CBalloonHelp::CallWndRetProc(int code, WPARAM wParam, LPARAM lParam)
  1323. {
  1324.    if (code>=0 && NULL != m_hWnd )
  1325.    {
  1326.       CWPRETSTRUCT* pcwpr = (CWPRETSTRUCT*)lParam;
  1327.       if ( WM_MOVE == pcwpr->message && pcwpr->hwnd == m_hwndAnchor )
  1328.          PositionWindow();
  1329.    }
  1330.    return ::CallNextHookEx(m_hCallWndRetHook, code, wParam, lParam);
  1331. }