/*
UserinfoEx plugin for Miranda IM

Copyright:
� 2006-2010 DeathAxe, Yasnovidyashii, Merlin, K. Romanov, Kreol

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.

===============================================================================

File name      : $HeadURL: https://userinfoex.googlecode.com/svn/trunk/dlg_anniversarylist.cpp $
Revision       : $Revision: 202 $
Last change on : $Date: 2010-09-24 23:46:57 +0400 (Пт, 24 сен 2010) $
Last change by : $Author: ing.u.horn $

===============================================================================
*/

/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "svc_gender.h"
#include "svc_reminder.h"
#include "dlg_anniversarylist.h"

#include "m_message.h"
#include "m_email.h"

#define IsLeap(wYear)	(!(((wYear) % 4 != 0) || (((wYear) % 100 == 0) && ((wYear) % 400 != 0))))

class CAnnivList;

static CAnnivList *gpDlg = NULL;

/***********************************************************************************************************
 * class CAnnivList
 ***********************************************************************************************************/

class CAnnivList
{
	HWND		_hDlg;
	HWND		_hList;
	SIZE		_sizeMin;
	RECT		_rcWin;
	SHORT		_sortOrder;
	INT			_sortHeader;
	INT			_curSel;
	INT			_numRows;
	BYTE		_bRemindEnable;
	HANDLE		_mHookExit;
	bool		_wmINIT;

	typedef INT (CALLBACK* CMPPROC)(LPARAM, LPARAM	LPARAM);

	enum EColumn 
	{
		COLUMN_ETA = 0,
		COLUMN_CONTACT,
		COLUMN_PROTO,
		COLUMN_AGE,
		COLUMN_DESC,
		COLUMN_DATE,
	};

	enum EFilter 
	{
		FILTER_ALL = 0,
		FILTER_BIRTHDAY,
		FILTER_ANNIV,
		FILTER_DISABLED_REMINDER
	};

	struct CFilter 
	{
		WORD	wDaysBefore;
		BYTE	bFilterIndex;
		LPSTR	pszProto;
		LPTSTR	pszAnniv;

		CFilter() 
		{
			wDaysBefore		= (WORD)-1;
			bFilterIndex	= 0;
			pszProto		= NULL;
			pszAnniv		= NULL;
		}
	} _filter;

	struct CItemData 
	{
		HANDLE		_hContact;
		MAnnivDate*	_pDate;
		WORD		_wDaysBefore;
		BYTE		_wReminderState;

		CItemData(HANDLE hContact, MAnnivDate &date) 
		{
			_hContact = hContact;
			_wReminderState = date.RemindOption();
			_wDaysBefore = date.RemindOffset();
			_pDate = new MAnnivDate(date);
		}

		~CItemData() 
		{
			if (_pDate) 
			{
				// save changes
				if (_wReminderState != _pDate->RemindOption() || _wDaysBefore != _pDate->RemindOffset()) 
				{
					_pDate->RemindOffset(_wDaysBefore);
					_pDate->RemindOption(_wReminderState);
					_pDate->DBWriteReminderOpts(_hContact);
				}
				delete _pDate;
				_pDate = NULL;
			}
		}
	};

	/**
	 * This class handles the movement of child controls on size change.
	 **/
	class CAnchor 
	{
	public:
		enum EAnchor
		{
			ANCHOR_LEFT		= 1,
			ANCHOR_RIGHT	= 2,
			ANCHOR_TOP		= 4,
			ANCHOR_BOTTOM	= 8,
			ANCHOR_ALL		= ANCHOR_LEFT | ANCHOR_RIGHT | ANCHOR_TOP | ANCHOR_BOTTOM
		};

	private:
		WINDOWPOS*	_wndPos;
		HDWP		_hdWnds;
		RECT		_rcParent;

		VOID _ScreenToClient(HWND hWnd, LPRECT rc) 
		{
			POINT pt = { rc->left, rc->top };
			
			ScreenToClient(hWnd, &pt);
			rc->right	+= pt.x - rc->left;
			rc->bottom	+= pt.y - rc->top;
			rc->left	 = pt.x;
			rc->top		 = pt.y;
		}

		VOID _MoveWindow(HWND hWnd, INT anchors) 
		{
			if (!(_wndPos->flags & SWP_NOSIZE)) 
			{
				RECT rcc = _CalcPos(hWnd, anchors);
				MoveWindow(hWnd, rcc.left, rcc.top, rcc.right - rcc.left, rcc.bottom - rcc.top, FALSE);
			}
		}

		RECT _CalcPos(HWND hWnd, INT anchors) 
		{
			RECT rcc;

			GetWindowRect(hWnd, &rcc);
			_ScreenToClient(_wndPos->hwnd, &rcc);
			if (!(_wndPos->flags & SWP_NOSIZE)) 
			{
				// calculate difference between new and old size
				const INT cx = _wndPos->cx - _rcParent.right + _rcParent.left;
				const INT cy = _wndPos->cy - _rcParent.bottom + _rcParent.top;

				if (cx != 0 || cy != 0) 
				{
					// move client rect points to the desired new position
					if (!(anchors & ANCHOR_LEFT) || (anchors & ANCHOR_RIGHT))
					{
						rcc.right += cx;
					}
					if (!(anchors & ANCHOR_TOP) || (anchors & ANCHOR_BOTTOM))
					{
						rcc.bottom += cy;
					}
					if ((anchors & ANCHOR_RIGHT) && (!(anchors & ANCHOR_LEFT)))
					{
						rcc.left += cx;
					}
					if ((anchors & ANCHOR_BOTTOM) && (!(anchors & ANCHOR_TOP)))
					{
						rcc.top += cy;
					}
				}
			}
			return rcc;
		}

	public:
		CAnchor(WINDOWPOS* wndPos, SIZE minSize) 
		{
			GetWindowRect(wndPos->hwnd, &_rcParent);
			if (wndPos->cx < minSize.cx) {
				wndPos->cx = minSize.cx;
			}
			if (wndPos->cy < minSize.cy) {
				wndPos->cy = minSize.cy;
			}
			_wndPos = wndPos;
			_hdWnds = BeginDeferWindowPos(2);
		}

		~CAnchor() 
		{
			EndDeferWindowPos(_hdWnds);
		}

		VOID MoveCtrl(WORD idCtrl, INT anchors) 
		{
			if (!(_wndPos->flags & SWP_NOSIZE)) 
			{
				HWND hCtrl = GetDlgItem(_wndPos->hwnd, idCtrl);
				RECT rcc = _CalcPos(hCtrl, anchors);
				_hdWnds = DeferWindowPos(
						_hdWnds,					//HDWP hWinPosInfo
						hCtrl,						//HWND hWnd
						HWND_NOTOPMOST,				//hWndInsertAfter
						rcc.left,					//int x
						rcc.top,					//int y
						rcc.right  - rcc.left,
						rcc.bottom - rcc.top,
						SWP_NOZORDER				//UINT uFlags
						);
			}
		}
	};

	/**
	 * This compare function is used by ListView_SortItemsEx to sort the listview.
	 *
	 * @param		iItem1	- index of the first item
	 * @param		iItem2	- index of the second item
	 * @param		pDlg	- pointer to the class' object
	 *
	 * @return	This function returns a number indicating comparison result.
	 **/
	static INT CALLBACK cmpProc(INT iItem1, INT iItem2, CAnnivList* pDlg)
	{
		INT result;

		if (pDlg) {
			TCHAR szText1[MAX_PATH];
			TCHAR szText2[MAX_PATH];

			szText1[0] = szText2[0] = 0;
			switch (pDlg->_sortHeader) {
				case COLUMN_CONTACT:
				case COLUMN_PROTO:
				case COLUMN_DESC:
					{
						ListView_GetItemText(pDlg->_hList, iItem1, pDlg->_sortHeader, szText1, MAX_PATH);
						ListView_GetItemText(pDlg->_hList, iItem2, pDlg->_sortHeader, szText2, MAX_PATH);
						result = pDlg->_sortOrder * mir_tcscmp(szText1, szText2);
					} break;

				case COLUMN_AGE:
				case COLUMN_ETA:
					{
						ListView_GetItemText(pDlg->_hList, iItem1, pDlg->_sortHeader, szText1, MAX_PATH);
						ListView_GetItemText(pDlg->_hList, iItem2, pDlg->_sortHeader, szText2, MAX_PATH);
						result = pDlg->_sortOrder * (_ttoi(szText1) - _ttoi(szText2));
					} break;

				case COLUMN_DATE: 
					{
						CItemData	*id1 = pDlg->ItemData(iItem1),
									*id2 = pDlg->ItemData(iItem2);

						if (PtrIsValid(id1) && PtrIsValid(id2)) {
							result = pDlg->_sortOrder * id1->_pDate->Compare(*id2->_pDate);
							break;
						}
					}
				default:
					{
						result = 0;
					}
			}
		}
		else {
			result = 0;
		}
		return result;
	}

	/**
	 * This static method is the window procedure for the dialog.
	 *
	 * @param		hDlg	- handle of the dialog window
	 * @param		uMsg	- message to handle
	 * @param		wParam	- message dependend parameter
	 * @param		lParam	- message dependend parameter
	 *
	 * @return	depends on message
	 **/
	static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) 
	{
		CAnnivList *pDlg = (CAnnivList*)GetUserData(hDlg);
		
		switch (uMsg) 
		{
		case WM_INITDIALOG:
			{
				INT i = 0;
				HWND hCtrl;
				HICON hIcon;
				RECT rc;

				// link the class to the window handle
				pDlg = (CAnnivList*)lParam;
				if (!pDlg) {
					break;
				}
				SetUserData(hDlg, lParam);
				pDlg->_hDlg = hDlg;

				// init pointer listview control
				pDlg->_hList = GetDlgItem(hDlg, EDIT_ANNIVERSARY_DATE);
				if (!pDlg->_hList) {
					break;
				}

				// set icons
				hIcon = IcoLib_GetIcon(ICO_DLG_ANNIVERSARY);
				SendDlgItemMessage(hDlg, IDC_HEADERBAR, WM_SETICON, 0, (LPARAM)hIcon);
				SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);

				// insert columns into the listboxes
				ListView_SetExtendedListViewStyle(pDlg->_hList, LVS_EX_FULLROWSELECT);

				// add columns
				if (pDlg->AddColumn(CAnnivList::COLUMN_ETA, LPGENT("ETA"), 40) ||
						pDlg->AddColumn(CAnnivList::COLUMN_CONTACT, LPGENT("Contact"), 160) ||
						pDlg->AddColumn(CAnnivList::COLUMN_PROTO, LPGENT("Proto"), 50) ||
						pDlg->AddColumn(CAnnivList::COLUMN_AGE, LPGENT("Age/Nr."), 40) ||
						pDlg->AddColumn(CAnnivList::COLUMN_DESC, LPGENT("Anniversary"), 100) ||
						pDlg->AddColumn(CAnnivList::COLUMN_DATE, LPGENT("Date"), 80)) 
				{
					break;
				}

				TranslateDialogDefault(hDlg);
				
				// save minimal size
				GetWindowRect(hDlg, &rc);
				pDlg->_sizeMin.cx = rc.right - rc.left;
				pDlg->_sizeMin.cy = rc.bottom - rc.top;
				
				// restore position and size
				Utils_RestoreWindowPosition(hDlg, NULL, MODNAME, "AnnivDlg_");

				//save win pos
				GetWindowRect(hDlg, &pDlg->_rcWin);

				// add filter strings
				if (hCtrl = GetDlgItem(hDlg, COMBO_VIEW)) {
					ComboBox_AddString(hCtrl, TranslateT("All contacts"));
					ComboBox_AddString(hCtrl, TranslateT("Birthdays only"));
					ComboBox_AddString(hCtrl, TranslateT("Anniversaries only"));
					ComboBox_AddString(hCtrl, TranslateT("Disabled reminder"));
					ComboBox_SetCurSel(hCtrl, pDlg->_filter.bFilterIndex);
				}

				// init reminder groups
				pDlg->_bRemindEnable = DB::Setting::GetByte(SET_REMIND_ENABLED, DEFVAL_REMIND_ENABLED) != REMIND_OFF;
				if (hCtrl = GetDlgItem(hDlg, CHECK_REMIND)) {
					Button_SetCheck(hCtrl, pDlg->_bRemindEnable ? BST_INDETERMINATE : BST_UNCHECKED);
					EnableWindow(hCtrl, pDlg->_bRemindEnable);
				}

				CheckDlgButton(hDlg, CHECK_POPUP, DB::Setting::GetByte(SET_ANNIVLIST_POPUP, FALSE));
				// set number of days to show contact in advance
				SetDlgItemInt(hDlg, EDIT_DAYS, pDlg->_filter.wDaysBefore , FALSE);
				if (hCtrl = GetDlgItem(hDlg, CHECK_DAYS)) {
					Button_SetCheck(hCtrl, DB::Setting::GetByte(SET_ANNIVLIST_FILTER_DAYSENABLED, FALSE));
					DlgProc(hDlg, WM_COMMAND, MAKEWPARAM(CHECK_DAYS, BN_CLICKED), (LPARAM)hCtrl);
				}

				pDlg->_wmINIT = false;
			}
			return TRUE;

		/**
		 * set propertysheet page's background white in aero mode
		 **/
		case WM_CTLCOLORSTATIC:
		case WM_CTLCOLORDLG:
			if (IsAeroMode())
				return (INT_PTR)GetStockBrush(WHITE_BRUSH);
			break;

		case WM_NOTIFY:
			{
				switch (((LPNMHDR)lParam)->idFrom) 
				{
				case EDIT_ANNIVERSARY_DATE:
					{
						switch (((LPNMHDR)lParam)->code) 
						{
						/*
						 * handle changed selection
						 */
						case LVN_ITEMCHANGED:
							{
								CItemData* pid;
								HWND hCheck;

								pDlg->_curSel = ((LPNMLISTVIEW)lParam)->iItem;
								pid = pDlg->ItemData(pDlg->_curSel);
								if (pid && pDlg->_bRemindEnable && (hCheck = GetDlgItem(hDlg, CHECK_REMIND))) {
									SetDlgItemInt(hDlg, EDIT_REMIND, pid->_wDaysBefore, FALSE);
									Button_SetCheck(hCheck, pid->_wReminderState);
									DlgProc(hDlg, WM_COMMAND, MAKEWPARAM(CHECK_REMIND, BN_CLICKED), (LPARAM)hCheck);
								}
							} break;

						/*
						 * resort the list
						 */
						case LVN_COLUMNCLICK:
							{
								LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;

								if (pDlg->_sortHeader == pnmv->iSubItem) {
									pDlg->_sortOrder *= -1;
								}
								else {
									pDlg->_sortOrder = 1;
									pDlg->_sortHeader = pnmv->iSubItem;
								}
								ListView_SortItemsEx(pDlg->_hList, (CMPPROC)cmpProc, pDlg);
							} break;

						/*
						 * show contact menu
						 */
						case NM_RCLICK:
							{
								CItemData* pid = pDlg->ItemData(pDlg->_curSel);
								if (pid) {
									HMENU hPopup = (HMENU) CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM) pid->_hContact, 0);
									if (hPopup) {
										POINT pt;
										GetCursorPos(&pt);
										TrackPopupMenu(hPopup, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hDlg, NULL);
										DestroyMenu(hPopup);
									}
								}
							} break;

						/*
						 * handle double click on contact: show message dialog
						 */
						case NM_DBLCLK:
							{
								CItemData* pid = pDlg->ItemData(((LPNMITEMACTIVATE)lParam)->iItem);
								if (pid) {
									CallService(MS_MSG_SENDMESSAGE,(WPARAM)pid->_hContact, NULL);
								}
							}
						}
					}
				}
			} break;

		case WM_COMMAND:
			{
				if (PtrIsValid(pDlg)) {
					CItemData* pid = pDlg->ItemData(pDlg->_curSel);

					// process contact menu command
					if (pid && CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam), MPCF_CONTACTMENU), (LPARAM)pid->_hContact)) {
						break;
					}

					switch (LOWORD(wParam))
					{

					/*
					 * enable/disable reminder checkbox is clicked
					 */
					case CHECK_REMIND:
						{
							if (pDlg->_bRemindEnable && HIWORD(wParam) == BN_CLICKED) {
								BOOLEAN checkState = Button_GetCheck((HWND)lParam);

								EnableWindow(GetDlgItem(hDlg, EDIT_REMIND), checkState == BST_CHECKED);
								EnableWindow(GetDlgItem(hDlg, SPIN_REMIND), checkState == BST_CHECKED);
								EnableWindow(GetDlgItem(hDlg, TXT_REMIND5), checkState == BST_CHECKED);
								if (pid && pid->_wReminderState != checkState) {
									pid->_wReminderState = checkState;
								}
							}
						} break;

					/*
					 * number of days to remind in advance is edited
					 */
					case EDIT_REMIND:
						{
							if (pid && pDlg->_bRemindEnable && HIWORD(wParam) == EN_CHANGE) {
								WORD wDaysBefore = GetDlgItemInt(hDlg, LOWORD(wParam), NULL, FALSE);
								if (pid->_wReminderState == BST_CHECKED && pid->_wDaysBefore != wDaysBefore) {
									pid->_wDaysBefore = wDaysBefore;
								}
							}
						} break;

					/*
					 * the filter to display only contacts which have an anniversary in a certain 
					 * period of time is enabled/disabled
					 */
					case CHECK_DAYS:
						{
							if (HIWORD(wParam) == BN_CLICKED) {
								BOOLEAN isChecked = Button_GetCheck((HWND)lParam);
								EnableWindow(GetDlgItem(hDlg, EDIT_DAYS), isChecked);
								EnableWindow(GetDlgItem(hDlg, TXT_DAYS), isChecked);
								pDlg->_filter.wDaysBefore = isChecked ? GetDlgItemInt(hDlg, EDIT_DAYS, NULL, FALSE) : (WORD)-1;
								pDlg->RebuildList();
							}
						} break;

					/*
					 * the number of days a contact must have an anniversary in advance to be displayed is edited
					 */
					case EDIT_DAYS:
						{
							if (HIWORD(wParam) == EN_CHANGE) {
								WORD wNewDays = GetDlgItemInt(hDlg, LOWORD(wParam), NULL, FALSE);
								if (wNewDays != pDlg->_filter.wDaysBefore) {
									pDlg->_filter.wDaysBefore = wNewDays;
									pDlg->RebuildList();
								}
							}
						} break;

					/*
					 * the filter selection of the filter combobox has changed
					 */
					case COMBO_VIEW:
						{
							if (HIWORD(wParam) == CBN_SELCHANGE) {
								pDlg->_filter.bFilterIndex = ComboBox_GetCurSel((HWND)lParam);
								pDlg->RebuildList();
							}
						}
					}
				}
			} break;

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

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

		case WM_WINDOWPOSCHANGING:
			{
				if (PtrIsValid(pDlg)) {
					WINDOWPOS* wndPos = (WINDOWPOS*)lParam;
					if (!pDlg->_wmINIT && (wndPos->cx != 0 || wndPos->cy != 0)) {
						//win pos change
						if ( (wndPos->cx == pDlg->_rcWin.right  - pDlg->_rcWin.left) && 
							(wndPos->cy == pDlg->_rcWin.bottom - pDlg->_rcWin.top)) {
							//win pos change (store new pos)
							GetWindowRect(hDlg, &pDlg->_rcWin);
						}
						//win size change
						else {
							// l change
							if (	(wndPos->cx < pDlg->_sizeMin.cx) && (wndPos->x > pDlg->_rcWin.left)) {
								wndPos->x  = wndPos->x + wndPos->cx - pDlg->_sizeMin.cx;
								wndPos->cx = pDlg->_sizeMin.cx;
							}
							// r change
							else if (wndPos->cx < pDlg->_sizeMin.cx) {
								wndPos->cx = pDlg->_sizeMin.cx;
							}

							// t change
							if (	(wndPos->cy < pDlg->_sizeMin.cy) && (wndPos->y > pDlg->_rcWin.top)) {
								wndPos->y  = wndPos->y + wndPos->cy - pDlg->_sizeMin.cy;
								wndPos->cy = pDlg->_sizeMin.cy;
							}
							// b change
							else if (wndPos->cy < pDlg->_sizeMin.cy) {
								wndPos->cy = pDlg->_sizeMin.cy;
							}

							pDlg->_rcWin.left	= wndPos->x;
							pDlg->_rcWin.right	= wndPos->x + wndPos->cx;
							pDlg->_rcWin.top	= wndPos->y;
							pDlg->_rcWin.bottom	= wndPos->y + wndPos->cy;
						}
					}

					CAnchor anchor(wndPos, pDlg->_sizeMin);
					INT anchorPos = CAnchor::ANCHOR_LEFT | CAnchor::ANCHOR_RIGHT | CAnchor::ANCHOR_TOP;

					anchor.MoveCtrl(IDC_HEADERBAR, anchorPos);
					anchor.MoveCtrl(GROUP_STATS, anchorPos);

					// birthday list
					anchor.MoveCtrl(EDIT_ANNIVERSARY_DATE, CAnchor::ANCHOR_ALL);

					anchorPos = CAnchor::ANCHOR_RIGHT | CAnchor::ANCHOR_BOTTOM;

					// filter group
					anchor.MoveCtrl(GROUP_FILTER, anchorPos);
					anchor.MoveCtrl(COMBO_VIEW, anchorPos);
					anchor.MoveCtrl(CHECK_DAYS, anchorPos);
					anchor.MoveCtrl(EDIT_DAYS, anchorPos);
					anchor.MoveCtrl(TXT_DAYS, anchorPos);

					// filter group
					anchor.MoveCtrl(GROUP_REMINDER, anchorPos);
					anchor.MoveCtrl(CHECK_REMIND, anchorPos);
					anchor.MoveCtrl(EDIT_REMIND, anchorPos);
					anchor.MoveCtrl(SPIN_REMIND, anchorPos);
					anchor.MoveCtrl(TXT_REMIND6, anchorPos);
					anchor.MoveCtrl(CHECK_POPUP, anchorPos);
				}
			} break;

		/**
		 * This message is sent if eighter the user clicked on the close button or
		 * Miranda fires the ME_SYSTEM_SHUTDOWN event.
		 **/
		case WM_CLOSE:
			{
				DestroyWindow(hDlg);
			} break;

		/**
		 * If the anniversary list is destroyed somehow, the data class must be
		 * deleted, too.
		 **/
		case WM_DESTROY:
			{
				if (PtrIsValid(pDlg)) {
					SetUserData(hDlg, NULL);
					delete pDlg;
				}
				MagneticWindows_RemoveWindow(hDlg);
			} break;
		}
		return FALSE;
	}

	/**
	 * This method adds a column to the listview.
	 *
	 * @param	iSubItem		- desired column index
	 * @param	pszText			- the header text
	 * @param	defaultWidth	- the default witdth
	 *
	 * @retval	0 if successful
	 * @retval	1 if failed
	 **/
	BOOLEAN AddColumn(INT iSubItem, LPCTSTR pszText, INT defaultWidth) 
	{
		LVCOLUMN lvc;
		CHAR pszSetting[MAXSETTING];

		mir_snprintf(pszSetting, SIZEOF(pszSetting), "AnnivDlg_Col%d", iSubItem);
		lvc.cx = DB::Setting::GetWord(pszSetting, defaultWidth);
		lvc.mask = LVCF_WIDTH|LVCF_TEXT;
		lvc.iSubItem = iSubItem;
		lvc.pszText = TranslateTS(pszText);
		return ListView_InsertColumn(_hList, lvc.iSubItem++, &lvc) == -1;
	}

	/**
	 * This method sets the subitem text for the current contact
	 *
	 * @param	iItem			- index of the current row
	 * @param	iSubItem		- column to set text for
	 * @param	pszText			- text to insert
	 *
	 * @retval	TRUE if successful
	 * @retval	FALSE if failed
	 **/
	BOOLEAN AddSubItem(INT iItem, INT iSubItem, LPTSTR pszText) 
	{
		LVITEM lvi;
		if (iSubItem > 0) 
		{
			lvi.iItem = iItem;
			lvi.iSubItem = iSubItem;
			lvi.pszText = pszText;
			lvi.mask = LVIF_TEXT;
			return ListView_SetItem(_hList, &lvi);
		}
		return FALSE;
	}

	/**
	 * This method adds a row to the listview.
	 *
	 * @param	pszText			- text to insert
	 * @param	lParam			- pointer to the rows data
	 *
	 * @retval	TRUE if successful
	 * @retval	FALSE if failed
	 **/	
	BOOLEAN AddItem(LPTSTR pszText, LPARAM lParam)
	{
		LVITEM lvi;

		if (!pszText) {
			return FALSE;
		}
		lvi.iItem = 0;
		lvi.iSubItem = 0;
		lvi.pszText = pszText;
		lvi.mask = LVIF_TEXT|TVIF_PARAM;
		lvi.lParam = lParam;
		return ListView_InsertItem(_hList, &lvi);
	}

	/**
	 * This method adds a row to the listview.
	 *
	 * @param	hContact		- contact to add the line for
	 * @param	pszProto		- contact's protocol
	 * @param	ad				- anniversary to add
	 * @param	mtNow			- current time
	 * @param	wDaysBefore		- number of days in advance to remind the user of the anniversary
	 *
	 * @retval	TRUE if successful
	 * @retval	FALSE if failed
	 **/
	BOOLEAN AddRow(HANDLE hContact, LPCSTR pszProto, MAnnivDate &ad, MTime &mtNow, WORD wDaysBefore) 
	{
		TCHAR szText[MAX_PATH];
		INT diff, iItem = -1;
		CItemData *pdata;
	
		//
		// first column: ETA
		//
		diff = ad.CompareDays(mtNow);
		if (diff < 0) 
		{
			diff += IsLeap(mtNow.Year() + 1) ? 366 : 365;
		}
		// is filtered
		if (diff <= _filter.wDaysBefore) 
		{
			// read reminder options for the contact
			ad.DBGetReminderOpts(hContact);
			if ((_filter.bFilterIndex != FILTER_DISABLED_REMINDER) || (ad.RemindOption() == BST_UNCHECKED))
			{
				// set default offset if required
				if (ad.RemindOffset() == (WORD)-1)
				{
					ad.RemindOffset(wDaysBefore);
					
					// create data object
					pdata = new CItemData(hContact, ad);
					if (!pdata) 
					{
						return FALSE;
					}
					// add item
					iItem = AddItem(_itot(diff, szText, 10), (LPARAM)pdata);
					if (iItem == -1) 
					{
						delete pdata;
						return FALSE;
					}

					// second column: contact name
					AddSubItem(iItem, COLUMN_CONTACT, DB::Contact::DisplayName(hContact));

					// third column: protocol
					TCHAR* ptszProto = mir_a2t(pszProto);
					AddSubItem(iItem, COLUMN_PROTO, ptszProto);
					mir_free(ptszProto);

					// forth line: age
					AddSubItem(iItem, COLUMN_AGE, _itot(ad.Age(&mtNow), szText, 10));

					// fifth line: anniversary
					AddSubItem(iItem, COLUMN_DESC, (LPTSTR)ad.Description());

					// sixth line: date
					ad.DateFormat(szText, SIZEOF(szText));
					AddSubItem(iItem, COLUMN_DATE, szText);
					
					_numRows++;
				}
			}
		}
		return TRUE;
	}

	/**
	 * This method clears the list and adds contacts again, according to the current filter settings.
	 **/
	VOID RebuildList() 
	{
		HANDLE		hContact;
		LPSTR		pszProto;
		MTime		mtNow;
		MAnnivDate	ad;
		INT			i	= 0;
		DWORD		age	= 0;
		WORD		wDaysBefore = DB::Setting::GetWord(SET_REMIND_OFFSET, DEFVAL_REMIND_OFFSET);
		WORD		numMale				= 0;
		WORD		numFemale			= 0;
		WORD		numContacts			= 0;
		WORD		numBirthContacts	= 0;

		ShowWindow(_hList, SW_HIDE);
		DeleteAllItems();
		mtNow.GetLocalTime();

		// insert the items into the list
		for (hContact = DB::Contact::FindFirst();
				hContact;
				hContact = DB::Contact::FindNext(hContact))
		{
			// ignore meta subcontacts here, as they are not interesting.
			if (!DB::MetaContact::IsSub(hContact)) {
				// filter protocol
				pszProto = DB::Contact::Proto(hContact);
				if (pszProto) {
					numContacts++;
					switch (GenderOf(hContact, pszProto))
					{
					case 'M': {
							numMale++;
						} break;

					case 'F': {
							numFemale++; 
						}
					}

					if (!ad.DBGetBirthDate(hContact, pszProto)) {
						age += ad.Age(&mtNow);
						numBirthContacts++;

						// add birthday
						if ((_filter.bFilterIndex != FILTER_ANNIV) && (!_filter.pszProto || !strcmpi(pszProto, _filter.pszProto))) {
							AddRow(hContact, pszProto, ad, mtNow, wDaysBefore);
						}
					}

					// add anniversaries
					if (	_filter.bFilterIndex != FILTER_BIRTHDAY && 
							(!_filter.pszProto || !strcmpi(pszProto, _filter.pszProto))) 
					{
						for (i = 0; !ad.DBGetAnniversaryDate(hContact, i); i++) {
							if (!_filter.pszAnniv || !_tcsicmp(_filter.pszAnniv, ad.Description())) {
								AddRow(hContact, pszProto, ad, mtNow, wDaysBefore);
							}
						}
					}
				}
			}
		}
		ListView_SortItemsEx(_hList, (CMPPROC)cmpProc, this);
		ShowWindow(_hList, SW_SHOW);

		// display statistics
		SetDlgItemInt(_hDlg, TXT_NUMBIRTH, numBirthContacts, FALSE);
		SetDlgItemInt(_hDlg, TXT_NUMCONTACT, numContacts, FALSE);
		SetDlgItemInt(_hDlg, TXT_FEMALE, numFemale, FALSE);
		SetDlgItemInt(_hDlg, TXT_MALE, numMale, FALSE);
		SetDlgItemInt(_hDlg, TXT_AGE, numBirthContacts > 0 ? max(0, (age - (age % numBirthContacts)) / numBirthContacts) : 0, FALSE);
	}

	/**
	 * This method deletes all items from the listview
	 **/	
	VOID DeleteAllItems() 
	{
		CItemData *pid;
		
		for (INT i = 0; i < _numRows; i++) 
		{
			pid = ItemData(i);
			if (pid) 
			{
				delete pid;
			}
		}
		ListView_DeleteAllItems(_hList);
		_numRows = 0;
	}

	/**
	 * This method returns the data structure accociated with a list item.
	 *
	 * @param	iItem			- index of the desired item
	 *
	 * @return	pointer to the data strucutre on success or NULL otherwise.
	 **/
	CItemData* ItemData(INT iItem) 
	{
		if (_hList && iItem >= 0 && iItem < _numRows) 
		{
			LVITEM lvi;
		
			lvi.mask = LVIF_PARAM;
			lvi.iItem = iItem;
			lvi.iSubItem = 0;
			if (ListView_GetItem(_hList, &lvi) && PtrIsValid(lvi.lParam))
			{
				return (CItemData*)lvi.lParam;
			}
		}
		return NULL;
	}

	/**
	 * This method loads all filter settings from db
	 **/	
	VOID LoadFilter() 
	{
		_filter.wDaysBefore = DB::Setting::GetWord(SET_ANNIVLIST_FILTER_DAYS, 9);
		_filter.bFilterIndex = DB::Setting::GetByte(SET_ANNIVLIST_FILTER_INDEX, 0);
	}

	/**
	 * This method saves all filter settings to db
	 **/	
	VOID SaveFilter() 
	{
		if (_hDlg) {
			DB::Setting::WriteWord(SET_ANNIVLIST_FILTER_DAYS, (WORD)GetDlgItemInt(_hDlg, EDIT_DAYS, NULL, FALSE));
			DB::Setting::WriteByte(SET_ANNIVLIST_FILTER_DAYSENABLED, (BYTE)Button_GetCheck(GetDlgItem(_hDlg, CHECK_DAYS)));
			DB::Setting::WriteByte(SET_ANNIVLIST_FILTER_INDEX, (BYTE)ComboBox_GetCurSel(GetDlgItem(_hDlg, EDIT_DAYS)));
		}
	}

public:

	/**
	 * This is the default constructor.
	 **/
	CAnnivList() 
	{
		_hList			= NULL;
		_sortHeader		= 0;
		_sortOrder		= 1;
		_curSel			= -1;
		_numRows		= 0;
		_wmINIT			= true;
		_rcWin.left		= _rcWin.right = _rcWin.top = _rcWin.bottom = 0;
		LoadFilter();

		_hDlg = CreateDialogParam(ghInst, MAKEINTRESOURCE(IDD_ANNIVERSARY_LIST), NULL, DlgProc, (LPARAM)this);
		if (_hDlg) {
				MagneticWindows_AddWindow(_hDlg);
				_mHookExit = HookEventMessage(ME_SYSTEM_PRESHUTDOWN, _hDlg, WM_CLOSE);
		}
		else {
			_mHookExit = NULL;
			delete this;
		}
	}

	/**
	 * This is the default destructor.
	 **/
	~CAnnivList() 
	{
		// delete the shutdown hook
		if (_mHookExit) {
			UnhookEvent(_mHookExit);
			_mHookExit = NULL;
		}

		// close window if required
		if (_hDlg) {
			// save list state
			if (_hList) {
				CHAR pszSetting[MAXSETTING];
				INT c, cc = Header_GetItemCount(ListView_GetHeader(_hList));

				for (c = 0; c < cc; c++) {
					mir_snprintf(pszSetting, MAXSETTING, "AnnivDlg_Col%d", c);
					DB::Setting::WriteWord(pszSetting, (WORD)ListView_GetColumnWidth(_hList, c));
				}
				DeleteAllItems();
			}
			// remember popup setting
			DB::Setting::WriteByte(SET_ANNIVLIST_POPUP, (BYTE)IsDlgButtonChecked(_hDlg, CHECK_POPUP));
			// save window position, size and column widths
			Utils_SaveWindowPosition(_hDlg, NULL, MODNAME, "AnnivDlg_");
			SaveFilter();

			// if the window did not yet retrieve a WM_DESTROY message, do it right now.
			if (PtrIsValid(GetUserData(_hDlg))) {
				SetUserData(_hDlg, NULL);
				DestroyWindow(_hDlg);
			}
		}	
		gpDlg = NULL;
	}

	/**
	 * name:	BringToFront
	 * class:	CAnnivList
	 * desc:	brings anniversary list to the top
	 * param:	none
	 * return:	nothing
	 **/
	VOID BringToFront()
	{
		ShowWindow(_hDlg, SW_RESTORE);
		SetForegroundWindow(_hDlg);
		SetFocus(_hDlg);
	}

}; // class CAnnivList

/***********************************************************************************************************
 * service handlers
 ***********************************************************************************************************/

/**
 * This is the service function that is called list all anniversaries.
 *
 * @param	wParam	- not used
 * @param	lParam	- not used
 *
 * @return	always 0
 **/
INT_PTR DlgAnniversaryListShow(WPARAM wParam, LPARAM lParam) 
{
	if (!gpDlg) {
		try 
		{
			myGlobals.WantAeroAdaption = DB::Setting::GetByte(SET_PROPSHEET_AEROADAPTION, TRUE);
			gpDlg = new CAnnivList();
		}
		catch(...) 
		{
			delete gpDlg;
			gpDlg = NULL;
		}
	} 
	else {
		gpDlg->BringToFront();
	}
	return 0;
}

/***********************************************************************************************************
 * loading and unloading module
 ***********************************************************************************************************/

#define TBB_IDBTN		"AnnivList"
#define TBB_ICONAME		TOOLBARBUTTON_ICONIDPREFIX TBB_IDBTN TOOLBARBUTTON_ICONIDPRIMARYSUFFIX

/**
 * This function is called by the ME_TTB_MODULELOADED event.
 * It adds a set of buttons to the TopToolbar plugin.
 *
 * @param	wParam	- none
 *
 * @return	nothing
 **/

VOID DlgAnniversaryListOnTopToolBarLoaded()
{
	TTBButton ttb = {0};
	ttb.cbSize = sizeof(ttb);
	ttb.dwFlags = TTBBF_VISIBLE | TTBBF_SHOWTOOLTIP;
	ttb.pszService = MS_USERINFO_REMINDER_LIST;
	ttb.hIconHandleUp = Skin_GetIconHandle(ICO_COMMON_ANNIVERSARY);
	ttb.name = ttb.pszTooltipUp = LPGEN("Anniversary list");
	TopToolbar_AddButton(&ttb);
}

/**
 * This function initially loads all required stuff for the anniversary list.
 *
 * @param	none
 *
 * @return	nothing
 **/
VOID DlgAnniversaryListLoadModule()
{
	CreateServiceFunction(MS_USERINFO_REMINDER_LIST, DlgAnniversaryListShow);

	HOTKEYDESC hk = { 0 };
	hk.cbSize			= sizeof(HOTKEYDESC);
	hk.pszSection		= MODNAME;
	hk.pszName			= "AnniversaryList";
	hk.pszDescription	= LPGEN("Popup Anniversary list");
	hk.pszService		= MS_USERINFO_REMINDER_LIST;
	Hotkey_Register(&hk);
}