/* Miranda SmileyAdd Plugin Copyright (C) 2005 - 2011 Boris Krasnovskiy Copyright (C) 2003 - 2004 Rein-Peter de Boer 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 version 2 of the License. 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, see . */ #include "general.h" #include "smileyroutines.h" #include "SmileyBase.h" #include "options.h" #include #include ISmileyBase* CreateSmileyObject(SmileyType* sml); ISmileyBase* CreateAniSmileyObject(SmileyType* sml, COLORREF clr, bool ishpp); #ifdef _UNICODE bool g_HiddenTextSupported = true; #else int RichEditVersion(void); bool g_HiddenTextSupported = (RichEditVersion() == 3); #endif // {8CC497C0-A1DF-11CE-8098-00AA0047BE5D} const GUID IID_ITextDocument = { 0x8CC497C0, 0xA1DF, 0x11CE, { 0x80,0x98,0x00,0xAA,0x00,0x47,0xBE,0x5D } }; void LookupAllSmileys(SmileyPackType* smileyPack, SmileyPackCType* smileyCPack, const TCHAR* lpstrText, SmileysQueueType& smllist, const bool firstOnly) { if (lpstrText == NULL || *lpstrText == 0) return; SmileyPackType::SmileyLookupType* sml = smileyPack ? smileyPack->GetSmileyLookup() : NULL; SmileyPackCType::SmileyLookupType* smlc = smileyCPack ? &smileyCPack->GetSmileyLookup() : NULL; // Precompute number of smileys int smlszo = sml ? sml->getCount() : 0; int smlszc = smlc ? smlc->getCount() : 0; int smlsz = smlszo + smlszc; if (smlsz == 0) return; // All possible smileys SmileyLookup::SmileyLocVecType* smileys = new SmileyLookup::SmileyLocVecType [smlsz]; // Find all possible smileys bkstring tmpstr(lpstrText); int i = 0; if (sml) { for (int j=0; jgetCount(); j++) { (*sml)[j].find(tmpstr, smileys[i], false); i++; } } if (smlc) { for (int j=0; jgetCount(); j++) { (*smlc)[j].find(tmpstr, smileys[i], false); i++; } } int* csmlit = (int*)alloca(smlsz * sizeof(int)); if (csmlit == NULL) return; memset(csmlit, 0, smlsz * sizeof(int)); long numCharsSoFar = 0; bkstring::size_type smloff = 0; for(;;) { int firstSml = -1; int firstSmlRef = -1; SmileyLookup::SmileyLocVecType* smlf = NULL; for (int csml=0; csml= smloff) { if (firstSmlRef == -1 || smlv[tsml].pos < (*smlf)[firstSmlRef].pos || (smlv[tsml].pos == (*smlf)[firstSmlRef].pos && smlv[tsml].len > (*smlf)[firstSmlRef].len)) { firstSmlRef = tsml; firstSml = csml; smlf = &smileys[csml]; } break; } } csmlit[csml] = tsml; } // Check if smiley found if (firstSml != -1) { ReplaceSmileyType *dat = new ReplaceSmileyType; const TCHAR* textToSearch = lpstrText + smloff; const TCHAR* textSmlStart = lpstrText + (*smlf)[firstSmlRef].pos; const TCHAR* textSmlEnd = textSmlStart + (*smlf)[firstSmlRef].len; // check if leading space exist const TCHAR* prech = _tcsdec(textToSearch, textSmlStart); dat->ldspace = prech != NULL ? _istspace(*prech) != 0 : smloff == 0; // check if trailing space exist dat->trspace = *textSmlEnd == 0 || _istspace(*textSmlEnd); // compute text location in RichEdit dat->loc.cpMin = (long)_tcsnccnt(textToSearch, (*smlf)[firstSmlRef].pos - smloff) + numCharsSoFar; dat->loc.cpMax = numCharsSoFar = (long)_tcsnccnt(textSmlStart, (*smlf)[firstSmlRef].len) + dat->loc.cpMin; if (!opt.EnforceSpaces || (dat->ldspace && dat->trspace)) { dat->ldspace |= !opt.SurroundSmileyWithSpaces; dat->trspace |= !opt.SurroundSmileyWithSpaces; if (firstSml < smlszo) { dat->sml = smileyPack->GetSmiley((*sml)[firstSml].GetIndex()); dat->smlc = NULL; } else { dat->smlc = smileyCPack->GetSmiley((*smlc)[firstSml-smlszo].GetIndex()); dat->sml = NULL; } if (dat->sml != NULL || dat->smlc != NULL) { // First smiley found record it smllist.insert(dat); if (firstOnly) break; } else delete dat; } else delete dat; // Advance string pointer to search for the next smiley smloff = (*smlf)[firstSmlRef].pos + (*smlf)[firstSmlRef].len; csmlit[firstSml]++; } else // Nothing to parse exit break; } delete[] smileys; } void FindSmileyInText(SmileyPackType* smp, const TCHAR* str, unsigned& first, unsigned& size, SmileyType** sml) { SmileysQueueType smllist; LookupAllSmileys(smp, NULL, str, smllist, true); if (smllist.getCount() == 0) { size = 0; *sml = NULL; } else { first = smllist[0].loc.cpMin; size = smllist[0].loc.cpMax - smllist[0].loc.cpMin; *sml = smllist[0].sml; } } SmileyType* FindButtonSmiley(SmileyPackType* smp) { unsigned start, size; SmileyType* sml; FindSmileyInText(smp, smp->GetButtonSmiley(), start, size, &sml); return sml; } void UpdateSelection(CHARRANGE& sel, int pos, int dif) { if (sel.cpMax == sel.cpMin) { if (sel.cpMax < LONG_MAX && sel.cpMax > pos) { sel.cpMax += dif; sel.cpMin += dif; } } else { if (sel.cpMax >= pos && sel.cpMax < LONG_MAX) sel.cpMax += dif; if (sel.cpMin > pos) sel.cpMin += dif; } } void ReplaceSmileys(HWND hwnd, SmileyPackType* smp, SmileyPackCType* smcp, const CHARRANGE& sel, bool useHidden, bool ignoreLast, bool unFreeze) { /* LARGE_INTEGER freq, strt, end; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&strt); */ IRichEditOle* RichEditOle; if (SendMessage(hwnd, EM_GETOLEINTERFACE, 0, (LPARAM)&RichEditOle) == 0) return; ITextDocument* TextDocument; if (RichEditOle->QueryInterface(IID_ITextDocument, (void**)&TextDocument) != S_OK) { RichEditOle->Release(); return; } long cnt; if (smp == NULL && smcp == NULL) { if (unFreeze) TextDocument->Unfreeze(&cnt); TextDocument->Release(); RichEditOle->Release(); return; } // retrieve text range ITextRange* TextRange; if (TextDocument->Range(sel.cpMin, sel.cpMax, &TextRange) != S_OK) { TextDocument->Release(); RichEditOle->Release(); return; } // retrieve text to parse for smileys BSTR btxt = 0; if (TextRange->GetText(&btxt) != S_OK) { TextRange->Release(); TextDocument->Release(); RichEditOle->Release(); return; } TextRange->Release(); SmileysQueueType smllist; #if !defined(_UNICODE) && !defined(UNICODE) size_t len = SysStringLen(btxt); for (unsigned i=0; iFreeze(&cnt); TCHAR classname[20]; GetClassName(hwnd, classname, SIZEOF(classname)); bool ishpp = (_tcsncmp(classname, _T("THppRichEdit"), 12) == 0); SetRichCallback(hwnd, NULL, false, true); bool rdo = (GetWindowLong(hwnd, GWL_STYLE) & ES_READONLY) != 0; if (rdo) SendMessage(hwnd, EM_SETREADONLY, FALSE, 0); ITextSelection* TextSelection; TextDocument->GetSelection(&TextSelection); ITextFont *TextFont; TextSelection->GetFont(&TextFont); //save selection CHARRANGE oldSel; TextSelection->GetStart(&oldSel.cpMin); TextSelection->GetEnd(&oldSel.cpMax); CHARFORMAT2 chf; chf.cbSize = sizeof(chf); chf.dwMask = CFM_ALL2; // Determine background color // This logic trying to minimize number of background color changes static COLORREF bkgColor = GetSysColor(COLOR_WINDOW); // if (!insemf) // { COLORREF bkgColorPv = (COLORREF)SendMessage(hwnd, EM_SETBKGNDCOLOR, 0, bkgColor); if (bkgColorPv != bkgColor) { bkgColor = bkgColorPv; SendMessage(hwnd, EM_SETBKGNDCOLOR, 0, bkgColor); } // } HDC hdc = GetDC(hwnd); int sclX = GetDeviceCaps(hdc, LOGPIXELSX); int sclY = GetDeviceCaps(hdc, LOGPIXELSY); unsigned numBTBSm = 0; BSTR spaceb = SysAllocString(L" "); // Replace smileys specified in the list in RichEdit for (int j = smllist.getCount(); j--; ) { CHARRANGE& smlpos = smllist[j].loc; if (ignoreLast && oldSel.cpMax == smlpos.cpMax) continue; smlpos.cpMin += sel.cpMin; smlpos.cpMax += sel.cpMin; // Find all back to back smileys and for propper hidden text detection if ( numBTBSm == 0 ) { CHARRANGE lastPos = smlpos; for (int jn = j; jn--; ) { if (jn != j && smllist[jn].loc.cpMax != lastPos.cpMin) break; ++numBTBSm; lastPos.cpMin = smllist[jn].loc.cpMin; } TextSelection->SetRange(lastPos.cpMin, lastPos.cpMax); long hid; TextFont->GetHidden(&hid); if (hid == tomFalse) numBTBSm = 0; } if ( numBTBSm != 0 ) { --numBTBSm; continue; } SmileyType* sml = smllist[j].sml; SmileyCType* smlc = smllist[j].smlc; if (sml == NULL && smlc == NULL) continue; // Select text analyze TextSelection->SetRange(smlpos.cpMin, smlpos.cpMax); BSTR btxt = NULL; if (smlc == NULL && sml->IsText()) { btxt = SysAllocString(T2W_SM(sml->GetToolText().c_str())); TextSelection->SetText(btxt); } else { TextSelection->GetText(&btxt); // Get font properties SendMessage(hwnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&chf); //do not look for smileys in hyperlinks if ((chf.dwEffects & (CFE_LINK | CFE_HIDDEN)) != 0) continue; SIZE osize; if (sml) sml->GetSize(osize); else smlc->GetSize(osize); if (osize.cx == 0 || osize.cy == 0) continue; int sizeX, sizeY; if (opt.ScaleToTextheight) { sizeY = CalculateTextHeight(hdc, &chf); sizeX = osize.cx * sizeY / osize.cy; int dx = osize.cx - sizeX; sizeX += dx & 1; int dy = osize.cy - sizeY; sizeY += dy & 1; } else { sizeX = osize.cx; sizeY = osize.cy; } if (smlc != NULL && opt.MaxCustomSmileySize && (unsigned)sizeY > opt.MaxCustomSmileySize) { sizeY = opt.MaxCustomSmileySize; sizeX = osize.cx * sizeY / osize.cy; int dx = osize.cx - sizeX; sizeX += dx & 1; int dy = osize.cy - opt.MaxCustomSmileySize; sizeY += dy & 1; } if (opt.MinSmileySize && (unsigned)sizeY < opt.MinSmileySize) { sizeY = opt.MinSmileySize; sizeX = osize.cx * sizeY / osize.cy; int dx = osize.cx - sizeX; sizeX += dx & 1; int dy = osize.cy - opt.MinSmileySize; sizeY += dy & 1; } // Convert pixel to HIMETRIC SIZEL sizehm; sizehm.cx = (2540 * (sizeX+1) + (sclX >> 1)) / sclX; sizehm.cy = (2540 * (sizeY+1) + (sclY >> 1)) / sclY; // If font does not have designated background use control background if (chf.dwEffects & CFE_AUTOBACKCOLOR) chf.crBackColor = bkgColor; // insert space after if (!smllist[j].trspace && useHidden) { TextSelection->SetStart(smlpos.cpMax); TextSelection->TypeText(spaceb); UpdateSelection(oldSel, smlpos.cpMax , 1); // Restore selection TextSelection->SetRange(smlpos.cpMin, smlpos.cpMax); } if (g_HiddenTextSupported && useHidden) { TextFont->SetHidden(tomTrue); TextSelection->SetEnd(smlpos.cpMin); UpdateSelection(oldSel, smlpos.cpMin , 1); } else { UpdateSelection(oldSel, smlpos.cpMin, -(int)SysStringLen(btxt)+1); } ISmileyBase* smileyBase = CreateAniSmileyObject(smlc ? smlc : sml, chf.crBackColor, ishpp); if (smileyBase == NULL) continue; smileyBase->SetExtent(DVASPECT_CONTENT, &sizehm); smileyBase->SetHint(W2T_SM(btxt)); smileyBase->SetPosition(hwnd, NULL); // Get the RichEdit container site IOleClientSite *pOleClientSite; RichEditOle->GetClientSite(&pOleClientSite); // Now Add the object to the RichEdit REOBJECT reobject = { 0 }; reobject.cbStruct = sizeof(REOBJECT); reobject.cp = REO_CP_SELECTION; reobject.dvaspect = DVASPECT_CONTENT; reobject.poleobj = smileyBase; reobject.polesite = pOleClientSite; reobject.dwFlags = REO_BELOWBASELINE | REO_BLANK; reobject.dwUser = (DWORD)smileyBase; // Insert the bitmap at the current location in the richedit control RichEditOle->InsertObject(&reobject); smileyBase->Release(); // insert space before if (!smllist[j].ldspace && useHidden) { TextSelection->SetRange(smlpos.cpMin, smlpos.cpMin); TextSelection->TypeText(spaceb); UpdateSelection(oldSel, smlpos.cpMin , 1); } } SysFreeString(btxt); } SysFreeString(spaceb); TextSelection->SetRange(oldSel.cpMin, oldSel.cpMax); if (rdo) SendMessage(hwnd, EM_SETREADONLY, TRUE, 0); TextFont->Release(); TextSelection->Release(); ReleaseDC(hwnd, hdc); TextDocument->Unfreeze(&cnt); if (cnt == 0) UpdateWindow(hwnd); } if (unFreeze) { TextDocument->Unfreeze(&cnt); if (cnt == 0) UpdateWindow(hwnd); } TextDocument->Release(); RichEditOle->Release(); /* QueryPerformanceCounter(&end); unsigned dif = (end.QuadPart - strt.QuadPart)/(freq.QuadPart/1000); TCHAR mess[300]; wsprintf(mess, _T("Time elapsed: %u"), dif); MessageBox(NULL, mess, _T(""), MB_OK); */ } void ReplaceSmileysWithText(HWND hwnd, CHARRANGE& sel, bool keepFrozen) { IRichEditOle* RichEditOle; if (SendMessage(hwnd, EM_GETOLEINTERFACE, 0, (LPARAM)&RichEditOle) == 0) return; ITextDocument* TextDocument; if (RichEditOle->QueryInterface(IID_ITextDocument, (void**)&TextDocument) != S_OK) { RichEditOle->Release(); return; } // retrieve text range ITextRange* TextRange; if (TextDocument->Range(0, 0, &TextRange) != S_OK) { TextDocument->Release(); RichEditOle->Release(); return; } long cnt; TextDocument->Freeze(&cnt); bool rdo = (GetWindowLong(hwnd, GWL_STYLE) & ES_READONLY) != 0; if (rdo) SendMessage(hwnd, EM_SETREADONLY, FALSE, 0); CHARRANGE oldSel; SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&oldSel); int objectCount = RichEditOle->GetObjectCount(); for (int i = objectCount - 1; i >= 0; i--) { REOBJECT reObj = {0}; reObj.cbStruct = sizeof(REOBJECT); HRESULT hr = RichEditOle->GetObject(i, &reObj, REO_GETOBJ_POLEOBJ); if (FAILED(hr)) continue; if (reObj.cp < sel.cpMin) { reObj.poleobj->Release(); break; } ISmileyBase *igsc = NULL; if (reObj.cp < sel.cpMax && reObj.clsid == CLSID_NULL) reObj.poleobj->QueryInterface(IID_ISmileyAddSmiley, (void**) &igsc); reObj.poleobj->Release(); if (igsc == NULL) continue; TextRange->SetRange(reObj.cp, reObj.cp + 1); BSTR bstr = NULL; igsc->GetTooltip(&bstr); TextRange->SetText(bstr); unsigned int len = SysStringLen(bstr); UpdateSelection(oldSel, reObj.cp, len-1); UpdateSelection(sel, reObj.cp, len-1); SysFreeString(bstr); igsc->Release(); } SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&oldSel); if (rdo) SendMessage(hwnd, EM_SETREADONLY, TRUE, 0); if (!keepFrozen) TextDocument->Unfreeze(&cnt); TextRange->Release(); TextDocument->Release(); RichEditOle->Release(); } #if !defined(_UNICODE) && !defined(UNICODE) int RichEditVersion(void) { int ret = -1; HMODULE hLib = GetModuleHandle(_T("riched20.dll")); HRSRC hVersion = FindResource(hLib, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); if (hVersion != NULL) { HGLOBAL hGlobal = LoadResource(hLib, hVersion); if (hGlobal != NULL) { LPVOID versionInfo = LockResource(hGlobal); if (versionInfo != NULL) { int vl = *(unsigned short*)versionInfo; unsigned *res = (unsigned*)versionInfo; while (*res != 0xfeef04bd && ((char*)res - (char*)versionInfo) < vl) ++res; if (((char*)res - (char*)versionInfo) < vl) { VS_FIXEDFILEINFO *vsInfo = (VS_FIXEDFILEINFO*)res; ret = LOWORD(vsInfo->dwFileVersionMS) ? 3 : 2; } } UnlockResource(hGlobal); FreeResource(hGlobal); } } return ret; } #endif