412 lines
13 KiB
C++
Executable File
412 lines
13 KiB
C++
Executable File
// 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 );
|
|
}
|