/*

Jabber Protocol Plugin for Miranda IM
Copyright (C) 2002-04  Santithorn Bunchua
Copyright (C) 2005-12  George Hazan
Copyright (C) 2007     Maxim Mluhov
Copyright (C) 2012-13  Miranda NG Project

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_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);
					db_free(&dbv);
					db_free(&dbvJid);
					db_free(&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,
		db_get_b(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 = db_get_w(NULL, m_proto->m_szModuleName, "discoWnd_cx0", 200);
	lvc.iSubItem = 0;
	lvc.pszText = TranslateT("Node hierarchy");
	ListView_InsertColumn(hwndList, 0, &lvc);
	lvc.cx = db_get_w(NULL, m_proto->m_szModuleName, "discoWnd_cx1", 200);
	lvc.iSubItem = 1;
	lvc.pszText = _T("JID");
	ListView_InsertColumn(hwndList, 1, &lvc);
	lvc.cx = db_get_w(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, db_get_b(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()
{
	db_set_b(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);
	db_set_w(NULL, m_proto->m_szModuleName, "discoWnd_cx0", lvc.cx);
	ListView_GetColumn(hwndList, 1, &lvc);
	db_set_w(NULL, m_proto->m_szModuleName, "discoWnd_cx1", lvc.cx);
	ListView_GetColumn(hwndList, 2, &lvc);
	db_set_w(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);
		}
		db_free(&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);
			db_free(&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);
			db_free(&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 (GetWindowLongPtr((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 (GetWindowLongPtr(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 ((GetWindowLongPtr(hwndFocus, GWL_ID) == IDC_COMBO_NODE) || (GetWindowLongPtr(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);
			db_unset(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;
}