/*

Jabber Protocol Plugin for Miranda IM
Copyright ( C ) 2002-04  Santithorn Bunchua
Copyright ( C ) 2005-11  George Hazan
Copyright ( C ) 2007     Maxim Mluhov

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.

Revision       : $Revision: 13899 $
Last change on : $Date: 2011-11-01 23:58:07 -0400 (Tue, 01 Nov 2011) $
Last change by : $Author: borkra $

*/

#include "jabber.h"
#include "jabber_iq.h"
#include "jabber_disco.h"

#define SD_FAKEJID_CONFERENCES	"@@conferences"
#define SD_FAKEJID_MYAGENTS		"@@my-transports"
#define SD_FAKEJID_AGENTS		"@@transports"
#define SD_FAKEJID_FAVORITES	"@@favorites"

enum {
	SD_BROWSE_NORMAL,
	SD_BROWSE_MYAGENTS,
	SD_BROWSE_AGENTS,
	SD_BROWSE_CONFERENCES,
	SD_BROWSE_FAVORITES
};
static int sttBrowseMode = SD_BROWSE_NORMAL;

#define REFRESH_TIMEOUT		500
#define REFRESH_TIMER		1607
static DWORD sttLastRefresh = 0;

#define AUTODISCO_TIMEOUT	500
#define AUTODISCO_TIMER		1608
static DWORD sttLastAutoDisco = 0;

enum { SD_OVERLAY_NONE, SD_OVERLAY_FAIL, SD_OVERLAY_PROGRESS, SD_OVERLAY_REGISTERED };

static struct
{
	TCHAR *feature;
	TCHAR *category;
	TCHAR *type;
	char *iconName;
	int iconIndex;
	int listIndex;
} sttNodeIcons[] =
{
//	standard identities: http://www.xmpp.org/registrar/disco-categories.html#directory
//	{NULL,	_T("account"),			_T("admin"),		NULL,			0},
//	{NULL,	_T("account"),			_T("anonymous"),	NULL,			0},
//	{NULL,	_T("account"),			_T("registered"),	NULL,			0},
	{NULL,	_T("account"),			NULL,				NULL,			SKINICON_STATUS_ONLINE},

//	{NULL,	_T("auth"),				_T("cert"),			NULL,			0},
//	{NULL,	_T("auth"),				_T("generic"),		NULL,			0},
//	{NULL,	_T("auth"),				_T("ldap"),			NULL,			0},
//	{NULL,	_T("auth"),				_T("ntlm"),			NULL,			0},
//	{NULL,	_T("auth"),				_T("pam"),			NULL,			0},
//	{NULL,	_T("auth"),				_T("radius"),		NULL,			0},
	{NULL,	_T("auth"),				NULL,				"key",			0},

///	{NULL,	_T("automation"),		_T("command-list"),	NULL,			0},
///	{NULL,	_T("automation"),		_T("command-node"),	NULL,			0},
//	{NULL,	_T("automation"),		_T("rpc"),			NULL,			0},
//	{NULL,	_T("automation"),		_T("soap"),			NULL,			0},
	{NULL,	_T("automation"),		NULL,				"adhoc",		0},

//	{NULL,	_T("client"),			_T("bot"),			NULL,			0},
//	{NULL,	_T("client"),			_T("console"),		NULL,			0},
//	{NULL,	_T("client"),			_T("handheld"),		NULL,			0},
//	{NULL,	_T("client"),			_T("pc"),			NULL,			0},
//	{NULL,	_T("client"),			_T("phone"),		NULL,			0},
//	{NULL,	_T("client"),			_T("web"),			NULL,			0},
	{NULL,	_T("client"),			NULL,				NULL,			SKINICON_STATUS_ONLINE},

//	{NULL,	_T("collaboration"),	_T("whiteboard"),	NULL,			0},
	{NULL,	_T("collaboration"),	NULL,				"group",		0},

//	{NULL,	_T("component"),		_T("archive"),		NULL,			0},
//	{NULL,	_T("component"),		_T("c2s"),			NULL,			0},
//	{NULL,	_T("component"),		_T("generic"),		NULL,			0},
//	{NULL,	_T("component"),		_T("load"),			NULL,			0},
//	{NULL,	_T("component"),		_T("log"),			NULL,			0},
//	{NULL,	_T("component"),		_T("presence"),		NULL,			0},
//	{NULL,	_T("component"),		_T("router"),		NULL,			0},
//	{NULL,	_T("component"),		_T("s2s"),			NULL,			0},
//	{NULL,	_T("component"),		_T("sm"),			NULL,			0},
//	{NULL,	_T("component"),		_T("stats"),		NULL,			0},

//	{NULL,	_T("conference"),		_T("irc"),			NULL,			0},
//	{NULL,	_T("conference"),		_T("text"),			NULL,			0},
	{NULL,	_T("conference"),		NULL,				"group",		0},

	{NULL,	_T("directory"),		_T("chatroom"),		"group",		0},
	{NULL,	_T("directory"),		_T("group"),		"group",		0},
	{NULL,	_T("directory"),		_T("user"),			NULL,			SKINICON_OTHER_FINDUSER},
//	{NULL,	_T("directory"),		_T("waitinglist"),	NULL,			0},
	{NULL,	_T("directory"),		NULL,				NULL,			SKINICON_OTHER_SEARCHALL},

	{NULL,	_T("gateway"),			_T("aim"),			"AIM",			SKINICON_STATUS_ONLINE},
	{NULL,	_T("gateway"),			_T("gadu-gadu"),	"GG",			SKINICON_STATUS_ONLINE},
//	{NULL,	_T("gateway"),			_T("http-ws"),		NUL,			0},
	{NULL,	_T("gateway"),			_T("icq"),			"ICQ",			SKINICON_STATUS_ONLINE},
	{NULL,	_T("gateway"),			_T("msn"),			"MSN",			SKINICON_STATUS_ONLINE},
	{NULL,	_T("gateway"),			_T("qq"),			"QQ",			SKINICON_STATUS_ONLINE},
//	{NULL,	_T("gateway"),			_T("sms"),			NULL,			0},
//	{NULL,	_T("gateway"),			_T("smtp"),			NULL,			0},
	{NULL,	_T("gateway"),			_T("tlen"),			"TLEN",			SKINICON_STATUS_ONLINE},
	{NULL,	_T("gateway"),			_T("yahoo"),		"YAHOO",		SKINICON_STATUS_ONLINE},
	{NULL,	_T("gateway"),			NULL,				"Agents",		0},

//	{NULL,	_T("headline"),			_T("newmail"),		NULL,			0},
	{NULL,	_T("headline"),			_T("rss"),			"node_rss",		0},
	{NULL,	_T("headline"),			_T("weather"),		"node_weather",	0},

//	{NULL,	_T("hierarchy"),		_T("branch"),		NULL,			0},
//	{NULL,	_T("hierarchy"),		_T("leaf"),			NULL,			0},

//	{NULL,	_T("proxy"),			_T("bytestreams"),	NULL,			0},
	{NULL,	_T("proxy"),			NULL,				NULL,			SKINICON_EVENT_FILE},

//	{NULL,	_T("pubsub"),			_T("collection"),	NULL,			0},
//	{NULL,	_T("pubsub"),			_T("leaf"),			NULL,			0},
//	{NULL,	_T("pubsub"),			_T("pep"),			NULL,			0},
//	{NULL,	_T("pubsub"),			_T("service"),		NULL,			0},

//	{NULL,	_T("server"),			_T("im"),			NULL,			0},
	{NULL,	_T("server"),			NULL,				"node_server",	0},

//	{NULL,	_T("store"),			_T("berkeley"),		NULL,			0},
///	{NULL,	_T("store"),			_T("file"),			NULL,			0},
//	{NULL,	_T("store"),			_T("generic"),		NULL,			0},
//	{NULL,	_T("store"),			_T("ldap"),			NULL,			0},
//	{NULL,	_T("store"),			_T("mysql"),		NULL,			0},
//	{NULL,	_T("store"),			_T("oracle"),		NULL,			0},
//	{NULL,	_T("store"),			_T("postgres"),		NULL,			0},
	{NULL,	_T("store"),			NULL,				"node_store",	0},

//	icons for non-standard identities
	{NULL,	_T("x-service"),		_T("x-rss"),		"node_rss",		0},
	{NULL,	_T("application"),		_T("x-weather"),	"node_weather",	0},
	{NULL,	_T("user"),				NULL,				NULL,			SKINICON_STATUS_ONLINE},

//	icon suggestions based on supported features
	{_T("jabber:iq:gateway"),		NULL,NULL,			"Agents",		0},
	{_T("jabber:iq:search"),		NULL,NULL,			NULL,			SKINICON_OTHER_FINDUSER},
	{_T(JABBER_FEAT_COMMANDS),		NULL,NULL,			"adhoc",		0},
	{_T(JABBER_FEAT_REGISTER),		NULL,NULL,			"key",			0},
};

static void sttApplyNodeIcon(HTREELISTITEM hItem, CJabberSDNode *pNode);

void CJabberProto::OnIqResultServiceDiscoveryInfo( HXML iqNode, CJabberIqInfo* pInfo )
{
	m_SDManager.Lock();
	CJabberSDNode* pNode = m_SDManager.FindByIqId( pInfo->GetIqId(), TRUE );
	if ( !pNode ) {
		m_SDManager.Unlock();
		return;
	}

	if ( pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT ) {
		HXML query = xmlGetChild( iqNode , "query" );
		if ( !query )
			pNode->SetInfoRequestId( JABBER_DISCO_RESULT_ERROR );
		else {
			HXML feature;
			int i;
			for ( i = 1; ( feature = xmlGetNthChild( query, _T("feature"), i )) != NULL; i++ )
				pNode->AddFeature( xmlGetAttrValue( feature, _T("var")));
			HXML identity;
			for ( i = 1; ( identity = xmlGetNthChild( query, _T("identity"), i )) != NULL; i++ )
				pNode->AddIdentity( xmlGetAttrValue( identity, _T("category")), xmlGetAttrValue( identity, _T("type")), xmlGetAttrValue( identity, _T("name")));

			pNode->SetInfoRequestId( JABBER_DISCO_RESULT_OK );
			pNode->SetInfoRequestErrorText( NULL );
		}
	}
	else {
		if ( pInfo->GetIqType() == JABBER_IQ_TYPE_ERROR ) {
			HXML errorNode = xmlGetChild( iqNode , "error" );
			TCHAR* str = JabberErrorMsg( errorNode );
			pNode->SetInfoRequestErrorText( str );
			mir_free( str );
		}
		else pNode->SetInfoRequestErrorText( TranslateT("request timeout.") );

		pNode->SetInfoRequestId( JABBER_DISCO_RESULT_ERROR );
	}

	m_SDManager.Unlock();

	if ( m_pDlgServiceDiscovery ) {
		ApplyNodeIcon(pNode->GetTreeItemHandle(), pNode);
		PostMessage( m_pDlgServiceDiscovery->GetHwnd(), WM_JABBER_REFRESH, 0, 0 );
	}
}

void CJabberProto::OnIqResultServiceDiscoveryItems( HXML iqNode, CJabberIqInfo* pInfo )
{
	m_SDManager.Lock();
	CJabberSDNode* pNode = m_SDManager.FindByIqId( pInfo->GetIqId(), FALSE );
	if ( !pNode ) {
		m_SDManager.Unlock();
		return;
	}

	if ( pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT ) {
		HXML query = xmlGetChild( iqNode , "query" );
		if ( !query )
			pNode->SetItemsRequestId( JABBER_DISCO_RESULT_ERROR );
		else {
			HXML item;
			for ( int i = 1; ( item = xmlGetNthChild( query, _T("item"), i )) != NULL; i++ ) {
				pNode->AddChildNode( xmlGetAttrValue( item, _T("jid")), xmlGetAttrValue( item, _T("node")), xmlGetAttrValue( item, _T("name")));
			}

			pNode->SetItemsRequestId( JABBER_DISCO_RESULT_OK );
			pNode->SetItemsRequestErrorText( NULL );
		}
	}
	else {
		if ( pInfo->GetIqType() == JABBER_IQ_TYPE_ERROR ) {
			HXML errorNode = xmlGetChild( iqNode , "error" );
			TCHAR* str = JabberErrorMsg( errorNode );
			pNode->SetItemsRequestErrorText( str );
			mir_free( str );
		}
		else {
			pNode->SetItemsRequestErrorText( _T("request timeout.") );
		}
		pNode->SetItemsRequestId( JABBER_DISCO_RESULT_ERROR );
	}

	m_SDManager.Unlock();

	if ( m_pDlgServiceDiscovery ) {
		ApplyNodeIcon(pNode->GetTreeItemHandle(), pNode);
		PostMessage( m_pDlgServiceDiscovery->GetHwnd(), WM_JABBER_REFRESH, 0, 0 );
	}
}

void CJabberProto::OnIqResultServiceDiscoveryRootInfo( HXML iqNode, CJabberIqInfo* pInfo )
{
	if (!pInfo->m_pUserData) return;
	m_SDManager.Lock();
	if ( pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT ) {
		HXML query = xmlGetChild( iqNode , "query" );
		if ( query ) {
			HXML feature;
			int i;
			for ( i = 1; ( feature = xmlGetNthChild( query, _T("feature"), i )) != NULL; i++ ) {
				if ( !lstrcmp( xmlGetAttrValue( feature, _T("var")), (TCHAR *)pInfo->m_pUserData)) {
					CJabberSDNode *pNode = m_SDManager.AddPrimaryNode( pInfo->GetReceiver(), xmlGetAttrValue( iqNode, _T("node")), NULL);
					SendBothRequests( pNode, NULL );
					break;
	}	}	}	}
	m_SDManager.Unlock();

	UI_SAFE_NOTIFY(m_pDlgServiceDiscovery, WM_JABBER_REFRESH);
}

void CJabberProto::OnIqResultServiceDiscoveryRootItems( HXML iqNode, CJabberIqInfo* pInfo )
{
	if (!pInfo->m_pUserData)
		return;

	XmlNode packet( NULL );
	m_SDManager.Lock();
	if ( pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT ) {
		HXML query = xmlGetChild( iqNode , "query" );
		if ( query ) {
			HXML item;
			for ( int i = 1; ( item = xmlGetNthChild( query, _T("item"), i )) != NULL; i++ ) {
				const TCHAR *szJid = xmlGetAttrValue( item, _T("jid"));
				const TCHAR *szNode = xmlGetAttrValue( item, _T("node"));
				CJabberIqInfo* pNewInfo = m_iqManager.AddHandler( &CJabberProto::OnIqResultServiceDiscoveryRootInfo, JABBER_IQ_TYPE_GET, szJid );
				pNewInfo->m_pUserData = pInfo->m_pUserData;
				pNewInfo->SetTimeout( 30000 );
				
				XmlNodeIq iq( pNewInfo );
				iq << XQUERY( _T(JABBER_FEAT_DISCO_INFO)) << XATTR( _T("node"), szNode );
				xmlAddChild( packet, iq );
	}	}	}
	m_SDManager.Unlock();

	if ( xmlGetChild( packet ,0))
		m_ThreadInfo->send( packet );
}

BOOL CJabberProto::SendInfoRequest(CJabberSDNode* pNode, HXML parent)
{
	if ( !pNode || !m_bJabberOnline )
		return FALSE;

	// disco#info
	if ( !pNode->GetInfoRequestId() ) {
		CJabberIqInfo* pInfo = m_iqManager.AddHandler( &CJabberProto::OnIqResultServiceDiscoveryInfo, JABBER_IQ_TYPE_GET, pNode->GetJid() );
		pInfo->SetTimeout( 30000 );
		pNode->SetInfoRequestId( pInfo->GetIqId() );

		XmlNodeIq iq( pInfo );
		HXML query = iq << XQUERY( _T(JABBER_FEAT_DISCO_INFO));
		if ( pNode->GetNode() )
			xmlAddAttr( query, _T("node"), pNode->GetNode() );

		if ( parent )
			xmlAddChild( parent, iq );
		else
			m_ThreadInfo->send( iq );
	}

	if ( m_pDlgServiceDiscovery ) {
		ApplyNodeIcon(pNode->GetTreeItemHandle(), pNode);
		PostMessage( m_pDlgServiceDiscovery->GetHwnd(), WM_JABBER_REFRESH, 0, 0 );
	}

	return TRUE;
}

BOOL CJabberProto::SendBothRequests(CJabberSDNode* pNode, HXML parent)
{
	if ( !pNode || !m_bJabberOnline )
		return FALSE;

	// disco#info
	if ( !pNode->GetInfoRequestId() ) {
		CJabberIqInfo* pInfo = m_iqManager.AddHandler( &CJabberProto::OnIqResultServiceDiscoveryInfo, JABBER_IQ_TYPE_GET, pNode->GetJid() );
		pInfo->SetTimeout( 30000 );
		pNode->SetInfoRequestId( pInfo->GetIqId() );

		XmlNodeIq iq( pInfo );
		HXML query = iq << XQUERY( _T(JABBER_FEAT_DISCO_INFO));
		if ( pNode->GetNode() )
			xmlAddAttr( query, _T("node"), pNode->GetNode() );

		if ( parent )
			xmlAddChild( parent, iq );
		else
			m_ThreadInfo->send( iq );
	}

	// disco#items
	if ( !pNode->GetItemsRequestId() ) {
		CJabberIqInfo* pInfo = m_iqManager.AddHandler( &CJabberProto::OnIqResultServiceDiscoveryItems, JABBER_IQ_TYPE_GET, pNode->GetJid() );
		pInfo->SetTimeout( 30000 );
		pNode->SetItemsRequestId( pInfo->GetIqId() );

		XmlNodeIq iq( pInfo );
		HXML query = iq << XQUERY( _T(JABBER_FEAT_DISCO_ITEMS));
		if ( pNode->GetNode() )
			xmlAddAttr( query, _T("node"), pNode->GetNode() );

		if ( parent )
			xmlAddChild( parent, iq );
		else
			m_ThreadInfo->send( iq );
	}

	if ( m_pDlgServiceDiscovery ) {
		ApplyNodeIcon(pNode->GetTreeItemHandle(), pNode);
		PostMessage( m_pDlgServiceDiscovery->GetHwnd(), WM_JABBER_REFRESH, 0, 0 );
	}

	return TRUE;
}

void CJabberProto::PerformBrowse(HWND hwndDlg)
{
	TCHAR szJid[ JABBER_MAX_JID_LEN ];
	TCHAR szNode[ 512 ];
	if ( !GetDlgItemText( hwndDlg, IDC_COMBO_JID, szJid, SIZEOF( szJid )))
		szJid[ 0 ] = 0;
	if ( !GetDlgItemText( hwndDlg, IDC_COMBO_NODE, szNode, SIZEOF( szNode )))
		szNode[ 0 ] = 0;
	
	ComboAddRecentString(hwndDlg, IDC_COMBO_JID, "discoWnd_rcJid", szJid);
	ComboAddRecentString(hwndDlg, IDC_COMBO_NODE, "discoWnd_rcNode", szNode);

	if ( _tcslen( szJid )) {
		HWND hwndList = GetDlgItem(hwndDlg, IDC_TREE_DISCO);
		TreeList_Reset(hwndList);

		m_SDManager.Lock();
		m_SDManager.RemoveAll();
		if (!lstrcmp(szJid, _T(SD_FAKEJID_MYAGENTS))) {
			sttBrowseMode = SD_BROWSE_MYAGENTS;
			JABBER_LIST_ITEM *item = NULL;
			LISTFOREACH(i, this, LIST_ROSTER)
			{
				if (( item=ListGetItemPtrFromIndex( i )) != NULL ) {
					if ( _tcschr( item->jid, '@' )==NULL && _tcschr( item->jid, '/' )==NULL && item->subscription!=SUB_NONE ) {
						HANDLE hContact = HContactFromJID( item->jid );
						if ( hContact != NULL )
							JSetByte( hContact, "IsTransport", TRUE );

						if ( m_lstTransports.getIndex( item->jid ) == -1 )
							m_lstTransports.insert( mir_tstrdup( item->jid ));

						CJabberSDNode* pNode = m_SDManager.AddPrimaryNode(item->jid, NULL, NULL);
						SendBothRequests( pNode, NULL );
				}	}
		}	}
		else if (!lstrcmp(szJid, _T(SD_FAKEJID_CONFERENCES))) {
			sttBrowseMode = SD_BROWSE_CONFERENCES;
			TCHAR *szServerJid = mir_a2t(m_ThreadInfo->server);
			CJabberIqInfo* pInfo = m_iqManager.AddHandler( &CJabberProto::OnIqResultServiceDiscoveryRootItems, JABBER_IQ_TYPE_GET, szServerJid );
			pInfo->m_pUserData = (void *)_T(JABBER_FEAT_MUC);
			pInfo->SetTimeout( 30000 );
			XmlNodeIq iq( pInfo );
			iq << XQUERY( _T(JABBER_FEAT_DISCO_ITEMS));
			m_ThreadInfo->send( iq );
			mir_free(szServerJid);
		}
		else if (!lstrcmp(szJid, _T(SD_FAKEJID_AGENTS))) {
			sttBrowseMode = SD_BROWSE_AGENTS;
			TCHAR *szServerJid = mir_a2t(m_ThreadInfo->server);
			CJabberIqInfo* pInfo = m_iqManager.AddHandler( &CJabberProto::OnIqResultServiceDiscoveryRootItems, JABBER_IQ_TYPE_GET, szServerJid );
			pInfo->m_pUserData = (void *)_T("jabber:iq:gateway");
			pInfo->SetTimeout( 30000 );
			XmlNodeIq iq( pInfo );
			iq << XQUERY( _T(JABBER_FEAT_DISCO_ITEMS));
			m_ThreadInfo->send( iq );
			mir_free(szServerJid);
		}
		else if (!lstrcmp(szJid, _T(SD_FAKEJID_FAVORITES))) {
			sttBrowseMode = SD_BROWSE_FAVORITES;
			int count = JGetDword(NULL, "discoWnd_favCount", 0);
			for (int i = 0; i < count; ++i)
			{
				DBVARIANT dbv;
				char setting[MAXMODULELABELLENGTH];
				mir_snprintf(setting, sizeof(setting), "discoWnd_favName_%d", i);
				if (!JGetStringT(NULL, setting, &dbv)) {
					DBVARIANT dbvJid, dbvNode;
					mir_snprintf(setting, sizeof(setting), "discoWnd_favJID_%d", i);
					JGetStringT(NULL, setting, &dbvJid);
					mir_snprintf(setting, sizeof(setting), "discoWnd_favNode_%d", i);
					JGetStringT(NULL, setting, &dbvNode);
					CJabberSDNode* pNode = m_SDManager.AddPrimaryNode(dbvJid.ptszVal, dbvNode.ptszVal, dbv.ptszVal);
					SendBothRequests( pNode, NULL );
					JFreeVariant(&dbv);
					JFreeVariant(&dbvJid);
					JFreeVariant(&dbvNode);
		}	}	}
		else {
			sttBrowseMode = SD_BROWSE_NORMAL;
			CJabberSDNode* pNode = m_SDManager.AddPrimaryNode(szJid, _tcslen( szNode ) ? szNode : NULL, NULL);
			SendBothRequests( pNode, NULL );
		}
		m_SDManager.Unlock();

		PostMessage( hwndDlg, WM_JABBER_REFRESH, 0, 0 );
	}
}

BOOL CJabberProto::IsNodeRegistered(CJabberSDNode *pNode)
{
	if (pNode->GetNode())
		return FALSE;

	JABBER_LIST_ITEM *item;
	if (item = ListGetItemPtr(LIST_ROSTER, pNode->GetJid()))
		return (item->subscription != SUB_NONE) ? TRUE : FALSE;

	if (item = ListGetItemPtr(LIST_BOOKMARK, pNode->GetJid()))
		return TRUE;

	return FALSE;
}

void CJabberProto::ApplyNodeIcon(HTREELISTITEM hItem, CJabberSDNode *pNode)
{
	if (!hItem || !pNode) return;

	int iIcon = -1, iOverlay = -1;

	if ((pNode->GetInfoRequestId() > 0) || (pNode->GetItemsRequestId() > 0))
		iOverlay = SD_OVERLAY_PROGRESS;
	else if (pNode->GetInfoRequestId() == JABBER_DISCO_RESULT_ERROR)
		iOverlay = SD_OVERLAY_FAIL;
	else if (pNode->GetInfoRequestId() == JABBER_DISCO_RESULT_NOT_REQUESTED) {
		if (IsNodeRegistered(pNode))
			iOverlay = SD_OVERLAY_REGISTERED;
		else
			iOverlay = SD_OVERLAY_NONE;
	} else if (pNode->GetInfoRequestId() == JABBER_DISCO_RESULT_OK) {
		if (IsNodeRegistered(pNode))
			iOverlay = SD_OVERLAY_REGISTERED;
		else if (pNode->GetInfoRequestId() == JABBER_DISCO_RESULT_ERROR)
			iOverlay = SD_OVERLAY_FAIL;
		else
			iOverlay = SD_OVERLAY_NONE;
	}

	for (int i = 0; i < SIZEOF(sttNodeIcons); ++i)
	{
		if (!sttNodeIcons[i].iconIndex && !sttNodeIcons[i].iconName) continue;

		if (sttNodeIcons[i].category)
		{
			CJabberSDIdentity *iIdentity;
			for (iIdentity = pNode->GetFirstIdentity(); iIdentity; iIdentity = iIdentity->GetNext())
				if (!lstrcmp(iIdentity->GetCategory(), sttNodeIcons[i].category) &&
					(!sttNodeIcons[i].type || !lstrcmp(iIdentity->GetType(), sttNodeIcons[i].type)))
				{
					iIcon = sttNodeIcons[i].listIndex;
					break;
				}
			if (iIdentity) break;
		}

		if (sttNodeIcons[i].feature)
		{
			CJabberSDFeature *iFeature;
			for (iFeature = pNode->GetFirstFeature(); iFeature; iFeature = iFeature->GetNext())
				if (!lstrcmp(iFeature->GetVar(), sttNodeIcons[i].feature))
				{
					iIcon = sttNodeIcons[i].listIndex;
					break;
				}
			if (iFeature) break;
		}
	}

	TreeList_SetIcon(pNode->GetTreeItemHandle(), iIcon, iOverlay);
}

BOOL CJabberProto::SyncTree(HTREELISTITEM hIndex, CJabberSDNode* pNode)
{
	if (!m_pDlgServiceDiscovery) return FALSE;

	CJabberSDNode* pTmp = pNode;
	while (pTmp) {
		if ( !pTmp->GetTreeItemHandle() ) {
			HTREELISTITEM hNewItem = TreeList_AddItem(
				GetDlgItem(m_pDlgServiceDiscovery->GetHwnd(), IDC_TREE_DISCO), hIndex,
				pTmp->GetName() ? pTmp->GetName() : pTmp->GetJid(),
				(LPARAM)pTmp);
			TreeList_AppendColumn(hNewItem, pTmp->GetJid());
			TreeList_AppendColumn(hNewItem, pTmp->GetNode());
			if (!pTmp->GetInfoRequestId())
				TreeList_MakeFakeParent(hNewItem, TRUE);
			else
				TreeList_MakeFakeParent(hNewItem, FALSE);
			pTmp->SetTreeItemHandle( hNewItem );
		}

		ApplyNodeIcon(pNode->GetTreeItemHandle(), pNode);

		if ( pTmp->GetFirstChildNode() )
			SyncTree( pTmp->GetTreeItemHandle(), pTmp->GetFirstChildNode() );

		pTmp = pTmp->GetNext();
	}
	return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// CJabberDlgDiscovery
class CJabberDlgDiscovery: public CJabberDlgBase
{
	typedef CJabberDlgBase CSuper;

public:
	CJabberDlgDiscovery(CJabberProto *proto, TCHAR *jid);

protected:
	void OnInitDialog();
	void OnClose();
	void OnDestroy();
	INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam);
	int Resizer(UTILRESIZECONTROL *urc);

private:
	TCHAR *m_jid;
	bool m_focusEditAfterBrowse;

	CCtrlMButton m_btnViewAsTree;
	CCtrlMButton m_btnViewAsList;
	CCtrlMButton m_btnGoHome;
	CCtrlMButton m_btnBookmarks;
	CCtrlMButton m_btnRefresh;
	CCtrlMButton m_btnBrowse;
	CCtrlFilterListView m_lstDiscoTree;

	void btnViewAsTree_OnClick(CCtrlButton *);
	void btnViewAsList_OnClick(CCtrlButton *);
	void btnGoHome_OnClick(CCtrlButton *);
	void btnBookmarks_OnClick(CCtrlButton *);
	void btnRefresh_OnClick(CCtrlButton *);
	void btnBrowse_OnClick(CCtrlButton *);
	void lstDiscoTree_OnFilter(CCtrlFilterListView *);
};

CJabberDlgDiscovery::CJabberDlgDiscovery(CJabberProto *proto, TCHAR *jid) :
	CJabberDlgBase(proto, IDD_SERVICE_DISCOVERY, NULL),
	m_jid(jid),
	m_btnViewAsTree(this, IDC_BTN_VIEWTREE, proto->LoadIconEx("sd_view_tree"), "View as tree"),
	m_btnViewAsList(this, IDC_BTN_VIEWLIST, proto->LoadIconEx("sd_view_list"), "View as list"),
	m_btnGoHome(this, IDC_BTN_NAVHOME, proto->LoadIconEx("sd_nav_home"), "Navigate home"),
	m_btnBookmarks(this, IDC_BTN_FAVORITE, proto->LoadIconEx("bookmarks"), "Favorites"),
	m_btnRefresh(this, IDC_BTN_REFRESH, proto->LoadIconEx("sd_nav_refresh"), "Refresh node"),
	m_btnBrowse(this, IDC_BUTTON_BROWSE, proto->LoadIconEx("sd_browse"), "Browse"),
	m_lstDiscoTree(this, IDC_TREE_DISCO, true, false)
{
	m_btnViewAsTree.OnClick = Callback(this, &CJabberDlgDiscovery::btnViewAsTree_OnClick);
	m_btnViewAsList.OnClick = Callback(this, &CJabberDlgDiscovery::btnViewAsList_OnClick);
	m_btnGoHome.OnClick = Callback(this, &CJabberDlgDiscovery::btnGoHome_OnClick);
	m_btnBookmarks.OnClick = Callback(this, &CJabberDlgDiscovery::btnBookmarks_OnClick);
	m_btnRefresh.OnClick = Callback(this, &CJabberDlgDiscovery::btnRefresh_OnClick);
	m_btnBrowse.OnClick = Callback(this, &CJabberDlgDiscovery::btnBrowse_OnClick);
	m_lstDiscoTree.OnFilterChanged = Callback(this, &CJabberDlgDiscovery::lstDiscoTree_OnFilter);
}

void CJabberDlgDiscovery::OnInitDialog()
{
	CSuper::OnInitDialog();

//	TranslateDialogDefault( m_hwnd );
	WindowSetIcon( m_hwnd, m_proto, "servicediscovery" );

	int i;

	if ( m_jid ) {
		SetDlgItemText( m_hwnd, IDC_COMBO_JID, m_jid );
		SetDlgItemText( m_hwnd, IDC_COMBO_NODE, _T("") );
		m_focusEditAfterBrowse = false;
	} else {
		SetDlgItemTextA( m_hwnd, IDC_COMBO_JID, m_proto->m_ThreadInfo->server );
		SetDlgItemText( m_hwnd, IDC_COMBO_NODE, _T("") );
		m_focusEditAfterBrowse = true;
	}

	m_btnViewAsList.MakePush();
	m_btnViewAsTree.MakePush();
	m_btnBookmarks.MakePush();

	CheckDlgButton(m_hwnd,
		DBGetContactSettingByte(NULL, m_proto->m_szModuleName, "discoWnd_useTree", 1) ?
			IDC_BTN_VIEWTREE : IDC_BTN_VIEWLIST,
		TRUE);

	EnableWindow(GetDlgItem(m_hwnd, IDC_BTN_FILTERRESET), FALSE);

	SendDlgItemMessage(m_hwnd, IDC_COMBO_JID, CB_ADDSTRING, 0, (LPARAM)_T(SD_FAKEJID_CONFERENCES));
	SendDlgItemMessage(m_hwnd, IDC_COMBO_JID, CB_ADDSTRING, 0, (LPARAM)_T(SD_FAKEJID_MYAGENTS));
	SendDlgItemMessage(m_hwnd, IDC_COMBO_JID, CB_ADDSTRING, 0, (LPARAM)_T(SD_FAKEJID_AGENTS));
	SendDlgItemMessage(m_hwnd, IDC_COMBO_JID, CB_ADDSTRING, 0, (LPARAM)_T(SD_FAKEJID_FAVORITES));
	m_proto->ComboLoadRecentStrings(m_hwnd, IDC_COMBO_JID, "discoWnd_rcJid");
	m_proto->ComboLoadRecentStrings(m_hwnd, IDC_COMBO_NODE, "discoWnd_rcNode");

	HWND hwndList = m_lstDiscoTree.GetHwnd();//GetDlgItem(m_hwnd, IDC_TREE_DISCO);
	LVCOLUMN lvc = {0};
	lvc.mask = LVCF_SUBITEM|LVCF_WIDTH|LVCF_TEXT;
	lvc.cx = DBGetContactSettingWord(NULL, m_proto->m_szModuleName, "discoWnd_cx0", 200);
	lvc.iSubItem = 0;
	lvc.pszText = TranslateT("Node hierarchy");
	ListView_InsertColumn(hwndList, 0, &lvc);
	lvc.cx = DBGetContactSettingWord(NULL, m_proto->m_szModuleName, "discoWnd_cx1", 200);
	lvc.iSubItem = 1;
	lvc.pszText = _T("JID");
	ListView_InsertColumn(hwndList, 1, &lvc);
	lvc.cx = DBGetContactSettingWord(NULL, m_proto->m_szModuleName, "discoWnd_cx2", 200);
	lvc.iSubItem = 2;
	lvc.pszText = TranslateT("Node");
	ListView_InsertColumn(hwndList, 2, &lvc);

	TreeList_Create(hwndList);
	TreeList_AddIcon(hwndList, m_proto->LoadIconEx("main"), 0);
	for (i = 0; i < SIZEOF(sttNodeIcons); ++i)
	{
		bool needDestroy = false;
		HICON hIcon;
		if ((sttNodeIcons[i].iconIndex == SKINICON_STATUS_ONLINE) && sttNodeIcons[i].iconName) {
			hIcon = (HICON)CallProtoService(sttNodeIcons[i].iconName, PS_LOADICON, PLI_PROTOCOL|PLIF_SMALL, 0);
			needDestroy = true;
		}
		else if (sttNodeIcons[i].iconName)
			hIcon = m_proto->LoadIconEx(sttNodeIcons[i].iconName);
		else if (sttNodeIcons[i].iconIndex)
			hIcon = LoadSkinnedIcon(sttNodeIcons[i].iconIndex);
		else continue;
		sttNodeIcons[i].listIndex = TreeList_AddIcon(hwndList, hIcon, 0);
		if (needDestroy) DestroyIcon(hIcon);
	}
	TreeList_AddIcon(hwndList, m_proto->LoadIconEx("disco_fail"), SD_OVERLAY_FAIL);
	TreeList_AddIcon(hwndList, m_proto->LoadIconEx("disco_progress"), SD_OVERLAY_PROGRESS);
	TreeList_AddIcon(hwndList, m_proto->LoadIconEx("disco_ok"), SD_OVERLAY_REGISTERED);

	TreeList_SetMode(hwndList, DBGetContactSettingByte(NULL, m_proto->m_szModuleName, "discoWnd_useTree", 1) ? TLM_TREE : TLM_REPORT);

	PostMessage( m_hwnd, WM_COMMAND, MAKEWPARAM( IDC_BUTTON_BROWSE, 0 ), 0 );

	Utils_RestoreWindowPosition(m_hwnd, NULL, m_proto->m_szModuleName, "discoWnd_");
}

void CJabberDlgDiscovery::OnClose()
{
	DBWriteContactSettingByte(NULL, m_proto->m_szModuleName, "discoWnd_useTree", IsDlgButtonChecked(m_hwnd, IDC_BTN_VIEWTREE));

	HWND hwndList = GetDlgItem(m_hwnd, IDC_TREE_DISCO);
	LVCOLUMN lvc = {0};
	lvc.mask = LVCF_WIDTH;
	ListView_GetColumn(hwndList, 0, &lvc);
	DBWriteContactSettingWord(NULL, m_proto->m_szModuleName, "discoWnd_cx0", lvc.cx);
	ListView_GetColumn(hwndList, 1, &lvc);
	DBWriteContactSettingWord(NULL, m_proto->m_szModuleName, "discoWnd_cx1", lvc.cx);
	ListView_GetColumn(hwndList, 2, &lvc);
	DBWriteContactSettingWord(NULL, m_proto->m_szModuleName, "discoWnd_cx2", lvc.cx);

	Utils_SaveWindowPosition(m_hwnd, NULL, m_proto->m_szModuleName, "discoWnd_");
	DestroyWindow( m_hwnd );

	CSuper::OnClose();
}

void CJabberDlgDiscovery::OnDestroy()
{
	m_proto->m_pDlgServiceDiscovery = NULL;
	m_proto->m_SDManager.Lock();
	m_proto->m_SDManager.RemoveAll();
	m_proto->m_SDManager.Unlock();
	TreeList_Destroy(GetDlgItem(m_hwnd, IDC_TREE_DISCO));

	CSuper::OnDestroy();
}

int CJabberDlgDiscovery::Resizer(UTILRESIZECONTROL *urc)
{
	RECT rc;

	switch ( urc->wId ) {
		case IDC_COMBO_JID:
		{
			GetWindowRect(GetDlgItem(m_hwnd, urc->wId), &rc);
			urc->rcItem.right += (urc->dlgNewSize.cx - urc->dlgOriginalSize.cx) / 2;
			urc->rcItem.bottom = urc->rcItem.top + rc.bottom - rc.top;
			return 0;
		}
		case IDC_TXT_NODELABEL:
		{
			urc->rcItem.left += (urc->dlgNewSize.cx - urc->dlgOriginalSize.cx) / 2;
			urc->rcItem.right += (urc->dlgNewSize.cx - urc->dlgOriginalSize.cx) / 2;
			return 0;
		}
		case IDC_COMBO_NODE:
		{
			GetWindowRect(GetDlgItem(m_hwnd, urc->wId), &rc);
			urc->rcItem.left += (urc->dlgNewSize.cx - urc->dlgOriginalSize.cx) / 2;
			urc->rcItem.right += urc->dlgNewSize.cx - urc->dlgOriginalSize.cx;
			urc->rcItem.bottom = urc->rcItem.top + rc.bottom - rc.top;
			return 0;
		}
		case IDC_BUTTON_BROWSE:
			return RD_ANCHORX_RIGHT|RD_ANCHORY_TOP;

		case IDC_TREE_DISCO:
			return RD_ANCHORX_WIDTH|RD_ANCHORY_HEIGHT;

		case IDC_TXT_FILTER:
			return RD_ANCHORX_LEFT|RD_ANCHORY_BOTTOM;
		case IDC_TXT_FILTERTEXT:
			return RD_ANCHORX_WIDTH|RD_ANCHORY_BOTTOM;
		case IDC_BTN_FILTERAPPLY:
		case IDC_BTN_FILTERRESET:
			return RD_ANCHORX_RIGHT|RD_ANCHORY_BOTTOM;
	}
	return CSuper::Resizer(urc);
}

void CJabberDlgDiscovery::btnViewAsTree_OnClick(CCtrlButton *)
{
	CheckDlgButton(m_hwnd, IDC_BTN_VIEWLIST, FALSE);
	CheckDlgButton(m_hwnd, IDC_BTN_VIEWTREE, TRUE);
	TreeList_SetMode(GetDlgItem(m_hwnd, IDC_TREE_DISCO), TLM_TREE);
}

void CJabberDlgDiscovery::btnViewAsList_OnClick(CCtrlButton *)
{
	CheckDlgButton(m_hwnd, IDC_BTN_VIEWLIST, TRUE);
	CheckDlgButton(m_hwnd, IDC_BTN_VIEWTREE, FALSE);
	TreeList_SetMode(GetDlgItem(m_hwnd, IDC_TREE_DISCO), TLM_REPORT);
}

void CJabberDlgDiscovery::btnGoHome_OnClick(CCtrlButton *)
{
	SetDlgItemTextA( m_hwnd, IDC_COMBO_JID, m_proto->m_ThreadInfo->server );
	SetDlgItemText( m_hwnd, IDC_COMBO_NODE, _T("") );
	PostMessage( m_hwnd, WM_COMMAND, MAKEWPARAM( IDC_BUTTON_BROWSE, 0 ), 0 );
}

void CJabberDlgDiscovery::btnBookmarks_OnClick(CCtrlButton *)
{
	HMENU hMenu = CreatePopupMenu();
	int count = m_proto->JGetDword(NULL, "discoWnd_favCount", 0);
	for (int i = 0; i < count; ++i)
	{
		DBVARIANT dbv;
		char setting[MAXMODULELABELLENGTH];
		mir_snprintf(setting, sizeof(setting), "discoWnd_favName_%d", i);
		if (!m_proto->JGetStringT(NULL, setting, &dbv))
		{
			HMENU hSubMenu = CreatePopupMenu();
			AppendMenu(hSubMenu, MF_STRING, 100+i*10+0, TranslateT("Navigate"));
			AppendMenu(hSubMenu, MF_SEPARATOR, 0, NULL);
			AppendMenu(hSubMenu, MF_STRING, 100+i*10+1, TranslateT("Remove"));
			AppendMenu(hMenu, MF_POPUP|MF_STRING, (UINT_PTR)hSubMenu, dbv.ptszVal);
		}
		JFreeVariant(&dbv);
	}
	int res = 0;
	if (GetMenuItemCount(hMenu)) {
		AppendMenu(hMenu, MF_SEPARATOR, 1, NULL);
		AppendMenu(hMenu, MF_STRING, 10+SD_BROWSE_FAVORITES, TranslateT("Browse all favorites"));
		AppendMenu(hMenu, MF_STRING, 1, TranslateT("Remove all favorites"));
	}
	if (GetMenuItemCount(hMenu))
		AppendMenu(hMenu, MF_SEPARATOR, 1, NULL);

	AppendMenu(hMenu, MF_STRING, 10+SD_BROWSE_MYAGENTS, TranslateT("Registered transports"));
	AppendMenu(hMenu, MF_STRING, 10+SD_BROWSE_AGENTS, TranslateT("Browse local transports"));
	AppendMenu(hMenu, MF_STRING, 10+SD_BROWSE_CONFERENCES, TranslateT("Browse chatrooms"));

	RECT rc; GetWindowRect(GetDlgItem(m_hwnd, IDC_BTN_FAVORITE), &rc);
	CheckDlgButton(m_hwnd, IDC_BTN_FAVORITE, TRUE);
	res = TrackPopupMenu(hMenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, NULL);
	CheckDlgButton(m_hwnd, IDC_BTN_FAVORITE, FALSE);
	DestroyMenu(hMenu);

	if (res >= 100)
	{
		res -= 100;
		if (res % 10)
		{
			res /= 10;
			char setting[MAXMODULELABELLENGTH];
			mir_snprintf(setting, sizeof(setting), "discoWnd_favName_%d", res);
			m_proto->JDeleteSetting(NULL, setting);
			mir_snprintf(setting, sizeof(setting), "discoWnd_favJID_%d", res);
			m_proto->JDeleteSetting(NULL, setting);
			mir_snprintf(setting, sizeof(setting), "discoWnd_favNode_%d", res);
			m_proto->JDeleteSetting(NULL, setting);
		} else
		{
			res /= 10;

			SetDlgItemText(m_hwnd, IDC_COMBO_JID, _T(""));
			SetDlgItemText(m_hwnd, IDC_COMBO_NODE, _T(""));

			DBVARIANT dbv;
			char setting[MAXMODULELABELLENGTH];
			mir_snprintf(setting, sizeof(setting), "discoWnd_favJID_%d", res);
			if (!m_proto->JGetStringT(NULL, setting, &dbv)) SetDlgItemText(m_hwnd, IDC_COMBO_JID, dbv.ptszVal);
			JFreeVariant(&dbv);
			mir_snprintf(setting, sizeof(setting), "discoWnd_favNode_%d", res);
			if (!m_proto->JGetStringT(NULL, setting, &dbv)) SetDlgItemText(m_hwnd, IDC_COMBO_NODE, dbv.ptszVal);
			JFreeVariant(&dbv);
			
			PostMessage( m_hwnd, WM_COMMAND, MAKEWPARAM( IDC_BUTTON_BROWSE, 0 ), 0 );
		}
	} else
	if (res == 1)
	{
		int count = m_proto->JGetDword(NULL, "discoWnd_favCount", 0);
		for (int i = 0; i < count; ++i)
		{
			char setting[MAXMODULELABELLENGTH];
			mir_snprintf(setting, sizeof(setting), "discoWnd_favName_%d", i);
			m_proto->JDeleteSetting(NULL, setting);
			mir_snprintf(setting, sizeof(setting), "discoWnd_favJID_%d", i);
			m_proto->JDeleteSetting(NULL, setting);
			mir_snprintf(setting, sizeof(setting), "discoWnd_favNode_%d", i);
			m_proto->JDeleteSetting(NULL, setting);
		}
		m_proto->JDeleteSetting(NULL, "discoWnd_favCount");
	} else
	if ((res >= 10) && (res <= 20))
	{
		switch (res-10) {
		case SD_BROWSE_FAVORITES:
			SetDlgItemText(m_hwnd, IDC_COMBO_JID, _T(SD_FAKEJID_FAVORITES));
			break;
		case SD_BROWSE_MYAGENTS:
			SetDlgItemText(m_hwnd, IDC_COMBO_JID, _T(SD_FAKEJID_MYAGENTS));
			break;
		case SD_BROWSE_AGENTS:
			SetDlgItemText(m_hwnd, IDC_COMBO_JID, _T(SD_FAKEJID_AGENTS));
			break;
		case SD_BROWSE_CONFERENCES:
			SetDlgItemText(m_hwnd, IDC_COMBO_JID, _T(SD_FAKEJID_CONFERENCES));
			break;
		}
		SetDlgItemText(m_hwnd, IDC_COMBO_NODE, _T(""));
		PostMessage( m_hwnd, WM_COMMAND, MAKEWPARAM( IDC_BUTTON_BROWSE, 0 ), 0 );
	}

	CheckDlgButton(m_hwnd, IDC_BTN_FAVORITE, FALSE);
}

void CJabberDlgDiscovery::btnRefresh_OnClick(CCtrlButton *)
{
	HTREELISTITEM hItem = (HTREELISTITEM)TreeList_GetActiveItem(GetDlgItem(m_hwnd, IDC_TREE_DISCO));
	if (!hItem) return;

	m_proto->m_SDManager.Lock();
	XmlNode packet( NULL );
	CJabberSDNode* pNode = (CJabberSDNode* )TreeList_GetData(hItem);
	if ( pNode ) {
		TreeList_ResetItem(GetDlgItem(m_hwnd, IDC_TREE_DISCO), hItem);
		pNode->ResetInfo();
		m_proto->SendBothRequests( pNode, packet );
		TreeList_MakeFakeParent(hItem, FALSE);
	}
	m_proto->m_SDManager.Unlock();

	if ( xmlGetChild( packet ,0))
		m_proto->m_ThreadInfo->send( packet );
}

void CJabberDlgDiscovery::btnBrowse_OnClick(CCtrlButton *)
{
	SetFocus(GetDlgItem(m_hwnd, m_focusEditAfterBrowse ? IDC_COMBO_JID : IDC_TREE_DISCO));
	m_focusEditAfterBrowse = false;

	m_proto->PerformBrowse(m_hwnd);
}

void CJabberDlgDiscovery::lstDiscoTree_OnFilter(CCtrlFilterListView *)
{
	TreeList_SetFilter(GetDlgItem(m_hwnd, IDC_TREE_DISCO), m_lstDiscoTree.GetFilterText());
}

INT_PTR CJabberDlgDiscovery::DlgProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
	BOOL result;
	if (TreeList_ProcessMessage(m_hwnd, msg, wParam, lParam, IDC_TREE_DISCO, &result))
		return result;

	switch ( msg ) {
	case WM_GETMINMAXINFO:
		{
			LPMINMAXINFO lpmmi = (LPMINMAXINFO)lParam;
			lpmmi->ptMinTrackSize.x = 538;
			lpmmi->ptMinTrackSize.y = 374;
			return 0;
		}

	case WM_JABBER_TRANSPORT_REFRESH:
		if (m_proto->m_nSDBrowseMode == SD_BROWSE_MYAGENTS) {
			SetDlgItemText(m_hwnd, IDC_COMBO_JID, _T(SD_FAKEJID_MYAGENTS));
			SetDlgItemText(m_hwnd, IDC_COMBO_NODE, _T(""));
			PostMessage( m_hwnd, WM_COMMAND, MAKEWPARAM( IDC_BUTTON_BROWSE, 0 ), 0 );
		}
		break;

	case WM_JABBER_REFRESH:
		KillTimer(m_hwnd, REFRESH_TIMER);
		if (GetTickCount() - m_proto->m_dwSDLastRefresh < REFRESH_TIMEOUT) {
			SetTimer(m_hwnd, REFRESH_TIMER, REFRESH_TIMEOUT, NULL);
			return TRUE;
		} 
		
		wParam = REFRESH_TIMER;
		// fall through

	case WM_TIMER:
		if (wParam == REFRESH_TIMER) {
			m_proto->m_SDManager.Lock();

			CJabberSDNode* pNode = m_proto->m_SDManager.GetPrimaryNode();
			while (pNode)
			{
				if ( pNode->GetJid() ) {
					if ( !pNode->GetTreeItemHandle() ) {
						HTREELISTITEM hNewItem = TreeList_AddItem(
							GetDlgItem( m_hwnd, IDC_TREE_DISCO), NULL,
							pNode->GetName() ? pNode->GetName() : pNode->GetJid(),
							(LPARAM)pNode);
						TreeList_AppendColumn(hNewItem, pNode->GetJid());
						TreeList_AppendColumn(hNewItem, pNode->GetNode());
						pNode->SetTreeItemHandle( hNewItem );
				}	}
				m_proto->SyncTree( NULL, pNode );
				pNode = pNode->GetNext();
			}
			m_proto->m_SDManager.Unlock();
			TreeList_Update(GetDlgItem(m_hwnd, IDC_TREE_DISCO));
			KillTimer(m_hwnd, REFRESH_TIMER);
			m_proto->m_dwSDLastRefresh = GetTickCount();
			return TRUE;
		}
		else if (wParam == AUTODISCO_TIMER) {
			HWND hwndList = GetDlgItem(m_hwnd, IDC_TREE_DISCO);
			RECT rcCtl; GetClientRect(hwndList, &rcCtl);
			RECT rcHdr; GetClientRect(ListView_GetHeader(hwndList), &rcHdr);
			LVHITTESTINFO lvhti = {0};
			lvhti.pt.x = rcCtl.left + 5;
			lvhti.pt.y = rcHdr.bottom + 5;
			int iFirst = ListView_HitTest(hwndList, &lvhti);
			ZeroMemory(&lvhti, sizeof(lvhti));
			lvhti.pt.x = rcCtl.left + 5;
			lvhti.pt.y = rcCtl.bottom - 5;
			int iLast = ListView_HitTest(hwndList, &lvhti);
			if (iFirst < 0) return FALSE;
			if (iLast < 0) iLast = ListView_GetItemCount(hwndList) - 1;

			m_proto->m_SDManager.Lock();
			XmlNode packet( NULL );
			for (int i = iFirst; i <= iLast; ++i)
			{
				LVITEM lvi = {0};
				lvi.mask = LVIF_PARAM;
				lvi.iItem = i;
				ListView_GetItem(hwndList, &lvi);
				if (!lvi.lParam)
					continue;

				CJabberSDNode *pNode = (CJabberSDNode *)TreeList_GetData((HTREELISTITEM)lvi.lParam);
				if (!pNode || pNode->GetInfoRequestId())
					continue;

				m_proto->SendInfoRequest(pNode, packet);
			}
			m_proto->m_SDManager.Unlock();
			if ( xmlGetChild( packet, 0))
				m_proto->m_ThreadInfo->send( packet );

			KillTimer(m_hwnd, AUTODISCO_TIMER);
			m_proto->m_dwSDLastRefresh = GetTickCount();
			return TRUE;
		}
		break;

	case WM_CONTEXTMENU:
		if (GetWindowLong((HWND)wParam, GWL_ID) == IDC_TREE_DISCO)
		{
			HWND hwndList = (HWND)wParam;
			POINT pt = { (signed short)LOWORD( lParam ), (signed short)HIWORD( lParam ) };

			if (( pt.x == -1 ) && ( pt.y == -1 )) {
				LVITEM lvi = {0};
				lvi.iItem = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED);
				if (lvi.iItem < 0) return FALSE;

				RECT rc;
				ListView_GetItemRect(hwndList, lvi.iItem, &rc, LVIR_LABEL);
				pt.x = rc.left;
				pt.y = rc.bottom;
				ClientToScreen(hwndList, &pt);
			}

			HTREELISTITEM hItem = TreeList_GetActiveItem(hwndList);
			if (!hItem) break;
			CJabberSDNode *pNode = (CJabberSDNode *)TreeList_GetData(hItem);
			if (!pNode) break;

			m_proto->ServiceDiscoveryShowMenu(pNode, hItem, pt);
		}
		break;

	case WM_NOTIFY:
		if ( wParam == IDC_TREE_DISCO ) {
			NMHDR* pHeader = (NMHDR* )lParam;
			if ( pHeader->code == LVN_GETINFOTIP ) {
				NMLVGETINFOTIP *pInfoTip = (NMLVGETINFOTIP *)lParam;
				LVITEM lvi;
				lvi.mask = LVIF_PARAM;
				lvi.iItem = pInfoTip->iItem;
				ListView_GetItem(pHeader->hwndFrom, &lvi);
				HTREELISTITEM hItem = (HTREELISTITEM)lvi.lParam;
				m_proto->m_SDManager.Lock();
				CJabberSDNode* pNode = (CJabberSDNode* )TreeList_GetData(hItem);
				if ( pNode ) {
					pNode->GetTooltipText( pInfoTip->pszText, pInfoTip->cchTextMax );
				}
				m_proto->m_SDManager.Unlock();
			}
			else if ( pHeader->code == TVN_ITEMEXPANDED ) {
				NMTREEVIEW *pNmTreeView = (NMTREEVIEW *)lParam;
				HTREELISTITEM hItem = (HTREELISTITEM)pNmTreeView->itemNew.hItem;

				m_proto->m_SDManager.Lock();
				XmlNode packet( NULL );
				CJabberSDNode* pNode;
				pNode = (CJabberSDNode* )TreeList_GetData(hItem);
				if ( pNode ) 
				{
					m_proto->SendBothRequests( pNode, packet );
					TreeList_MakeFakeParent(hItem, FALSE);
				}
				m_proto->m_SDManager.Unlock();

				if ( xmlGetChild( packet ))
					m_proto->m_ThreadInfo->send( packet );
			}
			else if ( pHeader->code == NM_CUSTOMDRAW ) {
				LPNMLVCUSTOMDRAW lpnmlvcd = (LPNMLVCUSTOMDRAW)lParam;
				if (lpnmlvcd->nmcd.dwDrawStage != CDDS_PREPAINT)
					return CDRF_DODEFAULT;

				KillTimer(m_hwnd, AUTODISCO_TIMER);
				if (GetTickCount() - sttLastAutoDisco < AUTODISCO_TIMEOUT) {
					SetTimer(m_hwnd, AUTODISCO_TIMER, AUTODISCO_TIMEOUT, NULL);
					return CDRF_DODEFAULT;
				}

				SendMessage(m_hwnd, WM_TIMER, AUTODISCO_TIMER, 0);

				return CDRF_DODEFAULT;
			}
			return TRUE;
		}

		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
			case IDOK:
			{
				HWND hwndFocus = GetFocus();
				if (!hwndFocus) return TRUE;
				if (GetWindowLong(hwndFocus, GWL_ID) == IDC_TXT_FILTERTEXT)
					PostMessage( m_hwnd, WM_COMMAND, MAKEWPARAM( IDC_BTN_FILTERAPPLY, 0 ), 0 );
				else if (m_hwnd == (hwndFocus = GetParent(hwndFocus)))
					break;
				else if ((GetWindowLong(hwndFocus, GWL_ID) == IDC_COMBO_NODE) || (GetWindowLong(hwndFocus, GWL_ID) == IDC_COMBO_JID))
					PostMessage( m_hwnd, WM_COMMAND, MAKEWPARAM( IDC_BUTTON_BROWSE, 0 ), 0 );
				return TRUE;
			}
			case IDCANCEL:
			{
				PostMessage(m_hwnd, WM_CLOSE, 0, 0);
				return TRUE;
			}
		}
		break;

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

	}

	return CSuper::DlgProc(msg, wParam, lParam);
}

// extern references to used functions:
void SearchAddToRecent( TCHAR* szAddr, HWND hwndDialog = NULL );

void CJabberProto::ServiceDiscoveryShowMenu(CJabberSDNode *pNode, HTREELISTITEM hItem, POINT pt)
{
	//ClientToScreen(GetDlgItem(hwndServiceDiscovery, IDC_TREE_DISCO), &pt);

	enum { // This values are below CLISTMENUIDMAX and won't overlap
		SD_ACT_REFRESH = 1, SD_ACT_REFRESHCHILDREN, SD_ACT_FAVORITE,
		SD_ACT_ROSTER, SD_ACT_COPYJID, SD_ACT_COPYNODE, SD_ACT_USERMENU,
		SD_ACT_COPYINFO,

		SD_ACT_LOGON = 100, SD_ACT_LOGOFF, SD_ACT_UNREGISTER,

		SD_ACT_REGISTER = 200, SD_ACT_ADHOC, SD_ACT_ADDDIRECTORY,
		SD_ACT_JOIN, SD_ACT_BOOKMARK, SD_ACT_PROXY, SD_ACT_VCARD
	};

	enum {
		SD_FLG_NONODE			= 0x001,
		SD_FLG_NOTONROSTER		= 0x002,
		SD_FLG_ONROSTER			= 0x004,
		SD_FLG_SUBSCRIBED		= 0x008,
		SD_FLG_NOTSUBSCRIBED	= 0x010,
		SD_FLG_ONLINE			= 0x020,
		SD_FLG_NOTONLINE		= 0x040,
		SD_FLG_NORESOURCE		= 0x080,
		SD_FLG_HASUSER			= 0x100
	};

	static struct
	{
		TCHAR *feature;
		TCHAR *title;
		int action;
		DWORD flags;
	} items[] =
	{
		{NULL,							_T("Contact Menu..."),		SD_ACT_USERMENU,			SD_FLG_NONODE},
		{NULL,							_T("View vCard"),			SD_ACT_VCARD,				SD_FLG_NONODE},
		{_T(JABBER_FEAT_MUC),			_T("Join chatroom"),		SD_ACT_JOIN,				SD_FLG_NORESOURCE},
		{0},
		{NULL,							_T("Refresh Info"),			SD_ACT_REFRESH},
		{NULL,							_T("Refresh Children"),		SD_ACT_REFRESHCHILDREN},
		{0},
		{NULL,							_T("Add to favorites"),		SD_ACT_FAVORITE},
		{NULL,							_T("Add to roster"),		SD_ACT_ROSTER,				SD_FLG_NONODE|SD_FLG_NOTONROSTER},
		{_T(JABBER_FEAT_MUC),			_T("Bookmark chatroom"),	SD_ACT_BOOKMARK,			SD_FLG_NORESOURCE|SD_FLG_HASUSER},
		{_T("jabber:iq:search"),		_T("Add search directory"),	SD_ACT_ADDDIRECTORY},
		{_T(JABBER_FEAT_BYTESTREAMS),	_T("Use this proxy"),		SD_ACT_PROXY},
		{0},
		{_T(JABBER_FEAT_REGISTER),		_T("Register"),				SD_ACT_REGISTER},
		{_T("jabber:iq:gateway"),		_T("Unregister"),			SD_ACT_UNREGISTER,			SD_FLG_ONROSTER|SD_FLG_SUBSCRIBED},
		{_T(JABBER_FEAT_COMMANDS),		_T("Commands..."),			SD_ACT_ADHOC},
		{0},
		{_T("jabber:iq:gateway"),		_T("Logon"),				SD_ACT_LOGON,				SD_FLG_ONROSTER|SD_FLG_SUBSCRIBED|SD_FLG_ONLINE},
		{_T("jabber:iq:gateway"),		_T("Logoff"),				SD_ACT_LOGOFF,				SD_FLG_ONROSTER|SD_FLG_SUBSCRIBED|SD_FLG_NOTONLINE},
		{0},
		{NULL,							_T("Copy JID"),				SD_ACT_COPYJID},
		{NULL,							_T("Copy node name"),		SD_ACT_COPYNODE},
		{NULL,							_T("Copy node information"),SD_ACT_COPYINFO},
	};

	HMENU hMenu = CreatePopupMenu();
	BOOL lastSeparator = TRUE;
	bool bFilterItems = !GetAsyncKeyState(VK_CONTROL);
	for (int i = 0; i < SIZEOF(items); ++i)
	{
		JABBER_LIST_ITEM *rosterItem = NULL;

		if (bFilterItems)
		{
			if ((items[i].flags&SD_FLG_NONODE) && pNode->GetNode())
				continue;
			if ((items[i].flags&SD_FLG_NOTONROSTER) && (rosterItem = ListGetItemPtr(LIST_ROSTER, pNode->GetJid())))
				continue;
			if ((items[i].flags&SD_FLG_ONROSTER) && !(rosterItem = ListGetItemPtr(LIST_ROSTER, pNode->GetJid())))
				continue;
			if ((items[i].flags&SD_FLG_SUBSCRIBED) && (!rosterItem || (rosterItem->subscription == SUB_NONE)))
				continue;
			if ((items[i].flags&SD_FLG_NOTSUBSCRIBED) && (rosterItem && (rosterItem->subscription != SUB_NONE)))
				continue;
			if ((items[i].flags&SD_FLG_ONLINE) && rosterItem && (rosterItem->itemResource.status != ID_STATUS_OFFLINE))
				continue;
			if ((items[i].flags&SD_FLG_NOTONLINE) && rosterItem && (rosterItem->itemResource.status == ID_STATUS_OFFLINE))
				continue;
			if ((items[i].flags&SD_FLG_NORESOURCE) && _tcschr(pNode->GetJid(), _T('/')))
				continue;
			if ((items[i].flags&SD_FLG_HASUSER) && !_tcschr(pNode->GetJid(), _T('@')))
				continue;
		}

		if (!items[i].feature)
		{
			if (items[i].title)
			{
				HANDLE hContact;
				if ((items[i].action == SD_ACT_USERMENU) && (hContact = HContactFromJID(pNode->GetJid()))) {
					HMENU hContactMenu = (HMENU)CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM)hContact, 0);
					AppendMenu(hMenu, MF_STRING|MF_POPUP, (UINT_PTR)hContactMenu, TranslateTS(items[i].title));
				} else
					AppendMenu(hMenu, MF_STRING, items[i].action, TranslateTS(items[i].title));
				lastSeparator = FALSE;
			} else
			if (!lastSeparator)
			{
				AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
				lastSeparator = TRUE;
			}
			continue;
		}

		bool bFeatureOk = !bFilterItems;
		if (bFilterItems)
			for (CJabberSDFeature *iFeature = pNode->GetFirstFeature(); iFeature; iFeature = iFeature->GetNext())
				if (!lstrcmp(iFeature->GetVar(), items[i].feature))
				{
					bFeatureOk = true;
					break;
				}

		if (bFeatureOk)
		{
			if (items[i].title)
			{
				AppendMenu(hMenu, MF_STRING, items[i].action, TranslateTS(items[i].title));
				lastSeparator = FALSE;
			} else
			if (!lastSeparator)
			{
				AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
				lastSeparator = TRUE;
			}
		}
	}

	if (!GetMenuItemCount(hMenu))
	{
		DestroyMenu(hMenu);
		return;
	}

	int res = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_pDlgServiceDiscovery->GetHwnd(), NULL);
	DestroyMenu(hMenu);

	switch (res)
	{
		case SD_ACT_REFRESH:
		{
			m_SDManager.Lock();
			XmlNode packet( NULL );
			if ( pNode ) 
			{
				TreeList_ResetItem(GetDlgItem(m_pDlgServiceDiscovery->GetHwnd(), IDC_TREE_DISCO), hItem);
				pNode->ResetInfo();
				SendBothRequests( pNode, packet );
				TreeList_MakeFakeParent(hItem, FALSE);
			}
			m_SDManager.Unlock();

			if ( xmlGetChild( packet ))
				m_ThreadInfo->send( packet );
			break;
		}

		case SD_ACT_REFRESHCHILDREN:
		{
			m_SDManager.Lock();
			XmlNode packet( NULL );
			for (int iChild = TreeList_GetChildrenCount(hItem); iChild--; ) {
				HTREELISTITEM hNode = TreeList_GetChild(hItem, iChild);
				CJabberSDNode *pNode = (CJabberSDNode *)TreeList_GetData(hNode);
				if ( pNode )
				{
					TreeList_ResetItem(GetDlgItem(m_pDlgServiceDiscovery->GetHwnd(), IDC_TREE_DISCO), hNode);
					pNode->ResetInfo();
					SendBothRequests( pNode, packet );
					TreeList_MakeFakeParent(hNode, FALSE);
				}

				if ( xmlGetChildCount( packet ) > 50 ) {
					m_ThreadInfo->send( packet );
					packet = XmlNode( NULL );
			}	}
			m_SDManager.Unlock();

			if ( xmlGetChildCount( packet ))
				m_ThreadInfo->send( packet );
			break;
		}

		case SD_ACT_COPYJID:
			JabberCopyText(m_pDlgServiceDiscovery->GetHwnd(), pNode->GetJid());
			break;

		case SD_ACT_COPYNODE:
			JabberCopyText(m_pDlgServiceDiscovery->GetHwnd(), pNode->GetNode());
			break;

		case SD_ACT_COPYINFO:
		{
			TCHAR buf[8192];
			pNode->GetTooltipText(buf, SIZEOF(buf));
			JabberCopyText(m_pDlgServiceDiscovery->GetHwnd(), buf);
			break;
		}

		case SD_ACT_FAVORITE:
		{
			char setting[MAXMODULELABELLENGTH];
			int count = JGetDword(NULL, "discoWnd_favCount", 0);
			mir_snprintf(setting, sizeof(setting), "discoWnd_favName_%d", count);
			JSetStringT(NULL, setting, pNode->GetName() ? pNode->GetName() : pNode->GetJid());
			mir_snprintf(setting, sizeof(setting), "discoWnd_favJID_%d", count);
			JSetStringT(NULL, setting, pNode->GetJid());
			mir_snprintf(setting, sizeof(setting), "discoWnd_favNode_%d", count);
			JSetStringT(NULL, setting, pNode->GetNode() ? pNode->GetNode() : _T(""));
			JSetDword(NULL, "discoWnd_favCount", ++count);
			break;
		}

		case SD_ACT_REGISTER:
			RegisterAgent(m_pDlgServiceDiscovery->GetHwnd(), pNode->GetJid());
			break;

		case SD_ACT_ADHOC:
			ContactMenuAdhocCommands( new CJabberAdhocStartupParams( this, pNode->GetJid(), pNode->GetNode()));
			break;

		case SD_ACT_ADDDIRECTORY:
			SearchAddToRecent(pNode->GetJid());
			break;

		case SD_ACT_PROXY:
			m_options.BsDirect = FALSE;
			m_options.BsProxyManual = TRUE;
			JSetStringT( NULL, "BsProxyServer", pNode->GetJid());
			break;

		case SD_ACT_JOIN:
			if ( jabberChatDllPresent )
				GroupchatJoinRoomByJid(m_pDlgServiceDiscovery->GetHwnd(), pNode->GetJid());
			else
				JabberChatDllError();
			break;

		case SD_ACT_BOOKMARK:
		{
			JABBER_LIST_ITEM* item = ListGetItemPtr( LIST_BOOKMARK, pNode->GetJid() );
			if ( item == NULL ) {
				item = ListGetItemPtr( LIST_BOOKMARK, pNode->GetJid() );
				if ( item == NULL ) {
					item = ListAdd( LIST_ROOM, pNode->GetJid() );
					item->name = mir_tstrdup( pNode->GetName() );
				}
				if ( item != NULL ) {
					item->type = _T("conference");
					AddEditBookmark( item );
			}	}
			break;
		}

		case SD_ACT_USERMENU:
		{
			HANDLE hContact = HContactFromJID( pNode->GetJid() );
			if ( !hContact ) {
				hContact = DBCreateContact( pNode->GetJid(), pNode->GetName(), TRUE, FALSE );
				JABBER_LIST_ITEM* item = ListAdd( LIST_VCARD_TEMP, pNode->GetJid() );
				item->bUseResource = TRUE;
			}
			HMENU hContactMenu = (HMENU)CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM)hContact, 0);
			GetCursorPos(&pt);
			int res = TrackPopupMenu(hContactMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_pDlgServiceDiscovery->GetHwnd(), NULL);
			CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(res, MPCF_CONTACTMENU), (LPARAM)hContact);
			break;
		}

		case SD_ACT_VCARD:
		{
			TCHAR * jid = pNode->GetJid();
			HANDLE hContact = HContactFromJID(pNode->GetJid());
			if ( !hContact ) {	
				JABBER_SEARCH_RESULT jsr={0};
				mir_sntprintf( jsr.jid, SIZEOF(jsr.jid), _T("%s"), jid );
				jsr.hdr.cbSize = sizeof( JABBER_SEARCH_RESULT );
				hContact = ( HANDLE )CallProtoService( m_szModuleName, PS_ADDTOLIST, PALF_TEMPORARY, ( LPARAM )&jsr );
			}
			if ( ListGetItemPtr( LIST_VCARD_TEMP, pNode->GetJid()) == NULL ) {
				JABBER_LIST_ITEM* item = ListAdd( LIST_VCARD_TEMP, pNode->GetJid() );
				item->bUseResource = TRUE;
				if ( item->resource == NULL )
					ListAddResource( LIST_VCARD_TEMP, jid, ID_STATUS_OFFLINE, NULL, 0);
			}
			CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)hContact, 0);
			break;
		}

		case SD_ACT_ROSTER:
		{
			HANDLE hContact = DBCreateContact(pNode->GetJid(), pNode->GetName(), FALSE, FALSE);
			DBDeleteContactSetting( hContact, "CList", "NotOnList" );
			JABBER_LIST_ITEM* item = ListAdd( LIST_VCARD_TEMP, pNode->GetJid() );
			item->bUseResource = TRUE;
			break;
		}

		case SD_ACT_LOGON:
		case SD_ACT_LOGOFF:
			m_ThreadInfo->send( XmlNode( _T("presence")) << XATTR( _T("to"), pNode->GetJid()) << XATTR( _T("type"), ( res != SD_ACT_LOGON ) ? _T("unavailable") : NULL ));
			break;

		case SD_ACT_UNREGISTER:
			m_ThreadInfo->send( XmlNodeIq( _T("set"), SerialNext(), pNode->GetJid() ) << XQUERY( _T(JABBER_FEAT_REGISTER)) << XCHILD( _T("remove")));
			
			m_ThreadInfo->send( XmlNodeIq( _T("set"), SerialNext()) << XQUERY( _T(JABBER_FEAT_IQ_ROSTER)) 
				<< XCHILD( _T("item")) << XATTR( _T("jid"), pNode->GetJid()) << XATTR( _T("subscription"), _T("remove")));
			break;

		default:
			if ((res >= CLISTMENUIDMIN) && (res <= CLISTMENUIDMAX)) {
				HANDLE hContact = HContactFromJID(pNode->GetJid());
				if (hContact)
					CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(res, MPCF_CONTACTMENU), (LPARAM)hContact);
			}
			break;
	}
}

void CJabberProto::LaunchServiceDiscovery(TCHAR *jid)
{
	if ( m_pDlgServiceDiscovery ) {
		SetForegroundWindow( m_pDlgServiceDiscovery->GetHwnd() );
		if (jid) {
			SetDlgItemText( m_pDlgServiceDiscovery->GetHwnd(), IDC_COMBO_JID, jid);
			SetDlgItemTextA( m_pDlgServiceDiscovery->GetHwnd(), IDC_COMBO_NODE, "");
			PostMessage( m_pDlgServiceDiscovery->GetHwnd(), WM_COMMAND, MAKEWPARAM( IDC_BUTTON_BROWSE, 0 ), 0 );
		}
	} else {
		m_pDlgServiceDiscovery = new CJabberDlgDiscovery(this, jid);
		m_pDlgServiceDiscovery->Show();
	}
}

INT_PTR __cdecl CJabberProto::OnMenuHandleServiceDiscovery( WPARAM, LPARAM )
{
	LaunchServiceDiscovery(NULL);
	return 0;
}

INT_PTR __cdecl CJabberProto::OnMenuHandleServiceDiscoveryMyTransports( WPARAM, LPARAM )
{
	LaunchServiceDiscovery(_T(SD_FAKEJID_MYAGENTS));
	return 0;
}

INT_PTR __cdecl CJabberProto::OnMenuHandleServiceDiscoveryTransports( WPARAM, LPARAM )
{
	LaunchServiceDiscovery(_T(SD_FAKEJID_AGENTS));
	return 0;
}

INT_PTR __cdecl CJabberProto::OnMenuHandleServiceDiscoveryConferences( WPARAM, LPARAM )
{
	LaunchServiceDiscovery(_T(SD_FAKEJID_CONFERENCES));
	return 0;
}
