Click here to Skip to main content
15,895,746 members
Articles / Desktop Programming / MFC

Calendar Plug-in for .dan.g.'s ToDoList - In 10 Easy Steps™

Rate me:
Please Sign up or sign in to vote.
4.99/5 (63 votes)
10 Jun 2008CC (ASA 2.5)12 min read 1.1M   7.7K   252  
A Calendar UI extension component for the ToDoList, giving you a timeline view of your tasks
/* PERF__FREZ/PUB
 * ====================================================
 * BigCalendarCtrl.cpp : implementation file
 * Frederic Rezeau
 * 
 * Perf'Control Personal Edition calendar 
 *
 * If you like it, feel free to use it. 
 *
 * www.performlabs.com 
 * ==================================================== 
 * Original file written by Frederic Rezeau (http://www.codeproject.com/KB/miscctrl/CCalendarCtrl.aspx)
 * Rewritten for the ToDoList (http://www.codeproject.com/KB/applications/todolist2.aspx)
 * Design and latest version - http://www.codeproject.com/KB/applications/TDL_Calendar.aspx
 * by demon.code.monkey@googlemail.com
 */

#include "stdafx.h"
#include "BigCalendarCtrl.h"
#include "BigCalendarTask.h"
#include "MemDC.h"
#include "CalendarDefines.h"
#include "CalendarUtils.h"
#include "CalendarData.h"
#include "..\..\CalendarExt\CalendarFrameWnd.h"
#include "..\..\Shared\DateHelper.h"
#include "..\..\Shared\Misc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//fonts
#define BIGCAL_FONT_NAME			"Tahoma"
#define BIGCAL_FONT_SIZE			8

//colours
#define COLOUR_TASKS				RGB(0,0,0)
#define COLOUR_DAYNUMBERS			RGB(70,70,70)
#define COLOUR_WHITE				RGB(255,255,255)
#define COLOUR_BACKGROUND			COLOUR_WHITE
#define COLOUR_INVALID_DATE			RGB(255,225,225)
#define COLOUR_IMPORTANTDAY_MARKER	RGB(255,255,100)
#define COLOUR_GRIDLINES			RGB(125,175,255)
#define COLOUR_SELECTED_DAYNUMBER	COLOUR_DAYNUMBERS
#define COLOUR_MULTIPLE_TASK_ARROW	RGB(170,170,170)
#define COLOUR_MONTHNAMES			RGB(0,0,255)


/////////////////////////////////////////////////////////////////////////////
// CBigCalendarCtrl

CBigCalendarCtrl::CBigCalendarCtrl()
:	m_pFrameWnd(NULL),
	m_pCalendarData(NULL),
	m_pFont(NULL),
	m_pFontBold(NULL),
	m_hwndMessageRoutingWindow(NULL),
	m_bTracking(FALSE),
	m_bMonthIsOdd(FALSE),
	m_nFirstWeekDay(-1),
	m_nSelectedTaskID(-1),
	m_nVscrollMax(100),	//this is enough for a two-year range
	m_nVscrollPos(50)
{
	m_nFirstWeekDay = CDateHelper::FirstDayOfWeek();

	CCalendarUtils::GetToday(m_dateSelected);	//today's date

	m_pFont = new CFont;
	Misc::CreateFont(*m_pFont, BIGCAL_FONT_NAME, BIGCAL_FONT_SIZE);

	m_pFontBold = new CFont;
	Misc::CreateFont(*m_pFontBold, BIGCAL_FONT_NAME, BIGCAL_FONT_SIZE, MFS_BOLD);
}

CBigCalendarCtrl::~CBigCalendarCtrl()
{
	for (POSITION pos = m_listCalendarTasks.GetHeadPosition(); pos != NULL; )
	{
		delete (CBigCalendarTask*)(m_listCalendarTasks.GetNext(pos));
	}
	m_listCalendarTasks.RemoveAll();

	m_pFont->DeleteObject();
	m_pFontBold->DeleteObject();
	delete m_pFont;
	delete m_pFontBold;
}

void CBigCalendarCtrl::Initialize(CCalendarFrameWnd* _pFrameWnd,
								  CCalendarData* _pCalendarData,
								  HWND _hwndMessageRoutingWindow)
{
	m_pFrameWnd = _pFrameWnd;
	m_pCalendarData = _pCalendarData;
	m_hwndMessageRoutingWindow = _hwndMessageRoutingWindow;
}


BEGIN_MESSAGE_MAP(CBigCalendarCtrl, CWnd)
	//{{AFX_MSG_MAP(CBigCalendarCtrl)
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_VSCROLL()
	ON_WM_MOUSEWHEEL()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


BOOL CBigCalendarCtrl::Create(DWORD _dwStyle,
							  const CRect& _rect,
							  CWnd* _pParent,
							  UINT _nID)
{
	BOOL bResult = CWnd::CreateEx(NULL, NULL, NULL, _dwStyle, _rect, _pParent, _nID);

	//this line seems to be needed here, to avoid problems with tooltips only appearing in one listbox
	EnableToolTips();

	SetScrollRange(SB_VERT, 0, m_nVscrollMax, FALSE);

	COleDateTime dtToday;
	CCalendarUtils::GetToday(dtToday);

	if (!CCalendarUtils::IsDateValid(dtToday))
	{
		ASSERT(FALSE);
		return bResult;
	}

	if (dtToday.GetMonth()%2) 
	{
		m_bMonthIsOdd = TRUE;
	}

	if (m_pFrameWnd->IsWeekendsHidden())
	{
		if (dtToday.GetDayOfWeek() == 1)
		{
			//sun
			dtToday -= 2;
		}
		else if (dtToday.GetDayOfWeek() == 7)
		{
			//sat
			dtToday -= 1;
		}
	}

	GotoMonth(dtToday);

	SetScrollPos(SB_VERT, m_nVscrollPos, TRUE);

	CreateTasks();

	return bResult;
}

void CBigCalendarCtrl::Repopulate()
{
	CreateTasks(TRUE);

	TasklistUpdated();

	//ensure that if number of weeks have been changed, current date may not be visible. if so, select it again
	if (!IsDateVisible(m_dateSelected))
	{
		Goto(m_dateSelected, TRUE);
	}
}

void CBigCalendarCtrl::SelectDate(const COleDateTime& _date)
{
	LeaveCell();
	Goto(_date, TRUE);
}

void CBigCalendarCtrl::TasklistUpdated()
{
	//just refresh the data in the view
	COleDateTime dtFirstCell(m_dayCells[0][0].date);	//current first-cell
	RepopulateAllCells(dtFirstCell);

	SendSelectDateMessageToParent();
}

void CBigCalendarCtrl::ClickedOnTaskInListbox()
{
	SendSelectDateMessageToParent();
}

void CBigCalendarCtrl::SelectTask(int _nTaskListboxID,
								  int _nTaskID)
{
	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

	//unselect all listboxes apart from _nTaskListboxID
	for (int i = 0; i < nNumWeeks; i++)
	{
		for (int u = 0; u < nNumColumns; u++)
		{
			if (m_dayCells[i][u].nListboxID == _nTaskListboxID)
			{
				//store the date of the newly selected task
				m_dateSelected = m_dayCells[i][u].date;
			}
			else
			{
				CBigCalendarTask* pWndTask = GetTaskListboxFromCell(i, u);
				if (pWndTask)
				{
					pWndTask->SetCurSel(-1);
				}
			}
		}
	}

	//store the newly selected task ID
	m_nSelectedTaskID = _nTaskID;

	Invalidate();


	//////////////////////////////////////////////////////////////////////////////////////////
	//select the newly selected task in the active tasklist, by sending a SELECTTASK message 
	CWnd* pFocus = GetFocus();

	DWORD dwTaskID = 0;
	CBigCalendarTask* pListbox = GetTaskListboxFromTaskListboxID(_nTaskListboxID);
	if (pListbox)
	{
		dwTaskID = ((SItem*)pListbox->GetItemData(_nTaskID))->dwItemData;
	}

	if (dwTaskID != 0)
	{
		COPYDATASTRUCT cds;
		cds.dwData = 3;//SELECTTASK;	//can't use SELECTTASK because including ToDoListWnd.h would break the build
		cds.cbData = sizeof(DWORD);
		cds.lpData = &dwTaskID;
		::SendMessage(m_hwndMessageRoutingWindow, WM_COPYDATA, NULL, (LPARAM)&cds);
	}
	else
	{
		ASSERT(FALSE);	//task ID not found
	}

	//set focus to the previously focussed window (the active tasklist steals the focus when it gets the SELECTTASK message)
	pFocus->SetFocus();
}

void CBigCalendarCtrl::GetSelectedDate(COleDateTime& _date) const
{
	_date = m_dateSelected;
}

DWORD CBigCalendarCtrl::GetSelectedTask() const
{
	DWORD dwTaskID = 0;

	if (m_nSelectedTaskID != -1)
	{
		CBigCalendarTask* plbTasks = GetTaskListboxFromDate(m_dateSelected);
		if (plbTasks != NULL)
		{
			if (m_nSelectedTaskID < plbTasks->GetCount())
			{
				dwTaskID = ((SItem*)(plbTasks->GetItemData(m_nSelectedTaskID)))->dwItemData;
			}
			else
			{
				ASSERT(FALSE);
			}
		}
	}

	return dwTaskID;
}

BOOL CBigCalendarCtrl::PreTranslateMessage(MSG* pMsg) 
{		
	if( pMsg->message == WM_KEYDOWN )
	{
		COleDateTime dtNew = m_dateSelected;
		switch(pMsg->wParam)
		{
			case VK_ESCAPE:
			{
				if (m_pFrameWnd != NULL)
				{
					m_pFrameWnd->Show(FALSE);
					return TRUE;//no further processing
				}
				break;
			}
			case VK_UP:
			case VK_DOWN:
			case VK_LEFT:
			case VK_RIGHT:
			{
				int nOldScrollPos = m_nVscrollPos;

				int nRow = 0;
				int nCol = 0;
				GetLastSelectedGridCell(nRow, nCol);
				if (!CCalendarUtils::IsDateValid(m_dayCells[nRow][nCol].date))
				{
					ASSERT(FALSE);
					return FALSE;
				}

				int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
				int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

				if (pMsg->wParam == VK_UP)
				{
					//select previous task
					if (m_nSelectedTaskID != -1)
					{
						CBigCalendarTask* plbTasks = GetTaskListboxFromDate(m_dateSelected);
						if (plbTasks != NULL)
						{
							m_nSelectedTaskID--;
							if (m_nSelectedTaskID < 0)
							{
								m_nSelectedTaskID = -1;
							}
							plbTasks->SetCurSel(m_nSelectedTaskID);

							SendSelectDateMessageToParent();
						}
					}

					if (m_nSelectedTaskID == -1)
					{
						//move to cell above
						if (nRow == 0)
						{
							CCalendarUtils::SubtractDay(dtNew, 7);
							if (!CCalendarUtils::IsDateValid(dtNew))
							{
								ASSERT(FALSE);
								return FALSE;
							}

							m_nVscrollPos--;
						}
						else
						{
							nRow--;	
						}
					}
				}
				else if (pMsg->wParam == VK_DOWN)
				{
					//select next task
					if (m_nSelectedTaskID != -1)
					{
						CBigCalendarTask* plbTasks = GetTaskListboxFromDate(m_dateSelected);
						if (plbTasks != NULL)
						{
							m_nSelectedTaskID++;
							if (m_nSelectedTaskID >= plbTasks->GetCount())
							{
								m_nSelectedTaskID = -1;
							}
							plbTasks->SetCurSel(m_nSelectedTaskID);

							SendSelectDateMessageToParent();
						}
					}

					if (m_nSelectedTaskID == -1)
					{
						LeaveCell();

						//move to cell below
						if (nRow == nNumWeeks-1)
						{
							CCalendarUtils::AddDay(dtNew, 7);
							if (!CCalendarUtils::IsDateValid(dtNew))
							{
								ASSERT(FALSE);
								return FALSE;
							}

							m_nVscrollPos++;
						}
						else
						{
							nRow++;
						}
					}
				}
				else if (pMsg->wParam == VK_LEFT)
				{
					LeaveCell();

					//move to previous cell
					if (nCol > 0)
					{
						nCol--;
					}
					else
					{
						nCol = nNumColumns-1;
						if (nRow == 0)
						{
							CCalendarUtils::SubtractDay(dtNew, 7);
							if (!CCalendarUtils::IsDateValid(dtNew))
							{
								ASSERT(FALSE);
								return FALSE;
							}

							m_nVscrollPos--;
						}
						else
						{
							nRow--;
						}
					}
				}
				else //(pMsg->wParam == VK_RIGHT)
				{
					LeaveCell();

					//move to next cell
					if (nCol < nNumColumns-1)
					{
						nCol++;
					}
					else
					{
						nCol = 0;
						if (nRow == nNumWeeks-1)
						{
							CCalendarUtils::AddDay(dtNew, 7);
							if (!CCalendarUtils::IsDateValid(dtNew))
							{
								ASSERT(FALSE);
								return FALSE;
							}

							m_nVscrollPos++;
						}
						else
						{
							nRow++;
						}
					}
				}

				if (nOldScrollPos != m_nVscrollPos)	//scroll pos changed - update scrollpos and goto new date
				{
					SetScrollPos(SB_VERT, m_nVscrollPos, TRUE);
					Goto(dtNew);
				}

				if (!CCalendarUtils::IsDateValid(m_dayCells[nRow][nCol].date))
				{
					ASSERT(FALSE);
					return FALSE;
				}

				m_dateSelected = m_dayCells[nRow][nCol].date;
				Invalidate(TRUE);

				if (m_nSelectedTaskID == -1)	//no need if we still have a task selected
				{
					FireNotifySelectDate();
				}

				return TRUE;
			}
			case VK_NEXT:
			{
				LeaveCell();

				CCalendarUtils::AddMonth(m_dateSelected);
				SelectDate(m_dateSelected);

				FireNotifySelectDate();

				break;
			}
			case VK_PRIOR:
			{
				LeaveCell();

				CCalendarUtils::SubtractMonth(m_dateSelected);
				SelectDate(m_dateSelected);

				FireNotifySelectDate();

				break;
			}
			case VK_RETURN:
			{
				if (m_nSelectedTaskID != -1)
				{
					CBigCalendarTask* plbTasks = GetTaskListboxFromDate(m_dateSelected);
					if (plbTasks != NULL)
					{
						SelectTask(plbTasks->GetDlgCtrlID(), m_nSelectedTaskID);
					}
				}
				break;
			}
			default:
			{
				break;
			}
		}
	}

	return CWnd::PreTranslateMessage(pMsg);
}

BOOL CBigCalendarCtrl::GetGridCellFromPoint(CPoint _point,
											int& _nRow,
											int& _nCol) const
{
	if (_point.y > CALENDAR_HEADER_HEIGHT) 
	{
		int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
		int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

		CRect rc;
		GetClientRect(&rc);
		int nHeight = (rc.Height()-CALENDAR_HEADER_HEIGHT)/nNumWeeks;
		int nWidth = rc.Width()/nNumColumns;
		if(nHeight && nWidth)
		{
			_nRow = (_point.y-CALENDAR_HEADER_HEIGHT)/nHeight;
			_nCol = _point.x/nWidth;	
			if((_nRow>=0) && (_nRow<nNumWeeks) && (_nCol>=0) && (_nCol<nNumColumns))
				return TRUE;
		}
	}
	return FALSE;
}

BOOL CBigCalendarCtrl::GetRectFromCell(int _nRow,
									   int _nCol,
									   CRect& _rect) const
{
	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

	if (_nRow >=0 && _nRow<nNumWeeks && _nCol>=0 && _nCol<nNumColumns)
	{
		CRect rc;
		GetClientRect(&rc);
		int nHeight = (rc.Height()-CALENDAR_HEADER_HEIGHT)/nNumWeeks;
		int nWidth = rc.Width()/nNumColumns;
		_rect.left = nWidth*_nCol;
		_rect.right = _rect.left + nWidth;
		_rect.top = CALENDAR_HEADER_HEIGHT + _nRow*nHeight;
		_rect.bottom = _rect.top + nHeight;
		return TRUE;
	}
	return FALSE;
}

void CBigCalendarCtrl::GetLastSelectedGridCell(int& _nRow,
											   int& _nCol) const
{
	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

	_nRow = 0;
	_nCol = 0;
	for(int i=0; i<nNumWeeks; i++)
	{
		for(int u=0; u<nNumColumns; u++)
		{
			if (m_dateSelected == m_dayCells[i][u].date)
			{
				_nRow = i;
				_nCol = u;
				return;
			}
		}
	}
}

CBigCalendarTask* CBigCalendarCtrl::GetTaskListboxFromTaskListboxID(int _nListboxID) const
{
	CBigCalendarTask* pWndTask = (CBigCalendarTask*)GetDlgItem(_nListboxID);
	return pWndTask;
}

CBigCalendarTask* CBigCalendarCtrl::GetTaskListboxFromCell(int _nRow,
														   int _nCol) const
{
	return GetTaskListboxFromTaskListboxID(m_dayCells[_nRow][_nCol].nListboxID);
}

CBigCalendarTask* CBigCalendarCtrl::GetTaskListboxFromDate(const COleDateTime& _date) const
{
	const CBigCalendarCell* pCell = GetCell(_date);
	if (pCell)
	{
		return GetTaskListboxFromTaskListboxID(pCell->nListboxID);
	}
	return NULL;
}

const CBigCalendarCell* CBigCalendarCtrl::GetCell(const COleDateTime& _date) const
{
	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

	for(int i=0; i<nNumWeeks; i++)
		for(int u=0; u<nNumColumns; u++)
			if((m_dayCells[i][u].date) == _date)
				return &m_dayCells[i][u];
	return NULL;
}

//if date is NOT visible, and _pbAfter is NOT NULL, _pbAfter will contain TRUE if the date is AFTER _date
BOOL CBigCalendarCtrl::IsDateVisible(const COleDateTime& _date,
									 BOOL* _pbAfter/*=NULL*/) const
{
	if (GetCell(_date) == NULL)
	{
		if (_pbAfter != NULL)
		{
			*_pbAfter = FALSE;
			int nLastRow = m_pFrameWnd->GetNumWeeksToDisplay()-1;
			int nLastCol = m_pFrameWnd->GetNumDaysToDisplay()-1;
			if (_date > (m_dayCells[nLastRow][nLastCol].date))
			{
				*_pbAfter = TRUE;
			}
		}
		return FALSE;
	}
	else
	{
		return TRUE;
	}
}

BOOL CBigCalendarCtrl::IsFirstVisibleDayOfMonth(const COleDateTime& _date) const
{
	BOOL bReturn = FALSE;

	int nMonth = _date.GetMonth();
	int nDay = _date.GetDay();
	if (nDay == 1)
	{
		bReturn = TRUE;
	}
	else
	{
		if ((nDay > 0) && (nDay < 4))
		{
			COleDateTime date = _date;
			bReturn = TRUE; //assume TRUE unless found otherwise

			//try date-1
			CCalendarUtils::SubtractDay(date);
			if ((date.GetMonth() == nMonth) && IsDateVisible(date))
			{
				bReturn = FALSE;	//date-1 is in same month and is visible
			}
			else
			{
				//try date-2
				CCalendarUtils::SubtractDay(date);
				if ((date.GetMonth() == nMonth) && IsDateVisible(date))
				{
					bReturn = FALSE;	//date-2 is in same month and is visible
				}
				else
				{
					//try date-3
					CCalendarUtils::SubtractDay(date);
					if ((date.GetMonth() == nMonth) && IsDateVisible(date))
					{
						bReturn = FALSE;	//date-3 is in same month and is visible
					}
				}
			}
		}
	}

	return bReturn;
}

COLORREF CBigCalendarCtrl::GetFadedBlue(unsigned char _percent)
{	
	int r = 180, g = 205, b = 255;
	int al = _percent*75/100;
	return RGB(	(unsigned char)(r + ((255-r)/(float)75) * al), 
				(unsigned char)(g + ((255-g)/(float)75) * al), 
				(unsigned char)(b + ((255-b)/(float)75) * al));
}

void CBigCalendarCtrl::EnterCell()
{
	//select first task in cell, if there is one
	CBigCalendarTask* plbTasks = GetTaskListboxFromDate(m_dateSelected);
	if (plbTasks != NULL)
	{
		if (plbTasks->GetCount() > 0)
		{
			SelectTask(plbTasks->GetDlgCtrlID(), 0);
			plbTasks->SetCurSel(m_nSelectedTaskID);
		}
	}
}

void CBigCalendarCtrl::LeaveCell()
{
	//ensure previously selected task is no longer selected
	CBigCalendarTask* plbTasks = GetTaskListboxFromDate(m_dateSelected);
	if (plbTasks != NULL)
	{
		if (m_nSelectedTaskID != -1)
		{
			m_nSelectedTaskID = -1;
			plbTasks->SetCurSel(m_nSelectedTaskID);
		}
		plbTasks->EnsureTopItemVisible();
	}
}

void CBigCalendarCtrl::GotoMonth(const COleDateTime& _date)
{
	if (!CCalendarUtils::IsDateValid(_date))
	{
		ASSERT(FALSE);
		return;
	}

	COleDateTime dtFirstCell(_date.GetYear(), _date.GetMonth(), 1, 0, 0, 0);

	int narr[7];
	for (int d=0; d<7; d++)	
	{
		narr[((m_nFirstWeekDay-1)+d)%7] = d;
	}
	int nCellStart = narr[dtFirstCell.GetDayOfWeek()-1];

	dtFirstCell -= nCellStart;

	if (m_pFrameWnd->IsWeekendsHidden())
	{
		int iDayOfWeek = dtFirstCell.GetDayOfWeek();
		int nDiff = abs(m_nFirstWeekDay - iDayOfWeek);
		dtFirstCell -= nDiff;

		if (dtFirstCell.GetDayOfWeek() == 1)
		{
			//sun
			dtFirstCell += 1;
		}
		else if (dtFirstCell.GetDayOfWeek() == 7)
		{
			//sat
			dtFirstCell += 2;
		}
	}

	RepopulateAllCells(dtFirstCell);
}

void CBigCalendarCtrl::Goto(const COleDateTime& _date,
							BOOL _bSelect/*=FALSE*/)
{
	if (!CCalendarUtils::IsDateValid(_date))
	{
		ASSERT(FALSE);
		return;
	}

	COleDateTime dtNew = _date;
	if (m_pFrameWnd->IsDateHidden(dtNew)) //it was a saturday or sunday
	{
		CCalendarUtils::SubtractDay(dtNew, 1);
		if (m_pFrameWnd->IsDateHidden(dtNew))	//it was a sunday
		{
			CCalendarUtils::SubtractDay(dtNew, 1);
		}
	}

	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

	if (_bSelect)
	{
		m_dateSelected = dtNew;

		// Scrolling pos
		COleDateTime dtToday;
		CCalendarUtils::GetToday(dtToday);
		m_nVscrollPos = (m_nVscrollMax/2) + (m_dateSelected-dtToday).GetDays()/nNumColumns;
		SetScrollPos(SB_VERT, m_nVscrollPos, TRUE);
	}

	COleDateTime dtFirstCell = COleDateTime(dtNew.GetYear(), dtNew.GetMonth(), dtNew.GetDay(),0,0,0);

	BOOL bBelow = FALSE;
	if (IsDateVisible(dtNew, &bBelow))
	{
		//already visible - keep current 1st cell as the 1st cell
		dtFirstCell = m_dayCells[0][0].date;
	}
	else
	{
		//date not visible. get the date of the first day of the week that contains dtNew
		int narr[7];
		for (int d=0; d<7; d++)	
			narr[((m_nFirstWeekDay-1)+d)%7] = d;
		int nCellStart = narr[dtFirstCell.GetDayOfWeek()-1];

		CCalendarUtils::SubtractDay(dtFirstCell, nCellStart);

		if (bBelow)
		{
			//need this date on the bottom line. so scroll to a number of weeks before dtFirstCell
			CCalendarUtils::SubtractDay(dtFirstCell, 7*(nNumWeeks-1));
		}
	}

	//redraw all cells, populating each one with the new date
	RepopulateAllCells(dtFirstCell);
}

void CBigCalendarCtrl::DrawHeader(CDC* _pDC)
{
	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();

	CRect rc;
	GetClientRect(&rc);
	rc.bottom = rc.top + CALENDAR_HEADER_HEIGHT;

	CRect rcLine(rc);
	rcLine.top = rcLine.bottom-1;
	for(int i=0; i<CALENDAR_HEADER_HEIGHT; i++)
	{
		_pDC->FillSolidRect(rcLine, GetFadedBlue(i*4));
		rcLine.bottom--;
		rcLine.top = rcLine.bottom-1;
	}

	CStringList listDays;
	CStringList listDaysShort;
	CCalendarUtils::GetWeekdays(listDays, FALSE, m_pFrameWnd->IsWeekendsHidden());
	CCalendarUtils::GetWeekdays(listDaysShort, TRUE, m_pFrameWnd->IsWeekendsHidden());

	CFont* pOldFont = _pDC->SelectObject(m_pFont);
	int nWidth = rc.Width()/nNumColumns;

	BOOL bShort = FALSE;
	i = 0;
	for (POSITION pos = listDays.GetHeadPosition(); pos; )
	{
		CString strDay = listDays.GetNext(pos);
		CSize szTitle(_pDC->GetTextExtent(strDay));
		if(szTitle.cx > nWidth)
		{
			bShort = TRUE;
			break;
		}
		i++;
	}

	if (bShort)
	{
		//full names too long for columns - overwrite listDays with listDaysShort
		listDays.RemoveAll();
		listDays.AddTail(&listDaysShort);
	}

	i = 0;
	for (pos = listDays.GetHeadPosition(); pos; )
	{
		CString strDay = listDays.GetNext(pos);
		CRect rcText(i*nWidth, 2, (i+1)*nWidth, CALENDAR_HEADER_HEIGHT);
		_pDC->DrawText(strDay, rcText, DT_CENTER|DT_VCENTER);
		i++;
	}
	_pDC->SelectObject(pOldFont);
}

void CBigCalendarCtrl::DrawGrid(CDC* _pDC)
{
	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

	CRect rc;
	GetClientRect(&rc);												//rect of the BigCalendar
	int nHeight = (rc.Height()-CALENDAR_HEADER_HEIGHT)/nNumWeeks;	//height of each cell
	int nWidth = rc.Width()/nNumColumns;							//width of each cell

	CPen thinPen(PS_SOLID, 1, COLOUR_GRIDLINES);
	CPen* pOldPen = _pDC->SelectObject(&thinPen);
	for(int i=1; i < nNumColumns; i++)
	{
		_pDC->MoveTo(i*nWidth, CALENDAR_HEADER_HEIGHT);
		_pDC->LineTo(i*nWidth, rc.Height());
	}
	
	for(i=0; i<nNumWeeks; i++)
	{
		int nNewPos = (i*nHeight)+CALENDAR_HEADER_HEIGHT;
		_pDC->MoveTo(0, nNewPos);
		_pDC->LineTo(rc.Width(), nNewPos);
	}
	_pDC->SelectObject(pOldPen);
}

void CBigCalendarCtrl::DrawCells(CDC* _pDC)
{
	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

	CRect rc;
	GetClientRect(&rc);	//rect of the BigCalendar

	CFont* pOldFont = _pDC->SelectObject(m_pFont);

	CPen blackPen(PS_SOLID, 1, COLOUR_TASKS);
	CPen* pOldPen = _pDC->SelectObject(&blackPen);

	for(int i=0; i < nNumWeeks; i++)
	{
		for(int u=0; u < nNumColumns; u++)
		{
			CRect rect;
			if(GetRectFromCell(i, u, rect))
			{
				if(u == nNumColumns-1) rect.right = rc.right;
				if(i == nNumWeeks-1) rect.bottom = rc.bottom;

				if( (m_bMonthIsOdd && !(m_dayCells[i][u].date.GetMonth()%2)) ||
					(!m_bMonthIsOdd && (m_dayCells[i][u].date.GetMonth()%2)))
				{
					CBrush br;
					br.CreateSolidBrush(CALENDAR_LIGHTGREY);
					_pDC->FillRect(&rect ,&br);
				}

				BOOL bToday = FALSE;

				COleDateTime dtToday;
				CCalendarUtils::GetToday(dtToday);

				if (dtToday == m_dayCells[i][u].date)
				{
					// Draw the frame
					CRect rcLine(rect);
					rcLine.bottom = rcLine.top+15;
					rcLine.top = rcLine.bottom-1;
					for(int c=0; c<15; c++)
					{
						_pDC->FillSolidRect(rcLine, GetFadedBlue(c*6));
						rcLine.bottom--;
						rcLine.top = rcLine.bottom-1;
					}
					bToday = TRUE;
				}

				// Draw the selection
				if (m_dateSelected == m_dayCells[i][u].date)
				{						
					CRect selRect(rect);
					CBrush br;
					br.CreateSolidBrush(GetFadedBlue(70));
					if (bToday)
					{
						selRect.top += 15;
					}
					_pDC->FillRect(&selRect, &br);
				}

				// Out of range
				if (!CCalendarUtils::IsDateValid(m_dayCells[i][u].date))	
				{
					CRect selRect(rect);
					CBrush br;
					br.CreateSolidBrush(COLOUR_INVALID_DATE);
					_pDC->FillRect(&selRect, &br);
				}

				//draw Important marker
				if (m_dayCells[i][u].arrTasks.GetSize() > 0)
				{
					CBrush br;
					br.CreateSolidBrush(COLOUR_IMPORTANTDAY_MARKER);
					CRect rcMark(rect);
					rcMark.left = rcMark.right - 15;
					rcMark.bottom = rcMark.top + 13;
					_pDC->FillRect(&rcMark, &br);
				}

				// draw inside...
				rect.DeflateRect(1,1);		

				int nDay = m_dayCells[i][u].date.GetDay();

				if ((i==0 && u==0) || IsFirstVisibleDayOfMonth(m_dayCells[i][u].date))
				{
					//draw month name (for first cell always, and if this cell is first visible day of month)
					CRect rcMonth(rect);
					rcMonth.right -= 15;

					int nMonth = m_dayCells[i][u].date.GetMonth();
					CString csMonth = CDateHelper::GetMonth(nMonth, FALSE);
					CSize dtSize(_pDC->GetTextExtent(csMonth));
					if (dtSize.cx > rcMonth.Width())
					{
						//no room for long version, must use short version (e.g. must use "Dec" instead of "December")
						csMonth = CDateHelper::GetMonth(nMonth, TRUE);
					}

					unsigned long nOldColor = _pDC->SetTextColor(COLOUR_MONTHNAMES);
					_pDC->DrawText(csMonth, rcMonth, DT_RIGHT|DT_TOP);
					_pDC->SetTextColor(nOldColor);
				}

				if (m_dayCells[i][u].arrTasks.GetSize() > 0)
				{
					_pDC->SelectObject(m_pFontBold);
				}
				else
				{
					_pDC->SelectObject(m_pFont);
				}

				//draw day number
				CString strDay;
				strDay.Format("%d", nDay);
				unsigned long nOldColor = _pDC->SetTextColor(COLOUR_DAYNUMBERS);
				_pDC->DrawText(strDay, rect, DT_RIGHT|DT_TOP);
				_pDC->SetTextColor(nOldColor);
			}
		}
	}

	_pDC->SelectObject(pOldFont);
	_pDC->SelectObject(pOldPen);
}

void CBigCalendarCtrl::FireNotifySelectDate()
{
	EnterCell();
	SendSelectDateMessageToParent();
}

void CBigCalendarCtrl::SendSelectDateMessageToParent()
{
	NMHDR NotifyArea;
	NotifyArea.code = CALENDAR_MSG_SELECTDATE;
	NotifyArea.hwndFrom = m_hWnd;
	NotifyArea.idFrom = ::GetDlgCtrlID(m_hWnd);
	GetParent()->SendMessage(WM_NOTIFY, ::GetDlgCtrlID(m_hWnd), (WPARAM)&NotifyArea);
}

void CBigCalendarCtrl::ScrollDown(int _nLines)
{
	ASSERT(_nLines > 0);

	COleDateTime dtFirstCell(m_dayCells[0][0].date);	//current first-cell
	CCalendarUtils::AddDay(dtFirstCell, 7*_nLines);

	if (!CCalendarUtils::IsDateValid(dtFirstCell))
	{
		ASSERT(FALSE);
		return;
	}

	m_nVscrollPos += _nLines;
	RepopulateAllCells(dtFirstCell);

	SetScrollPos(SB_VERT, m_nVscrollPos, TRUE);
}

void CBigCalendarCtrl::ScrollUp(int _nLines)
{
	ASSERT(_nLines > 0);

	COleDateTime dtFirstCell(m_dayCells[0][0].date);	//current first-cell
	CCalendarUtils::SubtractDay(dtFirstCell, 7*_nLines);

	if (!CCalendarUtils::IsDateValid(dtFirstCell))
	{
		ASSERT(FALSE);
		return;
	}

	m_nVscrollPos -= _nLines;
	RepopulateAllCells(dtFirstCell);

	SetScrollPos(SB_VERT, m_nVscrollPos, TRUE);
}

void CBigCalendarCtrl::CreateTasks(BOOL _bRecreate/*=FALSE*/)
{
	if (_bRecreate)
	{
		for (POSITION pos = m_listCalendarTasks.GetHeadPosition(); pos; )
		{
			CBigCalendarTask* pTaskWnd = (CBigCalendarTask*)m_listCalendarTasks.GetNext(pos);
			pTaskWnd->DestroyWindow();
			delete pTaskWnd;
		}
		m_listCalendarTasks.RemoveAll();
	}

	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();
	DWORD dwStyleCompletedTasks = m_pFrameWnd->GetCompletedTasksStyle();

	int nTaskWindowID = 1234;

	for (int i = 0; i < nNumWeeks; i++)
	{
		for (int u = 0; u < nNumColumns; u++)
		{
			//create task windows
			CBigCalendarTask* pTaskWnd = new CBigCalendarTask(this, dwStyleCompletedTasks);
			m_listCalendarTasks.AddTail(pTaskWnd);

			pTaskWnd->Create(LBS_OWNERDRAWFIXED|LBS_SORT|LBS_HASSTRINGS|WS_VISIBLE,
							 CRect(0,0,0,0), this, nTaskWindowID);

			m_dayCells[i][u].nListboxID = nTaskWindowID;
			nTaskWindowID++;
		}
	}
}

void CBigCalendarCtrl::ResizeTasks(BOOL _bRepopulateTasks/*=FALSE*/)
{
	int nNumColumns = m_pFrameWnd->GetNumDaysToDisplay();
	int nNumWeeks = m_pFrameWnd->GetNumWeeksToDisplay();

	for (int i = 0; i < nNumWeeks; i++)
	{
		for (int u = 0; u < nNumColumns; u++)
		{
			CRect rcCell;
			if (GetRectFromCell(i, u, rcCell))
			{
				CBigCalendarTask* pWndTask = GetTaskListboxFromCell(i, u);
				if (pWndTask)
				{
					if (_bRepopulateTasks)
					{
						//remove existing items from all listboxes
						pWndTask->ResetContent();

						//re-add values to all listboxes
						int iNumTasks = m_dayCells[i][u].arrTasks.GetSize();
						for (int iTask = 0; iTask < iNumTasks; iTask++)
						{
							CString strOut = m_dayCells[i][u].arrTasks.GetAt(iTask);
							DWORD dwTaskID = m_dayCells[i][u].arrTaskIDs[iTask];
							BOOL bIsStartTask = m_dayCells[i][u].arrIsStartTask[iTask];
							BOOL bIsCompleteTask = m_dayCells[i][u].arrIsCompleteTask[iTask];
							pWndTask->AddItem(strOut, dwTaskID, bIsStartTask, bIsCompleteTask);
						}

						//does this cell contain any task that should be selected?
						if (m_dayCells[i][u].date == m_dateSelected)
						{
							//re-select task now that it has been redrawn
							pWndTask->SetCurSel(m_nSelectedTaskID);
						}
					}

					int nMaxHeight = rcCell.Height()-12;
					int nRequiredHeight = pWndTask->GetTotalItemHeight();
					if (nRequiredHeight > nMaxHeight)
					{
						nRequiredHeight = nMaxHeight;
					}

					//resize/move the tasks listbox
					pWndTask->SetWindowPos(NULL, rcCell.left+3, rcCell.top+14,
										   rcCell.Width()-5, nRequiredHeight, 0);
				}
			}
		}
	}
}

//redraws all cells, populating each one with the new date
void CBigCalendarCtrl::RepopulateAllCells(const COleDateTime& _dateFirstCell)
{
	COleDateTime dateCurrent = _dateFirstCell;
	int iFirstCellDayOfWeek = dateCurrent.GetDayOfWeek();

	if (m_pFrameWnd->IsWeekendsHidden())
	{
		if (iFirstCellDayOfWeek == 7)	//sat
		{
			dateCurrent += 2;	//mon
		}
		else if (iFirstCellDayOfWeek == 1)	//sun
		{
			dateCurrent += 1;	//mon
		}
	}
	else
	{
		dateCurrent += (m_nFirstWeekDay - iFirstCellDayOfWeek);
	}

	DWORD dwStyleCompletedTasks = m_pFrameWnd->GetCompletedTasksStyle();

	for (int iRow = 0; iRow < CALENDAR_ROWS; iRow++)
	{
		for (int iCol = 0; iCol < CALENDAR_COLUMNS; iCol++)
		{
			// Init the cell
			m_dayCells[iRow][iCol].date = dateCurrent;
			m_dayCells[iRow][iCol].arrTasks.RemoveAll();
			m_dayCells[iRow][iCol].arrTaskIDs.RemoveAll();
			m_dayCells[iRow][iCol].arrIsStartTask.RemoveAll();
			m_dayCells[iRow][iCol].arrIsDueTask.RemoveAll();
			m_dayCells[iRow][iCol].arrIsCompleteTask.RemoveAll();

			if (m_pCalendarData->IsImportantDate(dateCurrent))
			{
				//this date is special - get the tasks for this date
				CTaskInfoList listTasks;
				m_pCalendarData->GetTasks(dateCurrent, listTasks);
				ASSERT(!listTasks.IsEmpty());

				//add the tasks in listTasks to this date's cell
				CBigCalendarCell* pCell = (CBigCalendarCell*)GetCell(dateCurrent);	//frig to de-const the cell
				if (pCell)
				{
					for (POSITION pos = listTasks.GetHeadPosition(); pos != NULL; )
					{
						CTaskInfo ti = listTasks.GetNext(pos);
						if (!(dwStyleCompletedTasks & COMPLETEDTASKS_HIDDEN) || (!ti.IsComplete()))
						{
							pCell->arrTasks.Add(ti.GetTaskTitle());
							pCell->arrTaskIDs.Add(ti.GetTaskID());
							pCell->arrIsStartTask.Add(ti.IsStart());
							pCell->arrIsDueTask.Add(ti.IsDue());
							pCell->arrIsCompleteTask.Add(ti.IsComplete());
						}
					}
				}
			}

			CCalendarUtils::AddDay(dateCurrent);
		}
	}

	ResizeTasks(TRUE);

	Invalidate();

	if (m_pFrameWnd->IsWeekendsHidden())
	{
		if (m_dateSelected.GetDayOfWeek() == 7)
		{
			//sat
			m_dateSelected -= 1;
			SelectDate(m_dateSelected);
			FireNotifySelectDate();
		}
		else if (m_dateSelected.GetDayOfWeek() == 1)
		{
			//sun
			m_dateSelected -= 2;
			SelectDate(m_dateSelected);
			FireNotifySelectDate();
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// CBigCalendarCtrl message handlers

void CBigCalendarCtrl::OnPaint() 
{
	ResizeTasks();

	CPaintDC dc(this);
	CRect rc;
	GetClientRect(&rc);

	CDC MemDC;
	MemDC.CreateCompatibleDC(&dc);
	CBitmap MemBitmap;
	MemBitmap.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height());
	CBitmap *pOldBitmap = MemDC.SelectObject(&MemBitmap);

	CBrush brBkGnd;
	brBkGnd.CreateSolidBrush(COLOUR_BACKGROUND);
	MemDC.FillRect(&rc ,&brBkGnd);
	MemDC.SetBkMode(TRANSPARENT);

	// Draw calendar elements
	DrawHeader(&MemDC);
	DrawCells(&MemDC);
	DrawGrid(&MemDC);

	// Render
	dc.BitBlt(rc.left, rc.top, rc.Width(), rc.Height(), &MemDC, 0, 0, SRCCOPY);
	MemDC.SelectObject(pOldBitmap);
}

void CBigCalendarCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	int nRow = 0;
	int nCol = 0;
	if (GetGridCellFromPoint(point, nRow, nCol))
	{
		if (!CCalendarUtils::IsDateValid(m_dayCells[nRow][nCol].date))
		{
			ASSERT(FALSE);
			return;
		}

		if (m_dateSelected != m_dayCells[nRow][nCol].date)
		{
			LeaveCell();

			m_bTracking = TRUE;
			m_dateSelected = m_dayCells[nRow][nCol].date;
			SetCapture();
			Invalidate(TRUE);

			FireNotifySelectDate();
		}
	}	

	SetFocus();
	CWnd::OnLButtonDown(nFlags, point);
}

void CBigCalendarCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if (m_bTracking)
	{
		m_bTracking = FALSE;

		int nRow = 0;
		int nCol = 0;
		if (GetGridCellFromPoint(point, nRow, nCol))
		{
			if (!CCalendarUtils::IsDateValid(m_dayCells[nRow][nCol].date))
			{
				ASSERT(FALSE);
				return;
			}

			m_dateSelected = m_dayCells[nRow][nCol].date;
		}
		ReleaseCapture();
	}

	Invalidate(TRUE);
	CWnd::OnLButtonUp(nFlags, point);
}

void CBigCalendarCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
	if(m_bTracking)
	{
		int nRow = 0;
		int nCol = 0;
		if(GetGridCellFromPoint(point, nRow, nCol))
		{
			if (!CCalendarUtils::IsDateValid(m_dayCells[nRow][nCol].date))
			{
				ASSERT(FALSE);
				return;
			}

			if (m_dateSelected != m_dayCells[nRow][nCol].date)
			{
				LeaveCell();

				m_dateSelected = m_dayCells[nRow][nCol].date;
				Invalidate(TRUE);

				FireNotifySelectDate();
			}
		}
	}	
	CWnd::OnMouseMove(nFlags, point);
}

void CBigCalendarCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	int nLines = 0;
    switch (nSBCode)
    {
        case SB_TOP:
        case SB_LINEUP:
        case SB_PAGEUP:     
			nLines = -1;
			break;
		case SB_BOTTOM:
		case SB_LINEDOWN:
        case SB_PAGEDOWN:
			nLines = 1;
			break;
        case SB_THUMBTRACK: 
			nLines = nPos - m_nVscrollPos;
			break;
        default:
			nLines = 0;
    }

	if (nLines > 0)
	{
		ScrollDown(nLines);
	}
	else if (nLines < 0)
	{
		ScrollUp(abs(nLines));
	}

	SetFocus();
	CWnd::OnVScroll(nSBCode, nPos, pScrollBar);
}

BOOL CBigCalendarCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) 
{	
	if (zDelta < 0)
	{
		SendMessage(WM_VSCROLL, SB_LINEDOWN);
	}
	else if (zDelta > 0)
	{
		SendMessage(WM_VSCROLL, SB_LINEUP);
	}
	
	return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions