/* Copyright (C) 2006-2007 Scott Ellis Copyright (C) 2007-2011 Jan Holub 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 "common.h" #include "subst.h" #include "popwin.h" #include "message_pump.h" #include "str_utils.h" #include "bitmap_func.h" #include "translations.h" #include "m_gender.h" extern TOOLTIPSKIN skin; __inline void AddRow(PopupWindowData *pwd, TCHAR *swzLabel, TCHAR *swzValue, char *szProto, bool bParseSmileys, bool bNewline, bool bLineAbove, bool bIsTitle = false, HICON hIcon = NULL) { pwd->rows = (RowData *)mir_realloc(pwd->rows, sizeof(RowData) * (pwd->iRowCount + 1)); pwd->rows[pwd->iRowCount].swzLabel = swzLabel ? mir_tstrdup(swzLabel) : NULL; pwd->rows[pwd->iRowCount].swzValue = swzValue ? mir_tstrdup(swzValue) : NULL; pwd->rows[pwd->iRowCount].spi = bParseSmileys ? Smileys_PreParse(swzValue, (int)_tcslen(swzValue), szProto) : NULL; pwd->rows[pwd->iRowCount].bValueNewline = bNewline; pwd->rows[pwd->iRowCount].bLineAbove = bLineAbove; pwd->rows[pwd->iRowCount].bIsTitle = bIsTitle; pwd->rows[pwd->iRowCount].hIcon = hIcon; pwd->iRowCount++; } LRESULT CALLBACK PopupWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { PopupWindowData *pwd = (PopupWindowData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch(uMsg) { case WM_CREATE: { CREATESTRUCT *cs = (CREATESTRUCT *)lParam; pwd = (PopupWindowData *)mir_alloc(sizeof(PopupWindowData)); memset(pwd, 0, sizeof(PopupWindowData)); pwd->clcit = *(CLCINFOTIPEX *)cs->lpCreateParams; pwd->iIconIndex = -1; pwd->hpenBorder = CreatePen(PS_SOLID, 1, opt.bBorder ? opt.colBorder : opt.colBg); pwd->hpenDivider = CreatePen(PS_SOLID, 1, opt.colDivider); pwd->iTrans = (int)(opt.iOpacity / 100.0 * 255); // load icons order for (int i = 0; i < EXICONS_COUNT; i++) pwd->bIsIconVisible[opt.exIconsOrder[i]] = opt.exIconsVis[i] ? true : false; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pwd); // work around bug hiding tips GetCursorPos(&pwd->ptCursorStartPos); SetTimer(hwnd, ID_TIMER_CHECKMOUSE, CHECKMOUSE_ELAPSE, 0); //Register copy menu hotkey (CTRL+C) pwd->iHotkeyId = GlobalAddAtom(_T("Tipper")); RegisterHotKey(hwnd, pwd->iHotkeyId, MOD_CONTROL, 0x43); if (pwd->clcit.szProto) { pwd->bIsTextTip= false; pwd->iIndent = opt.iTextIndent; pwd->iSidebarWidth= opt.iSidebarWidth; if (ServiceExists(MS_PROTO_GETACCOUNT)) { PROTOACCOUNT *pa = ProtoGetAccount(pwd->clcit.szProto); if (pa) _tcscpy(pwd->swzTitle, pa->tszAccountName); } if (_tcslen(pwd->swzTitle) == 0) a2t(pwd->clcit.szProto, pwd->swzTitle, TITLE_TEXT_LEN); if (CallService(MS_PROTO_ISACCOUNTLOCKED,0,(LPARAM)pwd->clcit.szProto)) mir_sntprintf(pwd->swzTitle, SIZEOF(pwd->swzTitle), TranslateT("%s (locked)"), pwd->swzTitle); // protocol status WORD wStatus = (WORD)CallProtoService(pwd->clcit.szProto, PS_GETSTATUS, 0, 0); // get status icon if (pwd->bIsIconVisible[0]) { pwd->extraIcons[0].hIcon = LoadSkinnedProtoIcon(pwd->clcit.szProto, wStatus); pwd->extraIcons[0].bDestroy = false; } // get activity icon if (pwd->bIsIconVisible[2]) { pwd->extraIcons[2].hIcon = GetJabberActivityIcon(0, pwd->clcit.szProto); pwd->extraIcons[2].bDestroy = false; } // uid info TCHAR swzUid[256], swzUidName[256]; if (Uid(0, pwd->clcit.szProto, swzUid, 256) && UidName(pwd->clcit.szProto, swzUidName, 253)) { _tcscat(swzUidName, _T(": ")); AddRow(pwd, swzUidName, swzUid, NULL, false, false, false); } // logon info TCHAR swzLogon[64]; if (TimestampToTimeDifference(NULL, pwd->clcit.szProto, "LogonTS", swzLogon, 59)) { _tcscat(swzLogon, TranslateT(" ago")); AddRow(pwd, TranslateT("Log on:"), swzLogon, NULL, false, false, false); } // logoff info TCHAR swzLogoff[64]; if (TimestampToTimeDifference(NULL, pwd->clcit.szProto, "LogoffTS", swzLogoff, 59)) { _tcscat(swzLogoff, TranslateT(" ago")); AddRow(pwd, TranslateT("Log off:"), swzLogoff, NULL, false, false, false); } // number of unread emails TCHAR swzEmailCount[64]; if (ProtoServiceExists(pwd->clcit.szProto, "/GetUnreadEmailCount")) { int iCount = (int)CallProtoService(pwd->clcit.szProto, "/GetUnreadEmailCount", 0, 0); if (iCount > 0) { _itot(iCount, swzEmailCount, 10); AddRow(pwd, TranslateT("Unread emails:"), swzEmailCount, NULL, false, false, false); } } TCHAR *swzText = (TCHAR *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, wStatus, GSMDF_TCHAR); if (swzText) { AddRow(pwd, TranslateT("Status:"), swzText, NULL, false, false, false); } if (wStatus >= ID_STATUS_ONLINE && wStatus <= ID_STATUS_OUTTOLUNCH) { // status message TCHAR *swzText = GetProtoStatusMessage(pwd->clcit.szProto, wStatus); if (swzText) { StripBBCodesInPlace(swzText); AddRow(pwd, TranslateT("Status message:"), swzText, pwd->clcit.szProto, true, true, true); mir_free(swzText); } // jabber mood or icq xstatus TCHAR *swzAdvTitle = GetJabberAdvStatusText(pwd->clcit.szProto, "mood", "title"); if (swzAdvTitle) { StripBBCodesInPlace(swzAdvTitle); AddRow(pwd, TranslateT("Mood:"), swzAdvTitle, pwd->clcit.szProto, true, false, true); TCHAR *swzAdvText = GetJabberAdvStatusText(pwd->clcit.szProto, "mood", "text"); if (swzAdvText) { StripBBCodesInPlace(swzAdvText); AddRow(pwd, _T(""), swzAdvText, pwd->clcit.szProto, true, true, false); mir_free(swzAdvText); } } else { if (DBGetContactSettingByte(0, pwd->clcit.szProto, "XStatusId", 0)) { // xstatus title swzAdvTitle = GetProtoExtraStatusTitle(pwd->clcit.szProto); if (swzAdvTitle) { StripBBCodesInPlace(swzAdvTitle); AddRow(pwd, TranslateT("XStatus:"), swzAdvTitle, pwd->clcit.szProto, true, false, true); } // xstatus message TCHAR *swzAdvText = GetProtoExtraStatusMessage(pwd->clcit.szProto); if (swzAdvText) { StripBBCodesInPlace(swzAdvText); AddRow(pwd, _T(""), swzAdvText, pwd->clcit.szProto, true, true, false); mir_free(swzAdvText); } } } if (swzAdvTitle) { mir_free(swzAdvTitle); // get advanced status icon if (pwd->bIsIconVisible[1]) { pwd->extraIcons[1].hIcon = (HICON)CallProtoService(pwd->clcit.szProto, PS_ICQ_GETCUSTOMSTATUSICON, 0, 0); pwd->extraIcons[1].bDestroy = true; } } // jabber activity TCHAR *swzActTitle = GetJabberAdvStatusText(pwd->clcit.szProto, "activity", "title"); if (swzActTitle) { StripBBCodesInPlace(swzActTitle); AddRow(pwd, TranslateT("Activity:"), swzActTitle, pwd->clcit.szProto, true, false, true); mir_free(swzActTitle); } TCHAR *swzActText = GetJabberAdvStatusText(pwd->clcit.szProto, "activity", "text"); if (swzActText) { StripBBCodesInPlace(swzActText); AddRow(pwd, _T(""), swzActText, pwd->clcit.szProto, true, true, false); mir_free(swzActText); } // listening to TCHAR *swzListening = GetListeningTo(pwd->clcit.szProto); if (swzListening) { StripBBCodesInPlace(swzListening); AddRow(pwd, TranslateT("Listening to:"), swzListening, NULL, false, true, true); mir_free(swzListening); } } } else if (pwd->clcit.swzText) { pwd->bIsTextTip = true; pwd->iIndent = 0; pwd->iSidebarWidth= 0; RECT rc = pwd->clcit.rcItem; bool mirandaTrayTip = ((rc.right - rc.left) == 20) && ((rc.bottom - rc.top) == 20) ? true : false; if (mirandaTrayTip && !opt.bTraytip) { MyDestroyWindow(hwnd); return 0; } if (mirandaTrayTip && opt.bHandleByTipper) // extended tray tooltip { pwd->bIsTrayTip = true; pwd->iIndent = opt.iTextIndent; pwd->iSidebarWidth= opt.iSidebarWidth; SendMessage(hwnd, PUM_REFRESHTRAYTIP, 0, 0); if (opt.bExpandTraytip) SetTimer(hwnd, ID_TIMER_TRAYTIP, opt.iExpandTime, 0); } else { TCHAR buff[2048], *swzText = pwd->clcit.swzText; size_t iBuffPos, i = 0, iSize = _tcslen(pwd->clcit.swzText); bool bTopMessage = false; while (i < iSize && swzText[i] != _T('<')) { iBuffPos = 0; while (swzText[i] != _T('\n') && swzText[i] != _T('\r') && i < iSize && iBuffPos < 2048) { if (swzText[i] != _T('\t')) buff[iBuffPos++] = swzText[i]; i++; } buff[iBuffPos] = 0; if (iBuffPos) { AddRow(pwd, _T(""), buff, NULL, false, true, false); bTopMessage = true; } while (i < iSize && (swzText[i] == _T('\n') || swzText[i] == _T('\r'))) i++; } // parse bold bits into labels and the rest into items while (i < iSize) { while (i + 2 < iSize && (swzText[i] != _T('<') || swzText[i + 1] != _T('b') || swzText[i + 2] != _T('>'))) { i++; } i += 3; iBuffPos = 0; while (i + 3 < iSize && iBuffPos < 2048 && (swzText[i] != _T('<') || swzText[i + 1] != _T('/') || swzText[i + 2] != _T('b') || swzText[i + 3] != _T('>'))) { if (swzText[i] != _T('\t')) buff[iBuffPos++] = swzText[i]; i++; } i += 4; buff[iBuffPos] = 0; if (iBuffPos) { pwd->rows = (RowData *)mir_realloc(pwd->rows, sizeof(RowData) * (pwd->iRowCount + 1)); pwd->rows[pwd->iRowCount].bValueNewline = false; pwd->rows[pwd->iRowCount].swzLabel = mir_tstrdup(buff); if (pwd->iRowCount == 1 && bTopMessage) pwd->rows[pwd->iRowCount].bLineAbove = true; else pwd->rows[pwd->iRowCount].bLineAbove = false; iBuffPos = 0; while (i < iSize && iBuffPos < 2048 && swzText[i] != _T('\n')) { if (swzText[i] != _T('\t') && swzText[i] != _T('\r')) buff[iBuffPos++] = swzText[i]; i++; } buff[iBuffPos] = 0; pwd->rows[pwd->iRowCount].swzValue = mir_tstrdup(buff); pwd->rows[pwd->iRowCount].spi = NULL; pwd->iRowCount++; } i++; } if (pwd->iRowCount == 0) { // single item pwd->iRowCount = 1; pwd->rows = (RowData *)mir_alloc(sizeof(RowData)); pwd->rows[0].bLineAbove = pwd->rows[0].bValueNewline = false; pwd->rows[0].swzLabel = 0; pwd->rows[0].swzValue = swzText; pwd->rows[0].spi = NULL; } } } else { pwd->bIsTextTip = false; pwd->iIndent = opt.iTextIndent; pwd->iSidebarWidth= opt.iSidebarWidth; pwd->hContact = pwd->clcit.hItem; pwd->iIconIndex = (int)CallService(MS_CLIST_GETCONTACTICON, (WPARAM)pwd->hContact, 0); // don't use stored status message if (!opt.bWaitForContent) DBDeleteContactSetting(pwd->hContact, MODULE, "TempStatusMsg"); TCHAR *swzNick = (TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)pwd->hContact, GCDNF_TCHAR); _tcsncpy(pwd->swzTitle, swzNick, TITLE_TEXT_LEN); char *szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)pwd->hContact, 0); pwd->spiTitle = Smileys_PreParse(pwd->swzTitle, -1, szProto); // get extra icons DBVARIANT dbv = {0}; int i = 0; if (szProto) { // status icon if (pwd->bIsIconVisible[0]) { for (i = 0; opt.exIconsOrder[i] != 0; i++); pwd->extraIcons[i].hIcon = ImageList_GetIcon((HIMAGELIST)CallService(MS_CLIST_GETICONSIMAGELIST, 0, 0), pwd->iIconIndex, 0); pwd->extraIcons[i].bDestroy = true; } // xstatus icon if (pwd->bIsIconVisible[1]) { for (i = 0; opt.exIconsOrder[i] != 1; i++); int iXstatus = DBGetContactSettingByte(pwd->hContact, szProto, "XStatusId", 0); if (iXstatus) { char szIconProto[64]; if (strcmp(szProto, szMetaModuleName) != 0) { strcpy(szIconProto, szProto); } else if (!DBGetContactSettingString(pwd->hContact, szProto, "XStatusProto", &dbv)) { strcpy(szIconProto, dbv.pszVal); DBFreeVariant(&dbv); } pwd->extraIcons[i].hIcon = (HICON)CallProtoService(szIconProto, PS_ICQ_GETCUSTOMSTATUSICON, (WPARAM)iXstatus, LR_SHARED); pwd->extraIcons[i].bDestroy = false; } } // activity icon if (pwd->bIsIconVisible[2]) { for (i = 0; opt.exIconsOrder[i] != 2; i++); pwd->extraIcons[i].hIcon = GetJabberActivityIcon(pwd->hContact, szProto); pwd->extraIcons[i].bDestroy = false; } // gender icon if (pwd->bIsIconVisible[3]) { for (i = 0; opt.exIconsOrder[i] != 3; i++); if (ServiceExists(MS_GENDER_GETICON)) { pwd->extraIcons[i].hIcon = (HICON)CallService(MS_GENDER_GETICON, (WPARAM)pwd->hContact, 0); } else { int iGender = DBGetContactSettingByte(pwd->hContact, "UserInfo", "Gender", 0); if (iGender == 0) iGender = DBGetContactSettingByte(pwd->hContact, szProto, "Gender", 0); if (iGender == GEN_FEMALE) pwd->extraIcons[i].hIcon = (HICON)CallService(MS_SKIN2_GETICON, NULL, (LPARAM)"UserInfoEx_common_female"); else if (iGender == GEN_MALE) pwd->extraIcons[i].hIcon = (HICON)CallService(MS_SKIN2_GETICON, NULL, (LPARAM)"UserInfoEx_common_male"); } pwd->extraIcons[i].bDestroy = false; } // flags icon if (pwd->bIsIconVisible[4]) { for (i = 0; opt.exIconsOrder[i] != 4; i++); int iCountry = 0; if (ServiceExists(MS_FLAGS_DETECTCONTACTORIGINCOUNTRY)) iCountry = CallService(MS_FLAGS_DETECTCONTACTORIGINCOUNTRY, (WPARAM)pwd->hContact, 0); else if (ServiceExists(MS_FLAGS_GETCONTACTORIGINCOUNTRY)) iCountry = CallService(MS_FLAGS_GETCONTACTORIGINCOUNTRY, (WPARAM)pwd->hContact, 0); if (iCountry != 0 && iCountry != CTRY_UNKNOWN && iCountry != CTRY_ERROR) { pwd->extraIcons[i].hIcon = LoadFlagIcon(iCountry); pwd->extraIcons[i].bDestroy = false; } } // fingerprint icon if (pwd->bIsIconVisible[5]) { if (ServiceExists(MS_FP_GETCLIENTICONT)) { for (i = 0; opt.exIconsOrder[i] != 5; i++); if (!DBGetContactSettingTString(pwd->hContact, szProto, "MirVer", &dbv)) { pwd->extraIcons[i].hIcon = (HICON)CallService(MS_FP_GETCLIENTICONT, (WPARAM)dbv.ptszVal, 0); pwd->extraIcons[i].bDestroy = true; DBFreeVariant(&dbv); } } else if (ServiceExists(MS_FP_GETCLIENTICON)) { for (i = 0; opt.exIconsOrder[i] != 5; i++); if (!DBGetContactSettingString(pwd->hContact, szProto, "MirVer", &dbv)) { pwd->extraIcons[i].hIcon = (HICON)CallService(MS_FP_GETCLIENTICON, (WPARAM)dbv.pszVal, 0); pwd->extraIcons[i].bDestroy = true; DBFreeVariant(&dbv); } } } //request xstatus details if (opt.bRetrieveXstatus) { if (!DBGetContactSettingByte(0, szProto, "XStatusAuto", 1) && ProtoServiceExists(szProto, PS_ICQ_REQUESTCUSTOMSTATUS)) CallProtoService(szProto, PS_ICQ_REQUESTCUSTOMSTATUS, (WPARAM)pwd->hContact, 0); } } SendMessage(hwnd, PUM_REFRESH_VALUES, FALSE, 0); } SendMessage(hwnd, PUM_GETHEIGHT, 0, 0); SendMessage(hwnd, PUM_CALCPOS, 0, 0); // transparency SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); if (opt.bDropShadow && opt.skinMode == SM_COLORFILL) SetClassLongPtr(hwnd, GCL_STYLE, CS_DROPSHADOW); else SetClassLongPtr(hwnd, GCL_STYLE, 0); if (!skin.bNeedLayerUpdate && MySetLayeredWindowAttributes) MySetLayeredWindowAttributes(hwnd, RGB(0,0,0), 0, LWA_ALPHA); if (opt.showEffect) SetTimer(hwnd, ID_TIMER_ANIMATE, ANIM_ELAPSE, 0); ShowWindow(hwnd, SW_SHOWNOACTIVATE); InvalidateRect(hwnd, 0, FALSE); // since tipper win is topmost, this should put it at top of topmost windows SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); return 0; } case WM_ERASEBKGND: { if (!skin.bNeedLayerUpdate) { HDC hdc = (HDC)wParam; RECT rc; GetClientRect(hwnd, &rc); BitBlt(hdc, 0, 0, skin.iWidth, skin.iHeight, skin.hdc, 0, 0, SRCCOPY); // border if (opt.bBorder) { HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); HPEN hOldPen = (HPEN)SelectObject(hdc, pwd->hpenBorder); int h = 0; if (opt.bRound) { int v; int w = 14; h = (rc.right-rc.left) > (w*2) ? w : (rc.right-rc.left); v = (rc.bottom-rc.top) > (w*2) ? w : (rc.bottom-rc.top); h = (h < v) ? h : v; } RoundRect(hdc, 0, 0, (rc.right - rc.left), (rc.bottom - rc.top), h, h); SelectObject(hdc, hOldBrush); SelectObject(hdc, hOldPen); } } return TRUE; } case WM_PAINT: { RECT r, r2; PAINTSTRUCT ps; BeginPaint(hwnd, &ps); HDC hdc = skin.bNeedLayerUpdate ? skin.hdc : ps.hdc; GetClientRect(hwnd, &r); r2 = r; HFONT hOldFont = (HFONT)GetCurrentObject(hdc,OBJ_FONT); // text background SetBkMode(hdc, TRANSPARENT); BLENDFUNCTION blend; blend.BlendOp = AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = 255; blend.AlphaFormat = AC_SRC_ALPHA; // avatar if (!pwd->bIsTextTip&& opt.avatarLayout != PAV_NONE && pwd->iAvatarHeight) { RECT rcAvatar; rcAvatar.top = opt.iOuterAvatarPadding; if (opt.avatarLayout == PAV_LEFT) { rcAvatar.left = r.left + opt.iOuterAvatarPadding; rcAvatar.right = rcAvatar.left + pwd->iRealAvatarWidth; r2.left += pwd->iRealAvatarWidth + (opt.iOuterAvatarPadding + opt.iInnerAvatarPadding - opt.iPadding); // padding re-added for text } else if (opt.avatarLayout == PAV_RIGHT) { rcAvatar.right = r.right - opt.iOuterAvatarPadding; rcAvatar.left = rcAvatar.right - pwd->iRealAvatarWidth; r2.right -= pwd->iRealAvatarWidth + (opt.iOuterAvatarPadding + opt.iInnerAvatarPadding - opt.iPadding); } rcAvatar.bottom = rcAvatar.top + pwd->iRealAvatarHeight; AVATARCACHEENTRY *ace = 0; if (pwd->hContact) ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)pwd->hContact, 0); else ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETMYAVATAR, 0, (LPARAM)pwd->clcit.szProto); if (ace && ace->hbmPic && (ace->dwFlags & AVS_BITMAP_VALID) && !(ace->dwFlags & AVS_HIDEONCLIST)) { ResizeBitmap rb = {0}; rb.size = sizeof(rb); rb.max_width = pwd->iRealAvatarWidth; rb.max_height = pwd->iRealAvatarHeight; rb.fit = RESIZEBITMAP_STRETCH | RESIZEBITMAP_KEEP_PROPORTIONS; rb.hBmp = ace->hbmPic; HBITMAP hbmpAvatar = (HBITMAP)CallService(MS_IMG_RESIZE, (WPARAM)&rb, 0); if (hbmpAvatar) { HRGN hrgnAvatar = 0; if (opt.bAvatarRound) { hrgnAvatar = CreateRoundRectRgn(rcAvatar.left, rcAvatar.top, rcAvatar.right + 1, rcAvatar.bottom + 1, 9, 9); SelectClipRgn(hdc, hrgnAvatar); } BITMAP bm; GetObject(hbmpAvatar, sizeof(bm), &bm); HDC hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, hbmpAvatar); blend.SourceConstantAlpha = (BYTE)(opt.iAvatarOpacity / 100.0 * 255); AlphaBlend(hdc, rcAvatar.left, rcAvatar.top, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, blend); blend.SourceConstantAlpha = 255; if (opt.bAvatarBorder) { SaveAlpha(&rcAvatar); HBRUSH hbrBorder = CreateSolidBrush(opt.colAvatarBorder); if (opt.bAvatarRound) FrameRgn(hdc, hrgnAvatar, hbrBorder, 1, 1); else FrameRect(hdc, &rcAvatar, hbrBorder); DeleteObject(hbrBorder); RestoreAlpha(&rcAvatar, (BYTE)(opt.iAvatarOpacity / 100.0 * 255)); } if (hrgnAvatar) { SelectClipRgn(hdc, 0); DeleteObject(hrgnAvatar); } if (hbmpAvatar != ace->hbmPic) DeleteObject(hbmpAvatar); DeleteDC(hdcMem); } } } RECT tr; tr.left = r2.left + opt.iPadding + opt.iTitleIndent; tr.right = r2.right - opt.iPadding; tr.top = 0; tr.bottom = opt.iPadding; if (!pwd->bIsTextTip&& opt.titleLayout != PTL_NOTITLE) { if (opt.titleLayout != PTL_NOICON) { // draw icons int iIconX, iIconY; iIconY = opt.iPadding + opt.iTextPadding; if (opt.titleLayout == PTL_RIGHTICON) iIconX = r2.right - 16 - opt.iPadding; else iIconX = r2.left + opt.iPadding; for (int i = 0; i < EXICONS_COUNT; i++) { if (pwd->extraIcons[i].hIcon) { DrawIconExAlpha(hdc, iIconX, iIconY, pwd->extraIcons[i].hIcon, 16, 16, 0, NULL, DI_NORMAL, false); iIconY += 20; } } } // title text if (hFontTitle) SelectObject(hdc, (HGDIOBJ)hFontTitle); SetTextColor(hdc, opt.colTitle); tr.top = opt.iPadding; tr.bottom = tr.top + pwd->iTitleHeight - opt.iPadding; UINT uTextFormat = DT_TOP | DT_LEFT | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS | DT_NOPREFIX; DrawTextExt(hdc, pwd->swzTitle, -1, &tr, uTextFormat, NULL, pwd->spiTitle); } // values pwd->iTextHeight = 0; bool bIconPainted, bUseRect = true; int iRowHeight; for (int i = 0; i < pwd->iRowCount; i++) { tr.top = tr.bottom; bUseRect = (tr.top + opt.iTextPadding >= pwd->iAvatarHeight); bIconPainted = false; if (bUseRect) { if (pwd->rows[i].bLineAbove) { HPEN hOldPen = (HPEN)SelectObject(hdc, pwd->hpenDivider); tr.top += opt.iTextPadding; RECT rec; SetRect(&rec, r.left + opt.iPadding + pwd->iIndent, tr.top, r.right - opt.iPadding, tr.top + 1); SaveAlpha(&rec); Rectangle(hdc, rec.left, rec.top, rec.right, rec.bottom ); RestoreAlpha(&rec); SelectObject(hdc, hOldPen); } tr.left = r.left + opt.iPadding + pwd->iIndent; if (pwd->rows[i].bValueNewline) tr.right = r.right - opt.iPadding; else tr.right = r.left + opt.iPadding + pwd->iIndent + pwd->iLabelWidth; } else { if (pwd->rows[i].bLineAbove) { HPEN hOldPen = (HPEN)SelectObject(hdc, pwd->hpenDivider); tr.top += opt.iTextPadding; RECT rec; SetRect(&rec, r2.left + opt.iPadding + pwd->iIndent, tr.top, r2.right - opt.iPadding, tr.top + 1); SaveAlpha(&rec); Rectangle(hdc, rec.left, rec.top, rec.right, rec.bottom ); RestoreAlpha(&rec); SelectObject(hdc, hOldPen); } tr.left = r2.left + opt.iPadding + pwd->iIndent; if (pwd->rows[i].bValueNewline) tr.right = r2.right - opt.iPadding; else tr.right = r2.left + opt.iPadding + pwd->iIndent + pwd->iLabelWidth; } if (pwd->rows[i].bValueNewline) iRowHeight = pwd->rows[i].iLabelHeight; else iRowHeight = max(pwd->rows[i].iLabelHeight, pwd->rows[i].iValueHeight); if (pwd->rows[i].iLabelHeight) { tr.top += opt.iTextPadding; tr.bottom = tr.top + iRowHeight; if (pwd->bIsTrayTip && pwd->rows[i].bIsTitle) { if (hFontTrayTitle) SelectObject(hdc, (HGDIOBJ)hFontTrayTitle); SetTextColor(hdc, opt.colTrayTitle); } else { if (hFontLabels) SelectObject(hdc, (HGDIOBJ)hFontLabels); SetTextColor(hdc, opt.colLabel); } // status icon in tray tooltip if (opt.titleLayout != PTL_NOICON && pwd->bIsTrayTip && pwd->rows[i].hIcon) { DrawIconExAlpha(hdc, opt.iPadding, tr.top + (pwd->rows[i].iLabelHeight - 16) / 2, pwd->rows[i].hIcon, 16, 16, 0, NULL, DI_NORMAL, false); bIconPainted = true; } DrawTextAlpha(hdc, pwd->rows[i].swzLabel, -1, &tr, opt.iLabelValign | ((opt.iLabelHalign == DT_RIGHT && !pwd->rows[i].bValueNewline) ? DT_RIGHT : DT_LEFT) | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX); if (pwd->rows[i].bValueNewline) tr.top = tr.bottom; } else { tr.bottom = tr.top; } if (pwd->rows[i].bValueNewline) iRowHeight = pwd->rows[i].iValueHeight; if (hFontValues) SelectObject(hdc, (HGDIOBJ)hFontValues); SetTextColor(hdc, opt.colValue); if (bUseRect) { tr.left = r.left + opt.iPadding + pwd->iIndent; if (!pwd->rows[i].bValueNewline) tr.left += pwd->iLabelWidth + opt.iValueIndent; tr.right = r.right - opt.iPadding; } else { tr.left = r2.left + opt.iPadding + pwd->iIndent; if (!pwd->rows[i].bValueNewline) tr.left += pwd->iLabelWidth + opt.iValueIndent; tr.right = r2.right - opt.iPadding; } if (pwd->rows[i].iValueHeight) { if (pwd->rows[i].bValueNewline || !pwd->rows[i].iLabelHeight) tr.top += opt.iTextPadding; if (pwd->rows[i].iLabelHeight > pwd->rows[i].iValueHeight && pwd->bIsTextTip&& pwd->rows[i].bIsTitle) tr.top = tr.bottom - pwd->rows[i].iValueHeight - 2; else tr.bottom = tr.top + iRowHeight; if (opt.titleLayout != PTL_NOICON && pwd->bIsTrayTip && pwd->rows[i].hIcon && !bIconPainted) DrawIconExAlpha(hdc, opt.iPadding, tr.top + (pwd->rows[i].iValueHeight - 16) / 2, pwd->rows[i].hIcon, 16, 16, 0, NULL, DI_NORMAL, false); UINT uFormat = opt.iValueValign | opt.iValueHalign | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS | DT_NOPREFIX; DrawTextExt(hdc, pwd->rows[i].swzValue, -1, &tr, uFormat, NULL, pwd->rows[i].spi); } } PremultipleChannels(); if (opt.showEffect == PSE_NONE) { if (skin.bNeedLayerUpdate) { POINT ptSrc = {0, 0}; SIZE szTip = {r.right - r.left, r.bottom - r.top}; blend.SourceConstantAlpha = pwd->iTrans; MyUpdateLayeredWindow(hwnd, NULL, NULL, &szTip, skin.hdc, &ptSrc, 0xffffffff, &blend, LWA_ALPHA); if (opt.bAeroGlass && MyDwmEnableBlurBehindWindow) { DWM_BLURBEHIND bb = {0}; bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; bb.fEnable = TRUE; bb.hRgnBlur = CreateOpaqueRgn(25, true); MyDwmEnableBlurBehindWindow(hwnd, &bb); } } else if (MySetLayeredWindowAttributes) { MySetLayeredWindowAttributes(hwnd, RGB(0,0,0), pwd->iTrans, LWA_ALPHA); } } SelectObject(hdc, hOldFont); EndPaint(hwnd, &ps); pwd->bIsPainted = true; return 0; } case WM_HOTKEY: { if (LOWORD(lParam) == MOD_CONTROL && HIWORD(lParam) == 0x43) // CTRL+C { ICONINFO iconInfo; if (pwd->iRowCount == 0) return 0; ShowWindow(hwnd, SW_HIDE); HMENU hMenu = CreatePopupMenu(); if (!hMenu) return 0; HICON hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ITEM_ALL), IMAGE_ICON, 0, 0, LR_LOADTRANSPARENT); if (!hIcon) { DestroyMenu(hMenu); return 0; } GetIconInfo(hIcon, &iconInfo); HBITMAP hbmpAllItems = iconInfo.hbmColor; DestroyIcon(hIcon); AppendMenu(hMenu, MF_STRING, COPYMENU_ALLITEMS_LABELS, LPGENT("Copy all items with labels")); AppendMenu(hMenu, MF_STRING, COPYMENU_ALLITEMS, LPGENT("Copy all items")); if (pwd->clcit.szProto || pwd->hContact) AppendMenu(hMenu, MF_STRING, COPYMENU_AVATAR, LPGENT("Copy avatar")); AppendMenu(hMenu, MF_SEPARATOR, 2000, 0); TranslateMenu(hMenu); SetMenuItemBitmaps(hMenu, COPYMENU_ALLITEMS_LABELS, MF_BYCOMMAND, hbmpAllItems, hbmpAllItems); SetMenuItemBitmaps(hMenu, COPYMENU_ALLITEMS, MF_BYCOMMAND, hbmpAllItems, hbmpAllItems); SetMenuItemBitmaps(hMenu, COPYMENU_AVATAR, MF_BYCOMMAND, hbmpAllItems, hbmpAllItems); hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ITEM), IMAGE_ICON, 0, 0, LR_LOADTRANSPARENT); if (!hIcon) { DeleteObject(hbmpAllItems); DestroyMenu(hMenu); return 0; } GetIconInfo(hIcon, &iconInfo); HBITMAP hbmpItem = iconInfo.hbmColor; DestroyIcon(hIcon); for (int i = 0; i < pwd->iRowCount; i++) { if (pwd->rows[i].swzValue) { TCHAR buff[128]; int iLen = (int)_tcslen(pwd->rows[i].swzValue); if (iLen) { if (iLen > MAX_VALUE_LEN) { _tcsncpy(buff, pwd->rows[i].swzValue, MAX_VALUE_LEN); buff[MAX_VALUE_LEN] = 0; _tcscat(buff, _T("...")); } else { _tcscpy(buff, pwd->rows[i].swzValue); } AppendMenu(hMenu, MF_STRING, i + 1, buff); // first id = 1, because no select have id = 0 SetMenuItemBitmaps(hMenu, i + 1, MF_BYCOMMAND, hbmpItem, hbmpItem); } else { AppendMenu(hMenu, MF_SEPARATOR, 0, 0); } } } POINT pt; GetCursorPos(&pt); SetForegroundWindow(hwnd); int iSelItem = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, 0); DeleteObject(hbmpAllItems); DeleteObject(hbmpItem); DestroyMenu(hMenu); if (iSelItem == 0) return 0; // no item was selected if (OpenClipboard(NULL)) { EmptyClipboard(); if (iSelItem == COPYMENU_AVATAR) // copy avatar { AVATARCACHEENTRY *ace = 0; if (pwd->hContact) ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)pwd->hContact, 0); else ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETMYAVATAR, 0, (LPARAM)pwd->clcit.szProto); if (ace && ace->hbmPic && (ace->dwFlags & AVS_BITMAP_VALID) && !(ace->dwFlags & AVS_HIDEONCLIST)) { HDC hdc = GetDC(hwnd); HDC hdcSrc = CreateCompatibleDC(hdc); HDC hdcDes = CreateCompatibleDC(hdc); HBITMAP hbmAvr = CreateCompatibleBitmap(hdc, ace->bmWidth, ace->bmHeight); SelectObject(hdcSrc, ace->hbmPic); SelectObject(hdcDes, hbmAvr); BitBlt(hdcDes, 0, 0, ace->bmWidth, ace->bmHeight, hdcSrc, 0, 0, SRCCOPY); SetClipboardData(CF_BITMAP, hbmAvr); ReleaseDC(hwnd, hdc); DeleteDC(hdcSrc); DeleteDC(hdcDes); } } else // copy text { HGLOBAL hClipboardData = GlobalAlloc(GMEM_DDESHARE, 4096); TCHAR *pchData = (TCHAR *)GlobalLock(hClipboardData); pchData[0] = 0; if (iSelItem == COPYMENU_ALLITEMS_LABELS) // copy all items with labels { for (int i = 0; i < pwd->iRowCount; i++) { if ((pwd->rows[i].swzLabel && pwd->rows[i].swzLabel[0]) || (pwd->rows[i].swzValue && pwd->rows[i].swzValue[0])) { if (pwd->rows[i].swzLabel && pwd->rows[i].swzLabel[0]) { _tcscat(pchData, pwd->rows[i].swzLabel); _tcscat(pchData, _T(" ")); } else { _tcscat(pchData, TranslateT(": ")); } if (pwd->rows[i].swzValue && pwd->rows[i].swzValue[0]) _tcscat(pchData, pwd->rows[i].swzValue); else _tcscat(pchData, TranslateT("")); _tcscat(pchData, _T("\r\n")); } } } else if (iSelItem == COPYMENU_ALLITEMS) // copy all items { for (int i = 0; i < pwd->iRowCount; i++) { if (pwd->rows[i].swzValue && pwd->rows[i].swzValue[0]) { _tcscat(pchData, pwd->rows[i].swzValue); _tcscat(pchData, _T("\r\n")); } } } else // single row { _tcscpy(pchData, pwd->rows[iSelItem - 1].swzValue); } GlobalUnlock(hClipboardData); SetClipboardData(CF_UNICODETEXT, hClipboardData); } CloseClipboard(); } } break; } case PUM_FADEOUTWINDOW: { // kill timers KillTimer(hwnd, ID_TIMER_ANIMATE); KillTimer(hwnd, ID_TIMER_CHECKMOUSE); KillTimer(hwnd, ID_TIMER_TRAYTIP); if (opt.showEffect != PSE_NONE) { if (skin.bNeedLayerUpdate) { POINT ptSrc = {0, 0}; SIZE sz = {pwd->rcWindow.right - pwd->rcWindow.left, pwd->rcWindow.bottom - pwd->rcWindow.top}; BLENDFUNCTION blend; blend.BlendOp = AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = pwd->iTrans; blend.AlphaFormat = AC_SRC_ALPHA; while (blend.SourceConstantAlpha != 0) { MyUpdateLayeredWindow(hwnd, NULL, NULL, &sz, skin.hdc, &ptSrc, 0xffffffff, &blend, LWA_ALPHA); blend.SourceConstantAlpha = max(blend.SourceConstantAlpha - opt.iAnimateSpeed, 0); Sleep(ANIM_ELAPSE); } } else if (MySetLayeredWindowAttributes) { int iTrans = pwd->iTrans; while (iTrans != 0) { MySetLayeredWindowAttributes(hwnd, RGB(0,0,0), iTrans, LWA_ALPHA); iTrans = max(iTrans - opt.iAnimateSpeed, 0); Sleep(ANIM_ELAPSE); } } } break; } case WM_DESTROY: { ShowWindow(hwnd, SW_HIDE); // unregister hotkey UnregisterHotKey(hwnd, pwd->iHotkeyId); DeleteObject(pwd->hpenBorder); DeleteObject(pwd->hpenDivider); if (pwd->hrgnAeroGlass) DeleteObject(pwd->hrgnAeroGlass); Smileys_FreeParse(pwd->spiTitle); int i; for (i = 0; i < pwd->iRowCount; i++) { mir_free(pwd->rows[i].swzLabel); mir_free(pwd->rows[i].swzValue); Smileys_FreeParse(pwd->rows[i].spi); } if (pwd->rows) mir_free(pwd->rows); pwd->rows = NULL; // destroy icons for (i = 0; i < EXICONS_COUNT; i++) { if (pwd->extraIcons[i].bDestroy) DestroyIcon(pwd->extraIcons[i].hIcon); } if (pwd->clcit.swzText) { mir_free(pwd->clcit.swzText); pwd->clcit.swzText = NULL; } mir_free(pwd); pwd = NULL; if (skin.colSavedBits) mir_free(skin.colSavedBits); if (skin.hBitmap)DeleteObject(skin.hBitmap); if (skin.hdc) DeleteDC(skin.hdc); skin.colSavedBits = NULL; skin.hBitmap = NULL; skin.hdc = NULL; SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); break; } case WM_TIMER: { if (wParam == ID_TIMER_ANIMATE) { pwd->iAnimStep++; if (pwd->iAnimStep == ANIM_STEPS) KillTimer(hwnd, ID_TIMER_ANIMATE); SendMessage(hwnd, PUM_UPDATERGN, 1, 0); } else if (wParam == ID_TIMER_CHECKMOUSE) { // workaround for tips that just won't go away POINT pt; GetCursorPos(&pt); if (abs(pt.x - pwd->ptCursorStartPos.x) > opt.iMouseTollerance || abs(pt.y - pwd->ptCursorStartPos.y) > opt.iMouseTollerance) // mouse has moved beyond tollerance { PostMPMessage(MUM_DELETEPOPUP, 0, 0); } } else if (wParam == ID_TIMER_TRAYTIP) { KillTimer(hwnd, ID_TIMER_TRAYTIP); SendMessage(hwnd, PUM_EXPANDTRAYTIP, 0, 0); } break; } case PUM_SETSTATUSTEXT: { if (pwd && (HANDLE)wParam == pwd->hContact) { DBWriteContactSettingTString(pwd->hContact, MODULE, "TempStatusMsg", (TCHAR *)lParam); pwd->bIsPainted = false; pwd->bNeedRefresh = true; SendMessage(hwnd, PUM_REFRESH_VALUES, TRUE, 0); InvalidateRect(hwnd, 0, TRUE); } if (lParam) mir_free((void *)lParam); return TRUE; } case PUM_SHOWXSTATUS: { if (pwd && (HANDLE)wParam == pwd->hContact) { // in case we have retrieve xstatus pwd->bIsPainted = false; SendMessage(hwnd, PUM_REFRESH_VALUES, TRUE, 0); InvalidateRect(hwnd, 0, TRUE); } return TRUE; } case PUM_SETAVATAR: { if (pwd && (HANDLE)wParam == pwd->hContact) { pwd->bIsPainted = false; SendMessage(hwnd, PUM_GETHEIGHT, 0, 0); SendMessage(hwnd, PUM_CALCPOS, 0, 0); InvalidateRect(hwnd, 0, TRUE); } return TRUE; } case PUM_EXPANDTRAYTIP: { pwd->bIsPainted = false; if (skin.bNeedLayerUpdate) { RECT r = pwd->rcWindow; POINT ptSrc = {0, 0}; SIZE sz = {r.right - r.left, r.bottom - r.top}; BLENDFUNCTION blend; blend.BlendOp = AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = 0; blend.AlphaFormat = AC_SRC_ALPHA; MyUpdateLayeredWindow(hwnd, NULL, NULL, &sz, skin.hdc, &ptSrc, 0xffffffff, &blend, LWA_ALPHA); } else if (MySetLayeredWindowAttributes) { MySetLayeredWindowAttributes(hwnd, RGB(0,0,0), 0, LWA_ALPHA); } SendMessage(hwnd, PUM_REFRESHTRAYTIP, 1, 0); SendMessage(hwnd, PUM_GETHEIGHT, 0, 0); SendMessage(hwnd, PUM_CALCPOS, 0, 0); InvalidateRect(hwnd, 0, TRUE); if (opt.showEffect) { KillTimer(hwnd, ID_TIMER_ANIMATE); SetTimer(hwnd, ID_TIMER_ANIMATE, ANIM_ELAPSE, 0); pwd->iAnimStep = 0; pwd->iCurrentTrans = 0; } return TRUE; } case PUM_REFRESH_VALUES: { if (pwd && pwd->clcit.szProto == 0 && !pwd->bIsTextTip) { for (int i = 0; i < pwd->iRowCount; i++) { mir_free(pwd->rows[i].swzLabel); mir_free(pwd->rows[i].swzValue); Smileys_FreeParse(pwd->rows[i].spi); } if (pwd->rows) { mir_free(pwd->rows); pwd->rows = 0; } pwd->iRowCount = 0; DIListNode *node = opt.diList; TCHAR buff_label[LABEL_LEN], buff[VALUE_LEN]; while (node) { if (node->di.bIsVisible) { if (GetLabelText(pwd->hContact, node->di, buff_label, LABEL_LEN) && GetValueText(pwd->hContact, node->di, buff, VALUE_LEN)) { if (node->di.bLineAbove // we have a line above && pwd->iRowCount > 0 // and we're not the first row && pwd->rows[pwd->iRowCount - 1].bLineAbove // and above us there's a line above && pwd->rows[pwd->iRowCount - 1].swzLabel[0] == 0 // with no label && pwd->rows[pwd->iRowCount - 1].swzValue[0] == 0) // and no value { // overwrite item above pwd->iRowCount--; mir_free(pwd->rows[pwd->iRowCount].swzLabel); mir_free(pwd->rows[pwd->iRowCount].swzValue); Smileys_FreeParse(pwd->rows[pwd->iRowCount].spi); //prevent possible mem leak } else { pwd->rows = (RowData *)mir_realloc(pwd->rows, sizeof(RowData) * (pwd->iRowCount + 1)); } char *szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)pwd->hContact, 0); pwd->rows[pwd->iRowCount].swzLabel = mir_tstrdup(buff_label); pwd->rows[pwd->iRowCount].swzValue = mir_tstrdup(buff); pwd->rows[pwd->iRowCount].spi = Smileys_PreParse(buff, (int)_tcslen(buff), szProto); pwd->rows[pwd->iRowCount].bValueNewline = node->di.bValueNewline; pwd->rows[pwd->iRowCount].bLineAbove = node->di.bLineAbove; pwd->iRowCount++; } } node = node->next; } // if the last item is just a divider, remove it if (pwd->iRowCount > 0 && pwd->rows[pwd->iRowCount - 1].bLineAbove // and above us there's a line above && pwd->rows[pwd->iRowCount - 1].swzLabel[0] == 0 // with no label && pwd->rows[pwd->iRowCount - 1].swzValue[0] == 0) // and no value { pwd->iRowCount--; mir_free(pwd->rows[pwd->iRowCount].swzLabel); mir_free(pwd->rows[pwd->iRowCount].swzValue); Smileys_FreeParse(pwd->rows[pwd->iRowCount].spi); //prevent possible mem leak if (pwd->iRowCount == 0) { mir_free(pwd->rows); pwd->rows = 0; } } if (wParam == TRUE) { SendMessage(hwnd, PUM_GETHEIGHT, 0, 0); SendMessage(hwnd, PUM_CALCPOS, 0, 0); } } return TRUE; } case PUM_GETHEIGHT: { int *pHeight = (int *)wParam; HDC hdc = GetDC(hwnd); SIZE sz; RECT rc, smr; rc.top = rc.left = 0; rc.right = opt.iWinWidth; int iWidth = opt.iPadding; int iWinAvatarHeight = 0; bool bStatusMsg = false; HFONT hOldFont = (HFONT)GetCurrentObject(hdc,OBJ_FONT); // avatar height pwd->iAvatarHeight = 0; if (!pwd->bIsTextTip && opt.avatarLayout != PAV_NONE && ServiceExists(MS_AV_GETAVATARBITMAP)) { AVATARCACHEENTRY *ace = 0; if (pwd->hContact) ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)pwd->hContact, 0); else ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETMYAVATAR, 0, (LPARAM)pwd->clcit.szProto); if (ace && (ace->dwFlags & AVS_BITMAP_VALID) && !(ace->dwFlags & AVS_HIDEONCLIST)) { if (opt.bOriginalAvatarSize && max(ace->bmWidth, ace->bmHeight) <= opt.iAvatarSize) { pwd->iRealAvatarHeight = ace->bmHeight; pwd->iRealAvatarWidth = ace->bmWidth; } else { if (ace->bmHeight >= ace->bmWidth) { pwd->iRealAvatarHeight = opt.iAvatarSize; pwd->iRealAvatarWidth = (int)(opt.iAvatarSize * (ace->bmWidth / (double)ace->bmHeight)); } else { pwd->iRealAvatarHeight = (int)(opt.iAvatarSize * (ace->bmHeight / (double)ace->bmWidth)); pwd->iRealAvatarWidth = opt.iAvatarSize; } } pwd->iAvatarHeight = opt.iOuterAvatarPadding + opt.iInnerAvatarPadding + pwd->iRealAvatarHeight; iWinAvatarHeight = 2 * opt.iOuterAvatarPadding + pwd->iRealAvatarHeight; iWidth += pwd->iRealAvatarWidth + (opt.iOuterAvatarPadding + opt.iInnerAvatarPadding - opt.iPadding); } } // titlebar height if (!pwd->bIsTextTip && pwd->swzTitle && opt.titleLayout != PTL_NOTITLE) { smr.top = smr.bottom = 0; smr.left = rc.left + opt.iPadding + pwd->iIndent; smr.right = rc.right; if (pwd->iRealAvatarWidth > 0) smr.right -= pwd->iRealAvatarWidth + opt.iOuterAvatarPadding + opt.iInnerAvatarPadding; else smr.right -= opt.iPadding; if (hFontTitle) SelectObject(hdc, (HGDIOBJ)hFontTitle); DrawTextExt(hdc, pwd->swzTitle, -1, &smr, DT_CALCRECT | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS | DT_NOPREFIX, NULL, pwd->spiTitle); iWidth += opt.iPadding + opt.iTitleIndent + smr.right; pwd->iTitleHeight = opt.iPadding + smr.bottom; } else { pwd->iTitleHeight = opt.iPadding; } // icon height int i, iCount = 0; if (pwd->hContact || pwd->clcit.szProto) { for(i = 0; i < EXICONS_COUNT; i++) { if ((INT_PTR)pwd->extraIcons[i].hIcon == CALLSERVICE_NOTFOUND) pwd->extraIcons[i].hIcon = 0; if (pwd->extraIcons[i].hIcon) iCount++; } } pwd->iIconsHeight = (iCount * 20) + 20; // text height pwd->iTextHeight = pwd->iLabelWidth = 0; // iterate once to find max label width for items with label and value on same line, but don't consider width of labels on a new line for (i = 0; i < pwd->iRowCount; i++) { if (pwd->rows[i].swzLabel && !pwd->rows[i].bValueNewline) { if (pwd->bIsTrayTip && pwd->rows[i].bIsTitle) { if (hFontTrayTitle) SelectObject(hdc, (HGDIOBJ)hFontTrayTitle); } else { if (hFontLabels) SelectObject(hdc, (HGDIOBJ)hFontLabels); } GetTextExtentPoint32(hdc, pwd->rows[i].swzLabel, (int)_tcslen(pwd->rows[i].swzLabel), &sz); if (sz.cx > pwd->iLabelWidth) pwd->iLabelWidth = sz.cx; } } for (i = 0; i < pwd->iRowCount; i++) { if (pwd->bIsTrayTip && pwd->rows[i].bIsTitle) { if (hFontTrayTitle) SelectObject(hdc, (HGDIOBJ)hFontTrayTitle); } else { if (hFontLabels) SelectObject(hdc, (HGDIOBJ)hFontLabels); } if (pwd->rows[i].swzLabel && pwd->rows[i].swzLabel[0]) GetTextExtentPoint32(hdc, pwd->rows[i].swzLabel, (int)_tcslen(pwd->rows[i].swzLabel), &sz); else sz.cy = sz.cx = 0; // save so we don't have to recalculate pwd->rows[i].iLabelHeight = sz.cy; smr.top = smr.bottom = 0; smr.left = rc.left + opt.iPadding + pwd->iIndent; smr.right = rc.right; if (hFontValues) SelectObject(hdc, (HGDIOBJ)hFontValues); if (pwd->iTitleHeight + pwd->iTextHeight + opt.iTextPadding < pwd->iAvatarHeight) smr.right -= pwd->iRealAvatarWidth + opt.iOuterAvatarPadding + opt.iInnerAvatarPadding; else smr.right -= opt.iPadding; if (!pwd->rows[i].bValueNewline) smr.right -= pwd->iLabelWidth + opt.iValueIndent; if (pwd->rows[i].swzValue && pwd->rows[i].swzValue[0]) { if (!bStatusMsg && opt.bGetNewStatusMsg) { if (!_tcscmp(pwd->rows[i].swzValue, _T("%sys:status_msg%"))) bStatusMsg = true; } DrawTextExt(hdc, pwd->rows[i].swzValue, -1, &smr, DT_CALCRECT | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS | DT_NOPREFIX, NULL, pwd->rows[i].spi); } else { smr.left = smr.right = 0; } // save so we don't have to recalculate pwd->rows[i].iValueHeight = smr.bottom; pwd->rows[i].iTotalHeight = (pwd->rows[i].bLineAbove ? opt.iTextPadding : 0); if (pwd->rows[i].bValueNewline) { if (sz.cy) pwd->rows[i].iTotalHeight += sz.cy + opt.iTextPadding; if (smr.bottom) pwd->rows[i].iTotalHeight += smr.bottom + opt.iTextPadding; } else { int maxheight = max(sz.cy, smr.bottom); if (maxheight) pwd->rows[i].iTotalHeight += maxheight + opt.iTextPadding; } // only consider this item's width, and include it's height, if it doesn't make the window too big if (max(pwd->iTitleHeight + pwd->iTextHeight + opt.iPadding + pwd->rows[i].iTotalHeight, pwd->iAvatarHeight) <= opt.iWinMaxHeight || pwd->bIsTrayTip) { if (iWidth < opt.iWinWidth) { int wid = opt.iPadding + pwd->iIndent + (pwd->rows[i].bValueNewline ? max(sz.cx, smr.right - smr.left) : pwd->iLabelWidth + opt.iValueIndent + (smr.right - smr.left)); if (pwd->iTitleHeight + pwd->iTextHeight + opt.iTextPadding < pwd->iAvatarHeight) iWidth = max(iWidth, wid + pwd->iRealAvatarWidth + opt.iOuterAvatarPadding + opt.iInnerAvatarPadding); else iWidth = max(iWidth, wid + opt.iPadding); } pwd->iTextHeight += pwd->rows[i].iTotalHeight; } } SelectObject(hdc, hOldFont); ReleaseDC(hwnd, hdc); int iHeight = max(pwd->iTitleHeight + pwd->iTextHeight + opt.iPadding, iWinAvatarHeight); iHeight = max(pwd->iIconsHeight, iHeight); if (bStatusMsg) iHeight += 50; if (iHeight < opt.iMinHeight) iHeight = opt.iMinHeight; // ignore minwidth for text tips if (!pwd->bIsTextTip && iWidth < opt.iMinWidth) iWidth = opt.iMinWidth; // ignore maxheight for tray tip if (!pwd->bIsTrayTip && iHeight > opt.iWinMaxHeight) iHeight = opt.iWinMaxHeight; if (iWidth > opt.iWinWidth) iWidth = opt.iWinWidth; CreateSkinBitmap(iWidth, iHeight, pwd->bIsTextTip && !pwd->bIsTrayTip); GetWindowRect(hwnd, &rc); if (rc.right - rc.left != iWidth || rc.bottom - rc.top != iHeight) { SetWindowPos(hwnd, 0, 0, 0, iWidth, iHeight, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); GetWindowRect(hwnd, &pwd->rcWindow); SendMessage(hwnd, PUM_UPDATERGN, 0, 0); InvalidateRect(hwnd, 0, TRUE); } if (pHeight) *pHeight = iHeight; return TRUE; } case PUM_UPDATERGN: { HRGN hRgn; RECT r = pwd->rcWindow; int v, h; int w = 11; r.right -= r.left; r.left = 0; r.bottom -= r.top; r.top = 0; if (opt.showEffect == PSE_FADE && wParam == 1) { if (skin.bNeedLayerUpdate) { POINT ptSrc = {0, 0}; SIZE sz = {r.right - r.left, r.bottom - r.top}; BLENDFUNCTION blend; blend.BlendOp = AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = pwd->iCurrentTrans; blend.AlphaFormat = AC_SRC_ALPHA; pwd->iCurrentTrans += opt.iAnimateSpeed; if (pwd->iCurrentTrans > pwd->iTrans) { pwd->iCurrentTrans = pwd->iTrans; pwd->iAnimStep = ANIM_STEPS; } MyUpdateLayeredWindow(hwnd, NULL, NULL, &sz, skin.hdc, &ptSrc, 0xffffffff, &blend, LWA_ALPHA); } else if (MySetLayeredWindowAttributes) { pwd->iCurrentTrans += opt.iAnimateSpeed; if (pwd->iCurrentTrans > pwd->iTrans) { pwd->iCurrentTrans = pwd->iTrans; pwd->iAnimStep = ANIM_STEPS; } MySetLayeredWindowAttributes(hwnd, RGB(0,0,0), pwd->iCurrentTrans, LWA_ALPHA); } } else if (opt.showEffect == PSE_ANIMATE && pwd->bIsPainted) { if (pwd->iAnimStep <= (ANIM_STEPS / opt.iAnimateSpeed)) { float frac = 1.0f - pwd->iAnimStep / ((float)ANIM_STEPS / opt.iAnimateSpeed); int wi = r.right, hi = r.bottom; r.left += (int)(wi / 2.0f * frac + 0.5f); r.right -= (int)(wi / 2.0f * frac + 0.5f); r.top += (int)(hi / 2.0f * frac + 0.5f); r.bottom -= (int)(hi / 2.0f * frac + 0.5f); } else { pwd->iAnimStep = ANIM_STEPS; } if (skin.bNeedLayerUpdate) { RECT r2 = pwd->rcWindow; POINT ptPos = {r.left + r2.left, r.top + r2.top}; POINT ptSrc = {r.left, r.top}; SIZE sz = {r.right - r.left, r.bottom - r.top}; BLENDFUNCTION blend; blend.BlendOp = AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = pwd->iTrans; blend.AlphaFormat = AC_SRC_ALPHA; MyUpdateLayeredWindow(hwnd, NULL, &ptPos, &sz, skin.hdc, &ptSrc, 0xffffffff, &blend, LWA_ALPHA); } } if (!skin.bNeedLayerUpdate) { // round corners if (opt.bRound) { h = (r.right-r.left)>(w*2) ? w : (r.right-r.left); v = (r.bottom-r.top)>(w*2) ? w : (r.bottom-r.top); h = (hiAnimStep == 1) { if (MySetLayeredWindowAttributes) MySetLayeredWindowAttributes(hwnd, RGB(0,0,0), pwd->iTrans, LWA_ALPHA); } } else { // aero glass (vista+) if (opt.bAeroGlass && MyDwmEnableBlurBehindWindow && pwd->iAnimStep > 5) { if (pwd->hrgnAeroGlass) { DeleteObject(pwd->hrgnAeroGlass); pwd->hrgnAeroGlass = 0; } pwd->hrgnAeroGlass = CreateOpaqueRgn(25, true); if (pwd->hrgnAeroGlass) { DWM_BLURBEHIND bb = {0}; bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; bb.fEnable = TRUE; bb.hRgnBlur = pwd->hrgnAeroGlass; MyDwmEnableBlurBehindWindow(hwnd, &bb); } } } return TRUE; } case PUM_CALCPOS: { RECT rcWork, rc; SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, FALSE); if (MyMonitorFromPoint) { HMONITOR hMon = MyMonitorFromPoint(pwd->clcit.ptCursor, MONITOR_DEFAULTTONEAREST); MONITORINFO mi; mi.cbSize = sizeof(mi); if (MyGetMonitorInfo(hMon, &mi)) rcWork = mi.rcWork; } GetWindowRect(hwnd, &rc); int x = 0, y = 0, iWidth = (rc.right - rc.left), iHeight = (rc.bottom - rc.top); switch(opt.pos) { case PP_BOTTOMRIGHT: x = pwd->clcit.ptCursor.x + GetSystemMetrics(SM_CXSMICON); // cursor size is too large - use small icon size y = pwd->clcit.ptCursor.y + GetSystemMetrics(SM_CYSMICON); break; case PP_BOTTOMLEFT: x = pwd->clcit.ptCursor.x - iWidth - GetSystemMetrics(SM_CXSMICON); y = pwd->clcit.ptCursor.y + GetSystemMetrics(SM_CYSMICON); break; case PP_TOPRIGHT: x = pwd->clcit.ptCursor.x + GetSystemMetrics(SM_CXSMICON); y = pwd->clcit.ptCursor.y - iHeight - GetSystemMetrics(SM_CYSMICON); break; case PP_TOPLEFT: x = pwd->clcit.ptCursor.x - iWidth - GetSystemMetrics(SM_CXSMICON); y = pwd->clcit.ptCursor.y - iHeight - GetSystemMetrics(SM_CYSMICON); break; } if (x + iWidth + 8 > rcWork.right) x = rcWork.right - iWidth - 8; if (x - 8 < rcWork.left) x = rcWork.left + 8; if (pwd->bAllowReposition || !pwd->bNeedRefresh) { if (y + iHeight > rcWork.bottom) { y = pwd->clcit.ptCursor.y - iHeight - 8; pwd->bAllowReposition = true; } if (y - 8 < rcWork.top) { y = pwd->clcit.ptCursor.y + GetSystemMetrics(SM_CYSMICON); pwd->bAllowReposition = true; } } SetWindowPos(hwnd, 0, x, y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER); GetWindowRect(hwnd, &pwd->rcWindow); return TRUE; } case PUM_REFRESHTRAYTIP: { int i, j; for (i = 0; i < pwd->iRowCount; i++) { mir_free(pwd->rows[i].swzLabel); mir_free(pwd->rows[i].swzValue); Smileys_FreeParse(pwd->rows[i].spi); } if (pwd->rows) { mir_free(pwd->rows); pwd->rows = NULL; } pwd->iRowCount = 0; DWORD dwItems = (wParam == 0) ? opt.iFirstItems : opt.iSecondItems; bool bFirstItem = true; TCHAR buff[64]; int oldOrder = -1, iProtoCount = 0; PROTOACCOUNT **accs; ProtoEnumAccounts(&iProtoCount, &accs); for (j = 0; j < iProtoCount; j++) { PROTOACCOUNT *pa = NULL; for (i = 0; i < iProtoCount; i++) { if (accs[i]->iOrder > oldOrder && (pa == NULL || accs[i]->iOrder < pa->iOrder)) pa = accs[i]; } oldOrder = pa->iOrder; WORD wStatus = CallProtoService(pa->szModuleName, PS_GETSTATUS, 0, 0); if (opt.bHideOffline && wStatus == ID_STATUS_OFFLINE) continue; if (!IsAccountEnabled(pa) || !IsTrayProto(pa->tszAccountName, (BOOL)wParam)) continue; if (dwItems & TRAYTIP_NUMCONTACTS) { int iCount = 0, iCountOnline = 0; HANDLE hContact = db_find_first(); while (hContact) { char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); if (proto && !strcmp(proto, pa->szModuleName)) { if (DBGetContactSettingWord(hContact, proto, "Status", ID_STATUS_OFFLINE) != ID_STATUS_OFFLINE) iCountOnline++; iCount++; } hContact = db_find_next(hContact); } mir_sntprintf(buff, 64, _T("(%d/%d)"), iCountOnline, iCount); } else { _tcscpy(buff, _T("")); } TCHAR swzProto[256]; _tcscpy(swzProto, pa->tszAccountName); if (dwItems & TRAYTIP_LOCKSTATUS) { if (CallService(MS_PROTO_ISACCOUNTLOCKED,0,(LPARAM)pa->szModuleName)) mir_sntprintf(swzProto, SIZEOF(swzProto), TranslateT("%s (locked)"), pa->tszAccountName); } AddRow(pwd, swzProto, buff, NULL, false, false, !bFirstItem, true, LoadSkinnedProtoIcon(pa->szModuleName, wStatus)); bFirstItem = false; if (dwItems & TRAYTIP_LOGON) { if (TimestampToTimeDifference(NULL, pa->szModuleName, "LogonTS", buff, 59)) { _tcscat(buff, TranslateT(" ago")); AddRow(pwd, TranslateT("Log on:"), buff, NULL, false, false, false); } if (TimestampToTimeDifference(NULL, pwd->clcit.szProto, "LogoffTS", buff, 59)) { _tcscat(buff, TranslateT(" ago")); AddRow(pwd, TranslateT("Log off:"), buff, NULL, false, false, false); } } if (dwItems & TRAYTIP_UNREAD_EMAILS && ProtoServiceExists(pa->szModuleName, "/GetUnreadEmailCount")) { int iCount = (int)CallProtoService(pa->szModuleName, "/GetUnreadEmailCount", 0, 0); if (iCount > 0) { _itot(iCount, buff, 10); AddRow(pwd, TranslateT("Unread emails:"), buff, NULL, false, false, false); } } if (dwItems & TRAYTIP_STATUS) { TCHAR *swzText = (TCHAR *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, wStatus, GSMDF_TCHAR); if (swzText) { AddRow(pwd, TranslateT("Status:"), swzText, NULL, false, false, false); } } if (wStatus >= ID_STATUS_ONLINE && wStatus <= ID_STATUS_OUTTOLUNCH) { if (dwItems & TRAYTIP_STATUS_MSG) { TCHAR *swzText = GetProtoStatusMessage(pa->szModuleName, wStatus); if (swzText) { StripBBCodesInPlace(swzText); AddRow(pwd, TranslateT("Status message:"), swzText, pa->szModuleName, true, true, false); mir_free(swzText); } } if (dwItems & TRAYTIP_EXTRA_STATUS) { // jabber mood or icq xstatus TCHAR *swzAdvTitle = GetJabberAdvStatusText(pa->szModuleName, "mood", "title"); if (swzAdvTitle) { StripBBCodesInPlace(swzAdvTitle); AddRow(pwd, TranslateT("Mood:"), swzAdvTitle, pa->szModuleName, true, false, false); mir_free(swzAdvTitle); TCHAR *swzAdvText = GetJabberAdvStatusText(pa->szModuleName, "mood", "text"); if (swzAdvText) { StripBBCodesInPlace(swzAdvText); AddRow(pwd, _T(""), swzAdvText, pa->szModuleName, true, true, false); mir_free(swzAdvText); } } else { if (DBGetContactSettingByte(0, pa->szModuleName, "XStatusId", 0)) { // xstatus title swzAdvTitle = GetProtoExtraStatusTitle(pa->szModuleName); if (swzAdvTitle) { StripBBCodesInPlace(swzAdvTitle); AddRow(pwd, TranslateT("XStatus:"), swzAdvTitle, pa->szModuleName, true, false, false); mir_free(swzAdvTitle); } // xstatus message TCHAR *swzAdvText = GetProtoExtraStatusMessage(pa->szModuleName); if (swzAdvText) { StripBBCodesInPlace(swzAdvText); AddRow(pwd, _T(""), swzAdvText, pa->szModuleName, true, true, false); mir_free(swzAdvText); } } } TCHAR *swzActTitle = GetJabberAdvStatusText(pa->szModuleName, "activity", "title"); if (swzActTitle) { StripBBCodesInPlace(swzActTitle); AddRow(pwd, TranslateT("Activity:"), swzActTitle, pa->szModuleName, true, false, false); mir_free(swzActTitle); } TCHAR *swzActText = GetJabberAdvStatusText(pa->szModuleName, "activity", "text"); if (swzActText) { StripBBCodesInPlace(swzActText); AddRow(pwd, _T(""), swzActText, pa->szModuleName, true, true, false); mir_free(swzActText); } } if (dwItems & TRAYTIP_LISTENINGTO) { TCHAR *swzListening = GetListeningTo(pa->szModuleName); if (swzListening) { StripBBCodesInPlace(swzListening); AddRow(pwd, TranslateT("Listening to:"), swzListening, NULL, false, true, false); mir_free(swzListening); } } } } if (dwItems & TRAYTIP_FAVCONTACTS) { if (DBGetContactSettingDword(0, MODULE, "FavouriteContactsCount", 0)) { TCHAR swzName[256]; TCHAR swzStatus[256]; bool bTitlePainted = false; int iCount = 0, iCountOnline = 0; HANDLE hContact = db_find_first(); while (hContact) { if (DBGetContactSettingByte(hContact, MODULE, "FavouriteContact", 0)) { char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); if (proto) { WORD wStatus = DBGetContactSettingWord(hContact, proto, "Status", ID_STATUS_OFFLINE); WordToStatusDesc(hContact, proto, "Status", swzStatus, 256); if (wStatus != ID_STATUS_OFFLINE) iCountOnline++; iCount++; if (!(opt.iFavoriteContFlags & FAVCONT_HIDE_OFFLINE && wStatus == ID_STATUS_OFFLINE)) { if (!bTitlePainted) { AddRow(pwd, TranslateT("Fav. contacts"), NULL, NULL, false, false, !bFirstItem, true, NULL); bFirstItem = false; bTitlePainted = true; } TCHAR *swzNick = (TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR); if (opt.iFavoriteContFlags & FAVCONT_APPEND_PROTO) { TCHAR *swzProto = a2t(proto); mir_sntprintf(swzName, 256, _T("%s (%s)"), swzNick, swzProto); mir_free(swzProto); } else { _tcscpy(swzName, swzNick); } AddRow(pwd, swzName, swzStatus, NULL, false, false, false); } } } hContact = db_find_next(hContact); } int index = pwd->iRowCount - 1; if (opt.iFavoriteContFlags & FAVCONT_HIDE_OFFLINE) index -= iCountOnline; else index -= iCount; if (index >= 0 && (dwItems & TRAYTIP_NUMCONTACTS) && !((opt.iFavoriteContFlags & FAVCONT_HIDE_OFFLINE) && iCountOnline == 0)) { mir_sntprintf(buff, 64, _T("(%d/%d)"), iCountOnline, iCount); pwd->rows[index].swzValue = mir_tstrdup(buff); } } } if (dwItems & TRAYTIP_MIRANDA_UPTIME) { if (TimestampToTimeDifference(NULL, MODULE, "MirandaStartTS", buff, 64)) { AddRow(pwd, TranslateT("Other"), _T(""), NULL, false, false, !bFirstItem, true, NULL); AddRow(pwd, TranslateT("Miranda uptime:"), buff, NULL, false, false, false); } } if (dwItems & TRAYTIP_CLIST_EVENT && pwd->clcit.swzText) { TCHAR *pchBr = _tcschr(pwd->clcit.swzText, '\n'); TCHAR *pchBold = _tcsstr(pwd->clcit.swzText, _T("")); if (!pchBold || pchBold != pwd->clcit.swzText) { TCHAR swzText[256]; _tcscpy(swzText, pwd->clcit.swzText); if (pchBr) swzText[pchBr - pwd->clcit.swzText] = 0; AddRow(pwd, swzText, _T(""), NULL, false, true, false, true, LoadSkinnedIcon(SKINICON_OTHER_FILLEDBLOB)); } } return TRUE; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); }