From f920ef497f3299ae24fe783ce03bdd93b419f764 Mon Sep 17 00:00:00 2001 From: Kirill Volinsky Date: Fri, 18 May 2012 22:02:50 +0000 Subject: plugins folders renaming git-svn-id: http://svn.miranda-ng.org/main/trunk@60 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/Avs/CHANGELOG.AVS | 182 +++ plugins/Avs/README | 94 ++ plugins/Avs/README.SOURCE | 25 + plugins/Avs/acc.cpp | 908 ++++++++++++ plugins/Avs/acc.h | 44 + plugins/Avs/avs.rc | 251 ++++ plugins/Avs/avs_10.vcxproj | 310 +++++ plugins/Avs/avs_10.vcxproj.filters | 88 ++ plugins/Avs/commonheaders.h | 119 ++ plugins/Avs/image_utils.cpp | 757 ++++++++++ plugins/Avs/image_utils.h | 58 + plugins/Avs/license.txt | 340 +++++ plugins/Avs/main.cpp | 2684 ++++++++++++++++++++++++++++++++++++ plugins/Avs/mir_thread.cpp | 50 + plugins/Avs/mir_thread.h | 49 + plugins/Avs/options.cpp | 1147 +++++++++++++++ plugins/Avs/poll.cpp | 319 +++++ plugins/Avs/poll.h | 36 + plugins/Avs/res/avatar.ico | Bin 0 -> 2550 bytes plugins/Avs/resource.h | 60 + plugins/Avs/vc6.rc | 2 + plugins/Avs/version.h | 5 + plugins/Avs/version.rc | 113 ++ 23 files changed, 7641 insertions(+) create mode 100644 plugins/Avs/CHANGELOG.AVS create mode 100644 plugins/Avs/README create mode 100644 plugins/Avs/README.SOURCE create mode 100644 plugins/Avs/acc.cpp create mode 100644 plugins/Avs/acc.h create mode 100644 plugins/Avs/avs.rc create mode 100644 plugins/Avs/avs_10.vcxproj create mode 100644 plugins/Avs/avs_10.vcxproj.filters create mode 100644 plugins/Avs/commonheaders.h create mode 100644 plugins/Avs/image_utils.cpp create mode 100644 plugins/Avs/image_utils.h create mode 100644 plugins/Avs/license.txt create mode 100644 plugins/Avs/main.cpp create mode 100644 plugins/Avs/mir_thread.cpp create mode 100644 plugins/Avs/mir_thread.h create mode 100644 plugins/Avs/options.cpp create mode 100644 plugins/Avs/poll.cpp create mode 100644 plugins/Avs/poll.h create mode 100644 plugins/Avs/res/avatar.ico create mode 100644 plugins/Avs/resource.h create mode 100644 plugins/Avs/vc6.rc create mode 100644 plugins/Avs/version.h create mode 100644 plugins/Avs/version.rc (limited to 'plugins/Avs') diff --git a/plugins/Avs/CHANGELOG.AVS b/plugins/Avs/CHANGELOG.AVS new file mode 100644 index 0000000000..a673bdff73 --- /dev/null +++ b/plugins/Avs/CHANGELOG.AVS @@ -0,0 +1,182 @@ + + Version history: + +0.0.2.6 - 2006/08/03 + + * added file hashing (pescuma) + * rewrote the picture loading code. It is now running in a background thread + at low priority. The loader thread is the only instance which actually writes + to the cache entries. It does this with a small delay and notifies the hook + subscribers when a picture has been loaded. While it is not fully loaded, + the fallback protocol picture may be used. This makes connecting or + loading the clist with lots of avatars significantly faster at the cost + of delayed picture appearance. + It may also help with the sporadic lockups since the part of the code which + runs in the callers thread context is now much simpler. + +0.0.2.5 - 2006/07/30 + + * fixed updater support + * moved InitPolls() to ModulesLoaded() to avoid a problem with missing + core services. + * fixed keyboard navigation glitch in the Customize->Contact pictures dialog. + +0.0.2.4 - 2006/07/30 + + * bugfix (missing protocol avatars after migrating to new relpath) + * added proper version info resource record (showing ansi/unicode) + * added project files for Visual C++ 6 (source code release only) + +0.0.2.3 - 2006/07/11 + + * should now work better with metacontacts. Subcontact avatar changes are + "forwarded" to the master contact. + + * relative path names are now relative to the DATABASE directory, not + Mirandas root directory. + + * missing picture files and/or invalid database entries will now result + in a refresh request. + +0.0.2.0 introduced big internal changes. New request queue system (written by Pescuma) + loadavatars no longer depends on imgdecoder.dll, but needs a recent png2dib + plugin (supplied with Miranda 0.4.3.x nightly builds) + +0.0.1.12 - 0.0.2.2 - released via nightlys (see SVN changelog) + +0.0.1.11 - 2005/11/10 + + + added patch by pescuma for making normal avatars transparent (or semi- + transparent). + There are global (under Customize->Contact pictures) and per contact settings + (contact menu -> Contact picture...). In order to get good results, you will need + to tweak the settings a bit - it works for most avatar pictures with a uni-color + background, but it really depends on the image. + + + built with Visual Studio 2005 (Visual C++ 8.0). The DLL is now statically + linked, so they are somewhat bigger, but it avoids error messages because of the + new VC++ 8 runtime which is not installed on most PCs. + + + added unicode version (loadavatarsW.dll) which will display unicode nicknames in + the "per user" contact picture dialog. + + + added services and events to manage own avatar pictures. See m_avatars.h for more + information. Also, the drawing service was enhanced to allow drawing your own + avatars. + +0.0.1.10 - 2005/10/19 + + ! added support for jabber avatar updates. + + + better cleanup on Unload() + +0.0.1.9 - 2005/10/18 + + * accept .dat as valid image extension (sometimes, icq avatars are saved as .dat + files, even if they are actually JPG images (reason unknown, but we can load + them anyway. MS_UTILS_LOADBITMAP will return an error when the file is not + really a valid image - the extension doesn't matter). + +0.0.1.8 - 2005/10/17 + + ! fix. Don't show tray warning messages about invalid avatar file size when + the option "Show warning messages" on Customize->Contact pictures is unchecked. + + * changed way how protocols are enabled/disabled. After protocols have been added + or removed, "new" protocols are automatically enabled, so you don't have to do + that manually any more. + + * you can completely disable the size limit check by setting the size limit on + Customize->Contact List to 0 (zero). + +0.0.1.7 - 2005/10/16 + + * bugfix: check filenames more aggressivly. + + * bugfix: premultiply was broken (thanks FYR) + + + added a service to draw a contacts picture to a target device context. + See m_avatars.h for description on how to use that service. + + + added file size limitation to the picture loader. The default are 70Kbytes, + should be enough for most avatar pictures (they have to be small images). + The limit can be increased on the option page (Customize->contact pictures). + +0.0.1.5 - 2005/09/15 + + + added updater support + + * moved option page to Customize->Contact pictures + +0.0.1.4 - 2005/08/31 + + * cache reallocs will now send avatar change notifies (realloc() may move the + cache in memory, so pointers to avatar cache entries may become invalid). + + ! bugfix - badly written protocols which load themself too late are now + skipped and won't cause troubles because of the protocol list reallocation. + +0.0.1.3 - 2005/08/30 + + + ability to lock the avatar (protect from automatic updates). Useful, if you + have set a local contact picture and don't want it to be overwritten. + Actually, the feature does not really lock the avatar updating - it will + only save the picture from being overwritten. + + + added UI to set/change/lock avatar. DEVELOPERS please check m_avatars.h on how + to use the service from your own plugins. There are now services to lock and/or + set the avatar, either by providing a valid image filename or by using a file + selection dialog. Also, there is a service to call the avatar dialog for a + hContact. + + * struct avatarCacheEntry has changed too. It now provides szFilename which + will contain the full image filename of the current avatar in use. + + * added a menu item to the contact menu which allows to set avatar options (local + picture, locked avatar, and a "hidden" attribute). + The hidden attribute is set in the struct avatarCacheEntry + + + added support for PNG images (imgdecoder.dll needs to be present in either the + main miranda directory or the \Plugins subfolder. Transparent PNG images are + supported and the avatar service will perform the premultiplication of alpha + values so that the image can (and should) be rendered with the AlphaBlend() + API. + +0.0.1.2 - 2005/08/20 + + + the service now creates and uses relative filenames for all contact pictures, + including protocol avatars, if possible. + + * for protocols which are in "invisible" status, no active avatar fetching + is performed. This is to avoid problems with MSN and privacy issues for + other protocols (if you're invisible, then the protocol should not initiate + any outbound communications which may require direct p2p connections). + + * an option page has been added. Currently, you can: + + + select, for which protocols the service should ACTIVELY fetch avatars. + If you set a protocols to "inactive" (uncheck it), already existing + avatars for contacts of that protocol will continue to work, but the + service will no longer actively refresh avatars. + + + set protocol pictures (pseudo avatars). You can select local pictures + for each installed protocol. These will be used as a fallback then, if + no avatar is available for a contact. You can also remove these pictures. + + * don't fetch avatars for contacts which are either blocked or set on the + invisibility list (ApparentMode == ID_STATUS_OFFLINE). Reason: No active + outbound communications w/o user intervention should be initiated for blocked + contacts. + + + added support for the updater plugin. + +0.0.1.1 - 2005/08/06 + + * changed API. Don't return HBITMAP directly, but instead, it returns + a pointer to a struct avatarCacheEntry which can be used to obtain + the bitmap handle. + +0.0.1.0 - 2005/08/05 + + * initial release, cvs import. + diff --git a/plugins/Avs/README b/plugins/Avs/README new file mode 100644 index 0000000000..d82d1d7fec --- /dev/null +++ b/plugins/Avs/README @@ -0,0 +1,94 @@ + + Load avatars 0.0.1.0 + -------------------- + +This is a SERVICE plugin, which means, it doesn't provide anything useful +on its own except for a few service(s) and event(s) which can be used by +other plugins. + +What it does? +------------- + +It loads avatars on demand and maintains an internal cache of avatar +bitmap handles. It also handles avatar changes transparently and can +notify event subscribers about avatar changes. + +How it works? +------------- + +The service MS_AV_GETAVATARBITMAP returns a pointer to a cache entry, if an +avatar is present for that contact. The service MAY return 0, in which +case, there is no valid avatar yet. However, that doesn't mean there +isn't ANY avatar, just that the avatar is not yet ready for use. When +someone calls the service requesting an avatar, the plugin will try +to get the avatar (if possible) and notify all subscribers via a +hookable event as soon as the avatar is ready. If the avatar is +already in the cache, it simply returns the cached entry. + +Whenever an avatar changes, the plugin fires an event, passing the +contacts handle in wParam and a data structure with the avatar information +in lParam. Plugins which use the bitmap handles returned by +MS_AV_GETAVATARBITMAP MUST subscribe to ME_AV_AVATARCHANGED, because the +original bitmap handle may become invalid when the avatar changes. + +Fetching avatars is done in a separate thread with reasonable delays to +avoid getting into troubles with flood protection(s). Avatars are cached +"in memory". + + +The included clist_nicer_plus.dll is a demonstration of how the avatar +service works and can be used by developers. Having a central instance +which maintains avatars saves resources and increases performance. + + +// example, how to use it (FOR DEVS only) + +#include "m_avatars.h" + +struct avatarCacheEntry *ace = 0; +HBITMAP hbmAvatar = 0; + +ace = (struct avatarCacheEntry *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)hContact, 0); + +/* + now, check the return value. if it is 0, then the avatar is not yet ready or unavailble + for that contact. if it was only "not ready", your plugin will be notified by the + hookable event ME_AV_AVATARCHANGED + + if the return value is != 0, then it is a valid bitmap handle. DON'T DESTROY IT IN YOUR CODE +*/ + + +/* + * event function + * initialise with: + * HANDLE hEvent = HookEvent(ME_AV_AVATARCHANGED, AvatarChanged); + */ + +static int AvatarChanged(WPARAM wParam, LPARAM lParam) +{ + struct avatarCacheEntry *ace = (struct avatarCacheEntry *)lParam; + HANDLE hContact = (HANDLE)wParam; + + if(ace == NULL) + return 0; + if(ace->cbSize != sizeof(struct avatarCacheEntry)) + return 0; // safety check(s) + + HBITMAP hbmAvatar = ace->hbmPic; + ... + ... +} + + +TODO: maybe more intelligent cache managment, including auto-expire of avatars which + have not been used for a while. + + +Copyright and license: +---------------------- + +This plugin is released under the terms of the GNU general public license V2 or any later +version. + +Written, 2005 by Nightwish, silvercircle@gmail.com diff --git a/plugins/Avs/README.SOURCE b/plugins/Avs/README.SOURCE new file mode 100644 index 0000000000..5f87280afe --- /dev/null +++ b/plugins/Avs/README.SOURCE @@ -0,0 +1,25 @@ + +Source code for the loadavatars (avatar service plugin) is available via +anonymous SVN at: + +http://svn.berlios.de/svnroot/repos/mimplugins/trunk/avs/ + +You need a SVN client to access this, I recommend TortoiseSVN for Windows which +is easy to use and provides a good UI. + +To compile, you need a complete checkout of Mirandas main source repository and +Visual C++ 6 with service pack 6 + the latest platform SDK installed. + +Project files are provided for Visual C++ 6 and Visual Studio 2005 (aka Visual +C++ 8). The sources may compile with Mingw32/GCC, but this is not supported at +this time. + +This plugin for the Miranda Instant messenger is licensed under the GNU +General Public License Version 2. + +The code was written by: + +Nightwish (silvercircle@gmail.com) (original idea and implementation) +Pescuma (major rewrite for version 0.0.2.0 with new request/poll code and most of + the image manipulation utilities). + diff --git a/plugins/Avs/acc.cpp b/plugins/Avs/acc.cpp new file mode 100644 index 0000000000..f62009b43d --- /dev/null +++ b/plugins/Avs/acc.cpp @@ -0,0 +1,908 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2004 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. +*/ + +#include "commonheaders.h" + +extern FI_INTERFACE *fei; + +int GetImageFormat(TCHAR *filename); +INT_PTR DrawAvatarPicture(WPARAM wParam, LPARAM lParam); +INT_PTR GetAvatarBitmap(WPARAM wParam, LPARAM lParam); +INT_PTR GetMyAvatar(WPARAM wParam, LPARAM lParam); +void InternalDrawAvatar(AVATARDRAWREQUEST *r, HBITMAP hbm, LONG bmWidth, LONG bmHeight, DWORD dwFlags); + + +#define DM_AVATARCHANGED (WM_USER + 20) +#define DM_MYAVATARCHANGED (WM_USER + 21) + +#define GIF_DISPOSAL_UNSPECIFIED 0 +#define GIF_DISPOSAL_LEAVE 1 +#define GIF_DISPOSAL_BACKGROUND 2 +#define GIF_DISPOSAL_PREVIOUS 3 + +typedef struct +{ + HANDLE hContact; + char proto[64]; + HANDLE hHook; + HANDLE hHookMy; + HFONT hFont; // font + COLORREF borderColor; + COLORREF bkgColor; + COLORREF avatarBorderColor; + int avatarRoundCornerRadius; + TCHAR noAvatarText[128]; + BOOL respectHidden; + BOOL showingFlash; + BOOL resizeIfSmaller; + BOOL fAero; + BOOL showingAnimatedGif; + + struct { + HBITMAP *hbms; + int *times; + + FIMULTIBITMAP *multi; + FIBITMAP *dib; + int frameCount; + int logicalWidth; + int logicalHeight; + BOOL loop; + RGBQUAD background; + BOOL started; + + struct { + int num; + int top; + int left; + int width; + int height; + int disposal_method; + } frame; + } ag; + +} ACCData; + + +void ResizeFlash(HWND hwnd, ACCData* data) +{ + if ((data->hContact != NULL || data->proto[0] != '\0') + && ServiceExists(MS_FAVATAR_RESIZE)) + { + RECT rc; + GetClientRect(hwnd, &rc); + + if (data->borderColor != -1 || data->avatarBorderColor != -1) + { + rc.left ++; + rc.right -= 2; + rc.top ++; + rc.bottom -= 2; + } + + FLASHAVATAR fa = {0}; + fa.hContact = data->hContact; + fa.cProto = data->proto; + fa.hParentWindow = hwnd; + fa.id = 1675; + CallService(MS_FAVATAR_RESIZE, (WPARAM)&fa, (LPARAM)&rc); + CallService(MS_FAVATAR_SETPOS, (WPARAM)&fa, (LPARAM)&rc); + } +} + +void SetBkgFlash(HWND hwnd, ACCData* data) +{ + if ((data->hContact != NULL || data->proto[0] != '\0') + && ServiceExists(MS_FAVATAR_SETBKCOLOR)) + { + FLASHAVATAR fa = {0}; + fa.hContact = data->hContact; + fa.cProto = data->proto; + fa.hParentWindow = hwnd; + fa.id = 1675; + + if (data->bkgColor != -1) + CallService(MS_FAVATAR_SETBKCOLOR, (WPARAM)&fa, (LPARAM)data->bkgColor); + else + CallService(MS_FAVATAR_SETBKCOLOR, (WPARAM)&fa, (LPARAM)RGB(255,255,255)); + } +} + +void DestroyFlash(HWND hwnd, ACCData* data) +{ + if (!data->showingFlash) + return; + + if ((data->hContact != NULL || data->proto[0] != '\0') + && ServiceExists(MS_FAVATAR_DESTROY)) + { + FLASHAVATAR fa = {0}; + fa.hContact = data->hContact; + fa.cProto = data->proto; + fa.hParentWindow = hwnd; + fa.id = 1675; + CallService(MS_FAVATAR_DESTROY, (WPARAM)&fa, 0); + } + + data->showingFlash = FALSE; +} + +void StartFlash(HWND hwnd, ACCData* data) +{ + if (!ServiceExists(MS_FAVATAR_MAKE)) + return; + + int format; + if (data->hContact != NULL) + { + format = DBGetContactSettingWord(data->hContact, "ContactPhoto", "Format", 0); + } + else if (data->proto[0] != '\0') + { + protoPicCacheEntry *ace = NULL; + for(int i = 0; i < g_MyAvatars.getCount(); i++) + { + if (!lstrcmpA(data->proto, g_MyAvatars[i].szProtoname)) + { + ace = &g_MyAvatars[i]; + break; + } + } + + if (ace != NULL && ace->szFilename != NULL) + format = GetImageFormat(ace->szFilename); + else + format = 0; + } + else + return; + + if (format != PA_FORMAT_XML && format != PA_FORMAT_SWF) + return; + + FLASHAVATAR fa = {0}; + fa.hContact = data->hContact; + fa.cProto = data->proto; + fa.hParentWindow = hwnd; + fa.id = 1675; + CallService(MS_FAVATAR_MAKE, (WPARAM)&fa, 0); + + if (fa.hWindow == NULL) + return; + + data->showingFlash = TRUE; + ResizeFlash(hwnd, data); + SetBkgFlash(hwnd, data); +} + +BOOL AnimatedGifGetData(ACCData* data) +{ + FIBITMAP *page = fei->FI_LockPage(data->ag.multi, 0); + if (page == NULL) + return FALSE; + + // Get info + FITAG *tag = NULL; + if (!fei->FI_GetMetadata(FIMD_ANIMATION, page, "LogicalWidth", &tag)) + goto ERR; + data->ag.logicalWidth = *(WORD *)fei->FI_GetTagValue(tag); + + if (!fei->FI_GetMetadata(FIMD_ANIMATION, page, "LogicalHeight", &tag)) + goto ERR; + data->ag.logicalHeight = *(WORD *)fei->FI_GetTagValue(tag); + + if (!fei->FI_GetMetadata(FIMD_ANIMATION, page, "Loop", &tag)) + goto ERR; + data->ag.loop = (*(LONG *)fei->FI_GetTagValue(tag) > 0); + + if (fei->FI_HasBackgroundColor(page)) + fei->FI_GetBackgroundColor(page, &data->ag.background); + + fei->FI_UnlockPage(data->ag.multi, page, FALSE); + return TRUE; + +ERR: + fei->FI_UnlockPage(data->ag.multi, page, FALSE); + return FALSE; +} + +void AnimatedGifDispodeFrame(ACCData* data) +{ + if (data->ag.frame.disposal_method == GIF_DISPOSAL_PREVIOUS) + { + // TODO + } + else if (data->ag.frame.disposal_method == GIF_DISPOSAL_BACKGROUND) + { + for (int y = 0; y < data->ag.frame.height; y++) + { + RGBQUAD *scanline = (RGBQUAD *) fei->FI_GetScanLine(data->ag.dib, + data->ag.logicalHeight - (y + data->ag.frame.top) - 1) + data->ag.frame.left; + for (int x = 0; x < data->ag.frame.width; x++) + *scanline++ = data->ag.background; + } + } +} + +void AnimatedGifMountFrame(ACCData* data, int page) +{ + data->ag.frame.num = page; + + if (data->ag.hbms[page] != NULL) + { + data->ag.frame.disposal_method = GIF_DISPOSAL_LEAVE; + return; + } + + FIBITMAP *dib = fei->FI_LockPage(data->ag.multi, data->ag.frame.num); + if (dib == NULL) + return; + + FITAG *tag = NULL; + if (fei->FI_GetMetadata(FIMD_ANIMATION, dib, "FrameLeft", &tag)) + data->ag.frame.left = *(WORD *)fei->FI_GetTagValue(tag); + else + data->ag.frame.left = 0; + + if (fei->FI_GetMetadata(FIMD_ANIMATION, dib, "FrameTop", &tag)) + data->ag.frame.top = *(WORD *)fei->FI_GetTagValue(tag); + else + data->ag.frame.top = 0; + + if (fei->FI_GetMetadata(FIMD_ANIMATION, dib, "FrameTime", &tag)) + data->ag.times[page] = *(LONG *)fei->FI_GetTagValue(tag); + else + data->ag.times[page] = 0; + + if (fei->FI_GetMetadata(FIMD_ANIMATION, dib, "DisposalMethod", &tag)) + data->ag.frame.disposal_method = *(BYTE *)fei->FI_GetTagValue(tag); + else + data->ag.frame.disposal_method = 0; + + data->ag.frame.width = fei->FI_GetWidth(dib); + data->ag.frame.height = fei->FI_GetHeight(dib); + + + //decode page + int palSize = fei->FI_GetColorsUsed(dib); + RGBQUAD *pal = fei->FI_GetPalette(dib); + bool have_transparent = false; + int transparent_color = -1; + if( fei->FI_IsTransparent(dib) ) { + int count = fei->FI_GetTransparencyCount(dib); + BYTE *table = fei->FI_GetTransparencyTable(dib); + for( int i = 0; i < count; i++ ) { + if( table[i] == 0 ) { + have_transparent = true; + transparent_color = i; + break; + } + } + } + + //copy page data into logical buffer, with full alpha opaqueness + for( int y = 0; y < data->ag.frame.height; y++ ) { + RGBQUAD *scanline = (RGBQUAD *)fei->FI_GetScanLine(data->ag.dib, data->ag.logicalHeight - (y + data->ag.frame.top) - 1) + data->ag.frame.left; + BYTE *pageline = fei->FI_GetScanLine(dib, data->ag.frame.height - y - 1); + for( int x = 0; x < data->ag.frame.width; x++ ) { + if( !have_transparent || *pageline != transparent_color ) { + *scanline = pal[*pageline]; + scanline->rgbReserved = 255; + } + scanline++; + pageline++; + } + } + + data->ag.hbms[page] = fei->FI_CreateHBITMAPFromDIB(data->ag.dib); + + fei->FI_UnlockPage(data->ag.multi, dib, FALSE); +} + +void AnimatedGifDeleteTmpValues(ACCData* data) +{ + if (data->ag.multi != NULL) + { + fei->FI_CloseMultiBitmap(data->ag.multi, 0); + data->ag.multi = NULL; + } + + if (data->ag.dib != NULL) + { + fei->FI_Unload(data->ag.dib); + data->ag.dib = NULL; + } +} + +void DestroyAnimatedGif(HWND hwnd, ACCData* data) +{ + if (!data->showingAnimatedGif) + return; + + AnimatedGifDeleteTmpValues(data); + + if (data->ag.hbms != NULL) + { + for (int i = 0; i < data->ag.frameCount; i++) + if (data->ag.hbms[i] != NULL) + DeleteObject(data->ag.hbms[i]); + + free(data->ag.hbms); + data->ag.hbms = NULL; + } + + if (data->ag.times != NULL) + { + free(data->ag.times); + data->ag.times = NULL; + } + + data->showingAnimatedGif = FALSE; +} + +void StartAnimatedGif(HWND hwnd, ACCData* data) +{ + if (fei == NULL) + return; + + int x, y; + AVATARCACHEENTRY *ace = NULL; + + if (data->hContact != NULL) + ace = (AVATARCACHEENTRY *) GetAvatarBitmap((WPARAM) data->hContact, 0); + else + ace = (AVATARCACHEENTRY *) GetMyAvatar(0, (LPARAM) data->proto); + + if (ace == NULL) + return; + + int format = GetImageFormat(ace->szFilename); + if (format != PA_FORMAT_GIF) + return; + + FREE_IMAGE_FORMAT fif = fei->FI_GetFileTypeT(ace->szFilename, 0); + if(fif == FIF_UNKNOWN) + fif = fei->FI_GetFIFFromFilenameT(ace->szFilename); + + data->ag.multi = fei->FI_OpenMultiBitmapT(fif, ace->szFilename, FALSE, TRUE, FALSE, GIF_LOAD256); + if (data->ag.multi == NULL) + return; + + data->ag.frameCount = fei->FI_GetPageCount(data->ag.multi); + if (data->ag.frameCount <= 1) + goto ERR; + + if (!AnimatedGifGetData(data)) + goto ERR; + + //allocate entire logical area + data->ag.dib = fei->FI_Allocate(data->ag.logicalWidth, data->ag.logicalHeight, 32, 0, 0, 0); + if (data->ag.dib == NULL) + goto ERR; + + //fill with background color to start + for (y = 0; y < data->ag.logicalHeight; y++) + { + RGBQUAD *scanline = (RGBQUAD *) fei->FI_GetScanLine(data->ag.dib, y); + for (x = 0; x < data->ag.logicalWidth; x++) + *scanline++ = data->ag.background; + } + + data->ag.hbms = (HBITMAP *) malloc(sizeof(HBITMAP) * data->ag.frameCount); + memset(data->ag.hbms, 0, sizeof(HBITMAP) * data->ag.frameCount); + + data->ag.times = (int *) malloc(sizeof(int) * data->ag.frameCount); + memset(data->ag.times, 0, sizeof(int) * data->ag.frameCount); + + AnimatedGifMountFrame(data, 0); + + data->showingAnimatedGif = TRUE; + + return; +ERR: + fei->FI_CloseMultiBitmap(data->ag.multi, 0); + data->ag.multi = NULL; +} + +void DestroyAnimation(HWND hwnd, ACCData* data) +{ + DestroyFlash(hwnd, data); + DestroyAnimatedGif(hwnd, data); +} + +void StartAnimation(HWND hwnd, ACCData* data) +{ + StartFlash(hwnd, data); + + if (!data->showingFlash) + StartAnimatedGif(hwnd, data); +} + +BOOL ScreenToClient(HWND hWnd, LPRECT lpRect) +{ + BOOL ret; + + POINT pt; + + pt.x = lpRect->left; + pt.y = lpRect->top; + + ret = ScreenToClient(hWnd, &pt); + + if (!ret) return ret; + + lpRect->left = pt.x; + lpRect->top = pt.y; + + + pt.x = lpRect->right; + pt.y = lpRect->bottom; + + ret = ScreenToClient(hWnd, &pt); + + lpRect->right = pt.x; + lpRect->bottom = pt.y; + + return ret; +} + +static void Invalidate(HWND hwnd) +{ + ACCData* data = (ACCData *) GetWindowLongPtr(hwnd, 0); + if (data->bkgColor == -1) + { + HWND parent = GetParent(hwnd); + RECT rc; + GetWindowRect(hwnd, &rc); + ScreenToClient(parent, &rc); + InvalidateRect(parent, &rc, TRUE); + } + InvalidateRect(hwnd, NULL, TRUE); +} + +static void NotifyAvatarChange(HWND hwnd) +{ + PSHNOTIFY pshn = {0}; + pshn.hdr.idFrom = GetDlgCtrlID(hwnd); + pshn.hdr.hwndFrom = hwnd; + pshn.hdr.code = NM_AVATAR_CHANGED; + pshn.lParam = 0; + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) &pshn); +} + +static void DrawText(HDC hdc, HFONT hFont, const RECT &rc, const TCHAR *text) +{ + HGDIOBJ oldFont = SelectObject(hdc, hFont); + + // Get text rectangle + RECT tr = rc; + tr.top += 10; + tr.bottom -= 10; + tr.left += 10; + tr.right -= 10; + + // Calc text size + RECT tr_ret = tr; + DrawText(hdc, text, -1, &tr_ret, + DT_WORDBREAK | DT_NOPREFIX | DT_CENTER | DT_CALCRECT); + + // Calc needed size + tr.top += ((tr.bottom - tr.top) - (tr_ret.bottom - tr_ret.top)) / 2; + tr.bottom = tr.top + (tr_ret.bottom - tr_ret.top); + DrawText(hdc, text, -1, &tr, + DT_WORDBREAK | DT_NOPREFIX | DT_CENTER); + + SelectObject(hdc, oldFont); +} + +static LRESULT CALLBACK ACCWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + ACCData* data = (ACCData *) GetWindowLongPtr(hwnd, 0); + switch(msg) + { + case WM_NCCREATE: + { + SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | BS_OWNERDRAW); + SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_TRANSPARENT); + + data = (ACCData*) mir_alloc(sizeof(ACCData)); + if (data == NULL) + return FALSE; + SetWindowLongPtr(hwnd, 0, (LONG_PTR)data); + + ZeroMemory(data, sizeof(ACCData)); + data->hHook = HookEventMessage(ME_AV_AVATARCHANGED, hwnd, DM_AVATARCHANGED); + data->hHookMy = HookEventMessage(ME_AV_MYAVATARCHANGED, hwnd, DM_MYAVATARCHANGED); + data->hFont = (HFONT) GetStockObject(DEFAULT_GUI_FONT); + data->borderColor = -1; + data->bkgColor = -1; + data->avatarBorderColor = -1; + data->respectHidden = TRUE; + data->showingFlash = FALSE; + data->resizeIfSmaller = TRUE; + data->showingAnimatedGif = FALSE; + data->fAero = FALSE; + + return TRUE; + } + case WM_NCDESTROY: + { + DestroyAnimation(hwnd, data); + if (data) + { + UnhookEvent(data->hHook); + UnhookEvent(data->hHookMy); + mir_free(data); + } + SetWindowLongPtr(hwnd, 0, (LONG_PTR)NULL); + break; + } + case WM_SETFONT: + { + data->hFont = (HFONT)wParam; + Invalidate(hwnd); + break; + } + case AVATAR_SETCONTACT: + { + DestroyAnimation(hwnd, data); + + data->hContact = (HANDLE) lParam; + lstrcpynA(data->proto, (char*) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)data->hContact, 0), sizeof(data->proto)); + + StartAnimation(hwnd, data); + + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + return TRUE; + } + case AVATAR_SETPROTOCOL: + { + DestroyAnimation(hwnd, data); + + data->hContact = NULL; + if (lParam == NULL) + data->proto[0] = '\0'; + else + lstrcpynA(data->proto, (char *) lParam, sizeof(data->proto)); + + StartAnimation(hwnd, data); + + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + return TRUE; + } + case AVATAR_SETBKGCOLOR: + { + data->bkgColor = (COLORREF) lParam; + if (data->showingFlash) + SetBkgFlash(hwnd, data); + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + return TRUE; + } + case AVATAR_SETBORDERCOLOR: + { + data->borderColor = (COLORREF) lParam; + if (data->showingFlash) + ResizeFlash(hwnd, data); + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + return TRUE; + } + case AVATAR_SETAVATARBORDERCOLOR: + { + data->avatarBorderColor = (COLORREF) lParam; + if (data->showingFlash) + ResizeFlash(hwnd, data); + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + return TRUE; + } + case AVATAR_SETAVATARROUNDCORNERRADIUS: + { + data->avatarRoundCornerRadius = (int) lParam; + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + return TRUE; + } + case AVATAR_SETNOAVATARTEXT: + { + lstrcpyn(data->noAvatarText, TranslateTS((TCHAR*) lParam), SIZEOF(data->noAvatarText)); + Invalidate(hwnd); + return TRUE; + } + case AVATAR_RESPECTHIDDEN: + { + data->respectHidden = (BOOL) lParam; + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + return TRUE; + } + case AVATAR_SETRESIZEIFSMALLER: + { + data->resizeIfSmaller = (BOOL) lParam; + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + return TRUE; + } + + case AVATAR_SETAEROCOMPATDRAWING: + data->fAero = lParam; + return(TRUE); + + case AVATAR_GETUSEDSPACE: + { + int *width = (int *)wParam; + int *height = (int *)lParam; + + RECT rc; + GetClientRect(hwnd, &rc); + + // Get avatar + if (data->showingFlash && ServiceExists(MS_FAVATAR_GETINFO)) + { + FLASHAVATAR fa = {0}; + fa.hContact = data->hContact; + fa.cProto = data->proto; + fa.hParentWindow = hwnd; + fa.id = 1675; + CallService(MS_FAVATAR_GETINFO, (WPARAM)&fa, 0); + if (fa.hWindow != NULL) + { + *width = rc.right - rc.left; + *height = rc.bottom - rc.top; + return TRUE; + } + } + + avatarCacheEntry *ace; + if (data->hContact == NULL) + ace = (avatarCacheEntry *) CallService(MS_AV_GETMYAVATAR, 0, (LPARAM) data->proto); + else + ace = (avatarCacheEntry *) CallService(MS_AV_GETAVATARBITMAP, (WPARAM) data->hContact, 0); + + if (ace == NULL || ace->bmHeight == 0 || ace->bmWidth == 0 + || (data->respectHidden && (ace->dwFlags & AVS_HIDEONCLIST))) + { + *width = 0; + *height = 0; + return TRUE; + } + + // Get its size + int targetWidth = rc.right - rc.left; + int targetHeight = rc.bottom - rc.top; + + if (!data->resizeIfSmaller && ace->bmHeight <= targetHeight && ace->bmWidth <= targetWidth) + { + *height = ace->bmHeight; + *width = ace->bmWidth; + } + else if (ace->bmHeight > ace->bmWidth) + { + float dScale = targetHeight / (float)ace->bmHeight; + *height = targetHeight; + *width = (int) (ace->bmWidth * dScale); + } + else + { + float dScale = targetWidth / (float)ace->bmWidth; + *height = (int) (ace->bmHeight * dScale); + *width = targetWidth; + } + + return TRUE; + } + case DM_AVATARCHANGED: + { + if (data->hContact == (HANDLE) wParam) + { + DestroyAnimation(hwnd, data); + StartAnimation(hwnd, data); + + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + } + break; + } + case DM_MYAVATARCHANGED: + { + if (data->hContact == NULL && strcmp(data->proto, (char*) wParam) == 0) + { + DestroyAnimation(hwnd, data); + StartAnimation(hwnd, data); + + NotifyAvatarChange(hwnd); + Invalidate(hwnd); + } + break; + } + case WM_NCPAINT: + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + if (hdc == NULL) + break; + + int oldBkMode = SetBkMode(hdc, TRANSPARENT); + SetStretchBltMode(hdc, HALFTONE); + + RECT rc; + GetClientRect(hwnd, &rc); + + // Draw background + if (data->bkgColor != -1) + { + HBRUSH hbrush = CreateSolidBrush(data->bkgColor); + FillRect(hdc, &rc, hbrush); + DeleteObject(hbrush); + } + + if (data->hContact == NULL && data->proto[0] == '\0' + && DBGetContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 1)) + { + DrawText(hdc, data->hFont, rc, TranslateT("Protocols have different avatars")); + } + + // Has a flash avatar + else if (data->showingFlash) + { + // Don't draw + + // Draw control border if needed + if (data->borderColor == -1 && data->avatarBorderColor != -1) + { + HBRUSH hbrush = CreateSolidBrush(data->avatarBorderColor); + FrameRect(hdc, &rc, hbrush); + DeleteObject(hbrush); + } + } + + // Has an animated gif + // Has a "normal" image + else + { + // Draw avatar + AVATARDRAWREQUEST avdrq = {0}; + avdrq.cbSize = sizeof(avdrq); + avdrq.rcDraw = rc; + avdrq.hContact = data->hContact; + avdrq.szProto = data->proto; + avdrq.hTargetDC = hdc; + avdrq.dwFlags = AVDRQ_HIDEBORDERONTRANSPARENCY + | (data->respectHidden ? AVDRQ_RESPECTHIDDEN : 0) + | (data->hContact != NULL ? 0 : AVDRQ_OWNPIC) + | (data->avatarBorderColor == -1 ? 0 : AVDRQ_DRAWBORDER) + | (data->avatarRoundCornerRadius <= 0 ? 0 : AVDRQ_ROUNDEDCORNER) + | (data->fAero ? AVDRQ_AERO : 0) + | (data->resizeIfSmaller ? 0 : AVDRQ_DONTRESIZEIFSMALLER); + avdrq.clrBorder = data->avatarBorderColor; + avdrq.radius = data->avatarRoundCornerRadius; + + INT_PTR ret; + if (data->showingAnimatedGif) + { + InternalDrawAvatar(&avdrq, data->ag.hbms[data->ag.frame.num], data->ag.logicalWidth, data->ag.logicalHeight, 0); + ret = 1; + + if (!data->ag.started) + { + SetTimer(hwnd, 0, data->ag.times[data->ag.frame.num], NULL); + data->ag.started = TRUE; + } + } + else + ret = DrawAvatarPicture(0, (LPARAM)&avdrq); + + if (ret == 0) + DrawText(hdc, data->hFont, rc, data->noAvatarText); + } + + // Draw control border + if (data->borderColor != -1) + { + HBRUSH hbrush = CreateSolidBrush(data->borderColor); + FrameRect(hdc, &rc, hbrush); + DeleteObject(hbrush); + } + + SetBkMode(hdc, oldBkMode); + + EndPaint(hwnd, &ps); + return TRUE; + } + case WM_ERASEBKGND: + { + HDC hdc = (HDC) wParam; + RECT rc; + GetClientRect(hwnd, &rc); + + // Draw background + if (data->bkgColor != -1) + { + HBRUSH hbrush = CreateSolidBrush(data->bkgColor); + FillRect(hdc, &rc, hbrush); + DeleteObject(hbrush); + } + + // Draw control border + if (data->borderColor != -1) + { + HBRUSH hbrush = CreateSolidBrush(data->borderColor); + FrameRect(hdc, &rc, hbrush); + DeleteObject(hbrush); + } + + return TRUE; + } + case WM_SIZE: + { + if (data->showingFlash) + ResizeFlash(hwnd, data); + InvalidateRect(hwnd, NULL, TRUE); + break; + } + case WM_TIMER: + { + if (wParam != 0) + break; + KillTimer(hwnd, 0); + + if (!data->showingAnimatedGif) + break; + + AnimatedGifDispodeFrame(data); + + int frame = data->ag.frame.num + 1; + if (frame >= data->ag.frameCount) + { + // Don't need fi data no more + AnimatedGifDeleteTmpValues(data); + frame = 0; + } + AnimatedGifMountFrame(data, frame); + + data->ag.started = FALSE; + InvalidateRect(hwnd, NULL, FALSE); + + break; + } + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + + +int LoadACC() +{ + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpszClassName = AVATAR_CONTROL_CLASS; + wc.lpfnWndProc = ACCWndProc; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.cbWndExtra = sizeof(ACCData*); + wc.hbrBackground = 0; + wc.style = CS_GLOBALCLASS; + RegisterClassEx(&wc); + return 0; +} diff --git a/plugins/Avs/acc.h b/plugins/Avs/acc.h new file mode 100644 index 0000000000..11b235adc0 --- /dev/null +++ b/plugins/Avs/acc.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2006 Ricardo Pescuma Domenecci, Nightwish + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + + +#ifndef __ACC_H__ +# define __ACC_H__ + +class A2T +{ + TCHAR* buf; + +public: + A2T( const char* s ) : buf( mir_a2t( s )) {} + A2T( const char* s, int cp ) : buf( mir_a2t_cp( s, cp )) {} + ~A2T() { mir_free(buf); } + + __forceinline operator TCHAR*() const + { return buf; + } +}; + +INT_PTR avSetAvatar( HANDLE hContact, TCHAR* tszPath ); +INT_PTR avSetMyAvatar( char* szProto, TCHAR* tszPath ); + +int LoadACC(); + + +#endif // __ACC_H__ diff --git a/plugins/Avs/avs.rc b/plugins/Avs/avs.rc new file mode 100644 index 0000000000..e1818ede8d --- /dev/null +++ b/plugins/Avs/avs.rc @@ -0,0 +1,251 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Neutral resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OPTIONS_PICTS DIALOGEX 0, 0, 299, 214 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_CLIPSIBLINGS +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CTEXT "The pictures you can set here are used as default avatars\nfor contacts that don't have their own.\nUse the checkboxes to enable/disable showing avatars for the protocols.",IDC_STATIC,0,13,294,27 + CONTROL "",IDC_PROTOCOLS,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_AUTOARRANGE | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,19,83,150,85 + PUSHBUTTON "Set default picture",IDC_SETPROTOPIC,176,83,106,14 + PUSHBUTTON "Delete default picture",IDC_REMOVEPROTOPIC,176,99,106,14 + CONTROL "",IDC_PROTOPIC,"Button",BS_OWNERDRAW,197,118,56,50 + EDITTEXT IDC_PROTOAVATARNAME,18,176,264,30,ES_MULTILINE | ES_READONLY,WS_EX_CLIENTEDGE + CTEXT "CAUTION: These pictures are NOT your own avatars.\nTo set your own Avatar goto Main Menu | View/Change My Details | Avatars",IDC_STATIC,0,53,294,18 +END + +IDD_OPTIONS_AVATARS DIALOGEX 0, 0, 302, 189 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_CLIPSIBLINGS +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Try to draw avatar background transparent (for images without transparency)",IDC_MAKE_TRANSPARENT_BKG, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,19,17,278,12 + LTEXT "Num of points to define bkg:",IDC_BKG_NUM_POINTS_L,33,31,135,11 + EDITTEXT IDC_BKG_NUM_POINTS,173,30,36,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "",IDC_BKG_NUM_POINTS_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,209,30,11,12 + LTEXT "Color difference allowed:",IDC_BKG_COLOR_DIFFERENCE_L,33,44,135,11 + EDITTEXT IDC_BKG_COLOR_DIFFERENCE,173,43,36,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "",IDC_BKG_COLOR_DIFFERENCE_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,209,42,11,12 + CONTROL "Make transparency proportional to color diff",IDC_MAKE_TRANSP_PROPORTIONAL, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,33,57,247,10 + CONTROL "Draw avatars grayscale",IDC_MAKE_GRAYSCALE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,19,72,269,13 + CONTROL "Show warning messages",IDC_SHOWWARNINGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,19,87,269,13 +END + +IDD_OPTIONS_OWN DIALOGEX 0, 0, 302, 75 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_CLIPSIBLINGS +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "When setting avatars, always make them square",IDC_SET_MAKE_SQUARE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,17,281,11 + CONTROL "Try to draw own avatar background transparent (for images without transparency)",IDC_MAKE_MY_AVATARS_TRANSP, + "Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,7,30,289,16 + LTEXT "This uses the same additional options as in ""Contact Avatars"" tab",IDC_STATIC,17,50,257,13 +END + +IDD_OPENSUBCLASS DIALOGEX 0, 0, 246, 18 +STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Protect the picture and prevent automatic avatars from overwriting it",IDC_PROTECTAVATAR, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,237,8 +END + +IDD_SET_OWN_SUBCLASS DIALOG 0, 0, 323, 31 +STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS +FONT 8, "MS Shell Dlg" +BEGIN + CONTROL "Make the avatar square",IDC_MAKE_SQUARE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,68,0,237,8 + CONTROL "Resize the avatar to fit max allowed protocol size",IDC_GROW, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,68,12,237,8 +END + +IDD_AVATAROPTIONS DIALOGEX 0, 0, 213, 212 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Contact picture",IDC_STATIC,5,5,202,96 + CONTROL "",IDC_PROTOPIC,"Button",BS_OWNERDRAW,12,16,56,50 + PUSHBUTTON "Change",IDC_CHANGE,138,15,61,14 + PUSHBUTTON "Delete",IDC_DELETE,138,32,61,14 + PUSHBUTTON "Reset",IDC_RESET,138,49,61,14 + EDITTEXT IDC_AVATARNAME,12,68,187,29,ES_MULTILINE | ES_READONLY,WS_EX_CLIENTEDGE + GROUPBOX "Picture options",IDC_STATIC,5,102,202,92 + CONTROL "Protect the picture",IDC_PROTECTAVATAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,112,186,8 + CONTROL "Set as hidden",IDC_HIDEAVATAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,122,186,11 + CONTROL "Try to make picture background transparent",IDC_MAKETRANSPBKG, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,133,186,11 + LTEXT "Num of points to define bkg:",IDC_BKG_NUM_POINTS_L,23,148,118,11 + EDITTEXT IDC_BKG_NUM_POINTS,145,146,42,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "",IDC_BKG_NUM_POINTS_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,187,147,11,12 + LTEXT "Color difference allowed:",IDC_BKG_COLOR_DIFFERENCE_L,23,162,118,11 + EDITTEXT IDC_BKG_COLOR_DIFFERENCE,145,160,42,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "",IDC_BKG_COLOR_DIFFERENCE_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,188,160,11,12 + DEFPUSHBUTTON "Use defaults",ID_USE_DEFAULTS,11,177,63,12 + DEFPUSHBUTTON "OK",IDOK,93,197,50,14 + DEFPUSHBUTTON "Cancel",IDCANCEL,151,197,50,14 +END + +IDD_USER_AVATAR DIALOGEX 0, 0, 222, 152 +STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + CONTROL "",IDC_PROTOPIC,"MAvatarControlClass",0x0,3,4,96,89 + PUSHBUTTON "Change",IDC_CHANGE,3,102,96,14 + PUSHBUTTON "Delete",IDC_DELETE,3,118,96,14 + PUSHBUTTON "Reset",IDC_RESET,3,134,96,14 + GROUPBOX " Options ",IDC_STATIC,107,3,111,50 + CONTROL "Protect the picture",IDC_PROTECTAVATAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,118,18,93,8 + CONTROL "Set as hidden",IDC_HIDEAVATAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,118,34,93,11 + GROUPBOX " Background ",IDC_STATIC,107,59,111,89 + CONTROL "Transparent",IDC_MAKETRANSPBKG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,118,74,93,11 + LTEXT "Points:",IDC_BKG_NUM_POINTS_L,129,91,38,11 + EDITTEXT IDC_BKG_NUM_POINTS,169,89,42,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "",IDC_BKG_NUM_POINTS_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,199,80,11,12 + LTEXT "Color diff:",IDC_BKG_COLOR_DIFFERENCE_L,129,108,38,11 + EDITTEXT IDC_BKG_COLOR_DIFFERENCE,169,106,42,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "",IDC_BKG_COLOR_DIFFERENCE_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,199,117,11,12 + PUSHBUTTON "Use defaults",ID_USE_DEFAULTS,118,130,63,12 +END + +IDD_PROTO_AVATARS DIALOGEX 0, 0, 222, 152 +STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + CONTROL "",IDC_PROTOCOLS,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_AUTOARRANGE | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,3,4,101,126 + CONTROL "",IDC_PROTOPIC,"MAvatarControlClass",0x0,114,4,96,89 + PUSHBUTTON "Set",IDC_CHANGE,114,100,96,14 + PUSHBUTTON "Delete",IDC_DELETE,114,116,96,14 + CONTROL "Use per protocol avatars",IDC_PER_PROTO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,137,215,11 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OPTIONS_PICTS, DIALOG + BEGIN + RIGHTMARGIN, 294 + END + + IDD_OPENSUBCLASS, DIALOG + BEGIN + RIGHTMARGIN, 208 + TOPMARGIN, 7 + END + + IDD_AVATAROPTIONS, DIALOG + BEGIN + RIGHTMARGIN, 207 + BOTTOMMARGIN, 211 + END + + IDD_USER_AVATAR, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 218 + TOPMARGIN, 4 + BOTTOMMARGIN, 148 + END + + IDD_PROTO_AVATARS, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 218 + TOPMARGIN, 4 + BOTTOMMARGIN, 148 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_AVATAR ICON "res/avatar.ico" +#endif // Neutral resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// German (Germany) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU) +#ifdef _WIN32 +LANGUAGE LANG_GERMAN, SUBLANG_GERMAN +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // German (Germany) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/Avs/avs_10.vcxproj b/plugins/Avs/avs_10.vcxproj new file mode 100644 index 0000000000..3d42ee73d8 --- /dev/null +++ b/plugins/Avs/avs_10.vcxproj @@ -0,0 +1,310 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + avs + {7711F563-6473-4ABD-B5E3-477CE8384AD6} + avs + + + + DynamicLibrary + false + Unicode + true + + + DynamicLibrary + false + Unicode + + + DynamicLibrary + false + Unicode + true + + + DynamicLibrary + false + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + $(SolutionDir)$(Configuration)/Plugins\ + $(SolutionDir)$(Configuration)/Obj/$(ProjectName)\ + true + $(SolutionDir)$(Configuration)64/Plugins\ + $(SolutionDir)$(Configuration)64/Obj/$(ProjectName)\ + true + $(SolutionDir)$(Configuration)/Plugins\ + $(SolutionDir)$(Configuration)/Obj/$(ProjectName)\ + true + $(SolutionDir)$(Configuration)64/Plugins\ + $(SolutionDir)$(Configuration)64/Obj/$(ProjectName)\ + true + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug_Unicode/avatars.tlb + + + + + Disabled + ../../include;../ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;UNICODE;_USRDLL;LOADAVATARS_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + true + Fast + Use + commonheaders.h + Level3 + EditAndContinue + 4996;%(DisableSpecificWarnings) + + + _DEBUG;UNICODE;%(PreprocessorDefinitions) + 0x0809 + ./../../include/msapi/ + + + comctl32.lib;msimg32.lib;%(AdditionalDependencies) + true + true + $(TargetDir)$(TargetName).pdb + false + $(IntDir)$(TargetName).lib + MachineX86 + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Debug_Unicode/avatars.tlb + + + + + Disabled + ../../include;../ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;UNICODE;_USRDLL;LOADAVATARS_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + true + Fast + false + Use + commonheaders.h + Level3 + ProgramDatabase + 4996;%(DisableSpecificWarnings) + + + _DEBUG;UNICODE;%(PreprocessorDefinitions) + 0x0809 + ./../../include/msapi/ + + + comctl32.lib;msimg32.lib;%(AdditionalDependencies) + true + type=%27Win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27*%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) + true + $(TargetDir)$(TargetName).pdb + false + $(IntDir)$(TargetName).lib + MachineX64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release_Unicode/avatars.tlb + + + + + Full + OnlyExplicitInline + Size + true + ../../include;../ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;UNICODE;_USRDLL;LOADAVATARS_EXPORTS;%(PreprocessorDefinitions) + true + false + MultiThreadedDLL + false + true + Fast + false + Use + commonheaders.h + Level3 + ProgramDatabase + 4996;%(DisableSpecificWarnings) + + + NDEBUG;UNICODE;%(PreprocessorDefinitions) + 0x0809 + ./../../include/msapi/ + + + /IGNORE:4089 /filealign:512 %(AdditionalOptions) + comctl32.lib;msimg32.lib;%(AdditionalDependencies) + true + true + true + UseLinkTimeCodeGeneration + 0x5130000 + false + $(IntDir)$(TargetName).lib + MachineX86 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Release_Unicode/avatars.tlb + + + + + Full + OnlyExplicitInline + Size + true + ../../include;../ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;UNICODE;_USRDLL;LOADAVATARS_EXPORTS;%(PreprocessorDefinitions) + true + false + MultiThreadedDLL + false + true + Fast + false + Use + commonheaders.h + Level3 + ProgramDatabase + 4996;%(DisableSpecificWarnings) + + + NDEBUG;UNICODE;%(PreprocessorDefinitions) + 0x0809 + ./../../include/msapi/ + + + /IGNORE:4089 /filealign:512 %(AdditionalOptions) + comctl32.lib;msimg32.lib;%(AdditionalDependencies) + type=%27Win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27*%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) + true + true + true + UseLinkTimeCodeGeneration + 0x5130000 + false + $(IntDir)$(TargetName).lib + MachineX64 + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/Avs/avs_10.vcxproj.filters b/plugins/Avs/avs_10.vcxproj.filters new file mode 100644 index 0000000000..8bac5b1c84 --- /dev/null +++ b/plugins/Avs/avs_10.vcxproj.filters @@ -0,0 +1,88 @@ + + + + + {8803e79e-1c62-4fcc-8daf-35634ce5e990} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {533b0c62-6839-47b0-bf0d-03e20a1a4e06} + h;hpp;hxx;hm;inl + + + {214ac298-955e-4931-a01c-3516a7206928} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + Source Files + + + \ No newline at end of file diff --git a/plugins/Avs/commonheaders.h b/plugins/Avs/commonheaders.h new file mode 100644 index 0000000000..70b0c88fae --- /dev/null +++ b/plugins/Avs/commonheaders.h @@ -0,0 +1,119 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2004 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. +*/ + +#define MIRANDA_VER 0x0A00 +#define _WIN32_WINNT 0x0501 + +#include "m_stdhdr.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "m_folders.h" + +#include +#include +#include +#include "m_metacontacts.h" +#include "m_avatarhistory.h" + +#include "resource.h" +#include "m_updater.h" +#include "m_flash.h" +#include "image_utils.h" +#include "mir_thread.h" +#include "poll.h" +#include "m_acc.h" +#include "acc.h" + + +// shared vars +//extern HINSTANCE g_hInst; + +/* most free()'s are invalid when the code is executed from a dll, so this changes + all the bad free()'s to good ones, however it's still incorrect code. The reasons for not + changing them include: + + * DBFreeVariant has a CallService() lookup + * free() is executed in some large loops to do with clist creation of group data + * easy search and replace + +*/ + +// The same fields as avatarCacheEntry + proto name +struct protoPicCacheEntry : public avatarCacheEntry +{ + __inline void* operator new( size_t size ) { return mir_alloc( size ); } + __inline void operator delete( void* p ) { mir_free( p ); } + + protoPicCacheEntry() { memset(this, 0, sizeof(*this)); }; + ~protoPicCacheEntry(); + + void clear(); + + char* szProtoname; + TCHAR* tszAccName; +}; + +extern OBJLIST g_ProtoPictures, g_MyAvatars; + + +int SetAvatarAttribute(HANDLE hContact, DWORD attrib, int mode); + +#define MAX_REGS(_A_) ( sizeof(_A_) / sizeof(_A_[0]) ) + + +#define GAIR_FAILED 1000 + +#define AVS_IGNORENOTIFY 0x1000 + +#define AVS_DEFAULT "Global avatar" diff --git a/plugins/Avs/image_utils.cpp b/plugins/Avs/image_utils.cpp new file mode 100644 index 0000000000..f6b6c7a98b --- /dev/null +++ b/plugins/Avs/image_utils.cpp @@ -0,0 +1,757 @@ +#include "commonheaders.h" +#include "image_utils.h" + +#include +#include + +/* +Theese are the ones needed +#include +#include +#include +#include +#include +#include +*/ + +extern int _DebugTrace(const char *fmt, ...); +extern int _DebugTrace(HANDLE hContact, const char *fmt, ...); + + +#define GET_PIXEL(__P__, __X__, __Y__) ( __P__ + width * 4 * (__Y__) + 4 * (__X__) ) + + +extern FI_INTERFACE *fei; + +// Make a bitmap all transparent, but only if it is a 32bpp +void MakeBmpTransparent(HBITMAP hBitmap) +{ + BITMAP bmp; + DWORD dwLen; + BYTE *p; + + GetObject(hBitmap, sizeof(bmp), &bmp); + + if (bmp.bmBitsPixel != 32) + return; + + dwLen = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8); + p = (BYTE *)malloc(dwLen); + if (p == NULL) + return; + + memset(p, 0, dwLen); + SetBitmapBits(hBitmap, dwLen, p); + + free(p); +} + +// Resize ///////////////////////////////////////////////////////////////////////////////////////// + + +// Returns a copy of the bitmap with the size especified +// wParam = ResizeBitmap * +// lParam = NULL +INT_PTR BmpFilterResizeBitmap(WPARAM wParam,LPARAM lParam) +{ + // Call freeiamge service (is here only for backward compatibility) + return CallService(MS_IMG_RESIZE, wParam, lParam); +} + +HBITMAP CopyBitmapTo32(HBITMAP hBitmap) +{ + BITMAPINFO RGB32BitsBITMAPINFO; + BYTE * ptPixels; + HBITMAP hDirectBitmap; + + BITMAP bmp; + DWORD dwLen; + BYTE *p; + + GetObject(hBitmap, sizeof(bmp), &bmp); + + dwLen = bmp.bmWidth * bmp.bmHeight * 4; + p = (BYTE *)malloc(dwLen); + if (p == NULL) + return NULL; + + // Create bitmap + ZeroMemory(&RGB32BitsBITMAPINFO, sizeof(BITMAPINFO)); + RGB32BitsBITMAPINFO.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + RGB32BitsBITMAPINFO.bmiHeader.biWidth = bmp.bmWidth; + RGB32BitsBITMAPINFO.bmiHeader.biHeight = bmp.bmHeight; + RGB32BitsBITMAPINFO.bmiHeader.biPlanes = 1; + RGB32BitsBITMAPINFO.bmiHeader.biBitCount = 32; + + hDirectBitmap = CreateDIBSection(NULL, + (BITMAPINFO *)&RGB32BitsBITMAPINFO, + DIB_RGB_COLORS, + (void **)&ptPixels, + NULL, 0); + + // Copy data + if (bmp.bmBitsPixel != 32) + { + HDC hdcOrig, hdcDest; + HBITMAP oldOrig, oldDest; + + hdcOrig = CreateCompatibleDC(NULL); + oldOrig = (HBITMAP) SelectObject(hdcOrig, hBitmap); + + hdcDest = CreateCompatibleDC(NULL); + oldDest = (HBITMAP) SelectObject(hdcDest, hDirectBitmap); + + BitBlt(hdcDest, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcOrig, 0, 0, SRCCOPY); + + SelectObject(hdcDest, oldDest); + DeleteObject(hdcDest); + SelectObject(hdcOrig, oldOrig); + DeleteObject(hdcOrig); + + // Set alpha + fei->FI_CorrectBitmap32Alpha(hDirectBitmap, FALSE); + } + else + { + GetBitmapBits(hBitmap, dwLen, p); + SetBitmapBits(hDirectBitmap, dwLen, p); + } + + free(p); + + return hDirectBitmap; +} + +HBITMAP CreateBitmap32(int cx, int cy) +{ + BITMAPINFO RGB32BitsBITMAPINFO; + UINT * ptPixels; + HBITMAP DirectBitmap; + + ZeroMemory(&RGB32BitsBITMAPINFO,sizeof(BITMAPINFO)); + RGB32BitsBITMAPINFO.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + RGB32BitsBITMAPINFO.bmiHeader.biWidth=cx;//bm.bmWidth; + RGB32BitsBITMAPINFO.bmiHeader.biHeight=cy;//bm.bmHeight; + RGB32BitsBITMAPINFO.bmiHeader.biPlanes=1; + RGB32BitsBITMAPINFO.bmiHeader.biBitCount=32; + + DirectBitmap = CreateDIBSection(NULL, + (BITMAPINFO *)&RGB32BitsBITMAPINFO, + DIB_RGB_COLORS, + (void **)&ptPixels, + NULL, 0); + return DirectBitmap; +} + +// Set the color of points that are transparent +void SetTranspBkgColor(HBITMAP hBitmap, COLORREF color) +{ + BITMAP bmp; + DWORD dwLen; + BYTE *p; + int x, y; + + GetObject(hBitmap, sizeof(bmp), &bmp); + + if (bmp.bmBitsPixel != 32) + return; + + dwLen = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8); + p = (BYTE *)malloc(dwLen); + if (p == NULL) + return; + memset(p, 0, dwLen); + + GetBitmapBits(hBitmap, dwLen, p); + + bool changed = false; + for (y = 0; y < bmp.bmHeight; ++y) { + BYTE *px = p + bmp.bmWidth * 4 * y; + + for (x = 0; x < bmp.bmWidth; ++x) + { + if (px[3] == 0) + { + px[0] = GetBValue(color); + px[1] = GetGValue(color); + px[2] = GetRValue(color); + changed = true; + } + px += 4; + } + } + + if (changed) + SetBitmapBits(hBitmap, dwLen, p); + + free(p); +} + + +#define HIMETRIC_INCH 2540 // HIMETRIC units per inch + +void SetHIMETRICtoDP(HDC hdc, SIZE* sz) +{ + POINT pt; + int nMapMode = GetMapMode(hdc); + if ( nMapMode < MM_ISOTROPIC && nMapMode != MM_TEXT ) + { + // when using a constrained map mode, map against physical inch + SetMapMode(hdc,MM_HIMETRIC); + pt.x = sz->cx; + pt.y = sz->cy; + LPtoDP(hdc,&pt,1); + sz->cx = pt.x; + sz->cy = pt.y; + SetMapMode(hdc, nMapMode); + } + else + { + // map against logical inch for non-constrained mapping modes + int cxPerInch, cyPerInch; + cxPerInch = GetDeviceCaps(hdc,LOGPIXELSX); + cyPerInch = GetDeviceCaps(hdc,LOGPIXELSY); + sz->cx = MulDiv(sz->cx, cxPerInch, HIMETRIC_INCH); + sz->cy = MulDiv(sz->cy, cyPerInch, HIMETRIC_INCH); + } + + pt.x = sz->cx; + pt.y = sz->cy; + DPtoLP(hdc,&pt,1); + sz->cx = pt.x; + sz->cy = pt.y; +} + +INT_PTR BmpFilterLoadBitmap32(WPARAM wParam,LPARAM lParam) +{ + FIBITMAP *dib32 = NULL; + + if(fei == NULL) + return 0; + + FIBITMAP *dib = (FIBITMAP *)CallService(MS_IMG_LOAD, lParam, IMGL_RETURNDIB|IMGL_TCHAR); + + if(dib == NULL) + return 0; + + if(fei->FI_GetBPP(dib) != 32) { + dib32 = fei->FI_ConvertTo32Bits(dib); + fei->FI_Unload(dib); + } + else + dib32 = dib; + + if(dib32) { + if(fei->FI_IsTransparent(dib32)) { + if(wParam) { + DWORD *dwTrans = (DWORD *)wParam; + *dwTrans = 1; + } + } + if(fei->FI_GetWidth(dib32) > 128 || fei->FI_GetHeight(dib32) > 128) { + FIBITMAP *dib_new = fei->FI_MakeThumbnail(dib32, 128, FALSE); + fei->FI_Unload(dib32); + if(dib_new == NULL) + return 0; + dib32 = dib_new; + } + + HBITMAP bitmap = fei->FI_CreateHBITMAPFromDIB(dib32); + + fei->FI_Unload(dib32); + fei->FI_CorrectBitmap32Alpha(bitmap, FALSE); + return (INT_PTR)bitmap; + } + return 0; +} + +static HWND hwndClui = 0; + +// +// Save /////////////////////////////////////////////////////////////////////////////////////////// +// PNG and BMP will be saved as 32bit images, jpg as 24bit with default quality (75) +// returns 1 on success, 0 on failure + +int BmpFilterSaveBitmap(HBITMAP hBmp, char *szFile, int flags) +{ + IMGSRVC_INFO i = {0}; + i.cbSize = sizeof(IMGSRVC_INFO); + i.szName = szFile; + i.hbm = hBmp; + i.dwMask = IMGI_HBITMAP; + i.fif = FIF_UNKNOWN; + + return !CallService(MS_IMG_SAVE, (WPARAM) &i, MAKELONG(0, flags)); +} + + +int BmpFilterSaveBitmapW(HBITMAP hBmp, wchar_t *wszFile, int flags) +{ + IMGSRVC_INFO i = {0}; + i.cbSize = sizeof(IMGSRVC_INFO); + i.wszName = wszFile; + i.hbm = hBmp; + i.dwMask = IMGI_HBITMAP; + i.fif = FIF_UNKNOWN; + + return !CallService(MS_IMG_SAVE, (WPARAM) &i, MAKELONG(IMGL_WCHAR, flags)); +} + +// Save an HBITMAP to an image +// wParam = HBITMAP +// lParam = filename +INT_PTR BmpFilterSaveBitmap(WPARAM wParam,LPARAM lParam) +{ + if ( fei == NULL ) + return -1; + + const char *szFile = (const char*)lParam; + char szFilename[MAX_PATH]; + if ( !CallService(MS_UTILS_PATHTOABSOLUTE, (WPARAM)szFile, (LPARAM)szFilename)) + mir_snprintf(szFilename, SIZEOF(szFilename), "%s", szFile); + + int filenameLen = lstrlenA( szFilename ); + if ( filenameLen > 4 ) + return BmpFilterSaveBitmap(( HBITMAP )wParam, szFilename, 0); + + return -1; +} + +#if defined(_UNICODE) +INT_PTR BmpFilterSaveBitmapW(WPARAM wParam,LPARAM lParam) +{ + if ( fei == NULL ) + return -1; + + const wchar_t *wszFile = (const wchar_t *)lParam; + wchar_t wszFilename[MAX_PATH]; + if ( !CallService(MS_UTILS_PATHTOABSOLUTEW, (WPARAM)wszFile, (LPARAM)wszFilename)) + mir_sntprintf(wszFilename, SIZEOF(wszFilename), _T("%s"), wszFile); + + int filenameLen = lstrlenW( wszFilename ); + if ( filenameLen > 4 ) + return BmpFilterSaveBitmapW(( HBITMAP )wParam, wszFilename, 0 ); + + return -1; +} +#endif + +// Returns != 0 if can save that type of image, = 0 if cant +// wParam = 0 +// lParam = PA_FORMAT_* // image format +// kept for compatibilty - with freeimage we can save all common formats + +INT_PTR BmpFilterCanSaveBitmap(WPARAM wParam,LPARAM lParam) +{ + return 1; +} + + +// Other utilities //////////////////////////////////////////////////////////////////////////////// + + +static BOOL ColorsAreTheSame(int colorDiff, BYTE *px1, BYTE *px2) +{ + return abs(px1[0] - px2[0]) <= colorDiff + && abs(px1[1] - px2[1]) <= colorDiff + && abs(px1[2] - px2[2]) <= colorDiff; +} + + +void AddToStack(int *stack, int *topPos, int x, int y) +{ + int i; + + // Already is in stack? + for(i = 0 ; i < *topPos ; i += 2) + { + if (stack[i] == x && stack[i+1] == y) + return; + } + + stack[*topPos] = x; + (*topPos)++; + + stack[*topPos] = y; + (*topPos)++; +} + + +BOOL GetColorForPoint(int colorDiff, BYTE *p, int width, int height, + int x0, int y0, int x1, int y1, int x2, int y2, BOOL *foundBkg, BYTE colors[][3]) +{ + BYTE *px1, *px2, *px3; + + px1 = GET_PIXEL(p, x0,y0); + px2 = GET_PIXEL(p, x1,y1); + px3 = GET_PIXEL(p, x2,y2); + + // If any of the corners have transparency, forget about it + // Not using != 255 because some MSN bmps have 254 in some positions + if (px1[3] < 253 || px2[3] < 253 || px3[3] < 253) + return FALSE; + + // See if is the same color + if (ColorsAreTheSame(colorDiff, px1, px2) && ColorsAreTheSame(colorDiff, px3, px2)) + { + *foundBkg = TRUE; + memmove(colors, px1, 3); + } + else + { + *foundBkg = FALSE; + } + + return TRUE; +} + + +DWORD GetImgHash(HBITMAP hBitmap) +{ + BITMAP bmp; + DWORD dwLen; + WORD *p; + + GetObject(hBitmap, sizeof(bmp), &bmp); + + dwLen = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8); + p = (WORD *)malloc(dwLen); + if (p == NULL) + return 0; + memset(p, 0, dwLen); + + GetBitmapBits(hBitmap, dwLen, p); + + DWORD ret = 0; + for (DWORD i = 0 ; i < dwLen/2 ; i++) + ret += p[i]; + + free(p); + + return ret; +} + +/* + * Changes the handle to a grayscale image + */ +HBITMAP MakeGrayscale(HANDLE hContact, HBITMAP hBitmap) +{ + if(hBitmap) { + FIBITMAP *dib = fei->FI_CreateDIBFromHBITMAP(hBitmap); + + if(dib) { + FIBITMAP *dib_new = fei->FI_ConvertToGreyscale(dib); + fei->FI_Unload(dib); + if(dib_new) { + DeleteObject(hBitmap); + HBITMAP hbm_new = fei->FI_CreateHBITMAPFromDIB(dib_new); + fei->FI_Unload(dib_new); + return hbm_new; + } + } + } + return hBitmap; +} + +/* + * See if finds a transparent background in image, and set its transparency + * Return TRUE if found a transparent background + */ +BOOL MakeTransparentBkg(HANDLE hContact, HBITMAP *hBitmap) +{ + BYTE *p = NULL; + DWORD dwLen; + int width, height, i, j; + BITMAP bmp; + BYTE colors[8][3]; + BOOL foundBkg[8]; + BYTE *px1; + int count, maxCount, selectedColor; + HBITMAP hBmpTmp; + int colorDiff; + + GetObject(*hBitmap, sizeof(bmp), &bmp); + width = bmp.bmWidth; + height = bmp.bmHeight; + colorDiff = DBGetContactSettingWord(hContact, "ContactPhoto", "TranspBkgColorDiff", + DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgColorDiff", 10)); + + // Min 5x5 to easy things in loop + if (width <= 4 || height <= 4) + return FALSE; + + dwLen = width * height * 4; + p = (BYTE *)malloc(dwLen); + if (p == NULL) + { + return FALSE; + } + + if (bmp.bmBitsPixel == 32) + { + hBmpTmp = *hBitmap; + } + else + { + // Convert to 32 bpp + hBmpTmp = CopyBitmapTo32(*hBitmap); + } + + GetBitmapBits(hBmpTmp, dwLen, p); + + // **** Get corner colors + + // Top left + if (!GetColorForPoint(colorDiff, p, width, height, + 0, 0, 0, 1, 1, 0, &foundBkg[0], &colors[0])) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Top center + if (!GetColorForPoint(colorDiff, p, width, height, + width/2, 0, width/2-1, 0, width/2+1, 0, &foundBkg[1], &colors[1])) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Top Right + if (!GetColorForPoint(colorDiff, p, width, height, + width-1, 0, width-1, 1, width-2, 0, &foundBkg[2], &colors[2])) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Center left + if (!GetColorForPoint(colorDiff, p, width, height, + 0, height/2, 0, height/2-1, 0, height/2+1, &foundBkg[3], &colors[3])) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Center left + if (!GetColorForPoint(colorDiff, p, width, height, + width-1, height/2, width-1, height/2-1, width-1, height/2+1, &foundBkg[4], &colors[4])) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Bottom left + if (!GetColorForPoint(colorDiff, p, width, height, + 0, height-1, 0, height-2, 1, height-1, &foundBkg[5], &colors[5])) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Bottom center + if (!GetColorForPoint(colorDiff, p, width, height, + width/2, height-1, width/2-1, height-1, width/2+1, height-1, &foundBkg[6], &colors[6])) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Bottom Right + if (!GetColorForPoint(colorDiff, p, width, height, + width-1, height-1, width-1, height-2, width-2, height-1, &foundBkg[7], &colors[7])) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // **** X corners have to have the same color + + count = 0; + for (i = 0 ; i < 8 ; i++) + { + if (foundBkg[i]) + count++; + } + + if (count < DBGetContactSettingWord(hContact, "ContactPhoto", "TranspBkgNumPoints", + DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgNumPoints", 5))) + { + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Ok, X corners at least have a color, lets compare then + maxCount = 0; + for (i = 0 ; i < 8 ; i++) + { + if (foundBkg[i]) + { + count = 0; + + for (j = 0 ; j < 8 ; j++) + { + if (foundBkg[j] && ColorsAreTheSame(colorDiff, (BYTE *) &colors[i], (BYTE *) &colors[j])) + count++; + } + + if (count > maxCount) + { + maxCount = count; + selectedColor = i; + } + } + } + + if (maxCount < DBGetContactSettingWord(hContact, "ContactPhoto", "TranspBkgNumPoints", + DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgNumPoints", 5))) + { + // Not enought corners with the same color + if (hBmpTmp != *hBitmap) DeleteObject(hBmpTmp); + free(p); + return FALSE; + } + + // Get bkg color as mean of colors + { + int bkgColor[3]; + + bkgColor[0] = 0; + bkgColor[1] = 0; + bkgColor[2] = 0; + for (i = 0 ; i < 8 ; i++) + { + if (foundBkg[i] && ColorsAreTheSame(colorDiff, (BYTE *) &colors[i], (BYTE *) &colors[selectedColor])) + { + bkgColor[0] += colors[i][0]; + bkgColor[1] += colors[i][1]; + bkgColor[2] += colors[i][2]; + } + } + bkgColor[0] /= maxCount; + bkgColor[1] /= maxCount; + bkgColor[2] /= maxCount; + + colors[selectedColor][0] = bkgColor[0]; + colors[selectedColor][1] = bkgColor[1]; + colors[selectedColor][2] = bkgColor[2]; + } + + // **** Set alpha for the background color, from the borders + + if (hBmpTmp != *hBitmap) + { + DeleteObject(*hBitmap); + + *hBitmap = hBmpTmp; + + GetObject(*hBitmap, sizeof(bmp), &bmp); + GetBitmapBits(*hBitmap, dwLen, p); + } + + { + // Set alpha from borders + int x, y; + int topPos = 0; + int curPos = 0; + int *stack = (int *)malloc(width * height * 2 * sizeof(int)); + bool transpProportional = (DBGetContactSettingByte(NULL, AVS_MODULE, "MakeTransparencyProportionalToColorDiff", 0) != 0); + + if (stack == NULL) + { + free(p); + return FALSE; + } + + // Put four corners + AddToStack(stack, &topPos, 0, 0); + AddToStack(stack, &topPos, width/2, 0); + AddToStack(stack, &topPos, width-1, 0); + AddToStack(stack, &topPos, 0, height/2); + AddToStack(stack, &topPos, width-1, height/2); + AddToStack(stack, &topPos, 0, height-1); + AddToStack(stack, &topPos, width/2, height-1); + AddToStack(stack, &topPos, width-1, height-1); + + while(curPos < topPos) + { + // Get pos + x = stack[curPos]; curPos++; + y = stack[curPos]; curPos++; + + // Get pixel + px1 = GET_PIXEL(p, x, y); + + // It won't change the transparency if one exists + // (This avoid an endless loop too) + // Not using == 255 because some MSN bmps have 254 in some positions + if (px1[3] >= 253) + { + if (ColorsAreTheSame(colorDiff, px1, (BYTE *) &colors[selectedColor])) + { + if (transpProportional) + { + px1[3] = min(252, + (abs(px1[0] - colors[selectedColor][0]) + + abs(px1[1] - colors[selectedColor][1]) + + abs(px1[2] - colors[selectedColor][2])) / 3); + } + else + { + px1[3] = 0; + } + + // Add 4 neighbours + + if (x + 1 < width) + AddToStack(stack, &topPos, x + 1, y); + + if (x - 1 >= 0) + AddToStack(stack, &topPos, x - 1, y); + + if (y + 1 < height) + AddToStack(stack, &topPos, x, y + 1); + + if (y - 1 >= 0) + AddToStack(stack, &topPos, x, y - 1); + } + } + } + + free(stack); + } + + dwLen = SetBitmapBits(*hBitmap, dwLen, p); + free(p); + + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Other utils + +int SaveAvatar( const char* protocol, const TCHAR* tszFileName ) +{ + int result = CallProtoService(protocol, PS_SETMYAVATART, 0, ( LPARAM )tszFileName); + #if defined( _UNICODE ) + if ( result == CALLSERVICE_NOTFOUND ) { + if ( tszFileName != NULL ) { + char szFileName[ MAX_PATH ]; + WideCharToMultiByte( CP_ACP, 0, tszFileName, -1, szFileName, SIZEOF(szFileName), 0, 0 ); + result = CallProtoService(protocol, PS_SETMYAVATAR, 0, ( LPARAM )szFileName); + } + else result = CallProtoService(protocol, PS_SETMYAVATAR, 0, 0); + } + #endif + + return result; +} diff --git a/plugins/Avs/image_utils.h b/plugins/Avs/image_utils.h new file mode 100644 index 0000000000..2dd75d8211 --- /dev/null +++ b/plugins/Avs/image_utils.h @@ -0,0 +1,58 @@ +#ifndef __IMAGE_UTILS_H__ +# define __IMAGE_UTILS_H__ + +#define _WIN32_WINNT 0x0501 +#include + +#include + + +// Load an image +// wParam = NULL +// lParam = filename +INT_PTR BmpFilterLoadBitmap32(WPARAM wParam,LPARAM lParam); + +// Save an HBITMAP to an image +// wParam = HBITMAP +// lParam = full path of filename +INT_PTR BmpFilterSaveBitmap(WPARAM wParam,LPARAM lParam); +#if defined(_UNICODE) + INT_PTR BmpFilterSaveBitmapW(WPARAM wParam,LPARAM lParam); + #define BmpFilterSaveBitmapT BmpFilterSaveBitmapW +#else + #define BmpFilterSaveBitmapT BmpFilterSaveBitmap +#endif + +// Returns != 0 if can save that type of image, = 0 if cant +// wParam = 0 +// lParam = PA_FORMAT_* // image format +INT_PTR BmpFilterCanSaveBitmap(WPARAM wParam,LPARAM lParam); + +// Returns a copy of the bitmap with the size especified or the original bitmap if nothing has to be changed +// wParam = ResizeBitmap * +// lParam = NULL +INT_PTR BmpFilterResizeBitmap(WPARAM wParam,LPARAM lParam); + + +int BmpFilterSaveBitmap(HBITMAP hBmp, char *szFile, int flags); +#if defined(_UNICODE) + int BmpFilterSaveBitmapW(HBITMAP hBmp, wchar_t *wszFile, int flags); + #define BmpFilterSaveBitmapT BmpFilterSaveBitmapW +#else + #define BmpFilterSaveBitmapT BmpFilterSaveBitmap +#endif + +HBITMAP CopyBitmapTo32(HBITMAP hBitmap); + +BOOL PreMultiply(HBITMAP hBitmap); +BOOL MakeTransparentBkg(HANDLE hContact, HBITMAP *hBitmap); +HBITMAP MakeGrayscale(HANDLE hContact, HBITMAP hBitmap); +DWORD GetImgHash(HBITMAP hBitmap); + +int AVS_pathIsAbsolute(const TCHAR *path); +size_t AVS_pathToRelative(const TCHAR *sPrc, TCHAR *pOut); +size_t AVS_pathToAbsolute(const TCHAR *pSrc, TCHAR *pOut); + +int SaveAvatar( const char* protocol, const TCHAR* tszFileName ); + +#endif // __IMAGE_UTILS_H__ diff --git a/plugins/Avs/license.txt b/plugins/Avs/license.txt new file mode 100644 index 0000000000..7f1161073d --- /dev/null +++ b/plugins/Avs/license.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/plugins/Avs/main.cpp b/plugins/Avs/main.cpp new file mode 100644 index 0000000000..bb4ebc236f --- /dev/null +++ b/plugins/Avs/main.cpp @@ -0,0 +1,2684 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2004 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. +*/ + +#include "commonheaders.h" +#include "version.h" + +HINSTANCE g_hInst = 0; +PLUGINLINK *pluginLink; +MM_INTERFACE mmi; +LIST_INTERFACE li; +int hLangpack; + +static TCHAR g_szDataPath[MAX_PATH]; // user datae path (read at startup only) +static BOOL g_MetaAvail = FALSE; +BOOL g_AvatarHistoryAvail = FALSE; +static long hwndSetMyAvatar = 0; + +static HANDLE hMyAvatarsFolder = 0; +static HANDLE hGlobalAvatarFolder = 0; +static HANDLE hLoaderEvent = 0; +static HANDLE hLoaderThread = 0; + +static HANDLE hOptInit = 0; +static HANDLE hModulesLoaded = 0; +static HANDLE hPresutdown = 0; +static HANDLE hOkToExit = 0; +static HANDLE hAccChanged = 0; + +HANDLE hProtoAckHook = 0, hContactSettingChanged = 0, hEventChanged = 0, hEventContactAvatarChanged = 0, + hMyAvatarChanged = 0, hEventDeleted = 0, hUserInfoInitHook = 0; +HICON g_hIcon = 0; + +BOOL (WINAPI *AvsAlphaBlend)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION) = NULL; + +static struct CacheNode *g_Cache = 0; +static CRITICAL_SECTION cachecs, alloccs; + +static int ComparePicture( const protoPicCacheEntry* p1, const protoPicCacheEntry* p2 ) +{ + if ((lstrcmpA(p1->szProtoname, "Global avatar") == 0) || strstr(p1->szProtoname, "Global avatar")) + return -1; + if ((lstrcmpA(p2->szProtoname, "Global avatar") == 0) || strstr(p1->szProtoname, "Global avatar")) + return 1; + return lstrcmpA( p1->szProtoname, p2->szProtoname ); +} + +OBJLIST + g_ProtoPictures( 10, ComparePicture ), + g_MyAvatars( 10, ComparePicture ); + +char* g_szMetaName = NULL; + +#ifndef SHVIEW_THUMBNAIL +#define SHVIEW_THUMBNAIL 0x702D +#endif + +// Stores the id of the dialog + +int ChangeAvatar(HANDLE hContact, BOOL fLoad, BOOL fNotifyHist = FALSE, int pa_format = 0); +static int ShutdownProc(WPARAM wParam, LPARAM lParam); +static int OkToExitProc(WPARAM wParam, LPARAM lParam); +static int OnDetailsInit(WPARAM wParam, LPARAM lParam); +static int GetFileHash(TCHAR* filename); +static DWORD GetFileSize(TCHAR *szFilename); + +void ProcessAvatarInfo(HANDLE hContact, int type, PROTO_AVATAR_INFORMATIONT *pai, const char *szProto); +int FetchAvatarFor(HANDLE hContact, char *szProto = NULL); +static INT_PTR ReportMyAvatarChanged(WPARAM wParam, LPARAM lParam); + +BOOL Proto_IsAvatarsEnabled(const char *proto); +BOOL Proto_IsAvatarFormatSupported(const char *proto, int format); +void Proto_GetAvatarMaxSize(const char *proto, int *width, int *height); +int Proto_AvatarImageProportion(const char *proto); +BOOL Proto_NeedDelaysForAvatars(const char *proto); +int Proto_GetAvatarMaxFileSize(const char *proto); +int Proto_GetDelayAfterFail(const char *proto); + +FI_INTERFACE *fei = 0; + +PLUGININFOEX pluginInfoEx = { + sizeof(PLUGININFOEX), +#if defined(_UNICODE) + "Avatar service (Unicode)", +#else + "Avatar service", +#endif + __VERSION_DWORD, + "Load and manage contact pictures for other plugins.", + "Nightwish, Pescuma", + "", + "Copyright 2000-2012 Miranda-IM project", + "http://www.miranda-im.org", + UNICODE_AWARE, + 0, +#if defined(_UNICODE) +// {E00F1643-263C-4599-B84B-053E5C511D29} + { 0xe00f1643, 0x263c, 0x4599, { 0xb8, 0x4b, 0x5, 0x3e, 0x5c, 0x51, 0x1d, 0x29 } } +#else +// {C9E01EB0-A119-42d2-B340-E8678F5FEAD8} + { 0xc9e01eb0, 0xa119, 0x42d2, { 0xb3, 0x40, 0xe8, 0x67, 0x8f, 0x5f, 0xea, 0xd8 } } +#endif +}; + +extern INT_PTR CALLBACK DlgProcOptionsAvatars(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +extern INT_PTR CALLBACK DlgProcOptionsProtos(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +extern INT_PTR CALLBACK DlgProcOptionsOwn(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +extern INT_PTR CALLBACK DlgProcAvatarOptions(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +extern INT_PTR CALLBACK DlgProcAvatarUserInfo(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +extern INT_PTR CALLBACK DlgProcAvatarProtoInfo(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + + +static int SetProtoMyAvatar(char *protocol, HBITMAP hBmp, TCHAR *originalFilename, int format, BOOL square, BOOL grow); + +// See if a protocol service exists +int ProtoServiceExists(const char *szModule,const char *szService) +{ + char str[MAXMODULELABELLENGTH * 2]; + strcpy(str,szModule); + strcat(str,szService); + return ServiceExists(str); +} + + +/* + * output a notification message. + * may accept a hContact to include the contacts nickname in the notification message... + * the actual message is using printf() rules for formatting and passing the arguments... + * + * can display the message either as systray notification (baloon popup) or using the + * popup plugin. + */ + +#ifdef _DEBUG + +int _DebugTrace(const char *fmt, ...) +{ + char debug[2048]; + int ibsize = 2047; + va_list va; + va_start(va, fmt); + + mir_snprintf(debug, SIZEOF(debug) - 10, " ***** AVS [%08d] [ID:%04x]: ", GetTickCount(), GetCurrentThreadId()); + OutputDebugStringA(debug); + _vsnprintf(debug, ibsize, fmt, va); + OutputDebugStringA(debug); + OutputDebugStringA(" ***** \n"); + + return 0; +} + +int _DebugTrace(HANDLE hContact, const char *fmt, ...) +{ + char text[1024]; + size_t len; + va_list va; + + char *name = NULL; + char *proto = NULL; + if (hContact != NULL) + { + name = (char*) CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, 0); + proto = (char*) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + } + + mir_snprintf(text, SIZEOF(text) - 10, " ***** AVS [%08d] [ID:%04x]: [%08d - %s - %s] ", + GetTickCount(), GetCurrentThreadId(), hContact, proto == NULL ? "" : proto, name == NULL ? "" : name); + len = strlen(text); + + va_start(va, fmt); + mir_vsnprintf(&text[len], SIZEOF(text) - len, fmt, va); + va_end(va); + + OutputDebugStringA(text); + OutputDebugStringA(" ***** \n"); + + return 0; +} + +#endif + +/* + * path utilities (make avatar paths relative to *PROFILE* directory, not miranda directory. + * taken and modified from core services + */ + +int AVS_pathIsAbsolute(const TCHAR *path) +{ + if (!path || !(lstrlen(path) > 2)) + return 0; + if ((path[1]==':'&&path[2]=='\\')||(path[0]=='\\'&&path[1]=='\\')) return 1; + return 0; +} + +size_t AVS_pathToRelative(const TCHAR *pSrc, TCHAR *pOut) +{ + if (!pSrc || !*pSrc || _tcslen(pSrc) > MAX_PATH) return 0; + if (!AVS_pathIsAbsolute( pSrc )) + lstrcpyn(pOut, pSrc, MAX_PATH); + else { + TCHAR szTmp[MAX_PATH]; + mir_sntprintf(szTmp, SIZEOF(szTmp), _T("%s"), pSrc); + _tcslwr(szTmp); + if (_tcsstr(szTmp, g_szDataPath)) + lstrcpyn(pOut, pSrc + _tcslen(g_szDataPath) + 1, MAX_PATH); + else + lstrcpyn(pOut, pSrc, MAX_PATH); + } + return _tcslen(pOut); +} + +size_t AVS_pathToAbsolute(const TCHAR *pSrc, TCHAR *pOut) +{ + if (!pSrc || !lstrlen(pSrc) || lstrlen(pSrc) > MAX_PATH) + return 0; + + if (AVS_pathIsAbsolute(pSrc) || !_istalnum(pSrc[0])) + lstrcpyn(pOut, pSrc, MAX_PATH); + else + mir_sntprintf(pOut, MAX_PATH, _T("%s\\%s"), g_szDataPath, pSrc, MAX_PATH); + return lstrlen(pOut); +} + +static void NotifyMetaAware(HANDLE hContact, struct CacheNode *node = NULL, AVATARCACHEENTRY *ace = (AVATARCACHEENTRY *)-1) +{ + if (ace == (AVATARCACHEENTRY *)-1) + ace = &node->ace; + + NotifyEventHooks(hEventChanged, (WPARAM)hContact, (LPARAM)ace); + + if (g_MetaAvail && (node->dwFlags & MC_ISSUBCONTACT) && DBGetContactSettingByte(NULL, g_szMetaName, "Enabled", 0)) { + HANDLE hMasterContact = (HANDLE)DBGetContactSettingDword(hContact, g_szMetaName, "Handle", 0); + + if (hMasterContact && (HANDLE)CallService(MS_MC_GETMOSTONLINECONTACT, (WPARAM)hMasterContact, 0) == hContact && + !DBGetContactSettingByte(hMasterContact, "ContactPhoto", "Locked", 0)) + NotifyEventHooks(hEventChanged, (WPARAM)hMasterContact, (LPARAM)ace); + } + if (node->dwFlags & AVH_MUSTNOTIFY) { + // Fire the event for avatar history + node->dwFlags &= ~AVH_MUSTNOTIFY; + if (node->ace.szFilename[0] != '\0') { + CONTACTAVATARCHANGEDNOTIFICATION cacn = {0}; + cacn.cbSize = sizeof(CONTACTAVATARCHANGEDNOTIFICATION); + cacn.hContact = hContact; + cacn.format = node->pa_format; + _tcsncpy(cacn.filename, node->ace.szFilename, MAX_PATH); + cacn.filename[MAX_PATH - 1] = 0; + + // Get hash + char *szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + if (szProto != NULL) { + DBVARIANT dbv = {0}; + if (!DBGetContactSetting(hContact, szProto, "AvatarHash", &dbv)) { + if (dbv.type == DBVT_TCHAR) { + _tcsncpy(cacn.hash, dbv.ptszVal, SIZEOF(cacn.hash)); + } else if (dbv.type == DBVT_BLOB) { + // Lets use base64 encode + char *tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i; + for(i = 0; i < dbv.cpbVal / 3 && i*4+3 < sizeof(cacn.hash)-1; i++) { + BYTE a = dbv.pbVal[i*3]; + BYTE b = i*3 + 1 < dbv.cpbVal ? dbv.pbVal[i*3 + 1] : 0; + BYTE c = i*3 + 2 < dbv.cpbVal ? dbv.pbVal[i*3 + 2] : 0; + + cacn.hash[i*4] = tab[(a & 0xFC) >> 2]; + cacn.hash[i*4+1] = tab[((a & 0x3) << 4) + ((b & 0xF0) >> 4)]; + cacn.hash[i*4+2] = tab[((b & 0xF) << 2) + ((c & 0xC0) >> 6)]; + cacn.hash[i*4+3] = tab[c & 0x3F]; + } + if (dbv.cpbVal % 3 != 0 && i*4+3 < sizeof(cacn.hash)-1) { + BYTE a = dbv.pbVal[i*3]; + BYTE b = i*3 + 1 < dbv.cpbVal ? dbv.pbVal[i*3 + 1] : 0; + + cacn.hash[i*4] = tab[(a & 0xFC) >> 2]; + cacn.hash[i*4+1] = tab[((a & 0x3) << 4) + ((b & 0xF0) >> 4)]; + if (i + 1 < dbv.cpbVal) + cacn.hash[i*4+2] = tab[((b & 0xF) << 4)]; + else + cacn.hash[i*4+2] = '='; + cacn.hash[i*4+3] = '='; + } + } + DBFreeVariant(&dbv); + } + } + + // Default value + if (cacn.hash[0] == '\0') + mir_sntprintf(cacn.hash, SIZEOF(cacn.hash), _T("AVS-HASH-%x"), GetFileHash(cacn.filename)); + + NotifyEventHooks(hEventContactAvatarChanged, (WPARAM)cacn.hContact, (LPARAM)&cacn); + } + else + NotifyEventHooks(hEventContactAvatarChanged, (WPARAM)hContact, NULL); + } +} + +static int g_maxBlock = 0, g_curBlock = 0; +static struct CacheNode **g_cacheBlocks = NULL; + +/* + * allocate a cache block and add it to the list of blocks + * does not link the new block with the old block(s) - caller needs to do this + */ + +static struct CacheNode *AllocCacheBlock() +{ + struct CacheNode *allocedBlock = NULL; + + allocedBlock = (struct CacheNode *)malloc(CACHE_BLOCKSIZE * sizeof(struct CacheNode)); + ZeroMemory((void *)allocedBlock, sizeof(struct CacheNode) * CACHE_BLOCKSIZE); + + for(int i = 0; i < CACHE_BLOCKSIZE - 1; i++) + { + //InitializeCriticalSection(&allocedBlock[i].cs); + allocedBlock[i].pNextNode = &allocedBlock[i + 1]; // pre-link the alloced block + } + //InitializeCriticalSection(&allocedBlock[CACHE_BLOCKSIZE - 1].cs); + + if (g_Cache == NULL) // first time only... + g_Cache = allocedBlock; + + // add it to the list of blocks + + if (g_curBlock == g_maxBlock) { + g_maxBlock += 10; + g_cacheBlocks = (struct CacheNode **)realloc(g_cacheBlocks, g_maxBlock * sizeof(struct CacheNode *)); + } + g_cacheBlocks[g_curBlock++] = allocedBlock; + + return(allocedBlock); +} + +int SetAvatarAttribute(HANDLE hContact, DWORD attrib, int mode) +{ + struct CacheNode *cacheNode = g_Cache; + + while(cacheNode) { + if (cacheNode->ace.hContact == hContact) { + DWORD dwFlags = cacheNode->ace.dwFlags; + + cacheNode->ace.dwFlags = mode ? cacheNode->ace.dwFlags | attrib : cacheNode->ace.dwFlags & ~attrib; + if (cacheNode->ace.dwFlags != dwFlags) + NotifyMetaAware(hContact, cacheNode); + break; + } + cacheNode = cacheNode->pNextNode; + } + return 0; +} + +/* + * convert the avatar image path to a relative one... + * given: contact handle, path to image + */ +void MakePathRelative(HANDLE hContact, TCHAR *path) +{ + TCHAR szFinalPath[MAX_PATH]; + szFinalPath[0] = '\0'; + + size_t result = AVS_pathToRelative(path, szFinalPath); + if (result && lstrlen(szFinalPath) > 0) { + DBWriteContactSettingTString(hContact, "ContactPhoto", "RFile", szFinalPath); + if (!DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0)) + DBWriteContactSettingTString(hContact, "ContactPhoto", "Backup", szFinalPath); + } +} + +/* + * convert the avatar image path to a relative one... + * given: contact handle + */ + +static void MakePathRelative(HANDLE hContact) +{ + DBVARIANT dbv = {0}; + + if (!DBGetContactSetting(hContact, "ContactPhoto", "File", &dbv)) { + if (dbv.type == DBVT_TCHAR) { + MakePathRelative(hContact, dbv.ptszVal); + } + DBFreeVariant(&dbv); + } +} + +static void ResetTranspSettings(HANDLE hContact) +{ + DBDeleteContactSetting(hContact, "ContactPhoto", "MakeTransparentBkg"); + DBDeleteContactSetting(hContact, "ContactPhoto", "TranspBkgNumPoints"); + DBDeleteContactSetting(hContact, "ContactPhoto", "TranspBkgColorDiff"); +} + +static TCHAR *getJGMailID(char *szProto) +{ + static TCHAR szJID[MAX_PATH+1]; + DBVARIANT dbva={0}, dbvb={0}; + + szJID[0] = '\0'; + if (DBGetContactSettingTString(NULL, szProto, "LoginName", &dbva)) + return szJID; + if (DBGetContactSettingTString(NULL, szProto, "LoginServer", &dbvb)) { + DBFreeVariant(&dbva); + return szJID; + } + + mir_sntprintf(szJID, SIZEOF(szJID), _T("%s@%s"), dbva.ptszVal, dbvb.ptszVal); + DBFreeVariant(&dbva); + DBFreeVariant(&dbvb); + return szJID; +} + +// create the avatar in cache +// returns 0 if not created (no avatar), iIndex otherwise, -2 if has to request avatar, -3 if avatar too big +int CreateAvatarInCache(HANDLE hContact, avatarCacheEntry *ace, char *szProto) +{ + DBVARIANT dbv = {0}; + char *szExt = NULL; + TCHAR tszFilename[MAX_PATH]; + HANDLE hFile = INVALID_HANDLE_VALUE; + DWORD dwFileSizeHigh = 0, dwFileSize = 0, sizeLimit = 0; + + tszFilename[0] = 0; + + ace->hbmPic = 0; + ace->dwFlags = 0; + ace->bmHeight = 0; + ace->bmWidth = 0; + ace->lpDIBSection = NULL; + ace->szFilename[0] = 0; + + if (szProto == NULL) { + char *proto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + if (proto == NULL || !DBGetContactSettingByte(NULL, AVS_MODULE, proto, 1)) { + return -1; + } + + if (DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0) + && !DBGetContactSettingTString(hContact, "ContactPhoto", "Backup", &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, tszFilename); + DBFreeVariant(&dbv); + } + else if (!DBGetContactSettingTString(hContact, "ContactPhoto", "RFile", &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, tszFilename); + DBFreeVariant(&dbv); + } + else if (!DBGetContactSettingTString(hContact, "ContactPhoto", "File", &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, tszFilename); + DBFreeVariant(&dbv); + } + else { + return -2; + } + } + else { + if (hContact == 0) { // create a protocol picture in the proto picture cache + if (!DBGetContactSettingTString(NULL, PPICT_MODULE, szProto, &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, tszFilename); + DBFreeVariant(&dbv); + } + else { + if (lstrcmpA(szProto, AVS_DEFAULT)) { + if (!DBGetContactSettingTString(NULL, PPICT_MODULE, AVS_DEFAULT, &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, tszFilename); + DBFreeVariant(&dbv); + } + + if (!strstr(szProto, "Global avatar for")) { + PROTOACCOUNT* pdescr = (PROTOACCOUNT*)CallService(MS_PROTO_GETACCOUNT, 0, (LPARAM)szProto); + if (pdescr == NULL) + return -1; + char key[MAX_PATH]; + mir_snprintf(key, SIZEOF(key), "Global avatar for %s accounts", pdescr->szProtoName); + if (!DBGetContactSettingTString(NULL, PPICT_MODULE, key, &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, tszFilename); + DBFreeVariant(&dbv); + } + } + } + } + } + else if (hContact == (HANDLE)-1) { // create own picture - note, own avatars are not on demand, they are loaded once at + // startup and everytime they are changed. + if (szProto[0] == '\0') { + // Global avatar + if ( DBGetContactSettingTString(NULL, AVS_MODULE, "GlobalUserAvatarFile", &dbv)) + return -10; + + AVS_pathToAbsolute(dbv.ptszVal, tszFilename); + DBFreeVariant(&dbv); + } + else if (ProtoServiceExists(szProto, PS_GETMYAVATART)) { + if (CallProtoService(szProto, PS_GETMYAVATART, (WPARAM)tszFilename, (LPARAM)MAX_PATH)) + tszFilename[0] = '\0'; + } +#if defined( _UNICODE ) + else if (ProtoServiceExists(szProto, PS_GETMYAVATAR)) { + char szFileName[ MAX_PATH ]; + if (CallProtoService(szProto, PS_GETMYAVATAR, (WPARAM)szFileName, (LPARAM)MAX_PATH)) + tszFilename[0] = '\0'; + else + MultiByteToWideChar( CP_ACP, 0, szFileName, -1, tszFilename, SIZEOF( tszFilename )); + } +#endif + else if (!DBGetContactSettingTString(NULL, szProto, "AvatarFile", &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, tszFilename); + DBFreeVariant(&dbv); + } + else return -1; + } + } + + if ( lstrlen(tszFilename) < 4 ) + return -1; + + TCHAR* tmpPath = Utils_ReplaceVarsT(tszFilename); + mir_sntprintf(tszFilename, SIZEOF(tszFilename), _T("%s"), tmpPath); + mir_free(tmpPath); + + if ((hFile = CreateFile(tszFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) + return -2; + + CloseHandle(hFile); + WPARAM isTransparentImage = 0; + + ace->hbmPic = (HBITMAP) BmpFilterLoadBitmap32((WPARAM)&isTransparentImage, (LPARAM)tszFilename); + ace->dwFlags = 0; + ace->bmHeight = 0; + ace->bmWidth = 0; + ace->lpDIBSection = NULL; + _tcsncpy(ace->szFilename, tszFilename, MAX_PATH); + ace->szFilename[MAX_PATH - 1] = 0; + if (ace->hbmPic != 0) { + BITMAP bminfo; + + GetObject(ace->hbmPic, sizeof(bminfo), &bminfo); + + ace->cbSize = sizeof(avatarCacheEntry); + ace->dwFlags = AVS_BITMAP_VALID; + if (hContact != NULL && DBGetContactSettingByte(hContact, "ContactPhoto", "Hidden", 0)) + ace->dwFlags |= AVS_HIDEONCLIST; + ace->hContact = hContact; + ace->bmHeight = bminfo.bmHeight; + ace->bmWidth = bminfo.bmWidth; + + BOOL noTransparency = DBGetContactSettingByte(0, AVS_MODULE, "RemoveAllTransparency", 0); + + // Calc image hash + if (hContact != 0 && hContact != (HANDLE)-1) + { + // Have to reset settings? -> do it if image changed + DWORD imgHash = GetImgHash(ace->hbmPic); + + if (imgHash != DBGetContactSettingDword(hContact, "ContactPhoto", "ImageHash", 0)) + { + ResetTranspSettings(hContact); + DBWriteContactSettingDword(hContact, "ContactPhoto", "ImageHash", imgHash); + } + + // Make transparent? + if (!noTransparency && !isTransparentImage + && DBGetContactSettingByte(hContact, "ContactPhoto", "MakeTransparentBkg", + DBGetContactSettingByte(0, AVS_MODULE, "MakeTransparentBkg", 0))) + { + if (MakeTransparentBkg(hContact, &ace->hbmPic)) + { + ace->dwFlags |= AVS_CUSTOMTRANSPBKG | AVS_HASTRANSPARENCY; + GetObject(ace->hbmPic, sizeof(bminfo), &bminfo); + isTransparentImage = TRUE; + } + } + } + else if (hContact == (HANDLE)-1) // My avatars + { + if (!noTransparency && !isTransparentImage + && DBGetContactSettingByte(0, AVS_MODULE, "MakeTransparentBkg", 0) + && DBGetContactSettingByte(0, AVS_MODULE, "MakeMyAvatarsTransparent", 0)) + { + if (MakeTransparentBkg(0, &ace->hbmPic)) + { + ace->dwFlags |= AVS_CUSTOMTRANSPBKG | AVS_HASTRANSPARENCY; + GetObject(ace->hbmPic, sizeof(bminfo), &bminfo); + isTransparentImage = TRUE; + } + } + } + + if (DBGetContactSettingByte(0, AVS_MODULE, "MakeGrayscale", 0)) + { + ace->hbmPic = MakeGrayscale(hContact, ace->hbmPic); + } + + if (noTransparency) + { + fei->FI_CorrectBitmap32Alpha(ace->hbmPic, TRUE); + isTransparentImage = FALSE; + } + + if (bminfo.bmBitsPixel == 32 && isTransparentImage) + { + if (fei->FI_Premultiply(ace->hbmPic)) + { + ace->dwFlags |= AVS_HASTRANSPARENCY; + } + ace->dwFlags |= AVS_PREMULTIPLIED; + } + + if (szProto) + { + protoPicCacheEntry *pAce = (protoPicCacheEntry *)ace; + if (hContact == 0) + pAce->dwFlags |= AVS_PROTOPIC; + else if (hContact == (HANDLE)-1) + pAce->dwFlags |= AVS_OWNAVATAR; + } + + return 1; + } + return -1; +} + +/* + * link a new cache block with the already existing chain of blocks + */ + +static struct CacheNode *AddToList(struct CacheNode *node) { + struct CacheNode *pCurrent = g_Cache; + + while(pCurrent->pNextNode != 0) + pCurrent = pCurrent->pNextNode; + + pCurrent->pNextNode = node; + return pCurrent; +} + +struct CacheNode *FindAvatarInCache(HANDLE hContact, BOOL add, BOOL findAny = FALSE) +{ + struct CacheNode *cacheNode = g_Cache, *foundNode = NULL; + + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + if (szProto == NULL || !DBGetContactSettingByte(NULL, AVS_MODULE, szProto, 1)) + return NULL; + + EnterCriticalSection(&cachecs); + + while(cacheNode) + { + if (cacheNode->ace.hContact == hContact) + { + cacheNode->ace.t_lastAccess = time(NULL); + foundNode = cacheNode->loaded || findAny ? cacheNode : NULL; + LeaveCriticalSection(&cachecs); + return foundNode; + } + if (foundNode == NULL && cacheNode->ace.hContact == 0) + foundNode = cacheNode; // found an empty and usable node + + cacheNode = cacheNode->pNextNode; + } + + // not found + + if (add) + { + if (foundNode == NULL) { // no free entry found, create a new and append it to the list + EnterCriticalSection(&alloccs); // protect memory block allocation + struct CacheNode *newNode = AllocCacheBlock(); + AddToList(newNode); + foundNode = newNode; + LeaveCriticalSection(&alloccs); + } + + foundNode->ace.hContact = hContact; + if (g_MetaAvail) + foundNode->dwFlags |= (DBGetContactSettingByte(hContact, g_szMetaName, "IsSubcontact", 0) ? MC_ISSUBCONTACT : 0); + foundNode->loaded = FALSE; + foundNode->mustLoad = 1; // pic loader will watch this and load images + LeaveCriticalSection(&cachecs); + SetEvent(hLoaderEvent); // wake him up + return NULL; + } + else + { + foundNode = NULL; + } + LeaveCriticalSection(&cachecs); + return foundNode; +} + +#define POLYNOMIAL (0x488781ED) /* This is the CRC Poly */ +#define TOPBIT (1 << (WIDTH - 1)) /* MSB */ +#define WIDTH 32 + +static int GetFileHash(TCHAR* filename) +{ + HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) + return 0; + + int remainder = 0; + char data[1024]; + DWORD dwRead; + do + { + // Read file chunk + dwRead = 0; + ReadFile(hFile, data, 1024, &dwRead, NULL); + + /* loop through each byte of data */ + for (int byte = 0; byte < (int) dwRead; ++byte) { + /* store the next byte into the remainder */ + remainder ^= (data[byte] << (WIDTH - 8)); + /* calculate for all 8 bits in the byte */ + for (int bit = 8; bit > 0; --bit) { + /* check if MSB of remainder is a one */ + if (remainder & TOPBIT) + remainder = (remainder << 1) ^ POLYNOMIAL; + else + remainder = (remainder << 1); + } + } + } while(dwRead == 1024); + + CloseHandle(hFile); + + return remainder; +} + +static int ProtocolAck(WPARAM wParam, LPARAM lParam) +{ + ACKDATA *ack = (ACKDATA *) lParam; + + if (ack != NULL && ack->type == ACKTYPE_AVATAR && ack->hContact != 0 + // Ignore metacontacts + && (!g_MetaAvail || strcmp(ack->szModule, g_szMetaName))) + { + if (ack->result == ACKRESULT_SUCCESS) + { + if (ack->hProcess == NULL) + ProcessAvatarInfo(ack->hContact, GAIR_NOAVATAR, NULL, ack->szModule); + else + ProcessAvatarInfo(ack->hContact, GAIR_SUCCESS, (PROTO_AVATAR_INFORMATIONT *) ack->hProcess, ack->szModule); + } + else if (ack->result == ACKRESULT_FAILED) + { + ProcessAvatarInfo(ack->hContact, GAIR_FAILED, (PROTO_AVATAR_INFORMATIONT *) ack->hProcess, ack->szModule); + } + else if (ack->result == ACKRESULT_STATUS) + { + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)ack->hContact, 0); + if (szProto == NULL || Proto_NeedDelaysForAvatars(szProto)) + { + // Queue + DBWriteContactSettingByte(ack->hContact, "ContactPhoto", "NeedUpdate", 1); + QueueAdd(ack->hContact); + } + else + { + // Fetch it now + FetchAvatarFor(ack->hContact, szProto); + } + } + } + return 0; +} + +INT_PTR ProtectAvatar(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE)wParam; + BYTE was_locked = DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0); + + if (fei == NULL || was_locked == (BYTE)lParam) // no need for redundant lockings... + return 0; + + if (hContact) { + if (!was_locked) + MakePathRelative(hContact); + DBWriteContactSettingByte(hContact, "ContactPhoto", "Locked", lParam ? 1 : 0); + if (lParam == 0) + MakePathRelative(hContact); + ChangeAvatar(hContact, TRUE); + } + return 0; +} + +/* + * for subclassing the open file dialog... + */ +struct OpenFileSubclassData { + BYTE *locking_request; + BYTE setView; +}; + +static BOOL CALLBACK OpenFileSubclass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + { + OPENFILENAME *ofn = (OPENFILENAME *)lParam; + + OpenFileSubclassData *data = (OpenFileSubclassData *) malloc(sizeof(OpenFileSubclassData)); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)data); + data->locking_request = (BYTE *)ofn->lCustData; + data->setView = TRUE; + + TranslateDialogDefault(hwnd); + CheckDlgButton(hwnd, IDC_PROTECTAVATAR, *(data->locking_request)); + break; + } + + case WM_COMMAND: + if (LOWORD(wParam) == IDC_PROTECTAVATAR) + { + OpenFileSubclassData *data= (OpenFileSubclassData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + *(data->locking_request) = IsDlgButtonChecked(hwnd, IDC_PROTECTAVATAR) ? TRUE : FALSE; + } + break; + + case WM_NOTIFY: + { + OpenFileSubclassData *data= (OpenFileSubclassData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (data->setView) + { + HWND hwndParent = GetParent(hwnd); + HWND hwndLv = FindWindowEx(hwndParent, NULL, _T("SHELLDLL_DefView"), NULL) ; + if (hwndLv != NULL) + { + SendMessage(hwndLv, WM_COMMAND, SHVIEW_THUMBNAIL, 0); + data->setView = FALSE; + } + } + } + break; + + case WM_NCDESTROY: + OpenFileSubclassData *data= (OpenFileSubclassData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + free((OpenFileSubclassData *)data); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)0); + break; + } + + return FALSE; +} + +/* + * set an avatar (service function) + * if lParam == NULL, a open file dialog will be opened, otherwise, lParam is taken as a FULL + * image filename (will be checked for existance, though) + */ + +INT_PTR avSetAvatar(HANDLE hContact, TCHAR* tszPath) +{ + BYTE is_locked = 0; + TCHAR FileName[MAX_PATH], szBackupName[MAX_PATH]; + TCHAR *szFinalName = NULL; + HANDLE hFile = 0; + BYTE locking_request; + + if (hContact == NULL || fei == NULL) + return 0; + + is_locked = DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0); + + if ( tszPath == NULL ) { + OPENFILENAME ofn = {0}; + TCHAR filter[256]; + + filter[0] = '\0'; + CallService(MS_UTILS_GETBITMAPFILTERSTRINGST, SIZEOF(filter), ( LPARAM )filter); + + if (IsWinVer2000Plus()) + ofn.lStructSize = sizeof(ofn); + else + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = 0; + ofn.lpstrFile = FileName; + ofn.lpstrFilter = filter; + ofn.nMaxFile = MAX_PATH; + ofn.nMaxFileTitle = MAX_PATH; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_ENABLETEMPLATE | OFN_EXPLORER | OFN_ENABLESIZING | OFN_ENABLEHOOK; + ofn.lpstrInitialDir = _T("."); + *FileName = '\0'; + ofn.lpstrDefExt = _T(""); + ofn.hInstance = g_hInst; + ofn.lpTemplateName = MAKEINTRESOURCE(IDD_OPENSUBCLASS); + ofn.lpfnHook = (LPOFNHOOKPROC)OpenFileSubclass; + locking_request = is_locked; + ofn.lCustData = (LPARAM)&locking_request; + if (GetOpenFileName(&ofn)) { + szFinalName = FileName; + is_locked = locking_request ? 1 : is_locked; + } + else + return 0; + } + else + szFinalName = tszPath; + + /* + * filename is now set, check it and perform all needed action + */ + + if ((hFile = CreateFile(szFinalName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) + return 0; + + // file exists... + + CloseHandle(hFile); + + AVS_pathToRelative(szFinalName, szBackupName); + DBWriteContactSettingTString(hContact, "ContactPhoto", "Backup", szBackupName); + + DBWriteContactSettingByte(hContact, "ContactPhoto", "Locked", is_locked); + DBWriteContactSettingTString(hContact, "ContactPhoto", "File", szFinalName); + MakePathRelative(hContact, szFinalName); + // Fix cache + ChangeAvatar(hContact, TRUE); + + return 0; +} + +INT_PTR SetAvatar(WPARAM wParam, LPARAM lParam) +{ return avSetAvatar(( HANDLE )wParam, A2T(( const char* )lParam )); +} + +#if defined( _UNICODE ) +INT_PTR SetAvatarW(WPARAM wParam, LPARAM lParam) +{ return avSetAvatar(( HANDLE )wParam, ( TCHAR* )lParam ); +} +#endif + +/* + * see if is possible to set the avatar for the expecified protocol + */ +static INT_PTR CanSetMyAvatar(WPARAM wParam, LPARAM lParam) +{ + char *protocol = (char *) wParam; + if (protocol == NULL || fei == NULL) + return 0; + + return ProtoServiceExists(protocol, PS_SETMYAVATAR); +} + +struct SetMyAvatarHookData { + char *protocol; + BOOL square; + BOOL grow; + + BOOL thumbnail; +}; + + +/* + * Callback to set thumbnaill view to open dialog + */ +static UINT_PTR CALLBACK SetMyAvatarHookProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + { + InterlockedExchange(&hwndSetMyAvatar, (LONG) hwnd); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)lParam); + OPENFILENAME *ofn = (OPENFILENAME *)lParam; + SetMyAvatarHookData *data = (SetMyAvatarHookData *) ofn->lCustData; + data->thumbnail = TRUE; + + SetWindowText(GetDlgItem(hwnd, IDC_MAKE_SQUARE), TranslateT("Make the avatar square")); + SetWindowText(GetDlgItem(hwnd, IDC_GROW), TranslateT("Grow avatar to fit max allowed protocol size")); + + CheckDlgButton(hwnd, IDC_MAKE_SQUARE, data->square ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_GROW, data->grow ? BST_CHECKED : BST_UNCHECKED); + + if (data->protocol != NULL && (Proto_AvatarImageProportion(data->protocol) & PIP_SQUARE)) + EnableWindow(GetDlgItem(hwnd, IDC_MAKE_SQUARE), FALSE); + } + break; + + case WM_NOTIFY: + { + OPENFILENAME *ofn = (OPENFILENAME *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + SetMyAvatarHookData *data = (SetMyAvatarHookData *) ofn->lCustData; + if (data->thumbnail) + { + HWND hwndParent = GetParent(hwnd); + HWND hwndLv = FindWindowEx(hwndParent, NULL, _T("SHELLDLL_DefView"), NULL) ; + if (hwndLv != NULL) + { + SendMessage(hwndLv, WM_COMMAND, SHVIEW_THUMBNAIL, 0); + data->thumbnail = FALSE; + } + } + break; + } + case WM_DESTROY: + { + OPENFILENAME *ofn = (OPENFILENAME *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + SetMyAvatarHookData *data = (SetMyAvatarHookData *) ofn->lCustData; + data->square = IsDlgButtonChecked(hwnd, IDC_MAKE_SQUARE); + data->grow = IsDlgButtonChecked(hwnd, IDC_GROW); + + InterlockedExchange(&hwndSetMyAvatar, 0); + break; + } + } + + return 0; +} + +const TCHAR *GetFormatExtension(int format) +{ + if (format == PA_FORMAT_PNG) + return _T(".png"); + if (format == PA_FORMAT_JPEG) + return _T(".jpg"); + if (format == PA_FORMAT_ICON) + return _T(".ico"); + if (format == PA_FORMAT_BMP) + return _T(".bmp"); + if (format == PA_FORMAT_GIF) + return _T(".gif"); + if (format == PA_FORMAT_SWF) + return _T(".swf"); + if (format == PA_FORMAT_XML) + return _T(".xml"); + + return NULL; +} + +int GetImageFormat(TCHAR *filename) +{ + size_t len = lstrlen(filename); + + if (len < 5) + return PA_FORMAT_UNKNOWN; + + if (_tcsicmp(_T(".png"), &filename[len-4]) == 0) + return PA_FORMAT_PNG; + + if (_tcsicmp(_T(".jpg"), &filename[len-4]) == 0 || _tcsicmp(_T(".jpeg"), &filename[len-4]) == 0) + return PA_FORMAT_JPEG; + + if (_tcsicmp(_T(".ico"), &filename[len-4]) == 0) + return PA_FORMAT_ICON; + + if (_tcsicmp(_T(".bmp"), &filename[len-4]) == 0 || _tcsicmp(_T(".rle"), &filename[len-4]) == 0) + return PA_FORMAT_BMP; + + if (_tcsicmp(_T(".gif"), &filename[len-4]) == 0) + return PA_FORMAT_GIF; + + if (_tcsicmp(_T(".swf"), &filename[len-4]) == 0) + return PA_FORMAT_SWF; + + if (_tcsicmp(_T(".xml"), &filename[len-4]) == 0) + return PA_FORMAT_XML; + + return PA_FORMAT_UNKNOWN; +} + +static void FilterGetStrings(TCHAR *filter, int bytesLeft, BOOL xml, BOOL swf) +{ + TCHAR *pfilter; + int wParam = bytesLeft; + + lstrcpyn(filter, TranslateT("All Files"), bytesLeft); bytesLeft-=lstrlen(filter); + _tcsncat(filter, _T(" (*.bmp;*.jpg;*.gif;*.png"), bytesLeft); + if (swf) _tcscat(filter, _T(";*.swf")); + if (xml) _tcscat(filter, _T(";*.xml")); + _tcscat(filter, _T(")")); + pfilter=filter+lstrlen(filter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter, _T("*.BMP;*.RLE;*.JPG;*.JPEG;*.GIF;*.PNG"), bytesLeft); + if (swf) _tcscat(pfilter, _T(";*.SWF")); + if (xml) _tcscat(pfilter, _T(";*.XML")); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter, TranslateT("Windows Bitmaps"), bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter, _T(" (*.bmp;*.rle)"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter, _T("*.BMP;*.RLE"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter,TranslateT("JPEG Bitmaps"),bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter, _T(" (*.jpg;*.jpeg)"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter, _T("*.JPG;*.JPEG"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter,TranslateT("GIF Bitmaps"),bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter, _T(" (*.gif)"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter, _T("*.GIF"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter,TranslateT("PNG Bitmaps"), bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter, _T(" (*.png)"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter, _T("*.PNG"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + if (swf) + { + lstrcpyn(pfilter,TranslateT("Flash Animations"), bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter, _T(" (*.swf)"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter, _T("*.SWF"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + } + + if (xml) + { + lstrcpyn(pfilter, TranslateT("XML Files"), bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter, _T(" (*.xml)"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter, _T("*.XML"), bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + } + + if (bytesLeft) *pfilter='\0'; +} + +static void DeleteGlobalUserAvatar() +{ + DBVARIANT dbv = {0}; + if (DBGetContactSettingTString(NULL, AVS_MODULE, "GlobalUserAvatarFile", &dbv)) + return; + + TCHAR szFilename[MAX_PATH]; + AVS_pathToAbsolute(dbv.ptszVal, szFilename); + DBFreeVariant(&dbv); + + DeleteFile(szFilename); + DBDeleteContactSetting(NULL, AVS_MODULE, "GlobalUserAvatarFile"); +} + +static void SetIgnoreNotify(char *protocol, BOOL ignore) +{ + for(int i = 0; i < g_MyAvatars.getCount(); i++) { + if (protocol == NULL || !lstrcmpA(g_MyAvatars[i].szProtoname, protocol)) { + if (ignore) + g_MyAvatars[i].dwFlags |= AVS_IGNORENOTIFY; + else + g_MyAvatars[i].dwFlags &= ~AVS_IGNORENOTIFY; + } + } +} + +static int InternalRemoveMyAvatar(char *protocol) +{ + SetIgnoreNotify(protocol, TRUE); + + // Remove avatar + int ret = 0; + if (protocol != NULL) + { + if (ProtoServiceExists(protocol, PS_SETMYAVATAR)) + ret = SaveAvatar(protocol, NULL); + else + ret = -3; + + if (ret == 0) + { + // Has global avatar? + DBVARIANT dbv = {0}; + if (!DBGetContactSettingTString(NULL, AVS_MODULE, "GlobalUserAvatarFile", &dbv)) + { + DBFreeVariant(&dbv); + DBWriteContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 1); + DeleteGlobalUserAvatar(); + } + } + } + else + { + PROTOACCOUNT **accs; + int i,count; + + ProtoEnumAccounts( &count, &accs ); + for (i = 0; i < count; i++) + { + if ( !ProtoServiceExists( accs[i]->szModuleName, PS_SETMYAVATAR)) + continue; + + if (!Proto_IsAvatarsEnabled( accs[i]->szModuleName )) + continue; + + // Found a protocol + int retTmp = SaveAvatar( accs[i]->szModuleName, NULL); + if (retTmp != 0) + ret = retTmp; + } + + DeleteGlobalUserAvatar(); + + if (ret) + DBWriteContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 1); + else + DBWriteContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 0); + } + + SetIgnoreNotify(protocol, FALSE); + + ReportMyAvatarChanged((WPARAM)(( protocol == NULL ) ? "" : protocol ), 0); + return ret; +} + +static int InternalSetMyAvatar(char *protocol, TCHAR *szFinalName, SetMyAvatarHookData &data, BOOL allAcceptXML, BOOL allAcceptSWF) +{ + HANDLE hFile = 0; + + int format = GetImageFormat(szFinalName); + if (format == PA_FORMAT_UNKNOWN || (hFile = CreateFile(szFinalName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) + return -3; + + CloseHandle(hFile); + + // file exists... + + HBITMAP hBmp = NULL; + + if (format == PA_FORMAT_SWF) + { + if (!allAcceptSWF) + return -4; + } + else if (format == PA_FORMAT_XML) + { + if (!allAcceptXML) + return -4; + } + else + { + // Try to open if is not a flash or XML + hBmp = (HBITMAP) CallService(MS_IMG_LOAD, (WPARAM) szFinalName, IMGL_TCHAR); + if (hBmp == NULL) + return -4; + } + + SetIgnoreNotify(protocol, TRUE); + + int ret = 0; + if (protocol != NULL) + { + ret = SetProtoMyAvatar(protocol, hBmp, szFinalName, format, data.square, data.grow); + + if (ret == 0) + { + DeleteGlobalUserAvatar(); + DBWriteContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 1); + } + } + else + { + PROTOACCOUNT **accs; + int i,count; + + ProtoEnumAccounts( &count, &accs ); + for (i = 0; i < count; i++) + { + if ( !ProtoServiceExists( accs[i]->szModuleName, PS_SETMYAVATAR)) + continue; + + if ( !Proto_IsAvatarsEnabled( accs[i]->szModuleName )) + continue; + + int retTmp = SetProtoMyAvatar( accs[i]->szModuleName, hBmp, szFinalName, format, data.square, data.grow); + if (retTmp != 0) + ret = retTmp; + } + + DeleteGlobalUserAvatar(); + + if (ret) + { + DBWriteContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 1); + } + else + { + // Copy avatar file to store as global one + TCHAR globalFile[1024]; + BOOL saved = TRUE; + if (FoldersGetCustomPathT(hGlobalAvatarFolder, globalFile, SIZEOF(globalFile), _T(""))) + { + mir_sntprintf(globalFile, SIZEOF(globalFile), _T("%s\\%s"), g_szDataPath, _T("GlobalAvatar")); + CreateDirectory(globalFile, NULL); + } + + TCHAR *ext = _tcsrchr(szFinalName, _T('.')); // Can't be NULL here + if (format == PA_FORMAT_XML || format == PA_FORMAT_SWF) + { + mir_sntprintf(globalFile, SIZEOF(globalFile), _T("%s\\my_global_avatar%s"), globalFile, ext); + CopyFile(szFinalName, globalFile, FALSE); + } + else + { + // Resize (to avoid too big avatars) + ResizeBitmap rb = {0}; + rb.size = sizeof(ResizeBitmap); + rb.hBmp = hBmp; + rb.max_height = 300; + rb.max_width = 300; + rb.fit = (data.grow ? 0 : RESIZEBITMAP_FLAG_DONT_GROW) + | (data.square ? RESIZEBITMAP_MAKE_SQUARE : RESIZEBITMAP_KEEP_PROPORTIONS); + + HBITMAP hBmpTmp = (HBITMAP) BmpFilterResizeBitmap((WPARAM)&rb, 0); + + // Check if need to resize + if (hBmpTmp == hBmp || hBmpTmp == NULL) + { + // Use original image + mir_sntprintf(globalFile, SIZEOF(globalFile), _T("%s\\my_global_avatar%s"), globalFile, ext); + CopyFile(szFinalName, globalFile, FALSE); + } + else + { + // Save as PNG + mir_sntprintf(globalFile, SIZEOF(globalFile), _T("%s\\my_global_avatar.png"), globalFile); + if (BmpFilterSaveBitmap((WPARAM) hBmpTmp, (LPARAM) globalFile)) + saved = FALSE; + + DeleteObject(hBmpTmp); + } + } + + if (saved) + { + TCHAR relFile[1024]; + if (AVS_pathToRelative(globalFile, relFile)) + DBWriteContactSettingTString(NULL, AVS_MODULE, "GlobalUserAvatarFile", relFile); + else + DBWriteContactSettingTString(NULL, AVS_MODULE, "GlobalUserAvatarFile", globalFile); + + DBWriteContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 0); + } + else + { + DBWriteContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 1); + } + } + } + + DeleteObject(hBmp); + + SetIgnoreNotify(protocol, FALSE); + + ReportMyAvatarChanged((WPARAM)(( protocol == NULL ) ? "" : protocol ), 0); + return ret; +} + +/* + * set an avatar for a protocol (service function) + * if lParam == NULL, a open file dialog will be opened, otherwise, lParam is taken as a FULL + * image filename (will be checked for existance, though) + */ + +INT_PTR avSetMyAvatar( char* protocol, TCHAR* tszPath ) +{ + TCHAR FileName[MAX_PATH]; + TCHAR *szFinalName = NULL; + BOOL allAcceptXML; + BOOL allAcceptSWF; + + // Protocol allow seting of avatar? + if (protocol != NULL && !CanSetMyAvatar((WPARAM) protocol, 0)) + return -1; + + if (tszPath == NULL && hwndSetMyAvatar != 0) + { + SetForegroundWindow((HWND) hwndSetMyAvatar); + SetFocus((HWND) hwndSetMyAvatar); + ShowWindow((HWND) hwndSetMyAvatar, SW_SHOW); + return -2; + } + + SetMyAvatarHookData data = { 0 }; + + // Check for XML and SWF + if (protocol == NULL) + { + allAcceptXML = TRUE; + allAcceptSWF = TRUE; + + PROTOACCOUNT **accs; + int i,count; + + ProtoEnumAccounts( &count, &accs ); + for (i = 0; i < count; i++) + { + if ( !ProtoServiceExists( accs[i]->szModuleName, PS_SETMYAVATAR)) + continue; + + if ( !Proto_IsAvatarsEnabled( accs[i]->szModuleName )) + continue; + + allAcceptXML = allAcceptXML && Proto_IsAvatarFormatSupported( accs[i]->szModuleName, PA_FORMAT_XML); + allAcceptSWF = allAcceptSWF && Proto_IsAvatarFormatSupported( accs[i]->szModuleName, PA_FORMAT_SWF); + } + + data.square = DBGetContactSettingByte(0, AVS_MODULE, "SetAllwaysMakeSquare", 0); + } + else + { + allAcceptXML = Proto_IsAvatarFormatSupported(protocol, PA_FORMAT_XML); + allAcceptSWF = Proto_IsAvatarFormatSupported(protocol, PA_FORMAT_SWF); + + data.protocol = protocol; + data.square = (Proto_AvatarImageProportion(protocol) & PIP_SQUARE) + || DBGetContactSettingByte(0, AVS_MODULE, "SetAllwaysMakeSquare", 0); + } + + if (tszPath == NULL) { + OPENFILENAME ofn = {0}; + TCHAR filter[512]; + TCHAR inipath[1024]; + + data.protocol = protocol; + + filter[0] = '\0'; + FilterGetStrings(filter, SIZEOF(filter), allAcceptXML, allAcceptSWF); + + FoldersGetCustomPathT(hMyAvatarsFolder, inipath, SIZEOF(inipath), _T(".")); + + if (IsWinVer2000Plus()) + ofn.lStructSize = sizeof(ofn); + else + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = 0; + ofn.lpstrFile = FileName; + ofn.lpstrFilter = filter; + ofn.nMaxFile = MAX_PATH; + ofn.nMaxFileTitle = MAX_PATH; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_ENABLETEMPLATE | OFN_EXPLORER | OFN_ENABLESIZING | OFN_ENABLEHOOK; + ofn.lpstrInitialDir = inipath; + ofn.lpTemplateName = MAKEINTRESOURCE(IDD_SET_OWN_SUBCLASS); + ofn.lpfnHook = SetMyAvatarHookProc; + ofn.lCustData = (LPARAM) &data; + + *FileName = '\0'; + ofn.lpstrDefExt = _T(""); + ofn.hInstance = g_hInst; + + TCHAR title[256]; + if (protocol == NULL) + mir_sntprintf(title, SIZEOF(title), TranslateT("Set My Avatar")); + else + { + TCHAR* prototmp = mir_a2t(protocol); + mir_sntprintf(title, SIZEOF(title), TranslateT("Set My Avatar for %s"), prototmp); + mir_free(prototmp); + } + ofn.lpstrTitle = title; + + if (GetOpenFileName(&ofn)) + szFinalName = FileName; + else + return 1; + } + else + szFinalName = (TCHAR *)tszPath; + + /* + * filename is now set, check it and perform all needed action + */ + + if (szFinalName[0] == '\0') + return InternalRemoveMyAvatar(protocol); + + return InternalSetMyAvatar(protocol, szFinalName, data, allAcceptXML, allAcceptSWF); +} + +static INT_PTR SetMyAvatar( WPARAM wParam, LPARAM lParam ) +{ return avSetMyAvatar(( char* )wParam, A2T(( const char* )lParam )); +} + +#if defined( _UNICODE ) +static INT_PTR SetMyAvatarW( WPARAM wParam, LPARAM lParam ) +{ return avSetMyAvatar(( char* )wParam, ( TCHAR* )lParam ); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +static DWORD GetFileSize(TCHAR *szFilename) +{ + HANDLE hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + return 0; + + DWORD low = GetFileSize(hFile, NULL); + + CloseHandle(hFile); + + if (low == INVALID_FILE_SIZE) + return 0; + + return low; +} + +struct SaveProtocolData { + DWORD max_size; + TCHAR image_file_name[MAX_PATH]; + BOOL saved; + BOOL need_smaller_size; + int width; + int height; + TCHAR temp_file[MAX_PATH]; + HBITMAP hBmpProto; +}; + +static void SaveImage(SaveProtocolData &d, char *protocol, int format) +{ + if (Proto_IsAvatarFormatSupported(protocol, format)) + { + mir_sntprintf(d.image_file_name, SIZEOF(d.image_file_name), _T("%s%s"), d.temp_file, GetFormatExtension(format)); + if (!BmpFilterSaveBitmapT(d.hBmpProto, d.image_file_name, format == PA_FORMAT_JPEG ? JPEG_QUALITYSUPERB : 0)) + { + if (d.max_size != 0 && GetFileSize(d.image_file_name) > d.max_size) + { + DeleteFile(d.image_file_name); + + if (format == PA_FORMAT_JPEG) + { + // Try with lower quality + if (!BmpFilterSaveBitmapT(d.hBmpProto, d.image_file_name, JPEG_QUALITYGOOD)) + { + if (GetFileSize(d.image_file_name) > d.max_size) + { + DeleteFile(d.image_file_name); + d.need_smaller_size = TRUE; + } + else + d.saved = TRUE; + } + } + else + d.need_smaller_size = TRUE; + } + else + d.saved = TRUE; + } + } +} + +static int SetProtoMyAvatar(char *protocol, HBITMAP hBmp, TCHAR *originalFilename, int originalFormat, + BOOL square, BOOL grow) +{ + if (!ProtoServiceExists(protocol, PS_SETMYAVATAR)) + return -1; + + // If is swf or xml, just set it + + if (originalFormat == PA_FORMAT_SWF) + { + if (!Proto_IsAvatarFormatSupported(protocol, PA_FORMAT_SWF)) + return -1; + + return SaveAvatar(protocol, originalFilename); + } + + if (originalFormat == PA_FORMAT_XML) + { + if (!Proto_IsAvatarFormatSupported(protocol, PA_FORMAT_XML)) + return -1; + + return SaveAvatar(protocol, originalFilename); + } + + // Get protocol info + SaveProtocolData d = {0}; + + d.max_size = (DWORD) Proto_GetAvatarMaxFileSize(protocol); + + Proto_GetAvatarMaxSize(protocol, &d.width, &d.height); + int orig_width = d.width; + int orig_height = d.height; + + if (Proto_AvatarImageProportion(protocol) & PIP_SQUARE) + square = TRUE; + + + // Try to save until a valid image is found or we give up + int num_tries = 0; + do { + // Lets do it + ResizeBitmap rb; + rb.size = sizeof(ResizeBitmap); + rb.hBmp = hBmp; + rb.max_height = d.height; + rb.max_width = d.width; + rb.fit = (grow ? 0 : RESIZEBITMAP_FLAG_DONT_GROW) + | (square ? RESIZEBITMAP_MAKE_SQUARE : RESIZEBITMAP_KEEP_PROPORTIONS); + + d.hBmpProto = (HBITMAP) BmpFilterResizeBitmap((WPARAM)&rb, 0); + + if (d.hBmpProto == NULL) + { + if (d.temp_file[0] != '\0') + DeleteFile(d.temp_file); + return -1; + } + + // Check if can use original image + if (d.hBmpProto == hBmp + && Proto_IsAvatarFormatSupported(protocol, originalFormat) + && (d.max_size == 0 || GetFileSize(originalFilename) < d.max_size)) + { + if (d.temp_file[0] != '\0') + DeleteFile(d.temp_file); + + // Use original image + return SaveAvatar(protocol, originalFilename); + } + + // Create a temporary file (if was not created already) + if (d.temp_file[0] == '\0') + { + d.temp_file[0] = '\0'; + if (GetTempPath(MAX_PATH, d.temp_file) == 0 + || GetTempFileName(d.temp_file, _T("mir_av_"), 0, d.temp_file) == 0) + { + DeleteObject(d.hBmpProto); + return -1; + } + } + + // Which format? + + // First try to use original format + if (originalFormat != PA_FORMAT_BMP) + SaveImage(d, protocol, originalFormat); + + if (!d.saved && originalFormat != PA_FORMAT_PNG) + SaveImage(d, protocol, PA_FORMAT_PNG); + + if (!d.saved && originalFormat != PA_FORMAT_JPEG) + SaveImage(d, protocol, PA_FORMAT_JPEG); + + if (!d.saved && originalFormat != PA_FORMAT_GIF) + SaveImage(d, protocol, PA_FORMAT_GIF); + + if (!d.saved) + SaveImage(d, protocol, PA_FORMAT_BMP); + + num_tries++; + if (!d.saved && d.need_smaller_size && num_tries < 4) + { + // Cleanup + if (d.hBmpProto != hBmp) + DeleteObject(d.hBmpProto); + + // use a smaller size + d.width = orig_width * (4 - num_tries) / 4; + d.height = orig_height * (4 - num_tries) / 4; + } + + } while(!d.saved && d.need_smaller_size && num_tries < 4); + + int ret; + + if (d.saved) + { + // Call proto service + ret = SaveAvatar(protocol, d.image_file_name); + DeleteFile(d.image_file_name); + } + else + { + ret = -1; + } + + if (d.temp_file[0] != '\0') + DeleteFile(d.temp_file); + + if (d.hBmpProto != hBmp) + DeleteObject(d.hBmpProto); + + return ret; +} + +static INT_PTR ContactOptions(WPARAM wParam, LPARAM lParam) +{ + if (wParam) + CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_AVATAROPTIONS), 0, DlgProcAvatarOptions, (LPARAM)wParam); + return 0; +} + +INT_PTR GetMyAvatar(WPARAM wParam, LPARAM lParam) +{ + int i; + char *szProto = (char *)lParam; + + if (wParam || g_shutDown || fei == NULL) + return 0; + + if (lParam == 0 || IsBadReadPtr(szProto, 4)) + return 0; + + for(i = 0; i < g_MyAvatars.getCount(); i++) { + if (!lstrcmpA(szProto, g_MyAvatars[i].szProtoname) && g_MyAvatars[i].hbmPic != 0) + return (INT_PTR)&g_MyAvatars[i]; + } + return 0; +} + +static protoPicCacheEntry *GetProtoDefaultAvatar(HANDLE hContact) +{ + char *szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + if (szProto) { + for(int i = 0; i < g_ProtoPictures.getCount(); i++) { + protoPicCacheEntry& p = g_ProtoPictures[i]; + if ( !lstrcmpA(p.szProtoname, szProto) && p.hbmPic != NULL) + return &g_ProtoPictures[i]; + } + } + return NULL; +} + +HANDLE GetContactThatHaveTheAvatar(HANDLE hContact, int locked = -1) +{ + if (g_MetaAvail && DBGetContactSettingByte(NULL, g_szMetaName, "Enabled", 0)) { + if (DBGetContactSettingDword(hContact, g_szMetaName, "NumContacts", 0) >= 1) { + if (locked == -1) + locked = DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0); + + if (!locked) + hContact = (HANDLE)CallService(MS_MC_GETMOSTONLINECONTACT, (WPARAM)hContact, 0); + } + } + return hContact; +} + +INT_PTR GetAvatarBitmap(WPARAM wParam, LPARAM lParam) +{ + if (wParam == 0 || g_shutDown || fei == NULL) + return 0; + + HANDLE hContact = (HANDLE) wParam; + hContact = GetContactThatHaveTheAvatar(hContact); + + // Get the node + struct CacheNode *node = FindAvatarInCache(hContact, TRUE); + if (node == NULL || !node->loaded) + return (INT_PTR) GetProtoDefaultAvatar(hContact); + else + return (INT_PTR) &node->ace; +} + +// Just delete an avatar from cache +// An cache entry is never deleted. What is deleted is the image handle inside it +// This is done this way to keep track of which avatars avs have to keep track +void DeleteAvatarFromCache(HANDLE hContact, BOOL forever) +{ + hContact = GetContactThatHaveTheAvatar(hContact); + + struct CacheNode *node = FindAvatarInCache(hContact, FALSE); + if (node == NULL) { + struct CacheNode temp_node = {0}; + if (g_MetaAvail) + temp_node.dwFlags |= (DBGetContactSettingByte(hContact, g_szMetaName, "IsSubcontact", 0) ? MC_ISSUBCONTACT : 0); + NotifyMetaAware(hContact, &temp_node, (AVATARCACHEENTRY *)GetProtoDefaultAvatar(hContact)); + return; + } + node->mustLoad = -1; // mark for deletion + if (forever) + node->dwFlags |= AVS_DELETENODEFOREVER; + SetEvent(hLoaderEvent); +} + +int ChangeAvatar(HANDLE hContact, BOOL fLoad, BOOL fNotifyHist, int pa_format) +{ + if (g_shutDown) + return 0; + + hContact = GetContactThatHaveTheAvatar(hContact); + + // Get the node + struct CacheNode *node = FindAvatarInCache(hContact, g_AvatarHistoryAvail && fNotifyHist, TRUE); + if (node == NULL) + return 0; + + if (fNotifyHist) + node->dwFlags |= AVH_MUSTNOTIFY; + + node->mustLoad = fLoad ? 1 : -1; + node->pa_format = pa_format; + SetEvent(hLoaderEvent); + return 0; +} + +/* + * this thread scans the cache and handles nodes which have mustLoad set to > 0 (must be loaded/reloaded) or + * nodes where mustLoad is < 0 (must be deleted). + * its waken up by the event and tries to lock the cache only when absolutely necessary. + */ + +static void PicLoader(LPVOID param) +{ + DWORD dwDelay = DBGetContactSettingDword(NULL, AVS_MODULE, "picloader_sleeptime", 80); + + if (dwDelay < 30) + dwDelay = 30; + else if (dwDelay > 100) + dwDelay = 100; + + while(!g_shutDown) { + struct CacheNode *node = g_Cache; + + while(!g_shutDown && node) { + if (node->mustLoad > 0 && node->ace.hContact) { + node->mustLoad = 0; + AVATARCACHEENTRY ace_temp; + + if (DBGetContactSettingByte(node->ace.hContact, "ContactPhoto", "NeedUpdate", 0)) + QueueAdd(node->ace.hContact); + + CopyMemory(&ace_temp, &node->ace, sizeof(AVATARCACHEENTRY)); + ace_temp.hbmPic = 0; + + int result = CreateAvatarInCache(node->ace.hContact, &ace_temp, NULL); + + if (result == -2) + { + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)node->ace.hContact, 0); + if (szProto == NULL || Proto_NeedDelaysForAvatars(szProto)) + { + QueueAdd(node->ace.hContact); + } + else + { + if (FetchAvatarFor(node->ace.hContact, szProto) == GAIR_SUCCESS) + // Try yo create again + result = CreateAvatarInCache(node->ace.hContact, &ace_temp, NULL); + } + } + + if ((result == 1 && ace_temp.hbmPic != 0)) // Loaded + { + HBITMAP oldPic = node->ace.hbmPic; + + EnterCriticalSection(&cachecs); + CopyMemory(&node->ace, &ace_temp, sizeof(AVATARCACHEENTRY)); + node->loaded = TRUE; + LeaveCriticalSection(&cachecs); + if (oldPic) + DeleteObject(oldPic); + NotifyMetaAware(node->ace.hContact, node); + } + else if (result == 0 || result == -3) // Has no avatar + { + HBITMAP oldPic = node->ace.hbmPic; + + EnterCriticalSection(&cachecs); + CopyMemory(&node->ace, &ace_temp, sizeof(AVATARCACHEENTRY)); + node->loaded = FALSE; + node->mustLoad = 0; + LeaveCriticalSection(&cachecs); + if (oldPic) + DeleteObject(oldPic); + NotifyMetaAware(node->ace.hContact, node); + } + + mir_sleep(dwDelay); + } + else if (node->mustLoad < 0 && node->ace.hContact) { // delete this picture + HANDLE hContact = node->ace.hContact; + EnterCriticalSection(&cachecs); + node->mustLoad = 0; + node->loaded = 0; + if (node->ace.hbmPic) + DeleteObject(node->ace.hbmPic); + ZeroMemory(&node->ace, sizeof(AVATARCACHEENTRY)); + if (node->dwFlags & AVS_DELETENODEFOREVER) { + node->dwFlags &= ~AVS_DELETENODEFOREVER; + } else { + node->ace.hContact = hContact; + } + LeaveCriticalSection(&cachecs); + NotifyMetaAware(hContact, node, (AVATARCACHEENTRY *)GetProtoDefaultAvatar(hContact)); + } + // protect this by changes from the cache block allocator as it can cause inconsistencies while working + // on allocating a new block. + EnterCriticalSection(&alloccs); + node = node->pNextNode; + LeaveCriticalSection(&alloccs); + } + WaitForSingleObject(hLoaderEvent, INFINITE); + //_DebugTrace(0, "pic loader awake..."); + ResetEvent(hLoaderEvent); + } +} + +static int MetaChanged(WPARAM wParam, LPARAM lParam) +{ + if (wParam == 0 || g_shutDown) + return 0; + + AVATARCACHEENTRY *ace; + + HANDLE hContact = (HANDLE) wParam; + HANDLE hSubContact = GetContactThatHaveTheAvatar(hContact); + + // Get the node + struct CacheNode *node = FindAvatarInCache(hSubContact, TRUE); + if (node == NULL || !node->loaded) { + ace = (AVATARCACHEENTRY *)GetProtoDefaultAvatar(hSubContact); + QueueAdd(hSubContact); + } + else + ace = &node->ace; + + NotifyEventHooks(hEventChanged, (WPARAM)hContact, (LPARAM)ace); + return 0; +} + +static LIST arServices( 10 ); + +static int DestroyServicesAndEvents() +{ + UnhookEvent(hContactSettingChanged); + UnhookEvent(hProtoAckHook); + UnhookEvent(hUserInfoInitHook); + UnhookEvent(hOptInit); + UnhookEvent(hModulesLoaded); + UnhookEvent(hPresutdown); + UnhookEvent(hOkToExit); + UnhookEvent(hAccChanged); + + for ( int i=0; i < arServices.getCount(); i++ ) + DestroyServiceFunction( arServices[i] ); + + arServices.destroy(); + + DestroyHookableEvent(hEventChanged); + DestroyHookableEvent(hEventContactAvatarChanged); + DestroyHookableEvent(hMyAvatarChanged); + hEventChanged = 0; + hEventContactAvatarChanged = 0; + hMyAvatarChanged = 0; + UnhookEvent(hEventDeleted); + return 0; +} + +static void LoadDefaultInfo() +{ + protoPicCacheEntry* pce = new protoPicCacheEntry; + if (CreateAvatarInCache(0, pce, AVS_DEFAULT) != 1) + DBDeleteContactSetting(0, PPICT_MODULE, AVS_DEFAULT); + + pce->szProtoname = mir_strdup(AVS_DEFAULT); + pce->tszAccName = mir_tstrdup(TranslateT(AVS_DEFAULT)); + g_ProtoPictures.insert(pce); +} + +static void LoadProtoInfo( PROTOCOLDESCRIPTOR* proto ) +{ + if ( proto->type == PROTOTYPE_PROTOCOL && proto->cbSize == sizeof( *proto )) + { + char protoName[MAX_PATH]; + mir_snprintf(protoName, SIZEOF(protoName), "Global avatar for %s accounts", proto->szName); + TCHAR protoNameTmp[MAX_PATH]; + TCHAR *tszName = mir_a2t(proto->szName); + mir_sntprintf(protoNameTmp, SIZEOF(protoNameTmp), TranslateT("Global avatar for %s accounts"), tszName); + protoPicCacheEntry* pce = new protoPicCacheEntry; + if (CreateAvatarInCache(0, pce, protoName) != 1) + DBDeleteContactSetting(0, PPICT_MODULE, protoName); + + pce->szProtoname = mir_strdup(protoName); + pce->tszAccName = mir_tstrdup(protoNameTmp); + g_ProtoPictures.insert(pce); + mir_free(tszName); + } +} + +static void LoadAccountInfo( PROTOACCOUNT* acc ) +{ + protoPicCacheEntry* pce = new protoPicCacheEntry; + if ( CreateAvatarInCache(0, pce, acc->szModuleName ) != 1 ) + DBDeleteContactSetting(0, PPICT_MODULE, acc->szModuleName); + + pce->szProtoname = mir_strdup( acc->szModuleName ); + pce->tszAccName = mir_tstrdup( acc->tszAccountName ); + g_ProtoPictures.insert( pce ); + + pce = new protoPicCacheEntry; + CreateAvatarInCache((HANDLE)-1, pce, acc->szModuleName ); + pce->szProtoname = mir_strdup( acc->szModuleName ); + pce->tszAccName = mir_tstrdup( acc->tszAccountName ); + g_MyAvatars.insert( pce ); +} + +static int OnAccChanged(WPARAM wParam, LPARAM lParam) +{ + PROTOACCOUNT* pa = ( PROTOACCOUNT* )lParam; + + switch( wParam ) { + case PRAC_ADDED: + LoadAccountInfo( pa ); + break; + + case PRAC_REMOVED: + { + int idx; + protoPicCacheEntry tmp; + tmp.szProtoname = mir_strdup(pa->szModuleName); + if (( idx = g_ProtoPictures.getIndex( &tmp )) != -1 ) + g_ProtoPictures.remove( idx ); + if (( idx = g_MyAvatars.getIndex( &tmp )) != -1 ) + g_MyAvatars.remove( idx ); + } + break; + } + + return 0; +} + +static int ModulesLoaded(WPARAM wParam, LPARAM lParam) +{ + int i; + DBVARIANT dbv = {0}; + TCHAR szEventName[100]; + int result = 0; + + InitPolls(); + + mir_sntprintf(szEventName, 100, _T("avs_loaderthread_%d"), GetCurrentThreadId()); + hLoaderEvent = CreateEvent(NULL, TRUE, FALSE, szEventName); + hLoaderThread = (HANDLE) mir_forkthread(PicLoader, 0); + SetThreadPriority(hLoaderThread, THREAD_PRIORITY_IDLE); + + // Folders plugin support + if (ServiceExists(MS_FOLDERS_REGISTER_PATH)) + { + hMyAvatarsFolder = (HANDLE) FoldersRegisterCustomPathT("Avatars", "My Avatars", + MIRANDA_USERDATAT _T("\\Avatars")); + + hGlobalAvatarFolder = (HANDLE) FoldersRegisterCustomPathT("Avatars", "My Global Avatar Cache", + MIRANDA_USERDATAT _T("\\Avatars")); + } + + g_AvatarHistoryAvail = ServiceExists(MS_AVATARHISTORY_ENABLED); + + g_MetaAvail = ServiceExists(MS_MC_GETPROTOCOLNAME) ? TRUE : FALSE; + if (g_MetaAvail) { + g_szMetaName = (char *)CallService(MS_MC_GETPROTOCOLNAME, 0, 0); + if (g_szMetaName == NULL) + g_MetaAvail = FALSE; + } + + PROTOACCOUNT **accs = NULL; + int accCount; + ProtoEnumAccounts( &accCount, &accs ); + + if ( fei != NULL ) + { + LoadDefaultInfo(); + PROTOCOLDESCRIPTOR** proto; + int protoCount; + CallService(MS_PROTO_ENUMPROTOS, ( WPARAM )&protoCount, ( LPARAM )&proto); + for ( i=0; i < protoCount; i++ ) + LoadProtoInfo( proto[i] ); + for(i = 0; i < accCount; i++) + LoadAccountInfo( accs[i] ); + } + + // Load global avatar + protoPicCacheEntry* pce = new protoPicCacheEntry; + CreateAvatarInCache((HANDLE)-1, pce, ""); + pce->szProtoname = mir_strdup(""); + g_MyAvatars.insert( pce ); + + hAccChanged = HookEvent(ME_PROTO_ACCLISTCHANGED, OnAccChanged); + hPresutdown = HookEvent(ME_SYSTEM_PRESHUTDOWN, ShutdownProc); + hOkToExit = HookEvent(ME_SYSTEM_OKTOEXIT, OkToExitProc); + hUserInfoInitHook = HookEvent(ME_USERINFO_INITIALISE, OnDetailsInit); + + return 0; +} + +static void ReloadMyAvatar(LPVOID lpParam) +{ + char *szProto = (char *)lpParam; + + mir_sleep(500); + for(int i = 0; !g_shutDown && i < g_MyAvatars.getCount(); i++) { + char *myAvatarProto = g_MyAvatars[i].szProtoname; + + if (szProto[0] == 0) { + // Notify to all possibles + if (lstrcmpA(myAvatarProto, szProto)) { + if (!ProtoServiceExists( myAvatarProto, PS_SETMYAVATAR)) + continue; + if (!Proto_IsAvatarsEnabled( myAvatarProto )) + continue; + } + + } else if (lstrcmpA(myAvatarProto, szProto)) { + continue; + } + + if (g_MyAvatars[i].hbmPic) + DeleteObject(g_MyAvatars[i].hbmPic); + + if (CreateAvatarInCache((HANDLE)-1, &g_MyAvatars[i], myAvatarProto) != -1) + NotifyEventHooks(hMyAvatarChanged, (WPARAM)myAvatarProto, (LPARAM)&g_MyAvatars[i]); + else + NotifyEventHooks(hMyAvatarChanged, (WPARAM)myAvatarProto, 0); + } + + free(lpParam); +} + +static INT_PTR ReportMyAvatarChanged(WPARAM wParam, LPARAM lParam) +{ + if (wParam == NULL) + return -1; + + char *proto = (char *) wParam; + + for(int i = 0; i < g_MyAvatars.getCount(); i++) { + if (g_MyAvatars[i].dwFlags & AVS_IGNORENOTIFY) + continue; + + if ( !lstrcmpA(g_MyAvatars[i].szProtoname, proto)) { + LPVOID lpParam = (void *)malloc(lstrlenA(g_MyAvatars[i].szProtoname) + 2); + strcpy((char *)lpParam, g_MyAvatars[i].szProtoname); + mir_forkthread(ReloadMyAvatar, lpParam); + return 0; + } + } + + return -2; +} + +static int ContactSettingChanged(WPARAM wParam, LPARAM lParam) +{ + DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam; + + if (cws == NULL || g_shutDown) + return 0; + + if (wParam == 0) { + if (!strcmp(cws->szSetting, "AvatarFile") + || !strcmp(cws->szSetting, "PictObject") + || !strcmp(cws->szSetting, "AvatarHash") + || !strcmp(cws->szSetting, "AvatarSaved")) + { + ReportMyAvatarChanged((WPARAM) cws->szModule, 0); + } + return 0; + } + else if (g_MetaAvail && !strcmp(cws->szModule, g_szMetaName)) { + if (lstrlenA(cws->szSetting) > 6 && !strncmp(cws->szSetting, "Status", 5)) + MetaChanged(wParam, 0); + } + return 0; +} + +static int ContactDeleted(WPARAM wParam, LPARAM lParam) +{ + DeleteAvatarFromCache((HANDLE)wParam, TRUE); + + return 0; +} + +static int OptInit(WPARAM wParam, LPARAM lParam) +{ + OPTIONSDIALOGPAGE odp; + + ZeroMemory(&odp, sizeof(odp)); + odp.cbSize = sizeof(odp); + odp.position = 0; + odp.hInstance = g_hInst; + odp.flags = ODPF_BOLDGROUPS | ODPF_EXPERTONLY | ODPF_TCHAR; + odp.ptszGroup = LPGENT("Customize"); + odp.ptszTitle = LPGENT("Avatars"); + + odp.ptszTab = LPGENT("Protocols"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_PICTS); + odp.pfnDlgProc = DlgProcOptionsProtos; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM) &odp); + + odp.ptszTab = LPGENT("Contact Avatars"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_AVATARS); + odp.pfnDlgProc = DlgProcOptionsAvatars; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM) &odp); + + odp.ptszTab = LPGENT("Own Avatars"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_OWN); + odp.pfnDlgProc = DlgProcOptionsOwn; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM) &odp); + + return 0; +} + +static int OkToExitProc(WPARAM wParam, LPARAM lParam) +{ + EnterCriticalSection(&cachecs); + g_shutDown = TRUE; + + DestroyServicesAndEvents(); + + LeaveCriticalSection(&cachecs); + + SetEvent(hLoaderEvent); + FreePolls(); + return 0; +} + +static int ShutdownProc(WPARAM wParam, LPARAM lParam) +{ + DeleteCriticalSection(&cachecs); + DeleteCriticalSection(&alloccs); + return 0; +} + +void InternalDrawAvatar(AVATARDRAWREQUEST *r, HBITMAP hbm, LONG bmWidth, LONG bmHeight, DWORD dwFlags) +{ + float dScale = 0; + int newHeight, newWidth; + HDC hdcAvatar; + HBITMAP hbmMem; + DWORD topoffset = 0, leftoffset = 0; + HRGN rgn = 0, oldRgn = 0; + int targetWidth = r->rcDraw.right - r->rcDraw.left; + int targetHeight = r->rcDraw.bottom - r->rcDraw.top; + BLENDFUNCTION bf = {0}; + + hdcAvatar = CreateCompatibleDC(r->hTargetDC); + hbmMem = (HBITMAP)SelectObject(hdcAvatar, hbm); + + if ((r->dwFlags & AVDRQ_DONTRESIZEIFSMALLER) && bmHeight <= targetHeight && bmWidth <= targetWidth) { + newHeight = bmHeight; + newWidth = bmWidth; + } + else if (bmHeight >= bmWidth) { + dScale = targetHeight / (float)bmHeight; + newHeight = targetHeight; + newWidth = (int) (bmWidth * dScale); + } + else { + dScale = targetWidth / (float)bmWidth; + newWidth = targetWidth; + newHeight = (int) (bmHeight * dScale); + } + + topoffset = targetHeight > newHeight ? (targetHeight - newHeight) / 2 : 0; + leftoffset = targetWidth > newWidth ? (targetWidth - newWidth) / 2 : 0; + + // create the region for the avatar border - use the same region for clipping, if needed. + + oldRgn = CreateRectRgn(0,0,1,1); + + if (GetClipRgn(r->hTargetDC, oldRgn) != 1) + { + DeleteObject(oldRgn); + oldRgn = NULL; + } + + if (r->dwFlags & AVDRQ_ROUNDEDCORNER) + rgn = CreateRoundRectRgn(r->rcDraw.left + leftoffset, r->rcDraw.top + topoffset, r->rcDraw.left + leftoffset + newWidth + 1, r->rcDraw.top + topoffset + newHeight + 1, 2 * r->radius, 2 * r->radius); + else + rgn = CreateRectRgn(r->rcDraw.left + leftoffset, r->rcDraw.top + topoffset, r->rcDraw.left + leftoffset + newWidth, r->rcDraw.top + topoffset + newHeight); + + ExtSelectClipRgn(r->hTargetDC, rgn, RGN_AND); + + bf.SourceConstantAlpha = r->alpha > 0 ? r->alpha : 255; + bf.AlphaFormat = dwFlags & AVS_PREMULTIPLIED ? AC_SRC_ALPHA : 0; + + if (!(r->dwFlags & AVDRQ_AERO)) + SetStretchBltMode(r->hTargetDC, HALFTONE); + //else + // FillRect(r->hTargetDC, &r->rcDraw, (HBRUSH)GetStockObject(BLACK_BRUSH)); + + if (r->dwFlags & AVDRQ_FORCEFASTALPHA && !(r->dwFlags & AVDRQ_AERO) && AvsAlphaBlend) { + AvsAlphaBlend( + r->hTargetDC, r->rcDraw.left + leftoffset, r->rcDraw.top + topoffset, newWidth, newHeight, + hdcAvatar, 0, 0, bmWidth, bmHeight, bf); + } else { + if ((bf.SourceConstantAlpha == 255 && bf.AlphaFormat == 0 && !(r->dwFlags & AVDRQ_FORCEALPHA) && !(r->dwFlags & AVDRQ_AERO)) || !AvsAlphaBlend) { + StretchBlt(r->hTargetDC, r->rcDraw.left + leftoffset, r->rcDraw.top + topoffset, newWidth, newHeight, hdcAvatar, 0, 0, bmWidth, bmHeight, SRCCOPY); + } else { + /* + * get around SUCKY AlphaBlend() rescaling quality... + */ + FIBITMAP *fb = fei->FI_CreateDIBFromHBITMAP(hbm); + FIBITMAP *fbResized = fei->FI_Rescale(fb, newWidth, newHeight, FILTER_BICUBIC); + HBITMAP hbmResized = fei->FI_CreateHBITMAPFromDIB(fbResized); + fei->FI_Unload(fb); + fei->FI_Unload(fbResized); + + HBITMAP hbmTempOld; + HDC hdcTemp = CreateCompatibleDC(r->hTargetDC); + hbmTempOld = (HBITMAP)SelectObject(hdcTemp, hbmResized); + + AvsAlphaBlend( + r->hTargetDC, r->rcDraw.left + leftoffset, r->rcDraw.top + topoffset, newWidth, newHeight, + hdcTemp, 0, 0, newWidth, newHeight, bf); + + SelectObject(hdcTemp, hbmTempOld); + DeleteObject(hbmResized); + DeleteDC(hdcTemp); + } + + if ((r->dwFlags & AVDRQ_DRAWBORDER) && !((r->dwFlags & AVDRQ_HIDEBORDERONTRANSPARENCY) && (dwFlags & AVS_HASTRANSPARENCY))) { + HBRUSH br = CreateSolidBrush(r->clrBorder); + HBRUSH brOld = (HBRUSH)SelectObject(r->hTargetDC, br); + FrameRgn(r->hTargetDC, rgn, br, 1, 1); + SelectObject(r->hTargetDC, brOld); + DeleteObject(br); + } + + SelectClipRgn(r->hTargetDC, oldRgn); + DeleteObject(rgn); + if (oldRgn) DeleteObject(oldRgn); + + SelectObject(hdcAvatar, hbmMem); + DeleteDC(hdcAvatar); + } +} + +INT_PTR DrawAvatarPicture(WPARAM wParam, LPARAM lParam) +{ + AVATARDRAWREQUEST *r = (AVATARDRAWREQUEST *)lParam; + AVATARCACHEENTRY *ace = NULL; + + if (fei == NULL || r == NULL || IsBadReadPtr((void *)r, sizeof(AVATARDRAWREQUEST))) + return 0; + + if (r->cbSize != sizeof(AVATARDRAWREQUEST)) + return 0; + + if (r->dwFlags & AVDRQ_PROTOPICT) { + if (r->szProto == NULL) + return 0; + + for(int i = 0; i < g_ProtoPictures.getCount(); i++) { + protoPicCacheEntry& p = g_ProtoPictures[i]; + if ( !lstrcmpA(p.szProtoname, r->szProto) && lstrlenA(r->szProto) == lstrlenA(p.szProtoname) && p.hbmPic != 0) { + ace = (AVATARCACHEENTRY *)&g_ProtoPictures[i]; + break; + } + } + } + else if (r->dwFlags & AVDRQ_OWNPIC) { + if (r->szProto == NULL) + return 0; + + if (r->szProto[0] == '\0' && DBGetContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 1)) + return -1; + + ace = (AVATARCACHEENTRY *)GetMyAvatar(0, (LPARAM)r->szProto); + } + else + ace = (AVATARCACHEENTRY *)GetAvatarBitmap((WPARAM)r->hContact, 0); + + if (ace && (!(r->dwFlags & AVDRQ_RESPECTHIDDEN) || !(ace->dwFlags & AVS_HIDEONCLIST))) { + ace->t_lastAccess = time(NULL); + + if (ace->bmHeight == 0 || ace->bmWidth == 0 || ace->hbmPic == 0) + return 0; + + InternalDrawAvatar(r, ace->hbmPic, ace->bmWidth, ace->bmHeight, ace->dwFlags); + return 1; + } + + return 0; +} + +static int OnDetailsInit(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE) lParam; + if (hContact == NULL) + { + // User dialog + OPTIONSDIALOGPAGE odp = {0}; + odp.cbSize = sizeof(odp); + odp.flags = ODPF_TCHAR; + odp.hIcon = g_hIcon; + odp.hInstance = g_hInst; + odp.pfnDlgProc = DlgProcAvatarProtoInfo; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_PROTO_AVATARS); + odp.ptszTitle = LPGENT("Avatar"); + CallService(MS_USERINFO_ADDPAGE, wParam, (LPARAM)&odp); + } + else + { + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + if (szProto == NULL || DBGetContactSettingByte(NULL, AVS_MODULE, szProto, 1)) + { + // Contact dialog + OPTIONSDIALOGPAGE odp = {0}; + odp.cbSize = sizeof(odp); + odp.flags = ODPF_TCHAR; + odp.hIcon = g_hIcon; + odp.hInstance = g_hInst; + odp.pfnDlgProc = DlgProcAvatarUserInfo; + odp.position = -2000000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_USER_AVATAR); + odp.ptszTitle = LPGENT("Avatar"); + CallService(MS_USERINFO_ADDPAGE, wParam, (LPARAM)&odp); + } + } + return 0; +} + +static int LoadAvatarModule() +{ + mir_getMMI ( &mmi ); + mir_getLI ( &li ); + mir_getLP( &pluginInfoEx ); + + init_mir_thread(); + + InitializeCriticalSection(&cachecs); + InitializeCriticalSection(&alloccs); + + hOptInit = HookEvent(ME_OPT_INITIALISE, OptInit); + hModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded); + hContactSettingChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ContactSettingChanged); + hEventDeleted = HookEvent(ME_DB_CONTACT_DELETED, ContactDeleted); + hProtoAckHook = HookEvent(ME_PROTO_ACK, ProtocolAck); + + arServices.insert( CreateServiceFunction( MS_AV_GETAVATARBITMAP, GetAvatarBitmap )); + arServices.insert( CreateServiceFunction( MS_AV_PROTECTAVATAR, ProtectAvatar )); + arServices.insert( CreateServiceFunction( MS_AV_SETAVATAR, SetAvatar )); + arServices.insert( CreateServiceFunction( MS_AV_SETMYAVATAR, SetMyAvatar )); + arServices.insert( CreateServiceFunction( MS_AV_CANSETMYAVATAR, CanSetMyAvatar )); + arServices.insert( CreateServiceFunction( MS_AV_CONTACTOPTIONS, ContactOptions )); + arServices.insert( CreateServiceFunction( MS_AV_DRAWAVATAR, DrawAvatarPicture )); + arServices.insert( CreateServiceFunction( MS_AV_GETMYAVATAR, GetMyAvatar )); + arServices.insert( CreateServiceFunction( MS_AV_REPORTMYAVATARCHANGED, ReportMyAvatarChanged )); + arServices.insert( CreateServiceFunction( MS_AV_LOADBITMAP32, BmpFilterLoadBitmap32 )); + arServices.insert( CreateServiceFunction( MS_AV_SAVEBITMAP, BmpFilterSaveBitmap )); + arServices.insert( CreateServiceFunction( MS_AV_CANSAVEBITMAP, BmpFilterCanSaveBitmap )); + arServices.insert( CreateServiceFunction( MS_AV_RESIZEBITMAP, BmpFilterResizeBitmap )); + + #if defined( _UNICODE ) + arServices.insert( CreateServiceFunction( MS_AV_SETAVATARW, SetAvatarW )); + arServices.insert( CreateServiceFunction( MS_AV_SETMYAVATARW, SetMyAvatarW )); + #endif + + hEventChanged = CreateHookableEvent(ME_AV_AVATARCHANGED); + hEventContactAvatarChanged = CreateHookableEvent(ME_AV_CONTACTAVATARCHANGED); + hMyAvatarChanged = CreateHookableEvent(ME_AV_MYAVATARCHANGED); + + AllocCacheBlock(); + + HMODULE hDll; + if (hDll = GetModuleHandle(_T("gdi32"))) + AvsAlphaBlend = (BOOL (WINAPI *)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION)) GetProcAddress(hDll, "GdiAlphaBlend"); + if (AvsAlphaBlend == NULL && (hDll = LoadLibrary(_T("msimg32.dll")))) + AvsAlphaBlend = (BOOL (WINAPI *)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION)) GetProcAddress(hDll, "AlphaBlend"); + + TCHAR* tmpPath = Utils_ReplaceVarsT(_T("%miranda_userdata%")); + lstrcpyn(g_szDataPath, tmpPath, SIZEOF(g_szDataPath)-1); + mir_free(tmpPath); + g_szDataPath[MAX_PATH - 1] = 0; + _tcslwr(g_szDataPath); + + return 0; +} + +extern "C" BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD dwReason, LPVOID reserved) +{ + g_hInst = hInstDLL; + return TRUE; +} + +extern "C" __declspec(dllexport) PLUGININFOEX * MirandaPluginInfoEx(DWORD mirandaVersion) +{ + if (mirandaVersion < MIRANDA_VERSION_CORE) + return NULL; + return &pluginInfoEx; +} + +static const MUUID interfaces[] = { { 0xece29554, 0x1cf0, 0x41da, { 0x85, 0x82, 0xfb, 0xe8, 0x45, 0x5c, 0x6b, 0xec } }, MIID_LAST}; +extern "C" __declspec(dllexport) const MUUID * MirandaPluginInterfaces(void) +{ + return interfaces; +} + +extern "C" int __declspec(dllexport) Load(PLUGINLINK * link) +{ + INT_PTR result = CALLSERVICE_NOTFOUND; + + pluginLink = link; + mir_getLP( &pluginInfoEx ); + + if (ServiceExists(MS_IMG_GETINTERFACE)) + result = CallService(MS_IMG_GETINTERFACE, FI_IF_VERSION, (LPARAM)&fei); + + if (fei == NULL || result != S_OK) { + MessageBox(0, TranslateT("Fatal error, image services not found. Avatar services will be disabled."), TranslateT("Avatar Service"), MB_OK); + return 1; + } + LoadACC(); + return LoadAvatarModule(); +} + +extern "C" int __declspec(dllexport) Unload(void) +{ + struct CacheNode *pNode = g_Cache; + + while(pNode) { + //DeleteCriticalSection(&pNode->cs); + if (pNode->ace.hbmPic != 0) + DeleteObject(pNode->ace.hbmPic); + pNode = pNode->pNextNode; + } + + for(int i = 0; i < g_curBlock; i++) + free(g_cacheBlocks[i]); + free(g_cacheBlocks); + + g_ProtoPictures.destroy(); + g_MyAvatars.destroy(); + + CloseHandle(hLoaderEvent); + DeleteCriticalSection(&alloccs); + DeleteCriticalSection(&cachecs); + return 0; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +protoPicCacheEntry::~protoPicCacheEntry() +{ + if ( hbmPic != 0 ) + DeleteObject( hbmPic ); + mir_free( szProtoname ); + mir_free( tszAccName ); +} + +void protoPicCacheEntry::clear() +{ + if (hbmPic != 0) + DeleteObject(hbmPic); + + memset(this, 0, sizeof(avatarCacheEntry)); +} + +/* +wParam=(int *)max width of avatar - will be set (-1 for no max) +lParam=(int *)max height of avatar - will be set (-1 for no max) +return=0 for sucess +*/ +#define PS_GETMYAVATARMAXSIZE "/GetMyAvatarMaxSize" + +/* +wParam=0 +lParam=0 +return=One of PIP_SQUARE, PIP_FREEPROPORTIONS +*/ +#define PIP_FREEPROPORTIONS 0 +#define PIP_SQUARE 1 +#define PS_GETMYAVATARIMAGEPROPORTION "/GetMyAvatarImageProportion" + +/* +wParam = 0 +lParam = PA_FORMAT_* // avatar format +return = 1 (supported) or 0 (not supported) +*/ +#define PS_ISAVATARFORMATSUPPORTED "/IsAvatarFormatSupported" + + + +BOOL Proto_IsAvatarsEnabled(const char *proto) +{ + if (ProtoServiceExists(proto, PS_GETAVATARCAPS)) + return CallProtoService(proto, PS_GETAVATARCAPS, AF_ENABLED, 0); + + return TRUE; +} + +BOOL Proto_IsAvatarFormatSupported(const char *proto, int format) +{ + if (ProtoServiceExists(proto, PS_GETAVATARCAPS)) + return CallProtoService(proto, PS_GETAVATARCAPS, AF_FORMATSUPPORTED, format); + + if (ProtoServiceExists(proto, PS_ISAVATARFORMATSUPPORTED)) + return CallProtoService(proto, PS_ISAVATARFORMATSUPPORTED, 0, format); + + if (format >= PA_FORMAT_SWF) + return FALSE; + + return TRUE; +} + +int Proto_AvatarImageProportion(const char *proto) +{ + if (ProtoServiceExists(proto, PS_GETAVATARCAPS)) + return CallProtoService(proto, PS_GETAVATARCAPS, AF_PROPORTION, 0); + + if (ProtoServiceExists(proto, PS_GETMYAVATARIMAGEPROPORTION)) + return CallProtoService(proto, PS_GETMYAVATARIMAGEPROPORTION, 0, 0); + + return 0; +} + +void Proto_GetAvatarMaxSize(const char *proto, int *width, int *height) +{ + if (ProtoServiceExists(proto, PS_GETAVATARCAPS)) + { + POINT maxSize; + CallProtoService(proto, PS_GETAVATARCAPS, AF_MAXSIZE, (LPARAM) &maxSize); + *width = maxSize.y; + *height = maxSize.x; + } + else if (ProtoServiceExists(proto, PS_GETMYAVATARMAXSIZE)) + { + CallProtoService(proto, PS_GETMYAVATARMAXSIZE, (WPARAM) width, (LPARAM) height); + } + else + { + *width = 300; + *height = 300; + } + + if (*width < 0) + *width = 0; + else if (*width > 300) + *width = 300; + + if (*height < 0) + *height = 0; + else if (*height > 300) + *height = 300; +} + +BOOL Proto_NeedDelaysForAvatars(const char *proto) +{ + if (ProtoServiceExists(proto, PS_GETAVATARCAPS)) + { + int ret = CallProtoService(proto, PS_GETAVATARCAPS, AF_DONTNEEDDELAYS, 0); + if (ret > 0) + return FALSE; + else + return TRUE; + } + + return TRUE; +} + +int Proto_GetAvatarMaxFileSize(const char *proto) +{ + if (ProtoServiceExists(proto, PS_GETAVATARCAPS)) + return CallProtoService(proto, PS_GETAVATARCAPS, AF_MAXFILESIZE, 0); + + return 0; +} + +int Proto_GetDelayAfterFail(const char *proto) +{ + if (ProtoServiceExists(proto, PS_GETAVATARCAPS)) + return CallProtoService(proto, PS_GETAVATARCAPS, AF_DELAYAFTERFAIL, 0); + + return 0; +} + +BOOL Proto_IsFetchingAlwaysAllowed(const char *proto) +{ + if (ProtoServiceExists(proto, PS_GETAVATARCAPS)) + return CallProtoService(proto, PS_GETAVATARCAPS, AF_FETCHALWAYS, 0); + + return FALSE; +} diff --git a/plugins/Avs/mir_thread.cpp b/plugins/Avs/mir_thread.cpp new file mode 100644 index 0000000000..d12a0c9731 --- /dev/null +++ b/plugins/Avs/mir_thread.cpp @@ -0,0 +1,50 @@ +/* +Copyright (C) 2005 Ricardo Pescuma Domenecci + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + + +#include "commonheaders.h" + + +BOOL g_shutDown = FALSE; +static HANDLE hShutdownEvent = NULL; +static HANDLE hOkToExit = NULL; + + +static int OkToExitProc(WPARAM wParam, LPARAM lParam) +{ + g_shutDown = TRUE; + SetEvent(hShutdownEvent); + CloseHandle(hShutdownEvent); + UnhookEvent(hOkToExit); + return 0; +} + + +void init_mir_thread() +{ + hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + hOkToExit = HookEvent(ME_SYSTEM_OKTOEXIT, OkToExitProc); +} + + +void mir_sleep(int time) +{ + if (!g_shutDown) + WaitForSingleObject(hShutdownEvent, time); +} diff --git a/plugins/Avs/mir_thread.h b/plugins/Avs/mir_thread.h new file mode 100644 index 0000000000..0f4f20fe8e --- /dev/null +++ b/plugins/Avs/mir_thread.h @@ -0,0 +1,49 @@ +/* +Copyright (C) 2005 Ricardo Pescuma Domenecci + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + + +#ifndef __MIR_THREAD_H__ +# define __MIR_THREAD_H__ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +void init_mir_thread(); + + +// This variable need to be constantly checked against and the thread function must exit +// when this is true +extern BOOL g_shutDown; + +void mir_sleep(int time); + + + + + + +#ifdef __cplusplus +} +#endif + +#endif // __MIR_THREAD_H__ diff --git a/plugins/Avs/options.cpp b/plugins/Avs/options.cpp new file mode 100644 index 0000000000..58502a9655 --- /dev/null +++ b/plugins/Avs/options.cpp @@ -0,0 +1,1147 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2004 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. +*/ + +#include "commonheaders.h" + +#define DM_SETAVATARNAME (WM_USER + 10) +#define DM_REALODAVATAR (WM_USER + 11) +#define DM_AVATARCHANGED (WM_USER + 12) +#define DM_PROTOCOLCHANGED (WM_USER + 13) + +extern int _DebugPopup(HANDLE hContact, const char *fmt, ...); +extern INT_PTR SetAvatar(WPARAM wParam, LPARAM lParam); +extern OBJLIST g_ProtoPictures; +extern HANDLE hEventChanged; +extern HINSTANCE g_hInst; +extern HICON g_hIcon; + +extern int CreateAvatarInCache(HANDLE hContact, struct avatarCacheEntry *ace, char *szProto); +extern INT_PTR ProtectAvatar(WPARAM wParam, LPARAM lParam); +extern int SetAvatarAttribute(HANDLE hContact, DWORD attrib, int mode); +extern int ChangeAvatar(HANDLE hContact, BOOL fLoad, BOOL fNotifyHist = FALSE, int pa_format = 0); +extern void DeleteAvatarFromCache(HANDLE, BOOL); +extern HBITMAP LoadPNG(struct avatarCacheEntry *ace, char *szFilename); +extern HANDLE GetContactThatHaveTheAvatar(HANDLE hContact, int locked = -1); + +extern int ProtoServiceExists(const char *szModule,const char *szService); +extern BOOL Proto_IsAvatarsEnabled(const char *proto); +extern BOOL ScreenToClient(HWND hWnd, LPRECT lpRect); + +static BOOL dialoginit = TRUE; + +struct WindowData { + HANDLE hContact; + HANDLE hHook; +}; + +static void RemoveProtoPic(const char *szProto) +{ + DBDeleteContactSetting(NULL, PPICT_MODULE, szProto); + + if ( szProto == NULL ) + return; + + if ( !lstrcmpA(AVS_DEFAULT, szProto )) { + for (int i = 0; i < g_ProtoPictures.getCount(); i++) { + protoPicCacheEntry& p = g_ProtoPictures[i]; + if (p.szProtoname == NULL) + continue; + + p.clear(); + CreateAvatarInCache(0, &p, ( char* )p.szProtoname); + NotifyEventHooks(hEventChanged, 0, (LPARAM)&p); + } + return; + } + + if (strstr(szProto, "Global avatar for")) { + char szProtoname[MAX_PATH] = {0}; + lstrcpynA(szProtoname, szProto, lstrlenA(szProto)- lstrlenA("accounts")); + lstrcpyA(szProtoname, strrchr(szProtoname, ' ') + 1); + for (int i = 0; i < g_ProtoPictures.getCount(); i++) { + protoPicCacheEntry& p = g_ProtoPictures[i]; + + PROTOACCOUNT* pdescr = (PROTOACCOUNT*)CallService(MS_PROTO_GETACCOUNT, 0, (LPARAM)p.szProtoname); + if (pdescr == NULL && lstrcmpA(p.szProtoname, szProto)) + continue; + + if (!lstrcmpA(p.szProtoname, szProto) || !lstrcmpA(pdescr->szProtoName, szProtoname)) { + if (p.szProtoname == NULL) + continue; + + p.clear(); + CreateAvatarInCache(0, &p, ( char* )p.szProtoname); + NotifyEventHooks( hEventChanged, 0, (LPARAM)&p ); + } + } + return; + } + + for (int i = 0; i < g_ProtoPictures.getCount(); i++) { + protoPicCacheEntry& p = g_ProtoPictures[i]; + if ( !lstrcmpA( p.szProtoname, szProto )) { + p.clear(); + NotifyEventHooks( hEventChanged, 0, (LPARAM)&p ); + } + } +} + +static void SetProtoPic(char *szProto) +{ + TCHAR FileName[MAX_PATH]; + OPENFILENAME ofn={0}; + TCHAR filter[256]; + + filter[0] = '\0'; + CallService(MS_UTILS_GETBITMAPFILTERSTRINGST, SIZEOF(filter), ( LPARAM )filter); + + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.lpstrFilter = filter; + ofn.hwndOwner=0; + ofn.lpstrFile = FileName; + ofn.nMaxFile = MAX_PATH; + ofn.nMaxFileTitle = MAX_PATH; + ofn.Flags=OFN_HIDEREADONLY; + ofn.lpstrInitialDir = _T("."); + *FileName = '\0'; + ofn.lpstrDefExt = _T(""); + if ( GetOpenFileName( &ofn )) { + HANDLE hFile; + + if ((hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) + return; + + CloseHandle(hFile); + + TCHAR szNewPath[MAX_PATH]; + AVS_pathToRelative(FileName, szNewPath); + DBWriteContactSettingTString(NULL, PPICT_MODULE, szProto, szNewPath); + + if (!lstrcmpA(AVS_DEFAULT, szProto)) { + for ( int i = 0; i < g_ProtoPictures.getCount(); i++ ) { + protoPicCacheEntry& p = g_ProtoPictures[i]; + if (lstrlenA(p.szProtoname) != 0) { + if (p.hbmPic == 0) { + CreateAvatarInCache(0, &p, ( char* )szProto); + NotifyEventHooks(hEventChanged, 0, (LPARAM)&p); + } + } + } + } + else if (strstr(szProto, "Global avatar for")) { + char szProtoname[MAX_PATH] = {0}; + lstrcpynA(szProtoname, szProto, lstrlenA(szProto)- lstrlenA("accounts")); + lstrcpyA(szProtoname, strrchr(szProtoname, ' ') + 1); + for (int i = 0; i < g_ProtoPictures.getCount(); i++) { + PROTOACCOUNT* pdescr = (PROTOACCOUNT*)CallService(MS_PROTO_GETACCOUNT, 0, (LPARAM)g_ProtoPictures[i].szProtoname); + if (pdescr == NULL && lstrcmpA(g_ProtoPictures[i].szProtoname, szProto)) + continue; + + if (!lstrcmpA(g_ProtoPictures[i].szProtoname, szProto) || !lstrcmpA(pdescr->szProtoName, szProtoname)) { + protoPicCacheEntry& p = g_ProtoPictures[i]; + if (lstrlenA(p.szProtoname) != 0) { + if (p.hbmPic == 0) { + CreateAvatarInCache(0, &p, ( char* )szProto); + NotifyEventHooks(hEventChanged, 0, (LPARAM)&p); + } + } + } + } + } + else { + for (int i = 0; i < g_ProtoPictures.getCount(); i++) { + protoPicCacheEntry& p = g_ProtoPictures[i]; + if ( lstrlenA(p.szProtoname) == 0) + break; + + if (!strcmp(p.szProtoname, szProto) && lstrlenA(p.szProtoname) == lstrlenA(szProto)) { + if (p.hbmPic != 0) + DeleteObject(p.hbmPic); + ZeroMemory(&p, sizeof(avatarCacheEntry)); + CreateAvatarInCache(0, &p, ( char* )szProto); + NotifyEventHooks(hEventChanged, 0, (LPARAM)&p ); + break; + } + } + } + } +} + +static char* g_selectedProto; + +INT_PTR CALLBACK DlgProcOptionsAvatars(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + CheckDlgButton(hwndDlg, IDC_SHOWWARNINGS, DBGetContactSettingByte(0, AVS_MODULE, "warnings", 0)); + CheckDlgButton(hwndDlg, IDC_MAKE_GRAYSCALE, DBGetContactSettingByte(0, AVS_MODULE, "MakeGrayscale", 0)); + CheckDlgButton(hwndDlg, IDC_MAKE_TRANSPARENT_BKG, DBGetContactSettingByte(0, AVS_MODULE, "MakeTransparentBkg", 0)); + CheckDlgButton(hwndDlg, IDC_MAKE_TRANSP_PROPORTIONAL, DBGetContactSettingByte(0, AVS_MODULE, "MakeTransparencyProportionalToColorDiff", 0)); + + SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_SETRANGE, 0, MAKELONG(8, 2)); + SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_SETPOS, 0, (LPARAM)DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgNumPoints", 5)); + + SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_SETRANGE, 0, MAKELONG(100, 0)); + SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_SETPOS, 0, (LPARAM)DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgColorDiff", 10)); + { + BOOL enabled = IsDlgButtonChecked(hwndDlg, IDC_MAKE_TRANSPARENT_BKG); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_L), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_SPIN), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_L), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_MAKE_TRANSP_PROPORTIONAL), enabled); + } + + return TRUE; + + case WM_COMMAND: + if ((LOWORD(wParam) == IDC_BKG_NUM_POINTS || LOWORD(wParam) == IDC_BKG_COLOR_DIFFERENCE) + && (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus())) + return FALSE; + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + + case WM_NOTIFY: + switch (((LPNMHDR) lParam)->idFrom) { + case IDC_MAKE_TRANSPARENT_BKG: + { + BOOL transp_enabled = IsDlgButtonChecked(hwndDlg, IDC_MAKE_TRANSPARENT_BKG); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_L), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_SPIN), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_L), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_MAKE_TRANSP_PROPORTIONAL), transp_enabled); + break; + } + case 0: + switch (((LPNMHDR) lParam)->code) { + case PSN_APPLY: + DBWriteContactSettingByte(NULL, AVS_MODULE, "warnings", IsDlgButtonChecked(hwndDlg, IDC_SHOWWARNINGS) ? 1 : 0); + DBWriteContactSettingByte(NULL, AVS_MODULE, "MakeGrayscale", IsDlgButtonChecked(hwndDlg, IDC_MAKE_GRAYSCALE) ? 1 : 0); + DBWriteContactSettingByte(NULL, AVS_MODULE, "MakeTransparentBkg", IsDlgButtonChecked(hwndDlg, IDC_MAKE_TRANSPARENT_BKG) ? 1 : 0); + DBWriteContactSettingByte(NULL, AVS_MODULE, "MakeTransparencyProportionalToColorDiff", IsDlgButtonChecked(hwndDlg, IDC_MAKE_TRANSP_PROPORTIONAL) ? 1 : 0); + DBWriteContactSettingWord(NULL, AVS_MODULE, "TranspBkgNumPoints", (WORD) SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_GETPOS, 0, 0)); + DBWriteContactSettingWord(NULL, AVS_MODULE, "TranspBkgColorDiff", (WORD) SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_GETPOS, 0, 0)); + } } + break; + } + return FALSE; +} + +INT_PTR CALLBACK DlgProcOptionsOwn(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + CheckDlgButton(hwndDlg, IDC_MAKE_MY_AVATARS_TRANSP, DBGetContactSettingByte(0, AVS_MODULE, "MakeMyAvatarsTransparent", 0)); + CheckDlgButton(hwndDlg, IDC_SET_MAKE_SQUARE, DBGetContactSettingByte(0, AVS_MODULE, "SetAllwaysMakeSquare", 0)); + + return TRUE; + + case WM_COMMAND: + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + + case WM_NOTIFY: + switch (((LPNMHDR) lParam)->idFrom) { + case 0: + switch (((LPNMHDR) lParam)->code) { + case PSN_APPLY: + DBWriteContactSettingByte(NULL, AVS_MODULE, "MakeMyAvatarsTransparent", IsDlgButtonChecked(hwndDlg, IDC_MAKE_MY_AVATARS_TRANSP) ? 1 : 0); + DBWriteContactSettingByte(NULL, AVS_MODULE, "SetAllwaysMakeSquare", IsDlgButtonChecked(hwndDlg, IDC_SET_MAKE_SQUARE) ? 1 : 0); + } } + break; + } + return FALSE; +} + +static char* GetProtoFromList(HWND hwndDlg, int iItem) +{ + LVITEM item; + item.mask = LVIF_PARAM; + item.iItem = iItem; + if ( !ListView_GetItem( GetDlgItem(hwndDlg, IDC_PROTOCOLS), &item )) + return NULL; + + protoPicCacheEntry* pce = ( protoPicCacheEntry* )item.lParam; + return ( pce == NULL ) ? NULL : pce->szProtoname; +} + +INT_PTR CALLBACK DlgProcOptionsProtos(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hwndList = GetDlgItem(hwndDlg, IDC_PROTOCOLS); + HWND hwndChoosePic = GetDlgItem(hwndDlg, IDC_SETPROTOPIC); + HWND hwndRemovePic = GetDlgItem(hwndDlg, IDC_REMOVEPROTOPIC); + + switch (msg) { + case WM_INITDIALOG: + { + LVITEM item = {0}; + LVCOLUMN lvc = {0}; + UINT64 newItem = 0; + + dialoginit = TRUE; + TranslateDialogDefault(hwndDlg); + ListView_SetExtendedListViewStyle(hwndList, LVS_EX_CHECKBOXES); + lvc.mask = LVCF_FMT; + lvc.fmt = LVCFMT_IMAGE | LVCFMT_LEFT; + ListView_InsertColumn(hwndList, 0, &lvc); + + item.mask = LVIF_TEXT | LVIF_PARAM; + item.iItem = 1000; + for (int i = 0; i < g_ProtoPictures.getCount(); i++ ) { + item.lParam = ( LPARAM )&g_ProtoPictures[i]; + item.pszText = g_ProtoPictures[i].tszAccName; + newItem = ListView_InsertItem(hwndList, &item); + if (newItem >= 0) + ListView_SetCheckState(hwndList, newItem, + DBGetContactSettingByte(NULL, AVS_MODULE, g_ProtoPictures[i].szProtoname, 1) ? TRUE : FALSE); + } + ListView_SetColumnWidth(hwndList, 0, LVSCW_AUTOSIZE); + ListView_Arrange(hwndList, LVA_ALIGNLEFT | LVA_ALIGNTOP); + EnableWindow(hwndChoosePic, FALSE); + EnableWindow(hwndRemovePic, FALSE); + + dialoginit = FALSE; + } + return TRUE; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDC_SETPROTOPIC: + case IDC_REMOVEPROTOPIC: + { + int iItem = ListView_GetSelectionMark(hwndList); + char* szProto = GetProtoFromList(hwndDlg, iItem); + if ( szProto ) { + if (LOWORD(wParam) == IDC_SETPROTOPIC) + SetProtoPic( szProto ); + else + RemoveProtoPic( szProto ); + + NMHDR nm = { hwndList, IDC_PROTOCOLS, NM_CLICK }; + SendMessage(hwndDlg, WM_NOTIFY, 0, (LPARAM)&nm); + } + break; + } + } + break; + + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT dis = (LPDRAWITEMSTRUCT) lParam; + + if (dis->CtlType == ODT_BUTTON && dis->CtlID == IDC_PROTOPIC) { + AVATARDRAWREQUEST avdrq = {0}; + avdrq.cbSize = sizeof(avdrq); + avdrq.hTargetDC = dis->hDC; + avdrq.dwFlags |= AVDRQ_PROTOPICT; + avdrq.szProto = g_selectedProto; + GetClientRect(GetDlgItem(hwndDlg, IDC_PROTOPIC), &avdrq.rcDraw); + CallService(MS_AV_DRAWAVATAR, 0, (LPARAM)&avdrq); + } + return TRUE; + } + + case WM_NOTIFY: + if (dialoginit) + break; + + switch (((LPNMHDR) lParam)->idFrom) { + case IDC_PROTOCOLS: + switch (((LPNMHDR) lParam)->code) { + case LVN_KEYDOWN: + { + NMLVKEYDOWN* ptkd = (NMLVKEYDOWN*)lParam; + if (ptkd&&ptkd->wVKey==VK_SPACE&&ListView_GetSelectedCount(ptkd->hdr.hwndFrom)==1) + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + break; + case LVN_ITEMCHANGED: + { + NMLISTVIEW *nmlv = (NMLISTVIEW *)lParam; + if (IsWindowVisible(GetDlgItem(hwndDlg, IDC_PROTOCOLS)) && ((nmlv->uNewState ^ nmlv->uOldState) & LVIS_STATEIMAGEMASK)) + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + break; + case NM_CLICK: + { + EnableWindow(hwndChoosePic, TRUE); + EnableWindow(hwndRemovePic, TRUE); + + int iItem = ListView_GetSelectionMark(hwndList); + g_selectedProto = GetProtoFromList(hwndDlg, iItem); + if ( g_selectedProto ) { + DBVARIANT dbv = {0}; + if (!DBGetContactSettingTString(NULL, PPICT_MODULE, g_selectedProto, &dbv)) + { + if (!AVS_pathIsAbsolute(dbv.ptszVal)) + { + TCHAR szFinalPath[MAX_PATH]; + mir_sntprintf(szFinalPath, SIZEOF(szFinalPath), _T("%%miranda_path%%\\%s"), dbv.ptszVal); + SetDlgItemText(hwndDlg, IDC_PROTOAVATARNAME, szFinalPath); + } + else SetDlgItemText(hwndDlg, IDC_PROTOAVATARNAME, dbv.ptszVal); + + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROTOPIC), NULL, TRUE); + DBFreeVariant(&dbv); + } + else { + SetWindowText(GetDlgItem(hwndDlg, IDC_PROTOAVATARNAME), _T("")); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROTOPIC), NULL, TRUE); + } + } + break; + } + } + break; + case 0: + switch (((LPNMHDR) lParam)->code) { + case PSN_APPLY: + { + for (int i = 0; i < ListView_GetItemCount(hwndList); i++) { + char *szProto = GetProtoFromList(hwndDlg, i); + + BOOL oldVal = DBGetContactSettingByte(NULL, AVS_MODULE, szProto, 1); + BOOL newVal = ListView_GetCheckState(hwndList, i); + + if (oldVal && !newVal) + { + HANDLE hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + while (hContact != NULL) + { + char* szContactProto = (char*) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szContactProto != NULL && !strcmp(szContactProto, szProto)) + DeleteAvatarFromCache(hContact, TRUE); + + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0); + } + } + + if (newVal) + DBWriteContactSettingByte(NULL, AVS_MODULE, szProto, 1); + else + DBWriteContactSettingByte(NULL, AVS_MODULE, szProto, 0); + } + } + } + } + break; + } + return FALSE; +} + +void LoadTransparentData(HWND hwndDlg, HANDLE hContact) +{ + CheckDlgButton(hwndDlg, IDC_MAKETRANSPBKG, DBGetContactSettingByte(hContact, "ContactPhoto", "MakeTransparentBkg", DBGetContactSettingByte(0, AVS_MODULE, "MakeTransparentBkg", 0))); + SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_SETPOS, 0, (LPARAM)DBGetContactSettingWord(hContact, "ContactPhoto", "TranspBkgNumPoints", DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgNumPoints", 5))); + SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_SETPOS, 0, (LPARAM)DBGetContactSettingWord(hContact, "ContactPhoto", "TranspBkgColorDiff", DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgColorDiff", 10))); + + BOOL transp_enabled = IsDlgButtonChecked(hwndDlg, IDC_MAKETRANSPBKG); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_L), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_SPIN), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_L), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN), transp_enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE), transp_enabled); +} + +void SaveTransparentData(HWND hwndDlg, HANDLE hContact) +{ + BOOL transp = IsDlgButtonChecked(hwndDlg, IDC_MAKETRANSPBKG); + if (DBGetContactSettingByte(0, AVS_MODULE, "MakeTransparentBkg", 0) == transp) + DBDeleteContactSetting(hContact, "ContactPhoto", "MakeTransparentBkg"); + else + DBWriteContactSettingByte(hContact, "ContactPhoto", "MakeTransparentBkg", transp); + + WORD tmp = (WORD) SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_GETPOS, 0, 0); + if (DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgNumPoints", 5) == tmp) + DBDeleteContactSetting(hContact, "ContactPhoto", "TranspBkgNumPoints"); + else + DBWriteContactSettingWord(hContact, "ContactPhoto", "TranspBkgNumPoints", tmp); + + tmp = (WORD) SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_GETPOS, 0, 0); + if (DBGetContactSettingWord(0, AVS_MODULE, "TranspBkgColorDiff", 10) == tmp) + DBDeleteContactSetting(hContact, "ContactPhoto", "TranspBkgColorDiff"); + else + DBWriteContactSettingWord(hContact, "ContactPhoto", "TranspBkgColorDiff", tmp); +} + +void SaveTransparentData(HWND hwndDlg, HANDLE hContact, BOOL locked) +{ + SaveTransparentData(hwndDlg, hContact); + + HANDLE tmp = GetContactThatHaveTheAvatar(hContact, locked); + if (tmp != hContact) + SaveTransparentData(hwndDlg, tmp); +} + +INT_PTR CALLBACK DlgProcAvatarOptions(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact; + struct WindowData *dat = (struct WindowData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + if (dat) + hContact = dat->hContact; + + switch(msg) { + case WM_INITDIALOG: + { + TCHAR szTitle[512]; + TCHAR *szNick = NULL; + struct WindowData *dat = (struct WindowData *)malloc(sizeof(struct WindowData)); + + if (dat) + dat->hContact = (HANDLE)lParam; + + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + hContact = (HANDLE)lParam; + TranslateDialogDefault(hwndDlg); + if (hContact) { + szNick = (TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR); + mir_sntprintf(szTitle, 500, TranslateT("Set avatar options for %s"), szNick); + SetWindowText(hwndDlg, szTitle); + } + SendMessage(hwndDlg, DM_SETAVATARNAME, 0, 0); + ShowWindow(hwndDlg, SW_SHOWNORMAL); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROTOPIC), NULL, FALSE); + CheckDlgButton(hwndDlg, IDC_PROTECTAVATAR, DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0) ? TRUE : FALSE); + CheckDlgButton(hwndDlg, IDC_HIDEAVATAR, DBGetContactSettingByte(hContact, "ContactPhoto", "Hidden", 0) ? TRUE : FALSE); + + SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_SETBUDDY, (WPARAM)GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS), 0); + SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_SETRANGE, 0, MAKELONG(8, 2)); + + SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_SETBUDDY, (WPARAM)GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE), 0); + SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_SETRANGE, 0, MAKELONG(100, 0)); + + LoadTransparentData(hwndDlg, GetContactThatHaveTheAvatar(hContact)); + dat->hHook = HookEventMessage(ME_AV_AVATARCHANGED, hwndDlg, DM_AVATARCHANGED); + SendMessage(hwndDlg, WM_SETICON, IMAGE_ICON, (LPARAM)g_hIcon); + } + return TRUE; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case ID_USE_DEFAULTS: + hContact = GetContactThatHaveTheAvatar(hContact); + + DBDeleteContactSetting(hContact, "ContactPhoto", "MakeTransparentBkg"); + DBDeleteContactSetting(hContact, "ContactPhoto", "TranspBkgNumPoints"); + DBDeleteContactSetting(hContact, "ContactPhoto", "TranspBkgColorDiff"); + + LoadTransparentData(hwndDlg, hContact); + + SendMessage(hwndDlg, DM_REALODAVATAR, 0, 0); + break; + + case IDOK: + { + BOOL locked = IsDlgButtonChecked(hwndDlg, IDC_PROTECTAVATAR); + int hidden = IsDlgButtonChecked(hwndDlg, IDC_HIDEAVATAR) ? 1 : 0; + SetAvatarAttribute(hContact, AVS_HIDEONCLIST, hidden); + if (hidden != DBGetContactSettingByte(hContact, "ContactPhoto", "Hidden", 0)) + DBWriteContactSettingByte(hContact, "ContactPhoto", "Hidden", hidden); + + if (!locked && DBGetContactSettingByte(hContact, "ContactPhoto", "NeedUpdate", 0)) + QueueAdd(hContact); + + // Continue to the cancel handle + } + + case IDCANCEL: + DestroyWindow(hwndDlg); + break; + + case IDC_PROTECTAVATAR: + { + BOOL locked = IsDlgButtonChecked(hwndDlg, IDC_PROTECTAVATAR); + ProtectAvatar((WPARAM)hContact, locked ? 1 : 0); + } + break; + + case IDC_CHANGE: + SetAvatar((WPARAM)hContact, 0); + SendMessage(hwndDlg, DM_SETAVATARNAME, 0, 0); + CheckDlgButton(hwndDlg, IDC_PROTECTAVATAR, DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0) ? TRUE : FALSE); + break; + + case IDC_BKG_NUM_POINTS: + case IDC_BKG_COLOR_DIFFERENCE: + if (HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) + break; + + case IDC_MAKETRANSPBKG: + { + BOOL enable = IsDlgButtonChecked(hwndDlg, IDC_MAKETRANSPBKG); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_L), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_SPIN), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_L), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE), enable); + + SendMessage(hwndDlg, DM_REALODAVATAR, 0, 0); + } + break; + + case IDC_RESET: + { + char *szProto = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + DBVARIANT dbv = {0}; + + ProtectAvatar((WPARAM)hContact, 0); + if (MessageBox(0, TranslateT("Delete picture file from disk (may be necessary to force a reload, but will delete local pictures)?"), TranslateT("Reset contact picture"), MB_YESNO) == IDYES) { + if (!DBGetContactSettingTString(hContact, "ContactPhoto", "File", &dbv)) { + DeleteFile(dbv.ptszVal); + DBFreeVariant(&dbv); + } + } + DBDeleteContactSetting(hContact, "ContactPhoto", "Locked"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Backup"); + DBDeleteContactSetting(hContact, "ContactPhoto", "RFile"); + DBDeleteContactSetting(hContact, "ContactPhoto", "File"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Format"); + DBDeleteContactSetting(hContact, szProto, "AvatarHash"); + DBDeleteContactSetting(hContact, szProto, "AvatarSaved"); + DeleteAvatarFromCache(hContact, FALSE); + + QueueAdd(hContact); + + DestroyWindow(hwndDlg); + } + break; + + case IDC_DELETE: + { + DBVARIANT dbv = {0}; + ProtectAvatar((WPARAM)hContact, 0); + if (MessageBox(0, TranslateT("Delete picture file from disk (may be necessary to force a reload, but will delete local pictures)?"), TranslateT("Reset contact picture"), MB_YESNO) == IDYES) { + if (!DBGetContactSettingTString(hContact, "ContactPhoto", "File", &dbv)) { + DeleteFile(dbv.ptszVal); + DBFreeVariant(&dbv); + } + } + DBDeleteContactSetting(hContact, "ContactPhoto", "Locked"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Backup"); + DBDeleteContactSetting(hContact, "ContactPhoto", "RFile"); + DBDeleteContactSetting(hContact, "ContactPhoto", "File"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Format"); + DeleteAvatarFromCache(hContact, FALSE); + SendMessage(hwndDlg, DM_SETAVATARNAME, 0, 0); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROTOPIC), NULL, TRUE); + break; + } + } + break; + + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT dis = (LPDRAWITEMSTRUCT) lParam; + + if (dis->CtlType == ODT_BUTTON && dis->CtlID == IDC_PROTOPIC) { + AVATARDRAWREQUEST avdrq = {0}; + GetClientRect(GetDlgItem(hwndDlg, IDC_PROTOPIC), &avdrq.rcDraw); + + FillRect(dis->hDC, &avdrq.rcDraw, GetSysColorBrush(COLOR_BTNFACE)); + + avdrq.hContact = hContact; + avdrq.cbSize = sizeof(avdrq); + avdrq.hTargetDC = dis->hDC; + avdrq.dwFlags |= AVDRQ_DRAWBORDER; + avdrq.clrBorder = GetSysColor(COLOR_BTNTEXT); + avdrq.radius = 6; + if (!CallService(MS_AV_DRAWAVATAR, 0, (LPARAM)&avdrq)) + { + // Get text rectangle + RECT rc = avdrq.rcDraw; + rc.top += 10; + rc.bottom -= 10; + rc.left += 10; + rc.right -= 10; + + // Calc text size + RECT rc_ret = rc; + DrawText(dis->hDC, TranslateT("Contact has no avatar"), -1, &rc_ret, + DT_WORDBREAK | DT_NOPREFIX | DT_CENTER | DT_CALCRECT); + + // Calc needed size + rc.top += ((rc.bottom - rc.top) - (rc_ret.bottom - rc_ret.top)) / 2; + rc.bottom = rc.top + (rc_ret.bottom - rc_ret.top); + DrawText(dis->hDC, TranslateT("Contact has no avatar"), -1, &rc, + DT_WORDBREAK | DT_NOPREFIX | DT_CENTER); + } + + FrameRect(dis->hDC, &avdrq.rcDraw, GetSysColorBrush(COLOR_BTNSHADOW)); + } + return TRUE; + } + case DM_SETAVATARNAME: + { + TCHAR szFinalName[MAX_PATH]; + DBVARIANT dbv = {0}; + BYTE is_locked = DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0); + + szFinalName[0] = 0; + + if (is_locked && !DBGetContactSettingTString(hContact, "ContactPhoto", "Backup", &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, szFinalName); + DBFreeVariant(&dbv); + } + else if (!DBGetContactSettingTString(hContact, "ContactPhoto", "RFile", &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, szFinalName); + DBFreeVariant(&dbv); + } + else if (!DBGetContactSettingTString(hContact, "ContactPhoto", "File", &dbv)) { + AVS_pathToAbsolute(dbv.ptszVal, szFinalName); + DBFreeVariant(&dbv); + } + szFinalName[MAX_PATH - 1] = 0; + SetDlgItemText(hwndDlg, IDC_AVATARNAME, szFinalName); + break; + } + + case DM_REALODAVATAR: + SaveTransparentData(hwndDlg, hContact, IsDlgButtonChecked(hwndDlg, IDC_PROTECTAVATAR)); + ChangeAvatar(hContact, TRUE); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROTOPIC), NULL, TRUE); + break; + + case DM_AVATARCHANGED: + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROTOPIC), NULL, TRUE); + break; + + case WM_NCDESTROY: + if (dat) { + UnhookEvent(dat->hHook); + free(dat); + } + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + break; + } + return FALSE; +} + +INT_PTR CALLBACK DlgProcAvatarUserInfo(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact; + struct WindowData *dat = (struct WindowData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + if (dat) + hContact = dat->hContact; + + switch(msg) { + case WM_INITDIALOG: + { + dat = (struct WindowData *) malloc(sizeof(struct WindowData)); + if (dat == NULL) + return FALSE; + dat->hContact = (HANDLE)lParam; + + HWND protopic = GetDlgItem(hwndDlg, IDC_PROTOPIC); + SendMessage(protopic, AVATAR_SETCONTACT, 0, (LPARAM) dat->hContact); + SendMessage(protopic, AVATAR_SETAVATARBORDERCOLOR, 0, (LPARAM) GetSysColor(COLOR_BTNSHADOW)); + SendMessage(protopic, AVATAR_SETNOAVATARTEXT, 0, (LPARAM) LPGENT("Contact has no avatar")); + SendMessage(protopic, AVATAR_RESPECTHIDDEN, 0, (LPARAM) FALSE); + SendMessage(protopic, AVATAR_SETRESIZEIFSMALLER, 0, (LPARAM) FALSE); + + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + hContact = (HANDLE)lParam; + TranslateDialogDefault(hwndDlg); + SendMessage(hwndDlg, DM_SETAVATARNAME, 0, 0); + CheckDlgButton(hwndDlg, IDC_PROTECTAVATAR, DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0) ? TRUE : FALSE); + CheckDlgButton(hwndDlg, IDC_HIDEAVATAR, DBGetContactSettingByte(hContact, "ContactPhoto", "Hidden", 0) ? TRUE : FALSE); + + SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_SETBUDDY, (WPARAM)GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS), 0); + SendDlgItemMessage(hwndDlg, IDC_BKG_NUM_POINTS_SPIN, UDM_SETRANGE, 0, MAKELONG(8, 2)); + + SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_SETBUDDY, (WPARAM)GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE), 0); + SendDlgItemMessage(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN, UDM_SETRANGE, 0, MAKELONG(100, 0)); + + LoadTransparentData(hwndDlg, GetContactThatHaveTheAvatar(hContact)); + } + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case ID_USE_DEFAULTS: + hContact = GetContactThatHaveTheAvatar(hContact); + + DBDeleteContactSetting(hContact, "ContactPhoto", "MakeTransparentBkg"); + DBDeleteContactSetting(hContact, "ContactPhoto", "TranspBkgNumPoints"); + DBDeleteContactSetting(hContact, "ContactPhoto", "TranspBkgColorDiff"); + + LoadTransparentData(hwndDlg, hContact); + + SendMessage(hwndDlg, DM_REALODAVATAR, 0, 0); + break; + + case IDC_CHANGE: + SetAvatar((WPARAM)hContact, 0); + SendMessage(hwndDlg, DM_SETAVATARNAME, 0, 0); + CheckDlgButton(hwndDlg, IDC_PROTECTAVATAR, DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0) ? TRUE : FALSE); + break; + + case IDC_HIDEAVATAR: + { + int hidden = IsDlgButtonChecked(hwndDlg, IDC_HIDEAVATAR) ? 1 : 0; + SetAvatarAttribute(hContact, AVS_HIDEONCLIST, hidden); + if (hidden != DBGetContactSettingByte(hContact, "ContactPhoto", "Hidden", 0)) + DBWriteContactSettingByte(hContact, "ContactPhoto", "Hidden", hidden); + break; + } + + case IDC_PROTECTAVATAR: + { + BOOL locked = IsDlgButtonChecked(hwndDlg, IDC_PROTECTAVATAR); + SaveTransparentData(hwndDlg, hContact, locked); + ProtectAvatar((WPARAM)hContact, locked ? 1 : 0); + + break; + } + case IDC_BKG_NUM_POINTS: + case IDC_BKG_COLOR_DIFFERENCE: + if (HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) + break; + case IDC_MAKETRANSPBKG: + { + BOOL enable = IsDlgButtonChecked(hwndDlg, IDC_MAKETRANSPBKG); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_L), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS_SPIN), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_NUM_POINTS), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_L), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE_SPIN), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_BKG_COLOR_DIFFERENCE), enable); + + SendMessage(hwndDlg, DM_REALODAVATAR, 0, 0); + break; + } + case IDC_RESET: + { + char *szProto = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + DBVARIANT dbv = {0}; + + ProtectAvatar((WPARAM)hContact, 0); + if (MessageBox(0, TranslateT("Delete picture file from disk (may be necessary to force a reload, but will delete local pictures)?"), TranslateT("Reset contact picture"), MB_YESNO) == IDYES) { + if (!DBGetContactSettingTString(hContact, "ContactPhoto", "File", &dbv)) { + DeleteFile(dbv.ptszVal); + DBFreeVariant(&dbv); + } + } + DBDeleteContactSetting(hContact, "ContactPhoto", "Locked"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Backup"); + DBDeleteContactSetting(hContact, "ContactPhoto", "RFile"); + DBDeleteContactSetting(hContact, "ContactPhoto", "File"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Format"); + DBDeleteContactSetting(hContact, szProto, "AvatarHash"); + DBDeleteContactSetting(hContact, szProto, "AvatarSaved"); + DeleteAvatarFromCache(hContact, FALSE); + + QueueAdd(hContact); + break; + } + case IDC_DELETE: + { + DBVARIANT dbv = {0}; + + ProtectAvatar((WPARAM)hContact, 0); + if (MessageBox(0, TranslateT("Delete picture file from disk (may be necessary to force a reload, but will delete local pictures)?"), TranslateT("Reset contact picture"), MB_YESNO) == IDYES) { + if (!DBGetContactSettingTString(hContact, "ContactPhoto", "File", &dbv)) { + DeleteFile(dbv.ptszVal); + DBFreeVariant(&dbv); + } + } + DBDeleteContactSetting(hContact, "ContactPhoto", "Locked"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Backup"); + DBDeleteContactSetting(hContact, "ContactPhoto", "RFile"); + DBDeleteContactSetting(hContact, "ContactPhoto", "File"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Format"); + DeleteAvatarFromCache(hContact, FALSE); + SendMessage(hwndDlg, DM_SETAVATARNAME, 0, 0); + break; + } + } + break; + + case DM_REALODAVATAR: + SaveTransparentData(hwndDlg, hContact, IsDlgButtonChecked(hwndDlg, IDC_PROTECTAVATAR)); + ChangeAvatar(hContact, TRUE); + break; + + case WM_NCDESTROY: + if (dat) + free(dat); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + break; + } + return FALSE; +} + +static char * GetSelectedProtocol(HWND hwndDlg) +{ + HWND hwndList = GetDlgItem(hwndDlg, IDC_PROTOCOLS); + + // Get selection + int iItem = ListView_GetSelectionMark(hwndList); + if (iItem < 0) + return NULL; + + // Get protocol name + LVITEM item = {0}; + item.mask = LVIF_PARAM; + item.iItem = iItem; + SendMessage(hwndList, LVM_GETITEMA, 0, (LPARAM)&item); + return ( char* ) item.lParam; +} + +static void EnableDisableControls(HWND hwndDlg, char *proto) +{ + if (IsDlgButtonChecked(hwndDlg, IDC_PER_PROTO)) + { + if (proto == NULL) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_CHANGE), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE); + } + else + { + if (!ProtoServiceExists(proto, PS_SETMYAVATAR)) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_CHANGE), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE); + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_CHANGE), TRUE); + + int width, height; + SendDlgItemMessage(hwndDlg, IDC_PROTOPIC, AVATAR_GETUSEDSPACE, (WPARAM) &width, (LPARAM) &height); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), (LPARAM) width != 0 || height != 0); + } + } + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_CHANGE), TRUE); + + if (DBGetContactSettingByte(NULL, AVS_MODULE, "GlobalUserAvatarNotConsistent", 1)) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), TRUE); + } + else + { + int width, height; + SendDlgItemMessage(hwndDlg, IDC_PROTOPIC, AVATAR_GETUSEDSPACE, (WPARAM) &width, (LPARAM) &height); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), (LPARAM) width != 0 || height != 0); + } + } +} + +static void OffsetWindow(HWND parent, HWND hwnd, int dx, int dy) +{ + RECT rc; + GetWindowRect(hwnd, &rc); + ScreenToClient(parent, &rc); + OffsetRect(&rc, dx, dy); + MoveWindow(hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); +} + +static void EnableDisableProtocols(HWND hwndDlg, BOOL init) +{ + int diff = 147; // Pre-calc + BOOL perProto = IsDlgButtonChecked(hwndDlg, IDC_PER_PROTO); + HWND hwndList = GetDlgItem(hwndDlg, IDC_PROTOCOLS); + + if (perProto) + { + if (!init && !IsWindowVisible(hwndList)) + { + // Show list of protocols + ShowWindow(hwndList, SW_SHOW); + + // Move controls + OffsetWindow(hwndDlg, GetDlgItem(hwndDlg, IDC_PROTOPIC), diff, 0); + OffsetWindow(hwndDlg, GetDlgItem(hwndDlg, IDC_CHANGE), diff, 0); + OffsetWindow(hwndDlg, GetDlgItem(hwndDlg, IDC_DELETE), diff, 0); + } + + char * proto = GetSelectedProtocol(hwndDlg); + if (proto == NULL) + { + ListView_SetItemState(hwndList, 0, LVIS_FOCUSED | LVIS_SELECTED, 0x0F); + } + else + { + SendDlgItemMessage(hwndDlg, IDC_PROTOPIC, AVATAR_SETPROTOCOL, 0, (LPARAM) proto); + EnableDisableControls(hwndDlg, proto); + } + } + else + { + if (init || IsWindowVisible(hwndList)) + { + // Show list of protocols + ShowWindow(hwndList, SW_HIDE); + + // Move controls + OffsetWindow(hwndDlg, GetDlgItem(hwndDlg, IDC_PROTOPIC), -diff, 0); + OffsetWindow(hwndDlg, GetDlgItem(hwndDlg, IDC_CHANGE), -diff, 0); + OffsetWindow(hwndDlg, GetDlgItem(hwndDlg, IDC_DELETE), -diff, 0); + } + + SendDlgItemMessage(hwndDlg, IDC_PROTOPIC, AVATAR_SETPROTOCOL, 0, NULL); + } +} + +INT_PTR CALLBACK DlgProcAvatarProtoInfo(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { + HWND protopic = GetDlgItem(hwndDlg, IDC_PROTOPIC); + SendMessage(protopic, AVATAR_SETAVATARBORDERCOLOR, 0, (LPARAM) GetSysColor(COLOR_BTNSHADOW)); + SendMessage(protopic, AVATAR_SETNOAVATARTEXT, 0, (LPARAM) LPGENT("No avatar")); + SendMessage(protopic, AVATAR_SETRESIZEIFSMALLER, 0, (LPARAM) FALSE); + + HWND hwndList = GetDlgItem(hwndDlg, IDC_PROTOCOLS); + ListView_SetExtendedListViewStyleEx(hwndList, 0, LVS_EX_SUBITEMIMAGES); + + HIMAGELIST hIml = ImageList_Create(16, 16, ILC_MASK | (IsWinVerXPPlus()? ILC_COLOR32 : ILC_COLOR16), 4, 0); + ListView_SetImageList(hwndList, hIml, LVSIL_SMALL); + + LVCOLUMN lvc = {0}; + lvc.mask = LVCF_FMT; + lvc.fmt = LVCFMT_IMAGE | LVCFMT_LEFT; + ListView_InsertColumn(hwndList, 0, &lvc); + + LVITEM item = {0}; + item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + item.iItem = 1000; + + // List protocols + PROTOACCOUNT **accs; + int i, count, num = 0; + + ProtoEnumAccounts( &count, &accs ); + for (i = 0; i < count; i++) + { + if ( !ProtoServiceExists( accs[i]->szModuleName, PS_GETMYAVATAR)) + continue; + + if ( !Proto_IsAvatarsEnabled( accs[i]->szModuleName )) + continue; + + ImageList_AddIcon(hIml, LoadSkinnedProtoIcon( accs[i]->szModuleName, ID_STATUS_ONLINE)); + item.pszText = accs[i]->tszAccountName; + item.iImage = num; + item.lParam = (LPARAM)accs[i]->szModuleName; + + ListView_InsertItem(hwndList, &item); + num++; + } + + ListView_SetColumnWidth(hwndList, 0, LVSCW_AUTOSIZE); + ListView_Arrange(hwndList, LVA_ALIGNLEFT | LVA_ALIGNTOP); + + // Check if should show per protocol avatars + CheckDlgButton(hwndDlg, IDC_PER_PROTO, DBGetContactSettingByte(NULL, AVS_MODULE, "PerProtocolUserAvatars", 1)); + EnableDisableProtocols(hwndDlg, TRUE); + } + break; + + case WM_NOTIFY: + { + LPNMHDR nm = (LPNMHDR) lParam; + switch(nm->idFrom) { + case IDC_PROTOCOLS: + switch (nm->code) { + case LVN_ITEMCHANGED: + { + LPNMLISTVIEW li = (LPNMLISTVIEW) nm; + if (li->uNewState & LVIS_SELECTED) + { + SendDlgItemMessage(hwndDlg, IDC_PROTOPIC, AVATAR_SETPROTOCOL, 0, li->lParam); + EnableDisableControls(hwndDlg, ( char* ) li->lParam); + } + } + break; + } + break; + + case IDC_PROTOPIC: + switch (nm->code) { + case NM_AVATAR_CHANGED: + EnableDisableControls(hwndDlg, GetSelectedProtocol(hwndDlg)); + break; + } + break; + } + break; + } + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDC_CHANGE: + if (!IsDlgButtonChecked(hwndDlg, IDC_PER_PROTO)) + avSetMyAvatar(NULL, NULL); + else { + char *proto = GetSelectedProtocol(hwndDlg); + if (proto != NULL) + avSetMyAvatar(proto, NULL); + } + break; + + case IDC_DELETE: + if (!IsDlgButtonChecked(hwndDlg, IDC_PER_PROTO)) { + if (MessageBox(hwndDlg, TranslateT("Are you sure you want to remove your avatar?"), TranslateT("Global Avatar"), MB_YESNO) == IDYES) + avSetMyAvatar(NULL, _T("")); + } + else { + char *proto = GetSelectedProtocol(hwndDlg); + if (proto == NULL) + break; + + char description[256]; + CallProtoService(proto, PS_GETNAME, SIZEOF(description),(LPARAM) description); + TCHAR *descr = mir_a2t(description); + if (MessageBox(hwndDlg, TranslateT("Are you sure you want to remove your avatar?"), descr, MB_YESNO) == IDYES) + avSetMyAvatar(proto, _T("")); + mir_free(descr); + } + break; + + case IDC_PER_PROTO: + DBWriteContactSettingByte(NULL, AVS_MODULE, "PerProtocolUserAvatars", IsDlgButtonChecked(hwndDlg, IDC_PER_PROTO) ? 1 : 0); + EnableDisableProtocols(hwndDlg, FALSE); + break; + } + break; + } + return FALSE; +} diff --git a/plugins/Avs/poll.cpp b/plugins/Avs/poll.cpp new file mode 100644 index 0000000000..32553a9c33 --- /dev/null +++ b/plugins/Avs/poll.cpp @@ -0,0 +1,319 @@ +/* +Copyright (C) 2006 Ricardo Pescuma Domenecci, Nightwish + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + +#include "commonheaders.h" + +/* +It has 1 queue: +A queue to request items. One request is done at a time, REQUEST_WAIT_TIME miliseconts after it has beeing fired + ACKRESULT_STATUS. This thread only requests the avatar (and maybe add it to the cache queue) +*/ + +#define REQUEST_WAIT_TIME 3000 + +// Time to wait before re-requesting an avatar that failed +#define REQUEST_FAIL_WAIT_TIME (3 * 60 * 60 * 1000) + +// Time to wait before re-requesting an avatar that received an wait for +#define REQUEST_WAITFOR_WAIT_TIME (30 * 60 * 1000) + +// Number of mileseconds the threads wait until take a look if it is time to request another item +#define POOL_DELAY 1000 + +// Number of mileseconds the threads wait after a GAIR_WAITFOR is returned +#define REQUEST_DELAY 18000 + + +// Prototypes /////////////////////////////////////////////////////////////////////////// + +static void RequestThread(void *vParam); + +extern HANDLE hShutdownEvent; +extern char *g_szMetaName; +extern int ChangeAvatar(HANDLE hContact, BOOL fLoad, BOOL fNotifyHist = FALSE, int pa_format = 0); +extern int DeleteAvatar(HANDLE hContact); +extern void MakePathRelative(HANDLE hContact, TCHAR *path); +int Proto_GetDelayAfterFail(const char *proto); +BOOL Proto_IsFetchingAlwaysAllowed(const char *proto); + +struct CacheNode *FindAvatarInCache(HANDLE hContact, BOOL add, BOOL findAny = FALSE); + +extern HANDLE hEventContactAvatarChanged; +extern BOOL g_AvatarHistoryAvail; +extern FI_INTERFACE *fei; + +#ifdef _DEBUG +int _DebugTrace(const char *fmt, ...); +int _DebugTrace(HANDLE hContact, const char *fmt, ...); +#endif + +// Functions //////////////////////////////////////////////////////////////////////////// + +// Items with higher priority at end +static int QueueSortItems( const QueueItem* i1, const QueueItem* i2) +{ + return i2->check_time - i1->check_time; +} + +static OBJLIST queue( 20, QueueSortItems ); +static CRITICAL_SECTION cs; +static int waitTime; + +void InitPolls() +{ + waitTime = REQUEST_WAIT_TIME; + InitializeCriticalSection( &cs ); + + // Init request queue + mir_forkthread(RequestThread, NULL); +} + +void FreePolls() +{ +} + +// Return true if this protocol can have avatar requested +static BOOL PollProtocolCanHaveAvatar(const char *szProto) +{ + int pCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0); + int status = CallProtoService(szProto, PS_GETSTATUS, 0, 0); + return (pCaps & PF4_AVATARS) + && (g_szMetaName == NULL || strcmp(g_szMetaName, szProto)) + && ((status > ID_STATUS_OFFLINE && status != ID_STATUS_INVISIBLE) || Proto_IsFetchingAlwaysAllowed(szProto)); +} + +// Return true if this protocol has to be checked +static BOOL PollCheckProtocol(const char *szProto) +{ + return DBGetContactSettingByte(NULL, AVS_MODULE, szProto, 1); +} + +// Return true if this contact can have avatar requested +static BOOL PollContactCanHaveAvatar(HANDLE hContact, const char *szProto) +{ + int status = DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE); + return (Proto_IsFetchingAlwaysAllowed(szProto) || status != ID_STATUS_OFFLINE) + && !DBGetContactSettingByte(hContact, "CList", "NotOnList", 0) + && DBGetContactSettingByte(hContact, "CList", "ApparentMode", 0) != ID_STATUS_OFFLINE; +} + +// Return true if this contact has to be checked +static BOOL PollCheckContact(HANDLE hContact, const char *szProto) +{ + return !DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0) + && FindAvatarInCache(hContact, FALSE, TRUE) != NULL; +} + +static void QueueRemove(HANDLE hContact) +{ + EnterCriticalSection(&cs); + + for (int i = queue.getCount()-1 ; i >= 0 ; i-- ) { + QueueItem& item = queue[i]; + if (item.hContact == hContact) + queue.remove(i); + } + + LeaveCriticalSection(&cs); +} + +static void QueueAdd(HANDLE hContact, int waitTime) +{ + if(fei == NULL) + return; + + EnterCriticalSection(&cs); + + // Only add if not exists yet + int i; + for (i = queue.getCount()-1; i >= 0; i--) + if ( queue[i].hContact == hContact) + break; + + if (i < 0) { + QueueItem *item = new QueueItem; + if (item != NULL) { + item->hContact = hContact; + item->check_time = GetTickCount() + waitTime; + queue.insert(item); + } } + + LeaveCriticalSection(&cs); +} + +// Add an contact to a queue +void QueueAdd(HANDLE hContact) +{ + QueueAdd(hContact, waitTime); +} + +void ProcessAvatarInfo(HANDLE hContact, int type, PROTO_AVATAR_INFORMATIONT *pai, const char *szProto) +{ + QueueRemove(hContact); + + if (type == GAIR_SUCCESS) + { + if (pai == NULL) + return; + + // Fix settings in DB + DBDeleteContactSetting(hContact, "ContactPhoto", "NeedUpdate"); + DBDeleteContactSetting(hContact, "ContactPhoto", "RFile"); + if (!DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0)) + DBDeleteContactSetting(hContact, "ContactPhoto", "Backup"); + DBWriteContactSettingTString(hContact, "ContactPhoto", "File", pai->filename); + DBWriteContactSettingWord(hContact, "ContactPhoto", "Format", pai->format); + + if (pai->format == PA_FORMAT_PNG || pai->format == PA_FORMAT_JPEG + || pai->format == PA_FORMAT_ICON || pai->format == PA_FORMAT_BMP + || pai->format == PA_FORMAT_GIF) + { + // We can load it! + MakePathRelative(hContact, pai->filename); + ChangeAvatar(hContact, TRUE, TRUE, pai->format); + } + else + { + // As we can't load it, notify but don't load + ChangeAvatar(hContact, FALSE, TRUE, pai->format); + } + } + else if (type == GAIR_NOAVATAR) + { + DBDeleteContactSetting(hContact, "ContactPhoto", "NeedUpdate"); + + if (DBGetContactSettingByte(NULL, AVS_MODULE, "RemoveAvatarWhenContactRemoves", 1)) + { + // Delete settings + DBDeleteContactSetting(hContact, "ContactPhoto", "RFile"); + if (!DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0)) + DBDeleteContactSetting(hContact, "ContactPhoto", "Backup"); + DBDeleteContactSetting(hContact, "ContactPhoto", "File"); + DBDeleteContactSetting(hContact, "ContactPhoto", "Format"); + + // Fix cache + ChangeAvatar(hContact, FALSE, TRUE, 0); + } + } + else if (type == GAIR_FAILED) + { + int wait = Proto_GetDelayAfterFail(szProto); + if (wait > 0) + { + // Reschedule to request after needed time (and avoid requests before that) + EnterCriticalSection(&cs); + QueueRemove(hContact); + QueueAdd(hContact, wait); + LeaveCriticalSection(&cs); + } + } +} + +int FetchAvatarFor(HANDLE hContact, char *szProto = NULL) +{ + int result = GAIR_NOAVATAR; + + if (szProto == NULL) + szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + + if (szProto != NULL && PollProtocolCanHaveAvatar(szProto) && PollContactCanHaveAvatar(hContact, szProto)) + { + // Can have avatar, but must request it? + if ( + (g_AvatarHistoryAvail && CallService(MS_AVATARHISTORY_ENABLED, (WPARAM) hContact, 0)) + || (PollCheckProtocol(szProto) && PollCheckContact(hContact, szProto)) + ) + { + // Request it + PROTO_AVATAR_INFORMATIONT pai_s = {0}; + pai_s.cbSize = sizeof(pai_s); + pai_s.hContact = hContact; + //_DebugTrace(hContact, "schedule request"); + INT_PTR res = CallProtoService(szProto, PS_GETAVATARINFOT, GAIF_FORCE, (LPARAM)&pai_s); +#ifdef _UNICODE + if (res == CALLSERVICE_NOTFOUND) + { + PROTO_AVATAR_INFORMATION pai = {0}; + pai.cbSize = sizeof(pai); + pai.hContact = hContact; + res = CallProtoService(szProto, PS_GETAVATARINFO, GAIF_FORCE, (LPARAM)&pai); + MultiByteToWideChar( CP_ACP, 0, pai.filename, -1, pai_s.filename, SIZEOF(pai_s.filename)); + pai_s.format = pai.format; + } +#endif + if (res != CALLSERVICE_NOTFOUND) result = res; + ProcessAvatarInfo(pai_s.hContact, result, &pai_s, szProto); + } + } + + return result; +} + +static void RequestThread(void *vParam) +{ + while (!g_shutDown) + { + EnterCriticalSection(&cs); + + if ( queue.getCount() == 0 ) + { + // No items, so supend thread + LeaveCriticalSection(&cs); + + mir_sleep(POOL_DELAY); + } + else + { + // Take a look at first item + QueueItem& qi = queue[ queue.getCount()-1 ]; + + if (qi.check_time > GetTickCount()) + { + // Not time to request yet, wait... + LeaveCriticalSection(&cs); + mir_sleep(POOL_DELAY); + } + else + { + // Will request this item + HANDLE hContact = qi.hContact; + queue.remove( queue.getCount()-1 ); + + QueueRemove(hContact); + + LeaveCriticalSection(&cs); + + if (FetchAvatarFor(hContact) == GAIR_WAITFOR) + { + // Mark to not request this contact avatar for more 30 min + EnterCriticalSection(&cs); + QueueRemove(hContact); + QueueAdd(hContact, REQUEST_WAITFOR_WAIT_TIME); + LeaveCriticalSection(&cs); + + // Wait a little until requesting again + mir_sleep(REQUEST_DELAY); + } + } + } + } + + DeleteCriticalSection(&cs); + queue.destroy(); +} diff --git a/plugins/Avs/poll.h b/plugins/Avs/poll.h new file mode 100644 index 0000000000..9761e7360a --- /dev/null +++ b/plugins/Avs/poll.h @@ -0,0 +1,36 @@ +/* +Copyright (C) 2006 Ricardo Pescuma Domenecci, Nightwish + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + + +#ifndef __POLL_H__ +# define __POLL_H__ + +struct QueueItem +{ + HANDLE hContact; + DWORD check_time; +}; + +void InitPolls(); +void FreePolls(); + +// Add an contact to a queue +void QueueAdd(HANDLE hContact); + +#endif // __POLL_H__ diff --git a/plugins/Avs/res/avatar.ico b/plugins/Avs/res/avatar.ico new file mode 100644 index 0000000000..3525625479 Binary files /dev/null and b/plugins/Avs/res/avatar.ico differ diff --git a/plugins/Avs/resource.h b/plugins/Avs/resource.h new file mode 100644 index 0000000000..2d8ba9add0 --- /dev/null +++ b/plugins/Avs/resource.h @@ -0,0 +1,60 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by avs.rc +// +#define ID_USE_DEFAULTS 3 +#define IDD_OPTIONS 101 +#define IDD_OPTIONS_PICTS 101 +#define IDD_OPENSUBCLASS 102 +#define IDD_AVATAROPTIONS 103 +#define IDI_AVATAR 104 +#define IDD_SET_OWN_SUBCLASS 105 +#define IDD_DIALOG1 106 +#define IDD_USER_AVATAR 106 +#define IDD_PROTO_AVATARS 107 +#define IDD_OPTIONS_AVATARS 109 +#define IDD_OPTIONS_OWN 110 +#define IDC_PROTOCOLS 1001 +#define IDC_CLIST 1002 +#define IDC_SETPROTOPIC 1003 +#define IDC_PROTOPIC 1004 +#define IDC_REMOVEPROTOPIC 1005 +#define IDC_PROTOAVATARNAME 1006 +#define IDC_CHECK1 1007 +#define IDC_PROTECTAVATAR 1007 +#define IDC_SHOWWARNINGS 1007 +#define IDC_PER_PROTO 1007 +#define IDC_CHANGE 1008 +#define IDC_MAKE_TRANSPARENT_BKG 1008 +#define IDC_GROW 1008 +#define IDC_RESET 1009 +#define IDC_MAKE_GRAYSCALE 1009 +#define IDC_AVATARNAME 1010 +#define IDC_MAKE_GRAYSCALE2 1010 +#define IDC_SET_MAKE_SQUARE 1010 +#define IDC_HIDEAVATAR 1011 +#define IDC_DELETE 1012 +#define IDC_MAKETRANSPBKG 1013 +#define IDC_GUESS_LEVEL 1014 +#define IDC_BKG_NUM_POINTS 1014 +#define IDC_COMBO1 1015 +#define IDC_BKG_NUM_POINTS_SPIN 1015 +#define IDC_BKG_COLOR_DIFFERENCE 1016 +#define IDC_SIZELIMITSPIN3 1017 +#define IDC_BKG_COLOR_DIFFERENCE_SPIN 1017 +#define IDC_BKG_NUM_POINTS_L 1018 +#define IDC_BKG_COLOR_DIFFERENCE_L 1019 +#define IDC_MAKE_TRANSP_PROPORTIONAL 1020 +#define IDC_MAKE_MY_AVATARS_TRANSP 1021 +#define IDC_MAKE_SQUARE 1023 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 108 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1024 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugins/Avs/vc6.rc b/plugins/Avs/vc6.rc new file mode 100644 index 0000000000..99a8f9da1d --- /dev/null +++ b/plugins/Avs/vc6.rc @@ -0,0 +1,2 @@ +#include "avs.rc" +#include "version.rc" diff --git a/plugins/Avs/version.h b/plugins/Avs/version.h new file mode 100644 index 0000000000..826d9fdefb --- /dev/null +++ b/plugins/Avs/version.h @@ -0,0 +1,5 @@ +#include "../../include/m_version.h" + +#define __FILEVERSION_STRING MIRANDA_VERSION_FILEVERSION +#define __VERSION_STRING MIRANDA_VERSION_STRING +#define __VERSION_DWORD MIRANDA_VERSION_DWORD diff --git a/plugins/Avs/version.rc b/plugins/Avs/version.rc new file mode 100644 index 0000000000..43d374df60 --- /dev/null +++ b/plugins/Avs/version.rc @@ -0,0 +1,113 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "version.h" +#include "winres.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page( 1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\r\n" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION __FILEVERSION_STRING + PRODUCTVERSION __FILEVERSION_STRING + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN +#if !defined(UNICODE) + VALUE "Comments", "Service plugin to load and update avatars for Miranda IM" + VALUE "CompanyName", "Written by Nightwish and Pescuma for Miranda IM project" + VALUE "FileDescription", "Service plugin to load and update avatars for Miranda IM" + VALUE "FileVersion", __FILEVERSION_STRING + VALUE "InternalName", "avs" + VALUE "LegalCopyright", "Copyright (C) 2010, Nightwish, Pescuma" + VALUE "LegalTrademarks", "Licensed under the Gnu General Public License V2 or any later version" + VALUE "OriginalFilename", "avs.dll" + VALUE "ProductName", "Miranda IM Avatar Service plugin" + VALUE "ProductVersion", __FILEVERSION_STRING +#else + VALUE "Comments", "Service plugin to load and update avatars for Miranda IM" + VALUE "CompanyName", "Written by Nightwish and Pescuma for Miranda IM project" + VALUE "FileDescription", "Service plugin to load and update avatars for Miranda IM" + VALUE "FileVersion", __FILEVERSION_STRING + VALUE "InternalName", "avsW" + VALUE "LegalCopyright", "Copyright (C) 2010, Nightwish, Pescuma" + VALUE "LegalTrademarks", "Licensed under the Gnu General Public License V2 or any later version" + VALUE "OriginalFilename", "avs.dll" + VALUE "ProductName", "Miranda IM Avatar Service (Unicode)" + VALUE "ProductVersion", __FILEVERSION_STRING +#endif + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + +#endif // AFX_RESOURCE_DLL -- cgit v1.2.3