/* Miranda SmileyAdd Plugin Copyright (C) 2003 - 2004 Rein-Peter de Boer Copyright (C) 2005 - 2011 Boris Krasnovskiy 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 "stdafx.h" #include ISmileyBase* CreateAniSmileyObject(SmileyType *sml, COLORREF clr, bool ishpp); bool g_HiddenTextSupported = true; // {8CC497C0-A1DF-11CE-8098-00AA0047BE5D} const GUID IID_ITextDocument = { 0x8CC497C0, 0xA1DF, 0x11CE, { 0x80,0x98,0x00,0xAA,0x00,0x47,0xBE,0x5D } }; static int CompareSmileys(const SmileyLookup::SmileyLocType *p1, const SmileyLookup::SmileyLocType *p2) { // length is sorted in reverse order, the bigger one goes first if (p1->pos == p2->pos) return (int)p2->len - (int)p1->len; return (int)p1->pos - (int)p2->pos; } static bool HasOverlap(const CHARRANGE &loc, SmileysQueueType &smllist) { for (auto &it : smllist) if (it->loc.cpMin <= loc.cpMin && it->loc.cpMax >= loc.cpMax) return true; return false; } static void LookupAllSmileysWorker( SmileyPackType *smileyPack, SmileyPackCType *smileyCPack, const wchar_t *lpstrText, SmileysQueueType &smllist, const bool firstOnly) { if (lpstrText == nullptr || *lpstrText == 0) return; // All possible smileys OBJLIST smileys(20, CompareSmileys); // Find all possible smileys CMStringW tmpstr(lpstrText); if (smileyPack) for (auto &it : smileyPack->GetSmileyLookup()) it->Find(tmpstr, smileys, false); if (smileyCPack) for (auto &it : smileyCPack->GetSmileyLookup()) it->Find(tmpstr, smileys, false); if (smileys.getCount() == 0) return; // remove overlapped smileys, if any for (int i = 0; i < smileys.getCount() - 1; ) { auto &p = smileys[i]; if (p.pos + p.len > smileys[i + 1].pos) smileys.remove(i + 1); else i++; } long numCharsSoFar = 0; size_t smloff = 0; for (int i = 0; i < smileys.getCount(); i++) { auto &pCurr = smileys[i]; if (pCurr.pos < smloff) continue; const wchar_t *textToSearch = lpstrText + smloff; const wchar_t *textSmlStart = lpstrText + pCurr.pos; const wchar_t *textSmlEnd = textSmlStart + pCurr.len; ReplaceSmileyType dat; // check if leading space exist const wchar_t *prech = _wcsdec(textToSearch, textSmlStart); dat.ldspace = prech != nullptr ? iswspace(*prech) != 0 : smloff == 0; if (i > 0 && smileys[i - 1].pos + smileys[i - 1].len == smloff) dat.ldspace = true; // check if trailing space exist dat.trspace = *textSmlEnd == 0 || iswspace(*textSmlEnd); if (i < smileys.getCount() - 1 && pCurr.pos + pCurr.len == smileys[i + 1].pos) dat.trspace = true; // compute text location in RichEdit dat.loc.cpMin = (long)_wcsncnt(textToSearch, pCurr.pos - smloff) + numCharsSoFar; dat.loc.cpMax = numCharsSoFar = (long)_wcsncnt(textSmlStart, pCurr.len) + dat.loc.cpMin; if (!HasOverlap(dat.loc, smllist)) { if (!opt.EnforceSpaces || (dat.ldspace && dat.trspace)) { dat.ldspace |= !opt.SurroundSmileyWithSpaces; dat.trspace |= !opt.SurroundSmileyWithSpaces; if (smileyCPack && smileyCPack->GetSmileyLookup().find(pCurr.sml)) { dat.smlc = smileyCPack->GetSmiley(pCurr.sml->GetIndex()); dat.sml = nullptr; } else { dat.sml = smileyPack->GetSmiley(pCurr.sml->GetIndex()); dat.smlc = nullptr; } if (dat.sml != nullptr || dat.smlc != nullptr) { // First smiley found record it smllist.insert(new ReplaceSmileyType(dat)); if (firstOnly) return; } } } // Advance string pointer to search for the next smiley smloff = int(pCurr.pos + pCurr.len); } } void LookupAllSmileys(SmileyPackType *smileyPack, SmileyPackCType *smileyCPack, const wchar_t *lpstrText, SmileysQueueType &smllist, bool firstOnly) { LookupAllSmileysWorker(smileyPack, smileyCPack, lpstrText, smllist, firstOnly); if (g_pEmoji && smileyPack != g_pEmoji) LookupAllSmileysWorker(g_pEmoji, smileyCPack, lpstrText, smllist, firstOnly); } ///////////////////////////////////////////////////////////////////////////////////////// SmileyType* FindButtonSmiley(SmileyPackType *smp) { SmileysQueueType smllist; LookupAllSmileys(smp, nullptr, smp->GetButtonSmiley(), smllist, true); return (smllist.getCount() == 0) ? nullptr : smllist[0].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, bool fireView) { CComPtr RichEditOle; if (SendMessage(hwnd, EM_GETOLEINTERFACE, 0, (LPARAM)&RichEditOle) == 0) return; if (RichEditOle == nullptr) return; CComPtr TextDocument; if (RichEditOle->QueryInterface(IID_ITextDocument, (void **)&TextDocument) != S_OK) return; long cnt; if (smp == nullptr && smcp == nullptr) { if (unFreeze) TextDocument->Unfreeze(&cnt); return; } // retrieve text range CComPtr TextRange; if (TextDocument->Range(sel.cpMin, sel.cpMax, &TextRange) != S_OK) return; // retrieve text to parse for smileys BSTR btxt = nullptr; if (TextRange->GetText(&btxt) != S_OK) return; HDC hdc = GetDC(hwnd); if (hdc == nullptr) return; SmileysQueueType smllist; LookupAllSmileys(smp, smcp, btxt, smllist, false); SysFreeString(btxt); if (smllist.getCount() != 0) { // disable screen updates TextDocument->Freeze(&cnt); wchar_t classname[20]; GetClassName(hwnd, classname, _countof(classname)); bool ishpp = (wcsncmp(classname, L"THppRichEdit", 12) == 0) || fireView; SetRichCallback(hwnd, 0, false, true); bool rdo = (GetWindowLongPtr(hwnd, GWL_STYLE) & ES_READONLY) != 0; if (rdo) SendMessage(hwnd, EM_SETREADONLY, FALSE, 0); CComPtr TextSelection; TextDocument->GetSelection(&TextSelection); CComPtr 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 COLORREF bkgColor = GetSysColor(COLOR_WINDOW); COLORREF bkgColorPv = (COLORREF)SendMessage(hwnd, EM_SETBKGNDCOLOR, 0, bkgColor); if (bkgColorPv != bkgColor) { bkgColor = bkgColorPv; SendMessage(hwnd, EM_SETBKGNDCOLOR, 0, bkgColor); } 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() - 1; j >= 0; 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 == nullptr && smlc == nullptr) continue; // Select text analyze TextSelection->SetRange(smlpos.cpMin, smlpos.cpMax); BSTR bstrText = nullptr; if (smlc == nullptr && sml->IsText()) { bstrText = SysAllocString(sml->GetToolText().c_str()); TextSelection->SetText(bstrText); } else { TextSelection->GetText(&bstrText); // 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 != nullptr && 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(bstrText) + 1); ISmileyBase *smileyBase = CreateAniSmileyObject(smlc ? smlc : sml, chf.crBackColor, ishpp); if (smileyBase == nullptr) continue; smileyBase->SetExtent(DVASPECT_CONTENT, &sizehm); smileyBase->SetHint(bstrText); smileyBase->SetPosition(hwnd, nullptr); // Get the RichEdit container site IOleClientSite *pOleClientSite; RichEditOle->GetClientSite(&pOleClientSite); // Now Add the object to the RichEdit REOBJECT reobject = { 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_PTR)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(bstrText); } SysFreeString(spaceb); TextSelection->SetRange(oldSel.cpMin, oldSel.cpMax); if (rdo) SendMessage(hwnd, EM_SETREADONLY, TRUE, 0); ReleaseDC(hwnd, hdc); TextDocument->Unfreeze(&cnt); if (cnt == 0) UpdateWindow(hwnd); } else ReleaseDC(hwnd, hdc); if (unFreeze) { TextDocument->Unfreeze(&cnt); if (cnt == 0) UpdateWindow(hwnd); } } void ReplaceSmileysWithText(HWND hwnd, CHARRANGE &sel, bool keepFrozen) { CComPtr RichEditOle = nullptr; if (SendMessage(hwnd, EM_GETOLEINTERFACE, 0, (LPARAM)&RichEditOle) == 0) return; if (RichEditOle == nullptr) return; CComPtr TextDocument; if (RichEditOle->QueryInterface(IID_ITextDocument, (void **)&TextDocument) != S_OK) return; // retrieve text range CComPtr TextRange; if (TextDocument->Range(0, 0, &TextRange) != S_OK) return; long cnt; TextDocument->Freeze(&cnt); bool rdo = (GetWindowLongPtr(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 = nullptr; if (reObj.cp < sel.cpMax && reObj.clsid == CLSID_NULL) reObj.poleobj->QueryInterface(IID_ISmileyAddSmiley, (void **)&igsc); reObj.poleobj->Release(); if (igsc == nullptr) continue; TextRange->SetRange(reObj.cp, reObj.cp + 1); BSTR bstr = nullptr; 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); }