diff options
Diffstat (limited to 'plugins/FavContacts/src')
| -rw-r--r-- | plugins/FavContacts/src/contact_cache.cpp | 231 | ||||
| -rw-r--r-- | plugins/FavContacts/src/contact_cache.h | 61 | ||||
| -rw-r--r-- | plugins/FavContacts/src/cserver.cpp | 82 | ||||
| -rw-r--r-- | plugins/FavContacts/src/cserver.h | 42 | ||||
| -rw-r--r-- | plugins/FavContacts/src/csocket.cpp | 22 | ||||
| -rw-r--r-- | plugins/FavContacts/src/csocket.h | 16 | ||||
| -rw-r--r-- | plugins/FavContacts/src/favlist.cpp | 0 | ||||
| -rw-r--r-- | plugins/FavContacts/src/favlist.h | 134 | ||||
| -rw-r--r-- | plugins/FavContacts/src/headers.h | 94 | ||||
| -rw-r--r-- | plugins/FavContacts/src/http_api.cpp | 165 | ||||
| -rw-r--r-- | plugins/FavContacts/src/http_api.h | 7 | ||||
| -rw-r--r-- | plugins/FavContacts/src/main.cpp | 1453 | 
12 files changed, 2307 insertions, 0 deletions
diff --git a/plugins/FavContacts/src/contact_cache.cpp b/plugins/FavContacts/src/contact_cache.cpp new file mode 100644 index 0000000000..c61635cc26 --- /dev/null +++ b/plugins/FavContacts/src/contact_cache.cpp @@ -0,0 +1,231 @@ +#include "headers.h"
 +#include <time.h>
 +#include <math.h>
 +
 +int __cdecl CContactCache::OnDbEventAdded(WPARAM wParam, LPARAM lParam)
 +{
 +	HANDLE hContact = (HANDLE)wParam;
 +	HANDLE hEvent = (HANDLE)lParam;
 +
 +	DBEVENTINFO dbei = {0};
 +	dbei.cbSize = sizeof(dbei);
 +	CallService(MS_DB_EVENT_GET, (WPARAM)hEvent, (LPARAM)&dbei);
 +	if (dbei.eventType != EVENTTYPE_MESSAGE) return 0;
 +
 +	float weight = GetEventWeight(time(NULL) - dbei.timestamp);
 +	float q = GetTimeWeight(time(NULL) - m_lastUpdate);
 +	m_lastUpdate = time(NULL);
 +	if (!weight) return 0;
 +
 +	Lock();
 +	bool found = false;
 +	for (int i = 0; i < m_cache.getCount(); ++i)
 +	{
 +		m_cache[i].rate *= q;
 +		if (m_cache[i].hContact == hContact)
 +		{
 +			found = true;
 +			m_cache[i].rate += weight;
 +		}
 +	}
 +
 +	if (!found)
 +	{
 +		TContactInfo *info = new TContactInfo;
 +		info->hContact = hContact;
 +		info->rate = weight;
 +		m_cache.insert(info);
 +	} else
 +	{
 +		qsort(m_cache.getArray(), m_cache.getCount(), sizeof(TContactInfo *), TContactInfo::cmp2);
 +	}
 +
 +	Unlock();
 +	return 0;
 +}
 +
 +float CContactCache::GetEventWeight(unsigned long age)
 +{
 +	const float ceil = 1000.f;
 +	const float floor = 0.0001f;
 +	const int depth = 60 * 60 * 24 * 30;
 +	if (age > depth) return 0;
 +	return exp(log(ceil) - age * (log(ceil) - log(floor)) / depth);
 +}
 +
 +float CContactCache::GetTimeWeight(unsigned long age)
 +{
 +	const float ceil = 1000.f;
 +	const float floor = 0.0001f;
 +	const int depth = 60 * 60 * 24 * 30;
 +	if (age > depth) return 0;
 +	return exp(age * (log(ceil) - log(floor)) / depth);
 +}
 +
 +CContactCache::CContactCache(): m_cache(50, TContactInfo::cmp)
 +{
 +	InitializeCriticalSection(&m_cs);
 +
 +	int (__cdecl CContactCache::*pfn)(WPARAM, LPARAM);
 +	pfn = &CContactCache::OnDbEventAdded;
 +	m_hOnDbEventAdded = HookEventObj(ME_DB_EVENT_ADDED, *(MIRANDAHOOKOBJ *)&pfn, this);
 +
 +	Rebuild();
 +}
 +
 +CContactCache::~CContactCache()
 +{
 +	UnhookEvent(m_hOnDbEventAdded);
 +	DeleteCriticalSection(&m_cs);
 +}
 +
 +void CContactCache::Rebuild()
 +{
 +	unsigned long timestamp = time(NULL);
 +	m_lastUpdate = time(NULL);
 +
 +	HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
 +	while (hContact)
 +	{
 +		TContactInfo *info = new TContactInfo;
 +		info->hContact = hContact;
 +		info->rate = 0;
 +
 +		HANDLE hEvent = (HANDLE)CallService(MS_DB_EVENT_FINDLAST, (WPARAM)hContact, 0);
 +		while (hEvent)
 +		{
 +			DBEVENTINFO dbei = {0};
 +			dbei.cbSize = sizeof(dbei);
 +			if (!CallService(MS_DB_EVENT_GET, (WPARAM)hEvent, (LPARAM)&dbei))
 +			{
 +				if (float weight = GetEventWeight(timestamp - dbei.timestamp))
 +				{
 +					if (dbei.eventType == EVENTTYPE_MESSAGE)
 +						info->rate += weight;
 +				} else
 +				{
 +					break;
 +				}
 +			}
 +			hEvent = (HANDLE)CallService(MS_DB_EVENT_FINDPREV, (WPARAM)hEvent, 0);
 +		}
 +
 +		m_cache.insert(info);
 +		hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0);
 +	}
 +}
 +
 +HANDLE CContactCache::get(int rate)
 +{
 +	if (rate >= 0 && rate < m_cache.getCount())
 +		return m_cache[rate].hContact;
 +	return NULL;
 +}
 +
 +float CContactCache::getWeight(int rate)
 +{
 +	if (rate >= 0 && rate < m_cache.getCount())
 +		return m_cache[rate].rate;
 +	return -1;
 +}
 +
 +static bool AppendInfo(TCHAR *buf, int size, HANDLE hContact, int info)
 +{
 +	CONTACTINFO ci = {0};
 +	ci.cbSize = sizeof(ci);
 +	ci.hContact = hContact;
 +	ci.dwFlag = info;
 +#if defined(UNICODE) || defined(_UNICODE)
 +	ci.dwFlag |= CNF_UNICODE;
 +#endif
 +
 +	bool ret = false;
 +
 +	if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM)&ci) && (ci.type == CNFT_ASCIIZ) && ci.pszVal)
 +	{
 +		if (*ci.pszVal && (lstrlen(ci.pszVal) < size-2))
 +		{
 +			lstrcpy(buf, ci.pszVal);
 +			ret = true;
 +		}
 +		mir_free(ci.pszVal);
 +	}
 +
 +	return ret;
 +}
 +
 +void CContactCache::TContactInfo::LoadInfo()
 +{
 +	if (infoLoaded) return;
 +	TCHAR *p = info;
 +
 +	p[0] = p[1] = 0;
 +
 +	static const int items[] = {
 +		CNF_FIRSTNAME, CNF_LASTNAME, CNF_NICK , CNF_CUSTOMNICK, CNF_EMAIL, CNF_CITY, CNF_STATE,
 +		CNF_COUNTRY, CNF_PHONE, CNF_HOMEPAGE, CNF_ABOUT, CNF_UNIQUEID, CNF_MYNOTES, CNF_STREET,
 +		CNF_CONAME, CNF_CODEPT, CNF_COCITY, CNF_COSTATE, CNF_COSTREET, CNF_COCOUNTRY
 +	};
 +
 +	for (int i = 0; i < SIZEOF(items); ++i)
 +	{
 +		if (AppendInfo(p, SIZEOF(info) - (p - info), hContact, items[i]))
 +			p += lstrlen(p) + 1;
 +	}
 +	*p = 0;
 +
 +	infoLoaded = true;
 +}
 +
 +TCHAR *nb_stristr(TCHAR *str, TCHAR *substr)
 +{
 +	if (!substr || !*substr) return str;
 +	if (!str || !*str) return NULL;
 +
 +	TCHAR *str_up = NEWTSTR_ALLOCA(str);
 +	TCHAR *substr_up = NEWTSTR_ALLOCA(substr);
 +
 +	CharUpperBuff(str_up, lstrlen(str_up));
 +	CharUpperBuff(substr_up, lstrlen(substr_up));
 +
 +	TCHAR* p = _tcsstr(str_up, substr_up);
 +	return p ? (str + (p - str_up)) : NULL;
 +}
 +
 +bool CContactCache::filter(int rate, TCHAR *str)
 +{
 +	if (!str || !*str)
 +		return true;
 +	m_cache[rate].LoadInfo();
 +
 +	HKL kbdLayoutActive = GetKeyboardLayout(GetCurrentThreadId());
 +	HKL kbdLayouts[10];
 +	int nKbdLayouts = GetKeyboardLayoutList(SIZEOF(kbdLayouts), kbdLayouts);
 +
 +	TCHAR buf[256];
 +	BYTE keyState[256] = {0};
 +
 +	for (int iLayout = 0; iLayout < nKbdLayouts; ++iLayout)
 +	{
 +		if (kbdLayoutActive == kbdLayouts[iLayout])
 +		{
 +			lstrcpy(buf, str);
 +		} else
 +		{
 +			int i;
 +			for (i = 0; str[i]; ++i)
 +			{
 +				UINT vk = VkKeyScanEx(str[i], kbdLayoutActive);
 +				UINT scan = MapVirtualKeyEx(vk, 0, kbdLayoutActive);
 +				ToUnicodeEx(vk, scan, keyState, buf+i, SIZEOF(buf)-i, 0, kbdLayouts[iLayout]);
 +			}
 +			buf[i] = 0;
 +		}
 +
 +		for (TCHAR *p = m_cache[rate].info; p && *p; p = p + lstrlen(p) + 1)
 +			if (nb_stristr(p, buf))
 +				return true;
 +	}
 +
 +	return false;
 +}
 diff --git a/plugins/FavContacts/src/contact_cache.h b/plugins/FavContacts/src/contact_cache.h new file mode 100644 index 0000000000..905ba4b469 --- /dev/null +++ b/plugins/FavContacts/src/contact_cache.h @@ -0,0 +1,61 @@ +#ifndef __contact_cache__
 +#define __contact_cache__
 +
 +class CContactCache
 +{
 +public:
 +	enum { INFOSIZE = 1024 };
 +
 +private:
 +
 +	struct TContactInfo
 +	{
 +		HANDLE hContact;
 +		float rate;
 +		TCHAR info[INFOSIZE];
 +		bool infoLoaded;
 +
 +		static int cmp(const TContactInfo *p1, const TContactInfo *p2)
 +		{
 +			if (p1->rate > p2->rate) return -1;
 +			if (p1->rate < p2->rate) return 1;
 +			return 0;
 +		}
 +
 +		static int cmp2(const void *a1, const void *a2)
 +		{
 +			return cmp(*(const TContactInfo **)a1, *(const TContactInfo **)a2);
 +		}
 +
 +		TContactInfo()
 +		{
 +			info[0] = info[1] = 0;
 +			infoLoaded = false;
 +		}
 +
 +		void LoadInfo();
 +	};
 +
 +	OBJLIST<TContactInfo> m_cache;
 +	unsigned long m_lastUpdate;
 +	CRITICAL_SECTION m_cs;
 +	HANDLE m_hOnDbEventAdded;
 +
 +	int __cdecl OnDbEventAdded(WPARAM wParam, LPARAM lParam);
 +	float GetEventWeight(unsigned long age);
 +	float GetTimeWeight(unsigned long age);
 +
 +public:
 +	CContactCache();
 +	~CContactCache();
 +
 +	void Lock() { EnterCriticalSection(&m_cs); }
 +	void Unlock() { LeaveCriticalSection(&m_cs); }
 +	void Rebuild();
 +
 +	HANDLE get(int rate);
 +	float getWeight(int rate);
 +	bool filter(int rate, TCHAR *str);
 +};
 +
 +#endif // __contact_cache__
 diff --git a/plugins/FavContacts/src/cserver.cpp b/plugins/FavContacts/src/cserver.cpp new file mode 100644 index 0000000000..957d1a6048 --- /dev/null +++ b/plugins/FavContacts/src/cserver.cpp @@ -0,0 +1,82 @@ +#include <winsock2.h>
 +#include <windows.h>
 +#include <ws2tcpip.h>
 +
 +#include "csocket.h"
 +#include "cserver.h"
 +
 +void CServer::Start(int port, IConnectionProcessorFactory *connectionProcessorFactory, bool background)
 +{
 +	m_socket = socket(AF_INET, SOCK_STREAM, 0);
 +	if (m_socket == INVALID_SOCKET) return;
 +
 +    sockaddr_in addr = {0};
 +	addr.sin_family = AF_INET;
 +	addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
 +	addr.sin_port = htons((WORD)port);
 +	if (bind(m_socket, (sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
 +	{
 +		closesocket(m_socket);
 +		m_socket = INVALID_SOCKET;
 +		return;
 +	}
 +
 +	listen(m_socket, SOMAXCONN);
 +
 +	m_connectionProcessorFactory = connectionProcessorFactory;
 +
 +	if (background)
 +	{
 +		CreateThread(NULL, 0,
 +			GlobalConnectionAcceptThread,
 +			this,
 +			0, NULL);
 +	} else
 +	{
 +		ConnectionAcceptThread();
 +	}
 +}
 +
 +void CServer::Stop()
 +{
 +	shutdown(m_socket, SD_BOTH);
 +	closesocket(m_socket);
 +}
 +
 +DWORD CServer::ConnectionAcceptThread()
 +{
 +	while (1)
 +	{
 +		SOCKET s = accept(m_socket, NULL, NULL);
 +		if (s == INVALID_SOCKET) break;
 +
 +		CreateThread(NULL, 0,
 +			GlobalConnectionProcessThread,
 +			new GlobalConnectionProcessThreadArgs(this, s),
 +			0, NULL);
 +	}
 +	return 0;
 +}
 +
 +DWORD CServer::ConnectionProcessThread(SOCKET s)
 +{
 +	CSocket sock(s);
 +	IConnectionProcessor *processor = m_connectionProcessorFactory->Create(&sock);
 +	processor->ProcessConnection();
 +	delete processor;
 +	return 0;
 +}
 +
 +DWORD WINAPI CServer::GlobalConnectionAcceptThread(void *arg)
 +{
 +	CServer *server = (CServer *)arg;
 +	return server->ConnectionAcceptThread();
 +}
 +
 +DWORD WINAPI CServer::GlobalConnectionProcessThread(void *arg)
 +{
 +	GlobalConnectionProcessThreadArgs *args = (GlobalConnectionProcessThreadArgs *)arg;
 +	DWORD result = args->m_server->ConnectionProcessThread(args->m_socket);
 +	delete args;
 +	return result;
 +}
 diff --git a/plugins/FavContacts/src/cserver.h b/plugins/FavContacts/src/cserver.h new file mode 100644 index 0000000000..007a341896 --- /dev/null +++ b/plugins/FavContacts/src/cserver.h @@ -0,0 +1,42 @@ +#ifndef cserver_h__
 +#define cserver_h__
 +
 +class IConnectionProcessor
 +{
 +public:
 +	virtual ~IConnectionProcessor() {}
 +	virtual void ProcessConnection() = 0;
 +};
 +
 +class IConnectionProcessorFactory
 +{
 +public:
 +	virtual IConnectionProcessor *Create(CSocket *s) = 0;
 +};
 +
 +class CServer
 +{
 +private:
 +	SOCKET m_socket;
 +	IConnectionProcessorFactory *m_connectionProcessorFactory;
 +
 +	DWORD ConnectionAcceptThread();
 +	DWORD ConnectionProcessThread(SOCKET s);
 +
 +	static DWORD WINAPI GlobalConnectionAcceptThread(void *arg);
 +
 +	struct GlobalConnectionProcessThreadArgs
 +	{
 +		CServer *m_server;
 +		SOCKET m_socket;
 +
 +		GlobalConnectionProcessThreadArgs(CServer *server, SOCKET s): m_server(server), m_socket(s) {}
 +	};
 +	static DWORD WINAPI GlobalConnectionProcessThread(void *arg);
 +
 +public:
 +	void Start(int port, IConnectionProcessorFactory *connectionProcessorFactory, bool background);
 +	void Stop();
 +};
 +
 +#endif // cserver_h__
 diff --git a/plugins/FavContacts/src/csocket.cpp b/plugins/FavContacts/src/csocket.cpp new file mode 100644 index 0000000000..dfff16fbf1 --- /dev/null +++ b/plugins/FavContacts/src/csocket.cpp @@ -0,0 +1,22 @@ +#include <winsock2.h>
 +#include <windows.h>
 +#include <ws2tcpip.h>
 +
 +#include "csocket.h"
 +
 +int CSocket::Recv(char *buf, int count)
 +{
 +	return recv(m_socket, buf, count, 0);
 +}
 +
 +int CSocket::Send(char *buf, int count)
 +{
 +	if (count < 0) count = strlen(buf);
 +	return send(m_socket, buf, count, 0);
 +}
 +
 +void CSocket::Close()
 +{
 +	shutdown(m_socket, SD_BOTH);
 +	closesocket(m_socket);
 +}
 diff --git a/plugins/FavContacts/src/csocket.h b/plugins/FavContacts/src/csocket.h new file mode 100644 index 0000000000..3fa1c87852 --- /dev/null +++ b/plugins/FavContacts/src/csocket.h @@ -0,0 +1,16 @@ +#ifndef csocket_h__
 +#define csocket_h__
 +
 +class CSocket
 +{
 +protected:
 +	SOCKET m_socket;
 +
 +public:
 +	CSocket(SOCKET socket = INVALID_SOCKET): m_socket(socket) {}
 +	int Recv(char *buf, int count);
 +	int Send(char *buf, int count = -1);
 +	void Close();
 +};
 +
 +#endif // csocket_h__
 diff --git a/plugins/FavContacts/src/favlist.cpp b/plugins/FavContacts/src/favlist.cpp new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/plugins/FavContacts/src/favlist.cpp diff --git a/plugins/FavContacts/src/favlist.h b/plugins/FavContacts/src/favlist.h new file mode 100644 index 0000000000..6201126b44 --- /dev/null +++ b/plugins/FavContacts/src/favlist.h @@ -0,0 +1,134 @@ +#ifndef favlist_h__
 +#define favlist_h__
 +
 +struct TContactInfo
 +{
 +private:
 +	HANDLE hContact;
 +	DWORD status;
 +	TCHAR *name;
 +	TCHAR *group;
 +	bool bManual;
 +	float fRate;
 +
 +public:
 +	TContactInfo(HANDLE hContact, bool bManual, float fRate = 0)
 +	{
 +		DBVARIANT dbv = {0};
 +
 +		this->hContact = hContact;
 +		this->bManual = bManual;
 +		this->fRate = fRate;
 +		name = mir_tstrdup((TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR));
 +		if (g_Options.bUseGroups && !DBGetContactSettingTString(hContact, "CList", "Group", &dbv))
 +		{
 +			group = mir_tstrdup(dbv.ptszVal);
 +			DBFreeVariant(&dbv);
 +		} else
 +		if (g_Options.bUseGroups)
 +		{
 +			group = mir_tstrdup(TranslateT("<no group>"));
 +		} else
 +		{
 +			group = mir_tstrdup(TranslateT("Favourite Contacts"));
 +		}
 +		status = DBGetContactSettingWord(hContact, (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0), "Status", ID_STATUS_OFFLINE);
 +	}
 +
 +	~TContactInfo()
 +	{
 +		mir_free(name);
 +		mir_free(group);
 +	}
 +
 +	HANDLE getHandle() const
 +	{
 +		return hContact;
 +	}
 +
 +	TCHAR *getGroup() const
 +	{
 +		return group;
 +	}
 +
 +	static int cmp(const TContactInfo *p1, const TContactInfo *p2)
 +	{
 +		if (p1->bManual && !p2->bManual) return -1;
 +		if (!p1->bManual && p2->bManual) return 1;
 +
 +		if (!p1->bManual)
 +		{
 +			if (p1->fRate > p2->fRate) return -1;
 +			if (p1->fRate < p2->fRate) return 1;
 +		}
 +
 +		int res = 0;
 +		if (res = lstrcmp(p1->group, p2->group)) return res;
 +		//if (p1->status < p2->status) return -1;
 +		//if (p1->status < p2->status) return +1;
 +		if (res = lstrcmp(p1->name, p2->name)) return res;
 +		return 0;
 +	}
 +};
 +
 +class TFavContacts: public LIST<TContactInfo>
 +{
 +private:
 +	int nGroups;
 +
 +public:
 +	TFavContacts(): LIST<TContactInfo>(5, TContactInfo::cmp) {}
 +	~TFavContacts()
 +	{
 +		for (int i = 0; i < this->getCount(); ++i)
 +			delete (*this)[i];
 +	}
 +
 +	int groupCount()
 +	{
 +		return nGroups;
 +	}
 +
 +	TContactInfo *addContact(HANDLE hContact, bool bManual)
 +	{
 +		TContactInfo *info = new TContactInfo(hContact, bManual);
 +		this->insert(info);
 +		return info;
 +	}
 +
 +	void build()
 +	{
 +		TCHAR *prevGroup = NULL;
 +		int i;
 +		
 +		nGroups = 1;
 +
 +		HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
 +		for ( ; hContact; hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
 +			if (DBGetContactSettingByte(hContact, "FavContacts", "IsFavourite", 0))
 +			{
 +				TCHAR *group = addContact(hContact, true)->getGroup();
 +				if (prevGroup && lstrcmp(prevGroup, group))
 +					++nGroups;
 +				prevGroup = group;
 +			}
 +
 +		int nRecent = 0;
 +		for (i = 0; nRecent < g_Options.wMaxRecent; ++i)
 +		{
 +			hContact = g_contactCache->get(i);
 +			if (!hContact) break;
 +			if (!DBGetContactSettingByte(hContact, "FavContacts", "IsFavourite", 0))
 +			{
 +				TCHAR *group = addContact(hContact, false)->getGroup();
 +				if (prevGroup && lstrcmp(prevGroup, group))
 +					++nGroups;
 +				prevGroup = group;
 +
 +				++nRecent;
 +			}
 +		}
 +	}
 +};
 +
 +#endif // favlist_h__
 diff --git a/plugins/FavContacts/src/headers.h b/plugins/FavContacts/src/headers.h new file mode 100644 index 0000000000..8863317199 --- /dev/null +++ b/plugins/FavContacts/src/headers.h @@ -0,0 +1,94 @@ +/*
 +Favourite Contacts for Miranda IM
 +
 +Copyright 2007 Victor Pavlychko
 +
 +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.
 +*/
 +
 +#define _CRT_SECURE_NO_DEPRECATE
 +
 +#if defined(UNICODE) && !defined(_UNICODE)
 +   #define _UNICODE
 +#endif
 +
 +#include <tchar.h>
 +#define _WIN32_WINNT 0x0501
 +#include <windows.h>
 +#include <commctrl.h>
 +#include <stdio.h>
 +#include <malloc.h>
 +
 +#define MIRANDA_VER	0x800
 +#include <win2k.h>
 +#include <newpluginapi.h>
 +#include <m_system.h>
 +#include <m_system_cpp.h>
 +#include <m_database.h>
 +#include <m_langpack.h>
 +#include <m_button.h>
 +#include <m_clist.h>
 +#include <m_clc.h>
 +#include <m_clui.h>
 +#include <m_options.h>
 +#include <m_protosvc.h>
 +#include <m_utils.h>
 +#include <m_skin.h>
 +#include <m_contacts.h>
 +#include <m_userinfo.h>
 +#include <m_history.h>
 +#include <m_addcontact.h>
 +#include <m_message.h>
 +#include <m_file.h>
 +#include <m_icolib.h>
 +#include <m_idle.h>
 +#include <m_awaymsg.h>
 +#include <m_avatars.h>
 +#include <m_toolbar.h>
 +#include <m_fontservice.h>
 +#include <m_hotkeys.h>
 +
 +#include "../resource.h"
 +
 +#define NEWTSTR_ALLOCA(A) (A==NULL)?NULL:_tcscpy((TCHAR*)alloca(sizeof(TCHAR)*(_tcslen(A)+1)),A)
 +
 +struct Options
 +{
 +	BYTE bSecondLine;
 +	BYTE bAvatars;
 +	BYTE bAvatarBorder;
 +	WORD wAvatarRadius;
 +	BYTE bNoTransparentBorder;
 +	BYTE bSysColors;
 +	BYTE bCenterHotkey;
 +	BYTE bUseGroups;
 +	BYTE bUseColumns;
 +	BYTE bRightAvatars;
 +	BYTE bDimIdle;
 +	WORD wMaxRecent;
 +
 +	COLORREF clLine1, clLine2, clBack;
 +	COLORREF clLine1Sel, clLine2Sel, clBackSel;
 +	HFONT hfntName, hfntSecond;
 +};
 +
 +
 +#include "contact_cache.h"
 +
 +extern Options g_Options;
 +extern CContactCache *g_contactCache;
 +
 +#include "favlist.h"
 +#include "http_api.h"
 diff --git a/plugins/FavContacts/src/http_api.cpp b/plugins/FavContacts/src/http_api.cpp new file mode 100644 index 0000000000..147cabdd7d --- /dev/null +++ b/plugins/FavContacts/src/http_api.cpp @@ -0,0 +1,165 @@ +#include "headers.h"
 +
 +#pragma comment(lib,"ws2_32.lib")
 +
 +#include "csocket.h"
 +#include "cserver.h"
 +
 +#define MS_FAVCONTACTS_OPEN_CONTACT			"FavContacts/OpenContact"
 +
 +class CHttpProcessor: public IConnectionProcessor
 +{
 +private:
 +	CSocket *m_socket;
 +
 +	char *FetchURL(char *s)
 +	{
 +		char *p;
 +		if (p = strstr(s, "\r\n")) *p = 0;
 +		if (p = strrchr(s, ' ')) *p = 0;
 +		if (p = strchr(s, ' ')) while (*p && *p == ' ') p++;
 +		return mir_strdup(p);
 +	}
 +
 +public:
 +	CHttpProcessor(CSocket *s): m_socket(s) {}
 +
 +	void ProcessConnection()
 +	{
 +		char buf[1024];
 +		int n = m_socket->Recv(buf, sizeof(buf));
 +		buf[n] = 0;
 +
 +		char *s = FetchURL(buf);
 +
 +		if (!strncmp(s, "/fav/list/", 10))
 +		{
 +			SendList();
 +		} else
 +		if (!strncmp(s, "/fav/open/", 10))
 +		{
 +			OpenContact(s);
 +		}
 +
 +		mir_free(s);
 +		m_socket->Close();
 +	}
 +
 +	void OpenContact(char *s)
 +	{
 +		m_socket->Send("HTTP 200 OK\r\n\r\n");
 +
 +		int hContact;
 +		sscanf(s, "/fav/open/%d", &hContact);
 +		if (CallService(MS_DB_CONTACT_IS, hContact, 0))
 +			CallServiceSync(MS_FAVCONTACTS_OPEN_CONTACT, (WPARAM)hContact, 0);
 +	}
 +
 +	void SendList()
 +	{
 +		TFavContacts favList;
 +		favList.build();
 +
 +		m_socket->Send(
 +			"HTTP 200 OK\r\n"
 +			"Content-Type: text/javascript\r\n"
 +			"\r\n");
 +
 +		Send("try {\r\n");
 +		Send("SetContactCount(");
 +		Send(favList.getCount());
 +		Send(");\r\n");
 +
 +		for (int i = 0; i < favList.getCount(); ++i)
 +		{
 +			HANDLE hContact = favList[i]->getHandle();
 +			TCHAR *name = (TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR);
 +			AVATARCACHEENTRY *avatar = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)hContact, 0);
 +			int status = DBGetContactSettingWord(hContact, (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0), "Status", ID_STATUS_OFFLINE);
 +
 +			Send("SetContact(");
 +			Send(i);
 +			Send(", ");
 +			Send((int)hContact);
 +			Send(", '");
 +			SendQuoted(name);
 +			Send("', ");
 +			Send(status);
 +			Send(", '");
 +			SendQuoted(avatar ? avatar->szFilename : "");
 +			Send("');\r\n");
 +		}
 +		Send("} catch(e) {}\r\n");
 +	}
 +
 +	void Send(char *s)
 +	{
 +		m_socket->Send(s);
 +	}
 +
 +#ifdef UNICODE
 +	void Send(WCHAR *ws)
 +	{
 +		char *s = mir_utf8encodeW(ws);
 +		m_socket->Send(s);
 +		mir_free(s);
 +	}
 +#endif
 +
 +	void Send(int i)
 +	{
 +		char buf[32];
 +		wsprintfA(buf, "%d", i);
 +		Send(buf);
 +	}
 +
 +	template<class XCHAR>
 +	void SendQuoted(const XCHAR *s)
 +	{
 +		int length = 0;
 +		const XCHAR *p;
 +		for (p = s; *p;  p++)
 +		{
 +			if (*p == '\'' || *p == '\\' || *p == '\"')
 +				length++;
 +			length++;
 +		}
 +		XCHAR *buf = (XCHAR *)mir_alloc(sizeof(XCHAR) * (length + 1));
 +		XCHAR *q = buf;
 +		for (p = s; *p;  p++)
 +		{
 +			if (*p == '\'' || *p == '\\' || *p == '\"')
 +			{
 +				*q = '\\';
 +				q++;
 +			}
 +			*q = *p;
 +			q++;
 +		}
 +		*q = 0;
 +		Send(buf);
 +		mir_free(buf);
 +	}
 +};
 +
 +class CHttpProcessorFactory: public IConnectionProcessorFactory
 +{
 +public:
 +	IConnectionProcessor *Create(CSocket *s)
 +	{
 +		return new CHttpProcessor(s);
 +	}
 +};
 +
 +static CHttpProcessorFactory g_httpProcessorFactory;
 +static CServer g_httpServer;
 +
 +void LoadHttpApi()
 +{
 +	g_httpServer.Start(60888, &g_httpProcessorFactory, true);
 +}
 +
 +void UnloadHttpApi()
 +{
 +	g_httpServer.Stop();
 +}
 diff --git a/plugins/FavContacts/src/http_api.h b/plugins/FavContacts/src/http_api.h new file mode 100644 index 0000000000..cccb7ea13b --- /dev/null +++ b/plugins/FavContacts/src/http_api.h @@ -0,0 +1,7 @@ +#ifndef http_api_h__
 +#define http_api_h__
 +
 +void LoadHttpApi();
 +void UnloadHttpApi();
 +
 +#endif // http_api_h__
 diff --git a/plugins/FavContacts/src/main.cpp b/plugins/FavContacts/src/main.cpp new file mode 100644 index 0000000000..5e5a1f1f9a --- /dev/null +++ b/plugins/FavContacts/src/main.cpp @@ -0,0 +1,1453 @@ +/*
 +Favourite Contacts for Miranda IM
 +
 +Copyright 2007 Victor Pavlychko
 +
 +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 "headers.h"
 +
 +DWORD		g_mirandaVersion;
 +PLUGINLINK* pluginLink = NULL;
 +HINSTANCE   g_hInst;
 +
 +struct LIST_INTERFACE li;
 +struct MM_INTERFACE mmi;
 +struct UTF8_INTERFACE utfi;
 +
 +// {AC8B66B3-AFE1-4475-BABA-49783BA39A66}
 +#define MIID_FAVCONTACTS { 0xac8b66b3, 0xafe1, 0x4475, { 0xba, 0xba, 0x49, 0x78, 0x3b, 0xa3, 0x9a, 0x66 } }
 +
 +PLUGININFOEX pluginInfo = {
 +	sizeof(PLUGININFOEX),
 +	"Favourite Contacts",
 +	PLUGIN_MAKE_VERSION(0, 0, 0, 6),
 +	"Favourite contacts menu",
 +	"code by Victor Pavlychko, icons by Angeli-Ka",
 +	"nullbie@gmail.com",
 +	"Copyright 2007-2009 Victor Pavlychko",
 +	"http://nullbie.miranda.im/",
 +	UNICODE_AWARE,
 +	0,	           // replace internal version (if any)
 +#ifdef _UNICODE
 +	// {CE2C0401-F9E0-40d7-8E95-1A4197D7AB04}
 +	{ 0xce2c0401, 0xf9e0, 0x40d7, { 0x8e, 0x95, 0x1a, 0x41, 0x97, 0xd7, 0xab, 0x4 } }
 +#else
 +	// {DE1D765C-9DC2-4679-8633-EDAD492C8479}
 +	{ 0xde1d765c, 0x9dc2, 0x4679, { 0x86, 0x33, 0xed, 0xad, 0x49, 0x2c, 0x84, 0x79 } }
 +#endif
 +};
 +
 +#define MS_FAVCONTACTS_SHOWMENU				"FavContacts/ShowMenu"
 +#define MS_FAVCONTACTS_SHOWMENU_CENTERED	"FavContacts/ShowMenuCentered"
 +#define MS_FAVCONTACTS_OPEN_CONTACT			"FavContacts/OpenContact"
 +
 +HWND g_hwndMenuHost = NULL;
 +static LRESULT CALLBACK MenuHostWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
 +static BOOL CALLBACK OptionsDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
 +
 +static void sttLoadOptions();
 +static void sttSaveOptions();
 +
 +int sttShowMenu(bool centered);
 +
 +INT_PTR svcOpenContact(WPARAM wParam, LPARAM lParam);
 +
 +INT_PTR svcShowMenu(WPARAM wParam, LPARAM lParam);
 +INT_PTR svcShowMenuCentered(WPARAM wParam, LPARAM lParam);
 +int ProcessSrmmEvent(WPARAM wParam, LPARAM lParam);
 +int ProcessSrmmIconClick(WPARAM wParam, LPARAM lParam);
 +
 +int g_icoFavourite=0, g_icoRegular=0;
 +float g_widthMultiplier = 0;
 +int g_maxItemWidth = 0;
 +
 +CContactCache *g_contactCache = NULL;
 +TCHAR g_filter[1024] = {0};
 +
 +Options g_Options = {0};
 +
 +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
 +{
 +	g_hInst = hinstDLL;
 +	return TRUE;
 +}
 +
 +bool CoreCheck()
 +{
 +	char version[1024], exepath[1024];
 +	GetModuleFileNameA(GetModuleHandle(NULL), exepath, sizeof(exepath));
 +	CallService(MS_SYSTEM_GETVERSIONTEXT, sizeof(version), (LPARAM)version);
 +	_strlwr(exepath); _strlwr(version);
 +	if (!strstr(strrchr(exepath, '\\'), "miranda") ||
 +		strstr(version, "coffee") ||
 +		(*version && (!strncmp(version, "1.", 2) || strstr(version, " 1."))) ||
 +		(g_mirandaVersion >= PLUGIN_MAKE_VERSION(1,0,0,0)))
 +	{
 +		MessageBoxA(0,
 +			Translate(
 +				"Favourite Contacts plugin was designed to be used with Miranda IM only.\n"
 +				"For use with any other application, please contact author.\n"
 +				),
 +			"Favourite Contacts Error",
 +			MB_ICONSTOP|MB_OK);
 +		return false;
 +	}
 +	return true;
 +} 
 +
 +extern "C" __declspec(dllexport) PLUGININFOEX *MirandaPluginInfoEx(DWORD mirandaVersion)
 +{
 +	g_mirandaVersion = mirandaVersion;
 +	return &pluginInfo;
 +}
 +
 +extern "C" __declspec(dllexport) PLUGININFOEX *MirandaPluginInfo(DWORD mirandaVersion)
 +{
 +	g_mirandaVersion = mirandaVersion;
 +	return &pluginInfo;
 +}
 +
 +extern "C" __declspec(dllexport) const MUUID *MirandaPluginInterfaces(void)
 +{
 +	static const MUUID interfaces[] = { MIID_FAVCONTACTS, MIID_LAST };
 +	return interfaces;
 +}
 +
 +static __forceinline COLORREF sttShadeColor(COLORREF clLine1, COLORREF clBack)
 +{
 +	return RGB(
 +				(GetRValue(clLine1) * 66UL + GetRValue(clBack) * 34UL) / 100,
 +				(GetGValue(clLine1) * 66UL + GetGValue(clBack) * 34UL) / 100,
 +				(GetBValue(clLine1) * 66UL + GetBValue(clBack) * 34UL) / 100
 +			);
 +}
 +
 +HANDLE hhkProcessTBLoaded = NULL;
 +int ProcessTBLoaded(WPARAM wParam, LPARAM lParam)
 +{
 +	TBButton button = {0};
 +	button.cbSize = sizeof(button);
 +	button.pszButtonID = "FavContacts/ShowMenu";
 +	button.pszTooltipUp = button.pszTooltipUp =
 +	button.pszButtonName = "Favourite Contacts";
 +	button.pszServiceName = MS_FAVCONTACTS_SHOWMENU;
 +	button.defPos = 200;
 +	button.tbbFlags = TBBF_SHOWTOOLTIP|TBBF_VISIBLE;
 +	button.hSecondaryIconHandle = button.hPrimaryIconHandle = (HANDLE)g_icoFavourite;
 +	CallService(MS_TB_ADDBUTTON, 0, (LPARAM)&button);
 +
 +	return 0;
 +}
 +
 +int ProcessReloadFonts(WPARAM wParam, LPARAM lParam)
 +{
 +	if (g_Options.hfntName) DeleteObject(g_Options.hfntName);
 +	if (g_Options.hfntSecond) DeleteObject(g_Options.hfntSecond);
 +
 +	LOGFONT lf = {0};
 +	FontIDT fontid = {0};
 +	fontid.cbSize = sizeof(fontid);
 +	lstrcpy(fontid.group, _T("Favourite Contacts"));
 +
 +	lstrcpy(fontid.name, _T("Contact name"));
 +	g_Options.clLine1 = CallService(MS_FONT_GETT, (WPARAM)&fontid, (LPARAM)&lf);
 +	g_Options.hfntName = CreateFontIndirect(&lf);
 +
 +	lstrcpy(fontid.name, _T("Second line"));
 +	g_Options.clLine2 = CallService(MS_FONT_GETT, (WPARAM)&fontid, (LPARAM)&lf);
 +	g_Options.hfntSecond = CreateFontIndirect(&lf);
 +
 +	lstrcpy(fontid.name, _T("Selected contact name (color)"));
 +	g_Options.clLine1Sel = CallService(MS_FONT_GETT, (WPARAM)&fontid, (LPARAM)&lf);
 +
 +	lstrcpy(fontid.name, _T("Selected second line (color)"));
 +	g_Options.clLine2Sel = CallService(MS_FONT_GETT, (WPARAM)&fontid, (LPARAM)&lf);
 +
 +	ColourIDT colourid = {0};
 +	colourid.cbSize = sizeof(colourid);
 +	lstrcpy(colourid.group, _T("Favourite Contacts"));
 +
 +	lstrcpy(colourid.name, _T("Background"));
 +	g_Options.clBack = CallService(MS_COLOUR_GETT, (WPARAM)&colourid, (LPARAM)&lf);
 +
 +	lstrcpy(colourid.name, _T("Selected background"));
 +	g_Options.clBackSel = CallService(MS_COLOUR_GETT, (WPARAM)&colourid, (LPARAM)&lf);
 +
 +	return 0;
 +}
 +
 +int ProcessModulesLoaded(WPARAM wParam, LPARAM lParam)
 +{
 +	if (ServiceExists(MS_MSG_ADDICON))
 +	{
 +		StatusIconData sid = {0};
 +		sid.cbSize = sizeof(sid);
 +		sid.szModule = "FavContacts";
 +		sid.szTooltip = Translate("Favourite Contacts");
 +		sid.hIcon = (HICON)CallService(MS_SKIN2_GETICONBYHANDLE, 0, g_icoFavourite);
 +		sid.hIconDisabled = (HICON)CallService(MS_SKIN2_GETICONBYHANDLE, 0, g_icoRegular);
 +		CallService(MS_MSG_ADDICON, 0, (LPARAM)&sid);
 +
 +		HookEvent(ME_MSG_ICONPRESSED, ProcessSrmmIconClick);
 +		HookEvent(ME_MSG_WINDOWEVENT, ProcessSrmmEvent);
 +	}
 +
 +	if (true /* ServiceExists(MS_FONT_REGISTERT) */)
 +	{
 +		//LOGFONT lf;
 +		//GetObject(GetStockObject(DEFAULT_GUI_FONT), sizeof(lf), &lf);
 +
 +		FontIDT fontid = {0};
 +		fontid.cbSize = sizeof(fontid);
 +		lstrcpy(fontid.group, _T("Favourite Contacts"));
 +		lstrcpyA(fontid.dbSettingsGroup, "FavContacts");
 +		lstrcpy(fontid.backgroundGroup, _T("Favourite Contacts"));
 +		fontid.flags = FIDF_DEFAULTVALID;
 +		fontid.deffontsettings.charset = DEFAULT_CHARSET;
 +		fontid.deffontsettings.size = -11;
 +		lstrcpy(fontid.deffontsettings.szFace, _T("MS Shell Dlg"));
 +		fontid.deffontsettings.style = 0;
 +
 +		lstrcpy(fontid.backgroundName, _T("Background"));
 +
 +		lstrcpy(fontid.name, _T("Contact name"));
 +		lstrcpyA(fontid.prefix, "fntName");
 +		fontid.deffontsettings.colour = GetSysColor(COLOR_MENUTEXT);
 +		fontid.deffontsettings.style = DBFONTF_BOLD;
 +		CallService(MS_FONT_REGISTERT, (WPARAM)&fontid, 0);
 +
 +		lstrcpy(fontid.name, _T("Second line"));
 +		lstrcpyA(fontid.prefix, "fntSecond");
 +		fontid.deffontsettings.colour = sttShadeColor(GetSysColor(COLOR_MENUTEXT), GetSysColor(COLOR_MENU));
 +		fontid.deffontsettings.style = 0;
 +		CallService(MS_FONT_REGISTERT, (WPARAM)&fontid, 0);
 +
 +		lstrcpy(fontid.backgroundName, _T("Selected background"));
 +
 +		lstrcpy(fontid.name, _T("Selected contact name (color)"));
 +		lstrcpyA(fontid.prefix, "fntNameSel");
 +		fontid.deffontsettings.colour = GetSysColor(COLOR_HIGHLIGHTTEXT);
 +		fontid.deffontsettings.style = DBFONTF_BOLD;
 +		CallService(MS_FONT_REGISTERT, (WPARAM)&fontid, 0);
 +
 +		lstrcpy(fontid.name, _T("Selected second line (color)"));
 +		lstrcpyA(fontid.prefix, "fntSecondSel");
 +		fontid.deffontsettings.colour = sttShadeColor(GetSysColor(COLOR_HIGHLIGHTTEXT), GetSysColor(COLOR_HIGHLIGHT));
 +		fontid.deffontsettings.style = 0;
 +		CallService(MS_FONT_REGISTERT, (WPARAM)&fontid, 0);
 +
 +		ColourIDT colourid = {0};
 +		colourid.cbSize = sizeof(colourid);
 +		lstrcpy(colourid.group, _T("Favourite Contacts"));
 +		lstrcpyA(colourid.dbSettingsGroup, "FavContacts");
 +
 +		lstrcpy(colourid.name, _T("Background"));
 +		lstrcpyA(colourid.setting, "BackColour");
 +		colourid.defcolour = GetSysColor(COLOR_MENU);
 +		CallService(MS_COLOUR_REGISTERT, (WPARAM)&colourid, 0);
 +
 +		lstrcpy(colourid.name, _T("Selected background"));
 +		lstrcpyA(colourid.setting, "SelectedColour");
 +		colourid.defcolour = GetSysColor(COLOR_HIGHLIGHT);
 +		CallService(MS_COLOUR_REGISTERT, (WPARAM)&colourid, 0);
 +
 +		HookEvent(ME_FONT_RELOAD, ProcessReloadFonts);
 +		HookEvent(ME_COLOUR_RELOAD, ProcessReloadFonts);
 +		ProcessReloadFonts(0, 0);
 +	}
 +
 +	if (ServiceExists(MS_HOTKEY_REGISTER))
 +	{
 +		HOTKEYDESC hotkey = {0};
 +		hotkey.cbSize = sizeof(hotkey);
 +		hotkey.pszName = "FavContacts/ShowMenu";
 +		hotkey.pszDescription = "Show favourite contacts";
 +		hotkey.pszSection = "Contacts";
 +		hotkey.pszService = MS_FAVCONTACTS_SHOWMENU_CENTERED;
 +		hotkey.DefHotKey = MAKEWORD('Q', HOTKEYF_EXT);
 +		CallService(MS_HOTKEY_REGISTER, 0, (LPARAM)&hotkey);
 +	} 
 +
 +	if (ServiceExists(MS_AV_GETAVATARBITMAP))
 +	{
 +		HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
 +		for ( ; hContact; hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
 +			if (DBGetContactSettingByte(hContact, "FavContacts", "IsFavourite", 0))
 +				CallService(MS_AV_GETAVATARBITMAP, (WPARAM)hContact, 0);
 +	}
 +
 +	if (!hhkProcessTBLoaded) hhkProcessTBLoaded = HookEvent(ME_TB_MODULELOADED, ProcessTBLoaded);
 +
 +	return 0;
 +}
 +
 +int ProcessOptInitialise(WPARAM wParam, LPARAM lParam)
 +{
 +	OPTIONSDIALOGPAGE odp = { 0 };
 +	odp.cbSize = sizeof(odp);
 +	odp.position = 100000000;
 +	odp.hInstance = g_hInst;
 +	odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS);
 +	odp.ptszGroup = TranslateT("Contact List");
 +	odp.ptszTitle = TranslateT("Favourites");
 +	odp.groupPosition = 910000000;
 +	odp.flags = ODPF_BOLDGROUPS|ODPF_TCHAR;
 +	odp.pfnDlgProc = OptionsDlgProc;
 +	CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp);
 +	return 0;
 +}
 +
 +extern "C" __declspec(dllexport) int Load(PLUGINLINK * link)
 +{
 +	pluginLink = link;
 +
 +	if (!CoreCheck()) return 1;
 +
 +	mir_getLI(&li);
 +	mir_getMMI(&mmi);
 +	mir_getUTFI(&utfi);
 +
 +	g_contactCache = new CContactCache;
 +
 +	WNDCLASSEX wcl = {0};
 +	wcl.cbSize = sizeof(wcl);
 +	wcl.lpfnWndProc = MenuHostWndProc;
 +	wcl.style = 0;
 +	wcl.cbClsExtra = 0;
 +	wcl.cbWndExtra = 0;
 +	wcl.hInstance = g_hInst;
 +	wcl.hIcon = NULL;
 +	wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
 +	wcl.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
 +	wcl.lpszMenuName = NULL;
 +	wcl.lpszClassName = _T("FavContactsMenuHostWnd");
 +	wcl.hIconSm = NULL;
 +	RegisterClassEx(&wcl);
 +
 +	g_hwndMenuHost = CreateWindow(_T("FavContactsMenuHostWnd"), NULL, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, g_hInst, NULL);
 +	SetWindowPos(g_hwndMenuHost, 0, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_HIDEWINDOW);
 +
 +	sttLoadOptions();
 +
 +	CreateServiceFunction(MS_FAVCONTACTS_SHOWMENU, svcShowMenu);
 +	CreateServiceFunction(MS_FAVCONTACTS_SHOWMENU_CENTERED, svcShowMenuCentered);
 +	CreateServiceFunction(MS_FAVCONTACTS_OPEN_CONTACT, svcOpenContact);
 +
 +	HookEvent(ME_OPT_INITIALISE, ProcessOptInitialise);
 +	HookEvent(ME_SYSTEM_MODULESLOADED, ProcessModulesLoaded);
 +	hhkProcessTBLoaded = HookEvent(ME_TB_MODULELOADED, ProcessTBLoaded);
 +
 +	if (true /*ServiceExists(MS_SKIN2_ADDICON)*/)
 +	{
 +		TCHAR buf[MAX_PATH];
 +		GetModuleFileName(g_hInst, buf, SIZEOF(buf));
 +
 +		SKINICONDESC sid = {0};
 +		sid.cbSize = sizeof(sid);
 +		sid.ptszSection = _T("Favourites");
 +		sid.ptszDefaultFile = buf;
 +		sid.cx = sid.cy = 16;
 +		sid.flags = SIDF_ALL_TCHAR;
 +
 +		sid.pszName = "favcontacts_favourite";
 +		sid.ptszDescription = _T("Favourite Contact");
 +		sid.iDefaultIndex = -IDI_FAVOURITE;
 +		g_icoFavourite = CallService(MS_SKIN2_ADDICON, 0, (LPARAM)&sid);
 +
 +		sid.pszName = "favcontacts_regular";
 +		sid.ptszDescription = _T("Regular Contact");
 +		sid.iDefaultIndex = -IDI_REGULAR;
 +		g_icoRegular = CallService(MS_SKIN2_ADDICON, 0, (LPARAM)&sid);
 +	}
 +
 +	LoadHttpApi();
 +
 +#ifdef _DEBUG
 +	CLISTMENUITEM mi = { 0 };
 +	mi.cbSize = sizeof(mi);
 +	mi.flags = CMIF_ICONFROMICOLIB;
 +	mi.icolibItem = LoadSkinnedIconHandle(SKINICON_OTHER_OPTIONS);
 +	mi.position = 1900000000;
 +	mi.pszName = LPGEN("&Favourite Contacts...");
 +	mi.pszService = MS_FAVCONTACTS_SHOWMENU;
 +	CallService( MS_CLIST_ADDMAINMENUITEM, 0, ( LPARAM )&mi );
 +#endif
 +
 +	return 0;
 +}
 +
 +extern "C" __declspec(dllexport) int Unload(void)
 +{
 +	UnloadHttpApi();
 +	if (g_hwndMenuHost) DestroyWindow(g_hwndMenuHost);
 +	if (g_Options.hfntName) DeleteObject(g_Options.hfntName);
 +	if (g_Options.hfntSecond) DeleteObject(g_Options.hfntSecond);
 +	delete g_contactCache;
 +	return 0;
 +}
 +
 +static void sttLoadOptions()
 +{
 +	g_Options.bSecondLine			= DBGetContactSettingByte(NULL, "FavContacts", "SecondLine", 1);
 +	g_Options.bAvatars				= DBGetContactSettingByte(NULL, "FavContacts", "Avatars", 1);
 +	g_Options.bAvatarBorder			= DBGetContactSettingByte(NULL, "FavContacts", "AvatarBorder", 0);
 +	g_Options.wAvatarRadius			= DBGetContactSettingWord(NULL, "FavContacts", "AvatarRadius", 3);
 +	g_Options.bNoTransparentBorder	= DBGetContactSettingByte(NULL, "FavContacts", "NoTransparentBorder",
 +										!DBGetContactSettingByte(NULL, "FavContacts", "AvatarBorderTransparent", 1));
 +	g_Options.bSysColors			= DBGetContactSettingByte(NULL, "FavContacts", "SysColors", 0);
 +	g_Options.bCenterHotkey			= DBGetContactSettingByte(NULL, "FavContacts", "CenterHotkey", 1);
 +	g_Options.bUseGroups			= DBGetContactSettingByte(NULL, "FavContacts", "UseGroups", 0);
 +	g_Options.bUseColumns			= DBGetContactSettingByte(NULL, "FavContacts", "UseColumns", 1);
 +	g_Options.bRightAvatars			= DBGetContactSettingByte(NULL, "FavContacts", "RightAvatars", 0);
 +	g_Options.bDimIdle				= DBGetContactSettingByte(NULL, "FavContacts", "DimIdle", 1);
 +
 +	g_Options.wMaxRecent			= DBGetContactSettingByte(NULL, "FavContacts", "MaxRecent", 10);
 +}
 +
 +static void sttSaveOptions()
 +{
 +	DBWriteContactSettingByte(NULL, "FavContacts", "SecondLine",			g_Options.bSecondLine);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "Avatars",				g_Options.bAvatars);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "AvatarBorder",			g_Options.bAvatarBorder);
 +	DBWriteContactSettingWord(NULL, "FavContacts", "AvatarRadius",			g_Options.wAvatarRadius);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "NoTransparentBorder",	g_Options.bNoTransparentBorder);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "SysColors",				g_Options.bSysColors);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "CenterHotkey",			g_Options.bCenterHotkey);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "UseGroups",				g_Options.bUseGroups);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "UseColumns",			g_Options.bUseColumns);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "RightAvatars",			g_Options.bRightAvatars);
 +	DBWriteContactSettingByte(NULL, "FavContacts", "DimIdle",				g_Options.bDimIdle);
 +	DBWriteContactSettingWord(NULL, "FavContacts", "MaxRecent",				g_Options.wMaxRecent);
 +}
 +
 +static bool sttIsGroup(int id)
 +{
 +	if (id == 1) return true;
 +
 +	DBVARIANT dbv = {0};
 +	char buf[32];
 +	wsprintfA(buf, "%d", (int)(id-2));
 +	if (!DBGetContactSettingTString(NULL, "CListGroups", buf, &dbv))
 +	{
 +		DBFreeVariant(&dbv);
 +		return true;
 +	}
 +	return false;
 +}
 +
 +static TCHAR *sttGetGroupName(int id)
 +{
 +	if (id == 1)
 +	{
 +		if (g_Options.bUseGroups)
 +			return mir_tstrdup(TranslateT("<No group>"));
 +		return mir_tstrdup(TranslateT("Favourite Contacts"));
 +	}
 +
 +	DBVARIANT dbv = {0};
 +	char buf[32];
 +	wsprintfA(buf, "%d", (int)(id-2));
 +	if (!DBGetContactSettingTString(NULL, "CListGroups", buf, &dbv))
 +	{
 +		TCHAR *res = mir_tstrdup(dbv.ptszVal+1);
 +		DBFreeVariant(&dbv);
 +		return res;
 +	}
 +	return NULL;
 +}
 +
 +static int sttGetGroupId(TCHAR *name)
 +{
 +	for (int i = 0; ; ++i)
 +	{
 +		DBVARIANT dbv = {0};
 +		char buf[32];
 +		wsprintfA(buf, "%d", (int)i);
 +		if (!DBGetContactSettingTString(NULL, "CListGroups", buf, &dbv))
 +		{
 +			if (!lstrcmp(dbv.ptszVal+1, name))
 +			{
 +				DBFreeVariant(&dbv);
 +				return i+2;
 +			}
 +
 +			DBFreeVariant(&dbv);
 +		} else
 +		{
 +			// default is root
 +			return 1;
 +		}
 +	}
 +}
 +
 +static BOOL sttMeasureItem_Group(LPMEASUREITEMSTRUCT lpmis, Options *options)
 +{
 +	if (true)
 +	{
 +		HDC hdc = GetDC(g_hwndMenuHost);
 +		HFONT hfntSave = (HFONT)SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
 +		TCHAR *name = sttGetGroupName(lpmis->itemData);
 +		SIZE sz;
 +		if (!options->bSysColors) SelectObject(hdc, g_Options.hfntName);
 +		GetTextExtentPoint32(hdc, name, lstrlen(name), &sz);
 +		lpmis->itemHeight = sz.cy + 8;
 +		lpmis->itemWidth = sz.cx + 10;
 +		mir_free(name);
 +		SelectObject(hdc, hfntSave);
 +		ReleaseDC(g_hwndMenuHost, hdc);
 +	}
 +
 +	return TRUE;
 +}
 +
 +static BOOL sttMeasureItem_Contact(LPMEASUREITEMSTRUCT lpmis, Options *options)
 +{
 +	HANDLE hContact = (HANDLE)lpmis->itemData;
 +
 +	lpmis->itemHeight = 4;
 +	lpmis->itemWidth = 8+10;
 +
 +	if (true)
 +	{
 +		lpmis->itemWidth += 20;
 +	}
 +	
 +	if (true)
 +	{
 +		SIZE sz;
 +		int textWidth = 0;
 +
 +		HDC hdc = GetDC(g_hwndMenuHost);
 +		HFONT hfntSave = (HFONT)SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
 +
 +		if (options->bSecondLine)
 +		{
 +			DBVARIANT dbv;
 +			TCHAR *title;
 +			bool bFree = false;
 +			if (DBGetContactSettingTString(hContact, "CList", "StatusMsg", &dbv) || !*dbv.ptszVal)
 +			{
 +				char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
 +				int status = DBGetContactSettingWord(hContact, proto, "Status", ID_STATUS_OFFLINE);
 +				title = (TCHAR *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, status, GSMDF_TCHAR);
 +			} else
 +			{
 +				title = dbv.ptszVal;
 +				bFree = true;
 +			}
 +
 +			if (!options->bSysColors) SelectObject(hdc, g_Options.hfntSecond);
 +			GetTextExtentPoint32(hdc, title, lstrlen(title), &sz);
 +			if (bFree) DBFreeVariant(&dbv);
 +			textWidth = sz.cx;
 +			lpmis->itemHeight += sz.cy + 3;
 +		}
 +
 +		TCHAR *name = (TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR);
 +
 +		if (!options->bSysColors) SelectObject(hdc, g_Options.hfntName);
 +		GetTextExtentPoint32(hdc, name, lstrlen(name), &sz);
 +		textWidth = max(textWidth, sz.cx);
 +
 +		SelectObject(hdc, hfntSave);
 +		ReleaseDC(g_hwndMenuHost, hdc);
 +
 +		lpmis->itemWidth += textWidth;
 +		lpmis->itemHeight += sz.cy;
 +	}
 +
 +	if (options->bAvatars)
 +	{
 +		AVATARCACHEENTRY *ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)hContact, 0);
 +		if (ace && (ace != (AVATARCACHEENTRY *)CALLSERVICE_NOTFOUND))
 +		{
 +			int avatarWidth = lpmis->itemHeight;
 +			if (ace->bmWidth < ace->bmHeight)
 +				avatarWidth = lpmis->itemHeight * ace->bmWidth / ace->bmHeight;
 +
 +			lpmis->itemWidth += avatarWidth + 5;
 +		}
 +	}
 +
 +	if (lpmis->itemHeight < 18) lpmis->itemHeight = 18;
 +
 +	return TRUE;
 +}
 +
 +static BOOL sttMeasureItem(LPMEASUREITEMSTRUCT lpmis, Options *options=NULL)
 +{
 +	if (!options) options = &g_Options;
 +
 +	if (!lpmis->itemData)
 +		return FALSE;
 +
 +	BOOL res = FALSE;
 +	if (sttIsGroup(lpmis->itemData))
 +		res = sttMeasureItem_Group(lpmis, options);
 +	else if (CallService(MS_DB_CONTACT_IS, lpmis->itemData, 0))
 +		res = sttMeasureItem_Contact(lpmis, options);
 +
 +	if (res && (lpmis->itemWidth > g_maxItemWidth)) lpmis->itemWidth = g_maxItemWidth;
 +	if (res && g_widthMultiplier) lpmis->itemWidth *= g_widthMultiplier;
 +
 +	return FALSE;
 +}
 +
 +static BOOL sttDrawItem_Group(LPDRAWITEMSTRUCT lpdis, Options *options = NULL)
 +{
 +	lpdis->rcItem.top++;
 +	lpdis->rcItem.bottom--;
 +
 +	HFONT hfntSave = (HFONT)SelectObject(lpdis->hDC, GetStockObject(DEFAULT_GUI_FONT));
 +	SetBkMode(lpdis->hDC, TRANSPARENT);
 +	if (options->bSysColors)
 +	{
 +		FillRect(lpdis->hDC, &lpdis->rcItem, GetSysColorBrush(COLOR_HIGHLIGHT));
 +		//FrameRect(lpdis->hDC, &lpdis->rcItem, GetSysColorBrush(COLOR_HIGHLIGHT));
 +		SetTextColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
 +	} else
 +	{
 +		HBRUSH hbr;
 +		hbr = CreateSolidBrush(g_Options.clBackSel);
 +		FillRect(lpdis->hDC, &lpdis->rcItem, hbr);
 +		DeleteObject(hbr);
 +		//hbr = CreateSolidBrush(g_Options.clBackSel);
 +		//FrameRect(lpdis->hDC, &lpdis->rcItem, hbr);
 +		//DeleteObject(hbr);
 +		SetTextColor(lpdis->hDC, g_Options.clLine1Sel);
 +	}
 +
 +	if (true)
 +	{
 +		TCHAR *name = sttGetGroupName(lpdis->itemData);
 +		if (!options->bSysColors) SelectObject(lpdis->hDC, g_Options.hfntName);
 +		DrawText(lpdis->hDC, name, lstrlen(name), &lpdis->rcItem, DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER|DT_CENTER);
 +		mir_free(name);
 +	}
 +
 +	SelectObject(lpdis->hDC, hfntSave);
 +
 +	return TRUE;
 +}
 +
 +void ImageList_DrawDimmed(HIMAGELIST himl, int i, HDC hdc, int left, int top, UINT fStyle)
 +{
 +	typedef BOOL (WINAPI *TFnAlphaBlend)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION);
 +	static TFnAlphaBlend pfnAlphaBlend = NULL;
 +	bool load_funcs = true;
 +
 +	if (load_funcs)
 +	{
 +		pfnAlphaBlend = (TFnAlphaBlend)GetProcAddress(GetModuleHandleA("msimg32"), "AlphaBlend");
 +		load_funcs = false;
 +	}
 +
 +	int dx, dy;
 +	ImageList_GetIconSize(himl, &dx, &dy);
 +
 +	HDC dcMem = CreateCompatibleDC(hdc);
 +	HBITMAP hbm = CreateCompatibleBitmap(hdc, dx, dy);
 +	HBITMAP hbmOld = (HBITMAP)SelectObject(dcMem, hbm);
 +	BitBlt(dcMem, 0, 0, dx, dx, hdc, left, top, SRCCOPY);
 +	ImageList_Draw(himl, i, dcMem, 0, 0, fStyle);
 +	if (pfnAlphaBlend)
 +	{
 +		BLENDFUNCTION bf = {0};
 +		bf.SourceConstantAlpha = 180;
 +		pfnAlphaBlend(hdc, left, top, dx, dy, dcMem, 0, 0, dx, dy, bf);
 +	} else
 +	{
 +		SetStretchBltMode(hdc, HALFTONE);
 +		StretchBlt(hdc, left, top, dx, dy, dcMem, 0, 0, dx, dy, SRCCOPY);
 +	}
 +	SelectObject(dcMem, hbmOld);
 +	DeleteObject(hbm);
 +	DeleteDC(dcMem);
 +}
 +
 +static BOOL sttDrawItem_Contact(LPDRAWITEMSTRUCT lpdis, Options *options = NULL)
 +{
 +	HANDLE hContact = (HANDLE)lpdis->itemData;
 +
 +	HDC hdcTemp = CreateCompatibleDC(lpdis->hDC);
 +	HBITMAP hbmTemp = CreateCompatibleBitmap(lpdis->hDC, lpdis->rcItem.right-lpdis->rcItem.left, lpdis->rcItem.bottom-lpdis->rcItem.top);
 +    HBITMAP hbmSave = (HBITMAP)SelectObject(hdcTemp, hbmTemp);
 +	RECT rcSave = lpdis->rcItem;
 +
 +	OffsetRect(&lpdis->rcItem, -lpdis->rcItem.left, -lpdis->rcItem.top);
 +
 +	HFONT hfntSave = (HFONT)SelectObject(hdcTemp, GetStockObject(DEFAULT_GUI_FONT));
 +	SetBkMode(hdcTemp, TRANSPARENT);
 +	COLORREF clBack, clLine1, clLine2;
 +	if (lpdis->itemState & ODS_SELECTED)
 +	{
 +		if (options->bSysColors)
 +		{
 +			FillRect(hdcTemp, &lpdis->rcItem, GetSysColorBrush(COLOR_HIGHLIGHT));
 +			clBack = GetSysColor(COLOR_HIGHLIGHT);
 +			clLine1 = GetSysColor(COLOR_HIGHLIGHTTEXT);
 +		} else
 +		{
 +			clBack = g_Options.clBackSel;
 +			clLine1 = g_Options.clLine1Sel;
 +			clLine2 = g_Options.clLine2Sel;
 +		}
 +	} else
 +	{
 +		if (options->bSysColors)
 +		{
 +			FillRect(hdcTemp, &lpdis->rcItem, GetSysColorBrush(COLOR_MENU));
 +			clBack = GetSysColor(COLOR_MENU);
 +			clLine1 = GetSysColor(COLOR_MENUTEXT);
 +		} else
 +		{
 +			clBack = g_Options.clBack;
 +			clLine1 = g_Options.clLine1;
 +			clLine2 = g_Options.clLine2;
 +		}
 +	}
 +	if (options->bSysColors)
 +	{
 +		clLine2 = RGB(
 +				(GetRValue(clLine1) * 66UL + GetRValue(clBack) * 34UL) / 100,
 +				(GetGValue(clLine1) * 66UL + GetGValue(clBack) * 34UL) / 100,
 +				(GetBValue(clLine1) * 66UL + GetBValue(clBack) * 34UL) / 100
 +			);
 +	} else
 +	{
 +		HBRUSH hbr = CreateSolidBrush(clBack);
 +		FillRect(hdcTemp, &lpdis->rcItem, hbr);
 +		DeleteObject(hbr);
 +	}
 +
 +	lpdis->rcItem.left += 4;
 +	lpdis->rcItem.right -= 4;
 +
 +	lpdis->rcItem.top += 2;
 +	lpdis->rcItem.bottom -= 2;
 +
 +	char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
 +
 +	if (true)
 +	{
 +		HIMAGELIST hIml = (HIMAGELIST)CallService(MS_CLIST_GETICONSIMAGELIST, 0, 0);
 +		int iIcon = CallService(MS_CLIST_GETCONTACTICON, (WPARAM)hContact, 0);
 +
 +		if (DBGetContactSettingDword(hContact, proto, "IdleTS", 0))
 +		{
 +			ImageList_DrawDimmed(hIml, iIcon, hdcTemp,
 +				lpdis->rcItem.left, (lpdis->rcItem.top + lpdis->rcItem.bottom - 16) / 2,
 +				ILD_TRANSPARENT);
 +		} else
 +		{
 +			ImageList_Draw(hIml, iIcon, hdcTemp,
 +				lpdis->rcItem.left, (lpdis->rcItem.top + lpdis->rcItem.bottom - 16) / 2,
 +				ILD_TRANSPARENT);
 +		}
 +
 +		lpdis->rcItem.left += 20;
 +	}
 +
 +	if (options->wMaxRecent && DBGetContactSettingByte(hContact, "FavContacts", "IsFavourite", 0))
 +	{
 +		DrawIconEx(hdcTemp, lpdis->rcItem.right - 18, (lpdis->rcItem.top + lpdis->rcItem.bottom - 16) / 2,
 +			(HICON)CallService(MS_SKIN2_GETICONBYHANDLE, 0, g_icoFavourite), 16, 16, 0, NULL, DI_NORMAL);
 +		lpdis->rcItem.right -= 20;
 +	}
 +
 +	if (options->bAvatars)
 +	{
 +		AVATARCACHEENTRY *ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)hContact, 0);
 +		if (ace && (ace != (AVATARCACHEENTRY *)CALLSERVICE_NOTFOUND))
 +		{
 +			int avatarWidth = lpdis->rcItem.bottom - lpdis->rcItem.top;
 +			if (ace->bmWidth < ace->bmHeight)
 +				avatarWidth = (lpdis->rcItem.bottom - lpdis->rcItem.top) * ace->bmWidth / ace->bmHeight;
 +
 +			AVATARDRAWREQUEST avdr = {0};
 +			avdr.cbSize = sizeof(avdr);
 +			avdr.hContact = hContact;
 +			avdr.hTargetDC = hdcTemp;
 +			avdr.rcDraw = lpdis->rcItem;
 +			if (options->bRightAvatars)
 +				avdr.rcDraw.left = avdr.rcDraw.right - avatarWidth;
 +			else
 +				avdr.rcDraw.right = avdr.rcDraw.left + avatarWidth;
 +			avdr.dwFlags = AVDRQ_FALLBACKPROTO;
 +			if (options->bAvatarBorder)
 +			{
 +				avdr.dwFlags |= AVDRQ_DRAWBORDER;
 +				avdr.clrBorder = clLine1;
 +				if (options->bNoTransparentBorder)
 +					avdr.dwFlags |= AVDRQ_HIDEBORDERONTRANSPARENCY;
 +				if (options->wAvatarRadius)
 +				{
 +					avdr.dwFlags |= AVDRQ_ROUNDEDCORNER;
 +					avdr.radius = (unsigned char)options->wAvatarRadius;
 +				}
 +			}
 +			avdr.alpha = 255;
 +			CallService(MS_AV_DRAWAVATAR, 0, (LPARAM)&avdr);
 +
 +			if (options->bRightAvatars)
 +				lpdis->rcItem.right += avatarWidth + 5;
 +			else
 +				lpdis->rcItem.left += avatarWidth + 5;
 +		}
 +	}
 +	
 +	if (true)
 +	{
 +		TCHAR *name = (TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR);
 +
 +		if (!options->bSysColors) SelectObject(hdcTemp, g_Options.hfntName);
 +		SetTextColor(hdcTemp, clLine1);
 +		DrawText(hdcTemp, name, lstrlen(name), &lpdis->rcItem, DT_NOPREFIX|DT_SINGLELINE|DT_TOP|DT_LEFT);
 +
 +		SIZE sz; GetTextExtentPoint32(hdcTemp, name, lstrlen(name), &sz);
 +		lpdis->rcItem.top += sz.cy + 3;
 +	}
 +
 +	if (options->bSecondLine)
 +	{
 +		DBVARIANT dbv;
 +		TCHAR *title;
 +		bool bFree = false;
 +		if (DBGetContactSettingTString(hContact, "CList", "StatusMsg", &dbv) || !*dbv.ptszVal)
 +		{
 +			int status = DBGetContactSettingWord(hContact, proto, "Status", ID_STATUS_OFFLINE);
 +			title = (TCHAR *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, status, GSMDF_TCHAR);
 +		} else
 +		{
 +			title = dbv.ptszVal;
 +			bFree = true;
 +		}
 +
 +		if (!options->bSysColors) SelectObject(hdcTemp, g_Options.hfntSecond);
 +		SetTextColor(hdcTemp, clLine2);
 +		DrawText(hdcTemp, title, lstrlen(title), &lpdis->rcItem, DT_NOPREFIX|DT_SINGLELINE|DT_TOP|DT_LEFT);
 +
 +		if (bFree) DBFreeVariant(&dbv);
 +	}
 +
 +	SelectObject(hdcTemp, hfntSave);
 +
 +	BitBlt(lpdis->hDC,
 +		rcSave.left, rcSave.top,
 +		rcSave.right-rcSave.left, rcSave.bottom-rcSave.top,
 +		hdcTemp, 0, 0, SRCCOPY);
 +
 +    SelectObject(hdcTemp, hbmSave);
 +    DeleteObject(hbmTemp);
 +    DeleteDC(hdcTemp);
 +
 +	return TRUE;
 +}
 +
 +static BOOL sttDrawItem(LPDRAWITEMSTRUCT lpdis, Options *options=NULL)
 +{
 +	if (!options) options = &g_Options;
 +
 +	if (!lpdis->itemData)
 +		return FALSE;
 +
 +	if (sttIsGroup(lpdis->itemData))
 +		return sttDrawItem_Group(lpdis, options);
 +
 +	if (CallService(MS_DB_CONTACT_IS, lpdis->itemData, 0))
 +		return sttDrawItem_Contact(lpdis, options);
 +
 +	return FALSE;
 +}
 +
 +static LRESULT CALLBACK MenuHostWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 +{
 +	static HANDLE hContact = NULL;
 +
 +	switch (message)
 +	{
 +		case WM_MEASUREITEM:
 +		{
 +			LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT)lParam;
 +
 +			if (lpmis->CtlType != ODT_MENU) return FALSE;
 +
 +			if ((lpmis->itemID >= CLISTMENUIDMIN) && (lpmis->itemID <= CLISTMENUIDMAX))
 +				return CallService(MS_CLIST_MENUMEASUREITEM, wParam, lParam);
 +
 +			return sttMeasureItem(lpmis);
 +		}
 +
 +		case WM_DRAWITEM:
 +		{
 +			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
 +
 +			if (lpdis->CtlType != ODT_MENU) return FALSE;
 +
 +			if ((lpdis->itemID >= CLISTMENUIDMIN) && (lpdis->itemID <= CLISTMENUIDMAX))
 +				return CallService(MS_CLIST_MENUDRAWITEM, wParam, lParam);
 +
 +			return sttDrawItem(lpdis);
 +		}
 +
 +		case WM_MENUCHAR:
 +		{
 +			while (GetMenuItemCount((HMENU)lParam) > 1)
 +				RemoveMenu((HMENU)lParam, 1, MF_BYPOSITION);
 +
 +			if (LOWORD(wParam) == VK_BACK)
 +			{
 +				if (int l = lstrlen(g_filter))
 +					g_filter[l-1] = 0;
 +			} else
 +			if (_istalnum(LOWORD(wParam)))
 +			{
 +				if (lstrlen(g_filter) < SIZEOF(g_filter)-1)
 +				{
 +					TCHAR s[] = { LOWORD(wParam), 0 };
 +					lstrcat(g_filter, s);
 +				}
 +			}
 +
 +			int nRecent = 0;
 +			int maxRecent = g_Options.wMaxRecent ? g_Options.wMaxRecent : 10;
 +			for (int i = 0; nRecent < maxRecent; ++i)
 +			{
 +				HANDLE hContact = g_contactCache->get(i);
 +				if (!hContact) break;
 +				if (!g_contactCache->filter(i, g_filter)) continue;
 +
 +				AppendMenu((HMENU)lParam, MF_OWNERDRAW, nRecent+1, (LPCTSTR)hContact);
 +				++nRecent;
 +			}
 +			return MAKELRESULT(1, MNC_SELECT);
 +		}
 +
 +		case WM_MENURBUTTONUP:
 +		{
 +			MENUITEMINFO mii = {0};
 +			mii.cbSize = sizeof(mii);
 +			mii.fMask = MIIM_DATA;
 +			GetMenuItemInfo((HMENU)lParam, wParam, TRUE, &mii);
 +			HANDLE hContact = (HANDLE)mii.dwItemData;
 +			if (!CallService(MS_DB_CONTACT_IS, mii.dwItemData, 0)) return FALSE;
 +
 +			HMENU hMenu = (HMENU)CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM)hContact, 0);
 +
 +			POINT pt;
 +			GetCursorPos(&pt);
 +			HWND hwndSave = GetForegroundWindow();
 +			SetForegroundWindow(g_hwndMenuHost);
 +			int res = TrackPopupMenu(hMenu, TPM_RECURSE|TPM_RIGHTBUTTON|TPM_RETURNCMD, pt.x, pt.y, 0, g_hwndMenuHost, NULL);
 +			SetForegroundWindow(hwndSave);
 +			DestroyMenu(hMenu);
 +
 +			CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(res, MPCF_CONTACTMENU), (LPARAM)hContact);
 +			return TRUE;
 +		}
 +	}
 +
 +	return DefWindowProc(hwnd, message, wParam, lParam);
 +}
 +
 +int sttShowMenu(bool centered)
 +{
 +	TFavContacts favList;
 +	HMENU hMenu = CreatePopupMenu();
 +	SIZE szMenu = {0};
 +	SIZE szColumn = {0};
 +	TCHAR *prevGroup = NULL;
 +	int i, idItem;
 +	HANDLE hContact;
 +
 +	favList.build();
 +
 +	g_widthMultiplier = 0;
 +
 +	g_maxItemWidth = GetSystemMetrics(SM_CXSCREEN);
 +	if (g_Options.bUseColumns) g_maxItemWidth /= favList.groupCount();
 +
 +	prevGroup = NULL;
 +	for (i = 0; i < favList.getCount(); ++i)
 +	{
 +		hContact = favList[i]->getHandle();
 +
 +		MEASUREITEMSTRUCT mis = {0};
 +		mis.CtlID = 0;
 +		mis.CtlType = ODT_MENU;
 +
 +		if (!prevGroup || lstrcmp(prevGroup, favList[i]->getGroup()))
 +		{
 +			if (prevGroup && g_Options.bUseColumns)
 +			{
 +				szMenu.cx += szColumn.cx;
 +				szMenu.cy = max(szMenu.cy, szColumn.cy);
 +				szColumn.cx = szColumn.cy = 0;
 +			}
 +
 +			DWORD groupID = sttGetGroupId(favList[i]->getGroup());
 +
 +			AppendMenu(hMenu,
 +				MF_OWNERDRAW|MF_SEPARATOR| ((prevGroup && g_Options.bUseColumns) ? MF_MENUBREAK : 0),
 +				++idItem, (LPCTSTR)groupID);
 +
 +			mis.itemData = groupID;
 +			mis.itemID = idItem;
 +			sttMeasureItem(&mis);
 +			szColumn.cx = max(szColumn.cx, mis.itemWidth);
 +			szColumn.cy += mis.itemHeight;
 +		}
 +
 +		AppendMenu(hMenu, MF_OWNERDRAW, ++idItem, (LPCTSTR)hContact);
 +
 +		mis.itemData = (DWORD)hContact;
 +		mis.itemID = idItem;
 +		sttMeasureItem(&mis);
 +		szColumn.cx = max(szColumn.cx, mis.itemWidth);
 +		szColumn.cy += mis.itemHeight;
 +
 +		prevGroup = favList[i]->getGroup();
 +	}
 +	szMenu.cx += szColumn.cx;
 +	szMenu.cy = max(szMenu.cy, szColumn.cy);
 +	szColumn.cx = szColumn.cy = 0;
 +
 +	unsigned maxWidth = GetSystemMetrics(SM_CXSCREEN) * DBGetContactSettingByte(NULL, "FavContacts", "MenuWidth", 66) / 100;
 +	if (szMenu.cx > maxWidth)
 +	{
 +		g_widthMultiplier = (float)maxWidth / szMenu.cx;
 +		szMenu.cx *= g_widthMultiplier;
 +	}
 +
 +	POINT pt;
 +
 +//	RECT rc;
 +//	GetMenuItemRect(g_hwndMenuHost, hMenu, 1, &rc);
 +
 +	if (centered)
 +	{
 +		if ((pt.x = (GetSystemMetrics(SM_CXSCREEN) - szMenu.cx) / 2) < 0) pt.x = 0;
 +		if ((pt.y = (GetSystemMetrics(SM_CYSCREEN) - szMenu.cy) / 2) < 0) pt.y = 0;
 +	} else
 +	{
 +		GetCursorPos(&pt);
 +	}
 +
 +	HWND hwndSave = GetForegroundWindow();
 +	SetForegroundWindow(g_hwndMenuHost);
 +	hContact = NULL;
 +	g_filter[0] = 0;
 +	if (int res = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, g_hwndMenuHost, NULL))
 +	{
 +		MENUITEMINFO mii = {0};
 +		mii.cbSize = sizeof(mii);
 +		mii.fMask = MIIM_DATA;
 +		GetMenuItemInfo(hMenu, res, FALSE, &mii);
 +		hContact = (HANDLE)mii.dwItemData;
 +	}
 +	SetForegroundWindow(hwndSave);
 +	DestroyMenu(hMenu);
 +
 +	if (hContact)
 +		CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM)hContact, 0);
 +
 +	return 0;
 +}
 +
 +INT_PTR svcShowMenu(WPARAM wParam, LPARAM lParam)
 +{
 +	sttShowMenu(false);
 +	return 0;
 +}
 +
 +INT_PTR svcShowMenuCentered(WPARAM wParam, LPARAM lParam)
 +{
 +	sttShowMenu(g_Options.bCenterHotkey ? true : false);
 +	return 0;
 +}
 +
 +static HANDLE hDialogsList = NULL;
 +static HANDLE hContactToActivate = NULL;
 +
 +INT_PTR svcOpenContact(WPARAM wParam, LPARAM lParam)
 +{
 +	hContactToActivate = (HANDLE)wParam;
 +	CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM)hContactToActivate, 0);
 +	return 0;
 +}
 +
 +int ProcessSrmmEvent( WPARAM wParam, LPARAM lParam )
 +{
 +	MessageWindowEventData *event = (MessageWindowEventData *)lParam;
 +
 +	if ( event->uType == MSG_WINDOW_EVT_OPEN )
 +	{
 +		if ( !hDialogsList )
 +			hDialogsList = (HANDLE)CallService(MS_UTILS_ALLOCWINDOWLIST, 0, 0);
 +		WindowList_Add(hDialogsList, event->hwndWindow, event->hContact);
 +
 +		BYTE fav = DBGetContactSettingByte(event->hContact, "FavContacts", "IsFavourite", 0);
 +		StatusIconData sid = {0};
 +		sid.cbSize = sizeof(sid);
 +		sid.szModule = "FavContacts";
 +		sid.flags = fav ? 0 : MBF_DISABLED;
 +		CallService(MS_MSG_MODIFYICON, (WPARAM)event->hContact, (LPARAM)&sid);
 +
 +		if (event->hContact == hContactToActivate)
 +		{
 +			HWND hwndRoot = event->hwndWindow;
 +			while (HWND hwndParent = GetParent(hwndRoot))
 +				hwndRoot = hwndParent;
 +
 +			AttachThreadInput(GetWindowThreadProcessId(GetForegroundWindow(), NULL), GetCurrentThreadId(), TRUE);
 +			SetForegroundWindow(hwndRoot);
 +			SetActiveWindow(hwndRoot);
 +			SetFocus(hwndRoot);
 +			AttachThreadInput(GetWindowThreadProcessId(GetForegroundWindow(), NULL), GetCurrentThreadId(), FALSE);
 +		}
 +
 +		hContactToActivate = NULL;
 +	}
 +	else if ( event->uType == MSG_WINDOW_EVT_CLOSING )
 +	{
 +		if (hDialogsList)
 +			WindowList_Remove(hDialogsList, event->hwndWindow);
 +	}
 +
 +	return 0;
 +}
 +
 +int ProcessSrmmIconClick( WPARAM wParam, LPARAM lParam )
 +{
 +	StatusIconClickData *sicd = (StatusIconClickData *)lParam;
 +	if (lstrcmpA(sicd->szModule, "FavContacts")) return 0;
 +
 +	HANDLE hContact = (HANDLE)wParam;
 +	if (!hContact) return 0;
 +
 +	if (sicd->flags & MBCF_RIGHTBUTTON)
 +	{
 +		BYTE fav = !DBGetContactSettingByte(hContact, "FavContacts", "IsFavourite", 0);
 +		DBWriteContactSettingByte(hContact, "FavContacts", "IsFavourite", fav);
 +		if (fav) CallService(MS_AV_GETAVATARBITMAP, (WPARAM)hContact, 0);
 +
 +		StatusIconData sid = {0};
 +		sid.cbSize = sizeof(sid);
 +		sid.szModule = "FavContacts";
 +		sid.flags = fav ? 0 : MBF_DISABLED;
 +		CallService(MS_MSG_MODIFYICON, (WPARAM)hContact, (LPARAM)&sid);
 +	} else
 +	{
 +		sttShowMenu(false);
 +	}
 +
 +	return 0;
 +}
 +
 +///////////////////////////////////////////////
 +// Options
 +static void sttResetListOptions(HWND hwndList)
 +{
 +	int i;
 +	SendMessage(hwndList,CLM_SETBKBITMAP,0,(LPARAM)(HBITMAP)NULL);
 +	SendMessage(hwndList,CLM_SETBKCOLOR,GetSysColor(COLOR_WINDOW),0);
 +	SendMessage(hwndList,CLM_SETGREYOUTFLAGS,0,0);
 +	SendMessage(hwndList,CLM_SETLEFTMARGIN,4,0);
 +	SendMessage(hwndList,CLM_SETINDENT,10,0);
 +	SendMessage(hwndList,CLM_SETHIDEEMPTYGROUPS,1,0);
 +	SendMessage(hwndList,CLM_SETHIDEOFFLINEROOT,1,0);
 +	for (i = 0; i <= FONTID_MAX; ++i)
 +		SendMessage(hwndList, CLM_SETTEXTCOLOR, i, GetSysColor(COLOR_WINDOWTEXT));
 +}
 +
 +static void sttActivateOptionsPage(HWND hwnd, TCHAR *aSection, TCHAR *aPage)
 +{
 +	TCHAR buf[256];
 +	TCHAR *section = TranslateTS(aSection);
 +
 +	HWND hwndTree = FindWindowEx(GetParent(hwnd), NULL, WC_TREEVIEW, NULL);
 +	for (HTREEITEM htiSection = TreeView_GetRoot(hwndTree); htiSection; htiSection = TreeView_GetNextSibling(hwndTree, htiSection))
 +	{
 +		TVITEM tvi = {0};
 +		tvi.mask = TVIF_TEXT;
 +		tvi.hItem = htiSection;
 +		tvi.pszText = buf;
 +		tvi.cchTextMax = SIZEOF(buf);
 +		TreeView_GetItem(hwndTree, &tvi);
 +
 +		if (!lstrcmp(buf, section))
 +		{
 +			if (!aPage)
 +			{
 +				TreeView_Select(hwndTree, htiSection, TVGN_CARET);
 +				return;
 +			} else
 +			{
 +				TreeView_Expand(hwndTree, htiSection, TVE_EXPAND);
 +			}
 +
 +			TCHAR *page = TranslateTS(aPage);
 +			for (HTREEITEM htiPage = TreeView_GetChild(hwndTree, htiSection); htiPage; htiPage = TreeView_GetNextSibling(hwndTree, htiPage))
 +			{
 +				TVITEM tvi = {0};
 +				tvi.mask = TVIF_TEXT;
 +				tvi.hItem = htiPage;
 +				tvi.pszText = buf;
 +				tvi.cchTextMax = SIZEOF(buf);
 +				TreeView_GetItem(hwndTree, &tvi);
 +
 +				if (!lstrcmp(buf, page))
 +				{
 +					TreeView_Select(hwndTree, htiPage, TVGN_CARET);
 +					return;
 +				}
 +			}
 +
 +			break;
 +		}
 +	}
 +}
 +
 +static BOOL CALLBACK OptionsDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 +{
 +	static bool bInitialized = false;
 +	static HANDLE hSelectedContact = 0;
 +
 +	switch (msg)
 +	{
 +		case WM_INITDIALOG:
 +		{
 +			bInitialized = false;
 +
 +			TranslateDialogDefault(hwnd);
 +
 +			CheckDlgButton(hwnd, IDC_CHK_GROUPS, g_Options.bUseGroups ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_GROUPCOLUMS, g_Options.bUseColumns ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_SECONDLINE, g_Options.bSecondLine ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_AVATARS, g_Options.bAvatars ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_AVATARBORDER, g_Options.bAvatarBorder ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_NOTRANSPARENTBORDER, g_Options.bNoTransparentBorder ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_SYSCOLORS, g_Options.bSysColors ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_CENTERHOTKEY, g_Options.bCenterHotkey ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_RIGHTAVATARS, g_Options.bRightAvatars ? BST_CHECKED : BST_UNCHECKED);
 +			CheckDlgButton(hwnd, IDC_CHK_DIMIDLE, g_Options.bDimIdle ? BST_CHECKED : BST_UNCHECKED);
 +			SetDlgItemInt(hwnd, IDC_TXT_RADIUS, g_Options.wAvatarRadius, FALSE);
 +			SetDlgItemInt(hwnd, IDC_TXT_MAXRECENT, g_Options.wMaxRecent, FALSE);
 +
 +			SetWindowLong(GetDlgItem(hwnd, IDC_CLIST), GWL_STYLE,
 +				GetWindowLong(GetDlgItem(hwnd, IDC_CLIST), GWL_STYLE)|CLS_CHECKBOXES|CLS_HIDEEMPTYGROUPS|CLS_USEGROUPS|CLS_GREYALTERNATE|CLS_GROUPCHECKBOXES);
 +			SendMessage(GetDlgItem(hwnd, IDC_CLIST), CLM_SETEXSTYLE, CLS_EX_DISABLEDRAGDROP|CLS_EX_TRACKSELECT, 0);
 +			sttResetListOptions(GetDlgItem(hwnd, IDC_CLIST));
 +
 +			hSelectedContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
 +
 +			HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
 +			for ( ; hContact; hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
 +			{
 +				SendDlgItemMessage(hwnd, IDC_CLIST, CLM_SETCHECKMARK, 
 +					SendDlgItemMessage(hwnd, IDC_CLIST, CLM_FINDCONTACT, (WPARAM)hContact, 0),
 +					DBGetContactSettingByte(hContact, "FavContacts", "IsFavourite", 0));
 +			}
 +
 +			if (!ServiceExists(MS_HOTKEY_REGISTER))
 +				EnableWindow(GetDlgItem(hwnd, IDC_BTN_HOTKEYS), FALSE);
 +
 +			bInitialized = true;
 +
 +			PostMessage(hwnd, WM_APP, 0, 0);
 +
 +			return TRUE;
 +		}
 +
 +		case WM_APP:
 +		{
 +			BOOL bGroups = IsDlgButtonChecked(hwnd, IDC_CHK_GROUPS);
 +			EnableWindow(GetDlgItem(hwnd, IDC_CHK_GROUPCOLUMS), bGroups);
 +
 +			BOOL bAvatars = IsDlgButtonChecked(hwnd, IDC_CHK_AVATARS);
 +			BOOL bBorders = IsDlgButtonChecked(hwnd, IDC_CHK_AVATARBORDER);
 +			EnableWindow(GetDlgItem(hwnd, IDC_CHK_AVATARBORDER), bAvatars);
 +			EnableWindow(GetDlgItem(hwnd, IDC_CHK_RIGHTAVATARS), bAvatars);
 +			EnableWindow(GetDlgItem(hwnd, IDC_CHK_NOTRANSPARENTBORDER), bAvatars && bBorders);
 +			EnableWindow(GetDlgItem(hwnd, IDC_TXT_RADIUS), bAvatars && bBorders);
 +			return TRUE;
 +		}
 +
 +		case WM_DRAWITEM:
 +		{
 +			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
 +			if (lpdis->CtlID == IDC_CANVAS)
 +			{
 +				MEASUREITEMSTRUCT mis = {0};
 +				DRAWITEMSTRUCT dis = *lpdis;
 +
 +				FillRect(lpdis->hDC, &lpdis->rcItem, GetSysColorBrush(COLOR_BTNFACE));
 +				if (hSelectedContact)
 +				{
 +					Options options;
 +					options.bSecondLine = IsDlgButtonChecked(hwnd, IDC_CHK_SECONDLINE);
 +					options.bAvatars = IsDlgButtonChecked(hwnd, IDC_CHK_AVATARS);
 +					options.bAvatarBorder = IsDlgButtonChecked(hwnd, IDC_CHK_AVATARBORDER);
 +					options.bNoTransparentBorder = IsDlgButtonChecked(hwnd, IDC_CHK_NOTRANSPARENTBORDER);
 +					options.bSysColors = IsDlgButtonChecked(hwnd, IDC_CHK_SYSCOLORS);
 +					options.bCenterHotkey = IsDlgButtonChecked(hwnd, IDC_CHK_CENTERHOTKEY);
 +					options.bRightAvatars = IsDlgButtonChecked(hwnd, IDC_CHK_RIGHTAVATARS);
 +					options.bDimIdle = IsDlgButtonChecked(hwnd, IDC_CHK_DIMIDLE);
 +					options.wAvatarRadius = GetDlgItemInt(hwnd, IDC_TXT_RADIUS, NULL, FALSE);
 +					options.wMaxRecent = GetDlgItemInt(hwnd, IDC_TXT_MAXRECENT, NULL, FALSE);
 +
 +					mis.CtlID = 0;
 +					mis.CtlType = ODT_MENU;
 +					mis.itemData = (DWORD)hSelectedContact;
 +					sttMeasureItem(&mis, &options);
 +					dis.rcItem.bottom = dis.rcItem.top + mis.itemHeight;
 +
 +					dis.CtlID = 0;
 +					dis.CtlType = ODT_MENU;
 +					dis.itemData = (DWORD)hSelectedContact;
 +					sttDrawItem(&dis, &options);
 +
 +					RECT rc = lpdis->rcItem;
 +					rc.bottom = rc.top + mis.itemHeight;
 +					FrameRect(lpdis->hDC, &rc, GetSysColorBrush(COLOR_HIGHLIGHT));
 +				}
 +
 +				return TRUE;
 +			}
 +			return FALSE;
 +		}
 +
 +		case WM_COMMAND:
 +		{
 +			switch (LOWORD(wParam))
 +			{
 +				case IDC_CHK_SECONDLINE:
 +				case IDC_CHK_AVATARS:
 +				case IDC_CHK_AVATARBORDER:
 +				case IDC_CHK_NOTRANSPARENTBORDER:
 +				case IDC_CHK_SYSCOLORS:
 +				case IDC_CHK_CENTERHOTKEY:
 +				case IDC_CHK_GROUPS:
 +				case IDC_CHK_GROUPCOLUMS:
 +				case IDC_CHK_RIGHTAVATARS:
 +					SendMessage(GetParent(hwnd), PSM_CHANGED, 0, 0);
 +					RedrawWindow(GetDlgItem(hwnd, IDC_CANVAS), NULL, NULL, RDW_INVALIDATE);
 +					PostMessage(hwnd, WM_APP, 0, 0);
 +					break;
 +
 +				case IDC_BTN_HOTKEYS:
 +					if (ServiceExists(MS_HOTKEY_REGISTER))
 +					{
 +						sttActivateOptionsPage(hwnd, _T("Customize"), _T("Hotkeys"));
 +					}
 +					break;
 +
 +				case IDC_BTN_FONTS:
 +					sttActivateOptionsPage(hwnd, _T("Customize"), _T("Fonts"));
 +					break;
 +
 +				case IDC_TXT_RADIUS:
 +					if ((HIWORD(wParam) == EN_CHANGE) && bInitialized)
 +					{
 +						RedrawWindow(GetDlgItem(hwnd, IDC_CANVAS), NULL, NULL, RDW_INVALIDATE);
 +						SendMessage(GetParent(hwnd), PSM_CHANGED, 0, 0);
 +					}
 +					break;
 +
 +				case IDC_TXT_MAXRECENT:
 +					if ((HIWORD(wParam) == EN_CHANGE) && bInitialized)
 +						SendMessage(GetParent(hwnd), PSM_CHANGED, 0, 0);
 +					break;
 +			}
 +			break;
 +		}
 +
 +		case WM_NOTIFY:
 +		{
 +			if ((((LPNMHDR)lParam)->idFrom == 0) && (((LPNMHDR)lParam)->code == PSN_APPLY))
 +			{
 +				g_Options.bSecondLine = IsDlgButtonChecked(hwnd, IDC_CHK_SECONDLINE);
 +				g_Options.bAvatars = IsDlgButtonChecked(hwnd, IDC_CHK_AVATARS);
 +				g_Options.bAvatarBorder = IsDlgButtonChecked(hwnd, IDC_CHK_AVATARBORDER);
 +				g_Options.bNoTransparentBorder = IsDlgButtonChecked(hwnd, IDC_CHK_NOTRANSPARENTBORDER);
 +				g_Options.bSysColors = IsDlgButtonChecked(hwnd, IDC_CHK_SYSCOLORS);
 +				g_Options.bCenterHotkey = IsDlgButtonChecked(hwnd, IDC_CHK_CENTERHOTKEY);
 +				g_Options.bUseGroups = IsDlgButtonChecked(hwnd, IDC_CHK_GROUPS);
 +				g_Options.bUseColumns = IsDlgButtonChecked(hwnd, IDC_CHK_GROUPCOLUMS);
 +				g_Options.bRightAvatars = IsDlgButtonChecked(hwnd, IDC_CHK_RIGHTAVATARS);
 +				g_Options.bDimIdle = IsDlgButtonChecked(hwnd, IDC_CHK_DIMIDLE);
 +				g_Options.wAvatarRadius = GetDlgItemInt(hwnd, IDC_TXT_RADIUS, NULL, FALSE);
 +				g_Options.wMaxRecent = GetDlgItemInt(hwnd, IDC_TXT_MAXRECENT, NULL, FALSE);
 +
 +				sttSaveOptions();
 +
 +				HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
 +				for ( ; hContact; hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
 +				{
 +					BYTE fav = SendDlgItemMessage(hwnd, IDC_CLIST, CLM_GETCHECKMARK, 
 +						SendDlgItemMessage(hwnd, IDC_CLIST, CLM_FINDCONTACT, (WPARAM)hContact, 0), 0);
 +					if (fav != DBGetContactSettingByte(hContact, "FavContacts", "IsFavourite", 0))
 +						DBWriteContactSettingByte(hContact, "FavContacts", "IsFavourite", fav);
 +					if (fav) CallService(MS_AV_GETAVATARBITMAP, (WPARAM)hContact, 0);
 +				}
 +			} else
 +			if (((LPNMHDR)lParam)->idFrom == IDC_CLIST)
 +			{
 +				switch (((LPNMHDR)lParam)->code)
 +				{
 +					case CLN_OPTIONSCHANGED:
 +					{
 +						sttResetListOptions(GetDlgItem(hwnd,IDC_CLIST));
 +						break;
 +					}
 +
 +					case CLN_NEWCONTACT:
 +					{
 +						int iSelection = (int)((NMCLISTCONTROL *)lParam)->hItem;
 +						HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
 +						for ( ; hContact; hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
 +							if (SendDlgItemMessage(hwnd, IDC_CLIST, CLM_FINDCONTACT, (WPARAM)hContact, 0) == iSelection)
 +							{
 +								SendDlgItemMessage(hwnd, IDC_CLIST, CLM_SETCHECKMARK, iSelection,
 +									DBGetContactSettingByte(hContact, "FavContacts", "IsFavourite", 0));
 +								break;
 +							}
 +						break;
 +					}
 +
 +					case CLN_CHECKCHANGED:
 +					{
 +						int iSelection = (int)((NMCLISTCONTROL *)lParam)->hItem;
 +						HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
 +						for ( ; hContact; hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
 +							if (SendDlgItemMessage(hwnd, IDC_CLIST, CLM_FINDCONTACT, (WPARAM)hContact, 0) == iSelection)
 +								break;
 +						if (hContact)
 +						{
 +							hSelectedContact = hContact;
 +							RedrawWindow(GetDlgItem(hwnd, IDC_CANVAS), NULL, NULL, RDW_INVALIDATE);
 +						}
 +						SendMessage(GetParent(hwnd), PSM_CHANGED, 0, 0);
 +					}
 +				}
 +			}
 +			break;
 +		}
 +	}
 +
 +	return FALSE;
 +}
  | 
