//////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // // Copyright (C) 2012-24 Miranda NG team, // Copyright (c) 2000-09 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. // // part of tabSRMM messaging plugin for Miranda. // // (C) 2005-2010 by silvercircle _at_ gmail _dot_ com and contributors // // generic utility functions #include "stdafx.h" OBJLIST Utils::rtf_clrs(10); ///////////////////////////////////////////////////////////////////////////////////////// char* Utils::FilterEventMarkers(char *szText) { for (char *p = strstr(szText, "~-+"); p != nullptr; p = strstr(p, "~-+")) { char *pEnd = strstr(p + 3, "+-~"); if (pEnd == nullptr) break; strdel(p, (pEnd - p) + 3); } return szText; } wchar_t* Utils::FilterEventMarkers(wchar_t *wszText) { for (wchar_t *p = wcsstr(wszText, L"~-+"); p != nullptr; p = wcsstr(p, L"~-+")) { wchar_t *pEnd = wcsstr(p + 3, L"+-~"); if (pEnd == nullptr) break; strdelw(p, (pEnd - p) + 3); } return wszText; } ///////////////////////////////////////////////////////////////////////////////////////// void Utils::DoubleAmpersands(wchar_t *pszText, size_t len) { CMStringW text(pszText); text.Replace(L"&", L"&&"); mir_wstrncpy(pszText, text.c_str(), len); } ///////////////////////////////////////////////////////////////////////////////////////// // Get a preview of the text with an ellipsis appended(...) // // @param szText source text // @param iMaxLen max length of the preview // @return wchar_t* result (caller must mir_free() it) wchar_t* Utils::GetPreviewWithEllipsis(wchar_t *szText, size_t iMaxLen) { size_t uRequired; wchar_t *p = nullptr, cSaved; bool fEllipsis = false; if (mir_wstrlen(szText) <= iMaxLen) { uRequired = mir_wstrlen(szText) + 4; cSaved = 0; } else { p = &szText[iMaxLen - 1]; fEllipsis = true; while (p >= szText && *p != ' ') p--; if (p == szText) p = szText + iMaxLen - 1; cSaved = *p; *p = 0; uRequired = (p - szText) + 6; } wchar_t *szResult = reinterpret_cast(mir_alloc((uRequired + 1) * sizeof(wchar_t))); mir_snwprintf(szResult, (uRequired + 1), fEllipsis ? L"%s..." : L"%s", szText); if (p) *p = cSaved; return szResult; } ///////////////////////////////////////////////////////////////////////////////////////// // init default color table. the table may grow when using custom colors via bbcodes static const wchar_t *sttColorNames[] = { L"black", L"", L"", L"", L"", L"", L"", L"", L"", L"blue", L"cyan", L"magenta", L"green", L"yellow", L"red", L"white" }; void Utils::RTF_CTableInit() { int iTableSize; COLORREF *pTable = Srmm_GetColorTable(&iTableSize); for (int i = 0; i < iTableSize; i++) rtf_clrs.insert(new TRTFColorTable(sttColorNames[i], pTable[i])); } ///////////////////////////////////////////////////////////////////////////////////////// // add a color to the global rtf color table void Utils::RTF_ColorAdd(const wchar_t *tszColname) { wchar_t *stopped; COLORREF clr = wcstol(tszColname, &stopped, 16); rtf_clrs.insert(new TRTFColorTable(tszColname, RGB(GetBValue(clr), GetGValue(clr), GetRValue(clr)))); } ///////////////////////////////////////////////////////////////////////////////////////// // generic error popup dialog procedure LRESULT CALLBACK Utils::PopupDlgProcError(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { MCONTACT hContact = PUGetContact(hWnd); switch (message) { case WM_COMMAND: case WM_CONTEXTMENU: PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_HANDLECLISTEVENT, hContact, 0); PUDeletePopup(hWnd); break; case WM_MOUSEWHEEL: break; case WM_SETCURSOR: break; } return DefWindowProc(hWnd, message, wParam, lParam); } ///////////////////////////////////////////////////////////////////////////////////////// // read a blob from db into the container settings structure // @param hContact: contact handle (0 = read global) // @param cs TContainerSettings* target structure // @return 0 on success, 1 failure (blob does not exist OR is not a valid private setting structure struct TOldContainerSettings { BOOL fPrivate; uint32_t dwFlags; uint32_t dwFlagsEx; uint32_t dwTransparency; uint32_t panelheight; int iSplitterY; wchar_t szTitleFormat[32]; uint16_t avatarMode; uint16_t ownAvatarMode; uint16_t autoCloseSeconds; uint8_t reserved[10]; }; int Utils::ReadContainerSettingsFromDB(const MCONTACT hContact, TContainerSettings *cs, const char *szKey) { CMStringA szSetting(szKey ? szKey : CNT_KEYNAME); int iSplitterX = db_get_dw(0, SRMSGMOD_T, szSetting + "_SplitterX", -1); if (iSplitterX == -1) { // nothing? try the old format DBVARIANT dbv = { 0 }; if (0 == db_get(hContact, SRMSGMOD_T, szSetting + "_Blob", &dbv)) { TOldContainerSettings oldBin = {}; if (dbv.type == DBVT_BLOB && dbv.cpbVal > 0 && dbv.cpbVal <= sizeof(oldBin)) { ::memcpy(&oldBin, (void*)dbv.pbVal, dbv.cpbVal); cs->flags.dw = oldBin.dwFlags; cs->flagsEx.dw = oldBin.dwFlagsEx; cs->dwTransparency = oldBin.dwTransparency; cs->panelheight = oldBin.panelheight; cs->iSplitterY = oldBin.iSplitterY; cs->iSplitterX = 35; wcsncpy_s(cs->szTitleFormat, oldBin.szTitleFormat, _TRUNCATE); cs->avatarMode = oldBin.avatarMode; cs->ownAvatarMode = oldBin.ownAvatarMode; cs->autoCloseSeconds = oldBin.autoCloseSeconds; cs->fPrivate = oldBin.fPrivate != 0; Utils::WriteContainerSettingsToDB(hContact, cs, szKey); db_unset(hContact, SRMSGMOD_T, szSetting); ::db_free(&dbv); return 0; } } cs->fPrivate = false; db_free(&dbv); return 1; } cs->flags.dw = db_get_dw(hContact, SRMSGMOD_T, szSetting + "_Flags", 0); cs->flagsEx.dw = db_get_dw(hContact, SRMSGMOD_T, szSetting + "_FlagsEx", 0); cs->dwTransparency = db_get_dw(hContact, SRMSGMOD_T, szSetting + "_Transparency", 0); cs->panelheight = db_get_dw(hContact, SRMSGMOD_T, szSetting + "_PanelY", 0); cs->iSplitterX = iSplitterX; cs->iSplitterY = db_get_dw(hContact, SRMSGMOD_T, szSetting + "_SplitterY", 0); cs->avatarMode = db_get_w(hContact, SRMSGMOD_T, szSetting + "_AvatarMode", 0); cs->ownAvatarMode = db_get_w(hContact, SRMSGMOD_T, szSetting + "_OwnAvatarMode", 0); cs->autoCloseSeconds = db_get_w(hContact, SRMSGMOD_T, szSetting + "_AutoCloseSecs", 0); cs->fPrivate = db_get_b(hContact, SRMSGMOD_T, szSetting + "_Private", 0) != 0; db_get_wstatic(hContact, SRMSGMOD_T, szSetting + "_Format", cs->szTitleFormat, _countof(cs->szTitleFormat)); return 0; } int Utils::WriteContainerSettingsToDB(const MCONTACT hContact, TContainerSettings *cs, const char *szKey) { CMStringA szSetting(szKey ? szKey : CNT_KEYNAME); db_set_dw(hContact, SRMSGMOD_T, szSetting + "_Flags", cs->flags.dw); db_set_dw(hContact, SRMSGMOD_T, szSetting + "_FlagsEx", cs->flagsEx.dw); db_set_dw(hContact, SRMSGMOD_T, szSetting + "_Transparency", cs->dwTransparency); db_set_dw(hContact, SRMSGMOD_T, szSetting + "_PanelY", cs->panelheight); db_set_dw(hContact, SRMSGMOD_T, szSetting + "_SplitterX", cs->iSplitterX); db_set_dw(hContact, SRMSGMOD_T, szSetting + "_SplitterY", cs->iSplitterY); db_set_ws(hContact, SRMSGMOD_T, szSetting + "_Format", cs->szTitleFormat); db_set_w(hContact, SRMSGMOD_T, szSetting + "_AvatarMode", cs->avatarMode); db_set_w(hContact, SRMSGMOD_T, szSetting + "_OwnAvatarMode", cs->ownAvatarMode); db_set_w(hContact, SRMSGMOD_T, szSetting + "_AutoCloseSecs", cs->autoCloseSeconds); db_set_b(hContact, SRMSGMOD_T, szSetting + "_Private", cs->fPrivate); return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // calculate new width and height values for a user picture (avatar) // // @param: maxHeight - determines maximum height for the picture, width will // be scaled accordingly. void Utils::scaleAvatarHeightLimited(const HBITMAP hBm, double& dNewWidth, double& dNewHeight, LONG maxHeight) { BITMAP bm; double dAspect; GetObject(hBm, sizeof(bm), &bm); if (bm.bmHeight > bm.bmWidth) { if (bm.bmHeight > 0) dAspect = (double)(maxHeight) / (double)bm.bmHeight; else dAspect = 1.0; dNewWidth = (double)bm.bmWidth * dAspect; dNewHeight = (double)maxHeight; } else { if (bm.bmWidth > 0) dAspect = (double)(maxHeight) / (double)bm.bmWidth; else dAspect = 1.0; dNewHeight = (double)bm.bmHeight * dAspect; dNewWidth = (double)maxHeight; } } AVATARCACHEENTRY* Utils::loadAvatarFromAVS(const MCONTACT hContact) { if (!ServiceExists(MS_AV_GETAVATARBITMAP)) return nullptr; return (AVATARCACHEENTRY*)CallService(MS_AV_GETAVATARBITMAP, hContact, 0); } void Utils::sendContactMessage(MCONTACT hContact, UINT uMsg, WPARAM wParam, LPARAM lParam) { HWND h = Srmm_FindWindow(hContact); if (h != nullptr) PostMessage(h, uMsg, wParam, lParam); } void Utils::getIconSize(HICON hIcon, int& sizeX, int& sizeY) { ICONINFO ii; BITMAP bm; ::GetIconInfo(hIcon, &ii); ::GetObject(ii.hbmColor, sizeof(bm), &bm); sizeX = bm.bmWidth; sizeY = bm.bmHeight; ::DeleteObject(ii.hbmMask); ::DeleteObject(ii.hbmColor); } ///////////////////////////////////////////////////////////////////////////////////////// // add a menu item to a ownerdrawn menu. mii must be pre-initialized // // @param m menu handle // @param mii menu item info structure // @param hIcon the icon (0 allowed -> no icon) // @param szText menu item text (must NOT be 0) // @param uID the item command id // @param pos zero-based position index void Utils::addMenuItem(const HMENU& m, MENUITEMINFO &mii, HICON hIcon, const wchar_t *szText, UINT uID, UINT pos) { mii.wID = uID; mii.dwItemData = (ULONG_PTR)hIcon; mii.dwTypeData = const_cast(szText); mii.cch = (int)mir_wstrlen(mii.dwTypeData) + 1; ::InsertMenuItem(m, pos, TRUE, &mii); } ///////////////////////////////////////////////////////////////////////////////////////// // enable or disable a dialog control void Utils::enableDlgControl(const HWND hwnd, UINT id, bool fEnable) { ::EnableWindow(::GetDlgItem(hwnd, id), fEnable); } ///////////////////////////////////////////////////////////////////////////////////////// // show or hide a dialog control void Utils::showDlgControl(const HWND hwnd, UINT id, int showCmd) { ::ShowWindow(::GetDlgItem(hwnd, id), showCmd); } ///////////////////////////////////////////////////////////////////////////////////////// // stream function to write the contents of the message log to an rtf file uint32_t CALLBACK Utils::StreamOut(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb) { wchar_t *szFilename = (wchar_t*)dwCookie; HANDLE hFile = CreateFile(szFilename, GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile != INVALID_HANDLE_VALUE) { SetFilePointer(hFile, 0, nullptr, FILE_END); FilterEventMarkers((char*)pbBuff); WriteFile(hFile, pbBuff, cb, (DWORD *)pcb, nullptr); *pcb = cb; CloseHandle(hFile); return 0; } return 1; } ///////////////////////////////////////////////////////////////////////////////////////// // extract a resource from the given module // tszPath must end with \ bool Utils::extractResource(const HMODULE h, const UINT uID, const wchar_t *tszName, const wchar_t *tszPath, const wchar_t *tszFilename, bool fForceOverwrite) { HRSRC hRes = FindResource(h, MAKEINTRESOURCE(uID), tszName); if (hRes) { HGLOBAL hResource = LoadResource(h, hRes); if (hResource) { char *pData = (char *)LockResource(hResource); DWORD dwSize = SizeofResource(g_plugin.getInst(), hRes), written = 0; wchar_t szFilename[MAX_PATH]; mir_snwprintf(szFilename, L"%s%s", tszPath, tszFilename); if (!fForceOverwrite) if (PathFileExists(szFilename)) return true; HANDLE hFile = CreateFile(szFilename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) return false; WriteFile(hFile, (void*)pData, dwSize, &written, nullptr); CloseHandle(hFile); } } return true; } ///////////////////////////////////////////////////////////////////////////////////////// // extract the clicked URL from a rich edit control. Return the URL as wchar_t* // caller MUST mir_free() the returned string // @param hwndRich - rich edit window handle // @return wchar_t* extracted URL wchar_t* Utils::extractURLFromRichEdit(const ENLINK* _e, const HWND hwndRich) { CHARRANGE sel = { 0 }; ::SendMessage(hwndRich, EM_EXGETSEL, 0, (LPARAM)&sel); if (sel.cpMin != sel.cpMax) return nullptr; TEXTRANGE tr; tr.chrg = _e->chrg; tr.lpstrText = (wchar_t*)mir_alloc(sizeof(wchar_t) * (tr.chrg.cpMax - tr.chrg.cpMin + 8)); ::SendMessage(hwndRich, EM_GETTEXTRANGE, 0, (LPARAM)&tr); if (wcschr(tr.lpstrText, '@') != nullptr && wcschr(tr.lpstrText, ':') == nullptr && wcschr(tr.lpstrText, '/') == nullptr) { mir_wstrncpy(tr.lpstrText, L"mailto:", 7); mir_wstrncpy(tr.lpstrText + 7, tr.lpstrText, tr.chrg.cpMax - tr.chrg.cpMin + 1); } return tr.lpstrText; } ///////////////////////////////////////////////////////////////////////////////////////// // filters out invalid characters from a string used as part of a file // or folder name. All invalid characters will be replaced by spaces. // // @param tszFilename - string to filter. void Utils::sanitizeFilename(wchar_t* tszFilename) { static wchar_t *forbiddenCharacters = L"%/\\':|\"<>?"; static size_t forbiddenCharactersLen = mir_wstrlen(forbiddenCharacters); for (size_t i = 0; i < forbiddenCharactersLen; i++) { wchar_t* szFound = nullptr; while ((szFound = wcschr(tszFilename, (int)forbiddenCharacters[i])) != nullptr) *szFound = ' '; } } ///////////////////////////////////////////////////////////////////////////////////////// // ensure that a path name ends on a trailing backslash // @param szPathname - pathname to check void Utils::ensureTralingBackslash(wchar_t *szPathname) { if (szPathname[mir_wstrlen(szPathname) - 1] != '\\') mir_wstrcat(szPathname, L"\\"); } ///////////////////////////////////////////////////////////////////////////////////////// // load a system library from the Windows system path and return its module // handle. // // return 0 and throw an exception if something goes wrong. HMODULE Utils::loadSystemLibrary(const wchar_t* szFilename) { wchar_t sysPathName[MAX_PATH + 2]; if (0 == ::GetSystemDirectoryW(sysPathName, MAX_PATH)) return nullptr; sysPathName[MAX_PATH - 1] = 0; if (mir_wstrlen(sysPathName) + mir_wstrlen(szFilename) >= MAX_PATH) return nullptr; mir_wstrcat(sysPathName, szFilename); HMODULE _h = LoadLibraryW(sysPathName); if (nullptr == _h) return nullptr; return _h; } ///////////////////////////////////////////////////////////////////////////////////////// // setting avatar's contact void Utils::setAvatarContact(HWND hWnd, MCONTACT hContact) { MCONTACT hSub = db_mc_getSrmmSub(hContact); SendMessage(hWnd, AVATAR_SETCONTACT, 0, (hSub) ? hSub : hContact); } ///////////////////////////////////////////////////////////////////////////////////////// HWND TSAPI GetTabWindow(HWND hwndTab, int i) { if (i < 0) return nullptr; TCITEM tci; tci.mask = TCIF_PARAM; return (TabCtrl_GetItem(hwndTab, i, &tci)) ? (HWND)tci.lParam : nullptr; } ///////////////////////////////////////////////////////////////////////////////////////// // file list handler int _DebugTraceW(const wchar_t *fmt, ...) { wchar_t debug[2048]; int ibsize = 2047; SYSTEMTIME st; va_list va; char tszTime[50]; va_start(va, fmt); GetLocalTime(&st); mir_snprintf(tszTime, "%02d.%02d.%04d - %02d:%02d:%02d.%04d: ", st.wDay, st.wMonth, st.wYear, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); mir_vsnwprintf(debug, ibsize - 10, fmt, va); //#ifdef _DEBUG OutputDebugStringW(debug); //#else { char szLogFileName[MAX_PATH], szDataPath[MAX_PATH]; FILE *f; Profile_GetPathA(MAX_PATH, szDataPath); mir_snprintf(szLogFileName, "%s\\%s", szDataPath, "tabsrmm_debug.log"); f = fopen(szLogFileName, "a+"); if (f) { fputs(tszTime, f); fputs(T2Utf(debug), f); fputs("\n", f); fclose(f); } } //#endif return 0; } /* * 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. */ int _DebugPopup(MCONTACT hContact, const wchar_t *fmt, ...) { va_list va; wchar_t debug[1024]; int ibsize = 1023; va_start(va, fmt); mir_vsnwprintf(debug, ibsize, fmt, va); wchar_t szTitle[128]; mir_snwprintf(szTitle, TranslateT("TabSRMM message (%s)"), (hContact != 0) ? Clist_GetContactDisplayName(hContact) : TranslateT("Global")); Clist_TrayNotifyW(nullptr, szTitle, debug, NIIF_INFO, 1000 * 4); return 0; }