/*

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

Copyright 2000-12 Miranda IM, 2012-13 Miranda NG 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 LPGEN("<all modules>")
#define CORE_MODULES_FILTER LPGEN("<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;

extern bool bOldMode;

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 height;
	int width;
	DWORD flags;
	TCHAR *ptszTitle, *ptszGroup, *ptszTab;
	int hLangpack;
	BOOL insideTab;
	LPARAM dwInitParam;

	int offsetX;
	int offsetY;

	__forceinline TCHAR* getString(TCHAR *ptszStr)
	{	if (flags & ODPF_DONTTRANSLATE)
			return ptszStr;
		return TranslateTH(hLangpack, ptszStr);
	}
};

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

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

	OptionsPageData* getCurrent() const
	{	return (currentPage == -1) ? NULL : arOpd[currentPage];
	}
};

HTREEITEM FindNamedTreeItemAtRoot(HWND hwndTree, const TCHAR* name)
{
	TCHAR str[128];
	TVITEM tvi;
	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;
}

#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);
			db_set_b(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 LRESULT CALLBACK OptionsFilterSubclassProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message != WM_PAINT && message != WM_PRINT)
		return mir_callNextSubclass(hWnd, OptionsFilterSubclassProc, message, wParam, lParam);

	if (GetFocus() == hWnd || GetWindowTextLength(hWnd))
		return mir_callNextSubclass(hWnd, OptionsFilterSubclassProc, 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;
	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);
	mir_callNextSubclass(hwnd, OldWndProc, 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)
{
	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, AeroPaintSubclassProc, msg, lParam);
		return TRUE;

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

	case WM_DESTROY:
		RemovePropA(hwnd, "Miranda.AeroRender.Active");
		break;
	}
	return mir_callNextSubclass(hwnd, AeroPaintSubclassProc, 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(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, 0, (LPARAM)TranslateT(ALL_MODULES_FILTER));
	SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_SETITEMDATA, (WPARAM)index, 0);
	index = SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_ADDSTRING, 0, (LPARAM)TranslateT(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++) {
		FindFilterStrings(FALSE, FALSE, hDlg, dat->arOpd[i]); // only modules name (fast enougth)

		HINSTANCE inst = dat->arOpd[i]->hInst;
		if (inst == hInst)
			continue;

		int j;
		for (j = 0; j<countKnownInst; j++)
			if (KnownInstances[j] == inst)
				break;
		if (j != countKnownInst)
			continue;

		KnownInstances[countKnownInst] = inst;
		countKnownInst++;
		GetModuleFileName(inst, tszModuleName, MAX_PATH);

		TCHAR *dllName = NULL;
		{
			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, 0, (LPARAM)dllName);
			SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT) CB_SETITEMDATA, (WPARAM)index, (LPARAM)inst);
			mir_free(dllName);
		}
	}

	FilterLoadProgress = 100;
}

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->height = 0;
	dst->width = 0;
	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=dat->arOpd.getCount()-1; i >= 0; 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)
{
	OptionsPageData *opd;
	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);

		if ( !ServiceExists(MS_MODERNOPT_SHOW))
			ShowWindow( GetDlgItem(hdlg, IDC_MODERN), FALSE);
		{
			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);
				mir_subclassWindow(cbi.hwndItem, OptionsFilterSubclassProc);

				if (IsAeroMode()) {
					mir_subclassWindow(cbi.hwndCombo, AeroPaintSubclassProc);
					mir_subclassWindow(cbi.hwndItem, AeroPaintSubclassProc);
				}
			}

			Utils_RestoreWindowPositionNoSize(hdlg, NULL, "Options", "");
			Window_SetIcon_IcoLib(hdlg, SKINICON_OTHER_OPTIONS);
			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_APPLY, 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 ( !db_get_ts(NULL, "Options", "LastPage", &dbv)) {
					lastPage = mir_tstrdup(dbv.ptszVal);
					db_free(&dbv);
				}

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

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

			OPTIONSDIALOGPAGE *odp = (OPTIONSDIALOGPAGE*)psh->ppsp;
			for (size_t i=0; i < psh->nPages; i++, odp++) {
				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);

			FillFilterCombo(hdlg, dat);
			PostMessage(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, TranslateT(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, TranslateT(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

			SendMessage(hwndTree, WM_SETREDRAW, FALSE, 0);

			HWND oldWnd = NULL;
			HWND oldTab = NULL;

			opd = dat->getCurrent();
			if (opd != NULL) {
				oldWnd = opd->hwnd;
				if (opd->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;

				opd = dat->arOpd[i];
				TCHAR* ptszGroup = TranslateTH(opd->hLangpack, opd->ptszGroup);
				TCHAR* ptszTitle = opd->getString(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 ( !db_get_b(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) {
				opd = dat->getCurrent();
				if (opd && oldWnd != opd->hwnd) {
					ShowWindow(oldWnd, SW_HIDE);
					if (oldTab && (opd == NULL || !opd->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

			SendMessage(hwndTree, WM_SETREDRAW, TRUE, 0);
		}
		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);

		opd = dat->getCurrent();
		if (opd)
			opd->changed = 1;

		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:
				opd = dat->getCurrent();
				if (opd && opd->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);

				opd = dat->getCurrent();
				if (opd && opd->hwnd != NULL)
					ShowWindow(opd->hwnd, SW_HIDE);

				if (wParam != IDC_TAB) {
					TVITEM tvi;
					tvi.hItem = dat->hCurrentPage = TreeView_GetSelection(hwndTree);
					if (tvi.hItem == NULL) {
						ShowWindow( GetDlgItem(hdlg, IDC_TAB), SW_HIDE);
						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;
					tie.mask = TCIF_PARAM;
					TabCtrl_GetItem( GetDlgItem(hdlg, IDC_TAB), TabCtrl_GetCurSel( GetDlgItem(hdlg, IDC_TAB)), &tie);
					dat->currentPage = tie.lParam;

					TVITEM tvi;
					tvi.hItem = dat->hCurrentPage;
					tvi.mask = TVIF_PARAM;
					tvi.lParam = dat->currentPage;
					TreeView_SetItem(hwndTree, &tvi);
				}

				opd = dat->getCurrent();
				if (opd == NULL) {
					ShowWindow( GetDlgItem(hdlg, IDC_STNOPAGE), SW_SHOW);
					break;
				}
				if (opd->hwnd == NULL) {
					opd->hwnd = CreateDialogIndirectParamA(opd->hInst, opd->pTemplate, hdlg, opd->dlgProc, opd->dwInitParam);
					if (opd->flags & ODPF_BOLDGROUPS)
						EnumChildWindows(opd->hwnd, BoldGroupTitlesEnumChildren, (LPARAM)dat->hBoldFont);

					RECT rcPage;
					GetClientRect(opd->hwnd, &rcPage);
					int w = opd->width = rcPage.right;
					int h = opd->height = rcPage.bottom;

					RECT rc;
					GetWindowRect(opd->hwnd, &rc);

					opd->offsetX = 0;
					opd->offsetY = 0;

					opd->insideTab = IsInsideTab(hdlg, dat, dat->currentPage);
					if (opd->insideTab) {
						SetWindowPos(opd->hwnd, HWND_TOP, (dat->rcTab.left+dat->rcTab.right-w)>>1, dat->rcTab.top, w, h, 0);
						ThemeDialogBackground(opd->hwnd, TRUE);
					}
					else {
						SetWindowPos(opd->hwnd, HWND_TOP, (dat->rcDisplay.left+dat->rcDisplay.right-w)>>1, (dat->rcDisplay.top+dat->rcDisplay.bottom-h)>>1, w, h, 0);
						ThemeDialogBackground(opd->hwnd, FALSE);
					}
				}

				if (wParam != IDC_TAB) {
					opd->insideTab = IsInsideTab(hdlg, dat, dat->currentPage);
					if (opd->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 *p = dat->arOpd[i];
							if ( lstrcmp(opd->ptszTitle, p->ptszTitle) || lstrcmpnull(opd->ptszGroup, p->ptszGroup))
								continue;

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

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

				ShowWindow(opd->hwnd, SW_SHOW);
				if (((LPNMTREEVIEW)lParam)->action == TVC_BYMOUSE)
					PostMessage(hdlg, DM_FOCUSPAGE, 0, 0);
				else
					SetFocus(hwndTree);
			}
		}
		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_MODERN:
			db_set_b(NULL, "Options", "Expert", 0);
			SaveOptionsTreeState(hdlg);
			PostMessage(hdlg, WM_CLOSE, 0, 0);
			CallService(MS_MODERNOPT_SHOW, 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);

				opd = dat->getCurrent();
				if (opd != NULL) {
					pshn.hdr.idFrom = 0;
					pshn.lParam = 0;
					pshn.hdr.code = PSN_KILLACTIVE;
					pshn.hdr.hwndFrom = opd->hwnd;
					if (SendMessage(opd->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 (opd)
							ShowWindow(opd->hwnd, SW_HIDE);
						dat->currentPage = i;
						if (opd)
							ShowWindow(opd->hwnd, SW_SHOW);
						return 0;
					}
				}

				if (LOWORD(wParam) == IDOK)
					DestroyWindow(hdlg);
			}
		}
		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);

		opd = dat->getCurrent();
		if (opd) {
			if (opd->ptszTab)
				db_set_ts(NULL, "Options", "LastTab", opd->ptszTab);
			else
				db_unset(NULL, "Options", "LastTab");
			if (opd->ptszGroup)
				db_set_ts(NULL, "Options", "LastGroup", opd->ptszGroup);
			else
				db_unset(NULL, "Options", "LastGroup");
			db_set_ts(NULL, "Options", "LastPage", opd->ptszTitle);
		}
		else {
			db_unset(NULL, "Options", "LastTab");
			db_unset(NULL, "Options", "LastGroup");
			db_unset(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)
{
	if (pa->ppro == NULL)
		return;

	OptionsPageInit opi = { 0 };
	pa->ppro->OnEvent(EV_PROTO_ONOPTIONS, (WPARAM)&opi, 0);
	if (opi.pageCount == 0)
		return;

	TCHAR tszTitle[ 100 ];
	mir_sntprintf(tszTitle, SIZEOF(tszTitle), TranslateT("%s options"), pa->tszAccountName);

	OPENOPTIONSDIALOG ood = { sizeof(ood) };
	ood.pszGroup = LPGEN("Network");
	ood.pszPage = mir_t2a(pa->tszAccountName);

	PROPSHEETHEADER psh = { 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(int hLangpack, const char *pszGroup, const char *pszPage, const char *pszTab, bool bSinglePage = false)
{
	if (IsWindow(hwndOptions)) {
		ShowWindow(hwndOptions, SW_RESTORE);
		SetForegroundWindow(hwndOptions);
		if (pszPage != NULL) {
			ptrT ptszPage( mir_a2t(pszPage));
			HTREEITEM hItem = NULL;
			if (pszGroup != NULL) {
				ptrT ptszGroup( mir_a2t(pszGroup));
				hItem = FindNamedTreeItemAtRoot( GetDlgItem(hwndOptions, IDC_PAGETREE), TranslateTH(hLangpack, ptszGroup));
				if (hItem != NULL)
					hItem = FindNamedTreeItemAtChildren( GetDlgItem(hwndOptions, IDC_PAGETREE), hItem, TranslateTH(hLangpack, ptszPage));
			}
			else hItem = FindNamedTreeItemAtRoot( GetDlgItem(hwndOptions, IDC_PAGETREE), TranslateTH(hLangpack, ptszPage));

			if (hItem != NULL)
				TreeView_SelectItem( GetDlgItem(hwndOptions, IDC_PAGETREE), hItem);
		}
	}
	else {
		OptionsPageInit opi = { 0 };
		NotifyEventHooks(hOptionsInitEvent, (WPARAM)&opi, 0);
		if (opi.pageCount == 0)
			return;

		OPENOPTIONSDIALOG ood = { sizeof(ood) };
		ood.pszGroup = pszGroup;
		ood.pszPage = pszPage;
		ood.pszTab = pszTab;

		PROPSHEETHEADER psh = { sizeof(psh) };
		psh.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
		psh.nPages = opi.pageCount;
		psh.pStartPage = (LPCTSTR)&ood;	  //more structure misuse
		psh.pszCaption = TranslateT("Miranda NG 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 wParam, LPARAM lParam)
{
	OPENOPTIONSDIALOG *ood = (OPENOPTIONSDIALOG*)lParam;
	if (ood == NULL)
		return 1;

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

	return 0;
}

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

	int hLangpack = (int)wParam;
	if (ood->cbSize == OPENOPTIONSDIALOG_OLD_SIZE)
		OpenOptionsNow(hLangpack, ood->pszGroup, ood->pszPage, NULL, true);
	else if (ood->cbSize == sizeof(OPENOPTIONSDIALOG))
		OpenOptionsNow(hLangpack, 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, 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 = mir_a2t(odp->pszGroup);
			dst->flags |= ODPF_UNICODE;
		}
	}

	if (odp->ptszTab != NULL) {
		if (odp->flags & ODPF_UNICODE)
			dst->ptszTab = mir_wstrdup(odp->ptszTab);
		else {
			dst->ptszTab = mir_a2t(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 = { sizeof(mi) };
	mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_OPTIONS);
	mi.position = 1900000000;
	mi.pszName = LPGEN("&Options...");
	mi.pszService = "Options/OptionsCommand";
	Menu_AddMainMenuItem(&mi);

	bOldMode = db_get_b(NULL, "Options", "OldPluginSettings", false) != 0;
	return 0;
}

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

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