/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2010 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.
*/
#include "..\..\core\commonheaders.h"
#include "filter.h"

#define OPENOPTIONSDIALOG_OLD_SIZE 12

#define FILTER_TIMEOUT_TIMER 10012

#define ALL_MODULES_FILTER _T("<all modules>")
#define CORE_MODULES_FILTER _T("<core modules>")

static HANDLE hOptionsInitEvent;
static HWND hwndOptions = NULL;
static HWND hFilterSearchWnd = NULL;

// Thread for search keywords in dialogs
static BYTE bSearchState = 0; // 0 - not executed; 1 - in progress; 2 - completed;
static int FilterPage = 0;
static int FilterLoadProgress = 100;
static int FilterTimerId = 0;

struct OptionsPageInit
{
	int pageCount;
	OPTIONSDIALOGPAGE *odp;
};

struct DlgTemplateExBegin
{
	WORD   dlgVer;
	WORD   signature;
	DWORD  helpID;
	DWORD  exStyle;
	DWORD  style;
	WORD   cDlgItems;
	short  x;
	short  y;
	short  cx;
	short  cy;
};

struct OptionsPageData
{
	DLGTEMPLATE *pTemplate;
	DLGPROC dlgProc;
	HINSTANCE hInst;
	HTREEITEM hTreeItem;
	HWND hwnd;
	int changed;
	int simpleHeight, expertHeight;
	int simpleWidth, expertWidth;
	int simpleBottomControlId, simpleRightControlId;
	int nExpertOnlyControls;
	UINT *expertOnlyControls;
	DWORD flags;
	TCHAR *ptszTitle, *ptszGroup, *ptszTab;
	int hLangpack;
	BOOL insideTab;
	LPARAM dwInitParam;

	int offsetX;
	int offsetY;
};

struct OptionsDlgData
{
	OptionsDlgData() :
		arOpd(10)
		{}

	int currentPage;
	HTREEITEM hCurrentPage;
	LIST<OptionsPageData> arOpd;
	RECT rcDisplay;
	RECT rcTab;
	HFONT hBoldFont;
	TCHAR szFilterString[1024];	
	HANDLE hPluginLoad, hPluginUnload;
};

HTREEITEM FindNamedTreeItemAtRoot(HWND hwndTree, const TCHAR* name)
{
	TVITEM tvi;
	TCHAR str[128];

	tvi.mask = TVIF_TEXT;
	tvi.pszText = str;
	tvi.cchTextMax = SIZEOF(str);
	tvi.hItem = TreeView_GetRoot(hwndTree);
	while (tvi.hItem != NULL) {
		SendMessage(hwndTree, TVM_GETITEM, 0, (LPARAM)&tvi);
		if ( !_tcsicmp(str, name))
			return tvi.hItem;

		tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem);
	}
	return NULL;
}

static HTREEITEM FindNamedTreeItemAtChildren(HWND hwndTree, HTREEITEM hItem, const TCHAR* name)
{
	TCHAR str[128];
	TVITEM tvi;
	tvi.mask = TVIF_TEXT;
	tvi.pszText = str;
	tvi.cchTextMax = SIZEOF(str);
	tvi.hItem = TreeView_GetChild(hwndTree, hItem);
	while (tvi.hItem != NULL) {
		SendMessage(hwndTree, TVM_GETITEM, 0, (LPARAM)&tvi);
		if ( !_tcsicmp(str, name))
			return tvi.hItem;

		tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem);
	}
	return NULL;
}

static BOOL CALLBACK BoldGroupTitlesEnumChildren(HWND hwnd, LPARAM lParam)
{
	TCHAR szClass[64];

	GetClassName(hwnd, szClass, SIZEOF(szClass));
	if ( !lstrcmp(szClass, _T("Button")) && (GetWindowLongPtr(hwnd, GWL_STYLE)&0x0F) == BS_GROUPBOX)
		SendMessage(hwnd, WM_SETFONT, lParam, 0);
	return TRUE;
}

struct MoveChildParam
{
	HWND hDlg;
	POINT offset;
};

static BOOL CALLBACK MoveEnumChildren(HWND hwnd, LPARAM lParam)
{
	struct MoveChildParam * param = (struct MoveChildParam *) lParam;

	RECT rcWnd;
	GetWindowRect(hwnd, &rcWnd);

	HWND hwndParent = GetParent(hwnd);
	if (hwndParent != param->hDlg)
		return TRUE;	// Do not move subchilds

	POINT pt = { 0, 0 };

	ClientToScreen(hwndParent, &pt);
	OffsetRect(&rcWnd, -pt.x, -pt.y);

	SetWindowPos(hwnd, NULL, rcWnd.left + param->offset.x, rcWnd.top + param->offset.y, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
	return TRUE;
}

#define OPTSTATE_PREFIX "s_"

static void SaveOptionsTreeState(HWND hdlg)
{
	TVITEMA tvi;
	char buf[130], str[128];
	tvi.mask = TVIF_TEXT | TVIF_STATE;
	tvi.pszText = str;
	tvi.cchTextMax = SIZEOF(str);
	tvi.hItem = TreeView_GetRoot(GetDlgItem(hdlg, IDC_PAGETREE));
	while (tvi.hItem != NULL) {
		if (SendMessageA(GetDlgItem(hdlg, IDC_PAGETREE), TVM_GETITEMA, 0, (LPARAM)&tvi)) {
			mir_snprintf(buf, SIZEOF(buf), "%s%s", OPTSTATE_PREFIX, str);
			DBWriteContactSettingByte(NULL, "Options", buf, (BYTE)((tvi.state&TVIS_EXPANDED)?1:0));
		}
		tvi.hItem = TreeView_GetNextSibling(GetDlgItem(hdlg, IDC_PAGETREE), tvi.hItem);
}	}

#define DM_FOCUSPAGE   (WM_USER+10)
#define DM_REBUILDPAGETREE (WM_USER+11)

#define HM_MODULELOAD (WM_USER+12)
#define HM_MODULEUNLOAD (WM_USER+13)

static void ThemeDialogBackground(HWND hwnd, BOOL tabbed)
{
	if (enableThemeDialogTexture)
		enableThemeDialogTexture(hwnd, (tabbed ? ETDT_ENABLE : ETDT_DISABLE) | ETDT_USETABTEXTURE);
}

static int lstrcmpnull(TCHAR *str1, TCHAR *str2)
{
	if (str1 == NULL && str2 == NULL)
		return 0;
	if (str1 != NULL && str2 == NULL)
		return 1;
	if (str1 == NULL && str2 != NULL)
		return -1;

	return lstrcmp(str1, str2);
}

static TCHAR *GetPluginName(HINSTANCE hInstance, TCHAR *buffer, int size)
{
	TCHAR tszModuleName[MAX_PATH];
	GetModuleFileName(hInstance, tszModuleName, SIZEOF(tszModuleName));
	TCHAR *dllName = _tcsrchr(tszModuleName, '\\');
	if ( !dllName)
	{
		dllName = tszModuleName;
	}
	else {
		dllName++;
	}
	
	_tcsncpy(buffer, dllName, size);
	
	return buffer;
}

PageHash GetPluginPageHash(const OptionsPageData *page)
{
	return mir_hashstrT(page->ptszGroup) + mir_hashstrT(page->ptszTitle) + mir_hashstrT(page->ptszTab);
}

static void FindFilterStrings(int enableKeywordFiltering, int current, HWND hWndParent, const OptionsPageData *page)
{
	TCHAR pluginName[MAX_PATH];
	HWND hWnd = 0;
	if (enableKeywordFiltering) {
		if (current)
			hWnd = page->hwnd;
		else
		{
			hWnd = CreateDialogIndirectParamA(page->hInst, page->pTemplate, hWndParent, page->dlgProc, page->dwInitParam); //create the options dialog page so we can parse it
			ShowWindow(hWnd, SW_HIDE); //make sure it's hidden
	}	}
	
	DWORD key = GetPluginPageHash(page); //get the plugin page hash
	
	TCHAR *PluginFullName = NULL;
	char * temp = GetPluginNameByInstance(page->hInst);
	if (temp) PluginFullName = mir_a2t(temp);
	GetDialogStrings(enableKeywordFiltering, key, GetPluginName(page->hInst, pluginName, SIZEOF(pluginName)), hWnd, page->ptszGroup, page->ptszTitle, page->ptszTab, PluginFullName);
	if (PluginFullName) mir_free(PluginFullName);
	
	if (enableKeywordFiltering && !current)
		DestroyWindow(hWnd); //destroy the page, we're done with it
}

static int MatchesFilter(const OptionsPageData *page, TCHAR *szFilterString)
{
	DWORD key = GetPluginPageHash(page);
	
	return ContainsFilterString(key, szFilterString);
}

static WNDPROC OptionsFilterDefaultProc = NULL;

static LRESULT CALLBACK OptionsFilterSubclassProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message != WM_PAINT && message != WM_PRINT)
		return CallWindowProc(OptionsFilterDefaultProc, hWnd, message, wParam, lParam);

	if (GetFocus() == hWnd || GetWindowTextLength(hWnd)) 
		return CallWindowProc(OptionsFilterDefaultProc, hWnd, message, wParam, lParam);

	RECT rc;
	GetClientRect(hWnd, &rc);
	HDC hdc;
	PAINTSTRUCT paint;

	if (message == WM_PAINT)
		hdc = BeginPaint(hWnd, &paint);
	else
		hdc = (HDC)wParam;

	TCHAR buf[255];
	if (bSearchState == 1 && FilterLoadProgress < 100 && FilterLoadProgress > 0)
		mir_sntprintf(buf, SIZEOF(buf), TranslateT("Loading... %d%%"), FilterLoadProgress);
	else
		mir_sntprintf(buf, SIZEOF(buf), TranslateT("Search"));

	BOOL bDrawnByTheme = FALSE;
	
	int oldMode = SetBkMode(hdc, TRANSPARENT);

	if (openThemeData) {
		HTHEME hTheme = openThemeData(hWnd, L"EDIT");
		if (hTheme) {
			if (isThemeBackgroundPartiallyTransparent(hTheme, EP_EDITTEXT, ETS_NORMAL))
				drawThemeParentBackground(hWnd, hdc, &rc);

			RECT rc2;
			getThemeBackgroundContentRect(hTheme, hdc, EP_EDITTEXT, ETS_NORMAL, &rc, &rc2);
			rc2.top = 2 * rc.top    - rc2.top;
			rc2.left = 2 * rc.left   - rc2.left;
			rc2.bottom = 2 * rc.bottom - rc2.bottom;
			rc2.right = 2 * rc.right  - rc2.right;

			drawThemeBackground(hTheme, hdc, EP_EDITTEXT, ETS_NORMAL, &rc2, &rc);
			HFONT hFont = (HFONT) SendMessage(hWnd, WM_GETFONT, 0, 0);
			HFONT oldFont = (HFONT) SelectObject(hdc, hFont);

			wchar_t *bufW = mir_t2u(buf);
			drawThemeText(hTheme, hdc,  EP_EDITTEXT, ETS_DISABLED, bufW, -1, 0, 0, &rc);
			mir_free(bufW);

			SelectObject(hdc, oldFont);
			closeThemeData(hTheme);
			bDrawnByTheme = TRUE;
		}
	}

	SetBkMode(hdc, oldMode);

	if ( !bDrawnByTheme) {
		HFONT hFont = (HFONT) SendMessage(hWnd, WM_GETFONT, 0, 0);
		HFONT oldFont = (HFONT) SelectObject(hdc, hFont);
		SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
		FillRect(hdc, &rc, GetSysColorBrush(COLOR_WINDOW));
		int oldMode = SetBkMode(hdc, TRANSPARENT);
		DrawText(hdc, buf, -1, &rc, 0);
		SetBkMode(hdc, oldMode);
		SelectObject(hdc, oldFont);
	}

	if (message == WM_PAINT)
		EndPaint(hWnd, &paint);

	return 0;
}

static BOOL CheckPageShow(HWND hdlg, OptionsDlgData* dat, int i)
{
	OptionsPageData* opd = dat->arOpd[i];

	if (dat->szFilterString && dat->szFilterString[0] && !MatchesFilter(opd, dat->szFilterString)) return FALSE;
	if ((opd->flags & ODPF_SIMPLEONLY) && IsDlgButtonChecked(hdlg, IDC_EXPERT)) return FALSE;
	if ((opd->flags & ODPF_EXPERTONLY) && !IsDlgButtonChecked(hdlg, IDC_EXPERT)) return FALSE;
	return TRUE;
}

static BOOL IsAeroMode()
{
	BOOL result;
	return dwmIsCompositionEnabled && (dwmIsCompositionEnabled(&result) == S_OK) && result;
}

static void FreeOptionsData(OptionsPageInit* popi)
{
	for (int i=0; i < popi->pageCount; i++) {
		mir_free((char*)popi->odp[i].pszTitle);
		mir_free(popi->odp[i].pszGroup);
		mir_free(popi->odp[i].pszTab);
		if ((DWORD_PTR)popi->odp[i].pszTemplate & 0xFFFF0000)
			mir_free((char*)popi->odp[i].pszTemplate);
	}
	mir_free(popi->odp);
}

static void FreeOptionsPageData(OptionsPageData *opd)
{
	if (opd->hwnd != NULL)
		DestroyWindow(opd->hwnd);
	mir_free(opd->ptszGroup);
	mir_free(opd->ptszTab);
	mir_free(opd->ptszTitle);
	mir_free(opd->pTemplate);
	mir_free(opd);
}

static void AeroPaintControl(HWND hwnd, HDC hdc, WNDPROC OldWndProc, UINT msg = WM_PRINT, LPARAM lpFlags = PRF_CLIENT|PRF_NONCLIENT)
{
	HBITMAP hBmp, hOldBmp;
	RECT rc; GetClientRect(hwnd, &rc);
	BYTE *pBits;

	HDC tempDC = CreateCompatibleDC(hdc);

	BITMAPINFO bmi;
	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biWidth = rc.right;
	bmi.bmiHeader.biHeight = -rc.bottom;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biBitCount = 32;
	bmi.bmiHeader.biCompression = BI_RGB;
	hBmp = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, (void **)&pBits, NULL, 0);

	hOldBmp = (HBITMAP)SelectObject(tempDC, hBmp);

	//paint
	SetPropA(hwnd, "Miranda.AeroRender.Active", (HANDLE)TRUE);
	CallWindowProc(OldWndProc, hwnd, msg, (WPARAM)tempDC, lpFlags);
	SetPropA(hwnd, "Miranda.AeroRender.Active", (HANDLE)FALSE);

	// Fix alpha channel
	GdiFlush();
	for (int i=0; i < rc.right*rc.bottom; ++i, pBits += 4)
		if ( !pBits[3]) pBits[3] = 255;

	//Copy to output
	BitBlt(hdc, 0, 0, rc.right, rc.bottom, tempDC, 0, 0, SRCCOPY);
	SelectObject(tempDC, hOldBmp);
	DeleteObject(hBmp);
	DeleteDC(tempDC);
}

static LRESULT CALLBACK AeroPaintSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	WNDPROC OldWndProc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA);
	switch (msg)
	{
		case WM_CTLCOLOREDIT:
			if ( !GetPropA((HWND)lParam, "Miranda.AeroRender.Active"))
				RedrawWindow((HWND)lParam, NULL, NULL, RDW_INVALIDATE);
			break;

		case WM_ERASEBKGND:
			return TRUE;

		case WM_PRINT:
		case WM_PRINTCLIENT:
			AeroPaintControl(hwnd, (HDC)wParam, OldWndProc, msg, lParam);
			return TRUE;

		case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hwnd, &ps);
			AeroPaintControl(hwnd, hdc, OldWndProc);
			EndPaint(hwnd, &ps);
			return TRUE;
		}

		case WM_DESTROY:
			RemovePropA(hwnd, "Miranda.AeroRender.Active");
			break;
	}
	return CallWindowProc(OldWndProc, hwnd, msg, wParam, lParam);
}

static void CALLBACK FilterSearchTimerFunc(HWND hwnd, UINT, UINT_PTR, DWORD)
{
	OptionsDlgData* dat = (OptionsDlgData*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
	if ( !dat)
		return;
	
	if (hFilterSearchWnd == NULL)
		hFilterSearchWnd = CreateWindowA("STATIC", "Test", WS_OVERLAPPED|WS_DISABLED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), 0); // Fake window to keep option page focused

	if (FilterPage < dat->arOpd.getCount())
		FindFilterStrings(TRUE, dat->currentPage == FilterPage, hFilterSearchWnd, dat->arOpd[FilterPage]);		

	FilterPage++;
	FilterLoadProgress = FilterPage*100/((dat->arOpd.getCount()) ? dat->arOpd.getCount() : FilterPage);
	if (FilterPage >= dat->arOpd.getCount()) {
		KillTimer(hwnd, FilterTimerId);
		FilterTimerId = 0;
		bSearchState = 2;
		FilterLoadProgress = 100;
		DestroyWindow(hFilterSearchWnd);
		hFilterSearchWnd = NULL;
	}
	RedrawWindow(GetDlgItem(hwnd, IDC_KEYWORD_FILTER), NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_ERASE);
}

static void ExecuteFindFilterStringsTimer(HWND hdlg)
{
	bSearchState = 1;
	FilterPage = 0;
	if (FilterTimerId) KillTimer(hdlg, FilterTimerId);
	FilterTimerId = SetTimer(hdlg, NULL, 1, FilterSearchTimerFunc);
}

static void FillFilterCombo(int enableKeywordFiltering, HWND hDlg, OptionsDlgData* dat)
{
	HINSTANCE* KnownInstances = (HINSTANCE*)alloca(sizeof(HINSTANCE)*dat->arOpd.getCount());
	int countKnownInst = 0;
	SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_RESETCONTENT, 0, 0);
	int index = SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_ADDSTRING, (WPARAM)0, (LPARAM)TranslateTS(ALL_MODULES_FILTER));
	SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_SETITEMDATA, (WPARAM)index, (LPARAM)NULL);
	index = SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_ADDSTRING, (WPARAM)0, (LPARAM)TranslateTS(CORE_MODULES_FILTER));
	SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_SETITEMDATA, (WPARAM)index, (LPARAM)hInst);
	TCHAR* tszModuleName = (TCHAR*)alloca(MAX_PATH*sizeof(TCHAR));
	for (int i=0; i < dat->arOpd.getCount(); i++) {		
		TCHAR *dllName = NULL;
		int j;
		HINSTANCE inst = dat->arOpd[i]->hInst;
		
		if ( !enableKeywordFiltering)
			FindFilterStrings(enableKeywordFiltering, FALSE, hDlg, dat->arOpd[i]); // only modules name (fast enougth)
		
		if (inst == hInst) continue;
		for (j = 0; j<countKnownInst; j++)
			if (KnownInstances[j] == inst) break;
		if (j != countKnownInst) continue;
		KnownInstances[countKnownInst] = inst;
		countKnownInst++;
		GetModuleFileName(inst, tszModuleName, MAX_PATH);
		{
			char * name = GetPluginNameByInstance(inst);
			if (name)
				dllName = mir_a2t(name); 
		}

		if ( !dllName) dllName = mir_tstrdup(_tcsrchr(tszModuleName, _T('\\')));
		if ( !dllName) dllName = mir_tstrdup(tszModuleName);
		
		if (dllName) {
			index = SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_ADDSTRING, (WPARAM)0, (LPARAM)dllName);
			SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_SETITEMDATA, (WPARAM)index, (LPARAM)inst);
			mir_free(dllName);
		}	
	}

	FilterLoadProgress = 100;
	if (enableKeywordFiltering)
		ExecuteFindFilterStringsTimer(hDlg);
}

static BOOL IsInsideTab(HWND hdlg, OptionsDlgData * dat, int i)
{
	OptionsPageData* opd = dat->arOpd[i];
	int pages = 0;
	if (opd->ptszTab != NULL) {
		// Count tabs to calc position
		for (int j = 0; j < dat->arOpd.getCount() && pages < 2; j++) {
			OptionsPageData* opd2 = dat->arOpd[j];
			if ( !CheckPageShow(hdlg, dat, j)) continue;
			if ( lstrcmp(opd2->ptszTitle, opd->ptszTitle) || lstrcmpnull(opd2->ptszGroup, opd->ptszGroup))
				continue;
			pages++;
		}
	}
	return (pages > 1);
}

static bool LoadOptionsPage(OPTIONSDIALOGPAGE *src, OptionsPageData *dst)
{
	HRSRC hrsrc = FindResourceA(src->hInstance, src->pszTemplate, MAKEINTRESOURCEA(5));
	if (hrsrc == NULL) return false;
	
	HGLOBAL hglb = LoadResource(src->hInstance, hrsrc);
	if (hglb == NULL) return false;

	DWORD resSize = SizeofResource(src->hInstance, hrsrc);
	dst->pTemplate = (DLGTEMPLATE*)mir_alloc(resSize);
	memcpy(dst->pTemplate, LockResource(hglb), resSize);
	DlgTemplateExBegin *dte = (struct DlgTemplateExBegin*)dst->pTemplate;
	if (dte->signature == 0xFFFF) {
		//this feels like an access violation, and is according to boundschecker
		//...but it works - for now
		//may well have to remove and sort out the original dialogs
		dte->style &= ~(WS_VISIBLE|WS_CHILD|WS_POPUP|WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|DS_MODALFRAME|DS_CENTER);
		dte->style |= WS_CHILD;
	}
	else {
		dst->pTemplate->style &= ~(WS_VISIBLE|WS_CHILD|WS_POPUP|WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|DS_MODALFRAME|DS_CENTER);
		dst->pTemplate->style |= WS_CHILD;
	}
	dst->dlgProc = src->pfnDlgProc;
	dst->hInst = src->hInstance;
	dst->hwnd = NULL;
	dst->changed = 0;
	dst->simpleHeight = dst->expertHeight = 0;
	dst->simpleBottomControlId = src->nIDBottomSimpleControl;
	dst->simpleWidth = dst->expertWidth = 0;
	dst->simpleRightControlId = src->nIDRightSimpleControl;
	dst->nExpertOnlyControls = src->nExpertOnlyControls;
	dst->expertOnlyControls = src->expertOnlyControls;
	dst->flags = src->flags;
	dst->hLangpack = src->hLangpack;
	dst->dwInitParam = src->dwInitParam;
	if (src->pszTitle == NULL)
		dst->ptszTitle = NULL;
	else if (src->flags & ODPF_UNICODE)
		dst->ptszTitle = mir_tstrdup(src->ptszTitle);
	else 
		dst->ptszTitle = mir_a2t(src->pszTitle);

	if (src->pszGroup == NULL)
		dst->ptszGroup = NULL;
	else if (src->flags & ODPF_UNICODE)
		dst->ptszGroup = mir_tstrdup(src->ptszGroup);
	else
		dst->ptszGroup = mir_a2t(src->pszGroup);

	if (src->pszTab == NULL)
		dst->ptszTab = NULL;
	else if (src->flags & ODPF_UNICODE)
		dst->ptszTab = mir_tstrdup(src->ptszTab);
	else
		dst->ptszTab = mir_a2t(src->pszTab);
	
	return true;
}

static void LoadOptionsModule(HWND hdlg, OptionsDlgData *dat, HINSTANCE hInst)
{
	OptionsPageInit opi = { 0 };
	CallPluginEventHook(hInst, hOptionsInitEvent, (WPARAM)&opi, 0);
	if (opi.pageCount == 0)
		return;

	for (int i=0; i < opi.pageCount; i++) {
		OptionsPageData* opd = (OptionsPageData*)mir_calloc(sizeof(OptionsPageData));
		if ( LoadOptionsPage(&opi.odp[i], opd))
			dat->arOpd.insert(opd);
		else
			mir_free(opd);
	}

	FreeOptionsData(&opi);
	PostMessage(hdlg, DM_REBUILDPAGETREE, 0, 0);
}

static void UnloadOptionsModule(HWND hdlg, OptionsDlgData *dat, HINSTANCE hInst)
{
	bool bToRebuildTree = false;

	for (int i=0; i < dat->arOpd.getCount(); i++) {
		OptionsPageData* opd = dat->arOpd[i];
		if (opd->hInst != hInst)
			continue;

		if (dat->currentPage > i)
			dat->currentPage--;
		FreeOptionsPageData(opd);
		dat->arOpd.remove(i);
		bToRebuildTree = true;
	}

	if (bToRebuildTree)
		PostMessage(hdlg, DM_REBUILDPAGETREE, 0, 0);
}

static INT_PTR CALLBACK OptionsDlgProc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	OptionsDlgData* dat = (OptionsDlgData*)GetWindowLongPtr(hdlg, GWLP_USERDATA);
	HWND hwndTree = GetDlgItem(hdlg, IDC_PAGETREE);

	switch (message) {
	case WM_CTLCOLORSTATIC:
		switch (GetDlgCtrlID((HWND)lParam)) {
		case IDC_WHITERECT:
		case IDC_KEYWORD_FILTER:
			SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW));
			return (INT_PTR)GetSysColorBrush(COLOR_WINDOW);
		}
		break;

	case WM_INITDIALOG:
		TranslateDialogDefault(hdlg);
		{	
			PROPSHEETHEADER *psh = (PROPSHEETHEADER*)lParam;
			OPENOPTIONSDIALOG *ood = (OPENOPTIONSDIALOG*)psh->pStartPage;
			TCHAR *lastPage = NULL, *lastGroup = NULL, *lastTab = NULL;
			DBVARIANT dbv;

			typedef BOOL (STDAPICALLTYPE *pfnGetComboBoxInfo)(HWND, PCOMBOBOXINFO);
			pfnGetComboBoxInfo getComboBoxInfo = (pfnGetComboBoxInfo)GetProcAddress(GetModuleHandleA("user32"), "GetComboBoxInfo"); 
			if (getComboBoxInfo) {
				COMBOBOXINFO cbi;
				cbi.cbSize = sizeof(COMBOBOXINFO);
				getComboBoxInfo(GetDlgItem(hdlg, IDC_KEYWORD_FILTER), &cbi);
				OptionsFilterDefaultProc = (WNDPROC)SetWindowLongPtr(cbi.hwndItem, GWLP_WNDPROC, (LONG_PTR) OptionsFilterSubclassProc);

				if (IsAeroMode()) {
					SetWindowLongPtr(cbi.hwndCombo, GWLP_USERDATA, GetWindowLongPtr(cbi.hwndCombo, GWLP_WNDPROC));
					SetWindowLongPtr(cbi.hwndCombo, GWLP_WNDPROC, (LONG_PTR)AeroPaintSubclassProc);
					SetWindowLongPtr(cbi.hwndItem, GWLP_USERDATA, GetWindowLongPtr(cbi.hwndItem, GWLP_WNDPROC));
					SetWindowLongPtr(cbi.hwndItem, GWLP_WNDPROC, (LONG_PTR)AeroPaintSubclassProc);
				}
			}

			Utils_RestoreWindowPositionNoSize(hdlg, NULL, "Options", "");
			Window_SetIcon_IcoLib(hdlg, SKINICON_OTHER_OPTIONS);
			CheckDlgButton(hdlg, IDC_EXPERT, DBGetContactSettingByte(NULL, "Options", "Expert", SETTING_SHOWEXPERT_DEFAULT)?BST_CHECKED:BST_UNCHECKED);
			EnableWindow(GetDlgItem(hdlg, IDC_APPLY), FALSE);
			dat = new OptionsDlgData;
			SetWindowLongPtr(hdlg, GWLP_USERDATA, (LONG_PTR)dat);
			SetWindowText(hdlg, psh->pszCaption);
			
			LOGFONT lf;
			dat->hBoldFont = (HFONT)SendDlgItemMessage(hdlg, IDC_EXPERT, WM_GETFONT, 0, 0);
			GetObject(dat->hBoldFont, sizeof(lf), &lf);
			lf.lfWeight = FW_BOLD;
			dat->hBoldFont = CreateFontIndirect(&lf);
			
			dat->hPluginLoad = HookEventMessage(ME_SYSTEM_MODULELOAD, hdlg, HM_MODULELOAD);
			dat->hPluginUnload = HookEventMessage(ME_SYSTEM_MODULEUNLOAD, hdlg, HM_MODULEUNLOAD);

			dat->currentPage = -1;
			if (ood->pszPage == NULL) {
				if ( !DBGetContactSettingTString(NULL, "Options", "LastPage", &dbv)) {
					lastPage = mir_tstrdup(dbv.ptszVal);
					DBFreeVariant(&dbv);
				}

				if (ood->pszGroup == NULL) {
					if ( !DBGetContactSettingTString(NULL, "Options", "LastGroup", &dbv)) {
						lastGroup = mir_tstrdup(dbv.ptszVal);
						DBFreeVariant(&dbv);
					}
				}
				else lastGroup = Langpack_PcharToTchar(ood->pszGroup);
			}
			else {
				lastPage = Langpack_PcharToTchar(ood->pszPage);
				lastGroup = (ood->pszGroup == NULL) ? NULL : Langpack_PcharToTchar(ood->pszGroup);
			}

			if (ood->pszTab == NULL) {
				if ( !DBGetContactSettingTString(NULL, "Options", "LastTab", &dbv)) {
					lastTab = mir_tstrdup(dbv.ptszVal);
					DBFreeVariant(&dbv);
				}
			}
			else lastTab = Langpack_PcharToTchar(ood->pszTab);

			OPTIONSDIALOGPAGE *odp = (OPTIONSDIALOGPAGE*)psh->ppsp;
			for (size_t i=0; i < psh->nPages; i++, odp++) {
				OptionsPageData* opd = (OptionsPageData*)mir_calloc(sizeof(OptionsPageData));
				if ( !LoadOptionsPage(odp, opd)) {
					mir_free(opd);
					continue;
				}
				dat->arOpd.insert(opd);

				if ( !lstrcmp(lastPage, odp->ptszTitle) && !lstrcmpnull(lastGroup, odp->ptszGroup) &&
					  ((ood->pszTab == NULL && dat->currentPage == -1) || !lstrcmpnull(lastTab, odp->ptszTab)))
					dat->currentPage = (int)i;
			}
			mir_free(lastGroup);
			mir_free(lastPage);
			mir_free(lastTab);

			GetWindowRect(GetDlgItem(hdlg, IDC_STNOPAGE), &dat->rcDisplay);
			MapWindowPoints(NULL, hdlg, (LPPOINT)&dat->rcDisplay, 2);

			// Add an item to count in height
			TCITEM tie;
			tie.mask = TCIF_TEXT | TCIF_IMAGE;
			tie.iImage = -1;
			tie.pszText = _T("X");
			TabCtrl_InsertItem(GetDlgItem(hdlg, IDC_TAB), 0, &tie);

			GetWindowRect(GetDlgItem(hdlg, IDC_TAB), &dat->rcTab);
			MapWindowPoints(NULL, hdlg, (LPPOINT)&dat->rcTab, 2);
			TabCtrl_AdjustRect(GetDlgItem(hdlg, IDC_TAB), FALSE, &dat->rcTab);

			//!!!!!!!!!! int enableKeywordFiltering = DBGetContactSettingWord(NULL, "Options", "EnableKeywordFiltering", TRUE); 
			FillFilterCombo(0, hdlg, dat); //!!!!!!!!!!  enableKeywordFiltering, 
			SendMessage(hdlg, DM_REBUILDPAGETREE, 0, 0);

			return TRUE;
		}
	case DM_REBUILDPAGETREE:
		{	
			BOOL bRemoveFocusFromFilter = FALSE;
			HINSTANCE FilterInst = NULL;

			LPARAM oldSel = SendDlgItemMessage(hdlg, IDC_KEYWORD_FILTER, CB_GETEDITSEL, 0, 0);
			GetDlgItemText(hdlg, IDC_KEYWORD_FILTER, dat->szFilterString, SIZEOF(dat->szFilterString));
			
			//if filter string is set to all modules then make the filter string empty (this will return all modules)
			if (_tcscmp(dat->szFilterString, TranslateTS(ALL_MODULES_FILTER)) == 0) {
				dat->szFilterString[0] = 0; 
				bRemoveFocusFromFilter = TRUE;
			} 
			//if filter string is set to core modules replace it with the name of the executable (this will return all core modules)
			else if (_tcscmp(dat->szFilterString, TranslateTS(CORE_MODULES_FILTER)) == 0) {
				//replace string with process name - that will show core settings
				TCHAR szFileName[300];
				GetModuleFileName(NULL, szFileName, SIZEOF(szFileName));
				TCHAR *pos = _tcsrchr(szFileName, _T('\\'));
				if (pos)
					pos++;
				else
					pos = szFileName;
				
				_tcsncpy(dat->szFilterString, pos, SIZEOF(dat->szFilterString));
			}
			else  {
				int sel = SendMessage(GetDlgItem(hdlg, IDC_KEYWORD_FILTER), (UINT) CB_GETCURSEL, 0, 0);
				if (sel != -1) {
					HINSTANCE hinst = (HINSTANCE)SendMessage(GetDlgItem(hdlg, IDC_KEYWORD_FILTER), (UINT) CB_GETITEMDATA,  sel , 0);
					TCHAR szFileName[300];
					GetModuleFileName(hinst, szFileName, SIZEOF(szFileName));
					TCHAR *pos = _tcsrchr(szFileName, _T('\\'));
					if (pos) pos++;
					else pos = szFileName;
					_tcsncpy(dat->szFilterString, pos, SIZEOF(dat->szFilterString));
			}	}
			
			_tcslwr_locale(dat->szFilterString); //all strings are stored as lowercase ... make sure filter string is lowercase too

			ShowWindow(hwndTree, SW_HIDE);	 //deleteall is annoyingly visible
			
			HWND oldWnd = NULL;
			HWND oldTab = NULL; 
			if (dat->currentPage != (-1)) {	
				oldWnd = dat->arOpd[dat->currentPage]->hwnd;
				if (dat->arOpd[dat->currentPage]->insideTab)
					oldTab = GetDlgItem(hdlg, IDC_TAB); 
			}			

			dat->hCurrentPage = NULL;

			TreeView_SelectItem(hwndTree, NULL);
			TreeView_DeleteAllItems(hwndTree);
			
			TVINSERTSTRUCT tvis;
			tvis.hParent = NULL;
			tvis.hInsertAfter = TVI_SORT;
			tvis.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM;
			tvis.item.state = tvis.item.stateMask = TVIS_EXPANDED;
			for (int i=0; i < dat->arOpd.getCount(); i++) {
				static TCHAR *fullTitle = NULL;
				mir_free(fullTitle); fullTitle = NULL;
				if ( !CheckPageShow(hdlg, dat, i))
					continue;

				OptionsPageData* opd = dat->arOpd[i];
				TCHAR* ptszGroup = TranslateTH(opd->hLangpack, opd->ptszGroup);
				TCHAR* ptszTitle = TranslateTH(opd->hLangpack, opd->ptszTitle);
				TCHAR* ptszTab = TranslateTH(opd->hLangpack, opd->ptszTab);

				tvis.hParent = NULL;
				if (FilterInst != NULL) {
					size_t sz = ptszGroup ? _tcslen(ptszGroup)+1:0;
					if (sz) sz+=3;
					sz += ptszTitle ? _tcslen(ptszTitle)+1 : 0;
					fullTitle = (TCHAR*)mir_alloc(sz*sizeof(TCHAR));
					mir_sntprintf(fullTitle, sz, (ptszGroup && ptszTitle)?_T("%s - %s"):_T("%s%s"), 
						ptszGroup ? ptszGroup : _T(""), 
						ptszTitle ? ptszTitle : _T(""));
				}
				TCHAR *useTitle = fullTitle ? fullTitle : ptszTitle;
				if (ptszGroup != NULL && FilterInst == NULL) {
					tvis.hParent = FindNamedTreeItemAtRoot(hwndTree, ptszGroup);
					if (tvis.hParent == NULL) {
						tvis.item.lParam = -1;
						tvis.item.pszText = ptszGroup;
						tvis.hParent = TreeView_InsertItem(hwndTree, &tvis);
					}
				}
				else {
					TVITEM tvi;
					tvi.hItem = FindNamedTreeItemAtRoot(hwndTree, useTitle);
					if (tvi.hItem != NULL) {
						if (i == dat->currentPage) dat->hCurrentPage = tvi.hItem;
						tvi.mask = TVIF_PARAM;
						TreeView_GetItem(hwndTree, &tvi);
						if (tvi.lParam == -1) {
							tvi.lParam = i;
							TreeView_SetItem(hwndTree, &tvi);
							continue;
				}	}	}

				if (ptszTab != NULL) {
					HTREEITEM hItem;
					if (tvis.hParent == NULL)
						hItem = FindNamedTreeItemAtRoot(hwndTree, useTitle);
					else
						hItem = FindNamedTreeItemAtChildren(hwndTree, tvis.hParent, useTitle);
					if (hItem != NULL) {
						if (i == dat->currentPage) {
							TVITEM tvi;
							tvi.hItem = hItem;
							tvi.mask = TVIF_PARAM;
							tvi.lParam = dat->currentPage;
							TreeView_SetItem(hwndTree, &tvi);
							dat->hCurrentPage = hItem;
						}
						continue;
					}
				}

				tvis.item.pszText = useTitle;
				tvis.item.lParam = i;
				opd->hTreeItem = TreeView_InsertItem(hwndTree, &tvis);
				if (i == dat->currentPage)
					dat->hCurrentPage = opd->hTreeItem;	

				if (fullTitle) mir_free(fullTitle);
				fullTitle = NULL;
			}
			{
				char str[128];
				TVITEMA tvi;
				tvi.mask = TVIF_TEXT | TVIF_STATE;
				tvi.pszText = str;
				tvi.cchTextMax = SIZEOF(str);
				tvi.hItem = TreeView_GetRoot(hwndTree);
				while (tvi.hItem != NULL) {
					if (SendMessageA(hwndTree, TVM_GETITEMA, 0, (LPARAM)&tvi)) {
						char buf[130];
						mir_snprintf(buf, SIZEOF(buf), "%s%s", OPTSTATE_PREFIX, str);
						if ( !DBGetContactSettingByte(NULL, "Options", buf, 1))
							TreeView_Expand(hwndTree, tvi.hItem, TVE_COLLAPSE);
					}
					tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem);
				}
			}

			if (dat->hCurrentPage == NULL) {
				dat->hCurrentPage = TreeView_GetRoot(hwndTree);
				dat->currentPage = -1;
			}
			TreeView_SelectItem(hwndTree, dat->hCurrentPage);
			
			if (oldWnd) {
				if (dat->currentPage == -1 || oldWnd != dat->arOpd[dat->currentPage]->hwnd) {
					ShowWindow(oldWnd, SW_HIDE);
					if (oldTab && (dat->currentPage == -1 || !dat->arOpd[dat->currentPage]->insideTab))
						ShowWindow(oldTab, SW_HIDE);		
				}	
			}

			if (dat->szFilterString[0] == 0) // Clear the keyword combo box
				SetWindowText(GetDlgItem(hdlg, IDC_KEYWORD_FILTER), _T(""));
			if ( !bRemoveFocusFromFilter)
				SetFocus(GetDlgItem(hdlg, IDC_KEYWORD_FILTER)); //set the focus back to the combo box

			SendDlgItemMessage(hdlg, IDC_KEYWORD_FILTER, CB_SETEDITSEL, 0, oldSel); //but don't select any of the text
			
			ShowWindow(hwndTree, SW_SHOW);
		}
		break;

	case HM_MODULELOAD:
		LoadOptionsModule(hdlg, dat, (HINSTANCE)lParam);
		break;

	case HM_MODULEUNLOAD:
		UnloadOptionsModule(hdlg, dat, (HINSTANCE)lParam);
		break;

	case PSM_CHANGED:
		EnableWindow(GetDlgItem(hdlg, IDC_APPLY), TRUE);
		if (dat->currentPage != (-1)) dat->arOpd[dat->currentPage]->changed = 1;
		return TRUE;

	case PSM_ISEXPERT:
		SetWindowLongPtr(hdlg, DWLP_MSGRESULT, IsDlgButtonChecked(hdlg, IDC_EXPERT));
		return TRUE;

	case PSM_GETBOLDFONT:
		SetWindowLongPtr(hdlg, DWLP_MSGRESULT, (LONG_PTR)dat->hBoldFont);
		return TRUE;

	case WM_NOTIFY:
		switch(wParam) {
		case IDC_TAB:
		case IDC_PAGETREE:
			switch(((LPNMHDR)lParam)->code) {
			case TVN_ITEMEXPANDING:
				SetWindowLongPtr(hdlg, DWLP_MSGRESULT, FALSE);
				return TRUE;

			case TCN_SELCHANGING:
			case TVN_SELCHANGING:
				if (dat->currentPage != -1 && dat->arOpd[dat->currentPage]->hwnd != NULL) {
					PSHNOTIFY pshn;
					pshn.hdr.code = PSN_KILLACTIVE;
					pshn.hdr.hwndFrom = dat->arOpd[dat->currentPage]->hwnd;
					pshn.hdr.idFrom = 0;
					pshn.lParam = 0;
					if (SendMessage(dat->arOpd[dat->currentPage]->hwnd, WM_NOTIFY, 0, (LPARAM)&pshn)) {
						SetWindowLongPtr(hdlg, DWLP_MSGRESULT, TRUE);
						return TRUE;
					}
				}
				break;

			case TCN_SELCHANGE:
			case TVN_SELCHANGED:
				ShowWindow(GetDlgItem(hdlg, IDC_STNOPAGE), SW_HIDE);
				if (dat->currentPage != -1 && dat->arOpd[dat->currentPage]->hwnd != NULL)
					ShowWindow(dat->arOpd[dat->currentPage]->hwnd, SW_HIDE);

				if ((wParam != IDC_TAB)) {
					TVITEM tvi;
					tvi.hItem = dat->hCurrentPage = TreeView_GetSelection(hwndTree);
					if (tvi.hItem == NULL) break;
					tvi.mask = TVIF_HANDLE|TVIF_PARAM;
					TreeView_GetItem(hwndTree, &tvi);
					dat->currentPage = tvi.lParam;
					ShowWindow(GetDlgItem(hdlg, IDC_TAB), SW_HIDE);
				} 
				else {
					TCITEM tie;
					TVITEM tvi;

					tie.mask = TCIF_PARAM;
					TabCtrl_GetItem(GetDlgItem(hdlg, IDC_TAB), TabCtrl_GetCurSel(GetDlgItem(hdlg, IDC_TAB)), &tie);
					dat->currentPage = tie.lParam;

					tvi.hItem = dat->hCurrentPage;
					tvi.mask = TVIF_PARAM;
					tvi.lParam = dat->currentPage;
					TreeView_SetItem(hwndTree, &tvi);
				}
				if (dat->currentPage != -1) {
					OptionsPageData* p = dat->arOpd[dat->currentPage];
					if (p->hwnd == NULL) {
						RECT rcPage;
						RECT rcControl, rc;
						int w, h;

						p->hwnd = CreateDialogIndirectParamA(p->hInst, p->pTemplate, hdlg, p->dlgProc, p->dwInitParam);
						if (p->flags & ODPF_BOLDGROUPS)
							EnumChildWindows(p->hwnd, BoldGroupTitlesEnumChildren, (LPARAM)dat->hBoldFont);
						GetClientRect(p->hwnd, &rcPage);
						p->expertWidth = rcPage.right;
						p->expertHeight = rcPage.bottom;
						GetWindowRect(p->hwnd, &rc);

						if (p->simpleBottomControlId) {
							GetWindowRect(GetDlgItem(p->hwnd, p->simpleBottomControlId), &rcControl);
							p->simpleHeight = rcControl.bottom-rc.top;
						}
						else p->simpleHeight = p->expertHeight;

						if (p->simpleRightControlId) {
							GetWindowRect(GetDlgItem(p->hwnd, p->simpleRightControlId), &rcControl);
							p->simpleWidth = rcControl.right-rc.left;
						}
						else p->simpleWidth = p->expertWidth;

						if (IsDlgButtonChecked(hdlg, IDC_EXPERT)) {
							w = p->expertWidth;
							h = p->expertHeight;
						}
						else {
							for (int i=0; i < p->nExpertOnlyControls; i++)
								ShowWindow(GetDlgItem(p->hwnd, p->expertOnlyControls[i]), SW_HIDE);
							w = p->simpleWidth;
							h = p->simpleHeight;
						}

						p->offsetX = 0;
						p->offsetY = 0;

						p->insideTab = IsInsideTab(hdlg, dat, dat->currentPage);
						if (p->insideTab) {
							SetWindowPos(p->hwnd, HWND_TOP, (dat->rcTab.left+dat->rcTab.right-w)>>1, dat->rcTab.top, w, h, 0);
							ThemeDialogBackground(p->hwnd, TRUE);
						}
						else {
							SetWindowPos(p->hwnd, HWND_TOP, (dat->rcDisplay.left+dat->rcDisplay.right-w)>>1, (dat->rcDisplay.top+dat->rcDisplay.bottom-h)>>1, w, h, 0);
							ThemeDialogBackground(p->hwnd, FALSE);
						}
					}
					if (wParam != IDC_TAB) {
						p->insideTab = IsInsideTab(hdlg, dat, dat->currentPage);
						if (p->insideTab) {
							// Make tabbed pane
							int pages = 0, sel = 0;
							HWND hwndTab = GetDlgItem(hdlg, IDC_TAB);
							TabCtrl_DeleteAllItems(hwndTab);

							TCITEM tie;
							tie.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
							tie.iImage = -1;
							for (int i=0; i < dat->arOpd.getCount(); i++) {
								if ( !CheckPageShow(hdlg, dat, i))
									continue;

								OptionsPageData* opd = dat->arOpd[i];
								if ( lstrcmp(opd->ptszTitle, p->ptszTitle) || lstrcmpnull(opd->ptszGroup, p->ptszGroup))
									continue;

								tie.pszText = TranslateTH(opd->hLangpack, opd->ptszTab);
								tie.lParam = i;
								TabCtrl_InsertItem(hwndTab, pages, &tie);
								if ( !lstrcmp(opd->ptszTab, p->ptszTab))
									sel = pages;
								pages++;
							}
							TabCtrl_SetCurSel(hwndTab, sel);
							ShowWindow(hwndTab, p->insideTab ? SW_SHOW : SW_HIDE);
						}

						if (p->insideTab) 
							ThemeDialogBackground(p->hwnd, TRUE);
						else 
							ThemeDialogBackground(p->hwnd, FALSE);
					}

					// Resizing
					if ( !p->simpleBottomControlId) {
						int pageWidth, pageHeight;

						if (IsDlgButtonChecked(hdlg, IDC_EXPERT)) {
							pageWidth = p->expertWidth;
							pageHeight = p->expertHeight;
						}
						else {
							pageWidth = p->simpleWidth;
							pageHeight = p->simpleHeight;
						}

						RECT* parentPageRect = &dat->rcDisplay;
						if (p->insideTab)
							parentPageRect = &dat->rcTab;

						pageHeight = min(pageHeight, parentPageRect->bottom - parentPageRect->top);
						pageWidth = min(pageWidth,  parentPageRect->right - parentPageRect->left);

						int newOffsetX = (parentPageRect->right - parentPageRect->left - pageWidth) >> 1;
						int newOffsetY = p->insideTab ? 0 : (parentPageRect->bottom - parentPageRect->top - pageHeight) >> 1;

						struct MoveChildParam mcp;
						mcp.hDlg = p->hwnd;
						mcp.offset.x = newOffsetX - p->offsetX;
						mcp.offset.y = newOffsetY - p->offsetY;
													
						if (mcp.offset.x || mcp.offset.y) {
							EnumChildWindows(p->hwnd, MoveEnumChildren, (LPARAM)(&mcp));

							SetWindowPos(p->hwnd, NULL, 
								parentPageRect->left, parentPageRect->top, 
								parentPageRect->right - parentPageRect->left, 
								parentPageRect->bottom - parentPageRect->top, 
								SWP_NOZORDER | SWP_NOACTIVATE);
							p->offsetX = newOffsetX;
							p->offsetY = newOffsetY;
						}
							
					}						

					ShowWindow(p->hwnd, SW_SHOW);
					if (((LPNMTREEVIEW)lParam)->action == TVC_BYMOUSE) PostMessage(hdlg, DM_FOCUSPAGE, 0, 0);
					else SetFocus(hwndTree);
				}
				else ShowWindow(GetDlgItem(hdlg, IDC_STNOPAGE), SW_SHOW);
				break;
		}	}
		break;

	case DM_FOCUSPAGE:
		if (dat->currentPage != -1)
			SetFocus(dat->arOpd[dat->currentPage]->hwnd);
		break;
	
	case WM_TIMER:
		if (wParam == FILTER_TIMEOUT_TIMER) {
			SaveOptionsTreeState(hdlg);
			SendMessage(hdlg, DM_REBUILDPAGETREE, 0, 0);

			KillTimer(hdlg, FILTER_TIMEOUT_TIMER);
		}
		break;	
	
	case WM_COMMAND:
		switch(LOWORD(wParam)) {
		case IDC_KEYWORD_FILTER:
			//add a timer - when the timer elapses filter the option pages
			if ((HIWORD(wParam) == CBN_SELCHANGE) || (HIWORD(wParam) == CBN_EDITCHANGE))
				if ( !SetTimer(hdlg, FILTER_TIMEOUT_TIMER, 400, NULL))
					MessageBeep(MB_ICONSTOP);

			break;

		case IDC_EXPERT:
			{
				int expert = IsDlgButtonChecked(hdlg, IDC_EXPERT);
				RECT rcPage;
				int neww, newh;

				DBWriteContactSettingByte(NULL, "Options", "Expert", (BYTE)expert);

				PSHNOTIFY pshn;
				pshn.hdr.idFrom = 0;
				pshn.lParam = expert;
				pshn.hdr.code = PSN_EXPERTCHANGED;

				for (int i=0; i <dat->arOpd.getCount(); i++) {
					OptionsPageData *opd = dat->arOpd[i];
					if (opd->hwnd == NULL) continue;
					if ( !CheckPageShow(hdlg, dat, i)) continue;
					//if ((opd->flags & ODPF_SIMPLEONLY) && expert) continue;
					//if ((opd->flags & ODPF_EXPERTONLY) && !expert) continue;
					pshn.hdr.hwndFrom = opd->hwnd;
					SendMessage(opd->hwnd, WM_NOTIFY, 0, (LPARAM)&pshn);

					for (int j = 0; j < opd->nExpertOnlyControls; j++)
						ShowWindow(GetDlgItem(opd->hwnd, opd->expertOnlyControls[j]), expert ? SW_SHOW : SW_HIDE);

					opd->insideTab = IsInsideTab(hdlg, dat, i);

					GetWindowRect(opd->hwnd, &rcPage);
					if (opd->simpleBottomControlId)
						newh = expert ? opd->expertHeight : opd->simpleHeight;
					else 
						newh = rcPage.bottom - rcPage.top;
					if (opd->simpleRightControlId)
						neww = expert ? opd->expertWidth : opd->simpleWidth;
					else 
						neww = rcPage.right - rcPage.left;
					
					if (i == dat->currentPage) {
						POINT ptStart, ptEnd, ptNow;
						DWORD thisTick, startTick;
						RECT rc;

						ptNow.x = ptNow.y = 0;
						ClientToScreen(hdlg, &ptNow);
						GetWindowRect(opd->hwnd, &rc);
						ptStart.x = rc.left-ptNow.x;
						ptStart.y = rc.top-ptNow.y;
						if (opd->insideTab) {
							ptEnd.x = (dat->rcTab.left+dat->rcTab.right-neww)>>1;
							ptEnd.y = dat->rcTab.top;
						}
						else {
							ptEnd.x = (dat->rcDisplay.left+dat->rcDisplay.right-neww)>>1;
							ptEnd.y = (dat->rcDisplay.top+dat->rcDisplay.bottom-newh)>>1;
						}
						if (abs(ptEnd.x-ptStart.x)>5 || abs(ptEnd.y-ptStart.y)>5) {
							startTick = GetTickCount();
							SetWindowPos(opd->hwnd, HWND_TOP, 0, 0, min(neww, rcPage.right), min(newh, rcPage.bottom), SWP_NOMOVE);
							UpdateWindow(opd->hwnd);
							for (;;) {
								thisTick = GetTickCount();
								if (thisTick>startTick+100) break;
								ptNow.x = ptStart.x+(ptEnd.x-ptStart.x)*(int)(thisTick-startTick)/100;
								ptNow.y = ptStart.y+(ptEnd.y-ptStart.y)*(int)(thisTick-startTick)/100;
								SetWindowPos(opd->hwnd, 0, ptNow.x, ptNow.y, 0, 0, SWP_NOZORDER|SWP_NOSIZE);
							}
						}
						if (opd->insideTab)
							ShowWindow(GetDlgItem(hdlg, IDC_TAB), SW_SHOW);
						else
							ShowWindow(GetDlgItem(hdlg, IDC_TAB), SW_HIDE);
					}

					if (opd->insideTab) {
						SetWindowPos(opd->hwnd, HWND_TOP, (dat->rcTab.left+dat->rcTab.right-neww)>>1, dat->rcTab.top, neww, newh, 0);
						ThemeDialogBackground(opd->hwnd, TRUE);
					} 
					else {
						SetWindowPos(opd->hwnd, HWND_TOP, (dat->rcDisplay.left+dat->rcDisplay.right-neww)>>1, (dat->rcDisplay.top+dat->rcDisplay.bottom-newh)>>1, neww, newh, 0);
						ThemeDialogBackground(opd->hwnd, FALSE);
					}
				}
				SaveOptionsTreeState(hdlg);
				SendMessage(hdlg, DM_REBUILDPAGETREE, 0, 0);
				break;
			}
		case IDCANCEL:
			{	
				PSHNOTIFY pshn;
				pshn.hdr.idFrom = 0;
				pshn.lParam = 0;
				pshn.hdr.code = PSN_RESET;
				for (int i=0;i<dat->arOpd.getCount();i++) {
					if (dat->arOpd[i]->hwnd == NULL || !dat->arOpd[i]->changed)
						continue;
					pshn.hdr.hwndFrom = dat->arOpd[i]->hwnd;
					SendMessage(dat->arOpd[i]->hwnd, WM_NOTIFY, 0, (LPARAM)&pshn);
				}
				DestroyWindow(hdlg);
			}
			break;

		case IDOK:
		case IDC_APPLY:
			{
				if (LOWORD(wParam) == IDOK && GetParent(GetFocus()) == GetDlgItem(hdlg, IDC_KEYWORD_FILTER))
					return TRUE;
				
				PSHNOTIFY pshn;
				EnableWindow(GetDlgItem(hdlg, IDC_APPLY), FALSE);
				SetFocus(hwndTree);
				if (dat->currentPage != (-1)) {
					pshn.hdr.idFrom = 0;
					pshn.lParam = 0;
					pshn.hdr.code = PSN_KILLACTIVE;
					pshn.hdr.hwndFrom = dat->arOpd[dat->currentPage]->hwnd;
					if (SendMessage(dat->arOpd[dat->currentPage]->hwnd, WM_NOTIFY, 0, (LPARAM)&pshn))
						break;
				}

				pshn.hdr.code = PSN_APPLY;
				for (int i=0;i<dat->arOpd.getCount();i++) {
					if (dat->arOpd[i]->hwnd == NULL || !dat->arOpd[i]->changed) continue;
					dat->arOpd[i]->changed = 0;
					pshn.hdr.hwndFrom = dat->arOpd[i]->hwnd;
					if (SendMessage(dat->arOpd[i]->hwnd, WM_NOTIFY, 0, (LPARAM)&pshn) == PSNRET_INVALID_NOCHANGEPAGE) {
						dat->hCurrentPage = dat->arOpd[i]->hTreeItem;
						TreeView_SelectItem(hwndTree, dat->hCurrentPage);
						if (dat->currentPage != (-1)) ShowWindow(dat->arOpd[dat->currentPage]->hwnd, SW_HIDE);
						dat->currentPage = i;
						if (dat->currentPage != (-1)) ShowWindow(dat->arOpd[dat->currentPage]->hwnd, SW_SHOW);
						return 0;
				}	}

				if (LOWORD(wParam) == IDOK)
					DestroyWindow(hdlg);
			}
			break;
		}
		break;

	case WM_DESTROY:
		if (FilterTimerId)
			KillTimer (hdlg, FilterTimerId);
		DestroyWindow (hFilterSearchWnd);
		ClearFilterStrings();
		dat->szFilterString[0] = 0;

		UnhookEvent(dat->hPluginLoad);
		UnhookEvent(dat->hPluginUnload);

		SaveOptionsTreeState(hdlg);
		Window_FreeIcon_IcoLib(hdlg);

		if (dat->currentPage != -1) {
			if (dat->arOpd[dat->currentPage]->ptszTab)
				DBWriteContactSettingTString(NULL, "Options", "LastTab", dat->arOpd[dat->currentPage]->ptszTab);
			else DBDeleteContactSetting(NULL, "Options", "LastTab");
			if (dat->arOpd[dat->currentPage]->ptszGroup)
				DBWriteContactSettingTString(NULL, "Options", "LastGroup", dat->arOpd[dat->currentPage]->ptszGroup);
			else DBDeleteContactSetting(NULL, "Options", "LastGroup");
			DBWriteContactSettingTString(NULL, "Options", "LastPage", dat->arOpd[dat->currentPage]->ptszTitle);
		}
		else {
			DBDeleteContactSetting(NULL, "Options", "LastTab");
			DBDeleteContactSetting(NULL, "Options", "LastGroup");
			DBDeleteContactSetting(NULL, "Options", "LastPage");
		}
		Utils_SaveWindowPosition(hdlg, NULL, "Options", "");
		{
			for (int i=0; i < dat->arOpd.getCount(); i++)
				FreeOptionsPageData(dat->arOpd[i]);
		}

		DeleteObject(dat->hBoldFont);
		delete dat;
		hwndOptions = NULL;

		CallService(MS_MODERNOPT_RESTORE, 0, 0);
		break;
	}
	return FALSE;
}

void OpenAccountOptions(PROTOACCOUNT* pa)
{
	OptionsPageInit opi = { 0 };
	if (pa->ppro == NULL)
		return;

	pa->ppro->OnEvent(EV_PROTO_ONOPTIONS, (WPARAM)&opi, 0);
	if (opi.pageCount > 0) {
		TCHAR tszTitle[ 100 ];
		OPENOPTIONSDIALOG ood = { 0 };
		PROPSHEETHEADER psh = { 0 };

		mir_sntprintf(tszTitle, SIZEOF(tszTitle), TranslateT("%s options"), pa->tszAccountName);
		
		ood.cbSize = sizeof(ood);
		ood.pszGroup = LPGEN("Network");
		ood.pszPage = mir_t2a(pa->tszAccountName);

		psh.dwSize = sizeof(psh);
		psh.dwFlags = PSH_PROPSHEETPAGE|PSH_NOAPPLYNOW;
		psh.hwndParent = NULL;
		psh.nPages = opi.pageCount;
		psh.pStartPage = (LPCTSTR)&ood;
		psh.pszCaption = tszTitle;
		psh.ppsp = (PROPSHEETPAGE*)opi.odp;
		hwndOptions = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_OPTIONSPAGE), NULL, OptionsDlgProc, (LPARAM)&psh);
		mir_free((void*)ood.pszPage);
		FreeOptionsData(&opi);
}	}

static void OpenOptionsNow(const char *pszGroup, const char *pszPage, const char *pszTab, bool bSinglePage = false)
{
	if (IsWindow(hwndOptions)) {
		ShowWindow(hwndOptions, SW_RESTORE);
		SetForegroundWindow(hwndOptions);
		if (pszPage != NULL) {
			TCHAR *ptszPage = Langpack_PcharToTchar(pszPage);
			HTREEITEM hItem = NULL;
			if (pszGroup != NULL) {
				TCHAR *ptszGroup = Langpack_PcharToTchar(pszGroup);
				hItem = FindNamedTreeItemAtRoot(GetDlgItem(hwndOptions, IDC_PAGETREE), ptszGroup);
				if (hItem != NULL) {
					hItem = FindNamedTreeItemAtChildren(GetDlgItem(hwndOptions, IDC_PAGETREE), hItem, ptszPage);
				}
				mir_free(ptszGroup);
			} else {
				hItem = FindNamedTreeItemAtRoot(GetDlgItem(hwndOptions, IDC_PAGETREE), ptszPage);
			}
			if (hItem != NULL) {
				TreeView_SelectItem(GetDlgItem(hwndOptions, IDC_PAGETREE), hItem);
			}
			mir_free(ptszPage);
		}	
	}
	else {
		OptionsPageInit opi = { 0 };
		NotifyEventHooks(hOptionsInitEvent, (WPARAM)&opi, 0);
		if (opi.pageCount > 0) {
			OPENOPTIONSDIALOG ood = { 0 };
			ood.pszGroup = pszGroup;
			ood.pszPage = pszPage;
			ood.pszTab = pszTab;

			PROPSHEETHEADER psh = { 0 };
			psh.dwSize = sizeof(psh);
			psh.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
			psh.nPages = opi.pageCount;
			psh.pStartPage = (LPCTSTR)&ood;	  //more structure misuse
			psh.pszCaption = TranslateT("Miranda IM Options");
			psh.ppsp = (PROPSHEETPAGE*)opi.odp;		  //blatent misuse of the structure, but what the hell

			hwndOptions = CreateDialogParam(hInst, 
				MAKEINTRESOURCE(bSinglePage ? IDD_OPTIONSPAGE : IDD_OPTIONS), 
				NULL, OptionsDlgProc, (LPARAM)&psh);

			FreeOptionsData(&opi);
}	}	}

static INT_PTR OpenOptions(WPARAM, LPARAM lParam)
{
	OPENOPTIONSDIALOG *ood = (OPENOPTIONSDIALOG*)lParam;
	if (ood == NULL)
		return 1;

	if (ood->cbSize == OPENOPTIONSDIALOG_OLD_SIZE)
		OpenOptionsNow(ood->pszGroup, ood->pszPage, NULL);
	else if (ood->cbSize == sizeof(OPENOPTIONSDIALOG))
		OpenOptionsNow(ood->pszGroup, ood->pszPage, ood->pszTab);
	else
		return 1;

	return 0;
}

static INT_PTR OpenOptionsPage(WPARAM, LPARAM lParam)
{
	OPENOPTIONSDIALOG *ood = (OPENOPTIONSDIALOG*)lParam;
	if (ood == NULL)
		return 1;

	if (ood->cbSize == OPENOPTIONSDIALOG_OLD_SIZE)
		OpenOptionsNow(ood->pszGroup, ood->pszPage, NULL, true);
	else if (ood->cbSize == sizeof(OPENOPTIONSDIALOG))
		OpenOptionsNow(ood->pszGroup, ood->pszPage, ood->pszTab, true);
	else
		return 1;

	return (INT_PTR)hwndOptions;
}

static INT_PTR OpenOptionsDialog(WPARAM, LPARAM)
{
	if (hwndOptions || GetAsyncKeyState(VK_CONTROL) || !ServiceExists(MS_MODERNOPT_SHOW))
		OpenOptionsNow(NULL, NULL, NULL);
	else
		CallService(MS_MODERNOPT_SHOW, 0, 0);
	return 0;
}

static INT_PTR AddOptionsPage(WPARAM wParam, LPARAM lParam)
{
	OPTIONSDIALOGPAGE *odp = (OPTIONSDIALOGPAGE*)lParam, *dst;
	OptionsPageInit *opi = (OptionsPageInit*)wParam;

	if (odp == NULL || opi == NULL) return 1;
	if (odp->cbSize != sizeof(OPTIONSDIALOGPAGE) && odp->cbSize != OPTIONPAGE_OLD_SIZE)
		return 1;

	opi->odp = (OPTIONSDIALOGPAGE*)mir_realloc(opi->odp, sizeof(OPTIONSDIALOGPAGE)*(opi->pageCount+1));
	dst = opi->odp + opi->pageCount;
	memset(dst, 0, sizeof(OPTIONSDIALOGPAGE));
	memcpy(dst, odp, odp->cbSize);

	if (odp->ptszTitle != NULL) {
		if (odp->flags & ODPF_UNICODE)
			dst->ptszTitle = mir_wstrdup(odp->ptszTitle);
		else {
			dst->ptszTitle = mir_a2u(odp->pszTitle);
			dst->flags |= ODPF_UNICODE;
		}	
	}

	if (odp->ptszGroup != NULL) {
		if (odp->flags & ODPF_UNICODE)
			dst->ptszGroup = mir_wstrdup(odp->ptszGroup);
		else {
			dst->ptszGroup = Langpack_PcharToTchar(odp->pszGroup);
			dst->flags |= ODPF_UNICODE;
		}	
	}

	if (odp->ptszTab != NULL) {
		if (odp->flags & ODPF_UNICODE)
			dst->ptszTab = mir_wstrdup(odp->ptszTab);
		else {
			dst->ptszTab = Langpack_PcharToTchar(odp->pszTab);
			dst->flags |= ODPF_UNICODE;
		}
	}

	if ((DWORD_PTR)odp->pszTemplate & 0xFFFF0000)
		dst->pszTemplate = mir_strdup(odp->pszTemplate);

	opi->pageCount++;
	return 0;
}

static int OptModulesLoaded(WPARAM, LPARAM)
{
	CLISTMENUITEM mi = { 0 };
	mi.cbSize = sizeof(mi);
	mi.flags = CMIF_ICONFROMICOLIB;
	mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_OPTIONS);
	mi.position = 1900000000;
	mi.pszName = LPGEN("&Options...");
	mi.pszService = "Options/OptionsCommand";
	Menu_AddMainMenuItem(&mi);
	return 0;
}

int ShutdownOptionsModule(WPARAM, LPARAM)
{
	if (IsWindow(hwndOptions)) DestroyWindow(hwndOptions);
	hwndOptions = NULL;
	
	//!!!!!!!!!! UnhookFilterEvents();
	
	return 0;
}

int LoadOptionsModule(void)
{
	hwndOptions = NULL;
	hOptionsInitEvent = CreateHookableEvent(ME_OPT_INITIALISE);
	CreateServiceFunction("Opt/AddPage", AddOptionsPage);
	CreateServiceFunction(MS_OPT_OPENOPTIONS, OpenOptions);
	CreateServiceFunction(MS_OPT_OPENOPTIONSPAGE, OpenOptionsPage);
	CreateServiceFunction("Options/OptionsCommand", OpenOptionsDialog);
	HookEvent(ME_SYSTEM_MODULESLOADED, OptModulesLoaded);
	HookEvent(ME_SYSTEM_PRESHUTDOWN, ShutdownOptionsModule);
	
	//!!!!!!!!!! HookFilterEvents();
	return 0;
}