www.pudn.com > clamwin-0.85.1-src.zip > BalloonHelp.cpp


/*----------------------------------------------------------------------------- 
# Name:        BalloonHelp.cpp 
# Product:     ClamWin Antivirus 
# 
# Licence:      
#   This program is free software; you can redistribute it and/or modify 
#   it under the terms of the GNU General Public License as published by 
#   the Free Software Foundation; either version 2 of the License, or 
#   (at your option) any later version. 
#  
#   This program is distributed in the hope that it will be useful, 
#   but WITHOUT ANY WARRANTY; without even the implied warranty of 
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
#   GNU General Public License for more details. 
#  
#   You should have received a copy of the GNU General Public License 
#   along with this program; if not, write to the Free Software 
#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
#----------------------------------------------------------------------------- 
 
# 11 May 2004 Alch - Converted the class to pure win32 api - no MFC 
*/ 
 
// ****************************************************************************** 
 
// BalloonHelp.cpp : implementation file 
// Copyright 2001-2002, Joshua Heyer 
//  You are free to use this code for whatever you want, provided you 
// give credit where credit is due.  (I seem to get a lot of questions 
// about that statement...  All i mean is, don't copy huge bits of code 
// and then claim you wrote it.  You don't have to put my name in an about 
// box or anything.  Though i'm not going to stop you if that's really what 
// you want :~) ) 
//  I'm providing this code in the hope that it is useful to someone, as i have 
// gotten much use out of other peoples code over the years. 
//  If you see value in it, make some improvements, etc., i would appreciate it  
// if you sent me some feedback. 
// 
// ****************************************************************************** 
 
#include "stdafx.h" 
#include "BalloonHelp.h" 
 
// allow multimonitor-aware code on Win95 systems 
// comment out the first line if you have already define it in another file 
// comment out both lines if you don't care about Win95 
//#define COMPILE_MULTIMON_STUBS 
//#include "multimon.h" 
 
 
// 
// constants that may not be defined if you don't have the latest SDK 
// (but i like to use them anyway) 
// 
 
#ifndef DFCS_HOT 
#define DFCS_HOT 0x1000 
#endif 
 
#ifndef AW_HIDE 
#define AW_HIDE 0x00010000 
#define AW_BLEND 0x00080000 
#endif 
 
#ifndef CS_DROPSHADOW 
#define CS_DROPSHADOW   0x00020000 
#endif 
 
#ifndef SPI_GETDROPSHADOW 
#define SPI_GETDROPSHADOW  0x1024 
#endif 
 
#ifndef SPI_GETTOOLTIPANIMATION 
#define SPI_GETTOOLTIPANIMATION 0x1016 
#endif 
 
#ifndef SPI_GETTOOLTIPFADE 
#define SPI_GETTOOLTIPFADE 0x1018 
#endif 
 
///////////////////////////////////////////////////////////////////////////// 
// CBalloonHelp 
 
// option constants (bits) 
const unsigned int   CBalloonHelp::unCLOSE_ON_LBUTTON_UP    = 0x0001; 
const unsigned int   CBalloonHelp::unCLOSE_ON_MBUTTON_UP    = 0x0002; 
const unsigned int   CBalloonHelp::unCLOSE_ON_RBUTTON_UP    = 0x0004; 
const unsigned int   CBalloonHelp::unCLOSE_ON_LBUTTON_DOWN  = 0x0008; 
const unsigned int   CBalloonHelp::unCLOSE_ON_MBUTTON_DOWN  = 0x0010; 
const unsigned int   CBalloonHelp::unCLOSE_ON_RBUTTON_DOWN  = 0x0020; 
const unsigned int   CBalloonHelp::unCLOSE_ON_MOUSE_MOVE    = 0x0040; 
const unsigned int   CBalloonHelp::unCLOSE_ON_KEYPRESS      = 0x0080; 
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; 
const unsigned int   CBalloonHelp::unDELAY_CLOSE            = 0x0100; 
const unsigned int   CBalloonHelp::unDELETE_THIS_ON_CLOSE   = 0x0200; 
const unsigned int   CBalloonHelp::unSHOW_CLOSE_BUTTON      = 0x0400; 
const unsigned int   CBalloonHelp::unSHOW_INNER_SHADOW      = 0x0800; 
const unsigned int   CBalloonHelp::unSHOW_TOPMOST           = 0x1000; 
const unsigned int   CBalloonHelp::unDISABLE_XP_SHADOW      = 0x2000; 
const unsigned int   CBalloonHelp::unDISABLE_FADEIN         = 0x4000; 
const unsigned int   CBalloonHelp::unDISABLE_FADEOUT        = 0x8000; 
const unsigned int   CBalloonHelp::unDISABLE_FADE           = CBalloonHelp::unDISABLE_FADEIN|CBalloonHelp::unDISABLE_FADEOUT; 
 
// layout constants (should prolly be configurable, but who's really gonna care?) 
const int            CBalloonHelp::nTIP_TAIL             = 20; 
const int            CBalloonHelp::nTIP_MARGIN           = 8; 
// class atom (why don't i do this the MFC way?  Drop shadows!) 
ATOM                 CBalloonHelp::s_ClassAtom           = 0; 
ATOM                 CBalloonHelp::s_ClassAtomShadowed   = 0; 
 
// Kill timer 
#define ID_TIMER_CLOSE  1 
 
extern HINSTANCE g_hInstance; 
 
// 
// The launchers 
// 
// 
// Show a help balloon on screen 
// Parameters: 
//    strTitle    |  Title of balloon 
//    unTitle     |  Title of balloon (id of string resource) 
//    strContent  |  Content of balloon 
//    unContent   |  Content of balloon (id of string resource) 
//    ptAnchor    |  point tail of balloon will be "anchor"ed to 
//    szIcon      |  One of: 
//                   IDI_APPLICATION 
//                   IDI_INFORMATION IDI_ASTERISK (same) 
//                   IDI_ERROR IDI_HAND (same) 
//                   IDI_EXCLAMATION IDI_WARNING (same) 
//                   IDI_QUESTION 
//                   IDI_WINLOGO 
//                   NULL (no icon) 
//    unIconID    |  ID of icon to display (loaded from resources) 
//    unOptions   |  One or more of: 
//                :     unCLOSE_ON_LBUTTON_UP   |  closes window on WM_LBUTTON_UP 
//                :     unCLOSE_ON_MBUTTON_UP   |  closes window on WM_MBUTTON_UP 
//                :     unCLOSE_ON_RBUTTON_UP   |  closes window on WM_RBUTTON_UP 
//                :     unCLOSE_ON_LBUTTON_DOWN |  closes window on WM_LBUTTON_DOWN 
//                :     unCLOSE_ON_MBUTTON_DOWN |  closes window on WM_MBUTTON_DOWN 
//                :     unCLOSE_ON_RBUTTON_DOWN |  closes window on WM_RBUTTON_DOWN 
//                :     unCLOSE_ON_MOUSE_MOVE   |  closes window when user moves mouse past threshhold 
//                :     unCLOSE_ON_KEYPRESS     |  closes window on the next keypress message sent to this thread. 
//                :     unCLOSE_ON_ANYTHING     |  all of the above. 
//                :     unDELAY_CLOSE           |  when a user action triggers the close, begins timer.  closes when timer expires. 
//                :     unSHOW_CLOSE_BUTTON     |  shows close button in upper right 
//                :     unSHOW_INNER_SHADOW     |  draw inner shadow in balloon 
//                :     unSHOW_TOPMOST          |  place balloon above all other windows 
//                :     unDISABLE_XP_SHADOW     |  disable Windows XP's drop-shadow effect (overrides system and user settings) 
//                :     unDISABLE_FADE          |  disable the fade-in/fade-out effects (overrides system and user settings) 
//                :     unDISABLE_FADEIN        |  disable the fade-in effect 
//                :     unDISABLE_FADEOUT       |  disable the fade-out effect 
//    pParentWnd  |  Parent window.  If NULL will be set to AfxGetMainWnd(), and anchor to screen 
//    strURL      |  If not empty, when the balloon is clicked ShellExecute() will 
//                |  be called, with strURL passed in. 
//    unTimeout   |  If not 0, balloon will automatically close after unTimeout milliseconds. 
// 
void CBalloonHelp::LaunchBalloon(const CStdString& strTitle, const CStdString& strContent,  
               POINT& ptAnchor,  
               LPCTSTR szIcon /*= IDI_EXCLAMATION*/, 
               unsigned int unOptions /*= unSHOW_CLOSE_BUTTON*/, 
               HWND hParentWnd /*= NULL*/, 
               const CStdString strURL /*= ""*/, 
               unsigned int unTimeout /*= 10000*/) 
{ 
   CBalloonHelp* pbh = new CBalloonHelp; 
   if ( NULL != szIcon ) 
   { 
      // Note: Since i'm scaling the icon anyway, i'll allow it to become larger 
      // than the standard small icon if the close button is. 
      SIZE sizeIcon = {max(::GetSystemMetrics(SM_CXSIZE), ::GetSystemMetrics(SM_CXSMICON)), max(::GetSystemMetrics(SM_CYSIZE), ::GetSystemMetrics(SM_CYSMICON))}; 
      HICON hIcon = (HICON)::LoadImage(NULL, szIcon, IMAGE_ICON, sizeIcon.cx, sizeIcon.cy, LR_SHARED); 
      if (NULL != hIcon) 
         pbh->SetIconScaled(hIcon, sizeIcon.cx, sizeIcon.cy); 
   }	 
   pbh->Create(strTitle, strContent, ptAnchor, unOptions|unDELETE_THIS_ON_CLOSE,  
               hParentWnd, strURL, unTimeout, NULL); 
} 
 
 
// 
//  The class 
// 
 
CBalloonHelp::CBalloonHelp() 
:  CWnd(g_hInstance), 
	m_fnAnimateWindow(NULL), 
   m_unOptions(0), 
   m_unTimeout(0), 
   m_unTimerClose(0), 
   m_strURL(""),    
	m_hilIcon(NULL), 
   m_hwndAnchor(NULL), 
	m_hrgnComplete(NULL),    
   m_strContent(""), 
   m_nMouseMoveTolerance(3),     // later retrieved from system    
   m_uCloseState(0), 
   m_hTitleFont(NULL), 
   m_hContentFont(NULL), 
   m_crForeground(::GetSysColor(COLOR_INFOTEXT)), 
   m_crBackground(::GetSysColor(COLOR_INFOBK)), 
   m_hKeyboardHook(NULL), 
   m_hMouseHook(NULL), 
   m_hCallWndRetHook(NULL) 
{ 
	m_ptAnchor.x = m_ptAnchor.y = 0; 
	m_ptMouseOrig.x = m_ptMouseOrig.y = 0; 
	m_screenRect.top = m_screenRect.left = m_screenRect.bottom = m_screenRect.right = 0; 
 
   // retrieve window animation API if available 
   HMODULE hUser32 = GetModuleHandle(_T("USER32.DLL")); 
   // can't imagine why that would fail, but might as well *look* safe...  ;~) 
   if ( NULL != hUser32 ) 
      m_fnAnimateWindow = (FN_ANIMATE_WINDOW)GetProcAddress(hUser32, _T("AnimateWindow")); 
   else 
      m_fnAnimateWindow = NULL; 
 
   // get system tolerance values 
   int nTol = 0; 
   if ( ::SystemParametersInfo(SPI_GETMOUSEHOVERWIDTH, 0, &nTol, 0) && nTol > 0 ) 
      m_nMouseMoveTolerance = nTol; 
 
   // setup hook procedures 
   BHKeybHookThunk::InitThunk((TMFP)KeyboardHookProc, this); 
   BHMouseHookThunk::InitThunk((TMFP)MouseHookProc, this); 
   BHCallWndRetHookThunk::InitThunk((TMFP)CallWndRetProc, this); 
} 
 
CBalloonHelp::~CBalloonHelp() 
{ 
   if ( NULL != m_hTitleFont ) 
      ::DeleteObject(m_hTitleFont); 
   m_hTitleFont = NULL; 
   if ( NULL != m_hContentFont ) 
      ::DeleteObject(m_hContentFont); 
   m_hContentFont = NULL; 
	if ( NULL != m_hrgnComplete ) 
      ::DeleteObject(m_hrgnComplete); 
   m_hrgnComplete = NULL; 
	if ( NULL != m_hilIcon ) 
		::ImageList_Destroy(m_hilIcon); 
	m_hilIcon = NULL; 
} 
 
 
// Sets the font used for drawing the balloon title.  Deleted by balloon, do not use CFont* after passing to this function. 
void CBalloonHelp::SetTitleFont(HFONT hFont) 
{ 
   if ( NULL != m_hTitleFont ) 
      ::DeleteObject(m_hTitleFont); 
   m_hTitleFont = hFont; 
   // if already visible, resize & move 
   if ( NULL != m_hWnd ) 
      PositionWindow(); 
} 
 
// Sets the font used for drawing the balloon content.  Deleted by balloon, do not use CFont* after passing to this function. 
void CBalloonHelp::SetContentFont(HFONT hFont) 
{ 
   if ( NULL != m_hContentFont ) 
      ::DeleteObject(m_hContentFont); 
   m_hContentFont = hFont; 
   // if already visible, resize & move 
   if ( NULL != m_hWnd ) 
      PositionWindow(); 
} 
 
// Sets the icon displayed in the top left of the balloon (pass NULL to hide icon) 
void CBalloonHelp::SetIcon(HICON hIcon) 
{ 
   if ( NULL != m_hilIcon ) 
		::ImageList_Destroy(m_hilIcon); 
	 
   ICONINFO iconinfo; 
   if ( NULL != hIcon && ::GetIconInfo(hIcon, &iconinfo) ) 
   { 
      SetIcon(iconinfo.hbmColor, iconinfo.hbmMask); 
      ::DeleteObject(iconinfo.hbmColor); 
      ::DeleteObject(iconinfo.hbmMask); 
   } 
   // if already visible, resize & move (icon size may have changed) 
   if ( NULL != m_hWnd ) 
      PositionWindow(); 
} 
 
// Sets the icon displayed in the top left of the balloon (pass NULL to hide icon) 
void CBalloonHelp::SetIconScaled(HICON hIcon, int cx, int cy) 
{ 
   // i now have two device contexts and two bitmaps. 
   // i will select a bitmap in each device context, 
   // draw the icon into the first one, 
   // scale it into the second one, 
   // and set the second one as the balloon icon. 
   // This is a rather long process to get a scaled icon, 
   // but ensures maximum compatibility between different 
   // versions of Windows, while producing the best possible 
   // results on each version (quite good in WinNT and better, sorta ok in Win9x). 
   ICONINFO iconinfo; 
   if ( NULL != hIcon && ::GetIconInfo(hIcon, &iconinfo) ) 
   { 
      BITMAP bm; 
      if (::GetObject(iconinfo.hbmColor, sizeof(bm),(LPVOID)&bm)) 
      { 
         HDC dc; 
         HDC dcTmp1; 
         HDC dcTmp2; 
         HBITMAP bmpIcon; 
         HBITMAP bmpIconScaled; 
         dc = ::GetDC(NULL); 
         dcTmp1 = ::CreateCompatibleDC(dc); 
         dcTmp2 = ::CreateCompatibleDC(dc); 
         bmpIcon = ::CreateCompatibleBitmap(dc, bm.bmWidth, bm.bmHeight); 
         bmpIconScaled = CreateCompatibleBitmap(dc, cx, cy); 
         ::ReleaseDC(NULL, dc); 
 
         HBITMAP pbmpOld1 = (HBITMAP)SelectObject(dcTmp1, bmpIcon); 
         HBITMAP pbmpOld2 = (HBITMAP)SelectObject(dcTmp2, bmpIconScaled); 
			HBRUSH hBrush = ::CreateSolidBrush(m_crBackground); 
			RECT rc = {0,0,bm.bmWidth,bm.bmHeight}; 
			::FillRect(dcTmp1,&rc,hBrush); 
			::DeleteObject(hBrush); 
         ::DrawIconEx(dcTmp1, 0,0,hIcon,bm.bmWidth,bm.bmHeight,0,NULL,DI_NORMAL); 
         ::SetStretchBltMode(dcTmp2, HALFTONE); 
         ::StretchBlt(dcTmp2, 0,0,cx,cy,dcTmp1, 0,0,bm.bmWidth,bm.bmHeight,SRCCOPY); 
         ::SelectObject(dcTmp1, pbmpOld1); 
         ::SelectObject(dcTmp2, pbmpOld2); 
         SetIcon(bmpIconScaled, m_crBackground);			 
         ::ReleaseDC(m_hWnd, dcTmp1); 
         ::ReleaseDC(m_hWnd, dcTmp2); 
			::DeleteObject(dcTmp1); 
			::DeleteObject(dcTmp2); 
         ::DeleteObject(bmpIcon); 
         ::DeleteObject(bmpIconScaled); 
      } 
      ::DeleteObject(iconinfo.hbmColor); 
      ::DeleteObject(iconinfo.hbmMask); 
   } 
} 
 
// Sets the icon displayed in the top left of the balloon (pass NULL hBitmap to hide icon) 
void CBalloonHelp::SetIcon(HBITMAP hBitmap, COLORREF crMask) 
{ 
   if ( NULL != m_hilIcon ) 
		::ImageList_Destroy(m_hilIcon);	 
 
   if ( NULL != hBitmap ) 
   { 
      BITMAP bm; 
      if (::GetObject(hBitmap, sizeof(bm),(LPVOID)&bm)) 
      { 
         m_hilIcon = ::ImageList_Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24|ILC_MASK,1,0); 
         ::ImageList_AddMasked(m_hilIcon, hBitmap, crMask); 
      } 
   } 
   // if already visible, resize & move (icon size may have changed) 
   if ( NULL != m_hWnd ) 
      PositionWindow(); 
} 
 
// Sets the icon displayed in the top left of the balloon 
void CBalloonHelp::SetIcon(HBITMAP hBitmap, HBITMAP hMask) 
{ 
    if ( NULL != m_hilIcon ) 
		::ImageList_Destroy(m_hilIcon); 
 
   ASSERT(NULL != hBitmap); 
   ASSERT(NULL != hMask); 
 
   BITMAP bm; 
   if (::GetObject(hBitmap, sizeof(bm),(LPVOID)&bm)) 
   { 
      m_hilIcon = ::ImageList_Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24|ILC_MASK,1,0); 
      ::ImageList_Add(m_hilIcon, hBitmap, hMask); 
   } 
   // if already visible, resize & move (icon size may have changed) 
   if ( NULL != m_hWnd ) 
      PositionWindow(); 
} 
 
// Set icon displayed in the top left of the balloon to image # nIconIndex from pImageList 
void CBalloonHelp::SetIcon(HIMAGELIST hImageList, int nIconIndex) 
{ 
   // sanity checks 
   ASSERT(hImageList); 
   ASSERT(nIconIndex >= 0 && nIconIndex < ::ImageList_GetImageCount(hImageList)); 
 
   HICON hIcon = NULL; 
   if ( NULL != hImageList && nIconIndex >= 0 && nIconIndex < ::ImageList_GetImageCount(hImageList) ) 
       ::ImageList_ExtractIcon(&hIcon, hImageList, nIconIndex); 
   SetIcon(hIcon); 
   if ( NULL != hIcon ) 
      ::DestroyIcon(hIcon); 
   // if already visible, resize & move (icon size may have changed) 
   if ( NULL != m_hWnd ) 
      PositionWindow(); 
} 
 
// Sets the URL to be opened when balloon is clicked.  Pass "" to disable. 
void CBalloonHelp::SetURL(const CStdString& strURL) 
{ 
   m_strURL = strURL; 
} 
 
// Sets the number of milliseconds the balloon can remain open.  Set to 0 to disable timeout. 
void CBalloonHelp::SetTimeout(unsigned int unTimeout) 
{ 
   m_unTimeout = unTimeout; 
   // if timer is already set, reset. 
   if ( NULL != m_hWnd ) 
   { 
      if ( m_unTimeout > 0 ) 
      { 
         m_unTimerClose = ::SetTimer(m_hWnd, ID_TIMER_CLOSE, m_unTimeout, NULL); 
      } 
      else 
      { 
         ::KillTimer(m_hWnd, m_unTimerClose); 
      } 
   } 
} 
 
// Sets the point to which the balloon is "anchored" 
void CBalloonHelp::SetAnchorPoint(POINT& ptAnchor, HWND hWndAnchor /*= NULL*/) 
{ 
   m_ptAnchor = ptAnchor; 
   m_hwndAnchor = hWndAnchor; 
 
   // if we're anchored to a window, set hook 
   if ( NULL != m_hwndAnchor ) 
      SetCallWndRetHook(); 
   else 
      RemoveCallWndRetHook(); 
 
   // if already visible, move 
   if ( NULL != m_hWnd ) 
   { 
      // reposition 
      PositionWindow(); 
   } 
} 
 
// Sets the title of the balloon 
void CBalloonHelp::SetTitle(const CStdString& strTitle) 
{ 
   ::SetWindowText(m_hWnd, strTitle); 
   // if already visible, resize & move 
   if ( NULL != m_hWnd ) 
      PositionWindow(); 
} 
 
// Sets the content of the balloon (plain text only) 
void CBalloonHelp::SetContent(const CStdString& strContent) 
{ 
   m_strContent = strContent; 
   // if already visible, resize & move 
   if ( NULL != m_hWnd ) 
      PositionWindow(); 
} 
 
// Sets the forground (text and border) color of the balloon 
void CBalloonHelp::SetForegroundColor(COLORREF crForeground) 
{ 
   m_crForeground = crForeground; 
   // repaint if visible 
   if ( NULL != m_hWnd ) 
      ::InvalidateRect(m_hWnd, NULL, FALSE); 
} 
 
// Sets the background color of the balloon 
void CBalloonHelp::SetBackgroundColor(COLORREF crBackground) 
{ 
   m_crBackground = crBackground; 
   // repaint if visible 
   if ( NULL != m_hWnd ) 
      ::InvalidateRect(m_hWnd, NULL, FALSE); 
} 
 
// Sets the distance the mouse must move before the balloon closes when the unCLOSE_ON_MOUSE_MOVE option is set. 
void CBalloonHelp::SetMouseMoveTolerance(int nTolerance) 
{ 
   m_nMouseMoveTolerance = nTolerance; 
} 
 
// 
// creates a new balloon window 
// Parameters: 
//    strTitle    |  Title of balloon 
//    strContent  |  Content of balloon 
//    ptAnchor    |  point tail of balloon will be "anchor"ed to 
//    unOptions   |  One or more of: 
//                :     unCLOSE_ON_LBUTTON_UP   |  closes window on WM_LBUTTON_UP 
//                :     unCLOSE_ON_MBUTTON_UP   |  closes window on WM_MBUTTON_UP 
//                :     unCLOSE_ON_RBUTTON_UP   |  closes window on WM_RBUTTON_UP 
//                :     unCLOSE_ON_LBUTTON_DOWN |  closes window on WM_LBUTTON_DOWN 
//                :     unCLOSE_ON_MBUTTON_DOWN |  closes window on WM_MBUTTON_DOWN 
//                :     unCLOSE_ON_RBUTTON_DOWN |  closes window on WM_RBUTTON_DOWN 
//                :     unCLOSE_ON_MOUSE_MOVE   |  closes window when user moves mouse past threshhold 
//                :     unCLOSE_ON_KEYPRESS     |  closes window on the next keypress message sent to this thread. 
//                :     unCLOSE_ON_ANYTHING     |  all of the above. 
//                :     unDELAY_CLOSE           |  when a user action triggers the close, begins timer.  closes when timer expires. 
//                :     unDELETE_THIS_ON_CLOSE  |  deletes object when window is closed.  Used by LaunchBalloon(), use with care 
//                :     unSHOW_CLOSE_BUTTON     |  shows close button in upper right 
//                :     unSHOW_INNER_SHADOW     |  draw inner shadow in balloon 
//                :     unSHOW_TOPMOST          |  place balloon above all other windows 
//                :     unDISABLE_XP_SHADOW     |  disable Windows XP's drop-shadow effect (overrides system and user settings) 
//                :     unDISABLE_FADE          |  disable the fade-in/fade-out effects (overrides system and user settings) 
//                :     unDISABLE_FADEIN        |  disable the fade-in effect 
//                :     unDISABLE_FADEOUT       |  disable the fade-out effect 
//    pParentWnd  |  Parent window.  If NULL will be set to AfxGetMainWnd() and anchor to screen 
//    strURL      |  If not empty, when the balloon is clicked ShellExecute() will 
//                |  be called, with strURL passed in. 
//    unTimeout   |  If not 0, balloon will automatically close after unTimeout milliseconds. 
//    hIcon       |  If not NULL, the icon indicated by hIcon will be displayed at top-left of the balloon. 
// 
// Returns: 
//    TRUE if successful, else FALSE 
// 
BOOL CBalloonHelp::Create(const CStdString& strTitle, const CStdString& strContent,  
               POINT& ptAnchor, unsigned int unOptions, 
               HWND hParentWnd /*=NULL*/, 
               const CStdString strURL /*= ""*/, 
               unsigned int unTimeout /*= 0*/, 
               HICON hIcon /*= NULL*/) 
{ 
   m_strContent   = strContent; 
   SetAnchorPoint(ptAnchor, hParentWnd); 
   m_unOptions    = unOptions; 
   m_strURL       = strURL; 
   m_unTimeout    = unTimeout; 
 
   if ( NULL != hIcon ) 
      SetIcon(hIcon); 
    
    
   // if no fonts set, use defaults 
   if ( NULL == m_hContentFont ) 
   { 
      m_hContentFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT); 
      if ( NULL == m_hContentFont ) 
         return FALSE; 
   } 
 
   // title font defaults to bold version of content font 
   if ( NULL == m_hTitleFont ) 
   {       
      LOGFONT LogFont; 
      m_hTitleFont = (HFONT)::GetObject(m_hContentFont, sizeof(LOGFONT), &LogFont); 
      LogFont.lfWeight = FW_BOLD; 
		m_hTitleFont = ::CreateFontIndirect(&LogFont); 
      if ( NULL == m_hTitleFont ) 
         return FALSE; 
   } 
 
   ATOM wndClass = GetClassAtom(!(m_unOptions&unDISABLE_XP_SHADOW)); 
   if ( 0 == wndClass )  // couldn't register class 
      return FALSE; 
 
   // check system settings: if fade effects are disabled or unavailable, disable here too 
   BOOL bFade = FALSE; 
   ::SystemParametersInfo(SPI_GETTOOLTIPANIMATION, 0, &bFade, 0); 
   if (bFade) 
      ::SystemParametersInfo(SPI_GETTOOLTIPFADE, 0, &bFade, 0); 
   if (!bFade || NULL == m_fnAnimateWindow) 
      m_unOptions |= unDISABLE_FADE; 
 
   // create invisible at arbitrary position; then position, set region, and finally show 
 
   // the idea with WS_EX_TOOLWINDOW is, you can't switch to this using alt+tab 
   DWORD dwExStyle = WS_EX_TOOLWINDOW; 
   if ( m_unOptions&unSHOW_TOPMOST )      // make topmost, if requested 
      dwExStyle |= WS_EX_TOPMOST; 
	RECT rc = {0,0,10,10}; 
   if ( !CreateEx(dwExStyle, (LPCTSTR)wndClass, strTitle, WS_POPUP, rc, hParentWnd, 0, NULL) ) 
      return FALSE; 
   PositionWindow(); 
 
   if ( (m_unOptions&unCLOSE_ON_MOUSE_MOVE) 
      ||(m_unOptions&unCLOSE_ON_LBUTTON_UP) 
      ||(m_unOptions&unCLOSE_ON_LBUTTON_DOWN) 
      ||(m_unOptions&unCLOSE_ON_MBUTTON_UP) 
      ||(m_unOptions&unCLOSE_ON_MBUTTON_DOWN) 
      ||(m_unOptions&unCLOSE_ON_RBUTTON_UP) 
      ||(m_unOptions&unCLOSE_ON_RBUTTON_DOWN) ) 
   { 
      ::GetCursorPos(&m_ptMouseOrig); 
      SetMouseHook(); 
   } 
 
   // these need to take effect even if the window receiving them 
   // is not owned by this process.  So, if this process does not 
   // already have the mouse captured, capture it! 
   if ( (m_unOptions&unCLOSE_ON_LBUTTON_UP) 
      ||(m_unOptions&unCLOSE_ON_MBUTTON_UP) 
      ||(m_unOptions&unCLOSE_ON_RBUTTON_UP) ) 
   { 
      // no, i don't particularly need or want to deal with a situation 
      // where a balloon is being created and another program has captured 
      // the mouse.  If you need it, it shouldn't be too hard, just do it here. 
      if ( NULL == ::GetCapture() ) 
         ::SetCapture(m_hWnd); 
   } 
 
   if ( m_unOptions&unCLOSE_ON_KEYPRESS ) 
      SetKeyboardHook(); 
 
   ShowBalloon(); 
   return TRUE; 
} 
 
// calculate anchor position (adjust for client coordinates if used) 
POINT CBalloonHelp::GetAnchorPoint() 
{ 
   POINT ptAnchor = m_ptAnchor; 
   // assume if window was given, point is in client coords 
   if ( NULL != m_hwndAnchor ) 
      ::ClientToScreen(m_hwndAnchor, &ptAnchor); 
   return ptAnchor; 
} 
 
// determine bounds of screen anchor is on (Multi-Monitor compatibility) 
void CBalloonHelp::GetAnchorScreenBounds(RECT& rect) 
{ 
   if ( ::IsRectEmpty(&rect) ) 
   {      
      // get the nearest monitor to the anchor 
      HMONITOR hMonitor = MonitorFromPoint(GetAnchorPoint(), MONITOR_DEFAULTTONEAREST); 
 
      // get the monitor bounds 
      MONITORINFO mi; 
      mi.cbSize = sizeof(mi); 
      GetMonitorInfo(hMonitor, &mi); 
 
      // work area (area not obscured by task bar, etc.) 
      m_screenRect = mi.rcWork; 
   } 
   rect = m_screenRect; 
} 
 
// calculates the area of the screen the balloon falls into 
// this determins which direction the tail points 
CBalloonHelp::BALLOON_QUADRANT CBalloonHelp::GetBalloonQuadrant() 
{ 
   RECT rectDesktop; 
   GetAnchorScreenBounds(rectDesktop); 
   POINT ptAnchor = GetAnchorPoint(); 
    
   if ( ptAnchor.y < rectDesktop.top + (rectDesktop.bottom - rectDesktop.top)/2 ) 
   { 
      if ( ptAnchor.x < rectDesktop.left + (rectDesktop.right - rectDesktop.left)/2 ) 
      { 
         return BQ_TOPLEFT; 
      } 
      else 
      { 
         return BQ_TOPRIGHT; 
      } 
   } 
   else 
   { 
      if ( ptAnchor.x < rectDesktop.left + (rectDesktop.right - rectDesktop.left)/2 ) 
      { 
         return BQ_BOTTOMLEFT; 
      } 
      else 
      { 
         return BQ_BOTTOMRIGHT; 
      } 
   } 
 
   // unreachable 
} 
 
// Draw the non-client area 
void CBalloonHelp::DrawNonClientArea(HDC hDC) 
{ 
   RECT rect; 
   ::GetWindowRect(m_hWnd, &rect); 
	POINT pt = {rect.left, rect.top}; 
   ::ScreenToClient(m_hWnd, &pt); 
	rect.left = pt.x; rect.top = pt.y; 
	pt.x = rect.right; pt.y=rect.bottom; 
   ::ScreenToClient(m_hWnd, &pt); 
	rect.right = pt.x; rect.bottom = pt.y; 
   RECT rectClient; 
   ::GetClientRect(m_hWnd, &rectClient); 
   ::OffsetRect(&rectClient, -rect.left, -rect.top); 
   ::OffsetRect(&rect, -rect.left, -rect.top); 
   ::ExcludeClipRect(hDC, rectClient.left, rectClient.top, rectClient.right, rectClient.bottom); 
	HBRUSH hBrush = ::CreateSolidBrush(m_crBackground); 
   ::FillRect(hDC, &rect, hBrush); 
	::DeleteObject(hBrush); 
	::SelectClipRgn(hDC, NULL); 
 
   ASSERT(NULL != m_hrgnComplete); 
   HBRUSH  brushFg = ::CreateSolidBrush(m_crForeground); 
   if ( m_unOptions & unSHOW_INNER_SHADOW ) 
   { 
      HBRUSH    brushHL; 
      // slightly lighter color 
      int red = 170 + GetRValue(m_crBackground)/3; 
      int green = 170 + GetGValue(m_crBackground)/3; 
      int blue = 170 + GetBValue(m_crBackground)/3; 
      brushHL = ::CreateSolidBrush(RGB(red,green,blue)); 
      ::OffsetRgn(m_hrgnComplete, 1,1); 
      ::FrameRgn(hDC, m_hrgnComplete, brushHL, 2, 2); 
      // slightly darker color 
      red = GetRValue(m_crForeground)/3 + GetRValue(m_crBackground)/3*2; 
      green = GetGValue(m_crForeground)/3 + GetGValue(m_crBackground)/3*2; 
      blue = GetBValue(m_crForeground)/3 + GetBValue(m_crBackground)/3*2; 
      ::DeleteObject(brushHL); 
      ::OffsetRgn(m_hrgnComplete, -2,-2); 
      brushHL = ::CreateSolidBrush(RGB(red,green,blue)); 
      ::FrameRgn(hDC, m_hrgnComplete, brushHL, 2, 2); 
      ::OffsetRgn(m_hrgnComplete, 1,1); 
		::DeleteObject(brushHL); 
   }	 
   // outline 
   FrameRgn(hDC, m_hrgnComplete, brushFg, 1, 1); 
	::DeleteObject(brushFg); 
}	 
 
// Draw the client area 
void CBalloonHelp::DrawClientArea(HDC hDC) 
{ 
   SIZE sizeHeader = DrawHeader(hDC); 
   DrawContent(hDC, sizeHeader.cy+nTIP_MARGIN); 
} 
 
// Calculate the dimensions and draw the balloon header 
SIZE CBalloonHelp::DrawHeader(HDC hDC, bool bDraw) 
{ 
   SIZE sizeHdr = {0,0};	 
   RECT rectClient; 
   ::GetClientRect(m_hWnd, &rectClient);   // use this for positioning when drawing 
                                 // else if content is wider than title, centering wouldn't work 
 
   // calc & draw icon 
   if ( NULL != m_hilIcon) 
   { 
      int x = 0; 
      int y = 0; 
      ImageList_GetIconSize(m_hilIcon, &x, &y); 
      sizeHdr.cx += x; 
      sizeHdr.cy = max(sizeHdr.cy, y); 
      ImageList_SetBkColor(m_hilIcon, m_crBackground); 
      if (bDraw) 
         ImageList_Draw(m_hilIcon, 0, hDC, 0, 0, ILD_NORMAL);//ILD_TRANSPARENT); 
      rectClient.left += x; 
   } 
 
   // calc & draw close button 
   if ( m_unOptions & unSHOW_CLOSE_BUTTON ) 
   { 
      int nBtnWidth = ::GetSystemMetrics(SM_CXSIZE); 
      // if something is already in the header (icon) leave space 
      if ( sizeHdr.cx > 0 ) 
         sizeHdr.cx += nTIP_MARGIN; 
      sizeHdr.cx += nBtnWidth; 
      sizeHdr.cy = max(sizeHdr.cy, ::GetSystemMetrics(SM_CYSIZE)); 
      if (bDraw) 
		{ 
			RECT rc = {rectClient.right-nBtnWidth,0,rectClient.right,::GetSystemMetrics(SM_CYSIZE)}; 
         ::DrawFrameControl(hDC, &rc, DFC_CAPTION, DFCS_CAPTIONCLOSE|DFCS_FLAT); 
		} 
      rectClient.right -= nBtnWidth; 
   } 
 
   // calc title size 
	CStdString strTitle; 
   ::GetWindowText(m_hWnd, strTitle.GetBuffer(MAX_PATH), MAX_PATH); 
	strTitle.ReleaseBuffer(); 
   if ( !strTitle.IsEmpty() ) 
   { 
      HFONT hOldFont = (HFONT) SelectObject(hDC, m_hTitleFont); 
 
      // if something is already in the header (icon or close button) leave space 
      if ( sizeHdr.cx > 0 )  
         sizeHdr.cx += nTIP_MARGIN; 
      RECT rectTitle = {0,0,0,0}; 
      ::DrawText(hDC, strTitle, strTitle.GetLength(), &rectTitle, DT_CALCRECT | DT_NOPREFIX | DT_EXPANDTABS | DT_SINGLELINE); 
      sizeHdr.cx += rectTitle.right - rectTitle.left ; 
      sizeHdr.cy = max(sizeHdr.cy, rectTitle.bottom - rectTitle.top); 
 
      // draw title 
      if ( bDraw ) 
      { 
         ::SetBkMode(hDC, TRANSPARENT); 
         ::SetTextColor(hDC, m_crForeground); 
         ::DrawText(hDC, strTitle, strTitle.GetLength(), &rectClient, DT_CENTER | DT_NOPREFIX  | DT_EXPANDTABS | DT_SINGLELINE); 
      } 
 
      // cleanup 
      SelectObject(hDC, hOldFont); 
   } 
 
   return sizeHdr; 
} 
 
// Calculate the dimensions and draw the balloon contents 
SIZE CBalloonHelp::DrawContent(HDC hDC, int nTop, bool bDraw) 
{		 
	RECT rectContent; 
   GetAnchorScreenBounds(rectContent); 
   ::OffsetRect(&rectContent, -rectContent.left, -rectContent.top); 
   rectContent.top = nTop; 
 
   // limit to half screen width 
   rectContent.right -= (rectContent.right - rectContent.left)/2; 
 
   // calc size 
   HFONT hOldFont = (HFONT)SelectObject(hDC,m_hContentFont); 
   if ( !m_strContent.IsEmpty() ) 
      ::DrawText(hDC, m_strContent, m_strContent.GetLength(), &rectContent, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EXPANDTABS | DT_WORDBREAK); 
   else 
      ::SetRectEmpty(&rectContent);   // don't want to leave half the screen for empty strings ;) 
    
   // draw 
   if (bDraw) 
   { 
      ::SetBkMode(hDC, TRANSPARENT); 
      ::SetTextColor(hDC, m_crForeground); 
      ::DrawText(hDC, m_strContent, m_strContent.GetLength(), &rectContent, DT_LEFT | DT_NOPREFIX | DT_WORDBREAK | DT_EXPANDTABS); 
   } 
 
   // cleanup 
   ::SelectObject(hDC, hOldFont); 
	SIZE ret = {rectContent.right - rectContent.left, rectContent.bottom - rectContent.top};  
   return ret; 
} 
 
// calculates the client size necessary based on title and content 
SIZE CBalloonHelp::CalcClientSize() 
{ 
   ASSERT(NULL != m_hWnd); 
   HDC dc = ::GetWindowDC(m_hWnd); 
 
   SIZE sizeHeader = CalcHeaderSize(dc); 
   SIZE sizeContent = CalcContentSize(dc); 
	::ReleaseDC(m_hWnd, dc); 
	SIZE ret = {max(sizeHeader.cx,sizeContent.cx), sizeHeader.cy + nTIP_MARGIN + sizeContent.cy}; 
   return ret; 
} 
 
// calculates the size for the entire window based on content size 
SIZE CBalloonHelp::CalcWindowSize() 
{ 
   SIZE size = CalcClientSize(); 
   size.cx += nTIP_MARGIN*2; 
   size.cy += nTIP_TAIL+nTIP_MARGIN*2; 
   //size.cx = max(size.cx, nTIP_MARGIN*2+nTIP_TAIL*4); 
   return size; 
} 
 
 
// this routine calculates the size and position of the window relative 
// to it's anchor point, and moves the window accordingly.  The region is also 
// created and set here. 
void CBalloonHelp::PositionWindow() 
{ 
   SIZE sizeWnd = CalcWindowSize(); 
 
   POINT ptTail[3]; 
   POINT ptTopLeft = {0,0}; 
   POINT ptBottomRight = {sizeWnd.cx, sizeWnd.cy}; 
 
   // force recalculation of desktop 
   ::SetRectEmpty(&m_screenRect); 
 
   switch (GetBalloonQuadrant()) 
   { 
   case BQ_TOPLEFT: 
      ptTopLeft.y = nTIP_TAIL; 
      ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4 + nTIP_TAIL; 
      ptTail[0].y = nTIP_TAIL+1; 
      ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4; 
      ptTail[2].y = ptTail[0].y; 
      ptTail[1].x = ptTail[2].x; 
      ptTail[1].y = 1; 
      break; 
   case BQ_TOPRIGHT: 
      ptTopLeft.y = nTIP_TAIL; 
      ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4*3; 
      ptTail[0].y = nTIP_TAIL+1; 
      ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4*3 + nTIP_TAIL; 
      ptTail[2].y = ptTail[0].y; 
      ptTail[1].x = ptTail[2].x; 
      ptTail[1].y = 1; 
      break; 
   case BQ_BOTTOMLEFT: 
      ptBottomRight.y = sizeWnd.cy-nTIP_TAIL; 
      ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4 + nTIP_TAIL; 
      ptTail[0].y = sizeWnd.cy-nTIP_TAIL-2; 
      ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4; 
      ptTail[2].y = ptTail[0].y; 
      ptTail[1].x = ptTail[2].x; 
      ptTail[1].y = sizeWnd.cy-2; 
      break; 
   case BQ_BOTTOMRIGHT: 
      ptBottomRight.y = sizeWnd.cy-nTIP_TAIL; 
      ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4*3; 
      ptTail[0].y = sizeWnd.cy-nTIP_TAIL-2; 
      ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4*3 + nTIP_TAIL; 
      ptTail[2].y = ptTail[0].y; 
      ptTail[1].x = ptTail[2].x; 
      ptTail[1].y = sizeWnd.cy-2; 
      break; 
   } 
 
   // adjust for very narrow balloons 
   if ( ptTail[0].x < nTIP_MARGIN ) 
      ptTail[0].x = nTIP_MARGIN; 
   if ( ptTail[0].x > sizeWnd.cx - nTIP_MARGIN ) 
      ptTail[0].x = sizeWnd.cx - nTIP_MARGIN; 
   if ( ptTail[1].x < nTIP_MARGIN ) 
      ptTail[1].x = nTIP_MARGIN; 
   if ( ptTail[1].x > sizeWnd.cx - nTIP_MARGIN ) 
      ptTail[1].x = sizeWnd.cx - nTIP_MARGIN; 
   if ( ptTail[2].x < nTIP_MARGIN ) 
      ptTail[2].x = nTIP_MARGIN; 
   if ( ptTail[2].x > sizeWnd.cx - nTIP_MARGIN ) 
      ptTail[2].x = sizeWnd.cx - nTIP_MARGIN; 
 
   // get window position 
   POINT ptAnchor = GetAnchorPoint(); 
   POINT ptOffs = {ptAnchor.x - ptTail[1].x, ptAnchor.y - ptTail[1].y}; 
 
   // adjust position so all is visible 
   RECT rectScreen; 
   GetAnchorScreenBounds(rectScreen); 
   int nAdjustX = 0; 
   int nAdjustY = 0; 
   if ( ptOffs.x < rectScreen.left ) 
      nAdjustX = rectScreen.left-ptOffs.x; 
   else if ( ptOffs.x + sizeWnd.cx >= rectScreen.right ) 
      nAdjustX = rectScreen.right - (ptOffs.x + sizeWnd.cx); 
   if ( ptOffs.y + nTIP_TAIL < rectScreen.top ) 
      nAdjustY = rectScreen.top - (ptOffs.y + nTIP_TAIL); 
   else if ( ptOffs.y + sizeWnd.cy - nTIP_TAIL >= rectScreen.bottom ) 
      nAdjustY = rectScreen.bottom - (ptOffs.y + sizeWnd.cy - nTIP_TAIL); 
 
   // reposition tail 
   // uncomment two commented lines below to move entire tail  
   // instead of just anchor point 
 
   //ptTail[0].x -= nAdjustX; 
   ptTail[1].x -= nAdjustX; 
   //ptTail[2].x -= nAdjustX; 
   ptOffs.x    += nAdjustX; 
   ptOffs.y    += nAdjustY; 
 
   // place window 
   ::MoveWindow(m_hWnd, ptOffs.x, ptOffs.y, sizeWnd.cx, sizeWnd.cy, TRUE); 
 
   // apply region 
   HRGN region = ::CreatePolygonRgn(&ptTail[0], 3, ALTERNATE); 
   HRGN regionRound = ::CreateRoundRectRgn(ptTopLeft.x,ptTopLeft.y,ptBottomRight.x,ptBottomRight.y,nTIP_MARGIN*3,nTIP_MARGIN*3); 
   HRGN regionComplete = ::CreateRectRgn(0,0,1,1); 
       
   ::CombineRgn(regionComplete, region, regionRound, RGN_OR); 
 
   if ( NULL == m_hrgnComplete) 
      m_hrgnComplete = ::CreateRectRgn(0,0,1,1); 
 
   if ( !::EqualRgn(m_hrgnComplete, regionComplete) ) 
   { 
      ::CopyRgn(m_hrgnComplete, regionComplete); 
      ::SetWindowRgn(m_hWnd, regionComplete, TRUE); 
 
      // There is a bug with layered windows and NC changes in Win2k 
      // As a workaround, redraw the entire window if the NC area changed. 
      // Changing the anchor point is the ONLY thing that will change the 
      // position of the client area relative to the window during normal 
      // operation. 
      ::RedrawWindow(m_hWnd, NULL, NULL, RDW_UPDATENOW| RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); 
   } 
	else 
		::DeleteObject(regionComplete); 
	::DeleteObject(region); 
	::DeleteObject(regionRound); 
} 
 
 
 
// Returns the class ATOM for a BalloonHelp control.  Registers the class first, if necessary. 
ATOM CBalloonHelp::GetClassAtom(BOOL bShadowed) 
{ 
   if ( 0 == s_ClassAtom ) 
   { 
      WNDCLASSEX wcx;  
 
      // Fill in the window class structure with parameters  
      // that describe the main window.  
 
      wcx.cbSize = sizeof(wcx);                 // size of structure  
      wcx.style = CS_DBLCLKS|CS_SAVEBITS 
         |CS_DROPSHADOW;                        // notify of double clicks, save screen under, show dropshadow 
      wcx.lpfnWndProc = CWnd::stWinMsgHandler;             // points to window procedure  
      wcx.cbClsExtra = 0;                       // no extra class memory  
      wcx.cbWndExtra = 0;                       // no extra window memory  
      wcx.hInstance = g_hInstance;   // handle to instance  
      wcx.hIcon = NULL;                         // no app. icon  
      wcx.hCursor = LoadCursor(NULL,IDC_ARROW); // predefined arrow  
      wcx.hbrBackground = ::GetSysColorBrush(COLOR_WINDOW);                 // no background brush  
      wcx.lpszMenuName =  NULL;                 // no menu resource  
      wcx.lpszClassName = "BalloonHelpClassDS"; // name of window class  
      wcx.hIconSm = NULL;                       // no small class icon 
 
      // Register the window class (this may not work if dropshadows are not supported) 
      s_ClassAtomShadowed = ::RegisterClassEx(&wcx); 
 
      // Register shadow-less class 
      wcx.style &= ~CS_DROPSHADOW; 
      wcx.lpszClassName = "BalloonHelpClass"; 
      s_ClassAtom = ::RegisterClassEx(&wcx); 
   } 
 
   if ( bShadowed && 0 != s_ClassAtomShadowed ) 
      return s_ClassAtomShadowed; 
   return s_ClassAtom; 
} 
 
 
// Displays the balloon on the screen, performing fade-in if enabled. 
void CBalloonHelp::ShowBalloon(void) 
{ 
   ::ShowWindow(m_hWnd, SW_SHOWNOACTIVATE); 
   if ( !(m_unOptions&unDELAY_CLOSE) ) 
      SetTimeout(m_unTimeout);     // start close timer 
} 
 
// Removes the balloon from the screen, performing the fade-out if enabled 
void CBalloonHelp::HideBalloon(void) 
{ 
   if ( m_unOptions&unDELAY_CLOSE ) 
   { 
      m_unOptions &= ~(unDELAY_CLOSE|unCLOSE_ON_ANYTHING);  // close only via timer or button 
      SetTimeout(m_unTimeout);     // start close timer 
      return; 
   } 
   ::ShowWindow(m_hWnd, SW_HIDE); 
   if ( GetCapture() == m_hWnd )  
      ReleaseCapture(); 
   ::DestroyWindow(m_hWnd); 
} 
 
// 
// Keyboard hook 
// 
 
void CBalloonHelp::SetKeyboardHook() 
{ 
   if ( NULL==m_hKeyboardHook ) 
   { 
      m_hKeyboardHook = ::SetWindowsHookEx(WH_KEYBOARD, 
         (HOOKPROC)BHKeybHookThunk::GetThunk(), 
         NULL, ::GetCurrentThreadId()); 
   } 
} 
 
void CBalloonHelp::RemoveKeyboardHook() 
{ 
   if ( NULL!=m_hKeyboardHook ) 
   { 
      ::UnhookWindowsHookEx(m_hKeyboardHook); 
      m_hKeyboardHook=NULL; 
   } 
} 
 
 
// 
// Mouse hook 
// 
 
void CBalloonHelp::SetMouseHook() 
{ 
   if ( NULL==m_hMouseHook ) 
   { 
      m_hMouseHook = ::SetWindowsHookEx(WH_MOUSE, 
         (HOOKPROC)BHMouseHookThunk::GetThunk(), 
         NULL, ::GetCurrentThreadId()); 
   } 
} 
 
void CBalloonHelp::RemoveMouseHook() 
{ 
   if ( NULL!=m_hMouseHook ) 
   { 
      ::UnhookWindowsHookEx(m_hMouseHook); 
      m_hMouseHook=NULL; 
   } 
} 
 
// 
// Call Window Return hook 
// 
 
void CBalloonHelp::SetCallWndRetHook() 
{ 
   if ( NULL==m_hCallWndRetHook ) 
   { 
      m_hCallWndRetHook = ::SetWindowsHookEx(WH_CALLWNDPROCRET, 
         (HOOKPROC)BHCallWndRetHookThunk::GetThunk(), 
         NULL, ::GetCurrentThreadId()); 
   } 
} 
 
void CBalloonHelp::RemoveCallWndRetHook() 
{ 
   if ( NULL!=m_hCallWndRetHook ) 
   { 
      ::UnhookWindowsHookEx(m_hCallWndRetHook); 
      m_hCallWndRetHook=NULL; 
   } 
} 
 
LRESULT CALLBACK CBalloonHelp::WinMsgHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
	switch(uMsg) 
	{ 
		HANDLE_MSG (hwnd, WM_CLOSE, CBalloonHelp::OnClose); 
		HANDLE_MSG (hwnd, WM_DESTROY, CBalloonHelp::OnDestroy); 
		HANDLE_MSG (hwnd, WM_ERASEBKGND, CBalloonHelp::OnEraseBkgnd); 
		HANDLE_MSG (hwnd, WM_LBUTTONDOWN, CBalloonHelp::OnLButtonDown); 
		HANDLE_MSG (hwnd, WM_LBUTTONUP, CBalloonHelp::OnLButtonUp); 
		HANDLE_MSG (hwnd, WM_MOUSEMOVE, CBalloonHelp::OnMouseMove); 
		HANDLE_MSG (hwnd, WM_NCCALCSIZE, CBalloonHelp::OnNCCalcSize); 
		HANDLE_MSG (hwnd, WM_NCDESTROY, CBalloonHelp::OnNCDestroy); 
		HANDLE_MSG (hwnd, WM_NCHITTEST, CBalloonHelp::OnNCHitTest); 
		HANDLE_MSG (hwnd, WM_NCPAINT, CBalloonHelp::OnNCPaint); 
		HANDLE_MSG (hwnd, WM_PAINT, CBalloonHelp::OnPaint); 
		HANDLE_MSG (hwnd, WM_SHOWWINDOW, CBalloonHelp::OnShowWindow); 
		HANDLE_MSG (hwnd, WM_TIMER, CBalloonHelp::OnTimer); 
	case WM_PRINT: return CBalloonHelp::OnPrint(hwnd, wParam, lParam); 
	case WM_PRINTCLIENT: return CBalloonHelp::OnPrintClient(hwnd, wParam, lParam); 
	default: return DefWindowProc (hwnd, uMsg, wParam, lParam); 
	} 
} 
 
#define MSGHANDLER_PROLOG_BOOL\ 
	CBalloonHelp *thisPtr = (CBalloonHelp*)GetObjectFromWindow(hwnd);\ 
	if(!thisPtr) \ 
		return FALSE;  
 
#define MSGHANDLER_PROLOG\ 
	CBalloonHelp *thisPtr = (CBalloonHelp*)GetObjectFromWindow(hwnd);\ 
	if(!thisPtr) \ 
		return; \ 
 
 
#define MSGHANDLER_EPILOG  
 
void CBalloonHelp::OnShowWindow(HWND hwnd, BOOL fShow, UINT status) 
{ 
	MSGHANDLER_PROLOG	 
   if ( NULL != thisPtr->m_fnAnimateWindow ) 
   { 
      if ( fShow && !(thisPtr->m_unOptions&unDISABLE_FADEIN) ) 
         thisPtr->m_fnAnimateWindow(thisPtr->m_hWnd, 200, AW_BLEND); 
      else if ( !fShow && !(thisPtr->m_unOptions&unDISABLE_FADEOUT) ) 
         thisPtr->m_fnAnimateWindow(thisPtr->m_hWnd, 200, AW_HIDE | AW_BLEND ); 
   } 
	MSGHANDLER_EPILOG  
} 
 
// Erase client area of balloon 
BOOL CBalloonHelp::OnEraseBkgnd(HWND hwnd, HDC hdc) 
{ 
	MSGHANDLER_PROLOG_BOOL	 
   RECT rect; 
   GetClientRect(hwnd, &rect); 
	HBRUSH hBrush = ::CreateSolidBrush(thisPtr->m_crBackground); 
   ::FillRect(hdc, &rect, hBrush); 
	::DeleteObject(hBrush); 
	MSGHANDLER_EPILOG 
   return TRUE; 
} 
 
// draw balloon client area (title & contents) 
void CBalloonHelp::OnPaint(HWND hwnd)  
{ 
	MSGHANDLER_PROLOG	 
	PAINTSTRUCT ps; 
	HDC dc = ::BeginPaint(hwnd, &ps);    
   thisPtr->DrawClientArea(dc); 
	::EndPaint(hwnd, &ps); 
	MSGHANDLER_EPILOG 
} 
 
// draw balloon shape & border 
void CBalloonHelp::OnNCPaint(HWND hwnd, HRGN hrgn)  
{ 
	MSGHANDLER_PROLOG	 
	HDC dc = ::GetWindowDC(hwnd); 
   thisPtr->DrawNonClientArea(dc); 
	::ReleaseDC(hwnd, dc); 
	MSGHANDLER_EPILOG 
} 
 
// draw the window into the specified device context 
LRESULT CBalloonHelp::OnPrint(HWND hwnd, WPARAM wParam, LPARAM lParam) 
{ 
	MSGHANDLER_PROLOG_BOOL	 
   if ( lParam & PRF_NONCLIENT  )  
      thisPtr->DrawNonClientArea((HDC)wParam); 
	MSGHANDLER_EPILOG 
	return ::DefWindowProc(hwnd, WM_PRINT, wParam, lParam); 
} 
 
// draw the client area into the specified device context 
LRESULT CBalloonHelp::OnPrintClient(HWND hwnd, WPARAM wParam, LPARAM lParam) 
{   
	MSGHANDLER_PROLOG_BOOL	 
   if ( lParam & PRF_ERASEBKGND )  
      ::SendMessage(hwnd, WM_ERASEBKGND, wParam, lParam ); 
   if ( lParam & PRF_CLIENT )  
      thisPtr->DrawClientArea((HDC)wParam); 
	MSGHANDLER_EPILOG 
   return 0; 
} 
 
 
// Close button handler 
void CBalloonHelp::OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) 
{ 
	MSGHANDLER_PROLOG 
   if (thisPtr->m_unOptions & unSHOW_CLOSE_BUTTON) 
   { 
      RECT rect; 
      ::GetClientRect(hwnd, &rect); 
      rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE); 
      rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE); 
		POINT point = {x, y}; 
      if ( ::PtInRect(&rect, point) ) 
      { 
         thisPtr->m_uCloseState |= DFCS_PUSHED; 
         ::SetCapture(hwnd); 
         CBalloonHelp::OnMouseMove(hwnd, x, y, keyFlags); 
      } 
   } 
	MSGHANDLER_EPILOG 
} 
 
// Close button handler, 
// URL handler 
void CBalloonHelp::OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)  
{ 
	MSGHANDLER_PROLOG 
   if ( (thisPtr->m_unOptions & unSHOW_CLOSE_BUTTON) && (thisPtr->m_uCloseState & DFCS_PUSHED)) 
   { 
      ReleaseCapture(); 
      thisPtr->m_uCloseState &= ~DFCS_PUSHED; 
      RECT rect; 
      ::GetClientRect(hwnd, &rect); 
      rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE); 
      rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE); 
		POINT point = {x, y}; 
      if ( ::PtInRect(&rect, point) ) 
         thisPtr->HideBalloon(); 
   } 
   else if ( !thisPtr->m_strURL.IsEmpty() ) 
   { 
      RECT rect; 
      ::GetClientRect(hwnd, &rect); 
		POINT point = {x, y}; 
      if ( ::PtInRect(&rect, point) ) 
      { 
         ::ShellExecute(NULL, NULL, thisPtr->m_strURL, NULL, NULL, SW_SHOWNORMAL); 
         thisPtr->HideBalloon(); 
      } 
   } 
	MSGHANDLER_EPILOG 
} 
 
// 
// Ensure WM_MOUSEMOVE messages are sent for the entire window 
// 
UINT CBalloonHelp::OnNCHitTest(HWND hwnd, int x, int y) 
{ 
   return HTCLIENT; 
} 
 
// 
// do mouse tracking: 
//   Tracking for close button; 
// 
void CBalloonHelp::OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags) 
{ 
	MSGHANDLER_PROLOG 
   if (thisPtr->m_unOptions & unSHOW_CLOSE_BUTTON) 
   { 
      RECT rect; 
      GetClientRect(hwnd, &rect); 
      rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE); 
      rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE); 
      HDC dc = ::GetDC(hwnd); 
      UINT uState = DFCS_CAPTIONCLOSE; 
      BOOL bPushed = thisPtr->m_uCloseState&DFCS_PUSHED; 
      thisPtr->m_uCloseState &= ~DFCS_PUSHED; 
		POINT point = {x, y}; 
      if ( ::PtInRect(&rect, point) ) 
      { 
         uState |= DFCS_HOT; 
         if ( bPushed ) 
            uState |= DFCS_PUSHED; 
      } 
      else 
      { 
         uState |= DFCS_FLAT; 
      } 
      if ( uState != thisPtr->m_uCloseState ) 
      { 
         ::DrawFrameControl(dc, &rect, DFC_CAPTION, uState); 
         thisPtr->m_uCloseState = uState; 
      } 
      if ( bPushed ) 
         thisPtr->m_uCloseState |= DFCS_PUSHED; 
		::ReleaseDC(hwnd, dc); 
   } 
	MSGHANDLER_EPILOG 
} 
 
// Ensures client area is the correct size relative to window size, 
// presearves client contents if possible when moving. 
UINT CBalloonHelp::OnNCCalcSize(HWND hwnd, BOOL fCalcValidRects, NCCALCSIZE_PARAMS * lpcsp) 
{ 
	MSGHANDLER_PROLOG_BOOL 
   // nTIP_MARGIN pixel margin on all sides 
   ::InflateRect(&lpcsp->rgrc[0], -nTIP_MARGIN,-nTIP_MARGIN); 
 
   // nTIP_TAIL pixel "tail" on side closest to anchor 
   switch ( thisPtr->GetBalloonQuadrant() ) 
   { 
   case BQ_TOPRIGHT: 
   case BQ_TOPLEFT: 
      lpcsp->rgrc[0].top += nTIP_TAIL; 
      break; 
   case BQ_BOTTOMRIGHT: 
   case BQ_BOTTOMLEFT: 
      lpcsp->rgrc[0].bottom -= nTIP_TAIL; 
      break; 
   } 
 
   // sanity: ensure rect does not have negative size 
   if ( lpcsp->rgrc[0].right < lpcsp->rgrc[0].left ) 
      lpcsp->rgrc[0].right = lpcsp->rgrc[0].left; 
   if ( lpcsp->rgrc[0].bottom < lpcsp->rgrc[0].top ) 
      lpcsp->rgrc[0].bottom = lpcsp->rgrc[0].top; 
 
   if ( fCalcValidRects ) 
   { 
      // determine if client position has changed relative to the window position 
      // if so, don't bother presearving anything. 
      if ( !::EqualRect(&lpcsp->rgrc[0], &lpcsp->rgrc[2]) ) 
      { 
         ::SetRectEmpty(&lpcsp->rgrc[2]); 
      } 
   } 
	MSGHANDLER_EPILOG 
	return 0; 
} 
 
// handle kill timer 
void CBalloonHelp::OnTimer(HWND hwnd, UINT id) 
{ 
	MSGHANDLER_PROLOG 
   // really shouldn't be any other timers firing, but might as well make sure 
   if ( id == ID_TIMER_CLOSE ) 
   { 
      ::KillTimer(hwnd, thisPtr->m_unTimerClose); 
      thisPtr->HideBalloon(); 
   } 
	MSGHANDLER_EPILOG 
} 
 
// Called as the window is being destroyed.  Completes destruction after removing keyboard hook. 
void CBalloonHelp::OnDestroy(HWND hwnd) 
{ 
	MSGHANDLER_PROLOG 
   // remove hooks 
   thisPtr->RemoveMouseHook(); 
   thisPtr->RemoveKeyboardHook(); 
   thisPtr->RemoveCallWndRetHook();    
	MSGHANDLER_EPILOG 
} 
 
// close the balloon, performing any set transition effect. 
void CBalloonHelp::OnClose(HWND hwnd) 
{ 
	MSGHANDLER_PROLOG 
   thisPtr->HideBalloon(); 
	MSGHANDLER_EPILOG 
} 
 
// Called after window has been destroyed.  Destroys the object if option is set. 
void CBalloonHelp::OnNCDestroy(HWND hwnd) 
{    
	MSGHANDLER_PROLOG 
   // free object if requested 
   // be careful with this one :D 
   if ( thisPtr->m_unOptions & unDELETE_THIS_ON_CLOSE ) 
      delete thisPtr; 
	MSGHANDLER_EPILOG 
} 
 
// Keyboard hook: used to implement the unCLOSE_ON_KEYPRESS option 
LRESULT CBalloonHelp::KeyboardHookProc( int code, WPARAM wParam, LPARAM lParam) 
{ 
   // Skip if the key was released or if it's a repeat 
   // Bit 31:  Specifies the transition state. The value is 0 if the key   
   //       is being pressed and 1 if it is being released (see MSDN). 
   if ( code>=0 && !(lParam&0x80000000) && NULL != m_hWnd ) 
   { 
      ::PostMessage(m_hWnd, WM_CLOSE, 0, 0); 
   } 
   return ::CallNextHookEx(m_hKeyboardHook, code, wParam, lParam); 
} 
 
// Mouse hook: used to implement un-obtrusive mouse tracking 
LRESULT CBalloonHelp::MouseHookProc(int code, WPARAM wParam, LPARAM lParam) 
{ 
   if (code>=0 && NULL != m_hWnd ) 
   { 
      switch ( (UINT)wParam ) 
      { 
      case WM_NCMOUSEMOVE: 
      case WM_MOUSEMOVE: 
         if ((m_unOptions & unCLOSE_ON_MOUSE_MOVE)) 
         { 
            POINT pt; 
            ::GetCursorPos(&pt); 
            if ((abs(pt.x-m_ptMouseOrig.x) > m_nMouseMoveTolerance || abs(pt.y-m_ptMouseOrig.y) > m_nMouseMoveTolerance) ) 
               ::PostMessage(m_hWnd, WM_CLOSE, 0, 0); 
         } 
         break; 
      case WM_NCLBUTTONDOWN: 
      case WM_LBUTTONDOWN: 
         if ((m_unOptions & unCLOSE_ON_LBUTTON_DOWN)) 
            ::PostMessage(m_hWnd, WM_CLOSE, 0, 0); 
         break; 
      case WM_NCMBUTTONDOWN: 
      case WM_MBUTTONDOWN: 
         if ((m_unOptions & unCLOSE_ON_MBUTTON_DOWN)) 
            ::PostMessage(m_hWnd, WM_CLOSE, 0, 0); 
         break; 
      case WM_NCRBUTTONDOWN: 
      case WM_RBUTTONDOWN: 
         if ((m_unOptions& unCLOSE_ON_RBUTTON_DOWN)) 
            ::PostMessage(m_hWnd, WM_CLOSE, 0, 0); 
         break; 
      case WM_NCLBUTTONUP: 
      case WM_LBUTTONUP: 
         if ((m_unOptions & unCLOSE_ON_LBUTTON_UP)) 
            ::PostMessage(m_hWnd, WM_CLOSE, 0, 0); 
         break; 
      case WM_NCMBUTTONUP: 
      case WM_MBUTTONUP: 
         if ((m_unOptions & unCLOSE_ON_MBUTTON_UP)) 
            ::PostMessage(m_hWnd, WM_CLOSE, 0, 0); 
         break; 
      case WM_NCRBUTTONUP: 
      case WM_RBUTTONUP: 
         if ((m_unOptions & unCLOSE_ON_RBUTTON_UP)) 
            ::PostMessage(m_hWnd, WM_CLOSE, 0, 0); 
         break; 
      } 
   } 
   return ::CallNextHookEx(m_hMouseHook, code, wParam, lParam); 
} 
 
// Window Return hook: used to implement window following 
LRESULT CBalloonHelp::CallWndRetProc(int code, WPARAM wParam, LPARAM lParam) 
{ 
   if (code>=0 && NULL != m_hWnd ) 
   { 
      CWPRETSTRUCT* pcwpr = (CWPRETSTRUCT*)lParam; 
      if ( WM_MOVE == pcwpr->message && pcwpr->hwnd == m_hwndAnchor ) 
         PositionWindow(); 
   } 
 
   return ::CallNextHookEx(m_hCallWndRetHook, code, wParam, lParam); 
}