/*
Popup Plus plugin for Miranda IM

Copyright	© 2002 Luca Santarelli,
			© 2004-2007 Victor Pavlychko
			© 2010 MPK
			© 2010 Merlin_de

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.
*/

#include "headers.h"
#include "bitmap_funcs.h"
#include <math.h>

#define POPUP_WNDCLASS "PopupWnd2"

#ifndef CS_DROPSHADOW
#define CS_DROPSHADOW 0x00020000
#endif

#define ULW_ALPHA     0x00000002
#define AC_SRC_ALPHA  0x01

#define POPUP_TIMER   1607
#define AVATAR_TIMER  1608
#define CURSOR_TIMER  1609

#define UM_AVATARCHANGED (WM_USER+0x300)
#define UM_MENUDONE      (WM_USER+0x301)
#define UM_SHOWMENU      (WM_USER+0x302)

HWND	ghwndMenuHost = NULL;

void	WindowThread(void *arg);

LRESULT CALLBACK MenuHostWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

bool	LoadPopupWnd2()
{
	bool res = true;
	DWORD err;

	WNDCLASSEX wcl;
	wcl.cbSize = sizeof(wcl);
	wcl.lpfnWndProc = PopupWnd2::WindowProc;
	wcl.style = 0;
	wcl.cbClsExtra = 0;
	wcl.cbWndExtra = 0;
	wcl.hInstance = hInst;
	wcl.hIcon = NULL;
	wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcl.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
	wcl.lpszMenuName = NULL;
	wcl.lpszClassName = _T(POPUP_WNDCLASS);
	wcl.hIconSm = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_POPUP), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR); 
	g_wndClass.cPopupWnd2 = RegisterClassEx(&wcl);
	err = GetLastError();
	if (!g_wndClass.cPopupWnd2) {
		res = false;
		TCHAR msg[1024];
		mir_sntprintf(msg, SIZEOF(msg), TranslateT("Failed to register %s class."), wcl.lpszClassName);
		MessageBox(NULL, msg, _T(MODULNAME_LONG), MB_ICONSTOP|MB_OK);
	}

	// register custom class for edit box with drop-shadow attribute
	#define MyRegisterClassExW RegisterClassExW
	#define MyGetClassInfoExW  GetClassInfoExW

	WNDCLASSEXW wclw = {0};
	wclw.cbSize = sizeof(wclw);
	if (!MyGetClassInfoExW(NULL, L"EDIT", &wclw))
		MSGERROR(TranslateT("Failed to GetClassInfoExW from EDIT class."));
	wclw.hInstance = hInst;
	wclw.lpszClassName = L"PopupEditBox";
	wclw.style |= CS_DROPSHADOW;
	g_wndClass.cPopupEditBox = MyRegisterClassExW(&wclw);
	err = GetLastError();
	if (!g_wndClass.cPopupEditBox) {
		TCHAR msg[2048];
		mir_sntprintf(msg, SIZEOF(msg), TranslateT("Failed to register custom edit box window class.\r\n\r\ncbSize: %i\r\nstyle: %p\r\nlpfnWndProc: %i\r\ncbClsExtra: %i\r\ncbWndExtra: %i\r\nhInstance: %i\r\nhIcon: %i\r\nhCursor: %i\r\nhbrBackground: %i\r\nlpszMenuName: %s\r\nlpszClassName: %s\r\nhIconSm: %i\r\n"),
			wclw.cbSize,		//UINT        cbSize;
			wclw.style,			//UINT        style;
			wclw.lpfnWndProc,	//WNDPROC     lpfnWndProc;
			wclw.cbClsExtra,	//int         cbClsExtra;
			wclw.cbWndExtra,	//int         cbWndExtra;
			wclw.hInstance,		//HINSTANCE   hInstance;
			wclw.hIcon,			//HICON       hIcon;
			wclw.hCursor,		//HCURSOR     hCursor;
			wclw.hbrBackground,	//HBRUSH      hbrBackground;
			wclw.lpszMenuName,	//LPCWSTR     lpszMenuName;
			wclw.lpszClassName,	//LPCWSTR     lpszClassName;
			wclw.hIconSm		//HICON       hIconSm;
			);

		MSGERROR(msg);
	}

	ZeroMemory(&wcl, sizeof(wcl));
	wcl.cbSize = sizeof(wcl);
	wcl.lpfnWndProc = MenuHostWndProc;
	wcl.style = 0;
	wcl.cbClsExtra = 0;
	wcl.cbWndExtra = 0;
	wcl.hInstance = hInst;
	wcl.hIcon = NULL;
	wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcl.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
	wcl.lpszMenuName = NULL;
	wcl.lpszClassName = _T("PopupMenuHostWnd");
	wcl.hIconSm = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_POPUP), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
	g_wndClass.cPopupMenuHostWnd = RegisterClassEx(&wcl);
	err = GetLastError();
	if (!g_wndClass.cPopupMenuHostWnd) {
		res = false;
		TCHAR msg[1024];
		mir_sntprintf(msg, SIZEOF(msg), TranslateT("Failed to register %s class."), wcl.lpszClassName);
		MSGERROR(msg);
	}

	ghwndMenuHost = CreateWindow(_T("PopupMenuHostWnd"), NULL, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInst, NULL);
	SetWindowPos(ghwndMenuHost, 0, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_HIDEWINDOW);

	INITCOMMONCONTROLSEX iccex; 
	iccex.dwICC = ICC_WIN95_CLASSES;
	iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
	InitCommonControlsEx(&iccex);

	res = res && ghwndMenuHost;

	return res;
}

void	UnloadPopupWnd2()
{
	DestroyWindow(ghwndMenuHost);
}

PopupWnd2::PopupWnd2(POPUPDATA2 *ppd, POPUPOPTIONS *theCustomOptions, bool renderOnly)
{
	m_signature = POPUP_OBJECT_SIGNARURE;
	m_textType = TT_NONE;
	m_options = theCustomOptions ? theCustomOptions : &PopupOptions;
	m_hfnText = fonts.text;
	m_hfnTitle = fonts.title;
	updateData(ppd);
	if (!renderOnly)
		startThread();
}

PopupWnd2::~PopupWnd2()
{
	m_signature = 0;
	setIcon(NULL);

	mir_free(m_lpzSkin);
	mir_free(m_lpzTitle);
	mir_free(m_lpwzTitle);

	if (m_hwnd)
		SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0);
	if (m_bmp) delete m_bmp;
	if (m_bmpBase) delete m_bmpBase;
	if (m_bmpAnimate) delete m_bmpAnimate;
	if (m_mtText)  MText.Destroy(m_mtText);
	if (m_mtTitle) MText.Destroy(m_mtTitle);
	if (m_avatar) delete m_avatar;
	if (m_actions) delete [] m_actions;
}

void PopupWnd2::startThread()
{
	mir_forkthread(WindowThread, this);
}

void PopupWnd2::create()
{
	m_hwnd = CreateWindowEx(
		WS_EX_TRANSPARENT|					// prevents unwanted clicks
		WS_EX_TOOLWINDOW|WS_EX_TOPMOST,		// dwStyleEx
		_T(POPUP_WNDCLASS),					// Class name
		NULL,								// Title
		DS_SETFONT|DS_FIXEDSYS|WS_POPUP,	// dwStyle
		CW_USEDEFAULT,						// x
		CW_USEDEFAULT,						// y
		CW_USEDEFAULT,						// Width
		CW_USEDEFAULT,						// Height
		HWND_DESKTOP,						// Parent
		NULL,								// menu handle
		hInst,								// Instance
		(LPVOID)this);

	// Shadows
	ULONG_PTR style = GetClassLongPtr(m_hwnd, GCL_STYLE);
	if (m_options->DropShadow && !(style & CS_DROPSHADOW))
		style |= CS_DROPSHADOW;
	else if (!m_options->DropShadow && (style & CS_DROPSHADOW))
		style &= ~CS_DROPSHADOW;

	SetClassLongPtr(m_hwnd, GCL_STYLE, style);

	// tooltips
	m_hwndToolTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
		WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		m_hwnd, NULL, hInst, NULL);
	SetWindowPos(m_hwndToolTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

	m_bWindowCreated = true;
	update();
}

void PopupWnd2::updateLayered(BYTE opacity)
{
	if (!m_hwnd) return;

	if (SetWindowLongPtr(m_hwnd, GWL_EXSTYLE, GetWindowLongPtr(m_hwnd, GWL_EXSTYLE) | WS_EX_LAYERED)) {
		RECT rc; GetWindowRect(m_hwnd, &rc);
		POINT ptDst = {rc.left, rc.top};
		POINT ptSrc = {0, 0};

		BLENDFUNCTION blend;
		blend.BlendOp =             AC_SRC_OVER;
		blend.BlendFlags =          0;
		blend.SourceConstantAlpha = opacity; //m_options->UseTransparency ? opacity : 255;
		blend.AlphaFormat =         AC_SRC_ALPHA;

		UpdateLayeredWindow(m_hwnd, NULL, &ptDst, &m_sz,
			m_bmpAnimate ? m_bmpAnimate->getDC() : m_bmp->getDC(),
			&ptSrc, 0xffffffff, &blend, ULW_ALPHA);

		UpdateWindow(m_hwnd);
	}
}

SIZE PopupWnd2::measure()
{
	const PopupSkin *skin = skins.getSkin(m_lpzSkin?m_lpzSkin:m_options->SkinPack);
	if (!skin)
		return m_sz;

	MyBitmap bmpTmp(1,1);
	skin->measure(bmpTmp.getDC(), this, m_options->UseMaximumWidth ? m_options->MaximumWidth : SETTING_MAXIMUMWIDTH_MAX, m_options);
	return m_sz;
}

void PopupWnd2::update()
{
	const PopupSkin *skin = skins.getSkin(m_lpzSkin?m_lpzSkin:m_options->SkinPack);
	if (!skin) return;

	// update avatar
	fixAvatar();

	// destroy content bitmap so animate() can reallocate it if needed
	if (m_bmp) {
		delete m_bmp;
		m_bmp = NULL;
	}

	// measure popup
	if (m_bmpBase) delete m_bmpBase;
	if (m_bmpAnimate) delete m_bmpAnimate;
	m_bmpBase = new MyBitmap(1,1);
	skin->measure(m_bmpBase->getDC(), this, m_options->UseMaximumWidth ? m_options->MaximumWidth : SETTING_MAXIMUMWIDTH_MAX, m_options);

	// render popup
	m_bmpBase->allocate(m_sz.cx, m_sz.cy);
	HDC hdc = m_bmpBase->getDC();
	if (!skin) return;
	SetBkMode(hdc, TRANSPARENT);
	skin->display(m_bmpBase, this, m_options->UseMaximumWidth ? m_options->MaximumWidth : SETTING_MAXIMUMWIDTH_MAX, m_options,
		PopupSkin::DF_STATIC);
	if (*m_time && skin->useInternalClock())
	{
		SetTextColor(hdc, m_clClock);
		HFONT hfnSave = (HFONT)SelectObject(m_bmpBase->getDC(), fonts.clock);
		SIZE sz; GetTextExtentPoint32A(m_bmpBase->getDC(), m_time, lstrlenA(m_time), &sz);
		m_bmpBase->Draw_TextA(m_time, this->m_sz.cx - sz.cx - STYLE_SZ_GAP - skin->getRightGap(), STYLE_SZ_GAP);
		SelectObject(m_bmpBase->getDC(), hfnSave);
	}
	
	m_bReshapeWindow = true;

	animate();
}

void PopupWnd2::animate()
{
	const PopupSkin *skin = skins.getSkin(m_lpzSkin?m_lpzSkin:m_options->SkinPack);
	if (!skin) return;

	if (m_avatar->isAnimated() || m_actionCount)
	{
		if (!m_bmp) m_bmp = new MyBitmap(m_bmpBase->getWidth(), m_bmpBase->getHeight());
		m_bmp->Draw(m_bmpBase, 0, 0, m_bmpBase->getWidth(), m_bmpBase->getHeight());
		skin->display(m_bmp, this, m_options->UseMaximumWidth ? m_options->MaximumWidth : SETTING_MAXIMUMWIDTH_MAX, m_options, PopupSkin::DF_ANIMATE); 
	} else
	if (m_bmpBase)
	{
		if (m_bmp) delete m_bmp;
		m_bmp = m_bmpBase;
		m_bmpBase = NULL;
	}

	// update layered window if supported
	updateLayered((m_options->UseTransparency && !(m_bIsHovered && m_options->OpaqueOnHover)) ? m_options->Alpha : 255);

	if (m_bReshapeWindow)
	{
		m_bReshapeWindow = false;

		if (m_hwnd && m_bmp && m_options->DropShadow && PopupOptions.EnableFreeformShadows /*DoWeNeedRegionForThisSkin()*/)
			SetWindowRgn(m_hwnd, m_bmp->buildOpaqueRgn(skin->getShadowRegionOpacity()), FALSE);


		if (MyDwmEnableBlurBehindWindow && PopupOptions.EnableAeroGlass) {
			DWM_BLURBEHIND bb = {0};
			bb.dwFlags = DWM_BB_ENABLE|DWM_BB_BLURREGION;
			bb.fEnable = TRUE;
			bb.hRgnBlur = m_bmp->buildOpaqueRgn(254, false);
			MyDwmEnableBlurBehindWindow(m_hwnd, &bb);
			DeleteObject(bb.hRgnBlur);
		}

		// update tooltips
		for (int i=0; i < m_actionCount; ++i) {
			char *title = strchr(m_actions[i].actionA.lpzTitle, '/');
			if (title) title++;
			else title = m_actions[i].actionA.lpzTitle;
			AddTooltipTranslated(m_hwndToolTip, m_hwnd, i, m_actions[i].rc, title);
		}

//		if (!customPopup && hwnd /*&& IsWindowVisible(hwnd)*/)
//			PopupThreadUpdateWindow(this);
	}
}

void PopupWnd2::show()
{
	if ((m_options->UseEffect || (m_options->UseAnimations && !m_customPopup)) && m_options->FadeIn)
	{
		IPopupPlusEffect *effect = NULL;
		m_bSlide = m_bFade = false;
		DWORD dwTime, dwTime0 = GetTickCount();
		DWORD dwTime1 = dwTime0 + m_options->FadeIn;
		if (m_options->UseEffect)
		{
			m_bFade = true;
			m_btAlpha0 = 0;
			m_btAlpha1 = m_options->UseTransparency ? m_options->Alpha : 255;
			updateLayered(m_btAlpha0);

			if (*PopupOptions.Effect)
			{
				char vfxService[128];
				mir_snprintf(vfxService, sizeof(vfxService), "Popup/Vfx/%S", PopupOptions.Effect);
				if (ServiceExists(vfxService))
					if (effect = (IPopupPlusEffect *)CallService(vfxService, 0, 0))
					{
						effect->beginEffect(m_bmp->getWidth(), m_bmp->getHeight(), m_btAlpha0, m_btAlpha1, dwTime1-dwTime0);
						m_bmpAnimate = new MyBitmap(m_bmp->getWidth(), m_bmp->getHeight());
					}
			}
		} else
		{
			updateLayered(m_options->UseTransparency ? m_options->Alpha : 255);
		}
		if (m_options->UseAnimations && !m_customPopup)
		{
			m_bSlide = true;
			m_ptPosition0 = m_pos;
			m_ptPosition1 = m_pos;
			if (m_options->Position == POS_LOWERLEFT || m_options->Position == POS_UPPERLEFT)
				m_ptPosition0.x -= m_sz.cx + 2 * 5;
			else
				m_ptPosition0.x += m_sz.cx + 2 * 5;
			SetWindowPos(m_hwnd, 0, m_ptPosition0.x, m_ptPosition0.y, 0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_SHOWWINDOW);
		} else
		{
			SetWindowPos(m_hwnd, 0, m_pos.x, m_pos.y, 0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_SHOWWINDOW);
		}
		while ((dwTime = GetTickCount()) < dwTime1)
		{
			if (m_bFade)
			{
				if (effect)
				{
					effect->beginFrame(dwTime - dwTime0);
					m_bmpAnimate->Draw(m_bmp, 0, 0, m_bmp->getWidth(), m_bmp->getHeight());
					for (int row = 0; row < m_bmpAnimate->getHeight(); ++row)
					{
						unsigned char *pixel = (unsigned char *)m_bmpAnimate->getRow(row);
						for (int col = 0; col < m_bmpAnimate->getWidth(); ++col)
						{
							WORD alphaLevel = effect->getPixelAlpha(col, row);
							pixel[0] = (pixel[0] * alphaLevel) >> 8;
							pixel[1] = (pixel[1] * alphaLevel) >> 8;
							pixel[2] = (pixel[2] * alphaLevel) >> 8;
							pixel[3] = (pixel[3] * alphaLevel) >> 8;
							pixel += 4;
						}
					}
					effect->endFrame();
					updateLayered(255);
				} else
				{
					updateLayered(m_btAlpha0 + (m_btAlpha1 - m_btAlpha0) * int(dwTime - dwTime0) / m_options->FadeIn);
				}
			}
			if (m_bSlide)
				SetWindowPos(m_hwnd, 0,
					(int)m_ptPosition0.x + ((int)m_ptPosition1.x - (int)m_ptPosition0.x) * int(dwTime - dwTime0) / (int)m_options->FadeIn,
					(int)m_ptPosition0.y + ((int)m_ptPosition1.y - (int)m_ptPosition0.y) * int(dwTime - dwTime0) / (int)m_options->FadeIn,
					0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_SHOWWINDOW);
			UpdateWindow(m_hwnd);
			Sleep(1);
		}

		if (effect)
		{
			effect->endEffect();
			effect->destroy();
			delete m_bmpAnimate;
			m_bmpAnimate = NULL;
		}
	}

	m_bSlide = m_bFade = false;

	updateLayered((m_options->UseTransparency && !(m_bIsHovered && m_options->OpaqueOnHover)) ? m_options->Alpha : 255);
	//updateLayered(m_options->UseTransparency ? m_options->Alpha : 255);
	SetWindowPos(m_hwnd, 0, m_pos.x, m_pos.y, 0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_SHOWWINDOW);
}

void PopupWnd2::hide()
{
	if ((m_options->UseEffect || (m_options->UseAnimations && !m_customPopup)) && m_options->FadeOut)
	{
		m_bDestroy = true;
		IPopupPlusEffect *effect = NULL;
		m_bFade = m_bSlide = false;
		DWORD dwTime, dwTime0 = GetTickCount();
		DWORD dwTime1 = dwTime0 + m_options->FadeOut;
		if (m_options->UseEffect)
		{
			m_bFade = true;
			m_btAlpha0 = m_options->UseTransparency ? m_options->Alpha : 255;
			m_btAlpha1 = 0;
			updateLayered(m_btAlpha0);

			if (*PopupOptions.Effect)
			{
				char vfxService[128];
				mir_snprintf(vfxService, sizeof(vfxService), "Popup/Vfx/%S", PopupOptions.Effect);
				if (ServiceExists(vfxService))
					if (effect = (IPopupPlusEffect *)CallService(vfxService, 0, 0))
					{
						effect->beginEffect(m_bmp->getWidth(), m_bmp->getHeight(), m_btAlpha0, m_btAlpha1, dwTime1-dwTime0);
						m_bmpAnimate = new MyBitmap(m_bmp->getWidth(), m_bmp->getHeight());
					}
			}
		}
		if (m_options->UseAnimations && !m_customPopup)
		{
			m_bSlide = true;
			m_ptPosition0 = m_pos;
			m_ptPosition1 = m_pos;
			if (m_options->Position == POS_LOWERLEFT || m_options->Position == POS_UPPERLEFT)
				m_ptPosition1.x -= m_sz.cx + 2 * 5;
			else
				m_ptPosition1.x += m_sz.cx + 2 * 5;
			SetWindowPos(m_hwnd, 0, m_ptPosition0.x, m_ptPosition0.y, 0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_SHOWWINDOW);
		}
		while ((dwTime = GetTickCount()) < dwTime1)
		{
			if (m_bFade)
			{
				if (effect)
				{
					effect->beginFrame(dwTime - dwTime0);
					m_bmpAnimate->Draw(m_bmp, 0, 0, m_bmp->getWidth(), m_bmp->getHeight());
					for (int row = 0; row < m_bmpAnimate->getHeight(); ++row)
					{
						unsigned char *pixel = (unsigned char *)m_bmpAnimate->getRow(row);
						for (int col = 0; col < m_bmpAnimate->getWidth(); ++col)
						{
							WORD alphaLevel = effect->getPixelAlpha(col, row);
							pixel[0] = (pixel[0] * alphaLevel) >> 8;
							pixel[1] = (pixel[1] * alphaLevel) >> 8;
							pixel[2] = (pixel[2] * alphaLevel) >> 8;
							pixel[3] = (pixel[3] * alphaLevel) >> 8;
							pixel += 4;
						}
					}
					effect->endFrame();
					updateLayered(255);
				} else
				{
					updateLayered((int)m_btAlpha0 + ((int)m_btAlpha1 - (int)m_btAlpha0) * int(dwTime - dwTime0) / (int)m_options->FadeOut);
				}
			}
			if (m_bSlide)
				SetWindowPos(m_hwnd, 0,
					(int)m_ptPosition0.x + ((int)m_ptPosition1.x - (int)m_ptPosition0.x) * int(dwTime - dwTime0) / (int)m_options->FadeOut,
					(int)m_ptPosition0.y + ((int)m_ptPosition1.y - (int)m_ptPosition0.y) * int(dwTime - dwTime0) / (int)m_options->FadeOut,
					0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_SHOWWINDOW);
			UpdateWindow(m_hwnd);
			Sleep(1);
		}

		if (effect)
		{
			effect->endEffect();
			effect->destroy();
			delete m_bmpAnimate;
			m_bmpAnimate = NULL;
		}
	}

	SetWindowPos(m_hwnd, 0, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_HIDEWINDOW);
	DestroyWindow(m_hwnd);
//	hwnd = 0;
}

bool __forceinline isTextEmpty(char *text)
{
	if (!text)
		return true;
	while (*text)
		if (!isspace(BYTE(*text++)))
			return false;
	return true;
}

bool __forceinline isTextEmpty(WCHAR *text)
{
	if (!text)
		return true;
	while (*text)
		if (!iswspace(*text++))
			return false;
	return true;
}

void PopupWnd2::fixDefaults()
{
	if (m_options->UseWinColors) {
		m_clBack	= GetSysColor(COLOR_BTNFACE);
		m_clText	= GetSysColor(COLOR_WINDOWTEXT);
		m_clTitle	= GetSysColor(COLOR_WINDOWTEXT);
		m_clClock	= GetSysColor(COLOR_WINDOWTEXT);
	}
	else if ((m_clBack == (COLORREF)NULL) && (m_clText == (COLORREF)NULL)) {
		m_clTitle	= fonts.clTitle;
		m_clBack	= fonts.clBack;
		m_clText	= fonts.clText;
		m_clClock	= fonts.clClock;
	}
	else m_clClock	= m_clTitle;

	if (!m_iTimeout)
		m_iTimeout = m_options->InfiniteDelay ? -1 : m_options->Seconds;

	m_hContactPassed = m_hContact;
	if (m_hContact)
		if (!GetContactProto(m_hContact))
			m_hContact = NULL;
	
	switch (m_textType) {
	case TT_ANSI:
		m_bTextEmpty = ::isTextEmpty(m_lpzText);
		break;

	case TT_UNICODE:
		m_bTextEmpty = ::isTextEmpty(m_lpwzText);
		break;

	default:
		m_bTextEmpty = false;
		break;
	}
}

void PopupWnd2::fixAvatar()
{
	if (m_avatar && !m_avatar->isValid())
		delete m_avatar;

	if (m_hbmAvatar)
		m_avatar = new SimpleAvatar(m_hbmAvatar, true);
	else
		m_avatar = PopupAvatar::create(m_hContact);
}

int PopupWnd2::fixActions(POPUPACTION *theActions, int count)
{
	bool isIm = (m_hContact && (CallProtoService(GetContactProto(m_hContact), PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IM)) ? true : false;

	bool enableDefaultGen = (m_hContact || !(PopupOptions.actions&ACT_DEF_NOGLOBAL)) ? true : false;
	bool enableDefaultUsr = (isIm || m_hContact && !(PopupOptions.actions&ACT_DEF_IMONLY)) ? true : false;
	bool iconSize = PopupOptions.actions&ACT_LARGE ? TRUE : FALSE;

	if (PopupOptions.actions & ACT_ENABLE) {
		if (enableDefaultUsr && isIm && IsActionEnabled("General/Quick reply")) ++m_actionCount;
		if (enableDefaultUsr && isIm && IsActionEnabled("General/Send message")) ++m_actionCount;
		if (enableDefaultUsr && IsActionEnabled("General/User details")) ++m_actionCount;
		if (enableDefaultUsr && IsActionEnabled("General/Contact menu")) ++m_actionCount;
		if (enableDefaultUsr && db_get_b(m_hContact, "CList", "NotOnList", 0) && IsActionEnabled("General/Add permanently")) ++m_actionCount;
		if (enableDefaultGen && (m_iTimeout != -1) && IsActionEnabled("General/Pin popup")) ++m_actionCount;
		if (enableDefaultGen && IsActionEnabled("General/Dismiss popup")) ++m_actionCount;
		if (enableDefaultGen && IsActionEnabled("General/Copy to clipboard")) ++m_actionCount;
		
		int iAction = fixActions(theActions, count, m_actionCount);

		if (enableDefaultUsr && isIm && IsActionEnabled("General/Quick reply")) {
			m_actions[iAction].actionA.cbSize = sizeof(POPUPACTION);
			m_actions[iAction].actionA.lchIcon = IcoLib_GetIcon(ICO_ACT_REPLY,iconSize);
			lstrcpyA(m_actions[iAction].actionA.lpzTitle, "General/Quick reply");
			m_actions[iAction].actionA.wParam = 0;
			m_actions[iAction].actionA.lParam = ACT_DEF_REPLY;
			++iAction;
		}
		
		if (enableDefaultUsr && isIm && IsActionEnabled("General/Send message")) {
			m_actions[iAction].actionA.cbSize = sizeof(POPUPACTION);
			m_actions[iAction].actionA.lchIcon = IcoLib_GetIcon(ICO_ACT_MESS,iconSize);
			lstrcpyA(m_actions[iAction].actionA.lpzTitle, "General/Send message");
			m_actions[iAction].actionA.wParam = 0;
			m_actions[iAction].actionA.lParam = ACT_DEF_MESSAGE;
			++iAction;
		}
		
		if (enableDefaultUsr && IsActionEnabled("General/User details")) {
			m_actions[iAction].actionA.cbSize = sizeof(POPUPACTION);
			m_actions[iAction].actionA.lchIcon = IcoLib_GetIcon(ICO_ACT_INFO,iconSize);
			lstrcpyA(m_actions[iAction].actionA.lpzTitle, "General/User details");
			m_actions[iAction].actionA.wParam = 0;
			m_actions[iAction].actionA.lParam = ACT_DEF_DETAILS;
			++iAction;
		}
		
		if (enableDefaultUsr && IsActionEnabled("General/Contact menu")) {
			m_actions[iAction].actionA.cbSize = sizeof(POPUPACTION);
			m_actions[iAction].actionA.lchIcon = IcoLib_GetIcon(ICO_ACT_MENU,iconSize);
			lstrcpyA(m_actions[iAction].actionA.lpzTitle, "General/Contact menu");
			m_actions[iAction].actionA.wParam = 0;
			m_actions[iAction].actionA.lParam = ACT_DEF_MENU;
			++iAction;
		}
		
		if (enableDefaultUsr && db_get_b(m_hContact, "CList", "NotOnList", 0) && IsActionEnabled("General/Add permanently")) {
			m_actions[iAction].actionA.cbSize = sizeof(POPUPACTION);
			m_actions[iAction].actionA.lchIcon = IcoLib_GetIcon(ICO_ACT_ADD,iconSize);
			lstrcpyA(m_actions[iAction].actionA.lpzTitle, "General/Add permanently");
			m_actions[iAction].actionA.wParam = 0;
			m_actions[iAction].actionA.lParam = ACT_DEF_ADD;
			++iAction;
		}
		
		if (enableDefaultGen && (m_iTimeout != -1) && IsActionEnabled("General/Pin popup")) {
			m_actions[iAction].actionA.cbSize = sizeof(POPUPACTION);
			m_actions[iAction].actionA.lchIcon = m_bIsPinned ? IcoLib_GetIcon(ICO_ACT_PINNED,iconSize) : IcoLib_GetIcon(ICO_ACT_PIN,iconSize);
			lstrcpyA(m_actions[iAction].actionA.lpzTitle, "General/Pin popup");
			m_actions[iAction].actionA.wParam = 0;
			m_actions[iAction].actionA.lParam = ACT_DEF_PIN;
			++iAction;
		}
		
		if (enableDefaultGen && IsActionEnabled("General/Dismiss popup")) {
			m_actions[iAction].actionA.cbSize = sizeof(POPUPACTION);
			m_actions[iAction].actionA.lchIcon = IcoLib_GetIcon(ICO_ACT_CLOSE,iconSize);
			lstrcpyA(m_actions[iAction].actionA.lpzTitle, "General/Dismiss popup");
			m_actions[iAction].actionA.wParam = 0;
			m_actions[iAction].actionA.lParam = ACT_DEF_DISMISS;
			++iAction;
		}
		
		if (enableDefaultGen && IsActionEnabled("General/Copy to clipboard")) {
			m_actions[iAction].actionA.cbSize = sizeof(POPUPACTION);
			m_actions[iAction].actionA.lchIcon = IcoLib_GetIcon(ICO_ACT_COPY,iconSize);
			lstrcpyA(m_actions[iAction].actionA.lpzTitle, "General/Copy to clipboard");
			m_actions[iAction].actionA.wParam = 0;
			m_actions[iAction].actionA.lParam = ACT_DEF_COPY;
			++iAction;
		}
	}

	return m_actionCount;
}

int PopupWnd2::fixActions(POPUPACTION *theActions, int count, int additional)
{
	m_actionCount = 0;
	if (m_actions) delete [] m_actions;
	m_actions = NULL;

	int i;

	m_actionCount = additional;
	for (i = 0; i < count; ++i)
		if ((theActions[i].flags&PAF_ENABLED) && IsActionEnabled(&theActions[i]))
			++m_actionCount;

	m_actions = new ActionInfo[m_actionCount];
	int iAction = 0;

	for (i = 0; i < count; ++i)
		if ((theActions[i].flags&PAF_ENABLED) && IsActionEnabled(&theActions[i]))
		{
			m_actions[iAction].actionA = theActions[i];
			++iAction;
		}

	return iAction;
}

void PopupWnd2::setIcon(HICON hNewIcon)
{
	if (m_bIcoLib) {
		Skin_ReleaseIcon(m_hIcon);
		m_bIcoLib = false;
	}

	m_hIcon = hNewIcon;

	if ( CallService(MS_SKIN2_ISMANAGEDICON, (WPARAM)m_hIcon, 0)) {
		CallService(MS_SKIN2_ADDREFICON, (WPARAM)m_hIcon, 0);
		m_bIcoLib = true;
	}
}

void PopupWnd2::updateData(POPUPDATAW_V2 *ppd)
{
	m_hContact	= ppd->lchContact;

	m_clBack	= ppd->colorBack;
	m_clClock	= m_clTitle = m_clText = ppd->colorText;
	m_iTimeout	= ppd->iSeconds ? ppd->iSeconds : m_options->Seconds;

	if (m_textType == TT_NONE) m_textType = TT_UNICODE;
	mir_free(m_lpzText);  mir_free(m_lpzTitle);
	mir_free(m_lpwzText); mir_free(m_lpwzTitle);
	m_lpzTitle = m_lpzText = NULL;
	m_lpwzTitle = mir_wstrdup(ppd->lpwzContactName);
	m_lpwzText = mir_wstrdup(ppd->lpwzText);
	setIcon(ppd->lchIcon);
	m_hNotification = ppd->hNotification;

	m_PluginData = ppd->PluginData;
	m_PluginWindowProc = ppd->PluginWindowProc;

	if (m_options->DisplayTime)
		GetTimeFormatA(LOCALE_USER_DEFAULT, 0, NULL,"HH':'mm", m_time, SIZEOF(m_time));
	else m_time[0] = 0;

	fixDefaults();
	fixActions(ppd->lpActions, ppd->actionCount);

	if (m_textType == TT_MTEXT) buildMText();
}

void PopupWnd2::updateData(POPUPDATA2 *ppd)
{
	m_hContact	= ppd->lchContact;

	m_clBack	= ppd->colorBack;
	m_clClock	= m_clTitle = m_clText = ppd->colorText;
	m_iTimeout	= ppd->iSeconds;

	mir_free(m_lpzText);  mir_free(m_lpzTitle);
	mir_free(m_lpwzText); mir_free(m_lpwzTitle);
	if (ppd->flags & PU2_UNICODE) {
		if (m_textType == TT_NONE) m_textType = TT_UNICODE;
		m_lpzTitle = m_lpzText = NULL;
		m_lpwzTitle = mir_wstrdup(ppd->lpwzTitle);
		m_lpwzText = mir_wstrdup(ppd->lpwzText);
		m_lpzTitle = m_lpzText = NULL;
	}
	else {
		if (m_textType == TT_NONE) m_textType = TT_ANSI;
		m_lpzTitle = mir_strdup(ppd->lpzTitle);
		m_lpzText = mir_strdup(ppd->lpzText);
		m_lpwzTitle = m_lpwzText = NULL;
	}

	setIcon(ppd->lchIcon);
	m_hNotification = ppd->lchNotification;

	m_PluginData = ppd->PluginData;
	m_PluginWindowProc = ppd->PluginWindowProc;
	m_customPopup = (ppd->flags & PU2_CUSTOM_POPUP)!= 0;

	m_hbmAvatar = ppd->hbmAvatar;
	m_lpzSkin = mir_a2t(ppd->lpzSkin);

	if (m_options->DisplayTime)
	{
		if (ppd->dwTimestamp)
		{
			DBTIMETOSTRING dbtts;
			dbtts.szFormat = "t";
			dbtts.szDest = m_time;
			dbtts.cbDest = SIZEOF(m_time);
			CallService(MS_DB_TIME_TIMESTAMPTOSTRING, (WPARAM)ppd->dwTimestamp, (LPARAM)&dbtts);
		} else
		{
			GetTimeFormatA(LOCALE_USER_DEFAULT, 0, NULL,"HH':'mm", m_time, SIZEOF(m_time));
		}
	}
	else m_time[0] = 0;

	fixDefaults();
	fixActions(ppd->lpActions, ppd->actionCount);

	if (m_textType == TT_MTEXT) buildMText();
}

void PopupWnd2::buildMText()
{
	if (!(htuText && htuTitle && PopupOptions.UseMText))
		return;

	if (m_mtText) MText.Destroy(m_mtText);
	if (m_mtTitle)MText.Destroy(m_mtTitle);
	m_mtText = m_mtTitle = NULL;

	if (m_lpwzText && m_lpwzTitle) {
		m_textType	= TT_MTEXT;
		m_mtText	= MText.CreateW(htuText, m_lpwzText);
		m_mtTitle	= MText.CreateW(htuTitle,m_lpwzTitle);
	} 
	else if (m_lpzText && m_lpzTitle) {
		m_textType = TT_MTEXT;
		if (ServiceExists(MS_TEXT_CREATEW)) {
			LOGFONT lf;
			LPWSTR lpwzBuf = NULL;

			GetObject(m_hfnText, sizeof(lf), &lf);
			lpwzBuf = mir_a2u(m_lpzText);
			m_mtText = MText.CreateW(htuText, lpwzBuf);
			mir_free(lpwzBuf); lpwzBuf = NULL;

			GetObject(m_hfnTitle, sizeof(lf), &lf);
			lpwzBuf = mir_a2u(m_lpzTitle);
			m_mtTitle = MText.CreateW(htuTitle, lpwzBuf);
			mir_free(lpwzBuf); lpwzBuf = NULL;
		}
		else {
			m_mtText	= MText.Create(htuText, m_lpzText);
			m_mtTitle	= MText.Create(htuTitle,m_lpzTitle);
		}
	}
}

void PopupWnd2::updateText(char *text)
{
	if (m_lpzText)
	{
		mir_free(m_lpzText);
		m_lpzText = mir_strdup(text);
		if (m_textType == TT_MTEXT)
			buildMText();
	}
	m_bTextEmpty = ::isTextEmpty(m_lpzText);
}

void PopupWnd2::updateText(WCHAR *text)
{
	if (m_lpwzText)
	{
		mir_free(m_lpwzText);
		m_lpwzText = mir_wstrdup(text);
		if (m_textType == TT_MTEXT)
			buildMText();
	}
	m_bTextEmpty = ::isTextEmpty(m_lpwzText);
}

void PopupWnd2::updateTitle(char *title)
{
	if (m_lpzTitle)
	{
		mir_free(m_lpzTitle);
		m_lpzTitle = mir_strdup(title);
		if (m_textType == TT_MTEXT)
			buildMText();
	}
}

void PopupWnd2::updateTitle(WCHAR *title)
{
	if (m_lpwzTitle)
	{
		mir_free(m_lpwzTitle);
		m_lpwzTitle = mir_wstrdup(title);
		if (m_textType == TT_MTEXT)
			buildMText();
	}
}

void PopupWnd2::updateTimer()
{
	KillTimer(m_hwnd, POPUP_TIMER);
	if (m_iTimeout > 0)
		SetTimer(m_hwnd, POPUP_TIMER, 1000, 0);
}

LRESULT CALLBACK NullWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_COMMAND:
		case WM_CONTEXTMENU:
			PUDeletePopup(hwnd);
			break;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

struct	ReplyEditData
{
	HWND		hwndPopup;
	HANDLE		hContact;
	WNDPROC		oldWndProc;
};

BOOL	IsUtfSendAvailable(HANDLE hContact)
{
	char* szProto = GetContactProto(hContact);
	if (szProto == NULL) return FALSE;
	//check for MetaContact and get szProto from subcontact
	if (strcmp(szProto, gszMetaProto)==0) {
		HANDLE hSubContact = (HANDLE)CallService(MS_MC_GETDEFAULTCONTACT, (WPARAM)hContact, 0);
		if (!hSubContact) return FALSE;
		szProto = GetContactProto(hSubContact);
	}
	return(CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_IMSENDUTF) ? TRUE : FALSE;
}

void	AddMessageToDB(HANDLE hContact, char *msg, int flag/*bool utf*/)
{
	DBEVENTINFO dbei = {0};
	dbei.cbSize = sizeof(dbei);
	dbei.eventType = EVENTTYPE_MESSAGE;
	dbei.flags = DBEF_SENT | ((flag&PREF_UTF)==PREF_UTF ? DBEF_UTF : 0);
	dbei.szModule = GetContactProto(hContact);
	dbei.timestamp = time(NULL);
	if ( !((flag & PREF_UTF) == PREF_UTF) && (flag & PREF_UNICODE) == PREF_UNICODE)
		dbei.cbBlob = (lstrlenW((LPWSTR)msg) + 1)*sizeof(WCHAR/*TCHAR*/);
	else
		dbei.cbBlob = lstrlenA(msg) + 1;
	dbei.pBlob = (PBYTE)msg;
	db_event_add(hContact, &dbei);
}

LRESULT CALLBACK ReplyEditWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	ReplyEditData *dat = (ReplyEditData *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
	WNDPROC oldWndProc = dat ? dat->oldWndProc : NULL;

	switch (message) {
	case WM_KEYDOWN:
		switch (wParam) {
		case VK_RETURN:
			{
				char *buf = NULL;
				int flag = 0;
				if (IsWindowUnicode(hwnd)) {
					WCHAR msg[2048];
					LPWSTR bufW = NULL;

					SendMessageW(hwnd, WM_GETTEXT, SIZEOF(msg), (LPARAM)msg);

					if (wcslen(msg)==0){
						DestroyWindow(hwnd);
						return 0;
					}
					// we have unicode message, check if it is possible and reasonable to send it as unicode
					if (IsUtfSendAvailable(dat->hContact)) {
						buf = mir_utf8encodeW(msg);
						flag = PREF_UTF;
					}
					else {
						buf = mir_u2a(msg);
						flag = 0;
					}
				}
				else {
					char msg[2048];
					GetWindowTextA(hwnd, msg, SIZEOF(msg));
					if (strlen(msg)==0){
						DestroyWindow(hwnd);
						return 0;
					}
					// we have message, check if it is possible and reasonable to send it as unicode
					if ( IsUtfSendAvailable( dat->hContact )) {
						buf = mir_utf8encode(msg);
						flag = PREF_UTF;
					}
					else {
						buf = mir_strdup(msg)	/*mir_tstrdup(msg)*/;
						flag = 0				/*PREF_TCHAR*/;
					}
				}

				CallContactService(dat->hContact, PSS_MESSAGE, flag, (LPARAM)buf);
				AddMessageToDB(dat->hContact, buf, flag);
				mir_free(buf);
			}
			DestroyWindow(hwnd);
			return 0;

		case VK_ESCAPE:
			DestroyWindow(hwnd);
			return 0;
		}
		break;

	case WM_ACTIVATE:
		if (wParam == WA_INACTIVE)
			DestroyWindow(hwnd);
		break;

	case WM_DESTROY:
		PopupThreadUnlock();
		if (!(PopupOptions.actions&ACT_DEF_KEEPWND))
			PUDeletePopup(dat->hwndPopup);
		SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)dat->oldWndProc);
		SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
		mir_free(dat);
	}

	if (oldWndProc)
		return CallWindowProcW(oldWndProc, hwnd, message, wParam, lParam);

	return DefWindowProc(hwnd, message, wParam, lParam);
}

LRESULT CALLBACK PopupWnd2::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) {
	case WM_CREATE:
		return DefWindowProc(m_hwnd, message, wParam, lParam);

	case UM_INITPOPUP:
		if (!m_customPopup) PopupThreadAddWindow(this);
		if (m_iTimeout > 0) SetTimer(m_hwnd, POPUP_TIMER, m_iTimeout*1000, 0);
		if (m_hContact && !m_hbmAvatar && PopupOptions.EnableAvatarUpdates)
			m_hhkAvatarChanged = HookEventMessage(ME_AV_AVATARCHANGED, m_hwnd, UM_AVATARCHANGED);
		if (m_avatar->activeFrameDelay() > 0) SetTimer(m_hwnd, AVATAR_TIMER, m_avatar->activeFrameDelay(), 0);

		// prevent unwanted clicks, but allow wanted :)
		GetCursorPos(&m_ptPrevCursor);
		SetTimer(m_hwnd, CURSOR_TIMER, 500, NULL);

		break;

	case UM_POPUPSHOW:
		{
			POINT pt; pt.x = wParam; pt.y = lParam;
			setPosition(pt);
			show();
		}
		break;

	case UM_AVATARCHANGED:
		if ((HANDLE)wParam == m_hContact)
		{
			m_avatar->invalidate();
			update();
			if (m_avatar->activeFrameDelay() > 0) SetTimer(m_hwnd, AVATAR_TIMER, m_avatar->activeFrameDelay(), 0);
		}
		break;

	case UM_POPUPACTION:
		if (wParam != 0) break;
		switch (lParam) {
		case ACT_DEF_MESSAGE:
			CallServiceSync(MS_MSG_SENDMESSAGE, (WPARAM)m_hContact, 0);
			if (!(PopupOptions.actions&ACT_DEF_KEEPWND))
				PUDeletePopup(m_hwnd);
			break;

		case ACT_DEF_REPLY:
			{
				if (!m_customPopup) PopupThreadLock();
				//RECT rc = renderInfo.textRect;
				//MapWindowPoints(hwnd, NULL, (LPPOINT)&rc, 2);
				RECT rc; GetWindowRect(m_hwnd, &rc);

				HWND hwndEditBox = CreateWindowExW(WS_EX_TOOLWINDOW|WS_EX_TOPMOST,
					g_wndClass.cPopupEditBox ? L"PopupEditBox" : L"EDIT",
					NULL,
					WS_BORDER|WS_POPUP|WS_VISIBLE|ES_AUTOVSCROLL|ES_LEFT|ES_MULTILINE|ES_NOHIDESEL|ES_WANTRETURN,
					rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, NULL, NULL, hInst, NULL);

				ReplyEditData *dat = (ReplyEditData *)mir_alloc(sizeof(ReplyEditData));
				dat->oldWndProc = (WNDPROC)GetWindowLongPtr(hwndEditBox, (LONG_PTR)GWLP_WNDPROC);
				dat->hwndPopup = m_hwnd;
				dat->hContact = m_hContact;
				if (IsWindowUnicode(hwndEditBox)) {

					SendMessageW(hwndEditBox, WM_SETFONT, (WPARAM)fonts.text, TRUE);

					SetWindowLongPtrW(hwndEditBox, GWLP_USERDATA, (LONG_PTR)dat);
					SetWindowLongPtrW(hwndEditBox, GWLP_WNDPROC, (LONG_PTR)ReplyEditWndProc);
				}
				else {
					SendMessageA(hwndEditBox, WM_SETFONT, (WPARAM)fonts.text, TRUE);
					SetWindowLongPtrA(hwndEditBox, GWLP_USERDATA, (LONG_PTR)dat);
					SetWindowLongPtrA(hwndEditBox, GWLP_WNDPROC, (LONG_PTR)ReplyEditWndProc);
				}
				SetFocus(hwndEditBox);
				break;
			}

		case ACT_DEF_DETAILS:
			CallServiceSync(MS_USERINFO_SHOWDIALOG, (WPARAM)m_hContact, 0);
			if (!(PopupOptions.actions&ACT_DEF_KEEPWND))
				PUDeletePopup(m_hwnd);
			break;

		case ACT_DEF_MENU:
			lock();
			PostMessage(ghwndMenuHost, UM_SHOWMENU, (WPARAM)m_hwnd, (LPARAM)m_hContact);
			break;

		case ACT_DEF_ADD:
			{
				ADDCONTACTSTRUCT acs = {0};
				acs.handle = m_hContact;
				acs.handleType = HANDLE_CONTACT;
				acs.szProto = 0;
				CallServiceSync(MS_ADDCONTACT_SHOW, NULL, (LPARAM)&acs);
				if (!(PopupOptions.actions&ACT_DEF_KEEPWND))
					PUDeletePopup(m_hwnd);
			}
			break;

		case ACT_DEF_PIN:
			if (m_bIsPinned)
				SetTimer(m_hwnd, POPUP_TIMER, m_iTimeout*1000, NULL);
			else
				KillTimer(m_hwnd, POPUP_TIMER);

			m_bIsPinned = !m_bIsPinned;
			{
				bool iconSize = PopupOptions.actions&ACT_LARGE ? TRUE : FALSE;
				PUModifyActionIcon(m_hwnd, wParam, lParam, m_bIsPinned ? IcoLib_GetIcon(ICO_ACT_PINNED,iconSize) : IcoLib_GetIcon(ICO_ACT_PIN,iconSize));
			}
			break;

		case ACT_DEF_DISMISS:
			PUDeletePopup(m_hwnd);
			break;

		case ACT_DEF_COPY:
			{
				#define CF_TCHAR CF_UNICODETEXT 

				HGLOBAL clipbuffer;
				static TCHAR * buffer, *text;
				char* sztext;
				if ((this->m_lpwzText) || (this->m_lpwzTitle))
				{
					size_t size = _tcslen(this->m_lpwzText) + _tcslen(this->m_lpwzTitle) + 3;
					text = (TCHAR*)mir_alloc(size * sizeof(TCHAR));
					mir_sntprintf(text, size, _T("%s\n\n%s"), this->m_lpwzTitle, this->m_lpwzText);
				}
				else if ((this->m_lpzText) || (this->m_lpzTitle))
				{
					int size = lstrlenA(this->m_lpzText) + lstrlenA(this->m_lpzTitle) + 3;
					sztext = (char*)mir_alloc(size * sizeof(char));
					mir_snprintf(sztext, size, "%s\n\n%s", this->m_lpzTitle, this->m_lpzText);
					text = mir_a2t(sztext);
				}
				OpenClipboard(m_hwnd);
				EmptyClipboard();
				clipbuffer = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (lstrlen(text)+1) * sizeof(TCHAR));
				buffer = (TCHAR *)GlobalLock(clipbuffer);
				lstrcpy(buffer, text);
				GlobalUnlock(clipbuffer);
				SetClipboardData(CF_TCHAR, clipbuffer);
				CloseClipboard();
				if (sztext)
					mir_free(text);
				PUDeletePopup(m_hwnd);
				break;
			}
		}
		break;

	case UM_POPUPMODIFYACTIONICON:
		{
			LPPOPUPACTIONID actionId = (LPPOPUPACTIONID)wParam;
			for (int i=0; i < m_actionCount; ++i)
				if ((m_actions[i].actionA.wParam == actionId->wParam) &&
					(m_actions[i].actionA.lParam == actionId->lParam))
				{
					m_actions[i].actionA.lchIcon = (HICON)lParam;
					animate();
					break;
				}
		}
		break;

	case UM_MENUDONE:
		{
			unlock();
			if (!(PopupOptions.actions&ACT_DEF_KEEPWND))
				PUDeletePopup(m_hwnd);
			break;
		}

	case WM_LBUTTONUP:
		{
			int i;
			for (i = 0; i < m_actionCount; ++i)
				if (m_actions[i].hover)
				{
					SendMessage(m_hwnd, UM_POPUPACTION, m_actions[i].actionA.wParam, m_actions[i].actionA.lParam);
					break;
				}

			if (i == m_actionCount) {
				if (PopupOptions.overrideLeft!=false && (m_hContact!=NULL || PopupOptions.overrideLeft == 5 ||  PopupOptions.overrideLeft == 6)) {
					switch (PopupOptions.overrideLeft){
					default:
					case 1:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_MESSAGE); break;
					case 2:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_REPLY); break;
					case 3:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_DETAILS); break;
					case 4:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_MENU); break;
					case 5:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_DISMISS); break;
					case 6:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_PIN); break;
					case 7:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_COPY); break;
					}
				}
				else {
					lock();
					if (!PerformAction(m_hNotification, m_hwnd, message, wParam, lParam))
						SendMessage(m_hwnd, WM_COMMAND, 0, 0);
					unlock();
				}
			}
			break;
		}

	case WM_MBUTTONUP:
		if ( PopupOptions.overrideMiddle!=false && (m_hContact!=NULL || PopupOptions.overrideMiddle == 5 ||  PopupOptions.overrideMiddle == 6)) {
			switch (PopupOptions.overrideMiddle){
			default:
			case 1:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_MESSAGE); break;
			case 2:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_REPLY); break;
			case 3:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_DETAILS); break;
			case 4:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_MENU); break;
			case 5:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_DISMISS); break;
			case 6:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_PIN); break;
			case 7:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_COPY); break;
			}
		}
		break;

	case WM_CONTEXTMENU:
		if ( PopupOptions.overrideRight!=false && (m_hContact!=NULL || PopupOptions.overrideRight == 5 ||  PopupOptions.overrideRight == 6)) {
			switch (PopupOptions.overrideRight){
			default:
			case 1:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_MESSAGE); break;
			case 2:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_REPLY); break;
			case 3:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_DETAILS); break;
			case 4:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_MENU); break;
			case 5:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_DISMISS); break;
			case 6:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_PIN); break;
			case 7:SendMessage(m_hwnd, UM_POPUPACTION,0, ACT_DEF_COPY); break;
			}
			return TRUE;
		}
		else{
			lock();
			if (PerformAction(m_hNotification, m_hwnd, message, wParam, lParam)) {
				unlock();
				return TRUE;
			}
			unlock();
		}

	case WM_TIMER:
		switch (wParam) {
		case POPUP_TIMER:
			KillTimer(m_hwnd, POPUP_TIMER);
			if (!m_customPopup)
				PopupThreadRequestRemoveWindow(this);
			else {
				if (isLocked())
					updateTimer();
				else
					PostMessage(m_hwnd, WM_CLOSE, 0, 0);
			}
			break;

		case AVATAR_TIMER:
			{
				int newDelay = m_avatar->activeFrameDelay();
				animate();
				if ((newDelay <= 0) || (newDelay != m_avatarFrameDelay)) KillTimer(m_hwnd, AVATAR_TIMER);
				if (newDelay > 0) {
					SetTimer(m_hwnd, AVATAR_TIMER, newDelay, 0);
					m_avatarFrameDelay = newDelay;
				}
				break;
			}

		case CURSOR_TIMER:
			{
				POINT pt; GetCursorPos(&pt);
				if (abs(pt.x-m_ptPrevCursor.x) + abs(pt.y-m_ptPrevCursor.y) > 4) {
					SetWindowLongPtr(m_hwnd, GWL_EXSTYLE, GetWindowLongPtr(m_hwnd, GWL_EXSTYLE) & ~WS_EX_TRANSPARENT);
					KillTimer(m_hwnd, CURSOR_TIMER);
				}
				break;
			}
		}
		break;

	case UM_DESTROYPOPUP:
		KillTimer(m_hwnd, POPUP_TIMER);
		KillTimer(m_hwnd, AVATAR_TIMER);
		KillTimer(m_hwnd, CURSOR_TIMER);
		PostMessage(m_hwnd, WM_CLOSE, 0, 0);
		break;

	case UM_CHANGEPOPUP:
		switch (wParam) {
			case CPT_TEXT:   updateText((char *)lParam);        mir_free((void *)lParam); break;
			case CPT_TEXTW:  updateText((WCHAR *)lParam);       mir_free((void *)lParam); break;
			case CPT_TITLE:  updateTitle((char *)lParam);       mir_free((void *)lParam); break;
			case CPT_TITLEW: updateTitle((WCHAR *)lParam);      mir_free((void *)lParam); break;
			case CPT_DATAW:  updateData((POPUPDATAW_V2 *)lParam);  mir_free((void *)lParam); break;
		}
		update();
		break;

	case UM_CALLMETHOD:
		{
			MethodPtr *method_copy = (MethodPtr *)wParam;
			(this->*(*method_copy))(lParam);
			delete method_copy;
		}
		break;

	case WM_PAINT:
		if (GetUpdateRect(m_hwnd, 0, FALSE)) {
			PAINTSTRUCT ps;
			HDC mydc = BeginPaint(m_hwnd, &ps);
			BitBlt(mydc, 0, 0, m_sz.cx, m_sz.cy, m_bmp->getDC(), 0, 0, SRCCOPY);
			EndPaint(m_hwnd, &ps);
			return 0;
		}
		break;

	case WM_PRINT:
	case WM_PRINTCLIENT:
		BitBlt((HDC)wParam, 0, 0, m_sz.cx, m_sz.cy, m_bmp->getDC(), 0, 0, SRCCOPY);
		break;

	case WM_MOUSEMOVE:
		{
			const PopupSkin *skin = skins.getSkin(m_lpzSkin?m_lpzSkin:m_options->SkinPack);
			if (skin)
				if (skin->onMouseMove(this, LOWORD(lParam), HIWORD(lParam)))
					animate();

			if (m_bIsHovered) break;
			TRACKMOUSEEVENT tme;
			tme.cbSize = sizeof(tme);
			tme.dwFlags = TME_LEAVE;
			tme.dwHoverTime = HOVER_DEFAULT;
			tme.hwndTrack = m_hwnd;
			_TrackMouseEvent(&tme);
			if (!m_customPopup) PopupThreadLock();

			if (m_options->OpaqueOnHover)
				updateLayered(255);

			m_bIsHovered = true;
		}
		break;

	case WM_MOUSELEAVE:
		{
			const PopupSkin *skin = skins.getSkin(m_lpzSkin?m_lpzSkin:m_options->SkinPack);
			if (skin)
				if (skin->onMouseMove(this, LOWORD(lParam), HIWORD(lParam)))
					animate();

			if (!m_bIsHovered) break;

			if (m_options->OpaqueOnHover)
				updateLayered(m_options->UseTransparency ? m_options->Alpha : 255);

			if (!m_customPopup) PopupThreadUnlock();
			m_bIsHovered = false;
		}
		break;

	case WM_CLOSE:
		hide();
		return TRUE;

	case WM_DESTROY:
		if (m_bIsHovered) {
			if (!m_customPopup) PopupThreadUnlock();
			m_bIsHovered = false;
		}
		if (m_hhkAvatarChanged)
			Popup_UnhookEventAsync((WPARAM)m_hwnd, (LPARAM)m_hhkAvatarChanged);
		SendMessage(m_hwnd, UM_FREEPLUGINDATA, 0, 0);
		SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0);
		m_hwnd = 0;

		DestroyWindow(m_hwndToolTip);

		if (!m_customPopup)
			// we can't access "this" pointer after following line!
			PopupThreadRemoveWindow(this);
		else
			delete this;

		PostQuitMessage(0);
		return TRUE;
	}

	if (m_PluginWindowProc && !closing) {
		lock();

		// some plugins use cdecl instead of stdcall
		// this is an attempt to fix them
		BOOL result;

		result = m_PluginWindowProc(this->m_hwnd, message, wParam, lParam);
		unlock();
		return result;
	}

	return NullWindowProc(this->m_hwnd, message, wParam, lParam);
}

LRESULT CALLBACK PopupWnd2::WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PopupWnd2 *wnd = 0;
	if (message == WM_CREATE) {
		LPCREATESTRUCT cs = (LPCREATESTRUCT)lParam;
		wnd = (PopupWnd2 *)cs->lpCreateParams;
		SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)wnd);
	}
	else wnd = (PopupWnd2 *)GetWindowLongPtr(hwnd, GWLP_USERDATA);

	if (wnd) return wnd->WindowProc(message, wParam, lParam);
	return DefWindowProc(hwnd, message, wParam, lParam);
}

void	WindowThread(void *arg)
{
	OleInitialize(NULL); // we may need OLE in this thread for smiley substitution

	PopupWnd2 *wnd = (PopupWnd2 *)arg;
	wnd->buildMText();
	wnd->create();
	PostMessage(wnd->getHwnd(), UM_INITPOPUP, 0, 0);

	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

// Menu Host
LRESULT CALLBACK MenuHostWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HANDLE hContact = NULL;

	switch (message)
	{
		case UM_SHOWMENU:
		{
			hContact = (HANDLE)lParam;
			POINT pt = {0};
			HMENU hMenu = (HMENU)CallService(MS_CLIST_MENUBUILDCONTACT,(WPARAM)hContact,0);
			GetCursorPos(&pt);
			HWND hwndSave = GetForegroundWindow();
			SetForegroundWindow(hwnd);
			TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
			SetForegroundWindow(hwndSave);
			DestroyMenu(hMenu);
			PostMessage((HWND)wParam, UM_MENUDONE, 0, 0);
			break;
		}

		case WM_COMMAND:
		{
			// do not call PluginWindowProc if menu item was clicked. prevent auto-closing...
			if (CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam), MPCF_CONTACTMENU), (LPARAM)hContact))
				return DefWindowProc(hwnd, message, wParam, lParam);
			break;
		}

		case WM_MEASUREITEM:
			return CallService(MS_CLIST_MENUMEASUREITEM,wParam,lParam);

		case WM_DRAWITEM:
			return CallService(MS_CLIST_MENUDRAWITEM,wParam,lParam);		
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}