/**************************************************************************\ Miranda IM: the free IM client for Microsoft* Windows* Copyright 2000-2008 Miranda ICQ/IM project, all portions of this codebase are copyrighted to the people listed in contributors.txt. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. **************************************************************************** Created: Mar 9, 2007 Author: Artem Shpynov aka FYR: ashpynov@gmail.com **************************************************************************** File contains implementation of animated avatars in contact list \**************************************************************************/ #include "hdr/modern_commonheaders.h" #define IMMEDIATE_DRAW (!AniAva.bSeparateWindow) void GDIPlus_ExtractAnimatedGIF (TCHAR * szName, int width, int height, HBITMAP * pBmp, int ** pframesDelay, int * pframesCount, SIZE * sizeAvatar); BOOL GDIPlus_IsAnimatedGif (TCHAR * szName); /* Next is module */ #define ANIAVAWINDOWCLASS _T("MirandaModernAniAvatar") #define aacheck if ( !AniAva.bModuleStarted) return #define aalock EnterCriticalSection(&AniAva.CS) #define aaunlock LeaveCriticalSection(&AniAva.CS) #define AAO_HAS_BORDER 0x01 #define AAO_ROUND_CORNERS 0x02 #define AAO_HAS_OVERLAY 0x04 #define AAO_OPAQUE 0x08 //messages enum { AAM_FIRST = WM_USER, AAM_SETAVATAR , //sync WPARAM: TCHAR * filename, LPARAM: SIZE * size, RESULT: actual size AAM_SETPOSITION, //async LPARAM: pointer to set pos info - the handler will empty it, RESULT: 0 AAM_REDRAW, //async AAM_STOP, //async stops animation, timer, hide window - prepeare for deleting AAM_PAUSE, //sync keep timer and window, but do not process painting -need before graphics change AAM_RESUME, //async remobe previous flag. repaints if required AAM_REMOVEAVATAR, //sync WPARAM: if y more then wParam, LPARAM: shift up to lParam( remove if values is same) AAM_SETPARENT, //async WPARAM: handle of new parent window AAM_SELFDESTROY, //sync AAM_RENDER, //sync AAM_LAST, }; typedef struct _tagAniAva_Object { HANDLE hContact; HWND hWindow; BOOL bInvalidPos; BOOL bToBeDeleted; DWORD dwAvatarUniqId; SIZE ObjectSize; } ANIAVA_OBJECT; typedef struct _tagAniAva_Info { DWORD dwAvatarUniqId; TCHAR * tcsFilename; int nRefCount; int nStripTop; int nFrameCount; int * pFrameDelays; SIZE FrameSize; } ANIAVA_INFO; typedef struct _tagAniAva_WindowInfo { HWND hWindow; RECT rcPos; SIZE sizeAvatar; BOOL StopTimer; int TimerId; int nFramesCount; int * delaysInterval; int currentFrame; POINT ptFromPoint; BOOL bPlaying; int overlayIconIdx; BYTE bAlpha; BOOL bOrderTop; BOOL bPaused; // was request do not draw BOOL bPended; // till do not draw - was painting - need to be repaint } ANIAVA_WINDOWINFO; typedef struct _tagAniAva_PosInfo { RECT rcPos; int idxOverlay; BYTE bAlpha; } ANIAVA_POSINFO; typedef struct _tagAniAvaSyncCallItem { WPARAM wParam; LPARAM lParam; INT_PTR nResult; HANDLE hDoneEvent; PSYNCCALLBACKPROC pfnProc; } ANIAVA_SYNCCALLITEM; typedef struct _tagAniAvatarImageInfo { POINT ptImagePos; int nFramesCount; int * pFrameDelays; SIZE szSize; } ANIAVATARIMAGEINFO; //main structure to handle global typedef struct _tagAniAva { //protection BOOL bModuleStarted; CRITICAL_SECTION CS; //options BYTE bFlags; // 0x1 has border, 0x2 has round corners, 0x4 has overlay, 0x8 background color COLORREF borderColor; BYTE cornerRadius; COLORREF bkgColor; HIMAGELIST overlayIconImageList; //animations HDC hAniAvaDC; HBITMAP hAniAvaBitmap; HBITMAP hAniAvaOldBitmap; int width; int height; SortedList * AniAvatarList; UINT AnimationThreadID; HANDLE AnimationThreadHandle; HANDLE hExitEvent; //Objects SortedList * Objects; BOOL bSeparateWindow; } ANIAVA; //module static declarations static void __AniAva_DebugRenderStrip(); static void _AniAva_DestroyAvatarWindow( HWND hwnd); static void _AniAva_Clear_ANIAVA_WINDOWINFO(ANIAVA_WINDOWINFO * pavwi ); static void _AniAva_RenderAvatar(ANIAVA_WINDOWINFO * dat, HDC hdcParent = NULL, RECT *rcInParent = NULL ); static void _AniAva_PausePainting(); static void _AniAva_ResumePainting(); static void _AniAva_LoadOptions(); static void _AniAva_ReduceAvatarImages(int startY, int dY, BOOL bDestroyWindow); static void _AniAva_RemoveAniAvaDC(ANIAVA * pAniAva); static void _AniAva_RealRemoveAvatar(DWORD UniqueID); static int _AniAva_LoadAvatarFromImage(TCHAR * szFileName, int width, int height, ANIAVATARIMAGEINFO * pRetAII); static int _AniAva_SortAvatarInfo(void * first, void * last); static BOOL _AniAva_GetAvatarImageInfo(DWORD dwAvatarUniqId, ANIAVATARIMAGEINFO * avii); static HWND _AniAva_CreateAvatarWindowSync(TCHAR *szFileName); static LRESULT CALLBACK _AniAva_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); //module variables static ANIAVA AniAva = {0}; /// IMPLEMENTATION int _AniAva_OnModulesUnload(WPARAM wParam,LPARAM lParam) { SetEvent(AniAva.hExitEvent); return 0; } static unsigned __stdcall _AniAva_AnimationTreadProc(HANDLE hExitEvent) { //wait forever till hExitEvent signalled DWORD rc; HANDLE hThread = 0; DuplicateHandle(GetCurrentProcess(),GetCurrentThread(),GetCurrentProcess(),&hThread, 0, FALSE,DUPLICATE_SAME_ACCESS); AniAva.AnimationThreadHandle = hThread; SetThreadPriority(hThread,THREAD_PRIORITY_LOWEST); for (;;) { if ( fnMsgWaitForMultipleObjectsEx ) rc = fnMsgWaitForMultipleObjectsEx(1,&hExitEvent, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE); else rc = MsgWaitForMultipleObjects(1,&hExitEvent, FALSE, INFINITE, QS_ALLINPUT); ResetEvent(hExitEvent); if (rc == WAIT_OBJECT_0 + 1) { MSG msg; while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if ( IsDialogMessage(msg.hwnd, &msg)) continue; TranslateMessage(&msg); DispatchMessage(&msg); } } else if ( rc == WAIT_OBJECT_0 ) break; } CloseHandle(AniAva.AnimationThreadHandle); AniAva.AnimationThreadHandle = NULL; return 0; } // Init AniAva module int AniAva_InitModule() { memset(&AniAva, 0, sizeof(AniAva)); if (g_CluiData.fGDIPlusFail) return 0; if ( !( db_get_b(NULL,"CList","AvatarsAnimated",( ServiceExists(MS_AV_GETAVATARBITMAP) && !g_CluiData.fGDIPlusFail)) && db_get_b(NULL,"CList","AvatarsShow",SETTINGS_SHOWAVATARS_DEFAULT))) return 0; { WNDCLASSEX wc = { sizeof(wc) }; wc.lpszClassName = ANIAVAWINDOWCLASS; wc.lpfnWndProc = _AniAva_WndProc; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.cbWndExtra = sizeof(ANIAVA_WINDOWINFO*); wc.style = CS_GLOBALCLASS; RegisterClassEx(&wc); } InitializeCriticalSection(&AniAva.CS); AniAva.Objects = List_Create(0, 2); AniAva.AniAvatarList = List_Create(0, 1); AniAva.AniAvatarList->sortFunc = _AniAva_SortAvatarInfo; AniAva.bModuleStarted = TRUE; AniAva.hExitEvent = CreateEvent(NULL,FALSE,FALSE,NULL); mir_forkthreadex(_AniAva_AnimationTreadProc, AniAva.hExitEvent, &AniAva.AnimationThreadID); HookEvent(ME_SYSTEM_PRESHUTDOWN, _AniAva_OnModulesUnload); _AniAva_LoadOptions(); return 1; } // Unload AniAva module int AniAva_UnloadModule() { aacheck 0; aalock; { int i; AniAva.bModuleStarted = FALSE; for (i=0; i < AniAva.Objects->realCount; i++) { if (AniAva.Objects->items[i]) _AniAva_DestroyAvatarWindow(((ANIAVA_OBJECT*)AniAva.Objects->items[i])->hWindow); mir_free(AniAva.Objects->items[i]); } List_Destroy(AniAva.Objects); mir_free(AniAva.Objects); for (i=0; i < AniAva.AniAvatarList->realCount; i++) { ANIAVA_INFO * aai = (ANIAVA_INFO *)AniAva.AniAvatarList->items[i]; mir_free(aai->tcsFilename); if (aai->pFrameDelays) free(aai->pFrameDelays); mir_free(aai); } List_Destroy(AniAva.AniAvatarList); mir_free(AniAva.AniAvatarList); _AniAva_RemoveAniAvaDC(&AniAva); SetEvent(AniAva.hExitEvent); CloseHandle(AniAva.hExitEvent); } aaunlock; DeleteCriticalSection(&AniAva.CS); return 1; } // Update options int AniAva_UpdateOptions() { BOOL bReloadAvatars = FALSE; BOOL bBeEnabled = (!g_CluiData.fGDIPlusFail && db_get_b(NULL,"CList","AvatarsAnimated",( ServiceExists(MS_AV_GETAVATARBITMAP) && !g_CluiData.fGDIPlusFail)) && db_get_b(NULL,"CList","AvatarsShow",SETTINGS_SHOWAVATARS_DEFAULT)); if (bBeEnabled && !AniAva.bModuleStarted) { AniAva_InitModule(); bReloadAvatars = TRUE; } else if ( !bBeEnabled && AniAva.bModuleStarted) { AniAva_UnloadModule(); bReloadAvatars = TRUE; } BOOL oldSeparate = AniAva.bSeparateWindow; _AniAva_LoadOptions(); if ( oldSeparate != AniAva.bSeparateWindow ) { AniAva_InvalidateAvatarPositions(NULL); AniAva_RemoveInvalidatedAvatars(); } if ( bReloadAvatars ) PostMessage(pcli->hwndContactTree,INTM_AVATARCHANGED,0, 0); else AniAva_RedrawAllAvatars(TRUE); return 0; } // adds avatars to be displayed int AniAva_AddAvatar(HANDLE hContact, TCHAR * szFilename, int width, int heigth) { int res = 0; aacheck 0; if ( !GDIPlus_IsAnimatedGif (szFilename)) return 0; aalock; { //first try to find window for contact avatar HWND hwnd = NULL; int i; ANIAVA_OBJECT * pavi; ANIAVATARIMAGEINFO avii = {0}; SIZE szAva = { width, heigth }; for (i=0; i < AniAva.Objects->realCount; i++) { pavi = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; if (pavi->hContact == hContact) { if (pavi->ObjectSize.cx == width && pavi->ObjectSize.cy == heigth) { hwnd = pavi->hWindow; break; } else { _AniAva_DestroyAvatarWindow(pavi->hWindow); pavi->hWindow = NULL; _AniAva_RealRemoveAvatar(pavi->dwAvatarUniqId); pavi->dwAvatarUniqId = 0; break; } } } if (i == AniAva.Objects->realCount) { pavi = (ANIAVA_OBJECT *) mir_calloc( sizeof(ANIAVA_OBJECT)); pavi->hWindow = NULL; pavi->hContact = hContact; pavi->bInvalidPos = 0; List_Insert( AniAva.Objects, pavi, AniAva.Objects->realCount); } //change avatar pavi->bToBeDeleted = FALSE; pavi->bInvalidPos = 0; // now CreateAvatar if (pavi->dwAvatarUniqId) _AniAva_GetAvatarImageInfo(pavi->dwAvatarUniqId,&avii); else pavi->dwAvatarUniqId = _AniAva_LoadAvatarFromImage(szFilename, width, heigth, &avii); if (hwnd) SendMessage(hwnd, AAM_SETAVATAR, (WPARAM)&avii, (LPARAM) 0); pavi->ObjectSize = avii.szSize; res = MAKELONG(avii.szSize.cx, avii.szSize.cy); } aaunlock; return res; } // call windows to set they parent in order to ensure valid zorder void AniAva_UpdateParent() { aacheck; aalock; { int i; HWND parent = fnGetAncestor(pcli->hwndContactList,GA_PARENT); for (i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; SendMessage(pai->hWindow, AAM_SETPARENT, (WPARAM)parent,0); } } aaunlock; } ANIAVA_OBJECT * FindAvatarByContact( HANDLE hContact ) { for ( int i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = ((ANIAVA_OBJECT *)AniAva.Objects->items[i]); if (pai->hContact == hContact) return pai; } return NULL; } int AniAva_RenderAvatar( HANDLE hContact, HDC hdcMem, RECT *rc ) { aacheck 0; aalock; ANIAVA_OBJECT * pai = FindAvatarByContact( hContact ); if ( pai ) SendMessage(pai->hWindow, AAM_RENDER, (WPARAM)hdcMem, (LPARAM) rc); aaunlock; return 0; } // update avatars pos int AniAva_SetAvatarPos(HANDLE hContact, RECT *rc, int overlayIdx, BYTE bAlpha) { aacheck 0; aalock; if (AniAva.CS.LockCount>0) { aaunlock; return 0; } { ANIAVA_OBJECT * pai = FindAvatarByContact( hContact ); if ( pai ) { ANIAVA_POSINFO * api = (ANIAVA_POSINFO *)malloc(sizeof(ANIAVA_POSINFO)); if ( !pai->hWindow) { HWND hwnd; HWND parent; ANIAVATARIMAGEINFO avii = {0}; //not found -> create window char szName[150] = "AniAvaWnd_"; TCHAR * tszName; _itoa((int)hContact,szName+10, 16); tszName = mir_a2t( szName ); hwnd = _AniAva_CreateAvatarWindowSync(tszName); mir_free( tszName ); parent = fnGetAncestor(pcli->hwndContactList,GA_PARENT); pai->hWindow = hwnd; SendMessage(hwnd,AAM_SETPARENT,(WPARAM)parent,0); if (_AniAva_GetAvatarImageInfo(pai->dwAvatarUniqId,&avii)) SendMessage(pai->hWindow, AAM_SETAVATAR, (WPARAM)&avii, (LPARAM) 0); } api->bAlpha = bAlpha; api->idxOverlay = overlayIdx; api->rcPos = *rc; SendNotifyMessage(pai->hWindow, AAM_SETPOSITION, 0, (LPARAM) api); // the AAM_SETPOSITION is responsible to destroy memory under api pai->bInvalidPos = FALSE; pai->bToBeDeleted = FALSE; } } aaunlock; return 1; } // remove avatar int AniAva_RemoveAvatar(HANDLE hContact) { aacheck 0; aalock; { int i; for (i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; if (pai->hContact == hContact) { pai->bToBeDeleted = TRUE; break; } } } aaunlock; return 1; } // reset positions of avatars to be drawn (still be painted at same place) int AniAva_InvalidateAvatarPositions(HANDLE hContact) { int i; aacheck 0; aalock; for (i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; if (pai->hContact == hContact || !hContact) { pai->bInvalidPos++; if (hContact) break; } } aaunlock; return 1; } // all avatars without validated position will be stop painted and probably removed int AniAva_RemoveInvalidatedAvatars() { BOOL keepAvatar = FALSE; aacheck 0; aalock; { int i; for (i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; if (pai->hWindow && (pai->bInvalidPos)) { SendMessage(pai->hWindow,AAM_STOP, 0, 0); if (pai->bInvalidPos)//>3) { //keepAvatar = TRUE; //pai->bToBeDeleted = TRUE; pai->bInvalidPos = 0; _AniAva_DestroyAvatarWindow(pai->hWindow); pai->hWindow = NULL; } } if (pai->bToBeDeleted) { if (pai->hWindow) _AniAva_DestroyAvatarWindow(pai->hWindow); pai->hWindow = NULL; if ( !keepAvatar) _AniAva_RealRemoveAvatar(pai->dwAvatarUniqId); mir_free(pai); List_Remove(AniAva.Objects,i); i--; } } } aaunlock; return 1; } // repaint all avatars at positions (eg on main window movement) int AniAva_RedrawAllAvatars(BOOL updateZOrder) { int i; aacheck 0; aalock; updateZOrder = 1; for (i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; if (updateZOrder) SendMessage(pai->hWindow,AAM_REDRAW,(WPARAM)updateZOrder,0); else SendNotifyMessage(pai->hWindow,AAM_REDRAW,(WPARAM)updateZOrder,0); } aaunlock; return 1; } //Static procedures static void CALLBACK _AniAva_SyncCallerUserAPCProc(DWORD_PTR dwParam) { ANIAVA_SYNCCALLITEM* item = (ANIAVA_SYNCCALLITEM*) dwParam; item->nResult = item->pfnProc(item->wParam, item->lParam); SetEvent(item->hDoneEvent); } static INT_PTR _AniAva_CreateAvatarWindowSync_Worker(WPARAM tszName, LPARAM lParam) { HWND hwnd = CreateWindowEx( WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_NOPARENTNOTIFY,ANIAVAWINDOWCLASS,(TCHAR*)tszName,WS_POPUP, 0, 0, 1,1,pcli->hwndContactList, NULL, pcli->hInst, NULL ); return (INT_PTR)hwnd; } static HWND _AniAva_CreateAvatarWindowSync(TCHAR *szFileName) { ANIAVA_SYNCCALLITEM item = {0}; int res = 0; if ( !AniAva.AnimationThreadHandle) return NULL; if (AniAva.AnimationThreadID == 0) return NULL; item.wParam = (WPARAM) szFileName; item.lParam = 0; item.pfnProc = _AniAva_CreateAvatarWindowSync_Worker; item.hDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (GetCurrentThreadId() != AniAva.AnimationThreadID) QueueUserAPC(_AniAva_SyncCallerUserAPCProc, AniAva.AnimationThreadHandle, (DWORD_PTR) &item); else _AniAva_SyncCallerUserAPCProc((DWORD_PTR) &item); WaitForSingleObject(item.hDoneEvent, INFINITE); CloseHandle(item.hDoneEvent); return (HWND)item.nResult; } static void _AniAva_RealRemoveAvatar(DWORD UniqueID) { int j,k; for (j = 0; j < AniAva.AniAvatarList->realCount; j++) { ANIAVA_INFO * aai = (ANIAVA_INFO *) AniAva.AniAvatarList->items[j]; if (aai->dwAvatarUniqId == UniqueID) { aai->nRefCount--; if (aai->nRefCount == 0) { _AniAva_PausePainting(); #ifdef _DEBUG __AniAva_DebugRenderStrip(); #endif mir_free(aai->tcsFilename); if (aai->pFrameDelays) free(aai->pFrameDelays); _AniAva_ReduceAvatarImages(aai->nStripTop,aai->FrameSize.cx*aai->nFrameCount, FALSE); for (k = 0; k < AniAva.AniAvatarList->realCount; k++) if (k != j) { ANIAVA_INFO * taai = (ANIAVA_INFO *) AniAva.AniAvatarList->items[k]; if (taai->nStripTop>aai->nStripTop) taai->nStripTop -= aai->FrameSize.cx*aai->nFrameCount; } if (AniAva.AniAvatarList->realCount>0) { //lets create hNewDC HDC hNewDC; HBITMAP hNewBmp, hNewOldBmp; int newWidth = AniAva.width-aai->FrameSize.cx*aai->nFrameCount; int newHeight = 0; int i; for (i=0; i < AniAva.AniAvatarList->realCount; i++) if (i != j) { newHeight = max(newHeight,((ANIAVA_INFO *) AniAva.AniAvatarList->items[i])->FrameSize.cy); } hNewDC = CreateCompatibleDC(NULL); hNewBmp = ske_CreateDIB32(newWidth,newHeight); hNewOldBmp = (HBITMAP)SelectObject(hNewDC,hNewBmp); // copy from old and from new strip if (aai->nStripTop>0) BitBlt(hNewDC, 0, 0, aai->nStripTop,newHeight,AniAva.hAniAvaDC, 0, 0, SRCCOPY); if (aai->nStripTop+aai->FrameSize.cx*aai->nFrameCount < AniAva.width) BitBlt(hNewDC,aai->nStripTop, 0, AniAva.width-(aai->nStripTop+aai->FrameSize.cx*aai->nFrameCount),newHeight,AniAva.hAniAvaDC,aai->nStripTop+aai->FrameSize.cx*aai->nFrameCount, 0, SRCCOPY); _AniAva_RemoveAniAvaDC(&AniAva); AniAva.hAniAvaDC = hNewDC; AniAva.hAniAvaBitmap = hNewBmp; AniAva.hAniAvaOldBitmap = hNewOldBmp; AniAva.width = newWidth; AniAva.height = newHeight; } else { _AniAva_RemoveAniAvaDC(&AniAva); } #ifdef _DEBUG __AniAva_DebugRenderStrip(); #endif List_Remove(AniAva.AniAvatarList, j); mir_free(aai); _AniAva_ResumePainting(); break; } } } } static void _AniAva_RemoveAniAvaDC(ANIAVA * pAniAva) { if (pAniAva->hAniAvaDC) { SelectObject(pAniAva->hAniAvaDC, pAniAva->hAniAvaOldBitmap); DeleteObject(pAniAva->hAniAvaBitmap); DeleteDC(pAniAva->hAniAvaDC); pAniAva->hAniAvaDC = NULL; pAniAva->height = 0; pAniAva->width = 0; pAniAva->hAniAvaBitmap = NULL; } }; static void _AniAva_DestroyAvatarWindow( HWND hwnd) { SendMessage(hwnd,AAM_SELFDESTROY, 0, 0); } static int _AniAva_LoadAvatarFromImage(TCHAR * szFileName, int width, int height, ANIAVATARIMAGEINFO * pRetAII) { ANIAVA_INFO aai = {0}; ANIAVA_INFO * paai = NULL; BOOL fNeedInsertToList = FALSE; int idx = 0; aai.tcsFilename = szFileName; aai.FrameSize.cx = width; aai.FrameSize.cy = height; if ( !List_GetIndex(AniAva.AniAvatarList,(void*)&aai,&idx)) idx = -1; if (idx == -1) //item not present in list { HBITMAP hBitmap = NULL; HDC hTempDC; HBITMAP hOldBitmap; HDC hNewDC; HBITMAP hNewBmp; HBITMAP hNewOldBmp; int newWidth; int newHeight; paai = (ANIAVA_INFO *)mir_calloc(sizeof(ANIAVA_INFO)); paai->tcsFilename = mir_tstrdup(szFileName); paai->dwAvatarUniqId = rand(); fNeedInsertToList = TRUE; //get image strip GDIPlus_ExtractAnimatedGIF (szFileName, width, height, &hBitmap, &(paai->pFrameDelays), &(paai->nFrameCount), &(paai->FrameSize)); //copy image to temp DC hTempDC = CreateCompatibleDC(NULL); hOldBitmap = (HBITMAP)SelectObject(hTempDC,hBitmap); //lets create hNewDC /* newWidth = max(paai->FrameSize.cx*paai->nFrameCount,AniAva.width); newHeight = AniAva.height+paai->FrameSize.cy; */ newWidth = AniAva.width+paai->FrameSize.cx*paai->nFrameCount; newHeight = max(paai->FrameSize.cy,AniAva.height); hNewDC = CreateCompatibleDC(NULL); hNewBmp = ske_CreateDIB32(newWidth,newHeight); hNewOldBmp = (HBITMAP)SelectObject(hNewDC,hNewBmp); _AniAva_PausePainting(); GdiFlush(); // copy from old and from new strip BitBlt(hNewDC, 0, 0, AniAva.width,AniAva.height,AniAva.hAniAvaDC, 0, 0, SRCCOPY); BitBlt(hNewDC,AniAva.width, 0, paai->FrameSize.cx*paai->nFrameCount,paai->FrameSize.cy,hTempDC, 0, 0, SRCCOPY); paai->nStripTop = AniAva.width; GdiFlush(); //remove temp DC SelectObject(hTempDC,hOldBitmap); DeleteObject(hNewBmp); DeleteDC(hTempDC); DeleteObject(hBitmap); //delete old _AniAva_RemoveAniAvaDC(&AniAva); //setNewDC; AniAva.hAniAvaDC = hNewDC; AniAva.hAniAvaBitmap = hNewBmp; AniAva.hAniAvaOldBitmap = hNewOldBmp; AniAva.width = newWidth; AniAva.height = newHeight; GdiFlush(); _AniAva_ResumePainting(); } else { paai = (ANIAVA_INFO *)AniAva.AniAvatarList->items[idx]; } if (paai) { paai->nRefCount++; pRetAII->nFramesCount = paai->nFrameCount; pRetAII->pFrameDelays = paai->pFrameDelays; pRetAII->ptImagePos.x = paai->nStripTop; pRetAII->ptImagePos.y = 0; pRetAII->szSize = paai->FrameSize; if (fNeedInsertToList) { //add to list int idx = AniAva.AniAvatarList->realCount; List_GetIndex(AniAva.AniAvatarList, paai,&idx); List_Insert(AniAva.AniAvatarList, (void*)paai, idx); } return paai->dwAvatarUniqId; } return 0; } static BOOL _AniAva_GetAvatarImageInfo(DWORD dwAvatarUniqId, ANIAVATARIMAGEINFO * avii) { int j; BOOL res = FALSE; for (j = 0; j < AniAva.AniAvatarList->realCount; j++) { ANIAVA_INFO * aai = (ANIAVA_INFO *) AniAva.AniAvatarList->items[j]; if (aai->dwAvatarUniqId == dwAvatarUniqId) { avii->nFramesCount = aai->nFrameCount; avii->pFrameDelays = aai->pFrameDelays; avii->ptImagePos.x = aai->nStripTop; avii->ptImagePos.y = 0; avii->szSize = aai->FrameSize; res = TRUE; break; } } return res; } static void _AniAva_Clear_ANIAVA_WINDOWINFO(ANIAVA_WINDOWINFO * pavwi ) { pavwi->delaysInterval = NULL; pavwi->nFramesCount = 0; KillTimer(pavwi->hWindow,2); pavwi->bPlaying = FALSE; pavwi->TimerId = 0; } static void __AniAva_DebugRenderStrip() { return; #ifdef _DEBUG { HDC hDC_debug = GetDC(NULL); BitBlt(hDC_debug, 0, 0, AniAva.width, AniAva.height,AniAva.hAniAvaDC, 0, 0, SRCCOPY); DeleteDC(hDC_debug); } #endif } static void _AniAva_RenderAvatar(ANIAVA_WINDOWINFO * dat, HDC hdcParent /*= NULL*/, RECT *rcInParent /*= NULL */ ) { if (dat->bPaused>0) { dat->bPended = TRUE; return; } else dat->bPended = FALSE; if ( IMMEDIATE_DRAW && hdcParent == NULL ) return; GdiFlush(); #ifdef _DEBUG __AniAva_DebugRenderStrip(); #endif if (dat->bPlaying && IsWindowVisible(dat->hWindow)) { POINT ptWnd = {0}; SIZE szWnd = {dat->rcPos.right-dat->rcPos.left,dat->rcPos.bottom-dat->rcPos.top}; BLENDFUNCTION bf = {AC_SRC_OVER, 0, g_CluiData.bCurrentAlpha*dat->bAlpha/256, AC_SRC_ALPHA }; POINT pt_from = {0, 0}; HDC hDC_animation = GetDC(NULL); HDC copyFromDC; RECT clistRect; HDC tempDC = NULL; HBITMAP hBmp; HBITMAP hOldBmp; /* int x = bf.SourceConstantAlpha; x = (49152/(383-x))-129; x = min(x,255); x = max(x,0); bf.SourceConstantAlpha = x; */ if ( AniAva.bFlags == 0 ) //simple and fastest method - no borders, round corners and etc. just copy { pt_from.x = dat->ptFromPoint.x+dat->currentFrame*dat->sizeAvatar.cx; pt_from.y = dat->ptFromPoint.y; copyFromDC = AniAva.hAniAvaDC; } else { // ... need to create additional hDC_animation HRGN hRgn = NULL; int cornerRadius = AniAva.cornerRadius; tempDC = CreateCompatibleDC( NULL ); hBmp = ske_CreateDIB32( szWnd.cx, szWnd.cy ); hOldBmp = (HBITMAP)SelectObject(tempDC,hBmp); if ( AniAva.bFlags & AAO_ROUND_CORNERS ) { if ( !cornerRadius) //auto radius cornerRadius = min(szWnd.cx, szWnd.cy )/5; } if ( AniAva.bFlags & AAO_HAS_BORDER ) { // if has borders - create region (round corners) and fill it, remember internal as clipping HBRUSH hBrush = CreateSolidBrush( AniAva.borderColor ); HBRUSH hOldBrush = (HBRUSH)SelectObject( tempDC, hBrush ); HRGN rgnOutside = CreateRoundRectRgn( 0, 0, szWnd.cx+1, szWnd.cy+1, cornerRadius << 1, cornerRadius << 1); hRgn = CreateRoundRectRgn( 1, 1, szWnd.cx, szWnd.cy, cornerRadius << 1, cornerRadius << 1); CombineRgn( rgnOutside,rgnOutside,hRgn,RGN_DIFF); FillRgn( tempDC, rgnOutside, hBrush); ske_SetRgnOpaque( tempDC, rgnOutside, TRUE); SelectObject(tempDC, hOldBrush); DeleteObject(hBrush); DeleteObject(rgnOutside); } else if ( cornerRadius > 0 ) { // else create clipping area (round corners) hRgn = CreateRoundRectRgn(0, 0, szWnd.cx+1, szWnd.cy+1, cornerRadius << 1, cornerRadius << 1); } else { hRgn = CreateRectRgn(0, 0, szWnd.cx+1, szWnd.cy+1); } // select clip area if ( hRgn ) ExtSelectClipRgn(tempDC, hRgn, RGN_AND); if ( AniAva.bFlags & AAO_OPAQUE) { // if back color - fill clipping area HBRUSH hBrush = CreateSolidBrush( AniAva.bkgColor ); HBRUSH hOldBrush = (HBRUSH)SelectObject( tempDC, hBrush ); FillRgn( tempDC, hRgn, hBrush ); ske_SetRgnOpaque( tempDC, hRgn, TRUE ); } // draw avatar if ( !(AniAva.bFlags & AAO_OPAQUE)) BitBlt(tempDC, 0, 0, szWnd.cx, szWnd.cy , AniAva.hAniAvaDC , dat->ptFromPoint.x+dat->sizeAvatar.cx*dat->currentFrame, dat->ptFromPoint.y, SRCCOPY); else { BLENDFUNCTION abf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; ske_AlphaBlend(tempDC, 0, 0, szWnd.cx, szWnd.cy , AniAva.hAniAvaDC, dat->ptFromPoint.x+dat->sizeAvatar.cx*dat->currentFrame, dat->ptFromPoint.y, szWnd.cx, szWnd.cy, abf); } // reset clip area if ( hRgn ) { DeleteObject(hRgn); hRgn = CreateRectRgn(0, 0, szWnd.cx, szWnd.cy); SelectClipRgn(tempDC, hRgn); DeleteObject(hRgn); } if (( AniAva.bFlags & AAO_HAS_OVERLAY ) && ( dat->overlayIconIdx != -1 ) && ( AniAva.overlayIconImageList )) { // if overlay - draw overlay icon // position - on avatar int x = szWnd.cx - ICON_WIDTH; int y = szWnd.cy - ICON_HEIGHT; ske_ImageList_DrawEx(AniAva.overlayIconImageList, dat->overlayIconIdx & 0xFFFF, tempDC, x, y, ICON_WIDTH, ICON_HEIGHT, CLR_NONE, CLR_NONE, ILD_NORMAL); } copyFromDC = tempDC; } // intersect visible area // update layered window GetWindowRect(pcli->hwndContactTree, &clistRect); if (dat->rcPos.top < 0) { pt_from.y += -dat->rcPos.top; szWnd.cy += dat->rcPos.top; } if (dat->rcPos.bottom>clistRect.bottom-clistRect.top) szWnd.cy -= (dat->rcPos.bottom-(clistRect.bottom-clistRect.top)); ptWnd.x = dat->rcPos.left+clistRect.left; ptWnd.y = (dat->rcPos.top>0 ? dat->rcPos.top :0)+clistRect.top; if (szWnd.cy>0) { if ( hdcParent && rcInParent && IMMEDIATE_DRAW ) { if ( AniAva.bFlags & AAO_OPAQUE ) BitBlt( hdcParent, rcInParent->left, rcInParent->top, szWnd.cx, szWnd.cy, copyFromDC, pt_from.x, pt_from.y, SRCCOPY); else { BLENDFUNCTION abf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; ske_AlphaBlend( hdcParent, rcInParent->left, rcInParent->top, szWnd.cx, szWnd.cy, copyFromDC, pt_from.x, pt_from.y, szWnd.cx, szWnd.cy, abf); } } else if ( !g_proc_UpdateLayeredWindow(dat->hWindow, hDC_animation, &ptWnd, &szWnd, copyFromDC, &pt_from, RGB(0, 0, 0), &bf, ULW_ALPHA )) { LONG exStyle; exStyle = GetWindowLongPtr(dat->hWindow,GWL_EXSTYLE); exStyle |= WS_EX_LAYERED; SetWindowLongPtr(dat->hWindow,GWL_EXSTYLE,exStyle); if ( !IMMEDIATE_DRAW ) SetWindowPos( pcli->hwndContactTree, dat->hWindow, 0, 0, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING ); g_proc_UpdateLayeredWindow(dat->hWindow, hDC_animation, &ptWnd, &szWnd, copyFromDC, &pt_from, RGB(0, 0, 0), &bf, ULW_ALPHA ); } g_CluiData.fAeroGlass = false; CLUI_UpdateAeroGlass(); } else { dat->bPlaying = FALSE; } ReleaseDC(NULL,hDC_animation); if (tempDC) { SelectObject(tempDC, hOldBmp); DeleteObject(hBmp); DeleteDC(tempDC); } } if ( !dat->bPlaying) { ShowWindow(dat->hWindow, SW_HIDE); KillTimer(dat->hWindow,2); //stop animation till set pos will be called } GdiFlush(); } static void _AniAva_PausePainting() { int i; for (i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; SendMessage(pai->hWindow,AAM_PAUSE, 0, 0); } } static void _AniAva_ResumePainting() { int i; for (i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; SendNotifyMessage(pai->hWindow,AAM_RESUME, 0, 0); } } static void _AniAva_ReduceAvatarImages(int startY, int dY, BOOL bDestroyWindow) { int i; for (i=0; i < AniAva.Objects->realCount; i++) { ANIAVA_OBJECT * pai = (ANIAVA_OBJECT *)AniAva.Objects->items[i]; int res = SendMessage(pai->hWindow,AAM_REMOVEAVATAR,(WPARAM)startY,(LPARAM)dY); if (res == 0xDEAD && bDestroyWindow) { _AniAva_DestroyAvatarWindow(pai->hWindow); mir_free(pai); List_Remove(AniAva.Objects,i); i--; } } } static void _AniAva_LoadOptions() { aacheck; aalock; { AniAva.bFlags = ( db_get_b(NULL,"CList","AvatarsDrawBorders",SETTINGS_AVATARDRAWBORDER_DEFAULT)? AAO_HAS_BORDER :0) | ( db_get_b(NULL,"CList","AvatarsRoundCorners",SETTINGS_AVATARROUNDCORNERS_DEFAULT)? AAO_ROUND_CORNERS :0) | ( db_get_b(NULL,"CList","AvatarsDrawOverlay",SETTINGS_AVATARDRAWOVERLAY_DEFAULT)? AAO_HAS_OVERLAY :0) | ((0) ? AAO_OPAQUE :0); if (AniAva.bFlags & AAO_HAS_BORDER) AniAva.borderColor = (COLORREF)db_get_dw(NULL,"CList","AvatarsBorderColor",SETTINGS_AVATARBORDERCOLOR_DEFAULT);; if (AniAva.bFlags & AAO_ROUND_CORNERS) AniAva.cornerRadius = db_get_b(NULL,"CList","AvatarsUseCustomCornerSize",SETTINGS_AVATARUSECUTOMCORNERSIZE_DEFAULT)? db_get_w(NULL,"CList","AvatarsCustomCornerSize",SETTINGS_AVATARCORNERSIZE_DEFAULT) : 0; if (AniAva.bFlags & AAO_HAS_OVERLAY) { //check image list BYTE type = db_get_b(NULL,"CList","AvatarsOverlayType",SETTINGS_AVATAROVERLAYTYPE_DEFAULT); switch(type) { case SETTING_AVATAR_OVERLAY_TYPE_NORMAL: AniAva.overlayIconImageList = hAvatarOverlays; break; case SETTING_AVATAR_OVERLAY_TYPE_PROTOCOL: case SETTING_AVATAR_OVERLAY_TYPE_CONTACT: AniAva.overlayIconImageList = g_himlCListClc; break; default: AniAva.overlayIconImageList = NULL; } } if (AniAva.bFlags & AAO_OPAQUE) AniAva.bkgColor = 0; AniAva.bSeparateWindow = db_get_b(NULL,"CList","AvatarsInSeparateWnd",SETTINGS_AVATARINSEPARATE_DEFAULT); } aaunlock; } static int _AniAva_SortAvatarInfo(void * first, void * last) { int res = 0; ANIAVA_INFO * aai1 = (ANIAVA_INFO *)first; ANIAVA_INFO * aai2 = (ANIAVA_INFO *)last; if (aai1 && aai1->tcsFilename && aai2 && aai2->tcsFilename) { res = _tcsicmp(aai2->tcsFilename, aai1->tcsFilename); } else { int a1 = (aai1 && aai1->tcsFilename)? 1:0; int a2 = (aai2 && aai2->tcsFilename)? 1:0; res = a1-a2; } if (res == 0) { if ( aai1->FrameSize.cx == aai2->FrameSize.cx && aai1->FrameSize.cy == aai2->FrameSize.cy ) return 0; else return 1; } else return res; } void _AniAva_InvalidateParent(ANIAVA_WINDOWINFO * dat) { if ( !IMMEDIATE_DRAW ) return; HWND hwndParent = pcli->hwndContactTree; RECT rcPos = dat->rcPos; pcli->pfnInvalidateRect( hwndParent, &rcPos, FALSE ); } static LRESULT CALLBACK _AniAva_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { ANIAVA_WINDOWINFO * dat = NULL; if (msg == WM_TIMER || msg == WM_DESTROY || (msg>AAM_FIRST && msg < AAM_LAST)) dat = (ANIAVA_WINDOWINFO *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (msg) { case AAM_REMOVEAVATAR: if (dat->ptFromPoint.x == (int)wParam) return 0xDEAD; //need to destroy window else if (dat->ptFromPoint.x>(int)wParam) dat->ptFromPoint.x -= (int)lParam; return 0; case AAM_PAUSE: dat->bPaused++; return 0; case AAM_RESUME: dat->bPaused--; if (dat->bPaused) return 0; if (dat->bPended) { if ( !IMMEDIATE_DRAW ) _AniAva_RenderAvatar(dat); } dat->bPended = FALSE; return 0; case AAM_STOP: if (dat->bPlaying) { dat->bPlaying = FALSE; KillTimer(hwnd,2); ShowWindow(hwnd, SW_HIDE); } return 0; case AAM_SETAVATAR: { ANIAVATARIMAGEINFO *paaii = (ANIAVATARIMAGEINFO*)wParam; _AniAva_Clear_ANIAVA_WINDOWINFO(dat); dat->nFramesCount = paaii->nFramesCount; dat->delaysInterval = paaii->pFrameDelays; dat->sizeAvatar = paaii->szSize; dat->ptFromPoint = paaii->ptImagePos; dat->currentFrame = 0; dat->bPlaying = FALSE; return MAKELONG(dat->sizeAvatar.cx,dat->sizeAvatar.cy); } case AAM_SETPOSITION: { ANIAVA_POSINFO * papi = (ANIAVA_POSINFO *)lParam; if ( !dat->delaysInterval) return 0; if ( !papi) return 0; dat->rcPos = papi->rcPos; dat->overlayIconIdx = papi->idxOverlay; dat->bAlpha = papi->bAlpha; free(papi); if ( !dat->bPlaying) { dat->bPlaying = TRUE; ShowWindow(hwnd,SW_SHOWNA); dat->currentFrame = 0; KillTimer(hwnd,2); SetTimer(hwnd,2,dat->delaysInterval[0],NULL); } if ( !IMMEDIATE_DRAW ) _AniAva_RenderAvatar(dat); return 0; } case AAM_SETPARENT: if ( IMMEDIATE_DRAW ) return 0; dat->bOrderTop = ((HWND)wParam != GetDesktopWindow()); SetParent(hwnd,(HWND)wParam); if (dat->bOrderTop) { SetWindowPos(hwnd,HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_ASYNCWINDOWPOS); } else { LONG exStyle; exStyle = GetWindowLongPtr(pcli->hwndContactList,GWL_EXSTYLE); SetWindowPos(pcli->hwndContactList,hwnd, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE/*|SWP_ASYNCWINDOWPOS*/); if ( !(exStyle&WS_EX_TOPMOST)) SetWindowPos(pcli->hwndContactList,HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE/*|SWP_ASYNCWINDOWPOS*/); } return 0; case AAM_REDRAW: if ( IMMEDIATE_DRAW ) return 0; if ( wParam ) { if (dat->bOrderTop) { SetWindowPos(hwnd,HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_ASYNCWINDOWPOS); } else { LONG exStyle; exStyle = GetWindowLongPtr(pcli->hwndContactList,GWL_EXSTYLE); SetWindowPos(pcli->hwndContactList,hwnd, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE/*|SWP_ASYNCWINDOWPOS*/); if ( !(exStyle&WS_EX_TOPMOST)) SetWindowPos(pcli->hwndContactList,HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE/*|SWP_ASYNCWINDOWPOS*/); } } _AniAva_RenderAvatar( dat ); return 0; case AAM_RENDER: { HDC hdc = ( HDC )wParam; RECT* rect = ( RECT* )lParam; _AniAva_RenderAvatar( dat, hdc, rect ); } return 0; case AAM_SELFDESTROY: return DestroyWindow(hwnd); case WM_CREATE: { LONG exStyle; ANIAVA_WINDOWINFO * dat = (ANIAVA_WINDOWINFO *) mir_calloc(sizeof (ANIAVA_WINDOWINFO)); SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)dat); dat->hWindow = hwnd; //ShowWindow(dat->hWindow,SW_SHOW); //change layered mode exStyle = GetWindowLongPtr(dat->hWindow,GWL_EXSTYLE); exStyle |= WS_EX_LAYERED; SetWindowLongPtr(dat->hWindow,GWL_EXSTYLE,exStyle); exStyle = GetWindowLongPtr(dat->hWindow,GWL_STYLE); exStyle &= ~WS_POPUP; exStyle |= WS_CHILD; SetWindowLongPtr(dat->hWindow,GWL_STYLE,exStyle); break; } case WM_TIMER: { if ( !IsWindowVisible(hwnd)) { DestroyWindow(hwnd); return 0; } dat->currentFrame++; if (dat->currentFrame >= dat->nFramesCount) dat->currentFrame = 0; if ( !IMMEDIATE_DRAW ) _AniAva_RenderAvatar( dat ); else _AniAva_InvalidateParent( dat ); KillTimer(hwnd,2); SetTimer(hwnd,2,dat->delaysInterval[dat->currentFrame]+1,NULL); return 0; } case WM_DESTROY: { _AniAva_Clear_ANIAVA_WINDOWINFO(dat); mir_free(dat); SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)NULL); break; } } return DefWindowProc(hwnd, msg, wParam, lParam); } #undef aacheck #undef aalock #undef aaunlock ///////////////////////////////////////////////////////////////// // some stub HWND WINAPI MyGetAncestor( HWND hWnd, UINT option ) { if ( option == GA_PARENT ) return GetParent( hWnd ); if ( option == GA_ROOTOWNER ) { HWND result = hWnd; while( true ) { HWND hParent = GetParent( result ); if ( !hParent ) return result; result = hParent; } } return NULL; }