/*

Jabber Protocol Plugin for Miranda IM
Copyright ( C ) 2002-04  Santithorn Bunchua
Copyright ( C ) 2005-12  George Hazan

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 "jabber.h"
#include "jabber_caps.h"


static BOOL CALLBACK JabberFormMultiLineWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch ( msg ) {
	//case WM_GETDLGCODE:
	//	return DLGC_WANTARROWS|DLGC_WANTCHARS|DLGC_HASSETSEL|DLGC_WANTALLKEYS;
	case WM_KEYDOWN:
		if ( wParam == VK_TAB ) {
			SetFocus( GetNextDlgTabItem( GetParent( GetParent( hwnd )), hwnd, GetKeyState( VK_SHIFT )<0?TRUE:FALSE ));
			return TRUE;
		};
		break;
	}
	return CallWindowProc(( WNDPROC ) GetWindowLongPtr( hwnd, GWLP_USERDATA ), hwnd, msg, wParam, lParam );
}

struct TJabberFormControlInfo
{
	TJabberFormControlType type;
	SIZE szBlock;
	POINT ptLabel, ptCtrl;
	HWND hLabel, hCtrl;
};
typedef LIST<TJabberFormControlInfo> TJabberFormControlList;

struct TJabberFormLayoutInfo
{
	int ctrlHeight;
	int offset, width, maxLabelWidth;
	int y_pos, y_spacing;
	int id;
	bool compact;
};

void JabberFormCenterContent(HWND hwndStatic)
{
	RECT rcWindow;
	int minX;
	GetWindowRect(hwndStatic,&rcWindow);
	minX=rcWindow.right;
	HWND oldChild=NULL;
	HWND hWndChild=GetWindow(hwndStatic,GW_CHILD);
	while (hWndChild!=oldChild && hWndChild!=NULL)
	{
	   DWORD style=GetWindowLongPtr(hWndChild, GWL_STYLE);
	   RECT rc;
	   GetWindowRect(hWndChild,&rc);
	   if ((style&SS_RIGHT) && !(style&WS_TABSTOP))
	   {
		  TCHAR * text;
		  RECT calcRect=rc;
		  int len=GetWindowTextLength(hWndChild);
		  text=(TCHAR*)malloc(sizeof(TCHAR)*(len+1));
		  GetWindowText(hWndChild,text,len+1);
		  HDC hdc=GetDC(hWndChild);
		  HFONT hfntSave = (HFONT)SelectObject(hdc, (HFONT)SendMessage(hWndChild, WM_GETFONT, 0, 0));
		  DrawText(hdc,text,-1,&calcRect,DT_CALCRECT|DT_WORDBREAK);
		  minX=min(minX, rc.right-(calcRect.right-calcRect.left));
		  SelectObject(hdc, hfntSave);
		  ReleaseDC(hWndChild,hdc);
	   }
	   else
	   {
		  minX=min(minX,rc.left);
	   }
	   oldChild=hWndChild;
	   hWndChild=GetWindow(hWndChild,GW_HWNDNEXT);
	}
	if (minX>rcWindow.left+5)
	{
		int dx=(minX-rcWindow.left)/2;
		oldChild=NULL;
		hWndChild=GetWindow(hwndStatic,GW_CHILD);
		while (hWndChild!=oldChild && hWndChild!=NULL )
		{
			DWORD style=GetWindowLongPtr(hWndChild, GWL_STYLE);
			RECT rc;
			GetWindowRect(hWndChild,&rc);
			if ((style&SS_RIGHT) && !(style&WS_TABSTOP))
				MoveWindow(hWndChild,rc.left-rcWindow.left,rc.top-rcWindow.top, rc.right-rc.left-dx, rc.bottom-rc.top, TRUE);
			else
				MoveWindow(hWndChild,rc.left-dx-rcWindow.left,rc.top-rcWindow.top, rc.right-rc.left, rc.bottom-rc.top, TRUE);
			oldChild=hWndChild;
			hWndChild=GetWindow(hWndChild,GW_HWNDNEXT);
		}
	}
}

void JabberFormSetInstruction( HWND hwndForm, const TCHAR *text )
{
	if (!text) text = _T("");

	int len = lstrlen(text);
	int fixedLen = len;
	for (int i = 1; i < len; ++i)
		if ((text[i - 1] == _T('\n')) && (text[i] != _T('\r')))
			++fixedLen;
	TCHAR *fixedText = NULL;
	if (fixedLen != len) {
		fixedText = (TCHAR *)mir_alloc(sizeof(TCHAR) * (fixedLen+1));
		TCHAR *p = fixedText;
		for (int i = 0; i < len; ++i) {
			*p = text[i];
			if (i && (text[i] == _T('\n')) && (text[i] != _T('\r'))) {
				*p++ = _T('\r');
				*p = _T('\n');
			}
			++p;
		}
		*p = 0;
		text = fixedText;
	}

	SetDlgItemText( hwndForm, IDC_INSTRUCTION, text );

	RECT rcText;
	GetWindowRect(GetDlgItem(hwndForm, IDC_INSTRUCTION), &rcText);
	int oldWidth = rcText.right-rcText.left;
	int deltaHeight = -(rcText.bottom-rcText.top);

	SetRect(&rcText, 0, 0, rcText.right-rcText.left, 0);
	HDC hdcEdit = GetDC(GetDlgItem(hwndForm, IDC_INSTRUCTION));
	HFONT hfntSave = (HFONT)SelectObject(hdcEdit, (HFONT)SendDlgItemMessage(hwndForm, IDC_INSTRUCTION, WM_GETFONT, 0, 0));
	DrawTextEx(hdcEdit, (TCHAR *)text, lstrlen(text), &rcText,
		DT_CALCRECT|DT_EDITCONTROL|DT_TOP|DT_WORDBREAK, NULL);
	SelectObject(hdcEdit, hfntSave);
	ReleaseDC(GetDlgItem(hwndForm, IDC_INSTRUCTION), hdcEdit);

	RECT rcWindow; GetClientRect(hwndForm, &rcWindow);
	if (rcText.bottom-rcText.top > (rcWindow.bottom-rcWindow.top)/5) {
		HWND hwndEdit = GetDlgItem(hwndForm, IDC_INSTRUCTION);
		SetWindowLongPtr(hwndEdit, GWL_STYLE, WS_VSCROLL | GetWindowLongPtr(hwndEdit, GWL_STYLE));
		rcText.bottom = rcText.top + (rcWindow.bottom-rcWindow.top)/5;
	} else {
		HWND hwndEdit = GetDlgItem(hwndForm, IDC_INSTRUCTION);
		SetWindowLongPtr(hwndEdit, GWL_STYLE, ~WS_VSCROLL & GetWindowLongPtr(hwndEdit, GWL_STYLE));
	}
	deltaHeight += rcText.bottom-rcText.top;

	SetWindowPos(GetDlgItem(hwndForm, IDC_INSTRUCTION), 0, 0, 0,
		oldWidth,
		rcText.bottom-rcText.top,
		SWP_NOMOVE|SWP_NOZORDER);

	GetWindowRect(GetDlgItem(hwndForm, IDC_WHITERECT), &rcText);
	MapWindowPoints(NULL, hwndForm, (LPPOINT)&rcText, 2);
	rcText.bottom += deltaHeight;
	SetWindowPos(GetDlgItem(hwndForm, IDC_WHITERECT), 0, 0, 0,
		rcText.right-rcText.left,
		rcText.bottom-rcText.top,
		SWP_NOMOVE|SWP_NOZORDER);

	GetWindowRect(GetDlgItem(hwndForm, IDC_FRAME1), &rcText);
	MapWindowPoints(NULL, hwndForm, (LPPOINT)&rcText, 2);
	rcText.top += deltaHeight;
	SetWindowPos(GetDlgItem(hwndForm, IDC_FRAME1), 0,
		rcText.left,
		rcText.top,
		0, 0,
		SWP_NOSIZE|SWP_NOZORDER);

	GetWindowRect(GetDlgItem(hwndForm, IDC_FRAME), &rcText);
	MapWindowPoints(NULL, hwndForm, (LPPOINT)&rcText, 2);
	rcText.top += deltaHeight;
	SetWindowPos(GetDlgItem(hwndForm, IDC_FRAME), 0,
		rcText.left,
		rcText.top,
		rcText.right-rcText.left,
		rcText.bottom-rcText.top,
		SWP_NOZORDER);

	GetWindowRect(GetDlgItem(hwndForm, IDC_VSCROLL), &rcText);
	MapWindowPoints(NULL, hwndForm, (LPPOINT)&rcText, 2);
	rcText.top += deltaHeight;
	SetWindowPos(GetDlgItem(hwndForm, IDC_VSCROLL), 0,
		rcText.left,
		rcText.top,
		rcText.right-rcText.left,
		rcText.bottom-rcText.top,
		SWP_NOZORDER);

	if (fixedText) mir_free(fixedText);
}

static TJabberFormControlType JabberFormTypeNameToId( const TCHAR *type )
{
	if ( !_tcscmp( type, _T("text-private")))
		return JFORM_CTYPE_TEXT_PRIVATE;
	if ( !_tcscmp( type, _T("text-multi")) || !_tcscmp( type, _T("jid-multi")))
		return JFORM_CTYPE_TEXT_MULTI;
	if ( !_tcscmp( type, _T("boolean")))
		return JFORM_CTYPE_BOOLEAN;
	if ( !_tcscmp( type, _T("list-single")))
		return JFORM_CTYPE_LIST_SINGLE;
	if ( !_tcscmp( type, _T("list-multi")))
		return JFORM_CTYPE_LIST_MULTI;
	if ( !_tcscmp( type, _T("fixed")))
		return JFORM_CTYPE_FIXED;
	if ( !_tcscmp( type, _T("hidden")))
		return JFORM_CTYPE_HIDDEN;
	// else
	return JFORM_CTYPE_TEXT_SINGLE;
}

void JabberFormLayoutSingleControl(TJabberFormControlInfo *item, TJabberFormLayoutInfo *layout_info, const TCHAR *labelStr, const TCHAR *valueStr)
{
	RECT rcLabel = {0}, rcCtrl = {0};
	if (item->hLabel)
	{
		SetRect(&rcLabel, 0, 0, layout_info->width, 0);
		HDC hdc = GetDC( item->hLabel );
		HFONT hfntSave = (HFONT)SelectObject(hdc, (HFONT)SendMessage(item->hLabel, WM_GETFONT, 0, 0));
		DrawText( hdc, labelStr, -1, &rcLabel, DT_CALCRECT|DT_WORDBREAK );
		SelectObject(hdc, hfntSave);
		ReleaseDC(item->hLabel, hdc);
	}

	int indent = layout_info->compact ? 10 : 20;

	if ((layout_info->compact && (item->type != JFORM_CTYPE_BOOLEAN) && (item->type != JFORM_CTYPE_FIXED))||
		(rcLabel.right >= layout_info->maxLabelWidth) ||
		(rcLabel.bottom > layout_info->ctrlHeight) ||
		(item->type == JFORM_CTYPE_LIST_MULTI) ||
		(item->type == JFORM_CTYPE_TEXT_MULTI))
	{
		int height = layout_info->ctrlHeight;
		if ((item->type == JFORM_CTYPE_LIST_MULTI) || (item->type == JFORM_CTYPE_TEXT_MULTI)) height *= 3;
		SetRect(&rcCtrl, indent, rcLabel.bottom, layout_info->width, rcLabel.bottom + height);
	} else
	if (item->type == JFORM_CTYPE_BOOLEAN)
	{
		SetRect(&rcCtrl, 0, 0, layout_info->width-20, 0);
		HDC hdc = GetDC( item->hCtrl );
		HFONT hfntSave = (HFONT)SelectObject(hdc, (HFONT)SendMessage(item->hCtrl, WM_GETFONT, 0, 0));
		DrawText( hdc, labelStr, -1, &rcCtrl, DT_CALCRECT|DT_RIGHT|DT_WORDBREAK );
		SelectObject(hdc, hfntSave);
		ReleaseDC(item->hCtrl, hdc);
		rcCtrl.right += 20;
	} else
	if (item->type == JFORM_CTYPE_FIXED)
	{
		SetRect(&rcCtrl, 0, 0, layout_info->width, 0);
		HDC hdc = GetDC( item->hCtrl );
		HFONT hfntSave = (HFONT)SelectObject(hdc, (HFONT)SendMessage(item->hCtrl, WM_GETFONT, 0, 0));
		DrawText( hdc, valueStr, -1, &rcCtrl, DT_CALCRECT|DT_EDITCONTROL );
		rcCtrl.right += 20;
		SelectObject(hdc, hfntSave);
		ReleaseDC(item->hCtrl, hdc);
	} else
	{
		SetRect(&rcCtrl, rcLabel.right+5, 0, layout_info->width, layout_info->ctrlHeight);
		rcLabel.bottom = rcCtrl.bottom;
	}

	if (item->hLabel)
		SetWindowPos(item->hLabel, 0,
		0, 0, rcLabel.right-rcLabel.left, rcLabel.bottom-rcLabel.top,
		SWP_NOZORDER|SWP_NOMOVE);
	if (item->hCtrl)
		SetWindowPos(item->hCtrl, 0,
		0, 0, rcCtrl.right-rcCtrl.left, rcCtrl.bottom-rcCtrl.top,
		SWP_NOZORDER|SWP_NOMOVE);

	item->ptLabel.x = rcLabel.left;
	item->ptLabel.y = rcLabel.top;
	item->ptCtrl.x = rcCtrl.left;
	item->ptCtrl.y = rcCtrl.top;
	item->szBlock.cx = layout_info->width;
	item->szBlock.cy = max(rcLabel.bottom, rcCtrl.bottom);
}

#define JabberFormCreateLabel()	\
	CreateWindow( _T("static"), labelStr, WS_CHILD|WS_VISIBLE|SS_CENTERIMAGE, \
		0, 0, 0, 0, hwndStatic, ( HMENU )-1, hInst, NULL )

TJabberFormControlInfo *JabberFormAppendControl(HWND hwndStatic, TJabberFormLayoutInfo *layout_info, TJabberFormControlType type, const TCHAR *labelStr, const TCHAR *valueStr)
{
	TJabberFormControlList *controls = (TJabberFormControlList *)GetWindowLongPtr(hwndStatic, GWLP_USERDATA);
	if (!controls)
	{
		controls = new TJabberFormControlList(5);
		SetWindowLongPtr(hwndStatic, GWLP_USERDATA, (LONG_PTR)controls);
	}

	TJabberFormControlInfo *item = (TJabberFormControlInfo *)mir_alloc(sizeof(TJabberFormControlInfo));
	item->type = type;
	item->hLabel = item->hCtrl = NULL;

	switch (type)
	{
		case JFORM_CTYPE_TEXT_PRIVATE:
		{
			item->hLabel = JabberFormCreateLabel();
			item->hCtrl = CreateWindowEx( WS_EX_CLIENTEDGE, _T("edit"), valueStr,
				WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_LEFT|ES_AUTOHSCROLL|ES_PASSWORD,
				0, 0, 0, 0,
				hwndStatic, ( HMENU ) layout_info->id, hInst, NULL );
			++layout_info->id;
			break;
		}
		case JFORM_CTYPE_TEXT_MULTI:
		{
			item->hLabel = JabberFormCreateLabel();
			item->hCtrl = CreateWindowEx( WS_EX_CLIENTEDGE, _T("edit"), valueStr,
				WS_CHILD|WS_VISIBLE|WS_TABSTOP|WS_VSCROLL|ES_LEFT|ES_MULTILINE|ES_AUTOVSCROLL|ES_WANTRETURN,
				0, 0, 0, 0,
				hwndStatic, ( HMENU ) layout_info->id, hInst, NULL );
			WNDPROC oldWndProc = ( WNDPROC ) SetWindowLongPtr( item->hCtrl, GWLP_WNDPROC, ( LONG_PTR )JabberFormMultiLineWndProc );
			SetWindowLongPtr( item->hCtrl, GWLP_USERDATA, ( LONG_PTR ) oldWndProc );
			++layout_info->id;
			break;
		}
		case JFORM_CTYPE_BOOLEAN:
		{
			item->hCtrl = CreateWindowEx( 0, _T("button"), labelStr,
				WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_AUTOCHECKBOX|BS_MULTILINE,
				0, 0, 0, 0,
				hwndStatic, ( HMENU ) layout_info->id, hInst, NULL );
			if ( valueStr && !_tcscmp( valueStr, _T("1")))
				SendMessage( item->hCtrl, BM_SETCHECK, 1, 0 );
			++layout_info->id;
			break;
		}
		case JFORM_CTYPE_LIST_SINGLE:
		{
			item->hLabel = JabberFormCreateLabel();
			item->hCtrl = CreateWindowExA( WS_EX_CLIENTEDGE, "combobox", NULL,
				WS_CHILD|WS_VISIBLE|WS_TABSTOP|CBS_DROPDOWNLIST,
				0, 0, 0, 0,
				hwndStatic, ( HMENU ) layout_info->id, hInst, NULL );
			++layout_info->id;
			break;
		}
		case JFORM_CTYPE_LIST_MULTI:
		{
			item->hLabel = JabberFormCreateLabel();
			item->hCtrl = CreateWindowExA( WS_EX_CLIENTEDGE, "listbox",
				NULL, WS_CHILD|WS_VISIBLE|WS_TABSTOP|LBS_MULTIPLESEL,
				0, 0, 0, 0,
				hwndStatic, ( HMENU ) layout_info->id, hInst, NULL );
			++layout_info->id;
			break;
		}
		case JFORM_CTYPE_FIXED:
		{
			item->hCtrl = CreateWindow( _T("edit"), valueStr,
				WS_CHILD|WS_VISIBLE|ES_MULTILINE|ES_READONLY|ES_AUTOHSCROLL,
				0, 0, 0, 0,
				hwndStatic, ( HMENU )-1, hInst, NULL );
			break;
		}
		case JFORM_CTYPE_HIDDEN:
		{
			break;
		}
		case JFORM_CTYPE_TEXT_SINGLE:
		{
			item->hLabel = labelStr ? (JabberFormCreateLabel()) : NULL;
			item->hCtrl = CreateWindowEx( WS_EX_CLIENTEDGE, _T("edit"), valueStr,
				WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_LEFT|ES_AUTOHSCROLL,
				0, 0, 0, 0,
				hwndStatic, ( HMENU ) layout_info->id, hInst, NULL );
			++layout_info->id;
			break;
		}
	}

	HFONT hFont = ( HFONT ) SendMessage( GetParent(hwndStatic), WM_GETFONT, 0, 0 );
	if (item->hLabel) SendMessage( item->hLabel, WM_SETFONT, ( WPARAM ) hFont, 0 );
	if (item->hCtrl) SendMessage( item->hCtrl, WM_SETFONT, ( WPARAM ) hFont, 0 );

	JabberFormLayoutSingleControl(item, layout_info, labelStr, valueStr);

	controls->insert(item);
	return item;
}

void JabberFormAddListItem(TJabberFormControlInfo *item, TCHAR *text, bool selected)
{
	DWORD dwIndex;
	switch (item->type)
	{
	case JFORM_CTYPE_LIST_MULTI:
		dwIndex = SendMessage(item->hCtrl, LB_ADDSTRING, 0, (LPARAM)text);
		if (selected) SendMessage(item->hCtrl, LB_SETSEL, TRUE, dwIndex);
		break;
	case JFORM_CTYPE_LIST_SINGLE:
		dwIndex = SendMessage(item->hCtrl, CB_ADDSTRING, 0, (LPARAM)text);
		if (selected) SendMessage(item->hCtrl, CB_SETCURSEL, dwIndex, 0);
		break;
	}
}

void JabberFormLayoutControls(HWND hwndStatic, TJabberFormLayoutInfo *layout_info, int *formHeight)
{
	TJabberFormControlList *controls = (TJabberFormControlList *)GetWindowLongPtr(hwndStatic, GWLP_USERDATA);
	if (!controls) return;

	for (int i = 0; i < controls->getCount(); ++i)
	{
		if ((*controls)[i]->hLabel)
			SetWindowPos((*controls)[i]->hLabel, 0,
			layout_info->offset+(*controls)[i]->ptLabel.x, layout_info->y_pos+(*controls)[i]->ptLabel.y, 0, 0,
			SWP_NOZORDER|SWP_NOSIZE);
		if ((*controls)[i]->hCtrl)
			SetWindowPos((*controls)[i]->hCtrl, 0,
			layout_info->offset+(*controls)[i]->ptCtrl.x, layout_info->y_pos+(*controls)[i]->ptCtrl.y, 0, 0,
			SWP_NOZORDER|SWP_NOSIZE);

		layout_info->y_pos += (*controls)[i]->szBlock.cy;
		layout_info->y_pos += layout_info->y_spacing;
	}

	*formHeight = layout_info->y_pos + (layout_info->compact ? 0 : 9);
}

HJFORMLAYOUT JabberFormCreateLayout(HWND hwndStatic)
{
	RECT frameRect;
	GetClientRect( hwndStatic, &frameRect );

	TJabberFormLayoutInfo *layout_info = (TJabberFormLayoutInfo *)mir_alloc(sizeof(TJabberFormLayoutInfo));
	layout_info->compact = false;
	layout_info->ctrlHeight = 20;
	layout_info->id = 0;
	layout_info->width = frameRect.right - frameRect.left - 20 - 10;
	layout_info->y_spacing = 5;
	layout_info->maxLabelWidth = layout_info->width*2/5;
	layout_info->offset = 10;
	layout_info->y_pos = 14;
	return layout_info;
}

void JabberFormCreateUI( HWND hwndStatic, HXML xNode, int *formHeight, BOOL bCompact )
{
	JabberFormDestroyUI(hwndStatic);

	HXML v, o, vs;

	int i, j, k;
	const TCHAR* label, *typeName, *varStr, *str, *valueText;
	TCHAR *labelStr, *valueStr, *p;
	RECT frameRect;

	if ( xNode==NULL || xmlGetName( xNode )==NULL || lstrcmp( xmlGetName( xNode ), _T("x")) || hwndStatic==NULL ) return;

	GetClientRect( hwndStatic, &frameRect );

	TJabberFormLayoutInfo layout_info;
	layout_info.compact = bCompact ? true : false;
	layout_info.ctrlHeight = 20;
	layout_info.id = 0;
	layout_info.width = frameRect.right - frameRect.left - 20;
	if (!bCompact) layout_info.width -= 10;
	layout_info.y_spacing = bCompact ? 1 : 5;
	layout_info.maxLabelWidth = layout_info.width*2/5;
	layout_info.offset = 10;
	layout_info.y_pos = bCompact ? 0 : 14;
	for ( i=0; ; i++ ) {
		HXML n = xmlGetChild( xNode ,i);
		if ( !n )
			break;

		if ( xmlGetName( n )) {
			if ( !lstrcmp( xmlGetName( n ), _T("field"))) {
				varStr = xmlGetAttrValue( n, _T("var"));
				if (( typeName = xmlGetAttrValue( n, _T("type"))) != NULL ) {
 					if (( label = xmlGetAttrValue( n, _T("label"))) != NULL )
						labelStr = mir_tstrdup( label );
					else
						labelStr = mir_tstrdup( varStr );

					TJabberFormControlType type = JabberFormTypeNameToId(typeName);

					if (( v = xmlGetChild( n , "value" )) != NULL )
					{
						valueText = xmlGetText( v );
						if (type != JFORM_CTYPE_TEXT_MULTI)
						{
							valueStr = mir_tstrdup( valueText );
						} else
						{
							size_t size = 1;
							for ( j=0; ; j++ ) {
								v = xmlGetChild( n ,j);
								if ( !v )
									break;
								if ( xmlGetName( v ) && !lstrcmp( xmlGetName( v ), _T("value")) && xmlGetText( v ))
									size += _tcslen( xmlGetText( v )) + 2;
							}
							valueStr = ( TCHAR* )mir_alloc( sizeof(TCHAR)*size );
							valueStr[0] = '\0';
							for ( j=0; ; j++ ) {
								v = xmlGetChild( n ,j);
								if ( !v )
									break;
								if ( xmlGetName( v ) && !lstrcmp( xmlGetName( v ), _T("value")) && xmlGetText( v )) {
									if ( valueStr[0] )
										_tcscat( valueStr, _T("\r\n"));
									_tcscat( valueStr, xmlGetText( v ));
							}	}
						}
					} else
					{
						valueText = valueStr = NULL;
					}

					TJabberFormControlInfo *item = JabberFormAppendControl(hwndStatic, &layout_info, type, labelStr, valueStr);

					mir_free( labelStr );
					mir_free( valueStr );

					if (type == JFORM_CTYPE_LIST_SINGLE)
					{
						for ( j=0; ; j++ ) {
							o = xmlGetChild( n ,j);
							if ( !o )
								break;
							if ( xmlGetName( o ) && !lstrcmp( xmlGetName( o ), _T("option"))) {
								if (( v = xmlGetChild( o , "value" )) != NULL && xmlGetText( v )) {
									if (( str = xmlGetAttrValue( o, _T("label"))) == NULL )
										str = xmlGetText( v );
									if (( p = mir_tstrdup( str )) != NULL ) {
										bool selected = false;
										if ( valueText != NULL && !_tcscmp( valueText, xmlGetText( v )))
											selected = true;
										JabberFormAddListItem(item, p, selected);
										mir_free( p );
						}	}	}	}
					} else
					if (type == JFORM_CTYPE_LIST_MULTI)
					{
						for ( j=0; ; j++ ) {
							o = xmlGetChild( n ,j);
							if ( !o )
								break;
							if ( xmlGetName( o ) && !lstrcmp( xmlGetName( o ), _T("option"))) {
								if (( v = xmlGetChild( o , "value" )) != NULL && xmlGetText( v )) {
									if (( str = xmlGetAttrValue( o, _T("label"))) == NULL )
										str = xmlGetText( v );
									if (( p = mir_tstrdup( str )) != NULL ) {
										bool selected = false;
										for ( k=0; ; k++ ) {
											vs = xmlGetChild( n ,k);
											if ( !vs )
												break;
											if ( !lstrcmp( xmlGetName( vs ), _T("value")) && xmlGetText( vs ) && !_tcscmp( xmlGetText( vs ), xmlGetText( v )))
											{
												selected = true;
												break;
											}
										}
										JabberFormAddListItem( item, p, selected );
										mir_free( p );
	}	}	}	}	}	}	}	}	}

	JabberFormLayoutControls(hwndStatic, &layout_info, formHeight);
}

void JabberFormDestroyUI(HWND hwndStatic)
{
	TJabberFormControlList *controls = (TJabberFormControlList *)GetWindowLongPtr(hwndStatic, GWLP_USERDATA);
	if (controls)
	{
		for ( int i = 0; i < controls->getCount(); i++ )
			mir_free((*controls)[i]);
		controls->destroy();
		delete controls;
		SetWindowLongPtr(hwndStatic, GWLP_USERDATA, 0);
	}
}

HXML JabberFormGetData( HWND hwndStatic, HXML xNode )
{
	HWND hFrame, hCtrl;
	HXML n, v, o;
	int id, j, k, len;
	const TCHAR *varName, *type, *fieldStr, *labelText, *str2;
	TCHAR *p, *q, *str;

	if ( xNode == NULL || xmlGetName( xNode ) == NULL || lstrcmp( xmlGetName( xNode ), _T("x")) || hwndStatic == NULL )
		return NULL;

	hFrame = hwndStatic;
	id = 0;
	XmlNode x( _T("x"));
	x << XATTR( _T("xmlns"), _T(JABBER_FEAT_DATA_FORMS)) << XATTR( _T("type"), _T("submit"));

	for ( int i=0; ; i++ ) {
		n = xmlGetChild( xNode ,i);
		if ( !n )
			break;

		fieldStr = NULL;
		if ( lstrcmp( xmlGetName( n ), _T("field")))
			continue;

		if (( varName = xmlGetAttrValue( n, _T("var"))) == NULL || ( type = xmlGetAttrValue( n, _T("type"))) == NULL )
			continue;

		hCtrl = GetDlgItem( hFrame, id );
		HXML field = x << XCHILD( _T("field")) << XATTR( _T("var"), varName );

		if ( !_tcscmp( type, _T("text-multi")) || !_tcscmp( type, _T("jid-multi"))) {
			len = GetWindowTextLength( GetDlgItem( hFrame, id ));
			str = ( TCHAR* )mir_alloc( sizeof(TCHAR)*( len+1 ));
			GetDlgItemText( hFrame, id, str, len+1 );
			p = str;
			while ( p != NULL ) {
				if (( q = _tcsstr( p, _T("\r\n"))) != NULL )
					*q = '\0';
				field << XCHILD( _T("value"), p );
				p = q ? q+2 : NULL;
			}
			mir_free( str );
			id++;
		}
		else if ( !_tcscmp( type, _T("boolean"))) {
			TCHAR buf[ 10 ];
			_itot( IsDlgButtonChecked( hFrame, id ) == BST_CHECKED ? 1 : 0, buf, 10 );
			field << XCHILD( _T("value"), buf );
			id++;
		}
		else if ( !_tcscmp( type, _T("list-single"))) {
			len = GetWindowTextLength( GetDlgItem( hFrame, id ));
			str = ( TCHAR* )mir_alloc( sizeof( TCHAR )*( len+1 ));
			GetDlgItemText( hFrame, id, str, len+1 );
			v = NULL;
			for ( j=0; ; j++ ) {
				o = xmlGetChild( n ,j);
				if ( !o )
					break;

				if ( !lstrcmp( xmlGetName( o ), _T("option"))) {
					if (( v = xmlGetChild( o , "value" )) != NULL && xmlGetText( v )) {
						if (( str2 = xmlGetAttrValue( o, _T("label"))) == NULL )
							str2 = xmlGetText( v );
						if ( !lstrcmp( str2, str ))
							break;
			}	}	}

			if ( o )
				field << XCHILD( _T("value"), xmlGetText( v ));

			mir_free( str );
			id++;
		}
		else if ( !_tcscmp( type, _T("list-multi"))) {
			int count = SendMessage( hCtrl, LB_GETCOUNT, 0, 0 );
			for ( j=0; j<count; j++ ) {
				if ( SendMessage( hCtrl, LB_GETSEL, j, 0 ) > 0 ) {
					// an entry is selected
					len = SendMessage( hCtrl, LB_GETTEXTLEN, j, 0 );
					if (( str = ( TCHAR* )mir_alloc(( len+1 )*sizeof( TCHAR ))) != NULL ) {
						SendMessage( hCtrl, LB_GETTEXT, j, ( LPARAM )str );
						for ( k=0; ; k++ ) {
							o = xmlGetChild( n ,k);
							if ( !o )
								break;

							if ( xmlGetName( o ) && !lstrcmp( xmlGetName( o ), _T("option"))) {
								if (( v = xmlGetChild( o , "value" )) != NULL && xmlGetText( v )) {
									if (( labelText = xmlGetAttrValue( o, _T("label"))) == NULL )
										labelText = xmlGetText( v );

									if ( !lstrcmp( labelText, str ))
										field << XCHILD( _T("value"), xmlGetText( v ));
						}	}	}
						mir_free( str );
			}	}	}
			id++;
		}
		else if ( !_tcscmp( type, _T("fixed")) || !_tcscmp( type, _T("hidden"))) {
			v = xmlGetChild( n , "value" );
			if ( v != NULL && xmlGetText( v ) != NULL )
				field << XCHILD( _T("value"), xmlGetText( v ));
		}
		else { // everything else is considered "text-single" or "text-private"
			len = GetWindowTextLength( GetDlgItem( hFrame, id ));
			str = ( TCHAR* )mir_alloc( sizeof(TCHAR)*( len+1 ));
			GetDlgItemText( hFrame, id, str, len+1 );
			field << XCHILD( _T("value"), str );
			mir_free( str );
			id++;
	}	}

	return xi.copyNode( x );
}

struct JABBER_FORM_INFO
{
	~JABBER_FORM_INFO();

	CJabberProto* ppro;
	HXML xNode;
	TCHAR defTitle[128];	// Default title if no <title/> in xNode
	RECT frameRect;		// Clipping region of the frame to scroll
	int frameHeight;	// Height of the frame ( can be eliminated, redundant to frameRect )
	int formHeight;		// Actual height of the form
	int curPos;			// Current scroll position
	JABBER_FORM_SUBMIT_FUNC pfnSubmit;
	void *userdata;
};

static INT_PTR CALLBACK JabberFormDlgProc( HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
	JABBER_FORM_INFO *jfi;

	switch ( msg ) {
	case WM_INITDIALOG:
		{
			HXML n;
			LONG frameExStyle;

			// lParam is ( JABBER_FORM_INFO * )
			TranslateDialogDefault( hwndDlg );
			ShowWindow( GetDlgItem( hwndDlg, IDC_FRAME_TEXT ), SW_HIDE );
			jfi = ( JABBER_FORM_INFO * ) lParam;
			if ( jfi != NULL ) {
				// Set dialog title
				if ( jfi->xNode!=NULL && ( n = xmlGetChild( jfi->xNode , "title" )) != NULL && xmlGetText( n ) != NULL )
					SetWindowText( hwndDlg, xmlGetText( n ));
				else if ( jfi->defTitle != NULL )
					SetWindowText( hwndDlg, TranslateTS( jfi->defTitle ));
				// Set instruction field
				if ( jfi->xNode!=NULL && ( n = xmlGetChild( jfi->xNode , "instructions" )) != NULL && xmlGetText( n ) != NULL )
					JabberFormSetInstruction( hwndDlg, xmlGetText( n ));
				else
				{
					if ( jfi->xNode != NULL && ( n = xmlGetChild( jfi->xNode , "title" )) != NULL && xmlGetText( n ) != NULL )
						JabberFormSetInstruction( hwndDlg, xmlGetText( n ));
					else if ( jfi->defTitle != NULL )
						JabberFormSetInstruction( hwndDlg, TranslateTS( jfi->defTitle ));
				}

				// Create form
				if ( jfi->xNode != NULL ) {
					RECT rect;

					GetClientRect( GetDlgItem( hwndDlg, IDC_FRAME ), &( jfi->frameRect ));
					GetClientRect( GetDlgItem( hwndDlg, IDC_VSCROLL ), &rect );
					jfi->frameRect.right -= ( rect.right - rect.left );
					GetClientRect( GetDlgItem( hwndDlg, IDC_FRAME ), &rect );
					jfi->frameHeight = rect.bottom - rect.top;
					JabberFormCreateUI( GetDlgItem( hwndDlg, IDC_FRAME ), jfi->xNode, &( jfi->formHeight ));
				}
			}

			if ( jfi->formHeight > jfi->frameHeight ) {
				HWND hwndScroll;

				hwndScroll = GetDlgItem( hwndDlg, IDC_VSCROLL );
				EnableWindow( hwndScroll, TRUE );
				SetScrollRange( hwndScroll, SB_CTL, 0, jfi->formHeight - jfi->frameHeight, FALSE );
				jfi->curPos = 0;
			}

			// Enable WS_EX_CONTROLPARENT on IDC_FRAME ( so tab stop goes through all its children )
			frameExStyle = GetWindowLongPtr( GetDlgItem( hwndDlg, IDC_FRAME ), GWL_EXSTYLE );
			frameExStyle |= WS_EX_CONTROLPARENT;
			SetWindowLongPtr( GetDlgItem( hwndDlg, IDC_FRAME ), GWL_EXSTYLE, frameExStyle );

			SetWindowLongPtr( hwndDlg, GWLP_USERDATA, ( LONG_PTR ) jfi );
			if ( jfi->pfnSubmit != NULL )
				EnableWindow( GetDlgItem( hwndDlg, IDC_SUBMIT ), TRUE );
		}
		return TRUE;

	case WM_CTLCOLORSTATIC:
		if ((GetWindowLongPtr((HWND)lParam, GWL_ID) == IDC_WHITERECT) ||
			(GetWindowLongPtr((HWND)lParam, GWL_ID) == IDC_INSTRUCTION) ||
			(GetWindowLongPtr((HWND)lParam, GWL_ID) == IDC_TITLE))
		{
			return (INT_PTR)GetStockObject(WHITE_BRUSH);
		}
		
		return NULL;
		
	case WM_MOUSEWHEEL:
		{
			int zDelta = GET_WHEEL_DELTA_WPARAM( wParam );
			if ( zDelta ) {
				int nScrollLines=0;
				SystemParametersInfo( SPI_GETWHEELSCROLLLINES, 0, (void*)&nScrollLines, 0 );
				for (int i = 0; i < ( nScrollLines + 1 ) / 2; i++ )
					SendMessage( hwndDlg, WM_VSCROLL, ( zDelta < 0 ) ? SB_LINEDOWN : SB_LINEUP, 0 );
			}
		}
		break;

	case WM_VSCROLL:
		jfi = ( JABBER_FORM_INFO * ) GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
		if ( jfi != NULL ) {
			int pos = jfi->curPos;
			switch ( LOWORD( wParam )) {
			case SB_LINEDOWN:
				pos += 15;
				break;
			case SB_LINEUP:
				pos -= 15;
				break;
			case SB_PAGEDOWN:
				pos += ( jfi->frameHeight - 10 );
				break;
			case SB_PAGEUP:
				pos -= ( jfi->frameHeight - 10 );
				break;
			case SB_THUMBTRACK:
				pos = HIWORD( wParam );
				break;
			}
			if ( pos > ( jfi->formHeight - jfi->frameHeight ))
				pos = jfi->formHeight - jfi->frameHeight;
			if ( pos < 0 )
				pos = 0;
			if ( jfi->curPos != pos ) {
				ScrollWindow( GetDlgItem( hwndDlg, IDC_FRAME ), 0, jfi->curPos - pos, NULL, &( jfi->frameRect ));
				SetScrollPos( GetDlgItem( hwndDlg, IDC_VSCROLL ), SB_CTL, pos, TRUE );
				jfi->curPos = pos;
			}
		}
		break;

	case WM_COMMAND:
		switch ( LOWORD( wParam )) {
		case IDC_SUBMIT:
			jfi = ( JABBER_FORM_INFO * ) GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
			if ( jfi != NULL ) {
				HXML n = JabberFormGetData( GetDlgItem( hwndDlg, IDC_FRAME ), jfi->xNode );
				( jfi->ppro->*(jfi->pfnSubmit))( n, jfi->userdata );
				xi.destroyNode( n );
			}
			// fall through
		case IDCANCEL:
		case IDCLOSE:
			DestroyWindow( hwndDlg );
			return TRUE;
		}
		break;

	case WM_CLOSE:
		DestroyWindow( hwndDlg );
		break;

	case WM_DESTROY:
		JabberFormDestroyUI( GetDlgItem( hwndDlg, IDC_FRAME ));
		jfi = ( JABBER_FORM_INFO * ) GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
		delete jfi;
		break;
	}

	return FALSE;
}

static VOID CALLBACK JabberFormCreateDialogApcProc( void* param )
{
	CreateDialogParam( hInst, MAKEINTRESOURCE( IDD_FORM ), NULL, JabberFormDlgProc, ( LPARAM )param );
}

void CJabberProto::FormCreateDialog( HXML xNode, TCHAR* defTitle, JABBER_FORM_SUBMIT_FUNC pfnSubmit, void *userdata )
{
	JABBER_FORM_INFO *jfi = new JABBER_FORM_INFO;
	memset( jfi, 0, sizeof( JABBER_FORM_INFO ));
	jfi->ppro = this;
	jfi->xNode = xi.copyNode( xNode );
	if ( defTitle )
		_tcsncpy( jfi->defTitle, defTitle, SIZEOF( jfi->defTitle ));
	jfi->pfnSubmit = pfnSubmit;
	jfi->userdata = userdata;

	CallFunctionAsync( JabberFormCreateDialogApcProc, jfi );
}

//=======================================================================================

JABBER_FORM_INFO::~JABBER_FORM_INFO()
{
	xi.destroyNode( xNode );
	mir_free( userdata );
}