📄 monthcal.c
字号:
/* Month calendar control
*
* Copyright 1998, 1999 Eric Kohl (ekohl@abo.rhein-zeitung.de)
* Copyright 1999 Alex Priem (alexp@sci.kun.nl)
* Copyright 1999 Chris Morgan <cmorgan@wpi.edu> and
* James Abbatiello <abbeyj@wpi.edu>
* Copyright 2000 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* NOTE
*
* This code was audited for completeness against the documented features
* of Comctl32.dll version 6.0 on Oct. 20, 2004, by Dimitrie O. Paun.
*
* Unless otherwise noted, we believe this code to be complete, as per
* the specification mentioned above.
* If you discover missing features, or bugs, please note them below.
*
* TODO:
* -- MCM_[GS]ETUNICODEFORMAT
* -- MONTHCAL_GetMonthRange
* -- handle resources better (doesn't work now);
* -- take care of internationalization.
* -- keyboard handling.
* -- GetRange: At the moment, we copy ranges anyway, regardless of
* infoPtr->rangeValid; an invalid range is simply filled
* with zeros in SetRange. Is this the right behavior?
* -- search for FIXME
*/
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "commctrl.h"
#include "comctl32.h"
#include "uxtheme.h"
#include "tmschema.h"
#include "wine/unicode.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
#define MC_SEL_LBUTUP 1 /* Left button released */
#define MC_SEL_LBUTDOWN 2 /* Left button pressed in calendar */
#define MC_PREVPRESSED 4 /* Prev month button pressed */
#define MC_NEXTPRESSED 8 /* Next month button pressed */
#define MC_NEXTMONTHDELAY 350 /* when continuously pressing `next */
/* month', wait 500 ms before going */
/* to the next month */
#define MC_NEXTMONTHTIMER 1 /* Timer ID's */
#define MC_PREVMONTHTIMER 2
#define countof(arr) (sizeof(arr)/sizeof(arr[0]))
typedef struct
{
HWND hwndSelf;
COLORREF bk;
COLORREF txt;
COLORREF titlebk;
COLORREF titletxt;
COLORREF monthbk;
COLORREF trailingtxt;
HFONT hFont;
HFONT hBoldFont;
int textHeight;
int textWidth;
int height_increment;
int width_increment;
int firstDayplace; /* place of the first day of the current month */
int delta; /* scroll rate; # of months that the */
/* control moves when user clicks a scroll button */
int visible; /* # of months visible */
int firstDay; /* Start month calendar with firstDay's day */
int monthRange;
MONTHDAYSTATE *monthdayState;
SYSTEMTIME todaysDate;
DWORD currentMonth;
DWORD currentYear;
int status; /* See MC_SEL flags */
int curSelDay; /* current selected day */
int firstSelDay; /* first selected day */
int maxSelCount;
SYSTEMTIME minSel;
SYSTEMTIME maxSel;
DWORD rangeValid;
SYSTEMTIME minDate;
SYSTEMTIME maxDate;
RECT title; /* rect for the header above the calendar */
RECT titlebtnnext; /* the `next month' button in the header */
RECT titlebtnprev; /* the `prev month' button in the header */
RECT titlemonth; /* the `month name' txt in the header */
RECT titleyear; /* the `year number' txt in the header */
RECT wdays; /* week days at top */
RECT days; /* calendar area */
RECT weeknums; /* week numbers at left side */
RECT todayrect; /* `today: xx/xx/xx' text rect */
HWND hwndNotify; /* Window to receive the notifications */
HWND hWndYearEdit; /* Window Handle of edit box to handle years */
HWND hWndYearUpDown;/* Window Handle of updown box to handle years */
} MONTHCAL_INFO, *LPMONTHCAL_INFO;
/* Offsets of days in the week to the weekday of january 1 in a leap year */
static const int DayOfWeekTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
static const WCHAR themeClass[] = { 'S','c','r','o','l','l','b','a','r',0 };
#define MONTHCAL_GetInfoPtr(hwnd) ((MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0))
/* helper functions */
/* returns the number of days in any given month, checking for leap days */
/* january is 1, december is 12 */
int MONTHCAL_MonthLength(int month, int year)
{
const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
/*Wrap around, this eases handling*/
if(month == 0)
month = 12;
if(month == 13)
month = 1;
/* if we have a leap year add 1 day to February */
/* a leap year is a year either divisible by 400 */
/* or divisible by 4 and not by 100 */
if(month == 2) { /* February */
return mdays[month - 1] + ((year%400 == 0) ? 1 : ((year%100 != 0) &&
(year%4 == 0)) ? 1 : 0);
}
else {
return mdays[month - 1];
}
}
/* make sure that time is valid */
static int MONTHCAL_ValidateTime(SYSTEMTIME time)
{
if(time.wMonth < 1 || time.wMonth > 12 ) return FALSE;
if(time.wDayOfWeek > 6) return FALSE;
if(time.wDay > MONTHCAL_MonthLength(time.wMonth, time.wYear))
return FALSE;
return TRUE;
}
/* Note:Depending on DST, this may be offset by a day.
Need to find out if we're on a DST place & adjust the clock accordingly.
Above function assumes we have a valid data.
Valid for year>1752; 1 <= d <= 31, 1 <= m <= 12.
0 = Sunday.
*/
/* returns the day in the week(0 == sunday, 6 == saturday) */
/* day(1 == 1st, 2 == 2nd... etc), year is the year value */
static int MONTHCAL_CalculateDayOfWeek(DWORD day, DWORD month, DWORD year)
{
year-=(month < 3);
return((year + year/4 - year/100 + year/400 +
DayOfWeekTable[month-1] + day ) % 7);
}
/* From a given point, calculate the row (weekpos), column(daypos)
and day in the calendar. day== 0 mean the last day of tha last month
*/
static int MONTHCAL_CalcDayFromPos(MONTHCAL_INFO *infoPtr, int x, int y,
int *daypos,int *weekpos)
{
int retval, firstDay;
RECT rcClient;
GetClientRect(infoPtr->hwndSelf, &rcClient);
/* if the point is outside the x bounds of the window put
it at the boundary */
if (x > rcClient.right)
x = rcClient.right;
*daypos = (x - infoPtr->days.left ) / infoPtr->width_increment;
*weekpos = (y - infoPtr->days.top ) / infoPtr->height_increment;
firstDay = (MONTHCAL_CalculateDayOfWeek(1, infoPtr->currentMonth, infoPtr->currentYear)+6 - infoPtr->firstDay)%7;
retval = *daypos + (7 * *weekpos) - firstDay;
return retval;
}
/* day is the day of the month, 1 == 1st day of the month */
/* sets x and y to be the position of the day */
/* x == day, y == week where(0,0) == firstDay, 1st week */
static void MONTHCAL_CalcDayXY(MONTHCAL_INFO *infoPtr, int day, int month,
int *x, int *y)
{
int firstDay, prevMonth;
firstDay = (MONTHCAL_CalculateDayOfWeek(1, infoPtr->currentMonth, infoPtr->currentYear) +6 - infoPtr->firstDay)%7;
if(month==infoPtr->currentMonth) {
*x = (day + firstDay) % 7;
*y = (day + firstDay - *x) / 7;
return;
}
if(month < infoPtr->currentMonth) {
prevMonth = month - 1;
if(prevMonth==0)
prevMonth = 12;
*x = (MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear) - firstDay) % 7;
*y = 0;
return;
}
*y = MONTHCAL_MonthLength(month, infoPtr->currentYear - 1) / 7;
*x = (day + firstDay + MONTHCAL_MonthLength(month,
infoPtr->currentYear)) % 7;
}
/* x: column(day), y: row(week) */
static void MONTHCAL_CalcDayRect(MONTHCAL_INFO *infoPtr, RECT *r, int x, int y)
{
r->left = infoPtr->days.left + x * infoPtr->width_increment;
r->right = r->left + infoPtr->width_increment;
r->top = infoPtr->days.top + y * infoPtr->height_increment;
r->bottom = r->top + infoPtr->textHeight;
}
/* sets the RECT struct r to the rectangle around the day and month */
/* day is the day value of the month(1 == 1st), month is the month */
/* value(january == 1, december == 12) */
static inline void MONTHCAL_CalcPosFromDay(MONTHCAL_INFO *infoPtr,
int day, int month, RECT *r)
{
int x, y;
MONTHCAL_CalcDayXY(infoPtr, day, month, &x, &y);
MONTHCAL_CalcDayRect(infoPtr, r, x, y);
}
/* day is the day in the month(1 == 1st of the month) */
/* month is the month value(1 == january, 12 == december) */
static void MONTHCAL_CircleDay(MONTHCAL_INFO *infoPtr, HDC hdc, int day, int month)
{
HPEN hRedPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
HPEN hOldPen2 = SelectObject(hdc, hRedPen);
POINT points[13];
int x, y;
RECT day_rect;
MONTHCAL_CalcPosFromDay(infoPtr, day, month, &day_rect);
x = day_rect.left;
y = day_rect.top;
points[0].x = x;
points[0].y = y - 1;
points[1].x = x + 0.8 * infoPtr->width_increment;
points[1].y = y - 1;
points[2].x = x + 0.9 * infoPtr->width_increment;
points[2].y = y;
points[3].x = x + infoPtr->width_increment;
points[3].y = y + 0.5 * infoPtr->height_increment;
points[4].x = x + infoPtr->width_increment;
points[4].y = y + 0.9 * infoPtr->height_increment;
points[5].x = x + 0.6 * infoPtr->width_increment;
points[5].y = y + 0.9 * infoPtr->height_increment;
points[6].x = x + 0.5 * infoPtr->width_increment;
points[6].y = y + 0.9 * infoPtr->height_increment; /* bring the bottom up just
a hair to fit inside the day rectangle */
points[7].x = x + 0.2 * infoPtr->width_increment;
points[7].y = y + 0.8 * infoPtr->height_increment;
points[8].x = x + 0.1 * infoPtr->width_increment;
points[8].y = y + 0.8 * infoPtr->height_increment;
points[9].x = x;
points[9].y = y + 0.5 * infoPtr->height_increment;
points[10].x = x + 0.1 * infoPtr->width_increment;
points[10].y = y + 0.2 * infoPtr->height_increment;
points[11].x = x + 0.2 * infoPtr->width_increment;
points[11].y = y + 0.3 * infoPtr->height_increment;
points[12].x = x + 0.4 * infoPtr->width_increment;
points[12].y = y + 0.2 * infoPtr->height_increment;
PolyBezier(hdc, points, 13);
DeleteObject(hRedPen);
SelectObject(hdc, hOldPen2);
}
static void MONTHCAL_DrawDay(MONTHCAL_INFO *infoPtr, HDC hdc, int day, int month,
int x, int y, int bold)
{
static const WCHAR fmtW[] = { '%','d',0 };
WCHAR buf[10];
RECT r;
static int haveBoldFont, haveSelectedDay = FALSE;
HBRUSH hbr;
COLORREF oldCol = 0;
COLORREF oldBk = 0;
wsprintfW(buf, fmtW, day);
/* No need to check styles: when selection is not valid, it is set to zero.
* 1<day<31, so evertyhing's OK.
*/
MONTHCAL_CalcDayRect(infoPtr, &r, x, y);
if((day>=infoPtr->minSel.wDay) && (day<=infoPtr->maxSel.wDay)
&& (month==infoPtr->currentMonth)) {
HRGN hrgn;
RECT r2;
TRACE("%d %d %d\n",day, infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
TRACE("%d %d %d %d\n", r.left, r.top, r.right, r.bottom);
oldCol = SetTextColor(hdc, infoPtr->monthbk);
oldBk = SetBkColor(hdc, infoPtr->trailingtxt);
hbr = GetSysColorBrush(COLOR_GRAYTEXT);
hrgn = CreateEllipticRgn(r.left, r.top, r.right, r.bottom);
FillRgn(hdc, hrgn, hbr);
/* FIXME: this may need to be changed now b/c of the other
drawing changes 11/3/99 CMM */
r2.left = r.left - 0.25 * infoPtr->textWidth;
r2.top = r.top;
r2.right = r.left + 0.5 * infoPtr->textWidth;
r2.bottom = r.bottom;
if(haveSelectedDay) FillRect(hdc, &r2, hbr);
haveSelectedDay = TRUE;
} else {
haveSelectedDay = FALSE;
}
/* need to add some code for multiple selections */
if((bold) &&(!haveBoldFont)) {
SelectObject(hdc, infoPtr->hBoldFont);
haveBoldFont = TRUE;
}
if((!bold) &&(haveBoldFont)) {
SelectObject(hdc, infoPtr->hFont);
haveBoldFont = FALSE;
}
if(haveSelectedDay) {
SetTextColor(hdc, oldCol);
SetBkColor(hdc, oldBk);
}
SetBkMode(hdc,TRANSPARENT);
DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
/* draw a rectangle around the currently selected days text */
if((day==infoPtr->curSelDay) && (month==infoPtr->currentMonth))
DrawFocusRect(hdc, &r);
}
static void paint_button (MONTHCAL_INFO *infoPtr, HDC hdc, BOOL btnNext,
BOOL pressed, RECT* r)
{
HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
if (theme)
{
static const int states[] = {
/* Prev button */
ABS_LEFTNORMAL, ABS_LEFTPRESSED, ABS_LEFTDISABLED,
/* Next button */
ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, ABS_RIGHTDISABLED
};
int stateNum = btnNext ? 3 : 0;
if (pressed)
stateNum += 1;
else
{
DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
if (dwStyle & WS_DISABLED) stateNum += 2;
}
DrawThemeBackground (theme, hdc, SBP_ARROWBTN, states[stateNum], r, NULL);
}
else
{
int style = btnNext ? DFCS_SCROLLRIGHT : DFCS_SCROLLLEFT;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -