// ----------------------------------------------------------------------------
// Written by Tony Ioanides (tonyi@bigpond.com)
// Copyright (c) 2003 Tony Ioanides.
//
// This code may be used in compiled form in any way you desire. This file may
// be redistributed by any means PROVIDING it is not sold for profit without
// the authors written consent, and providing that this notice and the authors
// name is included.
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to you or your
// computer whatsoever.
// ----------------------------------------------------------------------------
#include "stdafx.h"
#include "MiniCalendarCtrl.h"
#include "MonthPopupCtrl.h"
#include <algorithm>
#ifndef NUM_ELEMENTS
#define NUM_ELEMENTS(a) (sizeof(a)/sizeof(*a))
#endif // NUM_ELEMENTS
// ----------------------------------------------------------------------------
static CRect
MC_GetArrowRect(CRect rectHeader, BOOL bLeftArrow)
{
const int yMid = (rectHeader.top + rectHeader.bottom) / 2;
const int cx = rectHeader.Height() / 5;
rectHeader.top = yMid - cx;
rectHeader.bottom = yMid + cx;
if (bLeftArrow)
{
rectHeader.left += 6;
rectHeader.right = rectHeader.left + cx;
}
else
{
rectHeader.right -= 6;
rectHeader.left = rectHeader.right - cx;
}
return rectHeader;
}
// ----------------------------------------------------------------------------
static void
MC_DrawArrow(CDCHandle dc, CRect rectHeader, BOOL bLeftArrow)
{
RECT rect = MC_GetArrowRect(rectHeader, bLeftArrow);
if (bLeftArrow)
std::swap(rect.left, rect.right);
POINT ptTriangle[] =
{
{ rect.left, rect.top },
{ rect.left, rect.bottom },
{ rect.right, (rect.top + rect.bottom) / 2 }
};
HBRUSH hbrOrig = dc.SelectStockBrush(BLACK_BRUSH);
dc.Polygon(ptTriangle, NUM_ELEMENTS(ptTriangle));
dc.SelectBrush(hbrOrig);
}
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Internal constants
enum
{
nMonthsPerYear = 12,
nDaysPerWeek = 7,
nRowsPerCell = 6,
nDaysPerCell = nRowsPerCell * nDaysPerWeek,
nBorderSize = 4,
nCellMargin = 10
};
#define ID_TIMER_FIRST 0xDEAD
#define ID_TIMER_REPEAT 0xC0DE
// ----------------------------------------------------------------------------
CMiniCalendarCtrl::CMiniCalendarCtrl()
{
m_crBack = GetSysColor(COLOR_WINDOW);
m_bShow3dBorder = 1;
m_bShowNonMonthDays = 1;
m_bHighlightToday = 1;
m_bMultiSelEnabled = 1;
m_nFirstDayOfWeek = 1;
m_nStartMonth = 1;
m_nStartYear = 1970;
m_nMonthsToScroll = 1;
m_nCols = 0;
m_nRows = 0;
SetLayout(1, 1);
m_daySpace.cx = 4;
m_daySpace.cy = 4;
m_nActiveElement = 0;
}
// ----------------------------------------------------------------------------
BOOL
CMiniCalendarCtrl::IsDateSelected(const COleDateTime& date) const
{
if (m_dtSelect.GetStatus() != COleDateTime::valid)
return FALSE;
if (date == m_dtSelect)
return TRUE;
if (m_bMultiSelEnabled && m_dtAnchor.GetStatus() == COleDateTime::valid)
return m_dtSelect > m_dtAnchor ?
(date >= m_dtAnchor && date <= m_dtSelect) :
(date >= m_dtSelect && date <= m_dtAnchor);
return FALSE;
}
// ----------------------------------------------------------------------------
SIZE
CMiniCalendarCtrl::GetMaxSize() const
{
return GetMaxSize(m_nCols, m_nRows);
}
// ----------------------------------------------------------------------------
SIZE
CMiniCalendarCtrl::GetMaxSize(UINT nCols, UINT nRows) const
{
const int cxyBorder = m_bShow3dBorder ? (nBorderSize * 2) : 0;
return CSize(
nCols * m_cellSize.cx + cxyBorder,
nRows * m_cellSize.cy + cxyBorder);
}
// ----------------------------------------------------------------------------
BOOL
CMiniCalendarCtrl::SetLayout(UINT nCols, UINT nRows)
{
if (nCols > 0 && nRows > 0 && ((int) nCols != m_nCols || (int) nRows != m_nRows))
{
m_nCols = nCols;
m_nRows = nRows;
// Update special day flags.
m_boldDays.clear();
m_boldDays.assign(nCols * nRows, 0);
if (IsWindow())
{
NotifyGetSpecialDays();
AutoSize();
}
return TRUE;
}
return FALSE;
}
// ----------------------------------------------------------------------------
BOOL
CMiniCalendarCtrl::SetFirstDayOfWeek(UINT nFirstDayOfWeek)
{
if (nFirstDayOfWeek >= 1 && nFirstDayOfWeek <= nDaysPerWeek &&
nFirstDayOfWeek != (UINT) m_nFirstDayOfWeek)
{
m_nFirstDayOfWeek = nFirstDayOfWeek;
Invalidate();
return TRUE;
}
return FALSE;
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::AutoSize()
{
const SIZE size = GetMaxSize();
SetWindowPos(0, 0, 0, size.cx, size.cy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
// ----------------------------------------------------------------------------
BOOL
CMiniCalendarCtrl::EnsureVisible(const COleDateTime& date)
{
if (date.GetStatus() != COleDateTime::valid)
return FALSE;
if (date >= GetMonthFromCell(0, 0) &&
date <= GetMonthFromCell(m_nCols - 1, m_nRows - 1))
return FALSE;
m_nStartMonth = date.GetMonth();
m_nStartYear = date.GetYear();
RedrawWindow();
return TRUE;
}
// ----------------------------------------------------------------------------
BOOL
CMiniCalendarCtrl::ResetRange()
{
if (m_dtAnchor.GetStatus() == COleDateTime::valid)
{
m_dtAnchor.SetStatus(COleDateTime::invalid);
NotifySelChanged();
Invalidate();
return TRUE;
}
m_dtAnchor.SetStatus(COleDateTime::invalid);
return FALSE;
}
// ----------------------------------------------------------------------------
LRESULT
CMiniCalendarCtrl::OnCreate(LPCREATESTRUCT lpcs)
{
m_font[FontHeader].bBold = TRUE;
m_font[FontHeader].nFontSize = 10;
m_font[FontSpecialDayNumber].crColor = GetSysColor(COLOR_HIGHLIGHT);
m_font[FontSpecialDayNumber].bBold = TRUE;
for (int i = 0; i < NUM_ELEMENTS(m_font); ++i)
CreateFont(i);
ApplyStyle(lpcs->style);
RecalcLayout();
AutoSize();
EnsureVisible(COleDateTime::GetCurrentTime());
NotifyGetSpecialDays();
SetMsgHandled(FALSE);
return 0;
}
// ----------------------------------------------------------------------------
LRESULT
CMiniCalendarCtrl::OnStyleChanged(UINT nType, LPSTYLESTRUCT lpss)
{
if (nType & GWL_STYLE)
ApplyStyle(lpss->styleNew);
SetMsgHandled(FALSE);
return 0;
}
// ----------------------------------------------------------------------------
LRESULT
CMiniCalendarCtrl::OnTimer(UINT nID, TIMERPROC /*fnProc*/)
{
switch (nID)
{
case ID_TIMER_FIRST:
KillTimer(ID_TIMER_FIRST);
if (m_nActiveElement & htHeader)
{
AutoScroll(m_nActiveElement);
int nElapse = 250;
DWORD nRepeat = 40;
if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &nRepeat, 0))
nElapse = 10000 / (5 * nRepeat + 25); // milli-seconds, approximated
SetTimer(ID_TIMER_REPEAT, nElapse);
}
break;
case ID_TIMER_REPEAT:
if (m_nActiveElement & htHeader)
AutoScroll(m_nActiveElement);
else if (GetCapture() != m_hWnd)
KillTimer(ID_TIMER_REPEAT);
break;
default:
break;
}
return 0;
}
// ----------------------------------------------------------------------------
LRESULT
CMiniCalendarCtrl::OnMouseMove(UINT /*nFlags*/, CPoint point)
{
if (GetCapture() == m_hWnd)
{
m_nActiveElement &= ~htHeader;
HitTestInfo ht;
ht.ptHit = point;
if (HitTest(ht))
{
if ((m_nActiveElement & htDate) && (ht.nFlags & htDate) && ht.dtHit != m_dtSelect)
{
m_dtSelect = ht.dtHit;
if (ht.nFlags & (htBack | htNext))
AutoScroll(ht.nFlags);
else
RedrawWindow();
NotifySelChanged();
}
if ((m_nActiveElement & (htBack | htNext)) &&
ht.nFlags == UINT(htHeader | m_nActiveElement))
{
ATLTRACE("ResumeScroll\n");
m_nActiveElement = ht.nFlags;
}
}
}
return 0;
}
// ----------------------------------------------------------------------------
LRESULT
CMiniCalendarCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
m_nActiveElement = 0;
HitTestInfo ht;
ht.ptHit = point;
if (HitTest(ht))
{
SetCapture();
m_nActiveElement = ht.nFlags;
if (m_nActiveElement & htHeader)
{
if (m_nActiveElement & (htBack | htNext))
{
AutoScroll(ht.nFlags);
int nElapse = 250;
int nDelay = 0;
if (SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &nDelay, 0))
nElapse += nDelay * 250; // all milli-seconds
SetTimer(ID_TIMER_FIRST, nElapse);
}
else if (m_nActiveElement & htPick)
DoPickDate(ht.nCol, ht.nRow);
}
else if (m_nActiveElement & htButton)
{
}
else if (m_nActiveElement & htDate)
{
if (m_dtAnchor.GetStatus() != COleDateTime::valid || ! (nFlags & MK_SHIFT))
m_dtAnchor = ht.dtHit;
m_dtSelect = ht.dtHit;
if (m_nActiveElement & (htBack | htNext))
AutoScroll(ht.nFlags);
else
RedrawWindow();
NotifySelChanged();
}
else
ResetRange();
}
return 0;
}
// ----------------------------------------------------------------------------
LRESULT
CMiniCalendarCtrl::OnLButtonUp(UINT /*nFlags*/, CPoint /*point*/)
{
ReleaseCapture();
OnCaptureChanged(0);
return 0;
}
// ----------------------------------------------------------------------------
LRESULT
CMiniCalendarCtrl::OnCaptureChanged(HWND /*hWnd*/)
{
m_nActiveElement = 0;
return 0;
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::DrawBorder(CDCHandle dc, CRect& rectClient)
{
if (m_bShow3dBorder)
{
static const struct
{
COLORREF crTL;
COLORREF crBR;
} border[] =
{
{ COLOR_3DFACE, COLOR_BTNTEXT },
{ COLOR_3DHILIGHT, COLOR_3DSHADOW },
{ COLOR_3DFACE, COLOR_3DFACE },
{ COLOR_3DSHADOW, COLOR_3DHILIGHT }
};
for (int i = 0; i < NUM_ELEMENTS(border); ++i)
{
dc.Draw3dRect(rectClient,
GetSysColor(border[i].crTL),
GetSysColor(border[i].crBR));
rectClient.DeflateRect(1, 1);
}
}
}
// ----------------------------------------------------------------------------
int
CMiniCalendarCtrl::DrawHeader(CDCHandle dc, CPoint pt, int nCol, int nRow)
{
CRect rectHeader(0, 0, m_cellSize.cx, m_cyHeader);
rectHeader.OffsetRect(pt);
// Draw 'button' part
dc.Draw3dRect(rectHeader,
GetSysColor(COLOR_3DHILIGHT), GetSysColor(COLOR_3DSHADOW));
rectHeader.DeflateRect(1, 1);
dc.FillSolidRect(rectHeader, GetSysColor(COLOR_3DFACE));
rectHeader.DeflateRect(1, 1);
// Draw the text
const COleDateTime dt = GetMonthFromCell(nCol, nRow);
const CString str = dt.Format(HEADER_FORMAT);
SelectFont(dc, FontHeader);
dc.DrawText(str, str.GetLength(), rectHeader,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// Draw arrow(s) (if required)
if (nRow == 0 && (nCol == 0 || nCol == m_nCols - 1))
{
rectHeader.SetRect(0, 0, m_cellSize.cx, m_cyHeader);
rectHeader.OffsetRect(pt);
if (nCol == 0)
MC_DrawArrow(dc, rectHeader, TRUE);
if (nCol == m_nCols - 1)
MC_DrawArrow(dc, rectHeader, FALSE);
}
return m_cyHeader;
}
// ----------------------------------------------------------------------------
int
CMiniCalendarCtrl::DrawDaysOfWeek(CDCHandle dc, CPoint pt, int /*nCol*/, int /*nRow*/)
{
const int cxColumn = m_dateSize.cx + m_daySpace.cx;
const int xBeg = pt.x + m_xCol;
SelectFont(dc, FontDayName);
// Draw day names.
CRect rectDate(xBeg, pt.y, xBeg + cxColumn, pt.y + m_cyDayNames);
for (int i = 1; i <= nDaysPerWeek; ++i)
{
dc.DrawText(GetDayOfWeekName(i), 1, rectDate,
DT_SINGLELINE | DT_RIGHT | DT_VCENTER);
rectDate.OffsetRect(cxColumn, 0);
}
rectDate.bottom += 4;
// Draw separator.
dc.SelectBrush(GetSysColorBrush(COLOR_3DSHADOW));
dc.PatBlt(xBeg, rectDate.bottom - 2,
cxColumn * nDaysPerWeek + m_daySpace.cx, 1, PATCOPY);
return rectDate.bottom - pt.y;
}
// ----------------------------------------------------------------------------
int
CMiniCalendarCtrl::DrawDays(CDCHandle dc, CPoint pt, int nCol, int nRow)
{
const int cxColumn = m_dateSize.cx + m_daySpace.cx;
const int xBeg = pt.x + m_xCol;
COleDateTime dt = GetFirstDayInCell(nCol, nRow);
const COleDateTime dtCell = GetMonthFromCell(nCol, nRow);
const COleDateTime dtToday = COleDateTime::GetCurrentTime();
ATLASSERT(nRow * m_nCols + nCol < int(m_boldDays.size()));
const DWORD fBoldDays = m_boldDays[nRow * m_nCols + nCol];
const BOOL bBegRange = nCol == 0 && nRow == 0;
const BOOL bEndRange = nCol == m_nCols - 1 && nRow == m_nRows - 1;
const int nThisMonth = dtCell.GetMonth();
// Determine position of 'today' in this cell (if required).
CPoint ptToday(-1, -1);
if (m_bHighlightToday &&
dtToday.GetMonth() == nThisMonth && dtToday.GetYear() == dtCell.GetYear())
{
const int nOffset = (dtToday - dt).GetDays();
ptToday.x = nOffset % nDaysPerWeek;
ptToday.y = nOffset / nDaysPerWeek;
}
SelectFont(dc, FontDayNumber);
CRect rectDate(xBeg, pt.y, xBeg + cxColumn, pt.y + m_dateSize.cy);
for (int nRow = 0; nRow < nRowsPerCell; ++nRow)
{
rectDate.left = xBeg;
rectDate.right = rectDate.left + cxColumn;
for (int nCol = 0; nCol < nDaysPerWeek; ++nCol)
{
const int nMonth = dt.GetMonth();
if (nMonth == nThisMonth || (m_bShowNonMonthDays &&
((bBegRange && dt < dtCell) || (bEndRange && dt > dtCell)) ) )
{
const CRect rectDigits(
rectDate.left + m_daySpace.cx / 2,
rectDate.top,
rectDate.right + m_daySpace.cx / 2,
rectDate.bottom + 1);
COLORREF crText = (nMonth == nThisMonth) ?
GetSysColor(COLOR_BTNTEXT) : GetSysColor(COLOR_GRAYTEXT);
const int nDay = dt.GetDay();
const BOOL bBold = (nMonth == nThisMonth) && (fBoldDays & (1 << nDay)) != 0;
if (IsDateSelected(dt))
{
crText = COLOR_GRAYTEXT;
dc.FillSolidRect(
rectDigits.left,
rectDigits.top,
rectDigits.Width(),
rectDate.Height() + m_daySpace.cy,
GetSysColor(COLOR_BTNFACE));
}
if (bBold)
crText = m_font[FontSpecialDayNumber].crColor;
SelectFont(dc.m_hDC, bBold ? FontSpecialDayNumber : FontDayNumber);
TCHAR szBuffer[10];
_itot(nDay, szBuffer, 10);
dc.SetTextColor(crText);
dc.DrawText(szBuffer, _tcslen(szBuffer), rectDate,
DT_BOTTOM | DT_RIGHT | DT_SINGLELINE);
// Check if date should be hilighted as today.
if (ptToday.x == nCol && ptToday.y == nRow)
dc.Draw3dRect(rectDigits, RGB(0x80,0,0), RGB(0x80,0,0));
}
rectDate.OffsetRect(cxColumn, 0);
dt += 1;
}
rectDate.OffsetRect(0, m_dateSize.cy + m_daySpace.cy);
}
return rectDate.bottom - pt.y;
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::DoPaint(CDCHandle dc)
{
CRect rectClient;
GetClientRect(rectClient);
CRect rectClip;
dc.SaveDC();
dc.SetBkMode(TRANSPARENT);
dc.GetClipBox(rectClip);
DrawBorder(dc, rectClient);
for (int nRow = 0; nRow < m_nRows; ++nRow)
for (int nCol = 0; nCol < m_nCols; ++nCol)
{
CRect rectCell(0, 0, m_cellSize.cx, m_cellSize.cy);
rectCell.OffsetRect(
nCol * m_cellSize.cx + rectClient.left,
nRow * m_cellSize.cy + rectClient.top);
if (rectClip & rectCell)
{
rectCell.top += DrawHeader(dc, rectCell.TopLeft(), nCol, nRow);
dc.FillSolidRect(rectCell, m_crBack);
rectCell.top += DrawDaysOfWeek(dc, rectCell.TopLeft(), nCol, nRow);
DrawDays(dc, rectCell.TopLeft(), nCol, nRow);
}
}
dc.RestoreDC(-1);
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::NotifyGetSpecialDays()
{
NMMCXGETSPECIALDAYS nm = {0};
for (int nRow = 0; nRow < m_nRows; ++nRow)
for (int nCol = 0; nCol < m_nCols; ++nCol)
{
const COleDateTime dtCell = GetMonthFromCell(nCol, nRow);
nm.month = dtCell.GetMonth();
nm.year = dtCell.GetYear();
nm.fDays = 0;
if (! NotifyParent(MCXN_GETSPECIALDAYS, reinterpret_cast<NMHDR*>(&nm)))
{
// Notifed must follow the protocol, ie if day flags have been changed,
// non-zero must be returned from the notification.
nm.fDays = 0;
}
SpecialDays::iterator i = m_boldDays.begin() + nRow * m_nCols + nCol;
if (*i != nm.fDays)
{
*i = nm.fDays;
RECT rcCell;
GetCellRect(nCol, nRow, &rcCell);
InvalidateRect(&rcCell, FALSE);
}
}
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::NotifySelChanged()
{
NMMCXSELCHANGED nm = {0};
if (m_dtSelect.GetStatus() == COleDateTime::valid)
{
if (m_bMultiSelEnabled)
{
if (m_dtAnchor > m_dtSelect)
{
m_dtSelect.GetAsSystemTime(nm.dateFrom);
m_dtAnchor.GetAsSystemTime(nm.dateTo);
}
else
{
m_dtAnchor.GetAsSystemTime(nm.dateFrom);
m_dtSelect.GetAsSystemTime(nm.dateTo);
}
}
else
{
m_dtSelect.GetAsSystemTime(nm.dateFrom);
m_dtSelect.GetAsSystemTime(nm.dateTo);
}
}
NotifyParent(MCXN_SELCHANGED, reinterpret_cast<NMHDR*>(&nm));
}
// ----------------------------------------------------------------------------
LONG
CMiniCalendarCtrl::NotifyParent(UINT nCode, LPNMHDR lpnmh)
{
lpnmh->code = nCode;
lpnmh->hwndFrom = m_hWnd;
lpnmh->idFrom = GetDlgCtrlID();
return GetParent().SendMessage(WM_NOTIFY, lpnmh->idFrom, (LPARAM) lpnmh);
}
// ----------------------------------------------------------------------------
BOOL
CMiniCalendarCtrl::HitTest(HitTestInfo& ht)
{
ht.nFlags = 0;
ht.nCol = -1;
ht.nRow = -1;
ht.dtHit.SetStatus(COleDateTime::invalid);
CPoint ptHit = ht.ptHit;
CRect rectClient;
GetClientRect(rectClient);
if (m_bShow3dBorder)
{
rectClient.DeflateRect(nBorderSize, nBorderSize);
ptHit -= CSize(nBorderSize, nBorderSize);
}
if (! rectClient.PtInRect(ht.ptHit))
return FALSE;
//rectClient.OffsetRect(-nBorderSize, -nBorderSize);
// Determine which cell was hit.
const int nCol = ptHit.x / m_cellSize.cx;
const int nRow = ptHit.y / m_cellSize.cy;
if (nCol >= m_nCols)
return FALSE;
// Determine if a button was hit.
if (nRow >= m_nRows)
{
return FALSE;
}
ht.nCol = nCol;
ht.nRow = nRow;
// Determine which part of the cell was hit.
ptHit.x %= m_cellSize.cx;
ptHit.y %= m_cellSize.cy;
if (ptHit.y < (int) m_cyHeader)
{
ht.nFlags = htHeader;
CRect rectHeader(0, 0, m_cellSize.cx, m_cyHeader);
if (nRow == 0)
{
if (nCol == 0 && MC_GetArrowRect(rectHeader, TRUE).PtInRect(ptHit))
ht.nFlags |= htBack;
else if (nCol == m_nCols - 1 && MC_GetArrowRect(rectHeader, FALSE).PtInRect(ptHit))
ht.nFlags |= htNext;
}
if (! (ht.nFlags & (htBack | htNext)))
{
rectHeader.DeflateRect((rectHeader.Width() - m_cxHeaderText) / 2, 4);
if (rectHeader.PtInRect(ptHit))
ht.nFlags |= htPick;
}
return TRUE;
}
ptHit.y -= m_cyHeader + m_dateSize.cy + 4;
// Determine if a date was hit.
const int cxColumn = m_dateSize.cx + m_daySpace.cx;
CRect rectDate(CPoint(m_xCol, 0), m_dateSize);
for (int r = 0; r < nRowsPerCell; ++r)
{
rectDate.left = m_xCol;
rectDate.right = rectDate.left + cxColumn;
for (int c = 0; c < nDaysPerWeek; ++c)
{
if (rectDate.PtInRect(ptHit))
{
const COleDateTime dtCell = GetMonthFromCell(nCol, nRow);
const int nDay = r * nDaysPerWeek + c;
const int nMonth = dtCell.GetMonth();
const COleDateTime dtBeg = GetFirstDayInCell(nCol, nRow);
const COleDateTime dtHit = dtBeg + COleDateTimeSpan(nDay);
if (dtHit.GetMonth() == nMonth)
{
ht.nFlags = htDate;
ht.dtHit = dtHit;
return TRUE;
}
if (m_bShowNonMonthDays)
{
if (nCol == 0 && nRow == 0 && dtHit < dtCell)
{
ht.nFlags = htDate | htBack;
ht.dtHit = dtHit;
return TRUE;
}
if (nCol == m_nCols - 1 && nRow == m_nRows - 1 && dtHit > dtCell)
{
ht.nFlags = htDate | htNext;
ht.dtHit = dtHit;
return TRUE;
}
}
return FALSE;
}
rectDate.OffsetRect(cxColumn, 0);
}
rectDate.OffsetRect(0, m_dateSize.cy + m_daySpace.cy);
}
return FALSE;
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::DoPickDate(int nCol, int nRow)
{
const COleDateTime dtCell = GetMonthFromCell(nCol, nRow);
CRect rectHeader;
GetHeaderRect(nCol, nRow, rectHeader);
ClientToScreen(rectHeader);
CMonthPopupCtrl::CreateParams params;
params.cx = m_cxHeaderText;
params.hFont = m_font[FontHeader].font;
params.nMonth = dtCell.GetMonth();
params.nYear = dtCell.GetYear();
params.ptCenter = rectHeader.CenterPoint();
#if 0
// This is an intentional bug (ie memory leak). I use it to capture a
// screenshot of the calendar with the picker visible.
// Note that it isn't normally included in the build.
CMonthPopupCtrl* pWnd = new CMonthPopupCtrl();
pWnd->Create(m_hWnd, params);
#else
CMonthPopupCtrl wndPopup;
if (wndPopup.Create(m_hWnd, params))
{
if (wndPopup.TrackPopup())
{
const CMonthYear myOriginal(dtCell.GetMonth(), dtCell.GetYear());
const CMonthYear mySelected = wndPopup.GetSelection();
DoScroll(mySelected - myOriginal);
}
wndPopup.DestroyWindow();
}
#endif
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::AutoScroll(UINT nFlags)
{
ATLASSERT(nMonths > 0);
if (nFlags & htBack)
DoScroll(-m_nMonthsToScroll);
else if (nFlags & htNext)
DoScroll(m_nMonthsToScroll);
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::DoScroll(int nMonths)
{
if (nMonths)
{
m_nStartYear += nMonths / nMonthsPerYear;
m_nStartMonth += nMonths % nMonthsPerYear;
if (m_nStartMonth < 1)
{
m_nStartMonth += nMonthsPerYear;
--m_nStartYear;
}
else if (m_nStartMonth > nMonthsPerYear)
{
m_nStartMonth -= nMonthsPerYear;
++m_nStartYear;
}
// REVISIT: This could be optimised to only do the callback for _NEW_
// months that have been scrolled into view.
// Also, it may be useful to defer this until scrolling is complete,
// (ie when autorepeat has finished, maybe in WM_LBUTTONUP).
NotifyGetSpecialDays();
RedrawWindow();
}
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::GetMaxTextExtent(CDCHandle dc, SIZE& size)
{
// Determine the maximum size for the range of day numbers.
// Single-digit day numbers are obviously smaller, so start at 10.
for (int i = 10; i <= 31; ++i)
{
TCHAR sz[10];
_itot(i, sz, 10);
SIZE sizeDate = {0};
dc.GetTextExtent(sz, _tcslen(sz), &sizeDate);
size.cx = max(sizeDate.cx, size.cx);
size.cy = max(sizeDate.cy, size.cy);
}
}
// ----------------------------------------------------------------------------
BOOL
CMiniCalendarCtrl::GetCellRect(int nCol, int nRow, LPRECT lprc) const
{
ATLASSERT(lprc != 0);
if (lprc != 0 && (nCol >= 0 && nCol < m_nCols) && (nRow >= 0 && nRow < m_nRows))
{
const int nBorder = m_bShow3dBorder ? nBorderSize : 0;
SetRect(lprc, 0, 0, m_cellSize.cx, m_cellSize.cy);
OffsetRect(lprc,
nCol * m_cellSize.cx + nBorder,
nRow * m_cellSize.cy + nBorder);
return TRUE;
}
return FALSE;
}
// ----------------------------------------------------------------------------
BOOL
CMiniCalendarCtrl::GetHeaderRect(int nCol, int nRow, LPRECT lprc) const
{
if (GetCellRect(nCol, nRow, lprc))
{
lprc->bottom = lprc->top + m_cyHeader;
return TRUE;
}
return FALSE;
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::RecalcLayout()
{
ATLASSERT(IsWindow());
CClientDC dc(m_hWnd);
HFONT hFontOrig = dc.SelectStockFont(DEFAULT_GUI_FONT);
m_dateSize = CSize(0, 0);
SelectFont(dc.m_hDC, FontDayNumber);
GetMaxTextExtent(dc.m_hDC, m_dateSize);
SelectFont(dc.m_hDC, FontSpecialDayNumber);
GetMaxTextExtent(dc.m_hDC, m_dateSize);
// Now check names of weekdays.
m_cyDayNames = 0;
SelectFont(dc.m_hDC, FontDayName);
LPCTSTR szNames = _T("SMTWF");
for (size_t i = 0; i < _tcslen(szNames); ++i)
{
SIZE size = {0};
dc.GetTextExtent(szNames + i, 1, &size);
m_dateSize.cx = max(size.cx, m_dateSize.cx);
m_cyDayNames = max((UINT) size.cy, m_cyDayNames);
}
// Lastly, calculate the header size.
const int cxWeek = (m_dateSize.cx + m_daySpace.cx) * nDaysPerWeek + m_daySpace.cx;
const int cxCalendar = cxWeek + nCellMargin * 2;
m_cxHeaderText = 0;
m_cyHeader = 0;
SelectFont(dc.m_hDC, FontHeader);
CString str;
for (int nYear = 1990; nYear < 2020; ++nYear)
for (int nMonth = 1; nMonth <= nMonthsPerYear; ++nMonth)
{
const COleDateTime dt(nYear, nMonth, 1, 0, 0, 0);
str = dt.Format(HEADER_FORMAT);
SIZE size = {0};
dc.GetTextExtent(str, str.GetLength(), &size);
m_cxHeaderText = max(UINT(size.cx), m_cxHeaderText);
m_cyHeader = max(UINT(size.cy), m_cyHeader);
}
// Header padding.
m_cyHeader += 6;
m_cellSize.cx = max(int(m_cxHeaderText + 30), cxCalendar);
m_cellSize.cy = m_cyHeader + (m_cyDayNames + 4) +
(m_dateSize.cy + m_daySpace.cy) * nRowsPerCell;
m_xCol = (m_cellSize.cx - cxWeek) / 2;
dc.SelectFont(hFontOrig);
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::ApplyStyle(DWORD dwStyle)
{
m_bShow3dBorder = (dwStyle & MCX_3DBORDER) != 0;
m_bShowNonMonthDays = (dwStyle & MCX_SHOWNONMONTHDAYS) != 0;
m_bHighlightToday = (dwStyle & MCX_HIGHLIGHTTODAY) != 0;
m_bMultiSelEnabled = (dwStyle & MCX_MULTISELECT) != 0;
if (! m_bMultiSelEnabled)
ResetRange();
Invalidate();
}
// ----------------------------------------------------------------------------
CString
CMiniCalendarCtrl::GetDayOfWeekName(int nDayOfWeek) const
{
ATLASSERT(nDayOfWeek >= 1 && nDayOfWeek <= nDaysPerWeek);
// NOTE: both m_nFirstDayOfWeek and nDayOfWeek are 1-based!
// Apr 1, 2001 is known to be a Sunday, ie dayOfWeek == 1
const COleDateTime dt =
COleDateTime(2001, 4, 1, 0, 0, 0) +
COleDateTimeSpan((nDayOfWeek - 1) + (m_nFirstDayOfWeek - 1));
return dt.Format(_T("%A"));
}
// ----------------------------------------------------------------------------
COleDateTime
CMiniCalendarCtrl::GetMonthFromCell(int nCol, int nRow) const
{
ATLASSERT(nCol >= 0 && nRow >= 0);
// NOTE: m_nMonth is 1-based!
const int nMonth = (m_nStartMonth - 1) + nRow * m_nCols + nCol;
return COleDateTime(m_nStartYear + nMonth / nMonthsPerYear,
nMonth % nMonthsPerYear + 1, 1, 0, 0, 0);
}
// ----------------------------------------------------------------------------
COleDateTime
CMiniCalendarCtrl::GetFirstDayInCell(int nCol, int nRow) const
{
const COleDateTime dt = GetMonthFromCell(nCol, nRow);
const int nPriorDays =
(dt.GetDayOfWeek() - m_nFirstDayOfWeek + nDaysPerWeek) % nDaysPerWeek;
return dt - COleDateTimeSpan(nPriorDays);
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::CreateFont(int nFont)
{
ATLASSERT(nFont >= FontHeader && nFont <= FontSpecialDayNumber);
FontInfo* pInfo = m_font + nFont;
CFont font;
font.CreatePointFont(pInfo->nFontSize * 10, pInfo->strFaceName);
LOGFONT lf = {0};
font.GetLogFont(&lf);
font.DeleteObject();
{
CClientDC dc(m_hWnd);
lf.lfHeight = -MulDiv(pInfo->nFontSize, dc.GetDeviceCaps(LOGPIXELSY), 72);
lf.lfWeight = FW_NORMAL;
lf.lfQuality = PROOF_QUALITY;
}
if (pInfo->bBold)
lf.lfWeight = FW_BOLD;
if (pInfo->bItalic)
lf.lfItalic = TRUE;
if (pInfo->bUnderline)
lf.lfUnderline = TRUE;
if (! pInfo->font.IsNull())
pInfo->font.DeleteObject();
pInfo->font.CreateFontIndirect(&lf);
}
// ----------------------------------------------------------------------------
void
CMiniCalendarCtrl::SelectFont(CDCHandle dc, int nFont)
{
ATLASSERT(nFont >= FontHeader && nFont <= FontSpecialDayNumber);
dc.SetTextColor(m_font[nFont].crColor);
dc.SelectFont(m_font[nFont].font);
}
// ----------------------------------------------------------------------------
CMiniCalendarCtrl::FontInfo::FontInfo()
{
strFaceName = _T("Tahoma");
crColor = GetSysColor(COLOR_BTNTEXT);
nFontSize = 9;
bBold = FALSE;
bItalic = FALSE;
bUnderline = FALSE;
}
// ----------------------------------------------------------------------------