// AlwaysOnTopPet.cpp: implementation of the CAlwaysOnTopPet class. // ////////////////////////////////////////////////////////////////////// #include "AlwaysOnTopPet.h" //class name constant static const char* g_szOnTopClassName = "NekoOnTop_Wnd"; //forward declaration LRESULT CALLBACK WndProc_OnTop( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); //static member BOOL CAlwaysOnTopPet::m_fRegisteredClass = FALSE; //external global variable extern HINSTANCE g_hInstance; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CAlwaysOnTopPet::CAlwaysOnTopPet() : CPet() { //clear regions m_hRgns = NULL; //initialise members m_fBeingDragged = FALSE; m_hWndOnTop = NULL; //register class if( m_fRegisteredClass == FALSE ) { WNDCLASS wc; wc.style = CS_OWNDC|CS_DBLCLKS|CS_SAVEBITS; wc.lpfnWndProc = (WNDPROC)WndProc_OnTop; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(LPVOID); wc.hInstance = g_hInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor( NULL, MAKEINTRESOURCE(IDC_ARROW) ); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = g_szOnTopClassName; m_fRegisteredClass = RegisterClass(&wc); } //set bounding rectangle SetRect( &m_rcBounds, 0, 0, GetSystemMetrics(SM_CXSCREEN)-1, GetSystemMetrics(SM_CYSCREEN)-1 ); //move this pet off-screen to start with m_ptPosition.x = m_rcBounds.right; m_ptPosition.y = m_rcBounds.bottom; } CAlwaysOnTopPet::~CAlwaysOnTopPet() { DestroyWindow( m_hWndOnTop ); } ////////////////////////////////////////////////////////////////////// // Member Functions ////////////////////////////////////////////////////////////////////// void CAlwaysOnTopPet::Draw( int nImage ) { //only draw if it's different && not being dragged if( nImage != m_nLastIcon && m_fBeingDragged == FALSE ) { //clip the window to the shape of the icon HRGN hRgnCopy = CreateRectRgn( 0, 0, m_sizeImage.cx, m_sizeImage.cy ); CombineRgn( hRgnCopy, m_hRgns[nImage], NULL, RGN_COPY ); SetWindowRgn( m_hWndOnTop, hRgnCopy, TRUE ); //draw the current frame on the window HDC hDC = GetDC( m_hWndOnTop ); DrawIconEx( hDC, 0, 0, m_hIcons[nImage], m_sizeImage.cx, m_sizeImage.cy, 0, NULL, DI_NORMAL ); ReleaseDC( m_hWndOnTop, hDC ); } } void CAlwaysOnTopPet::Erase() { //do nothing } void CAlwaysOnTopPet::SetImages(HICON * hIconTable, int nIcons ) { //remove current region handles DestroyRegions(); //call base class CPet::SetImages( hIconTable, nIcons ); //prepare region handles BuildRegions(); //create the window if it doesn't exist already if( m_hWndOnTop == NULL ) { m_hWndOnTop = CreateWindowEx( WS_EX_TOPMOST|WS_EX_TOOLWINDOW, g_szOnTopClassName, NULL, WS_POPUP, m_ptPosition.x, m_ptPosition.y, m_sizeImage.cx, m_sizeImage.cy, NULL, NULL, g_hInstance, NULL ); if( m_hWndOnTop ) { SetWindowLong( m_hWndOnTop, 0, (LONG)this ); ShowWindow( m_hWndOnTop, SW_SHOWNA ); UpdateWindow( m_hWndOnTop ); } } //FIXME: don't change it in the whole class, just this window!!! //change it's default icon //SetClassLong( m_hWndOnTop, GCL_HICON, m_hIcons[0] ); } void CAlwaysOnTopPet::DestroyImages() { //call base class CPet::DestroyImages(); //delete regions DestroyRegions(); } void CAlwaysOnTopPet::DrawOnTarget( int x, int y, HICON hIcon ) { //grab the device context of the display HDC hDC = GetDC( NULL ); //draw the icon on it DrawIconEx( hDC, x, y, hIcon, 0, 0, 0, NULL, DI_NORMAL ); //release the device context ReleaseDC( NULL, hDC ); } ////////////////////////////////////////////////////////////////////// // On Top Window Procedure ////////////////////////////////////////////////////////////////////// LRESULT CALLBACK WndProc_OnTop( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch( uMsg ) { case WM_PAINT: { //draw the most recent icon if the window is being dragged CAlwaysOnTopPet* pPet = (CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ); if( pPet->m_fBeingDragged ) { //draw the current icon onto the window (we can't call draw because it checks for icon index and changes the window's region HDC hDC = GetDC( hWnd ); DrawIconEx( hDC, 0, 0, pPet->m_hIcons[pPet->m_nLastIcon], pPet->GetSize().cx, pPet->GetSize().cy, 0, NULL, DI_NORMAL ); ReleaseDC( hWnd, hDC ); } ValidateRect( hWnd, NULL ); break; } case WM_SYSCOMMAND: //if the user alt+F4s us or (somehow) minimises or maximises us, ignore it if( LOWORD(wParam) != SC_CLOSE && LOWORD(wParam) != SC_MINIMIZE && LOWORD(wParam) != SC_MAXIMIZE ) return DefWindowProc( hWnd, uMsg, wParam, lParam ); break; case WM_ERASEBKGND: return TRUE; //don't erase the background //pass mouse messages onto the class case WM_LBUTTONDOWN: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnLButtonDown(); break; case WM_LBUTTONUP: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnLButtonUp(); break; case WM_LBUTTONDBLCLK: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnLButtonDblClk(); break; case WM_MBUTTONDOWN: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnMButtonDown(); break; case WM_MBUTTONUP: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnMButtonUp(); break; case WM_MBUTTONDBLCLK: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnMButtonDblClk(); break; case WM_RBUTTONDOWN: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnRButtonDown(); break; case WM_RBUTTONUP: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnRButtonUp(); break; case WM_RBUTTONDBLCLK: ((CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ))->OnRButtonDblClk(); break; //window is being dragged case WM_ENTERSIZEMOVE: { CAlwaysOnTopPet* pPet = (CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ); pPet->m_fBeingDragged = TRUE; break; } //window is being dropped case WM_EXITSIZEMOVE: { CAlwaysOnTopPet* pPet = (CAlwaysOnTopPet*)GetWindowLong( hWnd, 0 ); pPet->m_fBeingDragged = FALSE; RECT rc; GetWindowRect( hWnd, &rc ); pPet->MoveTo( rc.left, rc.top ); break; } default: return DefWindowProc( hWnd, uMsg, wParam, lParam ); } return 0; } void CAlwaysOnTopPet::OnLButtonDown() { //default left button handler - begin window dragging SendMessage( m_hWndOnTop, WM_SYSCOMMAND, SC_MOVE+2, 0 ); } void CAlwaysOnTopPet::DestroyRegions() { if( m_hRgns ) { //delete all regions and free the array for( int i = 0; i < m_nIcons; i++ ) if( m_hRgns[i] ) DeleteObject( m_hRgns[i] ); delete[] m_hRgns; m_hRgns = NULL; } } void CAlwaysOnTopPet::MoveTo(int nNewX, int nNewY) { if( m_fBeingDragged == FALSE ) { //store current position m_ptOldPosition.x = m_ptPosition.x; m_ptOldPosition.y = m_ptPosition.y; //change current position m_ptPosition.x = nNewX; m_ptPosition.y = nNewY; //move the window if it's not being moved by something else MoveWindow( m_hWndOnTop, nNewX, nNewY, m_sizeImage.cx, m_sizeImage.cy, TRUE ); } } void CAlwaysOnTopPet::SetImageAndMoveTo(int nImage, int nNewX, int nNewY) { if( m_fBeingDragged == FALSE ) { //move MoveTo( nNewX, nNewY ); //change image Draw( nImage ); m_nLastIcon = nImage; } } void CAlwaysOnTopPet::SetImage( int nImage ) { if( m_fBeingDragged == FALSE ) CPet::SetImage( nImage ); } //This function was based on the BitmapToRegion function found in www.codeguru.com's "bitmaps and palettes" section // Author : Jean-Edouard Lachand-Robert (http://www.geocities.com/Paris/LeftBank/1160/resume.htm), June 1998. void CAlwaysOnTopPet::BuildRegions() { //create a memory DC inside which we will scan the bitmap content HDC hMemDC = CreateCompatibleDC(NULL); //create a 32 bits depth bitmap and select it into the memory DC BITMAPINFOHEADER bi = { sizeof(BITMAPINFOHEADER), int(m_sizeImage.cx/m_fScale), int(m_sizeImage.cy/m_fScale), 1, 16, BI_RGB, 0, 0, 0, 0, 0 }; VOID* pBitsDib; HBITMAP hBmDib = CreateDIBSection( hMemDC, (BITMAPINFO*)&bi, DIB_RGB_COLORS, &pBitsDib, NULL, 0 ); HBITMAP hOldMemBmp = (HBITMAP)SelectObject( hMemDC, hBmDib ); //create a DC just to copy the bitmap into the memory DC HDC hDC = CreateCompatibleDC( hMemDC ); //get how many bytes per row we have for the bitmap bits (rounded up to 32 bits) BITMAP bmDib; GetObject( hBmDib, sizeof(bmDib), &bmDib ); while( bmDib.bmWidthBytes % 4 ) bmDib.bmWidthBytes++; //calculate scaling matrix XFORM xForm = { m_fScale, 0.0, 0.0, m_fScale, 0.0, 0.0 }; //allocate the region array m_hRgns = new HRGN[m_nIcons]; //build all regions for( int i = 0; i < m_nIcons; i++ ) { HRGN hRgn = NULL; //extract icon mask image ICONINFO ii; GetIconInfo( m_hIcons[i], &ii ); DeleteObject( ii.hbmColor ); //get bitmap size BITMAP bm; GetObject( ii.hbmMask, sizeof(bm), &bm ); //copy the bitmap into the memory DC HBITMAP hOldBmp = (HBITMAP)SelectObject( hDC, ii.hbmMask ); BitBlt( hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, hDC, 0, 0, SRCCOPY ); //For better performances, we will use the ExtCreateRegion() function to create the //region. This function take a RGNDATA structure on entry. We will add rectangles by //amount of ALLOC_UNIT number in this structure. #define ALLOC_UNIT 100 DWORD dwMaxRects = ALLOC_UNIT; HANDLE hData = GlobalAlloc( GMEM_MOVEABLE, sizeof(RGNDATAHEADER) + (sizeof(RECT) * dwMaxRects) ); RGNDATA* pData = (RGNDATA *)GlobalLock( hData ); pData->rdh.dwSize = sizeof(RGNDATAHEADER); pData->rdh.iType = RDH_RECTANGLES; pData->rdh.nCount = pData->rdh.nRgnSize = 0; SetRect( &pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0 ); //scan each bitmap row from bottom to top (the bitmap is inverted vertically) BYTE* pDib = (BYTE*)bmDib.bmBits + (bmDib.bmHeight - 1) * bmDib.bmWidthBytes; for( int y = 0; y < bm.bmHeight; y++ ) { //scan each bitmap pixel from left to right for( int x = 0; x < bm.bmWidth; x++ ) { //search for a continuous range of "non transparent pixels" int x0 = x; WORD* p = (WORD*)pDib + x; while( x < bm.bmWidth) { if( *p != 0 ) //This pixel is "transparent" break; p++; x++; } if( x > x0 ) { //Add the pixels (x0, y) to (x, y+1) as a new rectangle in the region if( pData->rdh.nCount >= dwMaxRects ) { GlobalUnlock(hData); dwMaxRects += ALLOC_UNIT; hData = GlobalReAlloc( hData, sizeof(RGNDATAHEADER) + (sizeof(RECT) * dwMaxRects), GMEM_MOVEABLE ); pData = (RGNDATA*)GlobalLock(hData); } RECT* pr = (RECT*)&pData->Buffer; SetRect(&pr[pData->rdh.nCount], x0, y, x, y+1); if (x0 < pData->rdh.rcBound.left) pData->rdh.rcBound.left = x0; if (y < pData->rdh.rcBound.top) pData->rdh.rcBound.top = y; if (x > pData->rdh.rcBound.right) pData->rdh.rcBound.right = x; if (y+1 > pData->rdh.rcBound.bottom) pData->rdh.rcBound.bottom = y+1; pData->rdh.nCount++; //On Windows98, ExtCreateRegion() may fail if the number of rectangles is too //large (ie: > 4000). Therefore, we have to create the region by multiple steps. if( pData->rdh.nCount == 2000 ) { HRGN h = ExtCreateRegion( &xForm, sizeof(RGNDATAHEADER) + (sizeof(RECT) * dwMaxRects), pData); if( hRgn ) { CombineRgn( hRgn, hRgn, h, RGN_OR ); DeleteObject(h); } else hRgn = h; pData->rdh.nCount = 0; SetRect( &pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0 ); } } } //go to next row (remember, the bitmap is inverted vertically) pDib -= bmDib.bmWidthBytes; } //create or extend the region with the remaining rectangles HRGN h = ExtCreateRegion( &xForm, sizeof(RGNDATAHEADER) + (sizeof(RECT) * dwMaxRects), pData ); if( hRgn ) { CombineRgn( hRgn, hRgn, h, RGN_OR ); DeleteObject(h); } else hRgn = h; //clean up DeleteObject( SelectObject( hDC, hOldBmp ) ); GlobalFree(hData); //store the region m_hRgns[i] = hRgn; } //clean up DeleteDC(hDC); DeleteObject( SelectObject( hMemDC, hOldMemBmp ) ); DeleteDC( hMemDC ); }