/*
Miranda SmileyAdd Plugin
Copyright (C) 2008 - 2011 Boris Krasnovskiy All Rights Reserved
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"
class CAniSmileyObject;
static int CompareAniSmiley(const CAniSmileyObject* p1, const CAniSmileyObject* p2)
{
return (int)((char*)p2 - (char*)p1);
}
static LIST regAniSmileys(10, CompareAniSmiley);
static UINT_PTR timerId;
static void CALLBACK timerProc(HWND, UINT, UINT_PTR, DWORD);
static void CALLBACK sttMainThreadCallback( PVOID )
{
if (timerId == 0xffffffff)
timerId = SetTimer(NULL, 0, 100, (TIMERPROC)timerProc);
}
class CAniSmileyObject : public ISmileyBase
{
private:
typedef enum { animStdOle, animDrctRichEd, animHpp } AnimType;
POINTL m_rectOrig;
SIZEL m_rectExt;
COLORREF m_bkg;
SmileyType *m_sml;
ImageBase *m_img;
unsigned m_nFramePosition;
long m_counter;
unsigned m_richFlags;
long m_lastObjNum;
AnimType m_animtype;
bool m_allowAni;
public:
CAniSmileyObject(SmileyType* sml, COLORREF clr, bool ishpp)
{
m_allowAni = false;
m_animtype = ishpp ? animHpp : animStdOle;
m_bkg = clr;
m_rectOrig.x = 0;
m_rectOrig.y = 0;
m_rectExt.cx = 0;
m_rectExt.cy = 0;
m_richFlags = 0;
m_lastObjNum = 0;
m_sml = sml;
m_img = NULL;
m_nFramePosition = 0;
m_counter = 0;
}
~CAniSmileyObject(void)
{
UnloadSmiley();
}
void LoadSmiley(void)
{
if (m_img != NULL) return;
m_img = m_sml->CreateCachedImage();
if (m_img && m_img->IsAnimated() && opt.AnimateDlg) {
m_nFramePosition = 0;
m_img->SelectFrame(m_nFramePosition);
long frtm = m_img->GetFrameDelay();
m_counter = frtm / 10 + ((frtm % 10) >= 5);
regAniSmileys.insert(this);
if (timerId == 0) {
timerId = 0xffffffff;
CallFunctionAsync(sttMainThreadCallback, NULL);
}
}
else m_nFramePosition = m_sml->GetStaticFrame();
}
void UnloadSmiley(void)
{
regAniSmileys.remove(this);
if (timerId && (timerId+1) && regAniSmileys.getCount() == 0) {
KillTimer(NULL, timerId);
timerId = 0;
}
if (m_img) m_img->Release();
m_img = NULL;
}
void GetDrawingProp(void)
{
if (m_hwnd == NULL) return;
IRichEditOle* RichEditOle;
if (SendMessage(m_hwnd, EM_GETOLEINTERFACE, 0, (LPARAM)&RichEditOle) == 0)
return;
REOBJECT reObj = {0};
reObj.cbStruct = sizeof(REOBJECT);
HRESULT hr = RichEditOle->GetObject(m_lastObjNum, &reObj, REO_GETOBJ_NO_INTERFACES);
if (hr == S_OK && reObj.dwUser == (DWORD)(ISmileyBase*)this && reObj.clsid == CLSID_NULL)
m_richFlags = reObj.dwFlags;
else {
long objectCount = RichEditOle->GetObjectCount();
for (long i = objectCount; i--; ) {
HRESULT hr = RichEditOle->GetObject(i, &reObj, REO_GETOBJ_NO_INTERFACES);
if (FAILED(hr)) continue;
if (reObj.dwUser == (DWORD)(ISmileyBase*)this && reObj.clsid == CLSID_NULL) {
m_lastObjNum = i;
m_richFlags = reObj.dwFlags;
break;
}
}
}
RichEditOle->Release();
if ((m_richFlags & REO_SELECTED) == 0) {
CHARRANGE sel;
SendMessage(m_hwnd, EM_EXGETSEL, 0, (LPARAM)&sel);
if (reObj.cp >= sel.cpMin && reObj.cp < sel.cpMax)
m_richFlags |= REO_INVERTEDSELECT;
else
m_richFlags &= ~REO_INVERTEDSELECT;
}
}
void DoDirectDraw(HDC hdc)
{
HBITMAP hBmp = CreateCompatibleBitmap(hdc, m_rectExt.cx, m_rectExt.cy);
HDC hdcMem = CreateCompatibleDC(hdc);
HANDLE hOld = SelectObject(hdcMem, hBmp);
RECT rc;
rc.left = m_rectExt.cx - m_sizeExtent.cx;
rc.top = m_rectExt.cy - m_sizeExtent.cy;
rc.right = rc.left + m_sizeExtent.cx;
rc.bottom = rc.top + m_sizeExtent.cy;
HBRUSH hbr = CreateSolidBrush(m_bkg);
RECT frc = { 0, 0, m_rectExt.cx, m_rectExt.cy };
FillRect(hdcMem, &frc, hbr);
DeleteObject(hbr);
m_img->DrawInternal(hdcMem, rc.left, rc.top, m_sizeExtent.cx - 1, m_sizeExtent.cy - 1);
if (m_richFlags & REO_SELECTED) {
HBRUSH hbr = CreateSolidBrush(m_bkg ^ 0xFFFFFF);
FrameRect(hdcMem, &rc, hbr);
DeleteObject(hbr);
}
if (m_richFlags & REO_INVERTEDSELECT)
InvertRect(hdcMem, &rc);
BitBlt(hdc, m_rectOrig.x, m_rectOrig.y, m_rectExt.cx, m_rectExt.cy, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hOld);
DeleteObject(hBmp);
DeleteDC(hdcMem);
}
void DrawOnRichEdit(void)
{
HDC hdc = GetDC(m_hwnd);
if (RectVisible(hdc, &m_orect)) {
RECT crct;
GetClientRect(m_hwnd, &crct);
HRGN hrgnOld = CreateRectRgnIndirect(&crct);
int res = GetClipRgn(hdc, hrgnOld);
HRGN hrgn = CreateRectRgnIndirect(&crct);
SelectClipRgn(hdc, hrgn);
DeleteObject(hrgn);
DoDirectDraw(hdc);
SelectClipRgn(hdc, res < 1 ? NULL : hrgnOld);
DeleteObject(hrgnOld);
}
else {
m_visible = false;
m_allowAni = false;
UnloadSmiley();
}
ReleaseDC(m_hwnd, hdc);
}
void DrawOnHPP(void)
{
FVCNDATA_NMHDR nmh = {0};
nmh.code = NM_FIREVIEWCHANGE;
nmh.hwndFrom = m_hwnd;
nmh.cbSize = sizeof(nmh);
nmh.bEvent = FVCN_PREFIRE;
nmh.bAction = FVCA_DRAW;
nmh.rcRect = m_orect;
SendMessage(GetParent(m_hwnd), WM_NOTIFY, (WPARAM)m_hwnd, (LPARAM)&nmh);
switch (nmh.bAction) {
case FVCA_DRAW:
// support for pseudo-edit mode and event details
m_animtype = m_dirAniAllow ? animDrctRichEd : animStdOle;
GetDrawingProp();
DrawOnRichEdit();
break;
case FVCA_CUSTOMDRAW:
m_rectExt.cy = nmh.rcRect.bottom - nmh.rcRect.top;
m_rectExt.cx = nmh.rcRect.right - nmh.rcRect.left;
m_rectOrig.x = nmh.rcRect.left;
m_rectOrig.y = nmh.rcRect.top;
m_bkg = nmh.clrBackground;
DoDirectDraw(nmh.hDC);
nmh.bEvent = FVCN_POSTFIRE;
SendMessage(GetParent(m_hwnd), WM_NOTIFY, (WPARAM)m_hwnd, (LPARAM)&nmh);
break;
case FVCA_SKIPDRAW:
break;
case FVCA_NONE:
m_visible = false;
break;
}
}
void ProcessTimerTick(void)
{
if (m_visible && m_img && --m_counter <= 0) {
m_nFramePosition = m_img->SelectNextFrame(m_nFramePosition);
long frtm = m_img->GetFrameDelay();
m_counter = frtm / 10 + ((frtm % 10) >= 5);
switch (m_animtype) {
case animStdOle:
if (m_allowAni) SendOnViewChange();
else {
m_visible = false;
UnloadSmiley();
}
m_allowAni = false;
break;
case animDrctRichEd:
DrawOnRichEdit();
break;
case animHpp:
DrawOnHPP();
break;
}
}
}
void SetPosition(HWND hwnd, LPCRECT lpRect)
{
ISmileyBase::SetPosition(hwnd, lpRect);
m_allowAni = m_visible;
if (m_visible) LoadSmiley();
else UnloadSmiley();
if (lpRect == NULL) return;
if (m_animtype == animStdOle) {
m_animtype = animDrctRichEd;
GetDrawingProp();
}
if (lpRect->top == -1) {
m_rectOrig.x = lpRect->left;
m_rectOrig.y = lpRect->bottom - m_sizeExtent.cy;
m_rectExt.cy = m_sizeExtent.cy;
}
else if (lpRect->bottom == -1) {
m_rectOrig.x = lpRect->left;
m_rectOrig.y = lpRect->top;
}
else {
m_rectOrig.x = lpRect->left;
m_rectOrig.y = lpRect->top;
m_rectExt.cy = lpRect->bottom - lpRect->top;
}
}
STDMETHOD(Close)(DWORD dwSaveOption)
{
m_visible = false;
UnloadSmiley();
return ISmileyBase::Close(dwSaveOption);
}
STDMETHOD(Draw)(DWORD dwAspect, LONG, void*, DVTARGETDEVICE*, HDC,
HDC hdc, LPCRECTL pRectBounds, LPCRECTL /* pRectWBounds */,
BOOL (__stdcall *)(ULONG_PTR), ULONG_PTR)
{
if (dwAspect != DVASPECT_CONTENT) return DV_E_DVASPECT;
if (pRectBounds == NULL) return E_INVALIDARG;
LoadSmiley();
if (m_img == NULL) return E_FAIL;
m_sizeExtent.cx = pRectBounds->right - pRectBounds->left;
m_sizeExtent.cy = pRectBounds->bottom - pRectBounds->top;
m_rectExt = m_sizeExtent;
switch (m_animtype) {
case animDrctRichEd:
{
m_rectExt.cy = pRectBounds->bottom - m_rectOrig.y;
RECT frc = { 0, 0, m_sizeExtent.cx - 1, m_sizeExtent.cy - 1 };
HBITMAP hBmp = CreateCompatibleBitmap(hdc, frc.right, frc.bottom);
HDC hdcMem = CreateCompatibleDC(hdc);
HANDLE hOld = SelectObject(hdcMem, hBmp);
HBRUSH hbr = CreateSolidBrush(m_bkg);
FillRect(hdcMem, &frc, hbr);
DeleteObject(hbr);
m_img->DrawInternal(hdcMem, 0, 0, frc.right, frc.bottom);
BitBlt(hdc, pRectBounds->left, pRectBounds->top, frc.right, frc.bottom, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hOld);
DeleteObject(hBmp);
DeleteDC(hdcMem);
}
GetDrawingProp();
break;
case animHpp:
m_orect = *(LPRECT)pRectBounds;
default:
m_img->DrawInternal(hdc, pRectBounds->left, pRectBounds->top,
m_sizeExtent.cx - 1, m_sizeExtent.cy - 1);
break;
}
m_allowAni = true;
m_visible = true;
return S_OK;
}
STDMETHOD(SetExtent)(DWORD dwDrawAspect, SIZEL* psizel)
{
HRESULT hr = ISmileyBase::SetExtent(dwDrawAspect, psizel);
if (hr == S_OK) m_rectExt = m_sizeExtent;
return hr;
}
};
ISmileyBase* CreateAniSmileyObject(SmileyType* sml, COLORREF clr, bool ishpp)
{
if (!sml->IsValid()) return NULL;
CAniSmileyObject *obj = new CAniSmileyObject(sml, clr, ishpp);
return obj;
}
static void CALLBACK timerProc(HWND, UINT, UINT_PTR, DWORD)
{
for (int i=0; i < regAniSmileys.getCount(); i++)
regAniSmileys[i]->ProcessTimerTick();
}
void DestroyAniSmileys(void)
{
if (timerId && (timerId+1)) {
KillTimer(NULL, timerId);
timerId = 0;
}
for (int i=0; i < regAniSmileys.getCount(); i++)
delete regAniSmileys[i];
}